# -*- coding: utf-8 -*- #!/usr/bin/env python import time from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT from apps.web.core.adapter.base import SmartBox, reverse_hex, hexbyte_2_bin, fill_2_hexByte, pack_float, unpack_float from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.device.models import Device class FunCode(object): SET_POWER_MAX = "02" GET_POWER_MAX = "" GET_POWER_MIN = "" SET_POWER_MIN = "16" GET_DEV_STATUS = "14" START_CHARGING = "06" REBOOT_DEV = "0A" class ChargingNanjiguangBox(SmartBox): MAX_CHARGING_TIME = 2**32 - 1 MAX_CHARGING_ELEC = 999.0 DEFAULT_POWER_MAX_LIMIT = 1000 DEFAULT_DEVICE_MIN_LIMIT = 50 STATUS_MAP = { "00": Const.DEV_WORK_STATUS_IDLE, "01": Const.DEV_WORK_STATUS_WORKING, "02": Const.DEV_WORK_STATUS_FAULT, "03": Const.DEV_WORK_STATUS_FAULT } def _send_data(self, funcCode, sendData=None, cmd=None, timeout=MQTT_TIMEOUT.NORMAL,orderNo=None): """ 发送 报文 :param funcCode: 串口命令 :param sendData: 发送数据 :param cmd: 报文命令 :param timeout: 超时时间 :return: """ if sendData is None: sendData = "" if cmd is None: cmd = DeviceCmdCode.OPERATE_DEV_SYNC if timeout is None: timeout = MQTT_TIMEOUT.NORMAL result = MessageSender.send(device = self.device, cmd = cmd, payload = { "IMEI": self._device["devNo"], "funCode": funcCode, "data": sendData }, timeout = timeout) if "rst" in result: if result["rst"] == -1: raise ServiceException({'result': 2, 'description': u'网络不通畅,请稍候再试'}) elif result["rst"] == 1: raise ServiceException({'result': 2, 'description': u'主板连接故障,请稍后再试'}) elif result["rst"] == 0: return result else: raise ServiceException({'result': 2, 'description': u'系统错误'}) else: raise ServiceException({'result': 2, 'description': u'系统错误'}) @staticmethod def _parse_result_data(result): """ 报文返回结果 返还命令字(1) + 数据长度(2) + 数据域(n) 低前高后 :param result: 报文回执 :return: """ data = result.get("data") receiveCmd = data[: 2] receiveLen = int(reverse_hex(data[2: 6]), 16) return receiveCmd, receiveLen, data[6: ] @staticmethod def _parse_card(cardNo): return cardNo def _get_device_status(self): """ 从设备端获取设备端口状态 烟感报警 最大功率等 :return: """ result = self._send_data(funcCode=FunCode.GET_DEV_STATUS) _, __ , data = self._parse_result_data(result) # 烟感报警字段 暂时不用 smokeWarning = hexbyte_2_bin(data[: 2])[0] portStatusData = data[2: -4] powerMax = data[-4: ] portStatus = dict() portNum = 1 while portStatusData: tempStatus = portStatusData[: 2] portStatus[str(portNum)] = {"status": self.STATUS_MAP.get(tempStatus)} portStatusData = portStatusData[2: ] portNum += 1 return portStatus def _charging(self, port, chargeType, timeStamp, maxTime, maxPower,orderNo=None): """ 授权充电 :param port: 端口号 :param chargeType: 充电类型 :param timeStamp: 时间戳 :param maxTime: 最大时间 :param maxPower: 最大电量 :return: """ portHex = fill_2_hexByte(hex(int(port)), 2) chargeHex = fill_2_hexByte(hex(int(chargeType)), 2) timeHex = fill_2_hexByte(hex(int(timeStamp)), 8, reverse=True) maxTimeHex = fill_2_hexByte(hex(int(maxTime)), 8, reverse=True) maxPowerHex = pack_float(maxPower, reverse=True) dataHex = portHex + chargeHex + timeHex + maxTimeHex + maxPowerHex result = self._send_data(funcCode = FunCode.START_CHARGING, sendData = dataHex, timeout = MQTT_TIMEOUT.START_DEVICE, orderNo = orderNo) _, __, data = self._parse_result_data(result) if data[10: 12] != "00": return return result def _trans_time(self, needTime, unit): """ 根据所给的单位将时间转换成分钟 :param needTime: :param unit: :return: """ if unit == u"小时": needTime = needTime * 60 * 60 elif unit == u"分钟": needTime = needTime * 60 elif unit == u"秒": needTime = needTime else: needTime = None return needTime def _make_time_stamp(self): return int(time.time()) def _reboot(self): """ 重启设备 :return: """ self._send_data(funcCode=FunCode.REBOOT_DEV) def _set_port_max_power(self, powerDict): """ 设置端口 功率上限 :param powerDict: 如果不存在power则是设置为FF 禁用端口 :return: """ powerHex = "" baseName = "portPower{port}" for i in xrange(10): power = powerDict.get(baseName.format(port=str(i+1))) if power is None: powerHex += "FFFFFFFF" continue powerHex += fill_2_hexByte(hex(int(power)), 8, reverse=True) self._send_data(funcCode=FunCode.SET_POWER_MAX, sendData=powerHex) def _set_device_power_min(self, minPower): """ 设置最小充电功率 :param minPower: :return: """ minPowerHex = fill_2_hexByte(hex(int(minPower)), 4, reverse=True) self._send_data(funcCode=FunCode.SET_POWER_MIN, sendData=minPowerHex) def _get_port_max_power(self): """ 获取 端口 功率上限 :return: """ otherConf = self._device.get("otherConf", {}) baseName = "portPower{port}" portMaxDict = { baseName.format(port=str(i+1)): otherConf.get(baseName.format(port=str(i+1)), self.DEFAULT_POWER_MAX_LIMIT) for i in xrange(10) } return portMaxDict def _get_device_min_power(self): """ 获取 设备 停止充电功率 :return: """ otherConf = self._device.get("otherConf", {}) devMinPower = otherConf.get("devMinPower", self.DEFAULT_DEVICE_MIN_LIMIT) return {"devMinPower": devMinPower} def start_device(self, package, openId, attachParas): """ 启动设备 :param package: :param openId: :param attachParas: :return: """ if "chargeIndex" not in attachParas: raise ServiceException({"result": 2, "description": u"未知端口,请联系经销商"}) portStr = attachParas.get("chargeIndex") coins = package.get("coins") price = package.get("price") unit = package.get("unit") needTime = package.get("time") needTime = self._trans_time(needTime, unit) if needTime is None: raise ServiceException({"result": "2", "description": u"套餐错误,请联系经销商"}) timeStamp = self._make_time_stamp() # TODO 还需要考虑充满自停的模式 chargeType = "01" maxPower = self.MAX_CHARGING_ELEC result = self._charging(port=portStr, chargeType=chargeType, timeStamp=timeStamp, maxTime=needTime, maxPower=maxPower) if result is None: raise ServiceException({"result": 2, "description": u"设备启动失败"}) finishedTime = timeStamp + needTime result.update({"finishedTime": finishedTime}) dataDict = { "status": Const.DEV_WORK_STATUS_WORKING, "finishedTime": result['finishedTime'], "coins": coins, "price": price, "needTime": needTime / 60, "port": portStr, "chargeType": chargeType, "openId": openId, "vCardId": self._vcard_id, "isStart": True, "needElec": maxPower, } Device.update_dev_control_cache(self._device["devNo"], {str(portStr): dataDict}) return result def get_port_status_from_dev(self): """ :return: """ portStatus = self._get_device_status() allPorts, usedPorts, usePorts = self.get_port_static_info(portStatus) Device.update_dev_control_cache(self._device['devNo'], {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts}) # 逐个更新端口状态 devCache = Device.get_dev_control_cache(self._device["devNo"]) for portStr, portInfo in portStatus.items(): if portStr in devCache: devCache[portStr].update(portInfo) else: devCache[portStr] = portInfo Device.update_dev_control_cache(self._device["devNo"], devCache) return portStatus def get_port_status(self, force = False): """ view 对接函数 :param force: :return: """ if force: return self.get_port_status_from_dev() devCache = Device.get_dev_control_cache(self._device['devNo']) statusDict = {} if not 'allPorts' in devCache: self.get_port_status_from_dev() devCache = Device.get_dev_control_cache(self._device['devNo']) allPorts = devCache.get('allPorts', 10) for ii in range(allPorts): tempDict = devCache.get(str(ii + 1), {}) if tempDict.has_key('status'): statusDict[str(ii + 1)] = {'status': tempDict.get('status')} elif tempDict.has_key('isStart'): if tempDict['isStart']: statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_WORKING} else: statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE} else: statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE} allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict) Device.update_dev_control_cache(self._device['devNo'], {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts}) return statusDict def get_dev_setting(self): """ 获取设备设置 对接view接口 :return: """ setConf = dict() portMaxPower = self._get_port_max_power() devMinPower = self._get_device_min_power() setConf.update(portMaxPower) setConf.update(devMinPower) # setConf.update({"portNum": len(portMaxPower.get("portMaxPower"))}) return setConf def set_device_function_param(self, request, lastSetConf): devMinPower = request.POST.get("devMinPower") if devMinPower: # 更新缓存 otherConf = self._device["otherConf"] otherConf.update({"devMinPower": devMinPower}) Device.objects.filter(devNo=self._device["devNo"]).update(otherConf=otherConf) Device.get_and_update_device_cache(self._device["devNo"], otherConf=otherConf) self._set_device_power_min(devMinPower) # 端口功率上限设置 portPower1 = request.POST.get("portPower1") if portPower1: baseName = "portPower{port}" portMaxDict = { baseName.format(port=str(i + 1)): request.POST.get(baseName.format(port=str(i + 1))) for i in xrange(10) } # 更新缓存 otherConf = self._device["otherConf"] otherConf.update(portMaxDict) Device.objects.filter(devNo=self._device["devNo"]).update(otherConf=otherConf) Device.get_and_update_device_cache(self._device["devNo"], otherConf=otherConf) self._set_port_max_power(portMaxDict) def set_device_function(self, request, lastSetConf): if request.POST.get("reboot"): self._reboot() def lock_unlock_port(self, port, lock=True): """ 禁用端口 经测试 设备不支持禁用端口 :param port: :param lock: :return: """ # devCache = Device.get_dev_control_cache(self._device["devNo"]) # portCache = devCache.get(str(port), {}) # # if lock and portCache.get("status", Const.DEV_WORK_STATUS_IDLE) != Const.DEV_WORK_STATUS_IDLE: # raise ServiceException({"result": "2", "description": u"端口非空闲不能设置禁用"}) # if not lock and portCache.get("status", Const.DEV_WORK_STATUS_IDLE) != Const.DEV_WORK_STATUS_FORBIDDEN: # raise ServiceException({"result": "2", "description": u"端口非禁用不能设置解除禁用"}) # # portName = "portPower{}".format(port) # portMaxPower = self._get_port_max_power() # # if lock: # portMaxPower[portName] = None # Device.update_dev_control_cache(self._device["devNo"], {str(port): {"status": Const.DEV_WORK_STATUS_IDLE}}) # # else: # otherConf = self._device["otherConf"] # portMaxPower[portName] = otherConf.get(portName, self.DEFAULT_POWER_MAX_LIMIT) # # Device.update_dev_control_cache(self._device["devNo"], {str(port): {"status": Const.DEV_WORK_STATUS_FORBIDDEN}}) # # self._set_port_max_power(portMaxPower) pass def analyze_event_data(self, data): """ 解析上报的报文 :param data: :return: """ cmdCode = data[36: 38].upper() # 结束充电 if cmdCode == "08": portStr = str(int(data[42: 44], 16)) chargeType = data[44: 46] timeStamp = int(reverse_hex(data[46: 54]), 16) spendTime = int(reverse_hex(data[54: 62]), 16) # spendElec = int(reverse_hex(data[62: 80]), 16) spendElec = unpack_float(data[62: 70], reverse=True) return { "port": portStr, "chargeType": chargeType, "timeStamp": timeStamp, "spendTime": spendTime, "spendElec": spendElec, "cmdCode": cmdCode, "portHex": data[42: 44], "timeStampHex": data[46: 54] } # 刷卡充电开始 elif cmdCode == "0C": portStr = str(int(data[42: 44], 16)) chargeType = data[44: 46] timeStamp = int(reverse_hex(data[46: 54]), 16) cardNoLen = int(data[54: 56], 16) * 2 cardNo = self._parse_card(data[56: 56+cardNoLen]) coins = round(unpack_float(data[56+cardNoLen: 64+cardNoLen], reverse=True), 2) return { "port": portStr, "chargeType": chargeType, "timeStamp": timeStamp, "cardNo": cardNo, "coins": coins, "cmdCode": cmdCode, "portHex": data[42: 44], "timeStampHex": data[46: 54] } # 刷卡充电结束 elif cmdCode == "0E": portStr = str(int(data[42: 44], 16)) chargeType = data[44: 46] timeStamp = int(reverse_hex(data[46: 54]), 16) spendTime = int(reverse_hex(data[54: 62]), 16) spendElec = unpack_float(data[62: 70], reverse=True) return { "port": portStr, "chargeType": chargeType, "timeStamp": timeStamp, "spendTime": spendTime, "spendElec": spendElec, "cmdCode": cmdCode, "portHex": data[42: 44], "timeStampHex": data[46: 54] } # 推送的报警信息 elif cmdCode == "12": timeStamp = int(reverse_hex(data[42: 50]), 16) warningCode = data[50: 52] return { "timeStamp": timeStamp, "warningCode": warningCode, "cmdCode": cmdCode, "timeStampHex": data[42: 50] } else: return