# coding=utf-8 import logging import typing from apilib.monetary import VirtualCoin from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT from apps.web.core.adapter.base import SmartBox from apps.web.core.adapter.cmCZ import CZGateway from apps.web.core.device_define.cmCZ import PORT_STATUS_MAP, DEV_PREFIX, DEFAULT_ACCOUNT_RULE, CARD_CST, CARD_CST_MIN from apps.web.core.exceptions import ServiceException from apps.web.device.models import Device if typing.TYPE_CHECKING: from apps.web.user.models import ConsumeRecord logger = logging.getLogger(__name__) class CZBox(SmartBox): """ 诚马的插座 实际网络的发送就由网关的发送进行处理 """ def __init__(self, device): super(CZBox, self).__init__(device) self._Gateway = CZGateway(self.masterDevice) def _send_data(self, funCode, data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=MQTT_TIMEOUT.NORMAL): return self._Gateway._send_data(funCode, data, cmd, timeout, addr=int(self.addr)) @property def masterDevice(self): devNo = self.device.otherConf.get("master") return Device.get_dev(devNo) @property def addr(self): """ 模块的devNo 的格式类似于 cm-addr """ return self.device.devNo.replace(DEV_PREFIX, "") def _read_port_status(self): """ 读取端口状态 """ result = self._send_data("0F", "00") content = result["data"][16: -2] num = int(content[: 2], 16) portDict = dict() offset = 2 for _portIndex in range(num): _port, _status = content[offset: offset+2], content[offset+2: offset+4] portDict[str(int(_port))] = {"status": PORT_STATUS_MAP.get(_status, Const.DEV_WORK_STATUS_FAULT)} offset += 4 return portDict def _lock_port(self, port): port, status = int(port), 0x00 data = "{:02X}{:02X}".format(port, status) return self._send_data(funCode="0C", data=data) def _unlock_port(self, port): port, status = int(port), 0x01 data = "{:02X}{:02X}".format(port, status) return self._send_data(funCode="0C", data=data) def _stop_port(self, port): port, _type = int(port), 0x00 data = "{:02X}{:02X}".format(port, _type) return self._send_data(funCode="0D", data=data) def _start_device(self, port, money, _time, elec, sid, _type): data = "{:02X}{:04X}{:04X}{:04X}{:08X}{:02X}".format(port, money, _time, elec, sid, _type) self._send_data(funCode="27", data=data) def _get_port_info(self, port): data = "{:02X}".format(int(port)) result = self._send_data(funCode="2E", data=data) content = result["data"][16: -2] # 插座的充电模式下 剩余电量和剩余时间没有用 return { "port": int(content[: 2], 16), "status": PORT_STATUS_MAP.get(content[2: 4], Const.DEV_WORK_STATUS_FAULT), # "leftTime": int(content[4: 8], 16), "power": int(content[8: 12], 16), # "leftElec": int(content[12: 16], 16) * 0.01, # "leftMoney": int(content[16: 20], 16) * 0.1, "V": int(content[20: 24], 16) * 0.1, "A": int(content[24: 28], 16) * 0.01, # "maxTime": int(content[28: 32], 16) } def _get_port_order(self, port): data = {"port": int(port)} result = self._send_data(funCode="QO", data=data) return result def remove_from_gateway(self): self._Gateway.remove_node(self.device) otherConf = self.device.otherConf otherConf.pop("master", None) Device.objects.filter(devNo=self.device.devNo).first().update(otherConf=otherConf) Device.invalid_device_cache(self.device.devNo) @staticmethod def _parse_26(data): nums, content = int(data[16: 18], 16), data[18: -2] portInfo = dict() for _portIndex in range(nums): offset = 26 * _portIndex _port = str(int(content[offset: offset+2], 16)) portInfo[_port] = dict() portInfo[_port]["type"] = content[offset+2: offset+4] portInfo[_port]["status"] = PORT_STATUS_MAP.get(content[offset+4: offset+6], Const.DEV_WORK_STATUS_IDLE) # portInfo[_port]["leftTime"] = int(content[offset+6: offset+10], 16) portInfo[_port]["power"] = int(content[offset+10: offset+14], 16) # portInfo[_port]["leftElec"] = int(bin(int(content[offset+14: offset+18], 16))[2:][1:], 2) * 0.01 portInfo[_port]["V"] = int(content[offset+18: offset+22], 16) * 0.1 portInfo[_port]["A"] = int(content[offset+22: offset+26], 16) * 0.01 return portInfo @staticmethod def _parse_2C(data): content = data[16: -2] return { "portStr": str(int(content[: 2], 16)), "leftTime": int(content[2: 6], 16), "leftElec": int(content[6: 10], 16) * 0.01, "cardNo": str(int(content[10: 18], 16)), "cardCst": int(content[18: 22], 16) * 0.1, "cardType": content[22: 24], "reason": str(content[24: 26]), "sessionId": content[26: 34] } @staticmethod def _parse_0A(data): content = data[16: -2] return { "port": str(int(content[: 2], 16)), "errorCode": content[2: 6] } @staticmethod def _parse_10(data): """ 解析刷卡上报指令 """ content = data[16: -2] return { "cardNo": str(int(content[: 8], 16)), "cardCst": int(content[8:10], 16), "cardOpe": int(content[10:12], 16) } def analyze_event_data(self, data): funCode = data[4: 6] eventData = {"cmdCode": funCode} if funCode == "26": eventData["portInfo"] = self._parse_26(data) elif funCode == "2C": eventData.update(self._parse_2C(data)) elif funCode == "0A": eventData.update(self._parse_0A(data)) elif funCode == "10": eventData.update(self._parse_10(data)) else: logger.info("[CZBox analyze_event_data] device <{}> get un parse event data = {}".format(self.device.devNo, data)) return return eventData def get_dev_setting(self): otherConf = self.device.otherConf return { "powerPackage": otherConf.get("rule", DEFAULT_ACCOUNT_RULE["rule"]), "powerPackageDefaultPrice": otherConf.get("defaultPrice", DEFAULT_ACCOUNT_RULE["defaultPrice"]), "cardCst": otherConf.get("cardCst", CARD_CST), "cardCstMin": otherConf.get("cardCstMin", CARD_CST_MIN) } def set_device_function_param(self, request, lastSetConf): powerPackage = request.POST.get("powerPackage", DEFAULT_ACCOUNT_RULE["rule"]) powerPackageDefaultPrice = request.POST.get("powerPackageDefaultPrice", DEFAULT_ACCOUNT_RULE["rule"]) cardCst = request.POST.get("cardCst", CARD_CST) cardCstMin = request.POST.get("cardCstMin", CARD_CST_MIN) # 对参数进行一次数据转换 主要是数据类型 powerPackageDefaultPrice = float(powerPackageDefaultPrice) powerPackage = [{"min": int(_["min"]), "max": int(_["max"]), "price": float(_["price"])} for _ in powerPackage] cardCst = float(cardCst) cardCstMin = float(cardCstMin) for _index, _rule in enumerate(powerPackage): if int(_rule["min"]) >= int(_rule["max"]): raise ServiceException({"result": 2, "description": u"第{}功率计费区间最小值大于最大值".format(_index)}) other = { "defaultPrice": powerPackageDefaultPrice, "rule": powerPackage, "cardCst": cardCst, "cardCstMin": cardCstMin } self.device.otherConf.update(other) Device.objects.get(devNo=self.device.devNo).update(otherConf=self.device.otherConf) Device.invalid_device_cache(self.device.devNo) def stop(self, port=None): return self._stop_port(int(port)) def get_port_info(self, port): result = self._get_port_order(int(port)) payload = { "port": int(port), "status": result["status"], "power": result["power"] } portCache = Device.get_port_control_cache(self.device.devNo, str(port)) if "spendMoney" in result and "coins" in portCache: consumeMoney = VirtualCoin(result["spendMoney"] / 3600) leftMoney = VirtualCoin(portCache["coins"]) - consumeMoney payload.update({ "consumeMoney": consumeMoney.amount, "leftMoney": leftMoney.amount }) return payload def get_port_status(self, force=False): if force: self.get_port_status_from_dev() devCache = Device.get_dev_control_cache(self.device.devNo) portStatus = dict() for _item in devCache: if isinstance(_item, (unicode, str)) and _item.isdigit(): portStatus[_item] = devCache.get(_item) return portStatus def get_port_status_from_dev(self): result = self._read_port_status() allPorts, usedPorts, usePorts = self.get_port_static_info(result) Device.update_dev_control_cache( self._device.devNo, {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts} ) ctrInfo = Device.get_dev_control_cache(self._device['devNo']) for strPort, info in result.items(): if strPort in ctrInfo: ctrInfo[strPort].update({'status': info['status']}) else: ctrInfo[strPort] = info Device.update_dev_control_cache(self._device['devNo'], ctrInfo) return result def start_device_realiable(self, order): # type:(ConsumeRecord) -> dict if order.is_on_point: raise ServiceException({"result": 2, "description": u"当前设备不支持远程上分,请直接给用户派币启动"}) attachParas = order.attachParas package = order.package if attachParas is None: raise ServiceException({"result": 2, "description": u"请您选择合适的充电线路"}) if "chargeIndex" not in attachParas: raise ServiceException({"result": 2, "description": u"请您选择合适的充电线路"}) portInfo = self._get_port_info(attachParas["chargeIndex"]) if portInfo["status"] != Const.DEV_WORK_STATUS_IDLE: raise ServiceException({"result": 2, "description": u"当前端口正在工作中,请选择其他端口"}) port = int(attachParas["chargeIndex"]) coins = package.get("coins") rule = self.device.otherConf.get("rule", DEFAULT_ACCOUNT_RULE["rule"]) defaultPrice = self.device.otherConf.get("defaultPrice", DEFAULT_ACCOUNT_RULE["defaultPrice"]) data = { 'order_type': "com_start", "balance": coins, "rule": rule, "defaultPrice": defaultPrice, "port": port, "order_id": order.orderNo, "addr": int(self.addr) } return self._send_data(funCode="27", data=data) def active_deactive_port(self, port, active): return self._stop_port(int(port)) def response_card(self, res, cst, balance, cardNo=None): rule = self.device.otherConf.get("rule", DEFAULT_ACCOUNT_RULE["rule"]) defaultPrice = self.device.otherConf.get("defaultPrice", DEFAULT_ACCOUNT_RULE["defaultPrice"]) data = { "res": res, "balance": VirtualCoin(cst).amount, "cardBalance": VirtualCoin(balance).amount, "rule": rule, "defaultPrice": defaultPrice, "cardNo": cardNo, "addr": int(self.addr) } return self._send_data(funCode="10", data=data)