# -*- coding: utf-8 -*- #!/usr/bin/env python import time import logging from apilib.utils_datetime import timestamp_to_dt from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT, DeviceErrorCodeDesc, ErrorCode from apps.web.core.adapter.base import SmartBox, fill_2_hexByte from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.device.models import Device, GroupDict logger = logging.getLogger(__name__) class ChargingHaiNiaoSingleBox(SmartBox): def __init__(self, device): super(ChargingHaiNiaoSingleBox, self).__init__(device) def translate_funcode(self, funCode): funCodeDict = { '01': u'获取端口状态', '02': u'获取设备参数', '03': u'操作端口', '04': u'设置参数', } return funCodeDict.get(funCode, '') def translate_event_cmdcode(self, cmdCode): cmdDict = { '86': u'设备上报信息', } return cmdDict.get(cmdCode, '') def check_dev_status(self, attachParas = None): """ 如果超过两个心跳周期没有报心跳,并且最后一次更新时间在2个小时内,需要从设备获取状态 否则以缓存状态为准。 :param attachParas: :return: """ if attachParas is None: raise ServiceException({'result': 0, 'description': u'请您选择合适的充电端口、电池类型信息'}) if not attachParas.has_key('chargeIndex'): raise ServiceException({'result': 0, 'description': u'请您选择合适的充电端口'}) if not self.device.need_fetch_online: raise ServiceException( {'result': 2, 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_CHECK_FAIL)}) self.get_port_status_from_dev() group = self.device.group # type: GroupDict if group.is_free: logger.debug('{} is free. no need to check continue pay.'.format(repr(self.device))) return # 处理是否能够续充 portDict = self.get_port_status() port = str(attachParas['chargeIndex']) if port in portDict: isCanAdd = self.device['devType'].get('payableWhileBusy', False) if portDict[port]['status'] == Const.DEV_WORK_STATUS_IDLE: return elif portDict[port]['status'] == Const.DEV_WORK_STATUS_FAULT: raise ServiceException({'result': 0, 'description': u'该端口故障,暂时不能使用'}) elif portDict[port]['status'] == Const.DEV_WORK_STATUS_WORKING: if isCanAdd: return else: raise ServiceException({'result': 0, 'description': u'该端口正在工作不能使用,请您使用其他端口'}) elif portDict[port]['status'] == Const.DEV_WORK_STATUS_FORBIDDEN: raise ServiceException({'result': 0, 'description': u'该端口已被禁止使用,请您使用其他端口'}) elif portDict[port]['status'] == Const.DEV_WORK_STATUS_CONNECTED: return else: raise ServiceException({'result': 0, 'description': u'端口状态未知,暂时不能使用'}) else: raise ServiceException({'result': 0, 'description': u'未知端口,暂时不能使用'}) def get_port_status_from_dev(self): # 获取端口状态 devInfo = MessageSender.send(self.device, self.make_random_cmdcode(), {'IMEI': self._device['devNo'], "funCode": '01', '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'当前设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) result = {} binStr = str(bin(int(devInfo['data'][10:12], 16)))[2:] while len(binStr) < 8: binStr = '0' + binStr portStatus = binStr[-1] if portStatus == '0': result['1'] = {'status': Const.DEV_WORK_STATUS_IDLE} elif portStatus == '1': result['1'] = {'status': Const.DEV_WORK_STATUS_WORKING} else: raise ServiceException( {'result': 2, 'description': u'端口状态返回错误'}) 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 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 = {} allPorts = ctrInfo.get('allPorts', 1) 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 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'请您选择合适的充电线路'}) # 只有一个端口 hexPort = '0001' needTime = 0 needLitre = 0 needPrice = float(package['price']) hexNeedPrice = fill_2_hexByte(hex(int(needPrice * 100))) unit = package['unit'] if unit == u'升': needLitre = int(package['time']) hexLitre = fill_2_hexByte(hex(needLitre * 10)) data = hexPort + '01' + hexLitre + hexNeedPrice elif unit == u'小时': needTime = int(package['time']) * 60 hexTime = fill_2_hexByte(hex(needTime)) data = hexPort + '01' + hexTime + hexNeedPrice else: needTime = int(package['time']) hexTime = fill_2_hexByte(hex(needTime)) data = hexPort + '01' + hexTime + hexNeedPrice billingMode = self.get_dev_setting()['billingMode'] if billingMode == 'litre' and unit == u'升': pass elif billingMode == 'time' and unit == u'分钟': pass elif billingMode == 'time' and unit == u'小时': pass else: raise ServiceException({'result': 2, 'description': u'套餐与主板的启动参数不匹配(时间/流量)'}) orderNo = attachParas.get('orderNo') portStatus = self.get_port_status_from_dev() if portStatus['1']['status'] == Const.DEV_WORK_STATUS_WORKING: data = data[0:4] + data[6:10] devInfo = MessageSender.send( device = self.device, cmd = DeviceCmdCode.OPERATE_DEV_SYNC, payload = { 'IMEI': self._device['devNo'], "funCode": '09', 'data': data }, 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: raise ServiceException({'result': 2, 'description': u'当前设备正在忙,无响应,您的金币还在,请试试其他线路,或者请稍后再试哦'}) data = devInfo['data'][8::] usePort = int(data[0:4], 16) result = data[4:8] if result == '0000': # 代表失败 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'充电站故障'}) start_timestamp = int(time.time()) finishedTime = start_timestamp + needTime * 60 portDict = { 'startTime': timestamp_to_dt(start_timestamp).strftime('%Y-%m-%d %H:%M:%S'), 'status': Const.DEV_WORK_STATUS_WORKING, 'finishedTime': finishedTime, 'needPrice': needPrice, # 个人中心断电需要用到 'coins': needPrice, 'isStart': True, 'needTime': needTime, 'needLitre': needLitre, 'openId': openId, 'refunded': False, 'vCardId': self._vcard_id } ctrInfo = Device.get_dev_control_cache(self._device['devNo']) lastPortInfo = ctrInfo.get(str(usePort), None) is_continue = False if lastPortInfo is not None and \ lastPortInfo.get('status', '') == Const.DEV_WORK_STATUS_WORKING and \ lastPortInfo.get('openId', '') == openId: is_continue = True if 'needPrice' in lastPortInfo: portDict['needPrice'] = float(needPrice) + lastPortInfo['needPrice'] if 'needTime' in lastPortInfo: portDict['needTime'] = needTime + lastPortInfo['needTime'] if 'needLitre' in lastPortInfo: portDict['needLitre'] = needLitre + lastPortInfo['needLitre'] finishedTime = int(time.time()) + portDict['needTime'] * 60 portDict.update({'finishedTime': finishedTime}) if 'linkedRechargeRecordId' in attachParas and attachParas.get('isQuickPay', False): item = { 'rechargeRcdId': str(attachParas['linkedRechargeRecordId']) } if is_continue: payInfo = lastPortInfo.get('payInfo', list()) else: payInfo = list() payInfo.append(item) portDict['payInfo'] = payInfo else: devInfo = MessageSender.send( device = self.device, cmd = DeviceCmdCode.OPERATE_DEV_SYNC, payload = { 'IMEI': self._device['devNo'], "funCode": '08', 'data': data }, 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: raise ServiceException({'result': 2, 'description': u'当前设备正在忙,无响应,您的金币还在,请试试其他线路,或者请稍后再试哦'}) data = devInfo['data'][8::] usePort = 1 result = data[4:8] if result == '0000': # 代表失败 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'充电站故障'}) start_timestamp = int(time.time()) finishedTime = start_timestamp + needTime * 60 portDict = { 'startTime': timestamp_to_dt(start_timestamp).strftime('%Y-%m-%d %H:%M:%S'), 'status': Const.DEV_WORK_STATUS_WORKING, 'finishedTime': finishedTime, 'needPrice': needPrice, # 个人中心断电需要用到 'coins': needPrice, 'isStart': True, 'needTime': needTime, 'needLitre': needLitre, 'openId': openId, 'refunded': False, 'vCardId': self._vcard_id } if 'linkedRechargeRecordId' in attachParas: item = { 'rechargeRcdId': str(attachParas['linkedRechargeRecordId']) } portDict['payInfo'] = [item] Device.update_dev_control_cache(self._device['devNo'], {str(usePort): portDict}) return devInfo def get_dev_setting(self): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'],"funCode":'02','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'当前设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) confData = devInfo['data'][8::] switchData = int(confData[0:2], 16) power1Price = int(confData[10:12], 16) power1Matter = int(confData[12:16], 16) power2Price = int(confData[16:18], 16) power2Matter = int(confData[18:22], 16) power3Price = int(confData[22:24], 16) power3Matter = int(confData[24:28], 16) power4Price = int(confData[28:30], 16) power4Matter = int(confData[30:34], 16) power5Price = int(confData[34:36], 16) power5Matter = int(confData[36:40], 16) power6Price = int(confData[40:42], 16) power6Matter = int(confData[42:46], 16) binStr = str(bin(switchData))[2:] while len(binStr) < 8: binStr = '0' + binStr billingMode = 'litre' if int(binStr[4]) else 'time' workingMode = 'multiple' if int(binStr[3]) else 'single' isPause = True if int(binStr[2]) else False isPowerOffMemory = True if int(binStr[1]) else False coinLaunchMode = 'button' if int(binStr[0]) else 'direct' device = Device.objects(devNo=self._device['devNo']).first() minConsumptionLimit = device.otherConf.get('minConsumptionLimit', 0) return { 'billingMode': billingMode, 'workingMode': workingMode, 'isPause': isPause, 'isPowerOffMemory': isPowerOffMemory, 'coinLaunchMode': coinLaunchMode, 'power1Price': power1Price, 'power1Matter': power1Matter, 'power2Price': power2Price, 'power2Matter': power2Matter, 'power3Price': power3Price, 'power3Matter': power3Matter, 'power4Price': power4Price, 'power4Matter': power4Matter, 'power5Price': power5Price, 'power5Matter': power5Matter, 'power6Price': power6Price, 'power6Matter': power6Matter, 'minConsumptionLimit': minConsumptionLimit } def send_setting_data(self, data): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '04', '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'当前设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) if devInfo['data'][14:16] == '01': raise ServiceException( {'result': 2, 'description': u'设置失败'}) def set_pause_mode(self, isPause): data = '08' + ('0001' if isPause else '0000') self.send_setting_data(data) def set_power_off_memory(self, isPowerOffMemory): data = '0C' + ('0001' if isPowerOffMemory else '0000') self.send_setting_data(data) def set_base_settings(self, baseSettingsDict): billingModeData = '0B' + ('0001' if baseSettingsDict['billingMode'] == 'litre' else '0000') self.send_setting_data(billingModeData) workingModeData = '0A' + ('0001' if baseSettingsDict['workingMode'] == 'multiple' else '0000') self.send_setting_data(workingModeData) coinLaunchModeData = '09' + ('0001' if baseSettingsDict['coinLaunchMode'] == 'button' else '0000') self.send_setting_data(coinLaunchModeData) device = Device.objects(devNo=self._device['devNo']).first() device.otherConf['minConsumptionLimit'] = baseSettingsDict['minConsumptionLimit'] device.save() def set_power_price(self, powerPriceDict): power1PriceData = '14' + fill_2_hexByte(hex(int(powerPriceDict['power1Price']))) self.send_setting_data(power1PriceData) power2PriceData = '16' + fill_2_hexByte(hex(int(powerPriceDict['power2Price']))) self.send_setting_data(power2PriceData) power3PriceData = '18' + fill_2_hexByte(hex(int(powerPriceDict['power3Price']))) self.send_setting_data(power3PriceData) power4PriceData = '1A' + fill_2_hexByte(hex(int(powerPriceDict['power4Price']))) self.send_setting_data(power4PriceData) power5PriceData = '1C' + fill_2_hexByte(hex(int(powerPriceDict['power5Price']))) self.send_setting_data(power5PriceData) power6PriceData = '1E' + fill_2_hexByte(hex(int(powerPriceDict['power6Price']))) self.send_setting_data(power6PriceData) def set_power_matter(self, powerMatterDict): power1MatterData = '15' + fill_2_hexByte(hex(int(powerMatterDict['power1Matter']))) self.send_setting_data(power1MatterData) power2MatterData = '17' + fill_2_hexByte(hex(int(powerMatterDict['power2Matter']))) self.send_setting_data(power2MatterData) power3MatterData = '19' + fill_2_hexByte(hex(int(powerMatterDict['power3Matter']))) self.send_setting_data(power3MatterData) power4MatterData = '1B' + fill_2_hexByte(hex(int(powerMatterDict['power4Matter']))) self.send_setting_data(power4MatterData) power5MatterData = '1D' + fill_2_hexByte(hex(int(powerMatterDict['power5Matter']))) self.send_setting_data(power5MatterData) power6MatterData = '1F' + fill_2_hexByte(hex(int(powerMatterDict['power6Matter']))) self.send_setting_data(power6MatterData) def set_device_function(self, request, lastSetConf): if 'isPause' in request.POST: isPause = request.POST['isPause'] self.set_pause_mode(isPause) if 'isPowerOffMemory' in request.POST: isPowerOffMemory = request.POST['isPowerOffMemory'] self.set_power_off_memory(isPowerOffMemory) def set_device_function_param(self, request, lastSetConf): if 'billingMode' in request.POST \ and 'workingMode' in request.POST \ and 'minConsumptionLimit' in request.POST \ and 'coinLaunchMode' in request.POST: billingMode = request.POST['billingMode'] workingMode = request.POST['workingMode'] minConsumptionLimit = request.POST['minConsumptionLimit'] coinLaunchMode = request.POST['coinLaunchMode'] self.set_base_settings({ 'billingMode': billingMode, 'workingMode': workingMode, 'minConsumptionLimit': minConsumptionLimit, 'coinLaunchMode': coinLaunchMode }) if 'power1Price' in request.POST \ and 'power2Price' in request.POST \ and 'power3Price' in request.POST \ and 'power4Price' in request.POST \ and 'power5Price' in request.POST \ and 'power6Price' in request.POST: power1Price = request.POST['power1Price'] power2Price = request.POST['power2Price'] power3Price = request.POST['power3Price'] power4Price = request.POST['power4Price'] power5Price = request.POST['power5Price'] power6Price = request.POST['power6Price'] self.set_power_price({ 'power1Price': power1Price, 'power2Price': power2Price, 'power3Price': power3Price, 'power4Price': power4Price, 'power5Price': power5Price, 'power6Price': power6Price }) if 'power1Matter' in request.POST \ and 'power2Matter' in request.POST \ and 'power3Matter' in request.POST \ and 'power4Matter' in request.POST \ and 'power5Matter' in request.POST \ and 'power6Matter' in request.POST: power1Matter = request.POST['power1Matter'] power2Matter = request.POST['power2Matter'] power3Matter = request.POST['power3Matter'] power4Matter = request.POST['power4Matter'] power5Matter = request.POST['power5Matter'] power6Matter = request.POST['power6Matter'] self.set_power_matter({ 'power1Matter': power1Matter, 'power2Matter': power2Matter, 'power3Matter': power3Matter, 'power4Matter': power4Matter, 'power5Matter': power5Matter, 'power6Matter': power6Matter }) def get_port_info(self, line): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '01', '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'当前设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) leftTimeSec = int(devInfo['data'][12:20], 16) leftTime = leftTimeSec / 60 return {'port': line, 'leftTime': leftTime} def stop(self, port = None): self.stop_charging_port(port) infoDict = dict() infoDict['remainder_time'] = 0 return infoDict def stop_charging_port(self, port): self.active_deactive_port(port, False) def active_deactive_port(self, port, active): if active: return else: data = '000100' devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '03', 'data': data + '0000'}) if devInfo.has_key('rst') and devInfo['rst'] != 0: if devInfo['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'当前设备正在玩命找网络,请您稍候再试'}) elif devInfo['rst'] == 1: # 不处理 pass def analyze_event_data(self, data): cmdCode = data[6:8] if cmdCode == '86': channelChangeStatus = data[10:12] binChannelChangeStatus = str(bin(int(channelChangeStatus, 16)))[2:] while len(binChannelChangeStatus) < 8: binChannelChangeStatus = '0' + binChannelChangeStatus channelOperateStatus = data[14:16] binChannelOperateStatus = str(bin(int(channelOperateStatus, 16)))[2:] while len(binChannelOperateStatus) < 8: binChannelOperateStatus = '0' + binChannelOperateStatus channel1Status = False if binChannelChangeStatus[-1] == '0' else True channel1Oper = False if binChannelOperateStatus[-1] == '0' else True # 判断条件成立代表结束 if channel1Status is True and channel1Oper is False: reason = data[16:18] if reason == '00': desc = u'购买的充电时间或者电量用完了。' elif reason in ['04', '06', '07']: desc = u'管理人员可能远程断电了,或是插头被拔掉, 建议您根据已经充电的时间评估是否需要到现场换到其他端口充电。' elif reason == '05': desc = u'空载断电。' else: desc = u'' leftTime = int(data[36:44], 16) / 60 leftPrice = float(int(data[44:48], 16)) / 100 return { 'status': Const.DEV_WORK_STATUS_IDLE, 'leftPrice': leftPrice, 'leftTime': leftTime, 'cmdCode': cmdCode, 'reason': desc, 'port': '1' } else: return {} def pauseToUseDevice(self, port, oper): MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, {'IMEI': self._device['devNo'], "funCode": '03', 'data': '0001' + oper + '0000'}) if oper == '02': Device.update_dev_control_cache(self._device['devNo'], {'1': {'pausePort': True}}) elif oper == '01': Device.update_dev_control_cache(self._device['devNo'], {'1': {'pausePort': False}}) else: pass @property def isHaveStopEvent(self): return True