# -*- coding: utf-8 -*- # !/usr/bin/env python import logging import re import struct from apps.web.constant import Const from apps.web.core.adapter.base import SmartBox from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.device.models import Device, Group from apps.web.user.models import MyUser logger = logging.getLogger(__name__) class ChargeCabinet(SmartBox): """ 一个485控制板只对应一个充电器, 以板子号作为端口区分 port 默认为1 """ def __init__(self, device): super(ChargeCabinet, self).__init__(device) def toHex(self, data): # return data.encode('hex').upper() return reduce(lambda a, b: a + b, map(lambda _: format(_, '02X'), struct.unpack('{}B'.format(len(data)), data))) def fromHex(self, send_str): return reduce(lambda a, b: a + b, map(lambda _: chr(int(_, 16)), re.findall('..', send_str))) def parser(self, data): if not data: return False, '', '' print('receive uart data is: ', self.toHex(data)) if len(data) < 6: return False, '', data[1: -1] if self.toHex(data[0:4]) == '4159574C': lenght = ord(data[4]) message = data[0: lenght + 1] if not self.check_crc(message): return False, '', data[1:-1] print('receive message is: ', self.toHex(message)) return True, self.toHex(message), data[1 + lenght: -1] else: return False, '', data[1: -1] def check_crc(self, data): return reduce(lambda a, b: a ^ b, map(lambda _: ord(_), data)) == 0 def crc(self, data): return reduce(lambda a, b: a ^ b, map(lambda _: ord(_), data)) def pack_data(self, funCode, addr, content=None): HEADER = 0x4159574C if content: LEN = len(content) + 8 return struct.pack('>IBBB{}s'.format(len(content)), HEADER, LEN, int(addr), funCode, content) else: LEN = 8 return struct.pack('>IBBB', HEADER, LEN, int(addr), funCode) # CRC = self.crc(struct.pack('>IBBB{}s'.format(len(content)), HEADER, LEN, addr, funCode, content)) # return struct.pack('>IBBB{}sB'.format(len(content)), HEADER, LEN, addr, funCode, content, CRC) def send(self, data): return data # 功能函数 def _open_door(self, addr, port=1): data = { 'funCode': '82', 'addr': format(int(addr), '02X'), 'data': format(int(port), '02X'), } return self.send_tcp(data) # def _reg(self): # fun_code = 0x81 # WRITER = self.pack_data(fun_code, 0, struct.pack('b', 0)) # self.send_tcp(self.toHex(WRITER),) # def _heartbeat(self): # fun_code = 0x80 # WRITER = self.pack_data(fun_code, 0, struct.pack('b', 0)) # self.send_tcp(self.toHex(WRITER)) def _battery_info(self, addr, port=1, fun_code=None): data = { 'funCode': fun_code or '83', 'addr': format(int(addr), '02X'), 'data': format(int(port), '02X'), } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': state_of_lock, state_of_fire, state_of_fan, box_temp, box_volt, current_power, charger_status, charger_temp, charger_volt, charger_ampere, battery_id, battery_ratio, battery_volt, battery_ampere, battery_factory_volt, battery_temp, battery_capacity, battery_usage_count = struct.unpack( '>4BHH2BHH4sBHHHBHH', self.fromHex(data[2:])) return { 'state_of_lock': state_of_lock == 1 and 'lock' or 'open', 'state_of_fire': state_of_fire, 'state_of_fan': state_of_fan, 'box_temp': box_temp, 'box_volt': round(box_volt * 0.01, 1), 'current_power': current_power, 'charger_status': charger_status, 'charger_temp': charger_temp, 'charger_volt': round(charger_volt * 0.01, 1), 'charger_ampere': round(charger_ampere * 0.01, 1), 'battery_id': self.toHex(battery_id), 'battery_ratio': battery_ratio, 'battery_volt': round(battery_volt * 0.01, 1), 'battery_ampere': round(battery_ampere * 0.01, 1), 'battery_factory_volt': round(battery_factory_volt * 0.01, 1), 'battery_temp': battery_temp, 'battery_capacity': round(battery_capacity * 0.01, 1), 'battery_usage_count': battery_usage_count, } else: return {} def _start_charging(self, addr, port=1): otherConf = self.device.otherConf max_time = min(float(otherConf.get('maxTime', 12)) * 3600, 12 * 3600) max_ampere = min(float(otherConf.get('maxAmpere', 10)) * 100, 1000) data = { 'funCode': '70', 'addr': format(int(addr), '02X'), 'data': '{:02X}{:04X}{:04X}'.format(int(port), int(max_time), int(max_ampere)), } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': return {'result': data[:2], 'port': data[2:]} else: raise ServiceException({'result': 2, 'description': u'设备充电器通讯异常'}) def _stop_charging(self, addr, port=1): data = { 'funCode': '71', 'addr': format(int(addr), '02X'), 'data': format(int(port), '02X'), } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': return {'result': data[:2], 'port': data[2:4], 'usedElec': data[4:6]} else: raise ServiceException({'result': 2, 'description': u'设备充电器通讯异常'}) def _set_power_limit(self, addr, max_power, min_power): data = { 'funCode': '74', 'addr': format(int(addr), '02X'), 'data': '{:04X}{:04X}'.format(int(max_power), int(min_power)), } devInfo = self.send_tcp(data, cmd=220) # data = devInfo['payload']['data'] # if data[:2] == '00': # return {'result': data[:2]} # else: # raise ServiceException({'result': 2, 'description': u'设置功率参数失败'}) def _get_power_limit(self, addr): data = { 'funCode': '75', 'addr': format(int(addr), '02X'), 'data': '', } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': max_power, min_power = struct.unpack('>HH', self.fromHex(data[2: 10])) return {'max_power': max_power, 'min_power': min_power, } else: raise ServiceException({'result': 2, 'description': u'设置功率参数失败'}) def _set_threshold(self, addr, fan_threshold, high_temp_threshold, heating_module_threshold): data = { 'funCode': '77', 'addr': format(int(addr), '02X'), 'data': '{:02X}{:02X}{:02X}'.format(int(fan_threshold), int(high_temp_threshold), int(heating_module_threshold)), } devInfo = self.send_tcp(data, cmd=220) # data = devInfo['payload']['data'] # if data[:2] == '00': # return {'result': data[:2]} # else: # raise ServiceException({'result': 2, 'description': u'设置功率参数失败'}) def _get_threshold(self, addr): data = { 'funCode': '61', 'addr': format(int(addr), '02X'), 'data': '', } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': fan_threshold, high_temp_threshold, temp_control_threshold = struct.unpack('>BBB', self.fromHex(data[2: 8])) return {'fan_threshold': fan_threshold, 'high_temp_threshold': high_temp_threshold, 'heating_module_threshold': temp_control_threshold} else: raise ServiceException({'result': 2, 'description': u'获取功率参数失败'}) def _battery_volt(self, addr, port=1): data = { 'funCode': '7A', 'addr': format(int(addr), '02X'), 'data': format(int(port), '02X'), } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': battery_volt = {} data = self.fromHex(data[2:]) for i in range(len(data) / 2): volt = struct.unpack('>H', data[2 * i: 2 + 2 * i])[0] battery_volt[str(i + 1)] = round(volt * 0.01, 1) return {'battery_volt': battery_volt} else: return {} def _cmd_with_charger(self, addr, port=1, content=''): length = len(content) data = { 'funCode': '7B', 'addr': format(int(addr), '02X'), 'data': '{:02X}{:02X}{}'.format(int(port), int(length), int(content)), } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': return {'result': data[:2], 'length': data[2:4], 'data': data[4:]} else: raise ServiceException({'result': 2, 'description': u'命令错误'}) def _clear_protection(self, addr, port=1): fun_code = 0X63 return self._battery_info(int(addr), port, fun_code) def _switch_protocol(self, addr, protocol_no): data = { 'funCode': '64', 'addr': format(int(addr), '02X'), 'data': format(int(protocol_no), '02X'), } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': pass else: raise ServiceException({'result': 2, 'description': u'切换命令错误'}) def _open_all(self): data = { 'funCode': '86', 'addr': format(0, '02X'), 'data': '' } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': pass else: raise ServiceException({'result': 2, 'description': u'切换命令错误'}) def _play_voice(self, voice_no): data = { 'funCode': '76', 'addr': format(0, '02X'), 'data': format(int(voice_no), '02X') } devInfo = self.send_tcp(data, cmd=220) def _meter_info(self): data = { 'funCode': '7D', 'addr': format(0, '02X'), } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': voltage, total_current, total_power, total_elec = struct.unpack('>HHHI', self.fromHex(data[2:])) return { 'voltage': voltage, 'total_current': total_current, 'total_power': total_power, 'total_elec': total_elec, } else: return { 'voltage': 0, 'total_current': 0, 'total_power': 0, 'total_elec': 0, } def _reboot(self): data = { 'funCode': '7E', 'addr': format(0XFF, '02X'), 'data': '', } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': pass else: raise ServiceException({'result': 2, 'description': u'重启失败'}) def _all_info(self): data = { 'funCode': '7F', 'addr': format(0, '02X'), 'data': '', } RESULT = {} devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': _data = self.fromHex(data[2:]) for _ in range(len(_data) / 8): traffic_status, status, percentage, voltage, power, state_of_lock = struct.unpack('>BBBHHB', _data[ _ * 8: 8 + _ * 8]) RESULT[format(_ + 1, 'd')] = { 'addr': _ + 1, 'traffic_status': traffic_status, 'status': status, 'percentage': percentage, 'voltage': round(voltage * 0.01, 2), 'power': power, 'state_of_lock': state_of_lock == 1 and 'lock' or 'open', } return RESULT def _open_led(self, addr, port=1, turnOn=1): data = { 'funCode': 'C0', 'addr': format(addr, '02X'), 'data': '{:02X}{:02X}'.format(int(port), turnOn and 1 or 0), } devInfo = self.send_tcp(data) data = devInfo['payload']['data'] if data[:2] == '00': pass else: raise ServiceException({'result': 2, 'description': u'开启失败'}) def send_tcp(self, payload, cmd=210, timeout=10): sendMsg = {'IMEI': self.device.devNo, 'payload': payload, 'reply': 'device', 'cmd': cmd} logger.info('send msg = < {} >'.format(payload)) devInfo = MessageSender.send(self.device, cmd, sendMsg, timeout=timeout) rst = devInfo.get('rst') if rst == 0: return devInfo elif rst == -1: raise ServiceException({'result': 2, 'description': u'设备正在玩命找网络,请您稍候再试'}) elif rst == 1: raise ServiceException({'result': 2, 'description': u'设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) else: raise ServiceException({'result': 2, 'description': u'设备返回数据有误。也可能是您的设备版本过低,暂时不支持此功能'}) def start_device(self, package, openId, attachParas): """ 换电柜开门的时候,不需要用户选择几号门打开,系统自动选定空仓打开(开门规则在最下注释) 但是经销商上分的时候是一定会选择仓门进行打开的 除此之外,应当在每次设备启动的时候记录一次缓存信息,最终整个换电结束的时候将此次的信息清空掉,这个缓存信息伴随整个换电过程而生 currentUseInfo :param package: :param openId: :param attachParas: :return: """ # 用户启动的,现在可以获取订单号 这个订单号伴随整个换电的过程直到结束 orderNo = attachParas.get('orderNo') # 对于终端用户,需要校验身份认证是否通过 user = MyUser.objects(openId=openId, groupId=self._device['groupId']).first() if not user: raise ServiceException({'result': 2, 'description': u'无效的用户'}) groupIds = Group.get_group_ids_of_dealer(str(self.device.ownerId)) activeInfo = MyUser.get_active_info(openId, agentId=user.agentId, groupId__in=groupIds) if not activeInfo or not activeInfo.get('isMember'): raise ServiceException({'result': 2, 'description': u'请先完成身份激活信息'}) # 查询缓存,如果当前正在使用的缓存存在,则说明上次的消费尚未结束 devCache = Device.get_dev_control_cache(self.device.devNo) currentUseInfo = devCache.get('currentUseInfo', {}) if currentUseInfo: raise ServiceException({'result': 2, 'description': u'上一个客户尚未使用完成,或者尚未关闭柜门,暂时无法使用设备, 请检查是否有柜门尚未关闭。'}) empty_box = self.search_empty_box() # 获取可以开的port以及Imei, 没有可用电池的情况下直接结束当前的服务 if not empty_box: raise ServiceException({'result': 2, 'description': u'当前设备格柜已满,请换个设备试试'}) full_box = self.search_full_box() # 记录下此次换电要开启的有电池的信息 currentUseInfo.update({ 'toOpenChargeIndex': str(full_box['addr']), 'toOpenBatteryId': full_box['battery_id'] }) # 检验工作通过之后,生下来的就是记录缓存, 开启柜门 播放音乐等行为 result = self._open_door(empty_box['addr']) if not result: raise ServiceException({'result': 2, 'description': u'柜门开启失败,请联系相应经销商解决'}) # 记录下此次换电过程的当前信息,换电结束的时候清空掉 currentUseInfo.update({ 'openId': openId, 'chargeIndex1': empty_box['addr'], 'orderNo': orderNo }) Device.update_dev_control_cache(self.device.devNo, {'currentUseInfo': currentUseInfo}) return {'rst': 0} def get_port_status(self, force=False): return None def get_port_status_from_dev(self): return {} def check_dev_status(self, attachParas=None): openId = attachParas.get('openId') user = MyUser.objects.filter(openId=openId, groupId=self.device.groupId).first() groupIds = Group.get_group_ids_of_dealer(str(self.device.ownerId)) activeInfo = MyUser.get_active_info(openId, agentId=user.agentId, groupId__in=groupIds) if not activeInfo or not activeInfo.get('isMember'): raise ServiceException({'result': 2, 'description': u'请先完成身份激活信息'}) def search_empty_box(self): ctrInfo = Device.get_dev_control_cache(self.device.devNo) empty_box = ctrInfo.get('empty_box', {}) lockPorts = self.device.get('otherConf', dict()).get('lockPorts', list()) if empty_box and str(empty_box['addr'] not in lockPorts): battery_info = self._battery_info(empty_box['addr']) if battery_info and battery_info['battery_id'] == '00000000': return empty_box else: addr_info = self._all_info() for addr, _info in addr_info.items(): if _info['traffic_status'] == 1 and _info['voltage'] == 0 and self._battery_info(_info['addr']).get( 'battery_id') == '00000000' and (str(addr) not in lockPorts): empty_box['addr'] = addr break return empty_box def search_full_box(self): box_info = self._all_info() lockPorts = self.device.get('otherConf', dict()).get('lockPorts', list()) if not box_info: raise ServiceException({'result': 2, 'description': '抱歉, 未找到可使用的换电格柜, 请选择其他换电柜进行使用, 谢谢!!'}) active_box = {} # 去掉禁用的端口 for addr in box_info.keys(): if addr not in lockPorts: active_box[addr] = box_info[addr] # 筛选电量最多的端口 canUseVoltage = float(self.device.otherConf.get("canUseVoltage", 65)) # 筛选电量大于配置的格柜 full_elec = filter(lambda _: (_['traffic_status'] == 1 and _['voltage'] >= canUseVoltage), active_box.values()) if not full_elec: raise ServiceException({'result': 2, 'description': '抱歉, 未找到已充满的电池, 请选择其他换电柜进行使用, 谢谢!!'}) # 筛选处最大的一个 max_one = sorted(full_elec, key=lambda _: (_['percentage'] or 1) * _['voltage'], reverse=True)[0] battery_info = self._battery_info(max_one['addr']) if not battery_info or battery_info['battery_id'] == '00000000': raise ServiceException({'result': 2, 'description': '抱歉, 未找到已充满的电池!!!, 请选择其他换电柜进行使用, 谢谢!!'}) return {'addr': max_one['addr'], 'battery_id': battery_info['battery_id']} def dealer_get_port_status(self): """ 经销商 上分的时候获取端口状态 :return: """ box_info = self._all_info() resultDict = dict() lockPorts = self.device.get('otherConf', dict()).get('lockPorts', list()) for portStr, portInfo in box_info.items(): if portInfo['traffic_status'] == 1: item = self._battery_info(int(portStr)) if item.get('battery_id', '00000000') != '00000000': item.update({'status': Const.DEV_WORK_STATUS_WORKING}) else: item.update({'status': Const.DEV_WORK_STATUS_IDLE, 'battery_id': '空格柜'}) if portStr in lockPorts: item.update({'status': Const.DEV_WORK_STATUS_FORBIDDEN}) resultDict[portStr] = item return resultDict def lock_unlock_port(self, port, lock=True): """ 禁用端口 换电柜禁用端口的时候就是做个标记,设备主板本身不支持禁用 :param port: :param lock: :return: """ 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('lock or unlock port error: %s' % e) raise ServiceException({'result': 2, 'description': u'操作失败,请重新试试'}) 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'操作失败,请重新试试'}) def _set_to_device(self, conf): box_info = self._all_info() box_info = filter(lambda _: _['traffic_status'] == 1, box_info.values()) for item in box_info: try: self._set_threshold(item['addr'], conf['fanThreshold'], conf['highTempThreshold'], conf['tempControlThreshold']) self._set_power_limit(item['addr'], conf['maxPower'], conf['minPower']) except: pass def set_device_function(self, request, lastSetConf): """ 设置设备功能参数 :param request: :param lastSetConf: :return: """ otherConf = self.device.otherConf repairDoorPort = otherConf.get("repairDoorPort", 11) repairDoor = request.POST.get("repairDoor", False) clearCurUse = request.POST.get("clearCurUse", False) reboot = request.POST.get("reboot", False) if repairDoor: self._open_door(repairDoorPort) # 经销商清除当前使用的信息 if clearCurUse: Device.clear_port_control_cache(self.device.devNo, "currentUseInfo") if reboot: self._reboot() def set_device_function_param(self, request, lastSetConf): """ 设置设备参数 :param request: :param lastSetConf: :return: """ repairDoorPort = request.POST.get('repairDoorPort') canUseVoltage = request.POST.get('canUseVoltage') voiceNum = request.POST.get('voiceNum') fanThreshold = request.POST.get('fanThreshold') highTempThreshold = request.POST.get('highTempThreshold') heatingModuleThreshold = request.POST.get('heatingModuleThreshold') maxPower = request.POST.get('maxPower') minPower = request.POST.get('minPower') maxTime = request.POST.get('maxTime') maxAmpere = request.POST.get('maxAmpere') otherConf = self.device.otherConf if repairDoorPort is not None: otherConf.update({'repairDoorPort': round(float(repairDoorPort), 1)}) if canUseVoltage is not None: otherConf.update({'canUseVoltage': round(float(canUseVoltage), 1)}) if voiceNum is not None: otherConf.update({'voiceNum': int(voiceNum)}) self._play_voice(int(voiceNum)) if maxPower and minPower: otherConf.update({ 'fanThreshold': fanThreshold, 'highTempThreshold': highTempThreshold, 'tempControlThreshold': heatingModuleThreshold, 'maxPower': maxPower, 'minPower': minPower, }) self._set_to_device({ 'fanThreshold': fanThreshold, 'highTempThreshold': highTempThreshold, 'tempControlThreshold': heatingModuleThreshold, 'maxPower': maxPower, 'minPower': minPower, }) if maxTime and maxAmpere: otherConf.update({ 'maxTime': maxTime, 'maxAmpere': maxAmpere }) Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf) Device.invalid_device_cache(self.device.devNo) def get_dev_setting(self): data = {} devCache = Device.get_dev_control_cache(self.device.devNo) currentUseInfo = devCache.get('currentUseInfo', dict()) openId = currentUseInfo.get('openId', '') if openId == 'invalid user': currentUser = u'非法开门' elif openId == 'dealer': currentUser = u'经销商远程上分' else: user = MyUser.objects.filter(openId=openId).first() if openId and user: currentUser = user.nickname else: currentUser = u'无用户使用' data.update({'currentUser': currentUser}) currentPort1 = currentUseInfo.get('chargeIndex1') currentPort2 = currentUseInfo.get('chargeIndex2') currentBatterySn1 = currentUseInfo.get('oldBatteryImei') currentBatterySn2 = currentUseInfo.get('newBatteryImei') if currentPort1: data.update({'currentPort1': currentPort1}) if currentPort2: data.update({'currentPort2': currentPort2}) if currentBatterySn1: data.update({'currentBatterySn1': currentBatterySn1}) if currentBatterySn2: data.update({'currentBatterySn2': currentBatterySn2}) otherConf = self.device.otherConf data.update({ 'maxTime': otherConf.get('maxTime', 12), 'maxAmpere': otherConf.get('maxAmpere', 10), 'repairDoorPort': otherConf.get('repairDoorPort', 11), 'canUseVoltage': otherConf.get('canUseVoltage', 65), 'voiceNum': otherConf.get('voiceNum', 1), 'fanThreshold': otherConf.get('fanThreshold', 70), 'highTempThreshold': otherConf.get('highTempThreshold', 35), 'heatingModuleThreshold': otherConf.get('heatingModuleThreshold', 1), 'maxPower': otherConf.get('maxPower', 3000), 'minPower': otherConf.get('minPower', 10), }) return data