# -*- coding: utf-8 -*- # !/usr/bin/env python # TODO 目前采用主体相同协议拷贝的方式, 后续需要考虑更适合的方式 # 修改zhixia.py问题的时候需要考虑是否修改aiyaxin5.py和zhixia2.py. # 三者协议一致, 但是因为加载reload问题, 目前必须拆开. import datetime import logging import time from decimal import Decimal from apilib.monetary import RMB from apilib.utils_datetime import timestamp_to_dt from apps.web.constant import DeviceCmdCode, Const, ErrorCode, MQTT_TIMEOUT from apps.web.core.adapter.base import SmartBox, fill_2_hexByte, make_six_bytes_session_id from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.device.models import Device logger = logging.getLogger(__name__) class ChargingXUZHOUBox(SmartBox): def __init__(self, device): super(ChargingXUZHOUBox, self).__init__(device) def translate_funcode(self, funCode): funCodeDict = { '01': u'获取端口数量', '02': u'移动支付', '06': u'获取设备端口详情', '09': u'设置刷卡投币使能', '0B': u'端口关闭', '13': u'设置卡充满退费', '14': u'设置功率费率配置', '12': u'充值', } return funCodeDict.get(funCode, '') def translate_event_cmdcode(self, cmdCode): cmdDict = { '04': u'刷卡上报', '05': u'充电结束', '0D': u'故障', '12': u'卡充值', } return cmdDict.get(cmdCode, '') def test(self, coins): hexPort = fill_2_hexByte(hex(int(1)), 2) hexCoins = fill_2_hexByte(hex(1))#测试充值1毛钱的电量测试 devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '02', 'data': hexPort + '00' + hexCoins}) return devInfo 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'请您选择合适的充电线路'}) port = hex(int(attachParas['chargeIndex'])) hexPort = fill_2_hexByte(port, 2) needTime = int(package['time']) unit = package.get('unit', u'分钟') if unit == u'小时': needTime = int(package['time']) * 60 elif unit == u'天': needTime = int(package['time']) * 1440 coins = int ( 10 * float(package['coins']) ) hexCoins = fill_2_hexByte(hex(coins)) devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '02', 'data': hexPort + '00' + hexCoins}, 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'][16::] usePort = int(attachParas['chargeIndex']) result = data[4:6] if result == '01': # 成功 pass elif result == '02': 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 == '03': 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()) Device.update_dev_control_cache( self._device['devNo'], { str(usePort): { 'startTime': timestamp_to_dt(start_timestamp).strftime('%Y-%m-%d %H:%M:%S'), 'status': Const.DEV_WORK_STATUS_WORKING, 'finishedTime': start_timestamp + needTime * 60, 'coins': coins, 'needTime': needTime, 'isStart': True, 'openId': openId, 'refunded': False, 'vCardId': self._vcard_id } }) return devInfo def get_card_charge_result_from_data(self,data): if data[4:6] != '16': logger.info('receive wrong card charge result data = %s' % data) return False,RMB(0) balance = RMB(1) * (int(data[26:30], 16) / 10.0) result = True if data[34:36] == '01' else False return result,balance def analyze_event_data(self, data): cmdCode = data[4:6] if cmdCode in ['05']: port = int(data[18:20], 16) leftMoney = int(data[20:24], 16)/10.0 reason = data[24:26] if reason == '00': desc = u'您购买的充电时间用完了。' elif reason == '01': desc = u'可能是插头被拔掉,或者电瓶已经充满。系统判断为异常断电,由于电瓶车充电器种类繁多,可能存在误差。如有问题,请您及时联系商家协助解决问题并恢复充电。' elif reason == '02': desc = u'恭喜您!电池已经充满电!' elif reason == '03': desc = u'设备或端口出现问题,为了安全起见,被迫停止工作。建议您根据已经充电的时间评估是否需要到现场换到其他端口充电。' elif reason == '04': desc = u'警告!您的电器功率超过充电站的单路最大输出功率,强制切断输出!提醒您,为了公共安全大功率的电池千万不要放入楼道、室内等位置充电哦!' elif reason == '05': desc = u'刷卡退费结束了哦' else: desc = u'' return {'status': Const.DEV_WORK_STATUS_IDLE, 'cmdCode': cmdCode, 'port': port, 'leftMoney': leftMoney, 'reason': desc, 'isStart': False,'reasonCode':reason} elif cmdCode == '0D': port = int(data[16:18], 16) errCode = data[18:20] if errCode == '01': return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'端口输出故障', 'cmdCode': cmdCode, 'port': port, 'FaultCode': errCode} elif errCode == '02': return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'负载充电功率过大', 'cmdCode': cmdCode, 'port': port, 'FaultCode': errCode} elif errCode == '03': return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'漏电报警', 'cmdCode': cmdCode, 'port': port, 'FaultCode': errCode} elif errCode == '04': return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'过压报警', 'cmdCode': cmdCode, 'port': port, 'FaultCode': errCode} elif errCode == '05': return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'过流报警', 'cmdCode': cmdCode, 'port': port, 'FaultCode': errCode} elif errCode == '06': return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'欠压报警', 'cmdCode': cmdCode, 'port': port, 'FaultCode': errCode} elif errCode == '07': return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'过温报警', 'cmdCode': cmdCode, 'port': port, 'FaultCode': errCode} elif errCode == '08': return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'烟雾报警', 'cmdCode': cmdCode, 'port': port, 'FaultCode': errCode} elif cmdCode == '12': cardNo = data[18:26] cardNo = str(int(cardNo, 16)) balance = int(data[26:30], 16) / 10.0 return {'cmdCode': cmdCode, 'balance': balance, 'cardNo': cardNo} elif cmdCode == '04': # 刷卡报文 cardNo = data[18:26] cardNo = str(int(cardNo, 16)) port = int(data[26:28],16) coins = int(data[28:32],16)/10.0 balance = int(data[32:36], 16)/10.0 status = data[36:38] return {'cmdCode': cmdCode, 'cardNo':cardNo, 'coins': coins, 'port':port, 'balance':balance,'status':status} 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": '06', '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'][18::] port = int(data[0:2], 16) strStatus = int(data[2:4], 16) status = Const.DEV_WORK_STATUS_IDLE if strStatus == '02': status = Const.DEV_WORK_STATUS_WORKING elif strStatus == '03': status = Const.DEV_WORK_STATUS_FORBIDDEN elif strStatus == '04': status = Const.DEV_WORK_STATUS_FAULT leftMoney = int(data[4:8],16)/10.0 duration = int(data[8:12],16) power = int(data[12:16],16) return {'port': port, 'status': status, 'leftMoney': leftMoney,'duration':duration,'power':power} # 访问设备,获取设备端口信息 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'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) data = devInfo['data'][16::] result = {} portNum = int(data[2:4], 16) portData = data[4::] ii = 0 while ii < portNum: statusTemp = portData[ii * 2:ii * 2 + 2] 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(ii)] = 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 get_port_status(self, force = False): if force: return self.get_port_status_from_dev() ctrInfo = Device.get_dev_control_cache(self._device['devNo']) 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) statusDict = {} 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 active_deactive_port(self, port, active): if active: raise ServiceException({'result': 2, 'description': u'该设备不支持直接打开端口'}) 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, 'needTime': 0, 'leftTime': 0, 'endTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)}) newValue = {str(port): portCtrInfo} Device.update_dev_control_cache(self._device['devNo'], newValue) 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": '0B', 'data': portStr}) 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'][16::] port = int(data[2:4], 16) leftMoney = int(data[4:8], 16) return {'port': port, 'leftMoney': leftMoney} # 获取设备配置参数 def get_dev_setting(self): resultDict = {} tempDict = self.get_gear_conf() resultDict.update(tempDict) tempDict = self.get_fullstop_cardrefund() resultDict.update(tempDict) return resultDict def get_fullstop_cardrefund(self): try: dev = Device.objects.get(devNo = self._device['devNo']) except Exception, e: logger.error('get dev error = %s' % e) return {'autoStop': False, 'cardRefund': False} return {'autoStop': dev['otherConf'].get('autoStop', False), 'cardRefund': dev['otherConf'].get('cardRefund', False)} def set_fullstop_cardrefund(self, infoDict): data = '' if infoDict['autoStop']: data += '01' else: data += '00' if infoDict['cardRefund']: data += '01' else: data += '00' devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '13', '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'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'}) try: conf = Device.objects.get(devNo = self._device['devNo']).otherConf conf.update({'autoStop': infoDict['autoStop'], 'cardRefund': infoDict['cardRefund']}) Device.get_collection().update({'devNo': self._device['devNo']}, {'$set': {'otherConf': conf}}) except Exception, e: logger.error('update dev=%s coin enable ic enable e=%s' % (self._device['devNo'], e)) def get_gear_conf(self): resultDict = {'time1': 0, 'time2': 0, 'time3': 0, 'time4': 0, 'time5': 0, 'time6': 0, 'time7': 0, 'time8': 0, 'time9': 0, 'fee1': 0, 'fee2': 0, 'fee3': 0, 'serviceFee': 0, 'outputPower': 0} devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '15', '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: return resultDict confData = devInfo['data'][18::] time1 = int(confData[0:4], 16) time2 = int(confData[4:8], 16) time3 = int(confData[8:12], 16) time4 = int(confData[12:16], 16) time5 = int(confData[16:20], 16) time6 = int(confData[20:24], 16) time7 = int(confData[24:28], 16) time8 = int(confData[28:32], 16) time9 = int(confData[32:36], 16) fee1 = int(confData[36:38], 16) fee2 = int(confData[38:40], 16) fee3 = int(confData[40:42], 16) serviceFee = int(confData[42:44], 16) outputPower = int(confData[44:46], 16) result = { 'time1': time1, 'time2': time2, 'time3': time3, 'time4': time4, 'time5': time5, 'time6': time6, 'time7': time7, 'time8': time8, 'time9': time9, 'fee1': fee1, 'fee2': fee2, 'fee3': fee3, 'serviceFee':serviceFee,'outputPower':outputPower } return result def set_gear_conf(self, infoDict): data = '' data += fill_2_hexByte(hex(int(infoDict['time1'])), 4) data += fill_2_hexByte(hex(int(infoDict['time2'])), 4) data += fill_2_hexByte(hex(int(infoDict['time3'])), 4) data += fill_2_hexByte(hex(int(infoDict['time4'])), 4) data += fill_2_hexByte(hex(int(infoDict['time5'])), 4) data += fill_2_hexByte(hex(int(infoDict['time6'])), 4) data += fill_2_hexByte(hex(int(infoDict['time7'])), 4) data += fill_2_hexByte(hex(int(infoDict['time8'])), 4) data += fill_2_hexByte(hex(int(infoDict['time9'])), 4) data += fill_2_hexByte(hex(int(infoDict['fee1'])), 2) data += fill_2_hexByte(hex(int(infoDict['fee2'])), 2) data += fill_2_hexByte(hex(int(infoDict['fee3'])), 2) data += fill_2_hexByte(hex(int(infoDict['serviceFee'])), 2) data += fill_2_hexByte(hex(int(infoDict['outputPower'])), 2) devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], "funCode": '14', '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 recharge_card(self, cardNo, money, orderNo=None): try: data = 'EE1012{}'.format(make_six_bytes_session_id()) cardNo = fill_2_hexByte(hex(int(cardNo)), 8) data += cardNo + fill_2_hexByte(hex(int(money * 10)), 4) + '0001' devInfo = MessageSender.send(self.device, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': data, 'funCode': '12'}) if devInfo['rst'] != 0: if devInfo['rst'] == ErrorCode.DEVICE_CONN_FAIL: # 离线无法判断是否成功, 认为充值成功, 走售后解决 return { 'result': ErrorCode.DEVICE_CONN_FAIL, 'description': u'当前充电桩正在玩命找网络,请您稍候再试' }, None elif devInfo['rst'] == ErrorCode.BOARD_UART_TIMEOUT: return { 'result': ErrorCode.BOARD_UART_TIMEOUT, 'description': u'当前充电桩忙,无响应,请您稍候再试' }, None else: return { 'result': devInfo['rst'], 'description': u'系统异常' }, None resultData = devInfo['data'] if resultData[4:6] != '16': return { 'result': ErrorCode.PARAMETER_ERROR_TO_BOX, 'description': u'充值返回报文命令码不为16' }, None balance = RMB(int(resultData[26:30], 16)) * Decimal('0.1') result = True if resultData[34:36] == '01' else False if result: return { 'result': ErrorCode.SUCCESS, 'description': '' }, balance else: return { 'result': ErrorCode.IC_RECHARGE_FAIL, 'description': u'充值失败' }, balance except Exception as e: logger.exception(e) return { 'result': ErrorCode.EXCEPTION, 'description': e.message }, None def set_device_function(self, request, lastSetConf): if request.POST.has_key('autoStop'): # autoStop = True if request.POST.get('autoStop') == 'true' else False autoStop = request.POST.get("autoStop", False) lastSetConf.update({'autoStop': autoStop}) self.set_fullstop_cardrefund(lastSetConf) if request.POST.has_key('cardRefund'): # cardRefund = True if request.POST.get('cardRefund') == 'true' else False cardRefund = request.POST.get("cardRefund", False) lastSetConf.update({'cardRefund': cardRefund}) self.set_fullstop_cardrefund(lastSetConf) def set_device_function_param(self, request, lastSetConf): if request.POST.has_key('time1') and request.POST.has_key('time2'): lastSetConf.update({'time1': int(request.POST.get('time1', 0))}) lastSetConf.update({'time2': int(request.POST.get('time2', 0))}) lastSetConf.update({'time3': int(request.POST.get('time3', 0))}) lastSetConf.update({'time4': int(request.POST.get('time4', 0))}) lastSetConf.update({'time5': int(request.POST.get('time5', 0))}) lastSetConf.update({'time6': int(request.POST.get('time6', 0))}) lastSetConf.update({'time7': int(request.POST.get('time7', 0))}) lastSetConf.update({'time8': int(request.POST.get('time8', 0))}) lastSetConf.update({'time9': int(request.POST.get('time9', 0))}) lastSetConf.update({'fee1': int(request.POST.get('fee1', 0))}) lastSetConf.update({'fee2': int(request.POST.get('fee2', 0))}) lastSetConf.update({'fee3': int(request.POST.get('fee3', 0))}) lastSetConf.update({'serviceFee': int(request.POST.get('serviceFee', 0))}) lastSetConf.update({'outputPower': int(request.POST.get('outputPower', 0))}) self.set_gear_conf(lastSetConf)