# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import time import uuid from apilib.monetary import RMB from apilib.utils_datetime import timestamp_to_dt from apps.web.constant import MQTT_TIMEOUT, DeviceCmdCode, Const from apps.web.common.models import TempValues from apps.web.core.adapter.base import SmartBox, fill_2_hexByte, start_error_timer from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.dealer.models import Dealer from apps.web.device.models import Device, Group from taskmanager.mediator import task_caller logger = logging.getLogger(__name__) class SendFunCode(object): GET_DEV_SETTINGS = "D0" SET_DEV_SETTINGS = "D1" START_PAY = "D2" SYNC_CARD_BALANCE = "D3" BEFORE_SYNC_CARD_BALANCE = "D4" RESET_DEV = "D5" STOP_DEV = "D6" SET_GROUP_PW = "DB" SET_SEND_CARD = "DC" ANSWER_CMD = "DF" SET_DEV_DISABLE = "DD" class AnalyzeCode(object): pass class ChargingCY4Box(SmartBox): START_TYPE_MAP = { "00": "card", "01": "mobile", "02": "coin" } STOP_TYPE_MAP = { "01": u"充满停止充电", "02": u"超出功率停止充电", "03": u"充电时间结束停止充电", "04": u"远程停止充电" } NEED_REFUND = ["01", "04"] FULL_REFUND_TIME = 180 MAX_TEMPERATURE = 70 MIN_TEMPERATURE = -15 DEFAULT_DISCOUNT = "0" REFUND_PRODUCTION_TIME = 5 VIR_CARD_TIME = 300 def __init__(self, device): super(ChargingCY4Box, self).__init__(device) 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 test(self, coins): """ 测试端口 测试机器启动 固定端口为 01 """ hexCoins = fill_2_hexByte(hex(int(float(coins))), 2) hexPort = "01" data = hexCoins + "0000" + hexPort return self.send_data(SendFunCode.START_PAY, data) def start_device(self, package, openId, attachParas): """ 指令实例 {"IMEI":"865650042848537", "cmd":210, "funCode": "D2", "data": "1E(金额)0000(预留)0(折扣)3(端口)"} """ # TODO 这个版本先将 续充的入口封死 然后运行一段时间 下次发布的时候就可以彻底将流程归并 if attachParas is None: raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路、电池类型信息'}) if "chargeIndex" not in attachParas: raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'}) # 参数获取 price = package.get("price") coins = package.get("coins") chargeIndex = attachParas.get("chargeIndex") devCache = Device.get_dev_control_cache(self.device.devNo) portCache = devCache.get(str(chargeIndex)) # 跟CY进行沟通 由于指令时序的不稳定 去掉续充保证稳定 彻底的将续充的口子封死 if portCache and "status" in portCache and portCache["status"] != Const.DEV_WORK_STATUS_IDLE: raise ServiceException({'result': 2, 'description': u'当前端口正在使用中,请选择其他端口进行充电'}) # 获取充值订单的ID 用来判断是现金启动还是金币启动 rechargeRcdId = str(attachParas.get("linkedRechargeRecordId", "")) # 首先获取折扣信息 以及端口信息 共一个字节 hexDiscount = self.get_discount(rechargeRcdId) hexPort = hexDiscount + fill_2_hexByte(hex(int(chargeIndex)), 1) # 获取下发设备的支付金额, 单位是角 共一个字节 hexCoins = fill_2_hexByte(hex(int(float(coins * 10))), 2) # 如果是虚拟卡(包月卡启动设备),获取经销商设置的虚拟卡启动时间 timeHex = "0000" if self._vcard_id: _time = self._device.get("otherConf", dict()).get("vCardTime", self.VIR_CARD_TIME) timeHex = fill_2_hexByte(hex(int(_time))) # 拼接发送的指令数据域 dataHex = hexCoins + timeHex + hexPort result = self.send_data(SendFunCode.START_PAY, dataHex, timeout = MQTT_TIMEOUT.START_DEVICE) data = result.get("data") if data[6: 8] != '4F': # 表示成功 raise ServiceException({'result': 2, 'description': u'启动充电端口失败,您的金币还在,您重新启动试试看'}) # 非续充 实现续充模式的时候 需要对支付类型限定判断 只能同类型支付模式的续充 即只支持mobile模式的续充 # 先设置启动时间为0 后续会更新上来 start_timestamp = int(time.time()) result['finishedTime'] = start_timestamp + 12 * 60 * 60 # 由于不会有续充了 重新清理下端口缓存 然后再放入 Device.clear_port_control_cache(self.device.devNo, chargeIndex) # TODO 缓存的数据结构还是保持不变 保证下个版本平稳过渡 cacheDict = { "startTime": timestamp_to_dt(start_timestamp).strftime("%Y-%m-%d %H:%M:%S"), "status": Const.DEV_WORK_STATUS_WORKING, 'finishedTime': result['finishedTime'], "coins": float(coins), "price": float(price), "openId": openId, "consumeType": "mobile", "payInfo": [{ "vCardId": self._vcard_id, "coins": float(coins), "price": float(price), "rechargeRcdId": rechargeRcdId, "needTime": 0, }], "waitD9": True } Device.update_dev_control_cache(self._device["devNo"], {str(chargeIndex): cacheDict}) # 如果是正常启动的话 if openId: result["rechargeRcdId"] = rechargeRcdId return result def apiStartDeviceForCy4(self, record): price = record['price'] # int discount = record.get('discount', 0) # int port = record['port'] # int extOrderNo = record.get('extOrderNo', '') hexCoins = fill_2_hexByte(hex(int(float(price * 10))), 2) timeHex = '0000' hexPort = str(discount) + fill_2_hexByte(hex(int(port)), 1) dataHex = hexCoins + timeHex + hexPort result = self.send_data(SendFunCode.START_PAY, dataHex, timeout=MQTT_TIMEOUT.START_DEVICE) data = result.get("data") if data[6: 8] != '4F': # 表示成功 raise ServiceException({"result": 2, "description": u"启动失败,请稍后再试试"}) cacheDict = { 'isAPI': True, 'orderNo': 'MOBILE' + str(uuid.uuid4()), 'startTime': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'status': Const.DEV_WORK_STATUS_WORKING, 'price': float(price), 'coins': float(price), 'consumeType': 'mobile', 'waitD9': True, 'extOrderNo': extOrderNo } Device.update_dev_control_cache(self._device["devNo"], {str(port): cacheDict}) return result def apiGetDevSettingsFromCy4(self, record): result = self.get_dev_setting() result.pop('temperature') result.pop('disableButton') result.pop('discount') result.pop('vCardTime') return result def apiSetDevSettingsFromCy4(self, record): self.set_dev_setting(record) return {} def apiStopChargingPortForCy4(self, record): port = record['port'] self.stop_charging_port(port) return {} def apiGetPortStatusFromCy4(self, record): return self.get_port_status_from_dev() def apiGetPortInfoFromCy4(self, record): return self.get_port_info(record['port']) def get_discount(self, rechargeRcdId): """ 根据启动方式的不通获取不同的折扣 金币启动的不打折 其余的根据设备的折扣而定 """ if rechargeRcdId: return self._device.get("otherConf", dict()).get("discount", self.DEFAULT_DISCOUNT) else: return self.DEFAULT_DISCOUNT def stop_charging_port(self, port): """ 指令实例 {"IMEI":"865650042848537", "cmd":210, "funCode": "D6", "data": "03(端口)"} """ portHex = fill_2_hexByte(hex(int(port)), 2) result = self.send_data(SendFunCode.STOP_DEV, data=portHex) data = result.get("data") if data[6: 8] != "4F": raise ServiceException({"result": 2, "description": u"停止充电失败,请重新试试"}) return {'port': port} def stop(self, port=None): """ :param port: :return: """ devCache = Device.get_dev_control_cache(self._device.get("devNo")) portCache = devCache.get(str(port), dict()) if not portCache.get("coins"): raise ServiceException({'result': 2, 'description': u'无法停止该端口,请重新试试或者联系经销商'}) infoDict = self.stop_charging_port(port) return infoDict @property def isHaveStopEvent(self): return True def get_port_status_from_dev(self): devCache = Device.get_dev_control_cache(self._device["devNo"]) allPorts = devCache.get("allPorts", 10) portInfo = {} for i in xrange(allPorts): portStr = str(i+1) portTemp = devCache.get(portStr) if not portTemp: portTemp = {"status": Const.DEV_WORK_STATUS_IDLE} portInfo[portStr] = portTemp return portInfo def get_temperature(self, tempHex): if tempHex[0: 2] == "00": temperature = int(tempHex[2: 4], 16) else: temperature = -int(tempHex[2: 4], 16) return temperature def parse_port_data(self, data): """ 解析端口上传的数据 :param data: :return: """ # 每一路都空闲的时候,只上报温度 2个字节 if data[4: 6] == "02": portInfo = {str(portNum+1): {"status": Const.DEV_WORK_STATUS_IDLE} for portNum in xrange(10)} temperature = self.get_temperature(data[6: 10]) else: statusHex = data[6: 26] powerHex = data[26: 66] leftHex = data[66: 106] temperature = self.get_temperature(data[106: 110]) portInfo = dict() for portNum in xrange(10): tempStatus = statusHex[portNum*2: portNum*2+2] tempPower = powerHex[portNum*4: portNum*4+4] tempLeft = leftHex[portNum*4: portNum*4+4] if tempStatus == "00": status = Const.DEV_WORK_STATUS_IDLE elif tempStatus == "01": status = Const.DEV_WORK_STATUS_WORKING elif tempStatus == "04": status = Const.DEV_WORK_STATUS_FAULT_RELAY_CONNECT else: logger.error("error port status, device is %s, port is %s, statusHex is %s" % (self._device["devNo"], str(portNum+1), tempStatus)) status = Const.DEV_WORK_STATUS_IDLE power = int(tempPower, 16) leftTime = int(tempLeft, 16) portInfo[str(portNum+1)] = {"status": status, "power": power} if leftTime: portInfo[str(portNum+1)].update({"leftTime": leftTime}) return {"temperature": temperature, "portInfo": portInfo} def analyze_event_data(self, data): cmdCode = data[2:4] #: D7 火警报警 if cmdCode == 'D7': return {'cmdCode': cmdCode} #: 状态上报 elif cmdCode == 'D8': resultDict = self.parse_port_data(data) # 处理静态端口相关事情 allPorts, usedPorts, usePorts = self.get_port_static_info(resultDict.get("portInfo")) Device.update_dev_control_cache(self._device['devNo'], {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts}) resultDict.update({"cmdCode": cmdCode}) return resultDict elif cmdCode == 'D9': cardNo = data[6: 14] cardBalance = int(data[14:18], 16) power = int(data[18:22], 16) coins = int(data[22:24], 16) result = True if data[24:26] == '00' else False port = str(int(data[26:28], 16)) consumeType = self.START_TYPE_MAP.get(data[28:30]) needTime = int(data[30:34], 16) return { 'cardNo': cardNo, 'cmdCode': cmdCode, 'balance': cardBalance, 'power': power, 'coins': coins, 'result': result, 'port': port, 'consumeType': consumeType, 'needTime': needTime, 'data':data } elif cmdCode == 'DA': port = str(int(data[6: 8], 16)) reasonCode = data[8: 10] reason = self.STOP_TYPE_MAP.get(reasonCode) elec = int(data[10: 14], 16) / 1000.0 leftTime = int(data[14: 18], 16) return {'port': port, 'reason': reason, 'spendElec': elec, 'leftTime': leftTime, "cmdCode": cmdCode, "reasonCode": reasonCode} elif cmdCode == 'D4': # 刷卡充值 cardNo = data[6: 14] cardBalance = int(data[14:18], 16) return { 'cmdCode': cmdCode, 'cardNo': cardNo, 'balance': cardBalance } elif cmdCode == 'D3': cardNo = data[6:14] cardBalance = int(data[14:18], 16) result = True if data[18:20] == 'AA' else False sid = str(int(data[20:24], 16)) return {'cmdCode':cmdCode,'cardNo':cardNo,'balance':cardBalance,'result':result,'sid':sid} def get_port_status(self, force = False): devCache = Device.get_dev_control_cache(self._device['devNo']) statusDict = dict() allPorts = devCache.get('allPorts', 10) for portNum in range(allPorts): tempDict = devCache.get(str(portNum + 1), {}) if "status" in tempDict: statusDict[str(portNum + 1)] = {'status': tempDict.get('status')} elif "isStart" in tempDict: if tempDict['isStart']: statusDict[str(portNum + 1)] = {'status': Const.DEV_WORK_STATUS_WORKING} else: statusDict[str(portNum + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE} else: statusDict[str(portNum + 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 active_deactive_port(self, port, active): if not active: self.stop_charging_port(port) else: raise ServiceException({'result': 2, 'description': u'此设备不支持直接打开端口'}) # 获取设备配置参数 def get_dev_setting(self): result = self.send_data(SendFunCode.GET_DEV_SETTINGS) confData = result.get("data")[6:] time1 = int(confData[0:4], 16) time2 = int(confData[4:8], 16) time3 = int(confData[8:12], 16) time4 = int(confData[12:16], 16) powerMax1 = int(confData[16:20], 16) powerMax2 = int(confData[20:24], 16) powerMax3 = int(confData[24:28], 16) powerMax4 = int(confData[28:32], 16) elecCheckMin = int(confData[32:34], 16) elecCheckTime = int(confData[34:36], 16) voice = int(confData[36:38], 16) devCache = Device.get_dev_control_cache(self.device.devNo) temperature = devCache.get("temperature", 0) resultDict = { 'time1': time1, 'time2': time2, 'time3': time3, 'time4': time4, 'powerMax1': powerMax1, 'powerMax2': powerMax2, 'powerMax3': powerMax3, 'powerMax4': powerMax4, 'elecCheckMin': elecCheckMin, 'elecCheckTime': elecCheckTime, 'voice': voice, "temperature": temperature } resultDict.update({ "disableButton": int(self._device.get("otherConf", {}).get("disableDevice", False)), "discount": self._device.get("otherConf", dict()).get("discount", self.DEFAULT_DISCOUNT), "vCardTime": self._device.get("otherConf", dict()).get("vCardTime", self.VIR_CARD_TIME), "needBindCard": self._device.get("otherConf", dict()).get("needBindCard", True), }) return resultDict # 设置设备配置参数 def set_dev_setting(self, setConf): data = '' data += fill_2_hexByte(hex(setConf['time1']), 4) data += fill_2_hexByte(hex(setConf['time2']), 4) data += fill_2_hexByte(hex(setConf['time3']), 4) data += fill_2_hexByte(hex(setConf['time4']), 4) data += fill_2_hexByte(hex(setConf['powerMax1']), 4) data += fill_2_hexByte(hex(setConf['powerMax2']), 4) data += fill_2_hexByte(hex(setConf['powerMax3']), 4) data += fill_2_hexByte(hex(setConf['powerMax4']), 4) data += fill_2_hexByte(hex(setConf['elecCheckMin']), 2) data += fill_2_hexByte(hex(setConf['elecCheckTime']), 2) data += fill_2_hexByte(hex(setConf['voice']), 2) if int(setConf["time1"]) < 10 or setConf["time2"] < 10 or setConf["time3"] < 10 or setConf["time4"] < 10: raise ServiceException({"result": 2, "description": "功率时间最小为 10 分钟"}) result = self.send_data(SendFunCode.SET_DEV_SETTINGS, data=data) data = result.get("data") if data[6: 10] != "4F4B": raise ServiceException({"result": 2, "description": u"参数设置失败,请重新试试!"}) def reboot_dev(self): """ 重启设备 """ self.send_data(SendFunCode.RESET_DEV, data="0000") @staticmethod def transform_password(password): passwordHex = "" while password: passwordHex += fill_2_hexByte(hex(int(password[: 2])), 2) password = password[2:] return passwordHex def set_password(self, setConf): """ 设置设备的小区密码 密码是否还需要再校验,以及是否是0开头 """ password = setConf.get("pwd") if len(password) != 10: raise ServiceException({"result": 0, "description": u"密码长度必须为10位"}) passwordHex = self.transform_password(password) self.send_data(SendFunCode.SET_GROUP_PW, data=passwordHex) def set_send_card(self, setConf): """ 设置卡机模式 用于重置 实体卡的小区密码 :param setConf: :return: """ # TODO 同上 需要设置开关 oldPassword = setConf.get("old_pwd") newPassword = setConf.get("new_pwd") if len(oldPassword) != 10 or len(newPassword) != 10: raise ServiceException({"result": 0, "description": u"密码长度必须为10位"}) oldPasswordHex = self.transform_password(oldPassword) newPasswordHex = self.transform_password(newPassword) self.send_data(SendFunCode.SET_SEND_CARD, data=oldPasswordHex+newPasswordHex) # 给实体卡充值 def recharge_card(self, cardNo, money, orderNo=None): # type:(str,RMB,str)->(dict, RMB) """ # TODO zjl 重点测试 昌原卡充值流程大致为 手机端卡充值成功,数据成功记录服务器 实体卡靠近 充值机器 然后主板发送 D4 指令 上传卡号以及卡的余额 (上抛事件) 服务器接受指令之后,判断是否正常卡,不是正常卡 下发同步余额为0 没有充过值 直接过滤(异步指令) 桩接受之后,返回同步状态 (指令模块压栈, 上抛事件) 服务器接受之后,返回 DF 指令(异步指令) 整个流程结束 {"IMEI":"865650042848537", "cmd": 210, "data": "01090001", "funCode": "D3"} :param **kwargs: """ # 获取充值的标志编号 sidKey = '%s-%s' % (self._device['devNo'], cardNo) sid = TempValues.get(sidKey) # 数据域为卡内当前总金额(元) + 订单编号 dataHex = fill_2_hexByte(hex(int(money)), 4) + fill_2_hexByte(hex(int(sid)), 4) MessageSender.send(device = self.device, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, payload = { "IMEI": self._device["devNo"], "funCode": SendFunCode.SYNC_CARD_BALANCE, "data": dataHex }) def ack_event(self): """ 应答报文 应答D7 D9 DA D3 指令 :return: """ MessageSender.send(device = self.device, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, payload = { "IMEI": self._device["devNo"], "funCode": SendFunCode.ANSWER_CMD, "data": "4F4B" }) def get_port_info(self, line=None): devCache = Device.get_dev_control_cache(self._device["devNo"]) return devCache.get(str(line), {}) def set_device_function_param(self, request, lastSetConf): time1 = request.POST.get('time1', None) time2 = request.POST.get('time2', None) time3 = request.POST.get('time3', None) time4 = request.POST.get('time4', None) powerMax1 = request.POST.get('powerMax1', None) powerMax2 = request.POST.get('powerMax2', None) powerMax3 = request.POST.get('powerMax3', None) powerMax4 = request.POST.get('powerMax4', None) elecCheckMin = request.POST.get('elecCheckMin', None) elecCheckTime = request.POST.get('elecCheckTime', None) voice = request.POST.get('voice', None) pw = request.POST.get("pwd", None) oldPw = request.POST.get("old_pwd", None) newPw = request.POST.get("new_pwd", None) discount = request.POST.get("discount") vCardTime = request.POST.get("vCardTime") maxTime = request.POST.get("maxTime") # 功率需要从小到大排序,这个地方要做一个校验 if all([powerMax1, powerMax2, powerMax3, powerMax4]) and not int(powerMax1) < int(powerMax2) < int(powerMax3) < int(powerMax4): raise ServiceException({"result": 0, "description": u"功率参数必须从小到大设置!"}) if time1: lastSetConf.update({'time1': int(time1)}) if time2: lastSetConf.update({'time2': int(time2)}) if time3: lastSetConf.update({'time3': int(time3)}) if time4: lastSetConf.update({'time4': int(time4)}) if powerMax1: lastSetConf.update({'powerMax1': int(powerMax1)}) if powerMax2: lastSetConf.update({'powerMax2': int(powerMax2)}) if powerMax3: lastSetConf.update({'powerMax3': int(powerMax3)}) if powerMax4: lastSetConf.update({'powerMax4': int(powerMax4)}) if elecCheckMin: lastSetConf.update({'elecCheckMin': int(elecCheckMin)}) if elecCheckTime: lastSetConf.update({'elecCheckTime': int(elecCheckTime)}) if voice: lastSetConf.update({'voice': int(voice)}) if pw: lastSetConf.update({"pwd": pw}) if oldPw: lastSetConf.update({"old_pwd": oldPw}) if newPw: lastSetConf.update({"new_pwd": newPw}) # 设备参数设置 if any([time1, time2, time3, time4, powerMax1, powerMax2, powerMax3, powerMax4, elecCheckMin, elecCheckTime, voice]): self.set_dev_setting(lastSetConf) # 小区密码设置 if pw: self.set_password(lastSetConf) # 发卡机器模式 if all([oldPw, newPw]): self.set_send_card(lastSetConf) # 设置安全充电时长 if maxTime is not None: self.set_max_time(int(maxTime)) if discount is not None: otherConf = self._device.get("otherConf", dict()) otherConf.update({"discount": str(discount)}) Device.objects.filter(devNo=self._device["devNo"]).update(otherConf=otherConf) Device.invalid_device_cache(self._device["devNo"]) if vCardTime is not None: otherConf = self._device.get("otherConf", dict()) otherConf.update({"vCardTime": str(vCardTime)}) Device.objects.filter(devNo=self._device["devNo"]).update(otherConf=otherConf) Device.invalid_device_cache(self._device["devNo"]) def set_device_function(self, request, lastSetConf): if "disableButton" in request.POST: if "dealerDisableDevice" not in self.device.owner: raise ServiceException({"result": 2, "description": "抱歉,您无此操作权限,请联系厂家获取权限"}) return self.set_dev_disable(request.POST["disableButton"]) if "reboot" in request.POST and request.POST.get("reboot"): return self.reboot_dev() if "needBindCard" in request.POST: needBindCard = request.POST.get('needBindCard') otherConf = self.device.get("otherConf", dict()) otherConf.update({"needBindCard": needBindCard}) Device.objects.get(devNo=self.device.devNo).update(otherConf=otherConf) Device.invalid_device_cache(self.device.devNo) def set_max_time(self, maxTime): data = "{:04X}".format(maxTime) try: self.send_data("F8", data, timeout=5) except ServiceException as e: raise ServiceException({"result": 2, "description": u"该设备暂不支持充电时长,请联系厂家进行升级"}) def set_dev_disable(self, disable): """ 设备端锁定解锁设备 :param disable: :return: """ if disable: status = "00AA" else: status = "0055" result = self.send_data(funCode=SendFunCode.SET_DEV_DISABLE, data=status) data = result["data"] if data[6: 10] != "4F4B": raise ServiceException({'result': 2, 'description': u'设置失败,请重试吧'}) otherConf = self._device.get("otherConf", {}) otherConf["disableDevice"] = disable Device.objects.filter(devNo=self._device["devNo"]).update(otherConf=otherConf) Device.invalid_device_cache(self._device["devNo"]) def handle_clear_start_error(self, port): dealer = Dealer.objects.get(id=self._device["ownerId"]) if not dealer or not dealer.managerialOpenId: return group = Group.get_group(self._device["groupId"]) notifyData = { "title": "设备故障报警解除", "device": u"{gNum}组-{lc}-{port}端口".format(gNum=self._device["groupNumber"], lc=self._device["logicalCode"], port=port), "location": u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), "fault": u"当前端口已被正常启动,报警解除", "notifyTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") } task_caller( func_name='report_to_dealer_via_wechat', openId=dealer.managerialOpenId, dealerId=str(dealer.id), templateName="device_fault", **notifyData ) def handle_out_start_error(self, port): dealer = Dealer.objects.get(id=self._device["ownerId"]) if not dealer or not dealer.managerialOpenId: return group = Group.get_group(self._device["groupId"]) times = Device.get_error_start_times(self._device["devNo"], port) notifyData = { "title": u"注意,您的设备可能发生故障!", "device": u"{gNum}组-{lc}-{port}端口".format(gNum=self._device["groupNumber"], lc=self._device["logicalCode"], port=port), "location": u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), "fault": u"当前设备端口连续启动 {times} 失败,请注意该端口是否发生故障".format(times=times), "notifyTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") } task_caller( func_name='report_to_dealer_via_wechat', openId=dealer.managerialOpenId, dealerId=str(dealer.id), templateName="device_fault", **notifyData )