# -*- coding: utf-8 -*- # !/usr/bin/env python import json import logging from apilib.monetary import RMB from apps.web.constant import DeviceCmdCode, MQTT_TIMEOUT from apps.web.core.adapter.base import SmartBox from apps.web.core.device_define.kehang import KeHangDeviceSettingsValidator, STATUS_MAP, BillingType from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.device.models import Device from apps.web.user.models import ConsumeRecord logger = logging.getLogger(__name__) class KeHangBox(SmartBox): def _send_data(self, funCode, data = None, timeout = MQTT_TIMEOUT.NORMAL): if data is None: data = "00" result = MessageSender.send( device = self.device, cmd = DeviceCmdCode.OPERATE_DEV_SYNC, payload = { "IMEI": self._device["devNo"], "funCode": funCode, "data": data }, timeout = timeout ) if "rst" in result and result.get("rst") != 0: if result.get("rst") == -1: raise ServiceException({"result": 2, "description": u"设备网络故障,请重新"}) if result.get("rst") == 1: raise ServiceException({"result": 2, "description": u"充电桩无响应,请稍后再试试"}) return result def _get_port_status(self): result = self._send_data("01") data = result["data"] portNum = int(data[18: 20], 16) portStatus = dict() for _i in range(portNum): _port = str(_i + 1) _offset = 20 + _i * 2 portStatus[_port] = data[_offset: _offset+2] return portStatus def _get_port_charge_info(self, port): """ 获取端口的充电信息 """ data = "{:02X}".format(int(port)) result = self._send_data("06", data) return { "port": str(int(result["data"][18:20], 16)), "left": int(result["data"][20: 24], 16), "power": int(result["data"][24: 28], 16) } def _get_consume_total(self): """ 获取总的消费数据 """ result = self._send_data("07") return { "cardMoney": int(result["data"][18: 22], 16), "coinMoney": int(result["data"][22: 26], 16), "totalUsedTime": int(result["data"][26: 30], 16), "totalUsedElec": int(result["data"][30: 34], 16) } def _set_card_coin_time(self, maxPower, cardCost, time1, time2, time3): """ 设置最大功率 刷卡消费 以及投币相应的时间 """ data = "{:04X}{:02X}{:04X}{:04X}{:04X}".format(maxPower, cardCost, time1, time2, time3) result = self._send_data("08", data) return result["data"][18: 20] == "01" def _set_card_coin_enable(self, isCardEnable, isCoinEnable): """ 设置刷卡投币使能 """ data = "{:02X}{:02X}".format(isCardEnable, isCoinEnable) self._send_data("09", data) return True def _stop(self, port): """ 停止端口 """ data = "{:02X}".format(port) result = self._send_data("0B", data) return { "port": str(int(result["data"][18: 20], 16)), "left": int(result["data"][20: 24], 16) } def _get_card_coin_time(self): result = self._send_data("0C") return { "maxPower": int(result["data"][18: 22], 16), "cardCost": int(result["data"][22: 24], 16), "coin1Time": int(result["data"][24: 28], 16), "coin2Time": int(result["data"][28: 32], 16), "coin3Time": int(result["data"][32: 36], 16), "isCardRefund": int(result["data"][36: 38], 16), "isAutoStop": int(result["data"][38: 40], 16) } def _set_stop_refund(self, isAutoStop, isCardRefund): data = "{:02X}{:02X}".format(isAutoStop, isCardRefund) self._send_data("13", data) return True def _set_power_step(self, power1, power2, power3, power4, power5): """ 设置5挡计费 """ data = "".join(("{:04X}{:02X}".format(_x["power"], _x["ratio"]) for _x in [power1, power2, power3, power4, power5])) result = self._send_data("14", data) return result["data"][18: 20] == "01" def _get_power_step(self): result = self._send_data("15") return [ {"power": int(result["data"][18: 24][:4], 16), "ratio": int(result["data"][18: 24][4:6], 16)}, {"power": int(result["data"][24: 30][:4], 16), "ratio": int(result["data"][24: 30][4:6], 16)}, {"power": int(result["data"][30: 36][:4], 16), "ratio": int(result["data"][30: 36][4:6], 16)}, {"power": int(result["data"][36: 42][:4], 16), "ratio": int(result["data"][36: 42][4:6], 16)}, {"power": int(result["data"][42: 48][:4], 16), "ratio": int(result["data"][42: 48][4:6], 16)}, ] def _set_free_voice(self, isFree, voice): """ 设置免费充电以及音量调节 """ data = "{:02X}{:02X}".format(isFree, voice) self._send_data("27", data) return True def _set_float_and_noload(self, floatPower, floatTime, noloadPower, noloadTime, minConsume=0): data = "{:04X}{:04X}{:04X}{:04X}{:02X}".format(floatPower, floatTime, noloadPower, noloadTime, minConsume) self._send_data("28", data) return True def _set_card_time(self, time1, time2, time3): """ 设置刷卡的充电时间 """ data = "{:04X}{:04X}{:04X}".format(time1, time2, time3) result = self._send_data("29", data) return result["data"][18: 20] == "01" def _get_card_time(self): result = self._send_data("2A") return { "card1Time": int(result["data"][18: 22], 16), "card2Time": int(result["data"][22: 26], 16), "card3Time": int(result["data"][26: 30], 16), "isFree": int(result["data"][30: 32], 16), "voice": int(result["data"][32: 34], 16), "floatPower": int(result["data"][34: 38], 16), "floatTime": int(result["data"][38: 42], 16), "noloadPower": int(result["data"][42: 46], 16), "noloadTime": int(result["data"][46: 50], 16), "minConsume": int(result["data"][50: 52], 16) } def _get_device_params(self): """ 获取设备的动态参数 主板暂时没有实现电压值""" result = self._send_data("35") return { "temperature": int(result["data"][18: 20], 16), "smokeWarning": int(result["data"][20: 22], 16), "voltage": int(result["data"][22: 24], 16) } def _get_consume_type(self): """ 读取设备所有的参数设置 协议有误 只是为了获取消费方式和消费流程""" result = self._send_data("36") return { "consumeType": int(result["data"][-6: -4], 16), "billingType": int(result["data"][-4: -2], 16), } def _set_consume_type(self, consumeType, billingType): """ 设置消费流程(先后按键)以及 计费方式(时间/电量) """ data = "{:02X}{:02X}".format(consumeType, billingType) self._send_data("38", data) return True def _clean_warning(self): """ 主板暂时没有实现此功能 """ self._send_data("39") def _reload_device(self): self._send_data("40") def _get_all_device_settings(self): devSettings = dict() # 首先获取消费统计 只读信息 result = self._get_consume_total() devSettings["cardMoney"] = str(RMB(result["cardMoney"] / 10.0)) devSettings["coinMoney"] = str(RMB(result["coinMoney"] / 1.0)) devSettings["totalUsedTime"] = result["totalUsedTime"] devSettings["totalUsedElec"] = result["totalUsedElec"] # 获取 设备IC卡 投币 最大功率 刷卡以及充满自停使能 result = self._get_card_coin_time() devSettings["maxPower"] = result["maxPower"] devSettings["cardCost"] = str(RMB(result["cardCost"] / 10.0)) devSettings["coin1Time"] = result["coin1Time"] devSettings["coin2Time"] = result["coin2Time"] devSettings["coin3Time"] = result["coin3Time"] devSettings["isCardRefund"] = bool(result["isCardRefund"]) devSettings["isAutoStop"] = bool(result["isAutoStop"]) # 获取功率计费阶梯 devSettings["powerStep"] = self._get_power_step() # 获取设备的刷卡充电时间 充电模式 语音 浮充 空载 result = self._get_card_time() devSettings["card1Time"] = result["card1Time"] devSettings["card2Time"] = result["card2Time"] devSettings["card3Time"] = result["card3Time"] devSettings["isFree"] = bool(result["isFree"]) devSettings["voice"] = result["voice"] devSettings["floatPower"] = result["floatPower"] / 10.0 devSettings["floatTime"] = result["floatTime"] devSettings["noloadPower"] = result["noloadPower"] / 10.0 devSettings["noloadTime"] = result["noloadTime"] devSettings["minConsume"] = str(RMB(result["minConsume"] / 100.0)) # 获取设备的其他参数 result = self._get_device_params() devSettings["temperature"] = result["temperature"] devSettings["voltage"] = result["voltage"] # 获取消费模式以及消费流程 result = self._get_consume_type() devSettings["consumeType"] = result["consumeType"] devSettings["billingType"] = result["billingType"] return devSettings def _response_card(self, cardNo, status, balance, openId=""): """ 回复卡余额 """ data = { "IMEI": self.device.devNo, "funCode": "23", "balance": int(balance), "status": status, "open_id": openId, "card": int(cardNo) } MessageSender.send( device = self.device, cmd = DeviceCmdCode.OPERATE_DEV_SYNC, payload = data, timeout = 30 ) def _response_sync_card_balance(self, cardNo, balance, cardType="0001"): data = "{:08X}{:04X}{}".format(cardNo, balance, cardType) self._send_data("16", data) @staticmethod def _parse_11(data): return { "cardNo": str(int(data[18: 26], 16)), "cost": int(data[26: 28], 16) / 10.0, "balance": int(data[28: 32], 16) / 10.0, "port": str(int(data[40: 42], 16)), "opt": str(int(data[42: 44], 16)) } @staticmethod def _parse_22(data): """EE10220000000000000131332B12AA330091""" cardNo = str(int(data[18: 26], 16)) return {"cardNo": cardNo} @staticmethod def _parse_41(data): pass @staticmethod def _parse_12(data): return { "cardNo": str(int(data[18: 26], 16)), "balance": int(data[26: 30], 16) / 10.0, "type": data[30: 34] } @staticmethod def _parse_16(data): return { "cardNo": str(int(data[18: 26], 16)), "balance": int(data[26: 30], 16) / 10.0, "type": data[30: 34], "success": data[34: 36] == "01" } @staticmethod def _check_package(package): if not package: raise ServiceException({"result": 2, "description": u"套餐单位错误(1001)"}) if package["unit"] == u"度": return int(float(package["time"]) * 100) if package["unit"] == u"分钟": return int(package["time"]) if package["unit"] == u"小时": return int(float(package["time"]) * 60) raise ServiceException({"result": 2, "description": u"套餐单位错误(1005)"}) def get_dev_setting(self): devSettings = self._get_all_device_settings() # 这两个参数没有读取 只有设置 只能从缓存中读取 devSettings["isCardEnable"] = self.device["otherConf"].get("isCardEnable", True) devSettings["isCoinEnable"] = self.device["otherConf"].get("isCoinEnable", True) return devSettings def set_device_function_param(self, request, lastSetConf): validator = KeHangDeviceSettingsValidator(request.POST) if not validator.is_valid(): raise ServiceException({"result": 2, "description": json.dumps(validator.str_errors)}) data = validator.suit_data() if not self._set_card_coin_time( data["maxPower"], data["cardCost"], data["coin1Time"], data["coin2Time"], data["coin3Time"] ): raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0001)"}) if not self._set_card_coin_enable(data["isCardEnable"], data["isCoinEnable"]): raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0002)"}) if not self._set_stop_refund(data["isAutoStop"], data["isCardRefund"]): raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0003)"}) if not self._set_power_step(*data["powerStep"]): raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0004)"}) if not self._set_free_voice(data["isFree"], data["voice"]): raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0005)"}) if not self._set_float_and_noload( data["floatPower"], data["floatTime"], data["noloadPower"], data["noloadTime"], data["minConsume"] ): raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0006)"}) if not self._set_card_time(data["card3Time"], data["card2Time"], data["card3Time"]): raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0007)"}) if not self._set_consume_type(data["consumeType"], data["billingType"]): raise ServiceException({"result": 2, "description": u"参数设置失败,请重试(0008)"}) # 保存数据库一次 otherConf = self.device["otherConf"] otherConf.update(validator.validated_data) Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf) def set_device_function(self, request, lastSetConf): if "cleanSmoke" in request.POST: self._clean_warning() if "reboot" in request.POST: self._reload_device() def start_device_realiable(self, order): # type: (ConsumeRecord)->dict """ 启动设备 """ chargeParam = self._check_package(order.package) port = int(order.attachParas["chargeIndex"]) data = { "funCode": "02", "order_id": order.orderNo, "port": port, "open_id": order.openId, "charge_param": chargeParam, "charge_mode": 0 # 默认是充满自停模式(即会自动断电) } result = MessageSender.send( device = self.device, cmd = DeviceCmdCode.OPERATE_DEV_SYNC, payload = data, timeout = 120 ) return result def _parse_51(self,data): return {} def analyze_event_data(self, data): """ 解析数据 """ funCode = data[4: 6] if funCode == "22": result = self._parse_22(data) elif funCode == "41": result = self._parse_41(data) elif funCode == "12": result = self._parse_12(data) elif funCode == "16": result = self._parse_16(data) elif funCode == "51": result = self._parse_51(data) else: return result["cmdCode"] = funCode return result def stop(self, port = None): """ 停止设备运行 """ self._stop(int(port)) def get_port_status(self, force = False): """ 获取端口的状态 """ devCache = Device.get_dev_control_cache(self.device.devNo) statusMap = dict() for _port, _item in devCache.items(): if isinstance(_port, (unicode, str)) and _port.isdigit(): statusMap[_port] = {'status': _item.get("status", 0)} if not statusMap or force: return self.get_port_status_from_dev() return statusMap def get_port_status_from_dev(self): """ 从设备端获取状态 """ result = self._get_port_status() devCache = Device.get_dev_control_cache(self.device.devNo) portStatus = dict() for _p, _s in result.items(): _status = STATUS_MAP.get(_s) devCache[_p] = devCache.get(_p) or dict() devCache[_p]["status"] = _status portStatus[_p] = {"status": _status} allPorts, usedPorts, usePorts = self.get_port_static_info(portStatus) devCache.update({"allPorts": allPorts, "usedPorts": usedPorts, "usdPorts": usePorts}) Device.update_dev_control_cache(self.device.devNo, devCache) return portStatus def get_port_info(self, port): """ 获取设备端口的使用详情 """ result = self._get_port_charge_info(port) left = result["left"] power = result["power"] / 10.0 portCache = Device.get_port_control_cache(self.device.devNo, str(port)) billingType = portCache.get("billingType") needKind = portCache.get("needKind") orderNo = portCache.get("orderNo") openId = portCache.get("openId") order = ConsumeRecord.objects.filter(orderNo=orderNo, openId=openId).first() # type: ConsumeRecord if order: portCache["nickname"] = order.user.nickname if needKind and billingType is not None: if billingType == BillingType.TIME: portCache[needKind] = portCache["needValue"] portCache["leftTime"] = left elif billingType == BillingType.ELEC: portCache[needKind] = portCache["needValue"] / 100.0 portCache["leftElec"] = left / 100.0 portCache["power"] = power return portCache def active_deactive_port(self, port, active): if not active: self._stop(int(port)) def custom_push_url(self, order, user, **kw): from apps.web.utils import concat_user_center_entry_url from apps.web.utils import concat_front_end_url return concat_user_center_entry_url(agentId=user.productAgentId, redirect=concat_front_end_url( uri='/user/index.html#/user/deviceStatus'))