| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 | # coding=utf-8import loggingimport typingfrom apilib.monetary import VirtualCoinfrom apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUTfrom apps.web.core.adapter.base import SmartBoxfrom apps.web.core.adapter.cmCZ import CZGatewayfrom apps.web.core.device_define.cmCZ import PORT_STATUS_MAP, DEV_PREFIX, DEFAULT_ACCOUNT_RULE, CARD_CST, CARD_CST_MINfrom apps.web.core.exceptions import ServiceExceptionfrom apps.web.device.models import Deviceif typing.TYPE_CHECKING:    from apps.web.user.models import ConsumeRecordlogger = 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)
 |