nanjiguang.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. # -*- coding: utf-8 -*-
  2. #!/usr/bin/env python
  3. import time
  4. from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT
  5. from apps.web.core.adapter.base import SmartBox, reverse_hex, hexbyte_2_bin, fill_2_hexByte, pack_float, unpack_float
  6. from apps.web.core.exceptions import ServiceException
  7. from apps.web.core.networking import MessageSender
  8. from apps.web.device.models import Device
  9. class FunCode(object):
  10. SET_POWER_MAX = "02"
  11. GET_POWER_MAX = ""
  12. GET_POWER_MIN = ""
  13. SET_POWER_MIN = "16"
  14. GET_DEV_STATUS = "14"
  15. START_CHARGING = "06"
  16. REBOOT_DEV = "0A"
  17. class ChargingNanjiguangBox(SmartBox):
  18. MAX_CHARGING_TIME = 2**32 - 1
  19. MAX_CHARGING_ELEC = 999.0
  20. DEFAULT_POWER_MAX_LIMIT = 1000
  21. DEFAULT_DEVICE_MIN_LIMIT = 50
  22. STATUS_MAP = {
  23. "00": Const.DEV_WORK_STATUS_IDLE,
  24. "01": Const.DEV_WORK_STATUS_WORKING,
  25. "02": Const.DEV_WORK_STATUS_FAULT,
  26. "03": Const.DEV_WORK_STATUS_FAULT
  27. }
  28. def _send_data(self, funcCode, sendData=None, cmd=None, timeout=MQTT_TIMEOUT.NORMAL,orderNo=None):
  29. """
  30. 发送 报文
  31. :param funcCode: 串口命令
  32. :param sendData: 发送数据
  33. :param cmd: 报文命令
  34. :param timeout: 超时时间
  35. :return:
  36. """
  37. if sendData is None:
  38. sendData = ""
  39. if cmd is None:
  40. cmd = DeviceCmdCode.OPERATE_DEV_SYNC
  41. if timeout is None:
  42. timeout = MQTT_TIMEOUT.NORMAL
  43. result = MessageSender.send(device = self.device, cmd = cmd, payload = {
  44. "IMEI": self._device["devNo"],
  45. "funCode": funcCode,
  46. "data": sendData
  47. }, timeout = timeout)
  48. if "rst" in result:
  49. if result["rst"] == -1:
  50. raise ServiceException({'result': 2, 'description': u'网络不通畅,请稍候再试'})
  51. elif result["rst"] == 1:
  52. raise ServiceException({'result': 2, 'description': u'主板连接故障,请稍后再试'})
  53. elif result["rst"] == 0:
  54. return result
  55. else:
  56. raise ServiceException({'result': 2, 'description': u'系统错误'})
  57. else:
  58. raise ServiceException({'result': 2, 'description': u'系统错误'})
  59. @staticmethod
  60. def _parse_result_data(result):
  61. """
  62. 报文返回结果 返还命令字(1) + 数据长度(2) + 数据域(n) 低前高后
  63. :param result: 报文回执
  64. :return:
  65. """
  66. data = result.get("data")
  67. receiveCmd = data[: 2]
  68. receiveLen = int(reverse_hex(data[2: 6]), 16)
  69. return receiveCmd, receiveLen, data[6: ]
  70. @staticmethod
  71. def _parse_card(cardNo):
  72. return cardNo
  73. def _get_device_status(self):
  74. """
  75. 从设备端获取设备端口状态 烟感报警 最大功率等
  76. :return:
  77. """
  78. result = self._send_data(funcCode=FunCode.GET_DEV_STATUS)
  79. _, __ , data = self._parse_result_data(result)
  80. # 烟感报警字段 暂时不用
  81. smokeWarning = hexbyte_2_bin(data[: 2])[0]
  82. portStatusData = data[2: -4]
  83. powerMax = data[-4: ]
  84. portStatus = dict()
  85. portNum = 1
  86. while portStatusData:
  87. tempStatus = portStatusData[: 2]
  88. portStatus[str(portNum)] = {"status": self.STATUS_MAP.get(tempStatus)}
  89. portStatusData = portStatusData[2: ]
  90. portNum += 1
  91. return portStatus
  92. def _charging(self, port, chargeType, timeStamp, maxTime, maxPower,orderNo=None):
  93. """
  94. 授权充电
  95. :param port: 端口号
  96. :param chargeType: 充电类型
  97. :param timeStamp: 时间戳
  98. :param maxTime: 最大时间
  99. :param maxPower: 最大电量
  100. :return:
  101. """
  102. portHex = fill_2_hexByte(hex(int(port)), 2)
  103. chargeHex = fill_2_hexByte(hex(int(chargeType)), 2)
  104. timeHex = fill_2_hexByte(hex(int(timeStamp)), 8, reverse=True)
  105. maxTimeHex = fill_2_hexByte(hex(int(maxTime)), 8, reverse=True)
  106. maxPowerHex = pack_float(maxPower, reverse=True)
  107. dataHex = portHex + chargeHex + timeHex + maxTimeHex + maxPowerHex
  108. result = self._send_data(funcCode = FunCode.START_CHARGING, sendData = dataHex,
  109. timeout = MQTT_TIMEOUT.START_DEVICE, orderNo = orderNo)
  110. _, __, data = self._parse_result_data(result)
  111. if data[10: 12] != "00":
  112. return
  113. return result
  114. def _trans_time(self, needTime, unit):
  115. """
  116. 根据所给的单位将时间转换成分钟
  117. :param needTime:
  118. :param unit:
  119. :return:
  120. """
  121. if unit == u"小时":
  122. needTime = needTime * 60 * 60
  123. elif unit == u"分钟":
  124. needTime = needTime * 60
  125. elif unit == u"秒":
  126. needTime = needTime
  127. else:
  128. needTime = None
  129. return needTime
  130. def _make_time_stamp(self):
  131. return int(time.time())
  132. def _reboot(self):
  133. """
  134. 重启设备
  135. :return:
  136. """
  137. self._send_data(funcCode=FunCode.REBOOT_DEV)
  138. def _set_port_max_power(self, powerDict):
  139. """
  140. 设置端口 功率上限
  141. :param powerDict: 如果不存在power则是设置为FF 禁用端口
  142. :return:
  143. """
  144. powerHex = ""
  145. baseName = "portPower{port}"
  146. for i in xrange(10):
  147. power = powerDict.get(baseName.format(port=str(i+1)))
  148. if power is None:
  149. powerHex += "FFFFFFFF"
  150. continue
  151. powerHex += fill_2_hexByte(hex(int(power)), 8, reverse=True)
  152. self._send_data(funcCode=FunCode.SET_POWER_MAX, sendData=powerHex)
  153. def _set_device_power_min(self, minPower):
  154. """
  155. 设置最小充电功率
  156. :param minPower:
  157. :return:
  158. """
  159. minPowerHex = fill_2_hexByte(hex(int(minPower)), 4, reverse=True)
  160. self._send_data(funcCode=FunCode.SET_POWER_MIN, sendData=minPowerHex)
  161. def _get_port_max_power(self):
  162. """
  163. 获取 端口 功率上限
  164. :return:
  165. """
  166. otherConf = self._device.get("otherConf", {})
  167. baseName = "portPower{port}"
  168. portMaxDict = {
  169. baseName.format(port=str(i+1)): otherConf.get(baseName.format(port=str(i+1)), self.DEFAULT_POWER_MAX_LIMIT) for i in xrange(10)
  170. }
  171. return portMaxDict
  172. def _get_device_min_power(self):
  173. """
  174. 获取 设备 停止充电功率
  175. :return:
  176. """
  177. otherConf = self._device.get("otherConf", {})
  178. devMinPower = otherConf.get("devMinPower", self.DEFAULT_DEVICE_MIN_LIMIT)
  179. return {"devMinPower": devMinPower}
  180. def start_device(self, package, openId, attachParas):
  181. """
  182. 启动设备
  183. :param package:
  184. :param openId:
  185. :param attachParas:
  186. :return:
  187. """
  188. if "chargeIndex" not in attachParas:
  189. raise ServiceException({"result": 2, "description": u"未知端口,请联系经销商"})
  190. portStr = attachParas.get("chargeIndex")
  191. coins = package.get("coins")
  192. price = package.get("price")
  193. unit = package.get("unit")
  194. needTime = package.get("time")
  195. needTime = self._trans_time(needTime, unit)
  196. if needTime is None:
  197. raise ServiceException({"result": "2", "description": u"套餐错误,请联系经销商"})
  198. timeStamp = self._make_time_stamp()
  199. # TODO 还需要考虑充满自停的模式
  200. chargeType = "01"
  201. maxPower = self.MAX_CHARGING_ELEC
  202. result = self._charging(port=portStr, chargeType=chargeType, timeStamp=timeStamp, maxTime=needTime, maxPower=maxPower)
  203. if result is None:
  204. raise ServiceException({"result": 2, "description": u"设备启动失败"})
  205. finishedTime = timeStamp + needTime
  206. result.update({"finishedTime": finishedTime})
  207. dataDict = {
  208. "status": Const.DEV_WORK_STATUS_WORKING,
  209. "finishedTime": result['finishedTime'],
  210. "coins": coins,
  211. "price": price,
  212. "needTime": needTime / 60,
  213. "port": portStr,
  214. "chargeType": chargeType,
  215. "openId": openId,
  216. "vCardId": self._vcard_id,
  217. "isStart": True,
  218. "needElec": maxPower,
  219. }
  220. Device.update_dev_control_cache(self._device["devNo"], {str(portStr): dataDict})
  221. return result
  222. def get_port_status_from_dev(self):
  223. """
  224. :return:
  225. """
  226. portStatus = self._get_device_status()
  227. allPorts, usedPorts, usePorts = self.get_port_static_info(portStatus)
  228. Device.update_dev_control_cache(self._device['devNo'], {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts})
  229. # 逐个更新端口状态
  230. devCache = Device.get_dev_control_cache(self._device["devNo"])
  231. for portStr, portInfo in portStatus.items():
  232. if portStr in devCache:
  233. devCache[portStr].update(portInfo)
  234. else:
  235. devCache[portStr] = portInfo
  236. Device.update_dev_control_cache(self._device["devNo"], devCache)
  237. return portStatus
  238. def get_port_status(self, force = False):
  239. """
  240. view 对接函数
  241. :param force:
  242. :return:
  243. """
  244. if force:
  245. return self.get_port_status_from_dev()
  246. devCache = Device.get_dev_control_cache(self._device['devNo'])
  247. statusDict = {}
  248. if not 'allPorts' in devCache:
  249. self.get_port_status_from_dev()
  250. devCache = Device.get_dev_control_cache(self._device['devNo'])
  251. allPorts = devCache.get('allPorts', 10)
  252. for ii in range(allPorts):
  253. tempDict = devCache.get(str(ii + 1), {})
  254. if tempDict.has_key('status'):
  255. statusDict[str(ii + 1)] = {'status': tempDict.get('status')}
  256. elif tempDict.has_key('isStart'):
  257. if tempDict['isStart']:
  258. statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_WORKING}
  259. else:
  260. statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  261. else:
  262. statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  263. allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict)
  264. Device.update_dev_control_cache(self._device['devNo'], {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts})
  265. return statusDict
  266. def get_dev_setting(self):
  267. """
  268. 获取设备设置 对接view接口
  269. :return:
  270. """
  271. setConf = dict()
  272. portMaxPower = self._get_port_max_power()
  273. devMinPower = self._get_device_min_power()
  274. setConf.update(portMaxPower)
  275. setConf.update(devMinPower)
  276. # setConf.update({"portNum": len(portMaxPower.get("portMaxPower"))})
  277. return setConf
  278. def set_device_function_param(self, request, lastSetConf):
  279. devMinPower = request.POST.get("devMinPower")
  280. if devMinPower:
  281. # 更新缓存
  282. otherConf = self._device["otherConf"]
  283. otherConf.update({"devMinPower": devMinPower})
  284. Device.objects.filter(devNo=self._device["devNo"]).update(otherConf=otherConf)
  285. Device.get_and_update_device_cache(self._device["devNo"], otherConf=otherConf)
  286. self._set_device_power_min(devMinPower)
  287. # 端口功率上限设置
  288. portPower1 = request.POST.get("portPower1")
  289. if portPower1:
  290. baseName = "portPower{port}"
  291. portMaxDict = {
  292. baseName.format(port=str(i + 1)): request.POST.get(baseName.format(port=str(i + 1))) for i in xrange(10)
  293. }
  294. # 更新缓存
  295. otherConf = self._device["otherConf"]
  296. otherConf.update(portMaxDict)
  297. Device.objects.filter(devNo=self._device["devNo"]).update(otherConf=otherConf)
  298. Device.get_and_update_device_cache(self._device["devNo"], otherConf=otherConf)
  299. self._set_port_max_power(portMaxDict)
  300. def set_device_function(self, request, lastSetConf):
  301. if request.POST.get("reboot"):
  302. self._reboot()
  303. def lock_unlock_port(self, port, lock=True):
  304. """
  305. 禁用端口
  306. 经测试 设备不支持禁用端口
  307. :param port:
  308. :param lock:
  309. :return:
  310. """
  311. # devCache = Device.get_dev_control_cache(self._device["devNo"])
  312. # portCache = devCache.get(str(port), {})
  313. #
  314. # if lock and portCache.get("status", Const.DEV_WORK_STATUS_IDLE) != Const.DEV_WORK_STATUS_IDLE:
  315. # raise ServiceException({"result": "2", "description": u"端口非空闲不能设置禁用"})
  316. # if not lock and portCache.get("status", Const.DEV_WORK_STATUS_IDLE) != Const.DEV_WORK_STATUS_FORBIDDEN:
  317. # raise ServiceException({"result": "2", "description": u"端口非禁用不能设置解除禁用"})
  318. #
  319. # portName = "portPower{}".format(port)
  320. # portMaxPower = self._get_port_max_power()
  321. #
  322. # if lock:
  323. # portMaxPower[portName] = None
  324. # Device.update_dev_control_cache(self._device["devNo"], {str(port): {"status": Const.DEV_WORK_STATUS_IDLE}})
  325. #
  326. # else:
  327. # otherConf = self._device["otherConf"]
  328. # portMaxPower[portName] = otherConf.get(portName, self.DEFAULT_POWER_MAX_LIMIT)
  329. #
  330. # Device.update_dev_control_cache(self._device["devNo"], {str(port): {"status": Const.DEV_WORK_STATUS_FORBIDDEN}})
  331. #
  332. # self._set_port_max_power(portMaxPower)
  333. pass
  334. def analyze_event_data(self, data):
  335. """
  336. 解析上报的报文
  337. :param data:
  338. :return:
  339. """
  340. cmdCode = data[36: 38].upper()
  341. # 结束充电
  342. if cmdCode == "08":
  343. portStr = str(int(data[42: 44], 16))
  344. chargeType = data[44: 46]
  345. timeStamp = int(reverse_hex(data[46: 54]), 16)
  346. spendTime = int(reverse_hex(data[54: 62]), 16)
  347. # spendElec = int(reverse_hex(data[62: 80]), 16)
  348. spendElec = unpack_float(data[62: 70], reverse=True)
  349. return {
  350. "port": portStr,
  351. "chargeType": chargeType,
  352. "timeStamp": timeStamp,
  353. "spendTime": spendTime,
  354. "spendElec": spendElec,
  355. "cmdCode": cmdCode,
  356. "portHex": data[42: 44],
  357. "timeStampHex": data[46: 54]
  358. }
  359. # 刷卡充电开始
  360. elif cmdCode == "0C":
  361. portStr = str(int(data[42: 44], 16))
  362. chargeType = data[44: 46]
  363. timeStamp = int(reverse_hex(data[46: 54]), 16)
  364. cardNoLen = int(data[54: 56], 16) * 2
  365. cardNo = self._parse_card(data[56: 56+cardNoLen])
  366. coins = round(unpack_float(data[56+cardNoLen: 64+cardNoLen], reverse=True), 2)
  367. return {
  368. "port": portStr,
  369. "chargeType": chargeType,
  370. "timeStamp": timeStamp,
  371. "cardNo": cardNo,
  372. "coins": coins,
  373. "cmdCode": cmdCode,
  374. "portHex": data[42: 44],
  375. "timeStampHex": data[46: 54]
  376. }
  377. # 刷卡充电结束
  378. elif cmdCode == "0E":
  379. portStr = str(int(data[42: 44], 16))
  380. chargeType = data[44: 46]
  381. timeStamp = int(reverse_hex(data[46: 54]), 16)
  382. spendTime = int(reverse_hex(data[54: 62]), 16)
  383. spendElec = unpack_float(data[62: 70], reverse=True)
  384. return {
  385. "port": portStr,
  386. "chargeType": chargeType,
  387. "timeStamp": timeStamp,
  388. "spendTime": spendTime,
  389. "spendElec": spendElec,
  390. "cmdCode": cmdCode,
  391. "portHex": data[42: 44],
  392. "timeStampHex": data[46: 54]
  393. }
  394. # 推送的报警信息
  395. elif cmdCode == "12":
  396. timeStamp = int(reverse_hex(data[42: 50]), 16)
  397. warningCode = data[50: 52]
  398. return {
  399. "timeStamp": timeStamp,
  400. "warningCode": warningCode,
  401. "cmdCode": cmdCode,
  402. "timeStampHex": data[42: 50]
  403. }
  404. else:
  405. return