# coding=utf-8 """ 清远粤万通充电桩 2020-09-03修改 计费模式分为 按功率计费(服务器不断的算钱有没有用完, 服务器下发停止) 按时间计费(一次性下发时间,不需要服务器停止) 按电量计费(用到最后收集电量 进行判定) chargeType 收费模式分为 预付费方式(服务器先收钱, 收完了之后在启动设备, 如果存在没有使用完的钱 需要退还到用户的账户) 后付费方式(先不收钱, 等用户使用完了之后再计算总的钱,然后结束这笔订单,将所需的费用写入, 同时执行订单支付,如账户余额不足支付, 标记等待下次支付) 套餐模式分为 正常套餐(标记好多少钱,最多就用这么多) 充满自停套餐(不知道多少钱,反正一直充,充满为止,一般的处理方式是下发一个极大值) fill_2_hexByte 函数统一修改为 int_to_hex 在发送断电指令之后,主板不会新上报端口信息,而是直接结束 """ import binascii import datetime import logging import simplejson as json from pandas import Interval from apilib.monetary import RMB from apps.common.utils import int_to_hex from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT from apps.web.core.adapter.base import SmartBox, reverse_hex from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.device.models import Device, DevicePortReport from apps.web.user.models import ServiceProgress from apps.web.utils import concat_user_login_entry_url logger = logging.getLogger(__name__) class YueWanTong(SmartBox): PORT_STATUS_MAP = { "0": Const.DEV_WORK_STATUS_IDLE, "1": Const.DEV_WORK_STATUS_WORKING, "2": Const.DEV_WORK_STATUS_APPOINTMENT, "3": Const.DEV_WORK_STATUS_FAULT, "4": Const.DEV_WORK_STATUS_FAULT, "5": Const.DEV_WORK_STATUS_FAULT, "6": Const.DEV_WORK_STATUS_FAULT } DEFAULT_MIN_CHARGE_TIME = 5 DEFAULT_PORT_NUM = 12 DEFAULT_MIN_POWER = 20 DEFAULT_MAX_POWER = 500 DEFAULT_FLOAT_TIME = 2 DEFAULT_IC_CODE = "0001" DEFAULT_MIN_CONSUME = 0 DEFAULT_POWER_PACKAGE = [ { "lowLimit": 0, "upLimit": 250, "price": 1, }, { "lowLimit": 250, "upLimit": 360, "price": 1, }, { "lowLimit": 360, "upLimit": 500, "price": 1, } ] DEFAULT_ELEC_PRICE = 1 DEFAULT_PAY_AFTER_USE = False DEFAULT_CHARGE_TYPE = "time" # 默认的计费模式 是时间计费 (功率不分档) # 服务费电费 DEFAULT_ELEC_CHARGE = 0.5 DEFAULT_SERVICE_CHARGE = 0.5 @staticmethod def _suit_power_package(powerPackage): """ 对于功率套餐的适配 旧版本是 100-200w x 元 y 分钟 新版本是 100-200w x 元 1 小时 :param powerPackage: :return: """ newPowerPackage = list() for _item in powerPackage: # 旧版本的 if "time" not in _item: newPowerPackage.append(_item) else: # 做一个数据的适配 price = float(_item["price"]) / (int(_item["time"]) / 60.0) newPowerPackage.append({ "upLimit": _item["upLimit"], "lowLimit": _item["lowLimit"], "price": str(RMB(price)) }) return newPowerPackage @staticmethod def _to_ascii(s): """ 将 字符串 转换为 16进制ascii 表达形式 :param s: 原始字符串 :return: """ return binascii.hexlify(s) @staticmethod def _to_str(h): """ 将 16进制ascii 转换成 字符串 :param h: :return: """ return binascii.unhexlify(h) @staticmethod def _transform_time(needTime, unit): """ 根据单位 将套餐时间转换成分钟 :param needTime: int :param unit: unicode :return: """ if unit == u"小时": needTime = needTime * 60 elif unit == u"分钟": needTime = needTime elif unit == u"秒": needTime = needTime // 60 elif unit == u"次": needTime = None else: raise ServiceException({'result': 2, 'description': u'套餐错误'}) return needTime def _send_data(self, funCode, sendData, cmd=None, timeout=MQTT_TIMEOUT.NORMAL, orderNo=None): """ 发送报文封装 :param funCode: :param sendData: :param cmd: :param timeout: :param orderNo: :return: """ if cmd is None: cmd = DeviceCmdCode.OPERATE_DEV_SYNC result = MessageSender.send(device = self.device, cmd = cmd, payload = { "IMEI": self._device["devNo"], "funCode": funCode, "data": sendData }, timeout = timeout) if result.has_key("rst"): 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'系统错误'}) def _start_device(self, orderNo, port, chargeTime=None): """ 开始充电 :param orderNo: 下发的订单编号 :param port: 开启的端口号 :param chargeTime: 充电的最大时间,如果是充满自停,则设置为最大时间 :return: """ if chargeTime is None: chargeTime = 0xFFFF if orderNo is None: orderNo = 0 orderNoHex = int_to_hex(int(orderNo), 14, reverse=True) portHex = int_to_hex(int(port), 2, reverse=True) chargeTimeHex = int_to_hex(int(chargeTime), 4, reverse=True) sendData = "{}{}{}010000".format(orderNoHex, portHex, chargeTimeHex) result = self._send_data("B2", sendData, timeout=MQTT_TIMEOUT.START_DEVICE, orderNo=orderNo) return result def _stop_device(self, orderNo, port): """ 停止充电 :param orderNo: 提供开始充电的时候的订单号 :param port: 端口号 :return: """ orderNoHex = int_to_hex(int(orderNo), 14, reverse=True) portHex = int_to_hex(int(port), 2, reverse=True) sendData = "{}{}0000120000".format(orderNoHex, portHex) result = self._send_data("B2", sendData, timeout=MQTT_TIMEOUT.START_DEVICE, orderNo=orderNo) return result @staticmethod def _parse_device_stop(data): """ 主动下发停止指令设备的回复 与结束上报不一样 :param data: :return: """ statusMap = { "00": Const.DEV_WORK_STATUS_IDLE, "01": Const.DEV_WORK_STATUS_WORKING, "02": Const.DEV_WORK_STATUS_APPOINTMENT, "03": Const.DEV_WORK_STATUS_FAULT, # 故障 "04": Const.DEV_WORK_STATUS_FAULT, # 烟感 "05": Const.DEV_WORK_STATUS_FAULT, # 雨感 "06": Const.DEV_WORK_STATUS_FAULT # 设备锁定 } orderHexNo = reverse_hex(data[8:22]) portHex = data[22:24] voltage = int(reverse_hex(data[24:28]), 16) power = int(reverse_hex(data[32:36]), 16) elec = int(reverse_hex(data[36:44]), 16) / 1000.0 chargeTime = int(reverse_hex(data[44:48]), 16) status = statusMap.get(data[52: 54], Const.DEV_WORK_STATUS_IDLE) return { "orderNoHex": orderHexNo, "portHex": portHex, "orderNo": int(orderHexNo, 16), "portStr": str(int(portHex, 16)), "voltage": voltage, "power": power, "elec": elec, "chargeTime": chargeTime, "status": status } def _read_port_status(self): """ 从设备端读取端口状态 仅仅只包含状态量 :return: """ result = self._send_data("B3", sendData="00") data = result.get("data", "")[8: -4] lockPorts = self.device.otherConf.get("lockPorts") or list() portInfo = dict() while data: tempPort = str(int(data[0: 2], 16)) tempStatus = self.PORT_STATUS_MAP.get(data[34: 36], Const.DEV_WORK_STATUS_IDLE) if tempPort not in lockPorts else Const.DEV_WORK_STATUS_FORBIDDEN portInfo.update({tempPort: tempStatus}) data = data[4: ] return portInfo def _read_port_charge_status(self): """ 从设备读取设备端口的状态 详细数据,包含电流 功率 电压 电量 时间 等等 :return: """ result = self._send_data("B4", sendData="00") data = result.get("data", "")[8: -4] lockPorts = self.device.otherConf.get("lockPorts") or list() portInfo = dict() while data: tempPort = str(int(data[0: 2], 16)) tempVoltage = int(reverse_hex(data[2: 6]), 16) tempPower = int(reverse_hex(data[10: 14]), 16) tempElec = int(reverse_hex(data[14: 22]), 16) / 1000.0 tempTime = int(reverse_hex(data[22: 26]), 16) tempTemper = int(reverse_hex(data[30: 34]), 16) tempStatus = self.PORT_STATUS_MAP.get(data[34: 36], Const.DEV_WORK_STATUS_IDLE) if tempPort not in lockPorts else Const.DEV_WORK_STATUS_FORBIDDEN portInfo.update({ tempPort: { "voltage": tempVoltage, "elec": tempElec, "power": tempPower, "chargeTime": tempTime, "temperature": tempTemper, "status": tempStatus } }) data = data[36: ] return portInfo def _reboot_device(self): """ 重启设备 :return: """ result = self._send_data("B5", sendData="00") status = result["data"][8: 10] if status != "01": raise ServiceException({'result': 2, 'description': u'设备重启失败,请重新操作试试'}) def _lock_unlock_device(self, lock): """ 锁定/解锁设备 :param lock: True / False :return: """ # 设备锁定发送 00 / 解锁发送 01 sendData = int_to_hex(int(lock), 2, reverse=True) message = "设备锁定失败" if lock else "设备解锁失败" result = self._send_data("B6", sendData=sendData) status = result["data"][8: 10] if status != "01": raise ServiceException({'result': 2, 'description': u'{},请重新操作试试'.format(message)}) def _sync_device_time(self): """ 开机后设备向服务器请求同步时间,服务器回复设备 :return: """ nowTime = datetime.datetime.now() yearHex = int_to_hex(nowTime.year, 4, reverse=True) monthHex = int_to_hex(nowTime.month, 2) dayHex = int_to_hex(nowTime.day, 2) hourHex = int_to_hex(nowTime.hour, 2) minuteHex = int_to_hex(nowTime.minute, 2) secondHex = int_to_hex(nowTime.second, 2) sendData = yearHex + monthHex + dayHex + hourHex + minuteHex + secondHex + "00" self._send_data("A1", sendData=sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) def _sync_device_settings(self): """ 开机后设备向服务器请求同步参数 包括需要扫描的二维码,刷卡前缀 最小功率最大功率等 :return: """ otherConf = self._device.get("otherConf", dict()) # 扫描的设备二维码 qr_code_url = concat_user_login_entry_url(l = self._device["logicalCode"]) snCode = otherConf.get("snCode", self._device["logicalCode"].replace("G", "")) minPower = otherConf.get("minPower", YueWanTong.DEFAULT_MIN_POWER) maxPower = otherConf.get("maxPower", YueWanTong.DEFAULT_MAX_POWER) floatTime = otherConf.get("floatTime", YueWanTong.DEFAULT_FLOAT_TIME) ICSetupCode = otherConf.get("ICSetupCode", YueWanTong.DEFAULT_IC_CODE) qrCodeLenHex = int_to_hex(len(qr_code_url), 2) minPowerHex = int_to_hex(int(minPower), 4, reverse=True) maxPowerHex = int_to_hex(int(maxPower), 4, reverse=True) floatTimeHex = int_to_hex(int(floatTime), 4, reverse=True) qrCodeHex = self._to_ascii(qr_code_url) snCodeHex = self._to_ascii(snCode) ICSetupCodeHex = self._to_ascii(ICSetupCode) sendData = snCodeHex + minPowerHex + maxPowerHex + floatTimeHex + ICSetupCodeHex + qrCodeLenHex + qrCodeHex self._send_data("A2", sendData=sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) @staticmethod def _parse_card_start(data): """ 解析刷卡启动的数据 :param data: :return: """ cardPre = YueWanTong._to_str(data[8: 16]) cardNo = YueWanTong._to_str(data[16: 32]) portStr = str(int(data[32: 34], 16)) return { "cardPre": cardPre, "cardNo": cardNo, "portStr": portStr, "portStrHex": data[32:34], "cardCode": data[8: 32] } @staticmethod def _parse_card_stop(data): """ 解析刷卡结束 数据 :param data: :return: """ cardInfo = YueWanTong._parse_card_start(data) orderNoHex = data[34: 48] orderNo = str(int(reverse_hex(orderNoHex), 16)) cardInfo.update({ "orderNo": orderNo, "orderNoHex": orderNoHex }) return cardInfo @staticmethod def _parse_report(data): """ 设备 每10分钟 每个端口 上报一次充电状态 电压 电流 功率 端口状态 温度 是 当前值 电量 时间(断电保护) 是累计值 电流 暂时用不上 不用解析 :param data: :return: """ statusMap = { "00": Const.DEV_WORK_STATUS_IDLE, "01": Const.DEV_WORK_STATUS_WORKING, "02": Const.DEV_WORK_STATUS_APPOINTMENT, "03": Const.DEV_WORK_STATUS_FAULT, # 故障 "04": Const.DEV_WORK_STATUS_FAULT, # 烟感 "05": Const.DEV_WORK_STATUS_FAULT, # 雨感 "06": Const.DEV_WORK_STATUS_FAULT # 设备锁定 } orderNoHex = data[8: 22] orderNo = str(int(reverse_hex(orderNoHex), 16)) portStr = str(int(data[22: 24], 16)) voltage = int(reverse_hex(data[24: 28]), 16) power = int(reverse_hex(data[32: 36]), 16) elec = int(reverse_hex(data[36: 44]), 16) / 1000.0 chargeTime = int(reverse_hex(data[44: 48]), 16) temperature = int(reverse_hex(data[52: 56]), 16) status = statusMap.get(data[56: 58], Const.DEV_WORK_STATUS_IDLE) return { "orderNo": orderNo, "orderNoHex": orderNoHex, "portStr": portStr, "voltage": voltage, "power": power, "elec": elec, "chargeTime": chargeTime, "temperature": temperature, "status": status } @staticmethod def _parse_all_report(data): data = data[8: -4] result = list() while data: orderNoHex = data[: 14] orderNo = str(int(reverse_hex(data[:14]), 16)) portStr = str(int(data[14: 16], 16)) voltage = int(reverse_hex(data[16: 20]), 16) power = int(reverse_hex(data[24: 28]), 16) elec = int(reverse_hex(data[28: 36]), 16) / 1000.0 chargeTime = int(reverse_hex(data[36: 40]), 16) temperature = int(reverse_hex(data[44: 48]), 16) status = YueWanTong.PORT_STATUS_MAP.get(data[48: 50], Const.DEV_WORK_STATUS_IDLE) data = data[50: ] result.append({ "orderNo": orderNo, "orderNoHex": orderNoHex, "portStr": portStr, "voltage": voltage, "power": power, "elec": elec, "chargeTime": chargeTime, "temperature": temperature, "status": status }) return {"subChargeList": result} @staticmethod def _parse_fault(data): """ 解析故障上报数据 :param data: :return: """ # TODO zjl 目前只有三个,后续可能会增加 faultMap = { "1001": u"烟雾报警", "1002": u"雨感报警", "1003": u"温度报警" } portStr = str(int(data[8: 10], 16)) faultHex = data[10: 14] fault = str(int(faultHex[2: 4] + faultHex[: 2], 16)) faultReason = faultMap.get(fault) return { "portHex": data[8: 10], "portStr": portStr, "faultCode": fault, "statusInfo": faultReason } @staticmethod def _parse_stop(data): """ 解析充电结束上报事件,与状态上报类似 :param data: :return: """ reasonMap = { "00": u"充满自停", "01": u"未检测到充电设备", "02": u"过载结束", "03": u"电压输出故障", "04": u"刷卡结束", "05": u"充满自停", "06": u"密码开锁结束订单" } orderNoHex = data[8: 22] orderNo = str(int(reverse_hex(orderNoHex), 16)) portHex = data[22: 24] portStr = str(int(data[22: 24], 16)) voltage = int(reverse_hex(data[24: 28]), 16) power = int(reverse_hex(data[32: 36]), 16) elec = int(reverse_hex(data[36: 44]), 16) / 1000.0 chargeTime = int(reverse_hex(data[44: 48]), 16) temperature = int(reverse_hex(data[52: 56]), 16) reasonCode = data[56: 58] reason = reasonMap.get(reasonCode) return { "orderNoHex": orderNoHex, "orderNo": orderNo, "portHex": portHex, "portStr": portStr, "voltage": voltage, "power": power, "elec": elec, "chargeTime": chargeTime, "temperature": temperature, "reasonCode": reasonCode, "reason": reason } def _ack(self, ack_id): self._send_data(funCode='AK', sendData=ack_id, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) def get_port_status_from_dev(self): """ 从设备端 获取端口信息 充电/空闲 :return: """ portInfo = self._read_port_status() portDict = dict() for portStr, status in portInfo.items(): portDict[portStr] = {"status": status} # 更新可用端口数量 allPorts, usedPorts, usePorts = self.get_port_static_info(portDict) Device.update_dev_control_cache( self._device["devNo"], { "allPorts": allPorts, "usedPorts": usedPorts, "usePorts": usePorts } ) # 更新端口状态 devCache = Device.get_dev_control_cache(self._device["devNo"]) for port, info in portDict.items(): if port in devCache and isinstance(info, dict): devCache[port].update({"status": info["status"]}) else: devCache[port] = info Device.update_dev_control_cache(self._device["devNo"], devCache) return portDict def get_port_status(self, force = False): """ 获取端口状态 :param force: :return: """ self.get_port_status_from_dev() # 获取端口缓存 devCache = Device.get_dev_control_cache(self._device["devNo"]) if "allPorts" not in devCache: self.get_port_status_from_dev() devCache = Device.get_dev_control_cache(self._device["devNo"]) allPorts = devCache.get("allPorts") if allPorts is None: raise ServiceException({'result': 2, 'description': u'充电端口信息获取失败'}) # 获取显示端口的数量 客户要求可以设置 显示给用户多少个端口 showPortNum = self._device.get("otherConf", {}).get("actualPortNum", self.DEFAULT_PORT_NUM) statusDict = dict() showStatusDict = dict() for portNum in xrange(allPorts): portStr = str(portNum + 1) tempDict = devCache.get(portStr, {}) if "status" in tempDict: statusDict[portStr] = {"status": tempDict["status"]} elif "isStart" in tempDict: if tempDict["isStart"]: statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_WORKING} else: statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_IDLE} else: statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_IDLE} if int(portStr) <= int(showPortNum): showStatusDict[portStr] = {"status": statusDict[portStr]["status"]} allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict) portsDict = {"allPorts": allPorts, "usedPorts": usedPorts, "usePorts": usePorts} Device.update_dev_control_cache(self._device["devNo"], portsDict) # 返还的是要显示的端口数量 return showStatusDict def get_port_info(self, line=None): """ 获取单一的端口状态 :param line: :return: """ if line is None: return portStr = str(line) # 设备端读取端口状态 result = self._read_port_charge_status() portInfo = result.get(portStr, {}) # 缓存端读取设备端口状态 devCache = Device.get_dev_control_cache(self._device["devNo"]) portCache = devCache.get(portStr, {}) portCache.update(portInfo) Device.update_dev_control_cache(self._device["devNo"], {portStr: portCache}) if portCache.get('chargeType') == 'billAsService': elecCharge = round(self.device.otherConf.get("elecCharge", YueWanTong.DEFAULT_ELEC_CHARGE), 2) serviceCharge = round(self.device.otherConf.get("serviceCharge", YueWanTong.DEFAULT_SERVICE_CHARGE), 2) portCache['elecFee'] = round(portInfo.get('elec', 0) * elecCharge, 2) portCache['serviceFee'] = round(portInfo.get('elec', 0) * serviceCharge, 2) return portCache def get_dev_setting(self): """ 读取设备设置 显示的有: IC卡设置码 最小充电功率 最大充电功率 浮充时间 显示的端口数量 :return: """ otherConf = self._device.get("otherConf", {}) powerPackage = otherConf.get("powerPackage", YueWanTong.DEFAULT_POWER_PACKAGE) newPowerPackage = YueWanTong._suit_power_package(powerPackage) payAfterUse = int(otherConf.get("payAfterUse", False)) # 前台接受的是数值模型 # 电费 + 服务费 elecCharge = round(otherConf.get("elecCharge", YueWanTong.DEFAULT_ELEC_CHARGE), 2) serviceCharge = round(otherConf.get("serviceCharge", YueWanTong.DEFAULT_SERVICE_CHARGE), 2) return { "minPower": otherConf.get("minPower", YueWanTong.DEFAULT_MIN_POWER), "maxPower": otherConf.get("maxPower", YueWanTong.DEFAULT_MAX_POWER), "floatTime": otherConf.get("floatTime", YueWanTong.DEFAULT_FLOAT_TIME), "ICSetupCode": otherConf.get("ICSetupCode", YueWanTong.DEFAULT_IC_CODE), "actualPortNum": otherConf.get("actualPortNum", YueWanTong.DEFAULT_PORT_NUM), "minConsume": otherConf.get("minConsume", YueWanTong.DEFAULT_MIN_CONSUME), "refundProtectTime": otherConf.get("refundProtectTime", YueWanTong.DEFAULT_MIN_CHARGE_TIME), "minAfterStartCoins": otherConf.get("minAfterStartCoins", 2), "payAfterUse": payAfterUse, "chargeType": otherConf.get("chargeType", YueWanTong.DEFAULT_CHARGE_TYPE), "elecPrice": otherConf.get("elecPrice", YueWanTong.DEFAULT_ELEC_PRICE), "powerPackage": newPowerPackage, # 电费 + 服务费 "elecCharge": elecCharge, "serviceCharge": serviceCharge, } def update_device_conf(self, data): minPower = data.get("minPower") maxPower = data.get("maxPower") floatTime = data.get("floatTime") ICSetupCode = data.get("ICSetupCode") actualPortNum = data.get("actualPortNum") minConsume = data.get("minConsume") payAfterUse = data.get("payAfterUse") refundProtectTime = data.get("refundProtectTime") minAfterStartCoins = data.get("minAfterStartCoins") powerPackage = data.get("powerPackage") chargeType = data.get("chargeType") elecPrice = data.get("elecPrice") # 电费 + 服务费 elecCharge = round(data.get("elecCharge", YueWanTong.DEFAULT_ELEC_CHARGE), 2) serviceCharge = round(data.get("serviceCharge", YueWanTong.DEFAULT_SERVICE_CHARGE), 2) otherConf = self._device.get("otherConf", {}) otherConf.update({ "minPower": int(minPower), "maxPower": int(maxPower), "floatTime": int(floatTime), "ICSetupCode": ICSetupCode, "actualPortNum": int(actualPortNum), "minConsume": float(minConsume), "minAfterStartCoins": float(minAfterStartCoins), "refundProtectTime": int(refundProtectTime), "payAfterUse": bool(int(payAfterUse)), "chargeType": chargeType, "elecPrice": float(elecPrice), "powerPackage": powerPackage, # 服务费 + 电量 "elecCharge": elecCharge, "serviceCharge": serviceCharge, }) devNo = self._device["devNo"] try: Device.objects.filter(devNo=devNo).update(otherConf=otherConf) except Exception as e: logger.error(e) raise ServiceException({'result': 2, 'description': u'设置错误,请重新操作试试'}) Device.invalid_device_cache(devNo) def set_device_function(self, request, lastSetConf): """ 设备功能 :param request: :param lastSetConf: :return: """ lockDevice = request.POST.get("lockDevice", None) rebootDevice = request.POST.get("rebootDevice", None) setToDevice = request.POST.get("setToDevice", None) if rebootDevice is not None: self._reboot_device() return if lockDevice is not None and lockDevice in ("false", "true"): lockDevice = json.loads(lockDevice) self._lock_unlock_device(lockDevice) return # 下发所有参数到设备,这个地方做一个保护 ,设备正在工作的时候不让设置参数 if setToDevice is not None: self._sync_device_settings() return def set_device_function_param(self, request, lastSetConf): """ 设置设备参数 需要对参数规则做校验 :param request: :param lastSetConf: :return: """ # 校验端口数量 actualPortNum = request.POST.get("actualPortNum", None) if actualPortNum is not None and int(actualPortNum) > 36: raise ServiceException({"result": 2, "description": u"实际端口数量最大36"}) powerPackage = request.POST.get("powerPackage", None) if powerPackage is not None: if len(powerPackage) > 6: raise ServiceException({"result": 2, "description": u"功率计费不得大于六档"}) # 这个地方开始各种计费模式收费模式 chargeType = request.POST.get("chargeType") if chargeType not in ["power", "time", "elec", "billAsService"]: raise ServiceException({"result": 2, "description": u"不支持的计费模式"}) self.update_device_conf(request.POST) def start_device(self, package, openId, attachParas): """ :param package: :param openId: :param attachParas: :return: """ # 在此次改版的时候, 凡是 payAfterUse == True 的设备 都说明是使用了按功率付费的设备,这个地方做一个转换 为下一步切换做准备 otherConf = self.device.get("otherConf", dict()) if "chargeType" not in otherConf: if otherConf.get("payAfterUse", YueWanTong.DEFAULT_PAY_AFTER_USE): otherConf["chargeType"] = "power" else: otherConf["chargeType"] = "time" Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf) Device.invalid_device_cache(self.device.devNo) # 由于参数重新更新过了 这个地方就在加载一次 dev = Device.get_dev(self.device.devNo) self._device = dev # 从启动参数中获取相关的 参数 订单号 以及 充电端口等等 portStr = attachParas.get("chargeIndex") orderNo = attachParas.get("orderNo") if portStr is None: raise ServiceException({'result': 2, 'description': u'未知端口'}) lockPorts = self.device.otherConf.get("lockPorts") or list() if str(portStr) in lockPorts: raise ServiceException({"result": 2, "description": u"当前端口已被禁用"}) # 计算充电时间 如果套餐是充满自停 则会在启动的时候设置为最大值 unit = package.get("unit") needTime = package.get("time") coins = package.get("coins") needTime = self._transform_time(needTime, unit) # 获取当前的计费模式 otherConf = self.device.get("otherConf", dict()) chargeType = otherConf.get("chargeType") payAfterUse = otherConf.get("payAfterUse") if chargeType == "time" and not needTime: raise ServiceException({"result": 2, "description": u"时间套餐设置错误,请联系经销商解决"}) # 发送指令 result = self._start_device(orderNo=orderNo, port=portStr, chargeTime=needTime) portDict = { "status": Const.DEV_WORK_STATUS_WORKING, "vCardId": self._vcard_id, "isStart": True, "openId": openId, "orderNo": orderNo, "coins": coins, "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "chargeType": chargeType, "payAfterUse": payAfterUse } if needTime: portDict["needTime"] = needTime Device.update_dev_control_cache(self._device["devNo"], {str(portStr): portDict}) result["consumeOrderNo"] = orderNo return result def analyze_event_data(self, data): """ 解析设备上报的各种事件 :param data: :return: """ funcCode = data[6: 8] # 主板请求同步时间 if funcCode == "A1": self._sync_device_time() return # 主板请求同步设备参数 elif funcCode == "A2": self._sync_device_settings() return # 刷卡请求充电 elif funcCode == "B0": ret = self._parse_card_start(data) ret.update({'cmdCode': 'B0'}) return ret # 刷卡结束充电 elif funcCode == "B1": ret = self._parse_card_stop(data) ret.update({'cmdCode': 'B1'}) return ret # 设备上报充电结束 elif funcCode == "C1": ret = self._parse_stop(data) ret.update({'cmdCode': 'C1'}) return ret # 端口状态上报 elif funcCode == "C0": ret = self._parse_report(data) ret.update({'cmdCode': 'C0'}) return ret elif funcCode == "CA": ret = self._parse_all_report(data) ret.update({'cmdCode': 'CA'}) return ret # 故障上报 elif funcCode == "E0": ret = self._parse_fault(data) ret.update({'cmdCode': 'E0'}) return ret else: logger.error("error event! funCode is {}".format(funcCode)) def _get_time_use_money(self, reportRecord): # type:(DevicePortReport) -> float """ 时间计费模式下 预付费的充电时间由 下发的参数进行控制 这个地方不再予以控制 :param reportRecord: :return: """ portCache = Device.get_port_control_cache(self.device.devNo, reportRecord.port) needTime = portCache.get("needTime") # 单位分钟 coins = portCache["coins"] # 时间模式下一定会有 needTime 如果没有needTime 一定是出错了 做个保护 if not needTime: return coins return coins * float(reportRecord.chargeTime) / float(needTime) def _get_elec_use_money(self, reportRecord): # type:(DevicePortReport) -> float """ 电量计费模式下 预付费模式下的已经使用的金额 :param reportRecord: :return: """ usedElec = reportRecord.elec elecPrice = self.device.get("otherConf", dict()).get("elecPrice", self.DEFAULT_ELEC_PRICE) return usedElec * elecPrice def _get_power_use_money(self, reportRecord): # type:(DevicePortReport) -> float records = DevicePortReport.billing_records(reportRecord.orderNo) intervalMap = self._get_power_interval_map() consumeMoney = DevicePortReport.calculate(records, intervalMap) return consumeMoney def _get_billAsService_use_money(self, reportRecord): """ 服务费 + 电费模式 :param reportRecord: :return: """ usedElec = reportRecord.elec elecCharge = round( float(self.device.get("otherConf", dict()).get("elecCharge", self.DEFAULT_ELEC_CHARGE)) * usedElec, 2) serviceCharge = round(float( self.device.get("otherConf", dict()).get("serviceCharge", self.DEFAULT_SERVICE_CHARGE)) * usedElec, 2) return elecCharge, serviceCharge def _get_power_interval_map(self): """ 获取计费区间---单价映射 :return: """ if hasattr(self, "_interval_map"): return self._interval_map powerPackage = self.device.get( "otherConf", dict() ).get("powerPackage", self.DEFAULT_POWER_PACKAGE) powerPackage = self._suit_power_package(powerPackage) mapDict = dict() for package in powerPackage: lowLimit = package.get("lowLimit") upLimit = package.get("upLimit") price = package.get("price") _time = package.get("time") mapDict.update({ Interval(int(lowLimit), int(upLimit), closed = "left"): { "unitPrice": float(price), "time": 0 } }) setattr(self, "_interval_map", mapDict) return mapDict def stop(self, port = None): portCache = Device.get_port_control_cache(self.device.devNo, str(port)) orderNo = portCache.get('orderNo', 0) openId = portCache.get('openId', None) if orderNo == 0 or openId is None: raise ServiceException({'result': 2, 'description': u'未知订单无法停止,请稍后再试。'}) result = self._stop_device(orderNo, str(port)) event_data = self.__parse_B2(result) from apps.web.eventer.yuewantong import YueWanTongWorkerEvent ywt = YueWanTongWorkerEvent(self,event_data) ywt.do() ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'open_id': openId, 'device_imei': self.device['devNo'], 'port': int(port), 'isFinished': False },{}) infoDict = dict() infoDict['remainder_time'] = 0 return infoDict @property def isHaveStopEvent(self): return True def __parse_B2(self,data): data = data.get("data") dataDict = { "cmdCode" : data[6:8], "orderNoHex" : data[8:22], "orderNo" :str(int(reverse_hex(data[8:22]), 16)), "portHex" : data[22:24], "portStr" : str(int(reverse_hex(data[22:24]), 16)), "voltage" : str(int(reverse_hex(data[24:28]), 16)), "power" : str(int(reverse_hex(data[32:36]), 16)), "elec" : int(reverse_hex(data[36:44]), 16) / 1000.0, "chargeTime" : int(reverse_hex(data[44:48]), 16), "reason" : u"远程手动停止充电!" } return dataDict def lock_unlock_port(self, port, lock=True): """ 禁用端口 主板不支持 禁用端口 只能服务器实现 """ otherConf = self.device.get("otherConf") lockPorts = otherConf.get("lockPorts", list()) port = str(port) try: if lock: if port not in lockPorts: lockPorts.append(port) else: lockPorts.remove(port) except Exception as e: logger.error(e) otherConf.update({"lockPorts": lockPorts}) try: Device.objects(devNo=self.device.devNo).update(otherConf=otherConf) Device.invalid_device_cache(self.device.devNo) except Exception as e: logger.error("update device %s lockPorts error the reason is %s " % (self.device.devNo, e)) raise ServiceException({'result': 2, 'description': u'操作失败,请重新试试'})