cmCZSub.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. # coding=utf-8
  2. import logging
  3. import typing
  4. from apilib.monetary import VirtualCoin
  5. from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT
  6. from apps.web.core.adapter.base import SmartBox
  7. from apps.web.core.adapter.cmCZ import CZGateway
  8. from apps.web.core.device_define.cmCZ import PORT_STATUS_MAP, DEV_PREFIX, DEFAULT_ACCOUNT_RULE, CARD_CST, CARD_CST_MIN
  9. from apps.web.core.exceptions import ServiceException
  10. from apps.web.device.models import Device
  11. if typing.TYPE_CHECKING:
  12. from apps.web.user.models import ConsumeRecord
  13. logger = logging.getLogger(__name__)
  14. class CZBox(SmartBox):
  15. """
  16. 诚马的插座 实际网络的发送就由网关的发送进行处理
  17. """
  18. def __init__(self, device):
  19. super(CZBox, self).__init__(device)
  20. self._Gateway = CZGateway(self.masterDevice)
  21. def _send_data(self, funCode, data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=MQTT_TIMEOUT.NORMAL):
  22. return self._Gateway._send_data(funCode, data, cmd, timeout, addr=int(self.addr))
  23. @property
  24. def masterDevice(self):
  25. devNo = self.device.otherConf.get("master")
  26. return Device.get_dev(devNo)
  27. @property
  28. def addr(self):
  29. """
  30. 模块的devNo 的格式类似于 cm-addr
  31. """
  32. return self.device.devNo.replace(DEV_PREFIX, "")
  33. def _read_port_status(self):
  34. """
  35. 读取端口状态
  36. """
  37. result = self._send_data("0F", "00")
  38. content = result["data"][16: -2]
  39. num = int(content[: 2], 16)
  40. portDict = dict()
  41. offset = 2
  42. for _portIndex in range(num):
  43. _port, _status = content[offset: offset+2], content[offset+2: offset+4]
  44. portDict[str(int(_port))] = {"status": PORT_STATUS_MAP.get(_status, Const.DEV_WORK_STATUS_FAULT)}
  45. offset += 4
  46. return portDict
  47. def _lock_port(self, port):
  48. port, status = int(port), 0x00
  49. data = "{:02X}{:02X}".format(port, status)
  50. return self._send_data(funCode="0C", data=data)
  51. def _unlock_port(self, port):
  52. port, status = int(port), 0x01
  53. data = "{:02X}{:02X}".format(port, status)
  54. return self._send_data(funCode="0C", data=data)
  55. def _stop_port(self, port):
  56. port, _type = int(port), 0x00
  57. data = "{:02X}{:02X}".format(port, _type)
  58. return self._send_data(funCode="0D", data=data)
  59. def _start_device(self, port, money, _time, elec, sid, _type):
  60. data = "{:02X}{:04X}{:04X}{:04X}{:08X}{:02X}".format(port, money, _time, elec, sid, _type)
  61. self._send_data(funCode="27", data=data)
  62. def _get_port_info(self, port):
  63. data = "{:02X}".format(int(port))
  64. result = self._send_data(funCode="2E", data=data)
  65. content = result["data"][16: -2]
  66. # 插座的充电模式下 剩余电量和剩余时间没有用
  67. return {
  68. "port": int(content[: 2], 16),
  69. "status": PORT_STATUS_MAP.get(content[2: 4], Const.DEV_WORK_STATUS_FAULT),
  70. # "leftTime": int(content[4: 8], 16),
  71. "power": int(content[8: 12], 16),
  72. # "leftElec": int(content[12: 16], 16) * 0.01,
  73. # "leftMoney": int(content[16: 20], 16) * 0.1,
  74. "V": int(content[20: 24], 16) * 0.1,
  75. "A": int(content[24: 28], 16) * 0.01,
  76. # "maxTime": int(content[28: 32], 16)
  77. }
  78. def _get_port_order(self, port):
  79. data = {"port": int(port)}
  80. result = self._send_data(funCode="QO", data=data)
  81. return result
  82. def remove_from_gateway(self):
  83. self._Gateway.remove_node(self.device)
  84. otherConf = self.device.otherConf
  85. otherConf.pop("master", None)
  86. Device.objects.filter(devNo=self.device.devNo).first().update(otherConf=otherConf)
  87. Device.invalid_device_cache(self.device.devNo)
  88. @staticmethod
  89. def _parse_26(data):
  90. nums, content = int(data[16: 18], 16), data[18: -2]
  91. portInfo = dict()
  92. for _portIndex in range(nums):
  93. offset = 26 * _portIndex
  94. _port = str(int(content[offset: offset+2], 16))
  95. portInfo[_port] = dict()
  96. portInfo[_port]["type"] = content[offset+2: offset+4]
  97. portInfo[_port]["status"] = PORT_STATUS_MAP.get(content[offset+4: offset+6], Const.DEV_WORK_STATUS_IDLE)
  98. # portInfo[_port]["leftTime"] = int(content[offset+6: offset+10], 16)
  99. portInfo[_port]["power"] = int(content[offset+10: offset+14], 16)
  100. # portInfo[_port]["leftElec"] = int(bin(int(content[offset+14: offset+18], 16))[2:][1:], 2) * 0.01
  101. portInfo[_port]["V"] = int(content[offset+18: offset+22], 16) * 0.1
  102. portInfo[_port]["A"] = int(content[offset+22: offset+26], 16) * 0.01
  103. return portInfo
  104. @staticmethod
  105. def _parse_2C(data):
  106. content = data[16: -2]
  107. return {
  108. "portStr": str(int(content[: 2], 16)),
  109. "leftTime": int(content[2: 6], 16),
  110. "leftElec": int(content[6: 10], 16) * 0.01,
  111. "cardNo": str(int(content[10: 18], 16)),
  112. "cardCst": int(content[18: 22], 16) * 0.1,
  113. "cardType": content[22: 24],
  114. "reason": str(content[24: 26]),
  115. "sessionId": content[26: 34]
  116. }
  117. @staticmethod
  118. def _parse_0A(data):
  119. content = data[16: -2]
  120. return {
  121. "port": str(int(content[: 2], 16)),
  122. "errorCode": content[2: 6]
  123. }
  124. @staticmethod
  125. def _parse_10(data):
  126. """
  127. 解析刷卡上报指令
  128. """
  129. content = data[16: -2]
  130. return {
  131. "cardNo": str(int(content[: 8], 16)),
  132. "cardCst": int(content[8:10], 16),
  133. "cardOpe": int(content[10:12], 16)
  134. }
  135. def analyze_event_data(self, data):
  136. funCode = data[4: 6]
  137. eventData = {"cmdCode": funCode}
  138. if funCode == "26":
  139. eventData["portInfo"] = self._parse_26(data)
  140. elif funCode == "2C":
  141. eventData.update(self._parse_2C(data))
  142. elif funCode == "0A":
  143. eventData.update(self._parse_0A(data))
  144. elif funCode == "10":
  145. eventData.update(self._parse_10(data))
  146. else:
  147. logger.info("[CZBox analyze_event_data] device <{}> get un parse event data = {}".format(self.device.devNo, data))
  148. return
  149. return eventData
  150. def get_dev_setting(self):
  151. otherConf = self.device.otherConf
  152. return {
  153. "powerPackage": otherConf.get("rule", DEFAULT_ACCOUNT_RULE["rule"]),
  154. "powerPackageDefaultPrice": otherConf.get("defaultPrice", DEFAULT_ACCOUNT_RULE["defaultPrice"]),
  155. "cardCst": otherConf.get("cardCst", CARD_CST),
  156. "cardCstMin": otherConf.get("cardCstMin", CARD_CST_MIN)
  157. }
  158. def set_device_function_param(self, request, lastSetConf):
  159. powerPackage = request.POST.get("powerPackage", DEFAULT_ACCOUNT_RULE["rule"])
  160. powerPackageDefaultPrice = request.POST.get("powerPackageDefaultPrice", DEFAULT_ACCOUNT_RULE["rule"])
  161. cardCst = request.POST.get("cardCst", CARD_CST)
  162. cardCstMin = request.POST.get("cardCstMin", CARD_CST_MIN)
  163. # 对参数进行一次数据转换 主要是数据类型
  164. powerPackageDefaultPrice = float(powerPackageDefaultPrice)
  165. powerPackage = [{"min": int(_["min"]), "max": int(_["max"]), "price": float(_["price"])} for _ in powerPackage]
  166. cardCst = float(cardCst)
  167. cardCstMin = float(cardCstMin)
  168. for _index, _rule in enumerate(powerPackage):
  169. if int(_rule["min"]) >= int(_rule["max"]):
  170. raise ServiceException({"result": 2, "description": u"第{}功率计费区间最小值大于最大值".format(_index)})
  171. other = {
  172. "defaultPrice": powerPackageDefaultPrice,
  173. "rule": powerPackage,
  174. "cardCst": cardCst,
  175. "cardCstMin": cardCstMin
  176. }
  177. self.device.otherConf.update(other)
  178. Device.objects.get(devNo=self.device.devNo).update(otherConf=self.device.otherConf)
  179. Device.invalid_device_cache(self.device.devNo)
  180. def stop(self, port=None):
  181. return self._stop_port(int(port))
  182. def get_port_info(self, port):
  183. result = self._get_port_order(int(port))
  184. payload = {
  185. "port": int(port),
  186. "status": result["status"],
  187. "power": result["power"]
  188. }
  189. portCache = Device.get_port_control_cache(self.device.devNo, str(port))
  190. if "spendMoney" in result and "coins" in portCache:
  191. consumeMoney = VirtualCoin(result["spendMoney"] / 3600)
  192. leftMoney = VirtualCoin(portCache["coins"]) - consumeMoney
  193. payload.update({
  194. "consumeMoney": consumeMoney.amount,
  195. "leftMoney": leftMoney.amount
  196. })
  197. return payload
  198. def get_port_status(self, force=False):
  199. if force:
  200. self.get_port_status_from_dev()
  201. devCache = Device.get_dev_control_cache(self.device.devNo)
  202. portStatus = dict()
  203. for _item in devCache:
  204. if isinstance(_item, (unicode, str)) and _item.isdigit():
  205. portStatus[_item] = devCache.get(_item)
  206. return portStatus
  207. def get_port_status_from_dev(self):
  208. result = self._read_port_status()
  209. allPorts, usedPorts, usePorts = self.get_port_static_info(result)
  210. Device.update_dev_control_cache(
  211. self._device.devNo,
  212. {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts}
  213. )
  214. ctrInfo = Device.get_dev_control_cache(self._device['devNo'])
  215. for strPort, info in result.items():
  216. if strPort in ctrInfo:
  217. ctrInfo[strPort].update({'status': info['status']})
  218. else:
  219. ctrInfo[strPort] = info
  220. Device.update_dev_control_cache(self._device['devNo'], ctrInfo)
  221. return result
  222. def start_device_realiable(self, order): # type:(ConsumeRecord) -> dict
  223. if order.is_on_point:
  224. raise ServiceException({"result": 2, "description": u"当前设备不支持远程上分,请直接给用户派币启动"})
  225. attachParas = order.attachParas
  226. package = order.package
  227. if attachParas is None:
  228. raise ServiceException({"result": 2, "description": u"请您选择合适的充电线路"})
  229. if "chargeIndex" not in attachParas:
  230. raise ServiceException({"result": 2, "description": u"请您选择合适的充电线路"})
  231. portInfo = self._get_port_info(attachParas["chargeIndex"])
  232. if portInfo["status"] != Const.DEV_WORK_STATUS_IDLE:
  233. raise ServiceException({"result": 2, "description": u"当前端口正在工作中,请选择其他端口"})
  234. port = int(attachParas["chargeIndex"])
  235. coins = package.get("coins")
  236. rule = self.device.otherConf.get("rule", DEFAULT_ACCOUNT_RULE["rule"])
  237. defaultPrice = self.device.otherConf.get("defaultPrice", DEFAULT_ACCOUNT_RULE["defaultPrice"])
  238. data = {
  239. 'order_type': "com_start",
  240. "balance": coins,
  241. "rule": rule,
  242. "defaultPrice": defaultPrice,
  243. "port": port,
  244. "order_id": order.orderNo,
  245. "addr": int(self.addr)
  246. }
  247. return self._send_data(funCode="27", data=data)
  248. def active_deactive_port(self, port, active):
  249. return self._stop_port(int(port))
  250. def response_card(self, res, cst, balance, cardNo=None):
  251. rule = self.device.otherConf.get("rule", DEFAULT_ACCOUNT_RULE["rule"])
  252. defaultPrice = self.device.otherConf.get("defaultPrice", DEFAULT_ACCOUNT_RULE["defaultPrice"])
  253. data = {
  254. "res": res,
  255. "balance": VirtualCoin(cst).amount,
  256. "cardBalance": VirtualCoin(balance).amount,
  257. "rule": rule,
  258. "defaultPrice": defaultPrice,
  259. "cardNo": cardNo,
  260. "addr": int(self.addr)
  261. }
  262. return self._send_data(funCode="10", data=data)