# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import time from django.core.cache import caches from typing import Optional, Dict, TYPE_CHECKING from apilib.utils_datetime import timestamp_to_dt from apps.web.agent.models import Agent from apps.web.constant import DeviceCmdCode, Const, MQTT_TIMEOUT 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, DeviceType from apps.web.core.device_define.jndz import CMD_CODE from apps.web.user.models import MyUser from taskmanager.mediator import task_caller if TYPE_CHECKING: from apps.web.api.models import APIStartDeviceRecord logger = logging.getLogger(__name__) class ChargingAOQIANGBox(SmartBox): """ 久恒协议和劲能一致,业务修改和功能扩充需要确定是否两边都修改 """ def __init__(self, device): super(ChargingAOQIANGBox, self).__init__(device) @property def isHaveStopEvent(self): return True def translate_funcode(self, funCode): funCodeDict = { '01': u'获取端口数量', '02': u'获取端口数据', '14': u'移动支付', '07': u'获取刷卡投币统计数据', '15': u'获取设备端口详情', '0F': u'获取端口状态', '0C': u'端口锁操作', '0D': u'端口开关', '1E': u'获取设备设置', '18': u'设置设备参数', '10': u'回复卡余额', } return funCodeDict.get(funCode, '') def translate_event_cmdcode(self, cmdCode): cmdDict = { '06': u'充电结束', '16': u'充电结束', '0A': u'故障', '10': u'刷卡上报', '20': u'启动设备', } return cmdDict.get(cmdCode, '') def test(self, coins): hexElec = fill_2_hexByte(hex(0), 4) hexPort = fill_2_hexByte(hex(1), 2) hexCoins = fill_2_hexByte(hex(1)) hexTime = fill_2_hexByte(hex(60)) devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '14', 'data': hexPort + hexCoins + hexTime + hexElec}) return devInfo def stop(self, port = None): infoDict = self.stop_charging_port(port) infoDict['remainder_time'] = infoDict['leftTime'] return infoDict @start_error_timer(missMessages = [u"请您选择合适的充电线路、电池类型信息", u"请您选择合适的充电线路", u"该端口正在使用中"]) def start_device(self, package, openId, attachParas): if attachParas is None: raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路、电池类型信息'}) if not attachParas.has_key('chargeIndex'): raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'}) devConf = caches['devmgr'].get('settingConf_%s' % (self._device['devNo'])) if devConf is None: conf = self.get_dev_setting() caches['devmgr'].set('settingConf_%s' % (self._device['devNo']), conf, 24 * 3600) coinElec = conf.get('coinElec', 0.9) refundProtection = conf.get('refundProtection', 0) refundProtectionTime = conf.get('refundProtectionTime', 5) lowPowerDetectionTime = conf.get('lowPowerDetectionTime', 60) lowPowerDetectionSwitch = conf.get('lowPowerDetectionSwitch', 0) else: coinElec = devConf.get('coinElec', 0.9) refundProtection = devConf.get('refundProtection', 0) refundProtectionTime = devConf.get('refundProtectionTime', 5) lowPowerDetectionTime = devConf.get('lowPowerDetectionTime', 60) lowPowerDetectionSwitch = devConf.get('lowPowerDetectionSwitch', 0) price = float(package['price']) port = hex(int(attachParas['chargeIndex'])) hexPort = fill_2_hexByte(port, 2) coins = float(package['coins']) hexCoins = fill_2_hexByte(hex(int(coins * 10))) # 注意单位是角 needElec = round((coins * coinElec) / 10.0, 2) # 单位是0.01度电(配置中每次投币的最大用电量单位却是0.1度电,这里要乘以10) hexElec = fill_2_hexByte(hex(int(needElec * 100)), 4) unit = package.get('unit', u'分钟') needTime = int(package['time']) if unit in [u'分钟', u'小时', u'天']: billingType = 'time' if unit == u'小时': needTime = int(package['time']) * 60 elif unit == u'天': needTime = int(package['time']) * 1440 hexTime = fill_2_hexByte(hex(needTime)) elif unit == u'度': billingType = 'elec' needElec = round((package['time']), 2) hexElec = fill_2_hexByte(hex(int(needElec * 100)), 4) hexTime = fill_2_hexByte(hex(12 * 60)) else: billingType = 'elec' devObj = Device.objects.get(devNo = self._device['devNo']) elecFee = float(devObj.otherConf.get('elecFee', 0.9)) needElec = round(min(float(coins / elecFee), needElec), 2) hexElec = fill_2_hexByte(hex(int(needElec * 100)), 4) hexTime = fill_2_hexByte(hex(12 * 60)) # 在启动设备前,如果可能是续充,需要获取下设备端口状态,便于后面核实。防止结束报文丢包导致数据不准确 # 重新把设备上的状态取回来,可以保证needTime数据不出错,正在服务显示也不会有问题 ctrInfo = Device.get_dev_control_cache(self._device['devNo']) lastPortInfo = ctrInfo.get(str(attachParas['chargeIndex']), None) if (lastPortInfo is not None) and lastPortInfo.get('status', '') == Const.DEV_WORK_STATUS_WORKING: self.get_port_status_from_dev() orderNo = attachParas.get('orderNo') devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, { 'IMEI': self._device['devNo'], "funCode": '14', 'data': hexPort + hexCoins + hexTime + hexElec }, timeout = MQTT_TIMEOUT.START_DEVICE) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( { 'result': 2, 'description': u'充电桩正在玩命找网络,您的金币还在,重试不需要重新付款,建议您试试旁边其他设备,或者稍后再试哦' } ) elif devInfo['rst'] == 1: self.check_serial_port_for_startcmd(attachParas['chargeIndex']) data = devInfo['data'][6::] if data[0:2] == '01': # 表示成功 pass else: raise ServiceException({'result': 2, 'description': u'获取端口数据失败,请重试看能否解决'}) usePort = int(attachParas['chargeIndex']) result = data[4:6] if result == '01': # 成功 pass elif result == '0B': newValue = {str(usePort): {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'充电站故障'}} Device.update_dev_control_cache(self._device['devNo'], newValue) raise ServiceException({'result': 2, 'description': u'充电站故障'}) elif result == '0C': newValue = {str(usePort): {'status': Const.DEV_WORK_STATUS_WORKING, 'statusInfo': u''}} Device.update_dev_control_cache(self._device['devNo'], newValue) raise ServiceException({'result': 2, 'description': u'该端口正在使用中'}) start_timestamp = int(time.time()) portDict = { 'startTime': timestamp_to_dt(start_timestamp).strftime('%Y-%m-%d %H:%M:%S'), 'status': Const.DEV_WORK_STATUS_WORKING, 'coins': float(coins), 'isStart': True, 'price': price, 'openId': openId, 'refunded': False, 'billingType': billingType, 'refundProtection': refundProtection, 'refundProtectionTime': refundProtectionTime, 'vCardId': self._vcard_id } ctrInfo = Device.get_dev_control_cache(self._device['devNo']) lastPortInfo = ctrInfo.get(str(usePort), None) if lastPortInfo is not None and lastPortInfo.get('status', '') == Const.DEV_WORK_STATUS_WORKING: if lastPortInfo.has_key('coins'): portDict['coins'] = float(coins) + lastPortInfo['coins'] if lastPortInfo.has_key('price'): portDict['price'] = price + lastPortInfo['price'] if unit in [u'分钟', u'小时', u'天']: portDict.update({'needTime': needTime, 'needElec': needElec}) if (lastPortInfo is not None) and lastPortInfo.get('status', '') == Const.DEV_WORK_STATUS_WORKING: if lastPortInfo.has_key('needTime'): portDict['needTime'] = needTime + lastPortInfo['needTime'] if lastPortInfo.has_key('needElec'): portDict['needElec'] = needElec + lastPortInfo['needElec'] finishedTime = int(time.time()) + portDict['needTime'] * 60 else: portDict.update({'needElec': needElec}) if (lastPortInfo is not None) and lastPortInfo.has_key('needElec') and lastPortInfo.get('status', '') == Const.DEV_WORK_STATUS_WORKING: lastPortInfo['needElec'] = needElec + lastPortInfo['needElec'] finishedTime = 12 * 60 * 60 if attachParas.has_key('orderNo'): portDict.update({'orderNo': attachParas['orderNo']}) if 'extOrderNo' in attachParas: portDict['extOrderNo'] = attachParas.get('extOrderNo') devInfo['finishedTime'] = finishedTime portDict.update({'finishedTime': finishedTime}) Device.update_dev_control_cache(self._device['devNo'], {str(usePort): portDict}) # begin: 低功率检测逻辑 try: if lowPowerDetectionSwitch == 1: if openId is not None: user = MyUser.objects(openId = openId, groupId = self._device['groupId']).first() dealer = Dealer.get_dealer_from_groupId(self._device['groupId']) port = str(attachParas['chargeIndex']) self.notify_low_power_to_user(user, dealer, port, int(lowPowerDetectionTime)) except Exception as e: logger.error(e) # end: 低功率检测逻辑 return devInfo def start_from_api(self, record): # type: (APIStartDeviceRecord)->Optional[Dict] if 'chargeIndex' not in record.attachParas: from apps.web.api.exceptions import ApiParameterError raise ApiParameterError(u'请传入充电桩端口号chargeIndex参数') return super(ChargingAOQIANGBox, self).start_from_api(record) def analyze_event_data(self, data): cmdCode = data[4:6] #: 提交充电结束状态 (06) #: 老版本 if cmdCode == CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_06: port = int(data[8:10], 16) leftTime = int(data[10:14], 16) reasonCode = data[14:16] desc_map = { '00': u'购买的充电时间或电量用完了。', '01': u'可能是插头被拔掉,或者电瓶已经充满。系统判断为异常断电,由于电瓶车充电器种类繁多,可能存在误差。如有问题,请您及时联系商家协助解决问题并恢复充电。', '02': u'恭喜您!电池已经充满电!', '0B': u'设备或端口出现问题,为了安全起见,被迫停止工作。建议您根据已经充电的时间评估是否需要到现场换到其他端口充电。' } return { 'status': Const.DEV_WORK_STATUS_IDLE, 'cmdCode': cmdCode, 'port': port, 'leftTime': leftTime, 'reason': desc_map[reasonCode], 'reasonCode': reasonCode } #: (高版本的) 提交充电结束状态 (16) elif cmdCode == CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_v2_16: port = int(data[8:10], 16) leftTime = int(data[10:14], 16) elec = int(data[14:18], 16) / 100.0 reasonCode = data[18:20] desc_map = { '00': u'购买的充电时间或电量用完了。', '01': u'可能是插头被拔掉,或者电瓶已经充满。系统判断为异常断电,由于电瓶车充电器种类繁多,可能存在误差。如有问题,请您及时联系商家协助解决问题并恢复充电。', '02': u'恭喜您!电池已经充满电!', '03': u'警告!您的电池超功率,已经停止充电,为了公共安全,不建议您在该充电桩充电!提醒您,为了安全大功率的电池不要放入楼道、室内等位置充电哦', '04': u'远程断电。', '0B': u'设备或端口出现问题,为了安全起见,被迫停止工作。建议您根据已经充电的时间评估是否需要到现场换到其他端口充电。' } desc = desc_map.get(reasonCode, u'电池没有充满!原因未知。') return { 'status': Const.DEV_WORK_STATUS_IDLE, 'cmdCode': cmdCode, 'port': port, 'leftTime': leftTime, 'elec': elec, 'reason': desc, 'reasonCode': reasonCode } #: 上传设备故障 elif cmdCode == CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A: port = int(data[8:10], 16) errCode = str(data[10:12]).upper() desc = u'设备故障' if errCode == 'A0': desc = u'停止充电异常,可能是继电器粘连、短路' elif errCode == 'A1': desc = u'高温' elif errCode == 'A2': desc = u'低温' elif errCode == 'A3': desc = u'空载' elif errCode == 'A4': desc = u'消防(烟感)' elif errCode == 'A5': desc = u'过载' elif errCode == 'A6': desc = u'倾斜' elif errCode == 'A7': desc = u'水压高' elif errCode == 'A8': desc = u'水压低' elif errCode == 'A9': desc = u'过压' elif errCode == 'AA': desc = u'欠压' return { 'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': desc, 'cmdCode': cmdCode, 'port': port, 'FaultCode': errCode } # 用户刷卡上报的信息 #: 在线卡上传卡号,预扣费 #: 示例: #: CARD_ID CARD_CST CARD_OPE #: 0x00000000 0x00 0x00 #: 参数 #: CARD_ID : IC 卡卡号 #: CARD_SURP: 卡片需要改变的金额信息(以角为单位) #: CARD_OPE: 0x00 是扣费(减少),0x01 是充值(增加)。 #: 回复 #: RES: 0x00,表示扣费成功,0x01 表示余额不足,0x02 表示非法卡。 #: CARD_SURP: 表示卡余额(以角为单位)。 elif cmdCode == CMD_CODE.SWIPE_CARD_10: group = Group.get_group(self._device['groupId']) dealer = Dealer.get_dealer(group['ownerId']) agent = Agent.objects(id=dealer['agentId']).first() device = Device.objects(devNo=self._device['devNo']).first() devType = DeviceType.objects(id=device['devType']['id']).first() cardNo = str(int(data[8:16], 16)) # 兼容以前的老代码, 先走以前的老代码, 如果有新代码再走新的. if agent is not None and 'cardNoReverse' in agent.features: cardData = data[14:16] + data[12:14] + data[10:12] + data[8:10] cardNo = str(int(cardData, 16)) if 'cardNoReverse' in devType.features: if devType.features['cardNoReverse'] is True: cardData = data[14:16] + data[12:14] + data[10:12] + data[8:10] cardNo = str(int(cardData, 16)) else: cardNo = str(int(data[8:16], 16)) else: pass #: 卡里的余额,以角为单位 preFee = int(data[16:18], 16) / 10.0 #: 操作符 00(增加) | 01(减少) oper = data[18:20] return {'cardNo': cardNo, 'preFee': preFee, 'cmdCode': cmdCode, 'oper': oper} #: 上报投币打开的信息 elif cmdCode == CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20: # 启动设备 port = int(data[8:10], 16) needTime = int(data[10:14], 16) elec = int(data[14:18], 16) / 100.0 consumeTypeTemp = data[18:20] if consumeTypeTemp == '00': consumeType = 'coin' elif consumeTypeTemp == '01': consumeType = 'card' elif consumeTypeTemp == '03': consumeType = 'server' else: consumeType = 'other' money = int(data[20:22], 16) / 10.0 return { 'cmdCode': cmdCode, 'port': port, 'needTime': needTime, 'elec': elec, 'consumeType': consumeType, 'coins': money } # 4个故障的告警 elif cmdCode == CMD_CODE.DEVICE_FAULT_FIRE: return {"cmdCode": cmdCode, "fault": u"火灾报警"} elif cmdCode == CMD_CODE.DEVICE_FAULT_SMOKE: return {"cmdCode": cmdCode, "fault": "烟雾报警"} elif cmdCode == CMD_CODE.DEVICE_FAULT_TEMPERATURE: return {"cmdCode": cmdCode, "fault": "温度超限告警"} elif cmdCode == CMD_CODE.DEVICE_FAULT_POWER: return {"cmdCode": cmdCode, "fault": "功率超线告警"} # 2个状态量上报 elif cmdCode == CMD_CODE.DEVICE_ELEC: elec = int(data[8:12], 16) return {"cmdCode": cmdCode, "elec": elec} elif cmdCode == CMD_CODE.DEVICE_TEMPERATURE: temperature = int(data[8:12], 16) return {"cmdCode": cmdCode, "temperature": temperature} def get_dev_consume_count(self): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '07', 'data': '00'}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) data = devInfo['data'][6::] if data[0:2] == '01': # 表示成功 pass else: raise ServiceException({'result': 2, 'description': u'获取统计数据失败,请重试看能否解决'}) cardFee = int(data[2:6], 16) / 10.0 # 以角为单位 coinFee = int(data[6:10], 16) # 以元为单位 return {'cardFee': cardFee, 'coinFee': coinFee} def get_port_info(self, line): data = fill_2_hexByte(hex(int(line)), 2) devInfo = MessageSender.send(self.device, self.make_random_cmdcode(), {'IMEI': self._device['devNo'], "funCode": '15', 'data': data}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) data = devInfo['data'][6::] if data[0:2] == '01': # 表示成功 pass else: raise ServiceException({'result': 2, 'description': u'获取统计数据失败,请重试看能否解决'}) leftTime = int(data[4:8], 16) if data[8:12] == 'FFFF': power = 0 else: power = int(data[8:12], 16) if data[12:16] == 'FFFF': elec = 0 else: elec = int(data[12:16], 16) if data[16:20] == 'FFFF': surp = 0 else: surp = int(data[16:20], 16) # return {'port':line,'leftTime':leftTime,'power':power,'leftElec':round(elec*0.01,2),'surp':surp}#剩余电量先不上,客户反馈有点不准 return {'port': line, 'leftTime': leftTime, 'power': power, 'surp': surp} def get_port_status(self, force = False): if force: return self.get_port_status_from_dev() ctrInfo = Device.get_dev_control_cache(self._device['devNo']) statusDict = {} if not ctrInfo.has_key('allPorts'): self.get_port_status_from_dev() ctrInfo = Device.get_dev_control_cache(self._device['devNo']) allPorts = ctrInfo.get('allPorts', 10) # allPorts 有的时候会为0, 这个时候强制设置为10 if allPorts == 0: allPorts = 10 for ii in range(allPorts): tempDict = ctrInfo.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_port_status_from_dev(self): devInfo = MessageSender.send(self.device, self.make_random_cmdcode(), {'IMEI': self._device['devNo'], "funCode": '0F', 'data': '00'}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) data = devInfo['data'][6::] if data[0:2] == '01': # 表示成功 pass else: raise ServiceException({'result': 2, 'description': u'获取统计数据失败,请重试看能否解决'}) result = {} portNum = int(data[2:4], 16) portData = data[4::] ii = 0 while ii < portNum: port = int(portData[ii * 4:ii * 4 + 2], 16) statusTemp = portData[ii * 4 + 2:ii * 4 + 4] if statusTemp == '01': status = {'status': Const.DEV_WORK_STATUS_IDLE} elif statusTemp == '02': status = {'status': Const.DEV_WORK_STATUS_WORKING} elif statusTemp == '03': status = {'status': Const.DEV_WORK_STATUS_FORBIDDEN} elif statusTemp == '04': status = {'status': Const.DEV_WORK_STATUS_FAULT} ii += 1 result[str(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 ctrInfo.has_key(strPort): ctrInfo[strPort].update({'status': info['status']}) else: ctrInfo[strPort] = info Device.update_dev_control_cache(self._device['devNo'], ctrInfo) return result def lock_unlock_port(self, port, lock = True): lockStr = '00' if lock else '01' portStr = fill_2_hexByte(hex(int(port)), 2) devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '0C', 'data': portStr + lockStr}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) data = devInfo['data'][6::] if data[0:2] == '01': # 表示成功 pass else: raise ServiceException({'result': 2, 'description': u'操作端口失败,请重试看能否解决'}) if lock: Device.update_dev_control_cache(self._device['devNo'], {str(port): {'status': Const.DEV_WORK_STATUS_FORBIDDEN}}) else: Device.update_dev_control_cache(self._device['devNo'], {str(port): {'status': Const.DEV_WORK_STATUS_IDLE}}) def active_deactive_port(self, port, active): if not active: self.stop_charging_port(port) devInfo = Device.get_dev_control_cache(self._device['devNo']) portCtrInfo = devInfo.get(str(port), {}) portCtrInfo.update({'isStart': False, 'status': Const.DEV_WORK_STATUS_IDLE, 'endTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)}) newValue = {str(port): portCtrInfo} Device.update_dev_control_cache(self._device['devNo'], newValue) else: raise ServiceException({'result': 2, 'description': u'此设备不支持直接打开端口'}) def stop_charging_port(self, port): portStr = fill_2_hexByte(hex(int(port)), 2) devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '0D', 'data': portStr + '00'}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) data = devInfo['data'][6::] if data[0:2] == '01': # 表示成功 pass else: raise ServiceException({'result': 2, 'description': u'操作端口失败,请重试看能否解决'}) port = int(data[2:4], 16) leftTime = int(data[4:8], 16) return {'port': port, 'leftTime': leftTime} # 获取设备配置参数 def get_dev_setting(self): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '1E', 'data': '00'}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) data = devInfo['data'][6::] if data[0:2] == '01': # 表示成功 pass else: raise ServiceException({'result': 2, 'description': u'操作端口失败,请重试看能否解决'}) confData = data[2:-2] coinMin = int(confData[0:4], 16) cardMin = int(confData[4:8], 16) coinElec = int(confData[8:10], 16) cardElec = int(confData[10:12], 16) cst = int(confData[12:14], 16) powerMax1 = int(confData[14:18], 16) powerMax2 = int(confData[18:22], 16) powerMax3 = int(confData[22:26], 16) powerMax4 = int(confData[26:30], 16) power2Ti = int(confData[30:32], 16) power3Ti = int(confData[32:34], 16) power4Ti = int(confData[34:36], 16) spRecMon = int(confData[36:38], 16) spFullEmpty = int(confData[38:40], 16) fullPowerMin = int(confData[40:42], 16) fullChargeTime = int(confData[42:44], 16) * 10 elecTimeFirst = int(confData[44:46], 16) dev = Device.objects.get(devNo = self._device['devNo']) billingType = dev.otherConf.get('billingType', 'time') elecFee = dev.otherConf.get('elecFee', 0.9) refundProtection = dev.otherConf.get('refundProtection', 0) refundProtectionTime = dev.otherConf.get('refundProtectionTime', 5) lowPowerDetectionTime = dev.otherConf.get('lowPowerDetectionTime', 60) lowPowerDetectionSwitch = dev.otherConf.get('lowPowerDetectionSwitch', 0) lowPowerDetectionPower = dev.otherConf.get('lowPowerDetectionPower', 50) # 为了预防万一,这里顺便把数据库的数据也更新一下,主要是以设备为准的数据,更新下 dev.otherConf.update({'cardMin': cardMin}) try: dev.save() except Exception, e: pass resultDict = { 'coinMin': coinMin, 'cardMin': cardMin, 'coinElec': coinElec, 'cardElec': cardElec, 'cst': cst, 'powerMax1': powerMax1, 'powerMax2': powerMax2, 'powerMax3': powerMax3, 'powerMax4': powerMax4, 'power2Ti': power2Ti, 'power3Ti': power3Ti, 'power4Ti': power4Ti, 'spRecMon': spRecMon, 'spFullEmpty': spFullEmpty, 'fullPowerMin': fullPowerMin, 'fullChargeTime': fullChargeTime, 'elecTimeFirst': elecTimeFirst, 'billingType': billingType, 'elecFee': elecFee, 'refundProtection': refundProtection, 'refundProtectionTime': refundProtectionTime, 'lowPowerDetectionTime': lowPowerDetectionTime, 'lowPowerDetectionSwitch': lowPowerDetectionSwitch, 'lowPowerDetectionPower': lowPowerDetectionPower } consumeInfo = self.get_dev_consume_count() resultDict.update(consumeInfo) setting2Info = self.get_dev_settings2() resultDict.update(setting2Info) return resultDict # 设置设备配置参数 def set_dev_setting(self, setConf): data = '' data += fill_2_hexByte(hex(int(setConf['coinMin'])), 4) data += fill_2_hexByte(hex(int(setConf['cardMin'])), 4) data += fill_2_hexByte(hex(int(setConf['coinElec'])), 2) data += fill_2_hexByte(hex(int(setConf['cardElec'])), 2) data += fill_2_hexByte(hex(int(setConf['cst'])), 2) data += fill_2_hexByte(hex(int(setConf['powerMax1'])), 4) data += fill_2_hexByte(hex(int(setConf['powerMax2'])), 4) data += fill_2_hexByte(hex(int(setConf['powerMax3'])), 4) data += fill_2_hexByte(hex(int(setConf['powerMax4'])), 4) data += fill_2_hexByte(hex(int(setConf['power2Ti'])), 2) data += fill_2_hexByte(hex(int(setConf['power3Ti'])), 2) data += fill_2_hexByte(hex(int(setConf['power4Ti'])), 2) data += fill_2_hexByte(hex(int(setConf['spRecMon'])), 2) data += fill_2_hexByte(hex(int(setConf['spFullEmpty'])), 2) data += fill_2_hexByte(hex(int(setConf['fullPowerMin'])), 2) data += fill_2_hexByte(hex(int(setConf['fullChargeTime'])), 2) data += fill_2_hexByte(hex(int(setConf['elecTimeFirst'])), 2) devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '18', 'data': data}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) data = devInfo['data'][6::] if data[0:2] == '01': # 表示成功 dev = Device.objects.get(devNo = self._device['devNo']) dev.otherConf.update({'billingType': setConf['billingType']}) dev.otherConf.update({'elecFee': setConf['elecFee']}) dev.otherConf.update({'refundProtection': setConf['refundProtection']}) dev.otherConf.update({'refundProtectionTime': setConf['refundProtectionTime']}) dev.otherConf.update({'cardMin': setConf['cardMin']}) dev.otherConf.update({'lowPowerDetectionTime': setConf['lowPowerDetectionTime']}) dev.otherConf.update({'lowPowerDetectionSwitch': setConf['lowPowerDetectionSwitch']}) dev.otherConf.update({'lowPowerDetectionPower': setConf['lowPowerDetectionPower']}) dev.save() else: raise ServiceException({'result': 2, 'description': u'操作端口失败,请重试看能否解决'}) # 设置额外的参数 def set_dev_setting2(self, setConf): data = fill_2_hexByte(hex(int(setConf['stopDelay'])), 2) data += fill_2_hexByte(hex(int(setConf['maxTotalTime']) / 10), 2) data += fill_2_hexByte(hex(int(setConf['overVoltage'])), 4) data += fill_2_hexByte(hex(int(setConf['lowVoltage'])), 4) data += fill_2_hexByte(hex(int(setConf['overTemp'])), 2) data += fill_2_hexByte(hex(int(setConf['overTempDelayTime'])), 2) data += str(setConf['timeDspOn']).replace(':', '') data += str(setConf['timeDspOff']).replace(':', '') devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '22', 'data': data}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) def get_dev_settings2(self): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '23', 'data': '00'}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) data = devInfo['data'] result = {} result['stopDelay'] = int(data[8:10], 16) result['maxTotalTime'] = int(data[10:12], 16) * 10 result['overVoltage'] = int(data[12:16], 16) result['lowVoltage'] = int(data[16:20], 16) result['overTemp'] = int(data[20:22], 16) result['overTempDelayTime'] = int(data[22:24], 16) # 处理返回数据为 'HH:MM' 格式的字符串 timeDspOn = int(data[24:28]) if data[24:28].isdigit() is True else None timeDspOff = int(data[28:32]) if data[28:32].isdigit() is True else None timeDspOnList = list(str(timeDspOn)) timeDspOffList = list(str(timeDspOff)) timeDspOnList.insert(-2, ':') timeDspOffList.insert(-2, ':') result['timeDspOn'] = ''.join(timeDspOnList) if len(timeDspOnList) == 5 else '0' + ''.join(timeDspOnList) result['timeDspOff'] = ''.join(timeDspOffList) if len(timeDspOffList) == 5 else '0' + ''.join(timeDspOffList) return result def response_use_card(self, res, balance): data = '55061001' data = data + res + fill_2_hexByte(hex(int(balance * 10)), 4) devInfo = MessageSender.send(device = self.device, cmd = DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE, payload = { 'IMEI': self._device['devNo'], "funCode": '10', 'data': data }) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: # 等于1的时候,说明服务器和远程模块通讯OK,响应已经收到,不需要异常处理 pass def set_device_function_param(self, request, lastSetConf): coinMin = request.POST.get('coinMin', None) cardMin = request.POST.get('cardMin', None) coinElec = request.POST.get('coinElec', None) cardElec = request.POST.get('cardElec', None) cst = request.POST.get('cst', 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) power2Ti = request.POST.get('power2Ti', None) power3Ti = request.POST.get('power3Ti', None) power4Ti = request.POST.get('power4Ti', None) spRecMon = request.POST.get('spRecMon', None) spFullEmpty = request.POST.get('spFullEmpty', None) fullPowerMin = request.POST.get('fullPowerMin', None) fullChargeTime = request.POST.get('fullChargeTime', None) elecTimeFirst = request.POST.get('elecTimeFirst', None) billingType = request.POST.get('billingType', None) elecFee = request.POST.get('elecFee', None) refundProtection = request.POST.get('refundProtection', None) refundProtectionTime = request.POST.get('refundProtectionTime', None) lowPowerDetectionTime = request.POST.get('lowPowerDetectionTime', None) lowPowerDetectionSwitch = request.POST.get('lowPowerDetectionSwitch', None) lowPowerDetectionPower = request.POST.get('lowPowerDetectionPower', None) # 这几个是参数是奥强特有的 stopDelay = request.POST.get('stopDelay', None) maxTotalTime = request.POST.get('maxTotalTime', None) overVoltage = request.POST.get('overVoltage', None) lowVoltage = request.POST.get('lowVoltage', None) overTemp = request.POST.get('overTemp', None) overTempDelayTime = request.POST.get('overTempDelayTime', None) timeDspOn = request.POST.get('timeDspOn', None) timeDspOff = request.POST.get('timeDspOff', None) # 这几个参数是墨小智V3特有的 stWTime = request.POST.get('stWTime', None) temThre = request.POST.get('temThre', None) freeUse = request.POST.get('freeUse', None) if coinMin: lastSetConf.update({'coinMin': int(coinMin)}) if cardMin: lastSetConf.update({'cardMin': int(cardMin)}) if coinElec: lastSetConf.update({'coinElec': int(coinElec)}) if cardElec: lastSetConf.update({'cardElec': int(cardElec)}) if cst: lastSetConf.update({'cst': int(cst)}) 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 power2Ti: lastSetConf.update({'power2Ti': int(power2Ti)}) if power3Ti: lastSetConf.update({'power3Ti': int(power3Ti)}) if power4Ti: lastSetConf.update({'power4Ti': int(power4Ti)}) if spRecMon: lastSetConf.update({'spRecMon': int(spRecMon)}) if spFullEmpty: lastSetConf.update({'spFullEmpty': int(spFullEmpty)}) if fullPowerMin: lastSetConf.update({'fullPowerMin': int(fullPowerMin)}) if fullChargeTime: lastSetConf.update({'fullChargeTime': int(fullChargeTime)}) if elecTimeFirst: lastSetConf.update({'elecTimeFirst': int(elecTimeFirst)}) if billingType: lastSetConf.update({'billingType': billingType}) if elecFee: lastSetConf.update({'elecFee': elecFee}) if refundProtection: lastSetConf.update({'refundProtection': int(refundProtection)}) if refundProtectionTime: lastSetConf.update({'refundProtectionTime': int(refundProtectionTime)}) if lowPowerDetectionTime: lastSetConf.update({'lowPowerDetectionTime': int(lowPowerDetectionTime)}) if lowPowerDetectionSwitch: lastSetConf.update({'lowPowerDetectionSwitch': int(lowPowerDetectionSwitch)}) if lowPowerDetectionPower: lastSetConf.update({'lowPowerDetectionPower': int(lowPowerDetectionPower)}) # 这几个参数是奥强特有的 if stopDelay: lastSetConf.update({'stopDelay': stopDelay}) if maxTotalTime: lastSetConf.update({'maxTotalTime': maxTotalTime}) if overVoltage: lastSetConf.update({'overVoltage': overVoltage}) if lowVoltage: lastSetConf.update({'lowVoltage': lowVoltage}) if overTemp: lastSetConf.update({'overTemp': overTemp}) if overTempDelayTime: lastSetConf.update({'overTempDelayTime': overTempDelayTime}) if timeDspOn: lastSetConf.update({'timeDspOn': timeDspOn}) if timeDspOff: lastSetConf.update({'timeDspOff': timeDspOff}) # 这几个参数是墨小智V3特有的 if stWTime: lastSetConf.update({'stWTime': int(stWTime)}) if temThre: lastSetConf.update({'temThre': int(temThre)}) if freeUse: lastSetConf.update({'freeUse': int(freeUse)}) self.set_dev_setting(lastSetConf) self.set_dev_setting2(lastSetConf) 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"]) self.lock_unlock_port(port, lock = False) 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 ) self.lock_unlock_port(port, lock = False) 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) self.lock_unlock_port(port, lock = True) 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 ) # 麦总的告警3次触发,同时锁定设备,为避免解锁设备之后 再次触发 这个地方需要将其缓存清空 Device.delete_error_start_times(self._device["devNo"], port)