kehang.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import json
  4. import logging
  5. from apilib.monetary import RMB
  6. from apps.web.constant import DeviceCmdCode, MQTT_TIMEOUT
  7. from apps.web.core.adapter.base import SmartBox
  8. from apps.web.core.device_define.kehang import KeHangDeviceSettingsValidator, STATUS_MAP, BillingType
  9. from apps.web.core.exceptions import ServiceException
  10. from apps.web.core.networking import MessageSender
  11. from apps.web.device.models import Device
  12. from apps.web.user.models import ConsumeRecord
  13. logger = logging.getLogger(__name__)
  14. class KeHangBox(SmartBox):
  15. def _send_data(self, funCode, data = None, timeout = MQTT_TIMEOUT.NORMAL):
  16. if data is None:
  17. data = "00"
  18. result = MessageSender.send(
  19. device = self.device,
  20. cmd = DeviceCmdCode.OPERATE_DEV_SYNC,
  21. payload = {
  22. "IMEI": self._device["devNo"],
  23. "funCode": funCode,
  24. "data": data
  25. },
  26. timeout = timeout
  27. )
  28. if "rst" in result and result.get("rst") != 0:
  29. if result.get("rst") == -1:
  30. raise ServiceException({"result": 2, "description": u"设备网络故障,请重新"})
  31. if result.get("rst") == 1:
  32. raise ServiceException({"result": 2, "description": u"充电桩无响应,请稍后再试试"})
  33. return result
  34. def _get_port_status(self):
  35. result = self._send_data("01")
  36. data = result["data"]
  37. portNum = int(data[18: 20], 16)
  38. portStatus = dict()
  39. for _i in range(portNum):
  40. _port = str(_i + 1)
  41. _offset = 20 + _i * 2
  42. portStatus[_port] = data[_offset: _offset+2]
  43. return portStatus
  44. def _get_port_charge_info(self, port):
  45. """ 获取端口的充电信息 """
  46. data = "{:02X}".format(int(port))
  47. result = self._send_data("06", data)
  48. return {
  49. "port": str(int(result["data"][18:20], 16)),
  50. "left": int(result["data"][20: 24], 16),
  51. "power": int(result["data"][24: 28], 16)
  52. }
  53. def _get_consume_total(self):
  54. """ 获取总的消费数据 """
  55. result = self._send_data("07")
  56. return {
  57. "cardMoney": int(result["data"][18: 22], 16),
  58. "coinMoney": int(result["data"][22: 26], 16),
  59. "totalUsedTime": int(result["data"][26: 30], 16),
  60. "totalUsedElec": int(result["data"][30: 34], 16)
  61. }
  62. def _set_card_coin_time(self, maxPower, cardCost, time1, time2, time3):
  63. """ 设置最大功率 刷卡消费 以及投币相应的时间 """
  64. data = "{:04X}{:02X}{:04X}{:04X}{:04X}".format(maxPower, cardCost, time1, time2, time3)
  65. result = self._send_data("08", data)
  66. return result["data"][18: 20] == "01"
  67. def _set_card_coin_enable(self, isCardEnable, isCoinEnable):
  68. """ 设置刷卡投币使能 """
  69. data = "{:02X}{:02X}".format(isCardEnable, isCoinEnable)
  70. self._send_data("09", data)
  71. return True
  72. def _stop(self, port):
  73. """ 停止端口 """
  74. data = "{:02X}".format(port)
  75. result = self._send_data("0B", data)
  76. return {
  77. "port": str(int(result["data"][18: 20], 16)),
  78. "left": int(result["data"][20: 24], 16)
  79. }
  80. def _get_card_coin_time(self):
  81. result = self._send_data("0C")
  82. return {
  83. "maxPower": int(result["data"][18: 22], 16),
  84. "cardCost": int(result["data"][22: 24], 16),
  85. "coin1Time": int(result["data"][24: 28], 16),
  86. "coin2Time": int(result["data"][28: 32], 16),
  87. "coin3Time": int(result["data"][32: 36], 16),
  88. "isCardRefund": int(result["data"][36: 38], 16),
  89. "isAutoStop": int(result["data"][38: 40], 16)
  90. }
  91. def _set_stop_refund(self, isAutoStop, isCardRefund):
  92. data = "{:02X}{:02X}".format(isAutoStop, isCardRefund)
  93. self._send_data("13", data)
  94. return True
  95. def _set_power_step(self, power1, power2, power3, power4, power5):
  96. """ 设置5挡计费 """
  97. data = "".join(("{:04X}{:02X}".format(_x["power"], _x["ratio"]) for _x in [power1, power2, power3, power4, power5]))
  98. result = self._send_data("14", data)
  99. return result["data"][18: 20] == "01"
  100. def _get_power_step(self):
  101. result = self._send_data("15")
  102. return [
  103. {"power": int(result["data"][18: 24][:4], 16), "ratio": int(result["data"][18: 24][4:6], 16)},
  104. {"power": int(result["data"][24: 30][:4], 16), "ratio": int(result["data"][24: 30][4:6], 16)},
  105. {"power": int(result["data"][30: 36][:4], 16), "ratio": int(result["data"][30: 36][4:6], 16)},
  106. {"power": int(result["data"][36: 42][:4], 16), "ratio": int(result["data"][36: 42][4:6], 16)},
  107. {"power": int(result["data"][42: 48][:4], 16), "ratio": int(result["data"][42: 48][4:6], 16)},
  108. ]
  109. def _set_free_voice(self, isFree, voice):
  110. """ 设置免费充电以及音量调节 """
  111. data = "{:02X}{:02X}".format(isFree, voice)
  112. self._send_data("27", data)
  113. return True
  114. def _set_float_and_noload(self, floatPower, floatTime, noloadPower, noloadTime, minConsume=0):
  115. data = "{:04X}{:04X}{:04X}{:04X}{:02X}".format(floatPower, floatTime, noloadPower, noloadTime, minConsume)
  116. self._send_data("28", data)
  117. return True
  118. def _set_card_time(self, time1, time2, time3):
  119. """ 设置刷卡的充电时间 """
  120. data = "{:04X}{:04X}{:04X}".format(time1, time2, time3)
  121. result = self._send_data("29", data)
  122. return result["data"][18: 20] == "01"
  123. def _get_card_time(self):
  124. result = self._send_data("2A")
  125. return {
  126. "card1Time": int(result["data"][18: 22], 16),
  127. "card2Time": int(result["data"][22: 26], 16),
  128. "card3Time": int(result["data"][26: 30], 16),
  129. "isFree": int(result["data"][30: 32], 16),
  130. "voice": int(result["data"][32: 34], 16),
  131. "floatPower": int(result["data"][34: 38], 16),
  132. "floatTime": int(result["data"][38: 42], 16),
  133. "noloadPower": int(result["data"][42: 46], 16),
  134. "noloadTime": int(result["data"][46: 50], 16),
  135. "minConsume": int(result["data"][50: 52], 16)
  136. }
  137. def _get_device_params(self):
  138. """ 获取设备的动态参数 主板暂时没有实现电压值"""
  139. result = self._send_data("35")
  140. return {
  141. "temperature": int(result["data"][18: 20], 16),
  142. "smokeWarning": int(result["data"][20: 22], 16),
  143. "voltage": int(result["data"][22: 24], 16)
  144. }
  145. def _get_consume_type(self):
  146. """ 读取设备所有的参数设置 协议有误 只是为了获取消费方式和消费流程"""
  147. result = self._send_data("36")
  148. return {
  149. "consumeType": int(result["data"][-6: -4], 16),
  150. "billingType": int(result["data"][-4: -2], 16),
  151. }
  152. def _set_consume_type(self, consumeType, billingType):
  153. """ 设置消费流程(先后按键)以及 计费方式(时间/电量) """
  154. data = "{:02X}{:02X}".format(consumeType, billingType)
  155. self._send_data("38", data)
  156. return True
  157. def _clean_warning(self):
  158. """ 主板暂时没有实现此功能 """
  159. self._send_data("39")
  160. def _reload_device(self):
  161. self._send_data("40")
  162. def _get_all_device_settings(self):
  163. devSettings = dict()
  164. # 首先获取消费统计 只读信息
  165. result = self._get_consume_total()
  166. devSettings["cardMoney"] = str(RMB(result["cardMoney"] / 10.0))
  167. devSettings["coinMoney"] = str(RMB(result["coinMoney"] / 1.0))
  168. devSettings["totalUsedTime"] = result["totalUsedTime"]
  169. devSettings["totalUsedElec"] = result["totalUsedElec"]
  170. # 获取 设备IC卡 投币 最大功率 刷卡以及充满自停使能
  171. result = self._get_card_coin_time()
  172. devSettings["maxPower"] = result["maxPower"]
  173. devSettings["cardCost"] = str(RMB(result["cardCost"] / 10.0))
  174. devSettings["coin1Time"] = result["coin1Time"]
  175. devSettings["coin2Time"] = result["coin2Time"]
  176. devSettings["coin3Time"] = result["coin3Time"]
  177. devSettings["isCardRefund"] = bool(result["isCardRefund"])
  178. devSettings["isAutoStop"] = bool(result["isAutoStop"])
  179. # 获取功率计费阶梯
  180. devSettings["powerStep"] = self._get_power_step()
  181. # 获取设备的刷卡充电时间 充电模式 语音 浮充 空载
  182. result = self._get_card_time()
  183. devSettings["card1Time"] = result["card1Time"]
  184. devSettings["card2Time"] = result["card2Time"]
  185. devSettings["card3Time"] = result["card3Time"]
  186. devSettings["isFree"] = bool(result["isFree"])
  187. devSettings["voice"] = result["voice"]
  188. devSettings["floatPower"] = result["floatPower"] / 10.0
  189. devSettings["floatTime"] = result["floatTime"]
  190. devSettings["noloadPower"] = result["noloadPower"] / 10.0
  191. devSettings["noloadTime"] = result["noloadTime"]
  192. devSettings["minConsume"] = str(RMB(result["minConsume"] / 100.0))
  193. # 获取设备的其他参数
  194. result = self._get_device_params()
  195. devSettings["temperature"] = result["temperature"]
  196. devSettings["voltage"] = result["voltage"]
  197. # 获取消费模式以及消费流程
  198. result = self._get_consume_type()
  199. devSettings["consumeType"] = result["consumeType"]
  200. devSettings["billingType"] = result["billingType"]
  201. return devSettings
  202. def _response_card(self, cardNo, status, balance, openId=""):
  203. """
  204. 回复卡余额
  205. """
  206. data = {
  207. "IMEI": self.device.devNo,
  208. "funCode": "23",
  209. "balance": int(balance),
  210. "status": status,
  211. "open_id": openId,
  212. "card": int(cardNo)
  213. }
  214. MessageSender.send(
  215. device = self.device,
  216. cmd = DeviceCmdCode.OPERATE_DEV_SYNC,
  217. payload = data,
  218. timeout = 30
  219. )
  220. def _response_sync_card_balance(self, cardNo, balance, cardType="0001"):
  221. data = "{:08X}{:04X}{}".format(cardNo, balance, cardType)
  222. self._send_data("16", data)
  223. @staticmethod
  224. def _parse_11(data):
  225. return {
  226. "cardNo": str(int(data[18: 26], 16)),
  227. "cost": int(data[26: 28], 16) / 10.0,
  228. "balance": int(data[28: 32], 16) / 10.0,
  229. "port": str(int(data[40: 42], 16)),
  230. "opt": str(int(data[42: 44], 16))
  231. }
  232. @staticmethod
  233. def _parse_22(data):
  234. """EE10220000000000000131332B12AA330091"""
  235. cardNo = str(int(data[18: 26], 16))
  236. return {"cardNo": cardNo}
  237. @staticmethod
  238. def _parse_41(data):
  239. pass
  240. @staticmethod
  241. def _parse_12(data):
  242. return {
  243. "cardNo": str(int(data[18: 26], 16)),
  244. "balance": int(data[26: 30], 16) / 10.0,
  245. "type": data[30: 34]
  246. }
  247. @staticmethod
  248. def _parse_16(data):
  249. return {
  250. "cardNo": str(int(data[18: 26], 16)),
  251. "balance": int(data[26: 30], 16) / 10.0,
  252. "type": data[30: 34],
  253. "success": data[34: 36] == "01"
  254. }
  255. @staticmethod
  256. def _check_package(package):
  257. if not package:
  258. raise ServiceException({"result": 2, "description": u"套餐单位错误(1001)"})
  259. if package["unit"] == u"度":
  260. return int(float(package["time"]) * 100)
  261. if package["unit"] == u"分钟":
  262. return int(package["time"])
  263. if package["unit"] == u"小时":
  264. return int(float(package["time"]) * 60)
  265. raise ServiceException({"result": 2, "description": u"套餐单位错误(1005)"})
  266. def get_dev_setting(self):
  267. devSettings = self._get_all_device_settings()
  268. # 这两个参数没有读取 只有设置 只能从缓存中读取
  269. devSettings["isCardEnable"] = self.device["otherConf"].get("isCardEnable", True)
  270. devSettings["isCoinEnable"] = self.device["otherConf"].get("isCoinEnable", True)
  271. return devSettings
  272. def set_device_function_param(self, request, lastSetConf):
  273. validator = KeHangDeviceSettingsValidator(request.POST)
  274. if not validator.is_valid():
  275. raise ServiceException({"result": 2, "description": json.dumps(validator.str_errors)})
  276. data = validator.suit_data()
  277. if not self._set_card_coin_time(
  278. data["maxPower"], data["cardCost"], data["coin1Time"], data["coin2Time"], data["coin3Time"]
  279. ):
  280. raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0001)"})
  281. if not self._set_card_coin_enable(data["isCardEnable"], data["isCoinEnable"]):
  282. raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0002)"})
  283. if not self._set_stop_refund(data["isAutoStop"], data["isCardRefund"]):
  284. raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0003)"})
  285. if not self._set_power_step(*data["powerStep"]):
  286. raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0004)"})
  287. if not self._set_free_voice(data["isFree"], data["voice"]):
  288. raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0005)"})
  289. if not self._set_float_and_noload(
  290. data["floatPower"], data["floatTime"], data["noloadPower"], data["noloadTime"], data["minConsume"]
  291. ):
  292. raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0006)"})
  293. if not self._set_card_time(data["card3Time"], data["card2Time"], data["card3Time"]):
  294. raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0007)"})
  295. if not self._set_consume_type(data["consumeType"], data["billingType"]):
  296. raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0008)"})
  297. # 保存数据库一次
  298. otherConf = self.device["otherConf"]
  299. otherConf.update(validator.validated_data)
  300. Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf)
  301. def set_device_function(self, request, lastSetConf):
  302. if "cleanSmoke" in request.POST:
  303. self._clean_warning()
  304. if "reboot" in request.POST:
  305. self._reload_device()
  306. def start_device_realiable(self, order): # type: (ConsumeRecord)->dict
  307. """ 启动设备 """
  308. chargeParam = self._check_package(order.package)
  309. port = int(order.attachParas["chargeIndex"])
  310. data = {
  311. "funCode": "02",
  312. "order_id": order.orderNo,
  313. "port": port,
  314. "open_id": order.openId,
  315. "charge_param": chargeParam,
  316. "charge_mode": 0 # 默认是充满自停模式(即会自动断电)
  317. }
  318. result = MessageSender.send(
  319. device = self.device,
  320. cmd = DeviceCmdCode.OPERATE_DEV_SYNC,
  321. payload = data,
  322. timeout = 120
  323. )
  324. return result
  325. def _parse_51(self,data):
  326. return {}
  327. def analyze_event_data(self, data):
  328. """ 解析数据 """
  329. funCode = data[4: 6]
  330. if funCode == "22":
  331. result = self._parse_22(data)
  332. elif funCode == "41":
  333. result = self._parse_41(data)
  334. elif funCode == "12":
  335. result = self._parse_12(data)
  336. elif funCode == "16":
  337. result = self._parse_16(data)
  338. elif funCode == "51":
  339. result = self._parse_51(data)
  340. else:
  341. return
  342. result["cmdCode"] = funCode
  343. return result
  344. def stop(self, port = None):
  345. """ 停止设备运行 """
  346. self._stop(int(port))
  347. def get_port_status(self, force = False):
  348. """ 获取端口的状态 """
  349. devCache = Device.get_dev_control_cache(self.device.devNo)
  350. statusMap = dict()
  351. for _port, _item in devCache.items():
  352. if isinstance(_port, (unicode, str)) and _port.isdigit():
  353. statusMap[_port] = {'status': _item.get("status", 0)}
  354. if not statusMap or force:
  355. return self.get_port_status_from_dev()
  356. return statusMap
  357. def get_port_status_from_dev(self):
  358. """ 从设备端获取状态 """
  359. result = self._get_port_status()
  360. devCache = Device.get_dev_control_cache(self.device.devNo)
  361. portStatus = dict()
  362. for _p, _s in result.items():
  363. _status = STATUS_MAP.get(_s)
  364. devCache[_p] = devCache.get(_p) or dict()
  365. devCache[_p]["status"] = _status
  366. portStatus[_p] = {"status": _status}
  367. allPorts, usedPorts, usePorts = self.get_port_static_info(portStatus)
  368. devCache.update({"allPorts": allPorts, "usedPorts": usedPorts, "usdPorts": usePorts})
  369. Device.update_dev_control_cache(self.device.devNo, devCache)
  370. return portStatus
  371. def get_port_info(self, port):
  372. """ 获取设备端口的使用详情 """
  373. result = self._get_port_charge_info(port)
  374. left = result["left"]
  375. power = result["power"] / 10.0
  376. portCache = Device.get_port_control_cache(self.device.devNo, str(port))
  377. billingType = portCache.get("billingType")
  378. needKind = portCache.get("needKind")
  379. orderNo = portCache.get("orderNo")
  380. openId = portCache.get("openId")
  381. order = ConsumeRecord.objects.filter(orderNo=orderNo, openId=openId).first() # type: ConsumeRecord
  382. if order:
  383. portCache["nickname"] = order.user.nickname
  384. if needKind and billingType is not None:
  385. if billingType == BillingType.TIME:
  386. portCache[needKind] = portCache["needValue"]
  387. portCache["leftTime"] = left
  388. elif billingType == BillingType.ELEC:
  389. portCache[needKind] = portCache["needValue"] / 100.0
  390. portCache["leftElec"] = left / 100.0
  391. portCache["power"] = power
  392. return portCache
  393. def active_deactive_port(self, port, active):
  394. if not active:
  395. self._stop(int(port))
  396. def custom_push_url(self, order, user, **kw):
  397. from apps.web.utils import concat_user_center_entry_url
  398. from apps.web.utils import concat_front_end_url
  399. return concat_user_center_entry_url(agentId=user.productAgentId, redirect=concat_front_end_url(
  400. uri='/user/index.html#/user/deviceStatus'))