cxjz2.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import json
  4. from typing import TYPE_CHECKING
  5. from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT
  6. from apps.web.core.adapter.base import SmartBox
  7. from apps.web.core.device_define.cxjz import SetResponse, CXJZDeviceSettingsValidator, ChargeMode, DEFAULT_REFUND_PROTECTION_TIME, DEFAULT_SERVICE_FEE, DEFAULT_REFUND_CAL_BASE
  8. from apps.web.core.exceptions import ServiceException
  9. from apps.web.core.networking import MessageSender
  10. from apps.web.device.models import Device
  11. if TYPE_CHECKING:
  12. from apps.web.user.models import ConsumeRecord
  13. class ChargingCXJZBox(SmartBox):
  14. @staticmethod
  15. def _convert_consume_type(_type):
  16. if _type == "AF":
  17. return "fault"
  18. elif _type == "01":
  19. return "card"
  20. elif _type == "02":
  21. return "coin"
  22. elif _type == "03":
  23. return "test"
  24. else:
  25. return "mobile"
  26. @staticmethod
  27. def _parse_port_status(data):
  28. """ 0A 00 0000 0000 0000 """
  29. port, consumeType, leftTime, leftElec, power = int(data[: 2], 16), data[2: 4], int(data[4: 8], 16), int(data[8: 12], 16), int(data[12: 16], 16)
  30. if not leftElec and not leftTime:
  31. return {"port": port, "status": Const.DEV_WORK_STATUS_IDLE }
  32. else:
  33. return {"port": port,"status": Const.DEV_WORK_STATUS_WORKING,
  34. "consumeType": ChargingCXJZBox._convert_consume_type(consumeType), "leftTime": leftTime, "leftElec": leftElec, "power": power}
  35. def _send_data(self, funCode, data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=MQTT_TIMEOUT.NORMAL):
  36. result = MessageSender.send(
  37. device = self.device,
  38. cmd = cmd,
  39. payload = {
  40. "IMEI": self._device["devNo"],
  41. "funCode": funCode,
  42. "data": data
  43. },
  44. timeout = timeout
  45. )
  46. if "rst" in result and result.get("rst") != 0:
  47. if result.get("rst") == -1:
  48. raise ServiceException({"result": 2, "description": u"设备网络故障,请重新"})
  49. if result.get("rst") == 1:
  50. raise ServiceException({"result": 2, "description": u"充电桩无响应,请稍后再试试"})
  51. return result
  52. def _get_A0_data(self, data=None):
  53. """ 查询整机当前的工作状态 """
  54. if not data:
  55. data = self._send_data("A0", "01FFFF")["data"]
  56. result = list()
  57. for _index in range(0, len(data[10: -6]), 16):
  58. content = data[10: -6][_index: _index+16]
  59. result.append(self._parse_port_status(content))
  60. return result
  61. def _get_A1_data(self, port, data=None):
  62. """ 查询单个端口的运行状态 """
  63. if not data:
  64. data = self._send_data("A1", "{:02X}FFFF".format(port))["data"]
  65. return self._parse_port_status(data[10: -6])
  66. def _get_A2_data(self, data=None):
  67. """ 查询计币信息 """
  68. if not data:
  69. data = self._send_data("A2", "01FFFF")["data"]
  70. return {
  71. "cardCode": data[10: -6][4: 8],
  72. "cardCount": int(data[10: -6][8: 12], 16),
  73. "coinCount": int(data[10: -6][12: 16], 16),
  74. }
  75. def _get_A3_data(self, data=None):
  76. """ 查询工作模式 """
  77. if not data:
  78. data = self._send_data("A3", "01FFFF")["data"]
  79. return {
  80. "powerSwitch": bool(int(data[10: -6][0: 2], 16)),
  81. "chargeMode": int(data[10: -6][2: 4], 16),
  82. "maxPower": int(data[10: -6][4: 8], 16),
  83. "stopSwitch": bool(int(data[10: -6][8: 10], 16)),
  84. "coinUpperLimit": int(data[10: -6][10: 14], 16),
  85. "coinSwitch": bool(int(data[10: -6][14: 16], 16))
  86. }
  87. def _get_A4_data(self, data=None):
  88. """ 查询时间模式 """
  89. if not data:
  90. data = self._send_data("A4", "01FFFF")["data"]
  91. return {
  92. "fullStopTime": int(data[10: -4][0: 4], 16),
  93. "cardCoinElec": int(data[10: -4][4: 8], 16) / 10.0,
  94. "time1": int(data[10: -4][8: 12], 16),
  95. "coinTime": int(data[10: -4][12: 16], 16),
  96. "coinMax": int(data[10: -4][16: 18], 16)
  97. }
  98. def _get_A8_data(self, data=None):
  99. if not data:
  100. data = self._send_data("A8", "01FFFF")["data"]
  101. return {
  102. "power1": int(data[4: 8], 16),
  103. "time2": int(data[8: 12], 16),
  104. "power2": int(data[12: 16], 16),
  105. "time3": int(data[16: 20], 16),
  106. "power3": int(data[20: 24], 16),
  107. "time4": int(data[24: 28], 16)
  108. }
  109. def _get_A9_data(self, data=None):
  110. """ 查询空载设置 """
  111. if not data:
  112. data = self._send_data("A9", "01FFFF")["data"]
  113. return {
  114. "reportTime": int(data[10: -6][0: 2], 16),
  115. "reCheckTime": int(data[10: -6][2: 4], 16),
  116. "noLoadTime": int(data[10: -6][4: 8], 16),
  117. "fullPower": int(data[10: -6][8: 12], 16),
  118. "noLoadPower": int(data[10: -6][12: 16], 16)
  119. }
  120. def _get_AE_data(self, data=None):
  121. """ 获取温度烟感设置 """
  122. if not data:
  123. data = self._send_data("AE", "01FFFF")["data"]
  124. return {
  125. "temperature": int(data[10: -6][4: 8], 16),
  126. "smoke": int(data[10: -6][8: 12], 16),
  127. "warning": bool(int(data[10: -6][12: 14], 16)),
  128. "smokeSwitch": bool(int(data[10: -6][14: 16], 16))
  129. }
  130. def _get_server_config(self):
  131. """
  132. 读取服务器的参数配置
  133. """
  134. otherConf = self.device.get("otherConf")
  135. return {
  136. "refundProtectionTime": otherConf.get("refundProtectionTime", DEFAULT_REFUND_PROTECTION_TIME),
  137. "serviceFee": otherConf.get("serviceFee", DEFAULT_SERVICE_FEE),
  138. "refundCalBase": otherConf.get("refundCalBase", DEFAULT_REFUND_CAL_BASE)
  139. }
  140. def _set_AF(self):
  141. self._send_data("AF", "01FFFF", cmd=220)
  142. return True
  143. def _set_B1(self, devCode=None):
  144. """ 设置设备编号 不建议使用 桩编号默认000000 """
  145. if not devCode:
  146. devCode = self.device.logicalCode.replace("G", "")
  147. self._send_data("B1", devCode, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  148. return True
  149. def _set_B0(self):
  150. """ 清除计币信息 """
  151. data = self._send_data("B0", "01FFFF")["data"]
  152. return data[2: 4] == SetResponse.SUCCESS
  153. def _set_B2(self, cardCode):
  154. """ 设置设备区号 即刷卡编号 """
  155. data = self._send_data("B2", "01{}".format(cardCode))["data"]
  156. return data[2: 4] == SetResponse.SUCCESS
  157. def _set_B3(self, chargeMode):
  158. data = self._send_data("B3", "01{:04X}".format(chargeMode))["data"]
  159. return data[2: 4] == SetResponse.SUCCESS
  160. def _set_B4(self, maxPower):
  161. data = self._send_data("B4", "01{:04X}".format(maxPower))["data"]
  162. return data[2: 4] == SetResponse.SUCCESS
  163. def _set_B5(self, stopSwitch):
  164. data = self._send_data("B5", "0100{:02X}".format(stopSwitch))["data"]
  165. return data[2: 4] == SetResponse.SUCCESS
  166. def _set_B6(self, coinSwitch):
  167. data = self._send_data("B6", "0100{:02X}".format(coinSwitch))["data"]
  168. return data[2: 4] == SetResponse.SUCCESS
  169. def _set_B7(self, coinUpperLimit):
  170. data = self._send_data("B7", "01{:04X}".format(coinUpperLimit))["data"]
  171. return data[2: 4] == SetResponse.SUCCESS
  172. def _set_B8(self, stopTime):
  173. data = self._send_data("B8", "01{:04X}".format(stopTime))["data"]
  174. return data[2: 4] == SetResponse.SUCCESS
  175. def _set_B9(self, fullPower):
  176. data = self._send_data("B9", "01{:04X}".format(fullPower))["data"]
  177. return data[2: 4] == SetResponse.SUCCESS
  178. def _set_BA(self, noLoadPower):
  179. data = self._send_data("BA", "01{:04X}".format(noLoadPower))["data"]
  180. return data[2: 4] == SetResponse.SUCCESS
  181. def _set_C1(self, cardTime):
  182. data = self._send_data("C1", "01{:04X}".format(cardTime))["data"]
  183. return data[2: 4] == SetResponse.SUCCESS
  184. def _set_C2(self, cardCoinElec):
  185. data = self._send_data("C2", "01{:04X}".format(cardCoinElec))["data"]
  186. return data[2: 4] == SetResponse.SUCCESS
  187. def _set_C3(self, coinTime):
  188. data = self._send_data("C3", "01{:04X}".format(coinTime))["data"]
  189. return data[2: 4] == SetResponse.SUCCESS
  190. def _set_C4(self, coinMax):
  191. data = self._send_data("C4", "01{:04X}".format(coinMax))["data"]
  192. return data[2: 4] == SetResponse.SUCCESS
  193. def _set_C5(self, reportTime):
  194. data = self._send_data("C5", "01{:04X}".format(reportTime))["data"]
  195. return data[2: 4] == SetResponse.SUCCESS
  196. def _set_C6(self, smokeSwitch):
  197. data = self._send_data("C6", "0100{:02X}".format(smokeSwitch))["data"]
  198. return data[2: 4] == SetResponse.SUCCESS
  199. def _set_C7(self, reCheckTime):
  200. data = self._send_data("C7", "01{:04X}".format(reCheckTime))["data"]
  201. return data[2: 4] == SetResponse.SUCCESS
  202. def _set_C9(self, powerSwitch):
  203. data = self._send_data("C9", "01{:04X}".format(powerSwitch))["data"]
  204. return data[2: 4] == SetResponse.SUCCESS
  205. def _set_C8_A1(self, time1):
  206. data = self._send_data("C8", "01{:04X}A1".format(time1))["data"]
  207. return data[2: 4] == SetResponse.SUCCESS
  208. def _set_C8_A2(self, time2):
  209. data = self._send_data("C8", "01{:04X}A2".format(time2))["data"]
  210. return data[2: 4] == SetResponse.SUCCESS
  211. def _set_C8_A3(self, time3):
  212. data = self._send_data("C8", "01{:04X}A3".format(time3))["data"]
  213. return data[2: 4] == SetResponse.SUCCESS
  214. def _set_C8_A4(self, time4):
  215. data = self._send_data("C8", "01{:04X}A4".format(time4))["data"]
  216. return data[2: 4] == SetResponse.SUCCESS
  217. def _set_C8_C1(self, power1):
  218. data = self._send_data("C8", "01{:04X}C1".format(power1))["data"]
  219. return data[2: 4] == SetResponse.SUCCESS
  220. def _set_C8_C2(self, power2):
  221. data = self._send_data("C8", "01{:04X}C2".format(power2))["data"]
  222. return data[2: 4] == SetResponse.SUCCESS
  223. def _set_C8_C3(self, power3):
  224. data = self._send_data("C8", "01{:04X}C3".format(power3))["data"]
  225. return data[2: 4] == SetResponse.SUCCESS
  226. def _set_D0(self):
  227. data = self._send_data("D0", "01FFFF")["data"]
  228. return data[2: 4] == SetResponse.SUCCESS
  229. def _set_D1(self):
  230. data = self._send_data("D1", "01FFFF")["data"]
  231. return data[2: 4] == SetResponse.SUCCESS
  232. def _time_start(self, port, needTime):
  233. return self._send_data("CA", "{:02X}{:04X}0000".format(port, needTime), cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  234. def _elec_start(self, port, needElec):
  235. return self._send_data("CA", "{:02X}0000{:04X}".format(port, needElec), cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  236. def _power_start(self, port, coins):
  237. return self._send_data("CC", "{:02X}{:04X}0000".format(port, coins), cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  238. def _stop(self, port):
  239. return self._send_data("FD", "{:02X}".format(port))
  240. def _get_all_settings(self):
  241. """ 自定义指令 获取所有的参数设置 """
  242. data = self._send_data("FA", "", timeout=5)["data"]
  243. content = data[10: -6]
  244. result = dict()
  245. result.update(self._get_A2_data(content[: 32]))
  246. result.update(self._get_A3_data(content[32: 64]))
  247. result.update(self._get_A4_data(content[64: 96]))
  248. result.update(self._get_A8_data(content[96: 128]))
  249. result.update(self._get_A9_data(content[128: 160]))
  250. result.update(self._get_AE_data(content[160: 192]))
  251. return result
  252. def _set_all_settings(self, **kwargs):
  253. """ 自定义指令 设置所有的参数设置 """
  254. result = self._send_data("FB", "{:4}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}{:04X}".format(
  255. kwargs["cardCode"],
  256. kwargs["chargeMode"],
  257. kwargs["maxPower"],
  258. kwargs["stopSwitch"],
  259. kwargs["coinSwitch"],
  260. kwargs["coinUpperLimit"],
  261. kwargs["noLoadTime"],
  262. kwargs["fullPower"],
  263. kwargs["noLoadPower"],
  264. kwargs["fullStopTime"],
  265. kwargs["time1"],
  266. kwargs["cardCoinElec"],
  267. kwargs["coinTime"],
  268. kwargs["coinMax"],
  269. kwargs["reportTime"],
  270. kwargs["smokeSwitch"],
  271. kwargs["reCheckTime"],
  272. kwargs["time1"],
  273. kwargs["time2"],
  274. kwargs["time3"],
  275. kwargs["time4"],
  276. kwargs["power1"],
  277. kwargs["power2"],
  278. kwargs["power3"],
  279. kwargs["powerSwitch"],
  280. ))
  281. return result
  282. @staticmethod
  283. def _parse_finished(data):
  284. return {
  285. "reason": data[2: 4],
  286. "port": data[10: 12],
  287. "consumeType": ChargingCXJZBox._convert_consume_type(data[12: 14]),
  288. "leftTime": data[14: 18],
  289. "leftElec": data[18: 22],
  290. "power": data[22: 26]
  291. }
  292. def _parse_fault(self, data):
  293. return self._get_AE_data(data)
  294. def _check_package(self, package):
  295. otherConf = self.device.get("otherConf")
  296. chargeMode = otherConf.get("chargeMode")
  297. if not chargeMode:
  298. chargeMode = self._get_A3_data()["chargeMode"]
  299. chargeElec, chargeCoins = 0, 0
  300. # 首先是时间模式下
  301. if chargeMode == ChargeMode.TIME:
  302. if package["unit"] != u"次":
  303. raise ServiceException({"result": 2, "description": u"启动失败,套餐单位错误(1001)"})
  304. chargeCoins = int(package["coins"])
  305. elif chargeMode == ChargeMode.ELEC:
  306. if package["unit"] != u"度":
  307. raise ServiceException({"result": 2, "description": u"启动失败,套餐单位错误(1002)"})
  308. chargeElec = int(float(package["time"]) * 10)
  309. else:
  310. raise ServiceException({"result": 2, "description": u"启动失败,工作模式错误(2001)"})
  311. return chargeMode, chargeElec, chargeCoins
  312. def analyze_event_data(self, data):
  313. funCode = data[2: 4]
  314. if funCode == "AE":
  315. result = self._parse_finished(data)
  316. else:
  317. result = self._parse_fault(data)
  318. result["cmdCode"] = funCode
  319. return result
  320. def start_device_realiable(self, order): # type:(ConsumeRecord)->dict
  321. chargeMode, chargeElec, chargeCoins = self._check_package(order.package)
  322. port = int(order.attachParas["chargeIndex"])
  323. data = {
  324. "funCode": "FC",
  325. "order_id": order.orderNo,
  326. "port": port,
  327. "open_id": order.openId,
  328. "charge_mode": chargeMode,
  329. "coins": chargeCoins,
  330. "time": 0, # 该参数已经废弃 不再需要纯时间模式的启动
  331. "elec": chargeElec
  332. }
  333. result = MessageSender.send(
  334. device = self.device,
  335. cmd = DeviceCmdCode.OPERATE_DEV_SYNC,
  336. payload = data,
  337. timeout = 120
  338. )
  339. return result
  340. def get_dev_setting(self):
  341. data = self._get_all_settings()
  342. data["coinTime"] = data["time1"] - data["coinTime"]
  343. serverData = self._get_server_config()
  344. data.update(serverData)
  345. return data
  346. def set_device_function(self, request, lastSetConf):
  347. if "cleanCoins" in lastSetConf:
  348. self._set_B0()
  349. if "testDevice" in lastSetConf:
  350. self._set_AF()
  351. if "lockDevice" in lastSetConf:
  352. self._set_D0()
  353. if "unLockDevice" in lastSetConf:
  354. self._set_D1()
  355. def set_device_function_param(self, request, lastSetConf):
  356. validator = CXJZDeviceSettingsValidator(request.POST)
  357. if not validator.is_valid():
  358. raise ServiceException({"result": 2, "description": json.dumps(validator.str_errors).decode("unicode-escape")})
  359. data = validator.suit_data()
  360. # 先保存一份数据库
  361. otherConf = self.device.get("otherConf")
  362. otherConf.update(data)
  363. Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf)
  364. Device.invalid_device_cache(self.device.devNo)
  365. self._set_all_settings(**data)
  366. def get_port_status_from_dev(self):
  367. result = self._get_A0_data()
  368. portDict = dict()
  369. # 做一次数据类型转换
  370. for _item in result:
  371. _port = str(_item.pop("port"))
  372. portDict[_port] = _item
  373. allPorts, usedPorts, usePorts = self.get_port_static_info(portDict)
  374. Device.update_dev_control_cache(self.device.devNo, {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts})
  375. devCache = Device.get_dev_control_cache(self.device.devNo)
  376. for _portStr, _info in portDict.items():
  377. if _portStr in devCache:
  378. devCache[_portStr].update(_info)
  379. else:
  380. devCache[_portStr] = _info
  381. Device.update_dev_control_cache(self.device.devNo, devCache)
  382. return portDict
  383. def get_port_info(self, port):
  384. return self._get_A1_data(int(port))
  385. def get_port_status(self, force = False):
  386. if force:
  387. self.get_port_status_from_dev()
  388. devCache = Device.get_dev_control_cache(self.device.devNo)
  389. portStatus = dict()
  390. for _k, _v in devCache.items():
  391. if isinstance(_k, (str, unicode)) and _k.isdigit():
  392. portStatus[_k] = {"status": _v.get("status", Const.DEV_WORK_STATUS_IDLE)}
  393. return portStatus
  394. def active_deactive_port(self, port, active):
  395. if not active:
  396. self._stop(port)