# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import re import time import logging from decimal import Decimal from apilib.monetary import RMB from apps.web.common.models import TempValues from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT 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 from apps.web.user.models import Card logger = logging.getLogger(__name__) class ChangyuanFive(SmartBox): FINISH_REASON_MAP = { '01': '设备充满自停或拔掉插头', '02': '超功率停止', '03': '时间用完停止', '04': '远程停止', '05': '预付卡的扣费金额用完', '06': '到达安全充电时间', '07': '正常结束充电', '09': '电量超限', '10': '设备充满停止充电' } PAY_TYPE_MAP = { 'cash': '01', 'vCard': '02', 'coin': '03' } DEFAULT_DISCOUNT = '0' @staticmethod def _parse_D0_data(data): ''' 解析 设备参数 返还的数据 :param data: :return: ''' powerTime1 = ChangyuanFive.decode_str(data[6:10]) powerTime2 = ChangyuanFive.decode_str(data[10:14]) powerTime3 = ChangyuanFive.decode_str(data[14:18]) powerTime4 = ChangyuanFive.decode_str(data[18:22]) power1 = ChangyuanFive.decode_str(data[22:26]) power2 = ChangyuanFive.decode_str(data[26:30]) power3 = ChangyuanFive.decode_str(data[30:34]) power4 = ChangyuanFive.decode_str(data[34:38]) noloadPower = ChangyuanFive.decode_str(data[38:40]) noloadTime = ChangyuanFive.decode_str(data[40:42]) volume = ChangyuanFive.decode_str(data[42:44]) cardFee = ChangyuanFive.decode_str(data[44:48], ratio=0.01) floatPower = ChangyuanFive.decode_str(data[48:50]) floatTime = ChangyuanFive.decode_str(data[50:54]) cardFree = True if data[54:56] == 'AA' else False return locals() @staticmethod def _parse_D3_data(data): """ 解析同步结果指令上报 :param data: :return: """ cardType = data[6:8] cardNo = data[8:16] cardBalance = RMB(int(data[16:22], 16)) result = True if data[22:24] == 'AA' else False sid = int(data[24:28], 16) if cardType == '01': cardBalance = cardBalance * 0.01 return { "cardType": cardType, "cardNo": cardNo, "cardBalance": cardBalance, "result": result, "sid": sid } @staticmethod def _parse_D4_data(data): """ 解析 卡余额同步 结果 :param data: :type data: :return: :rtype: """ cardType = data[6:8] cardNo = data[8:16] cardBalance = RMB(int(data[16:22], 16)) if cardType == '01': cardBalance = cardBalance * 0.01 return { "cardType": cardType, "cardNo": cardNo, "cardBalance": cardBalance } @staticmethod def _parse_D7_data(data): ''' 火警数据解析 :param data: :type data: :return: :rtype: ''' return {'data': data[6:8]} @staticmethod def _parse_D8_data(data): ''' 定时上传 桩的工作状态 :param data: :type data: :return: :rtype: ''' if len(data) == 18: # voltage = int(data[6:10], 16) / 10.0 temperature = int(data[8:10], 16) if data[6:8] == '01': temperature = temperature * -1 defaultPortInfo = {'status': Const.DEV_WORK_STATUS_IDLE} portInfo = {}.fromkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', ], defaultPortInfo) return {'temperature': temperature, 'portInfo': portInfo} else: all_port_status = ChangyuanFive.decode_long_hex_to_list(data[6:26], transformer=False) all_port_power = ChangyuanFive.decode_long_hex_to_list(data[26:66],split=4) all_port_left = ChangyuanFive.decode_long_hex_to_list(data[66:106],split=4) temperature = int(data[108:110], 16) if data[106:108] == '01': temperature = temperature * -1 portInfo = {} for i in xrange(len(all_port_status)): item = {} item['power'] = all_port_power[i] item['leftTime'] = all_port_left[i] tempStatus = all_port_status[i] if tempStatus == '00': status = Const.DEV_WORK_STATUS_IDLE elif tempStatus == '01': status = Const.DEV_WORK_STATUS_WORKING elif tempStatus == '11': status = Const.DEV_WORK_STATUS_WORKING del item['leftTime'] item['leftMoney'] = round(all_port_left[i] * 0.01, 2) else: status = Const.DEV_WORK_STATUS_IDLE item['status'] = status portInfo[str(i + 1)] = item.copy() return {'temperature': temperature, 'portInfo': portInfo} @staticmethod def _parse_D9_data(data): ''' 充电开始 上传 :param data: :type data: :return: :rtype: ''' power = ChangyuanFive.decode_str(data[20:24]) coins = ChangyuanFive.decode_str(data[24:28], ratio=0.01) port = ChangyuanFive.decode_str(data[28:30]) payType = data[30:32] if payType == '00': # 次卡 cardNo = data[6:14] cardType = "00" cardBalance = ChangyuanFive.decode_str(data[14:20]) needTime = ChangyuanFive.decode_str(data[32:36]) elif payType == '01': # 扫码 needTime = ChangyuanFive.decode_str(data[32:36]) elif payType == '02': # 投币 needTime = ChangyuanFive.decode_str(data[32:36]) elif payType == '03': # 预付卡 cardNo = data[6:14] cardType = "01" cardBalance = ChangyuanFive.decode_str(data[14:20], ratio=0.01) leftMoney = ChangyuanFive.decode_str(data[32:36], ratio=0.01) elif payType == '04': cardNo = data[6:14] cardType = "0A" cardBalance = ChangyuanFive.decode_str(data[14:20], ratio=0.01) needTime = ChangyuanFive.decode_str(data[32:36]) else: pass return locals() @staticmethod def _parse_DA_data(data): ''' 充电结束上传 :param data: :type data: :return: :rtype: ''' port = ChangyuanFive.decode_str(data[6:8]) reasonCode = data[8:10] usedElec = ChangyuanFive.decode_str(data[10:14], ratio=0.001) left = ChangyuanFive.decode_str(data[14:18]) # 在线卡版本设备充满自停和插座掉落分离开 # if ChangyuanFive.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD: # if reasonCode == '01': # reasonCode = '10' reason = ChangyuanFive.FINISH_REASON_MAP.get(reasonCode, u'未知停止方式') return locals() @staticmethod def _parse_DE_data(data): ''' 实体卡返费 的指令 :param data: :type data: :return: :rtype: ''' cardNo = data[6:14] beforeRefund = int(data[14:20], 16) / 100.0 refund = int(data[20:24], 16) / 100.0 afterRefund = int(data[24:30], 16) / 100.0 return locals() @staticmethod def _parse_F0_data(data): cardType = data[6:8] cardNo = data[8:16] port = data[16:18] return locals() @staticmethod def _parse_COMMON_data(data): ''' 解析 通用的数据返回 一般表示成功还是失败 :param data: :type data: :return: :rtype: ''' return True if data[6:10] == '4F4B' else False def _send_data(self, funCode, data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=MQTT_TIMEOUT.NORMAL): result = MessageSender.send(device=self.device, cmd=cmd, payload={ 'IMEI': self.device.devNo, 'funCode': funCode, 'data': data }, timeout=timeout) if result['rst'] != 0: if result['rst'] == -1: raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请稍候再试'}) elif result['rst'] == 1: raise ServiceException({'result': 2, 'description': u'充电桩主板连接故障'}) else: raise ServiceException({'result': 2, 'description': u'系统错误'}) return result def _start(self, payMoney, port, time='0000', discount='0'): ''' 启动设备 :param payMoney: :return: ''' data = '' data += self.encode_str(payMoney, ratio=10) data += self.encode_str(time, length=4) data += self.encode_str(discount, length=1) data += self.encode_str(port, length=1) result = self._send_data('D2', data, timeout=MQTT_TIMEOUT.START_DEVICE) if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')): raise ServiceException({'result': '2', 'description': u'设备启动错误,请联系经销商协助解决(设备返回支付不成功)'}) return result @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, password): """ 设置设备的小区密码 密码是否还需要再校验,以及是否是0开头 """ if not password.isdigit(): raise ServiceException({'result': '2', 'description': u'密码必须必须为0-9数字'}) if len(password) != 10: raise ServiceException({'result': 0, 'description': u'密码长度必须为10位'}) passwordHex = self.transform_password(password) result = self._send_data('DB', data=passwordHex) if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')): raise ServiceException({'result': '2', 'description': u'设置小区密码失败,请重新试试'}) return result def _set_send_card(self, oldPassword, newPassword, FaKacardType): ''' 设置卡机模式 用于重置 实体卡的小区密码 ''' if not oldPassword.isdigit() or not newPassword.isdigit(): raise ServiceException({'result': '2', 'description': u'密码必须必须为0-9数字'}) if len(oldPassword) != 10 or len(newPassword) != 10: raise ServiceException({'result': 0, 'description': u'密码长度必须为10位'}) if not FaKacardType: raise ServiceException({'result': 0, 'description': u'请选择要发行卡的类型'}) oldPasswordHex = self.transform_password(oldPassword) newPasswordHex = self.transform_password(newPassword) FaKacardType = self.encode_str(FaKacardType) result = self._send_data('DC', data=oldPasswordHex + newPasswordHex + FaKacardType) if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')): raise ServiceException({'result': '2', 'description': u'设置发卡机模式失败,请重新试试'}) return result def _reboot_device(self): result = self._send_data('D5', '0000') if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')): raise ServiceException({'result': '2', 'description': u'设备复位错误,请重新试试'}) return result def _async_card_balance(self, cardType, cardNo, asyncMoney): """ 同步卡内余额 :param cardType: :param cardNo: :param asyncMoney: :return: """ # 如果是预付费的卡 同步的金额需要变换为分 balance = asyncMoney if cardType == '00' else asyncMoney * 100 # 获取随机流水号 sidKey = '{}-{}'.format(self.device.devNo, cardNo) sid = TempValues.get(sidKey) balanceHex = self.encode_str(int(balance), length=6) sidHex = self.encode_str(sid, length=4) MessageSender.send(device=self.device, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, payload={ 'IMEI': self.device.devNo, 'funCode': 'D3', 'data': cardType + balanceHex + sidHex }) def _ack_finished_massage(self, daid): return MessageSender.send(device=self.device, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, payload={ "IMEI": self.device.devNo, "data": "", "daid": daid, "funCode": "FA"}) def _ack(self, ack_id): self._send_data(funCode='AK', data=ack_id, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) # 获取10条未返费的记录 暂时未使用 def _no_refund_record(self): result = self._send_data('DE', '00') data = result['data'] noRefund = list() data = data[6: -8] for i in xrange(0, 120, 12): tempData = data[i:i + 12] cardNo = tempData[:8] amount = tempData[8:] if cardNo == 'FFFFFFFF' or cardNo == '00000000': continue amount = int(amount, 16) / 100.0 noRefund.append({'cardNo': cardNo, 'amount': amount}) return noRefund def _set_all_settings(self, settings): powerTime1 = settings.get('powerTime1') powerTime2 = settings.get('powerTime2') powerTime3 = settings.get('powerTime3') powerTime4 = settings.get('powerTime4') power1 = settings.get('power1') power2 = settings.get('power2') power3 = settings.get('power3') power4 = settings.get('power4') noloadPower = settings.get('noloadPower') noloadTime = settings.get('noloadTime') volume = settings.get('volume') cardFee = settings.get('cardFee') floatPower = settings.get('floatPower') floatTime = settings.get('floatTime') cardFree = settings.get('cardFree') # powerTime1 = self.check_params_range(params=powerTime1, minData=0, maxData=999, desc='第1段功率时间') powerTime2 = self.check_params_range(params=powerTime2, minData=0, maxData=999, desc='第2段功率时间') powerTime3 = self.check_params_range(params=powerTime3, minData=0, maxData=999, desc='第3段功率时间') powerTime4 = self.check_params_range(params=powerTime4, minData=0, maxData=999, desc='第4段功率时间') power1 = self.check_params_range(params=power1, minData=0, maxData=999, desc='第1段功率值') power2 = self.check_params_range(params=power2, minData=0, maxData=999, desc='第2段功率值') power3 = self.check_params_range(params=power3, minData=0, maxData=999, desc='第3段功率值') power4 = self.check_params_range(params=power4, minData=0, maxData=999, desc='第4段功率值') noloadPower = self.check_params_range(params=noloadPower, minData=0, maxData=50, desc='空载检测功率') noloadTime = self.check_params_range(params=noloadTime, minData=0, maxData=255, desc='空载功率检测时间') volume = self.check_params_range(params=volume, minData=0, maxData=8, desc='音量') cardFee = self.check_params_range(params=cardFee, minData=0, maxData=655, desc='预付卡预扣金额') floatPower = self.check_params_range(params=floatPower, minData=0, maxData=50, desc='浮动功率') floatTime = self.check_params_range(params=floatTime, minData=0, maxData=60000, desc='浮动功率延长时间') data = '' data += self.encode_str(powerTime1, length=4) data += self.encode_str(powerTime2, length=4) data += self.encode_str(powerTime3, length=4) data += self.encode_str(powerTime4, length=4) data += self.encode_str(power1, length=4) data += self.encode_str(power2, length=4) data += self.encode_str(power3, length=4) data += self.encode_str(power4, length=4) data += self.encode_str(noloadPower, length=2) data += self.encode_str(noloadTime, length=2) data += self.encode_str(volume, length=2) data += self.encode_str(cardFee, length=4, ratio=100) data += self.encode_str(floatPower, length=2) data += self.encode_str(floatTime, length=4) data += cardFree result = self._send_data('D1', data) if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')): raise ServiceException({'result': '2', 'description': u'设置设备参数错误,请联系经销商协助解决'}) return result def _set_elec_price(self, elecPrice): """ 设置电量单价 这个参数是模块侧的 只下发到模块驱动 不到主板 :param elecPrice: :return: """ elecPriceHex = "{:0>8X}".format(int(3600 * 1000 * float(elecPrice))) self._send_data("EP", elecPriceHex) # 更新参数设置 Device.get_collection().update_one({'devNo': self.device['devNo']}, {'$set': {'otherConf.elecPrice': elecPrice}}) Device.invalid_device_cache(self.device.devNo) def _get_dev_port_info(self): result = self._send_data('E1', '0000') data = result['data'] return ChangyuanFive._parse_D8_data(data) @staticmethod def check_params_range(params, minData=None, maxData=None, desc=''): # type:(str,float,float,str) -> str ''' 检查参数,返回字符串参数 ''' if params is None: raise ServiceException({'result': 2, 'description': u'参数错误.'}) if not isinstance(params, Decimal): params = Decimal(params) if not minData and maxData: if not isinstance(maxData, Decimal): maxData = Decimal(maxData) if params <= maxData: return '%g' % params else: raise ServiceException({'result': 2, 'description': u'%s超出可选范围,可选最大值为%g' % (desc, maxData)}) if not maxData and minData: if not isinstance(minData, Decimal): minData = Decimal(minData) if minData <= params: return '%g' % params else: raise ServiceException({'result': 2, 'description': u'%s超出可选范围,可选最小值为%g' % (desc, minData)}) if not minData and not maxData: return '%g' % params else: if not isinstance(minData, Decimal): minData = Decimal(minData) if not isinstance(maxData, Decimal): maxData = Decimal(maxData) if minData <= params <= maxData: return '%g' % params else: raise ServiceException( {'result': 2, 'description': u'%s参数超出可选范围,可取范围为%g-%g' % (desc, minData, maxData)}) @staticmethod def reverse_hex(data): # type:(str) -> str if not isinstance(data, str): raise TypeError return "".join(list(reversed(re.findall(r".{2}", data)))) @staticmethod def encode_str(data, length=2, ratio=1.0, base=16): # type:(any,int,float,int) -> str if not isinstance(data, Decimal): data = Decimal(data) if not isinstance(length, str): length = str(length) if not isinstance(ratio, Decimal): ratio = Decimal(ratio) end = 'X' if base == 16 else 'd' encodeStr = '%.' + length + end encodeStr = encodeStr % (data * ratio) return encodeStr @staticmethod def decode_str(data, ratio=1.0, base=16, reverse=False, to_int=True): ''' ratio:比率单位转换 ''' if not isinstance(data, str): data = str(data) if reverse: data = "".join(list(reversed(re.findall(r".{2}", data)))) if to_int: result = int(data, base) * ratio return int(result) if result == int(result) else round(result, 2) else: return '%.10g' % (int(data, base) * ratio) @staticmethod def decode_long_hex_to_list(data, split=2, ratio=1.0, base=16, transformer=True): # type:(str,int,float,int,bool) -> list ''' return: list ''' if len(data) % split != 0: raise Exception('Invalid data') pattern = r'.{%s}' % split hex_list = re.findall(pattern, data) if transformer: hex_list = map(lambda x: ChangyuanFive.decode_str(x, ratio=ratio, base=base), hex_list) return hex_list def test(self, coins): """ 测试端口 测试机器启动 固定端口为 01 { 'IMEI': '865650040606119', 'cmd': 210, 'data': '0303E8000001', 'funCode': 'D2' }, """ return self._start('coin', coins, 01) def get_dev_setting(self): """ 获取设备参数 :return: """ result = self._send_data('D0', '00') data = result["data"] devSetting = self._parse_D0_data(data) disable = self.device.get('otherConf', {}).get('disableDevice') elecPrice = self.device.get("otherConf", {}).get("elecPrice", 0) needBindCard = self.device.get("otherConf", {}).get("needBindCard", True) vCardTime = self.device.get("otherConf", {}).get("vCardTime", 300) discount = self.device.get("otherConf", {}).get("discount", self.DEFAULT_DISCOUNT) onlineCardFee = self.device.get("otherConf", {}).get("onlineCardFee",0) maxTime = self.device.get("otherConf", {}).get("maxTime",720) devSetting.update( {'disable': disable, 'elecPrice': elecPrice, 'needBindCard': needBindCard, 'vCardTime': vCardTime, 'discount': discount,'onlineCardFee':onlineCardFee,'maxTime':maxTime}) dev_control_cache = Device.get_dev_control_cache(self.device.devNo) devSetting['temperature'] = dev_control_cache.get('temperature', '获取中...') if self.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD: if devSetting['temperature'] == '获取中...': devSetting['temperature'] = self._get_dev_port_info().get('temperature') devSetting['voltage'] = dev_control_cache.get('voltage', '获取中...') return devSetting def set_device_function_param(self, request, lastSetConf): """设备设备的参数""" if "pwd" in request.POST: return self._set_password(request.POST["pwd"]) if "old_pwd" in request.POST: old_pwd = request.POST["old_pwd"] new_pwd = request.POST["new_pwd"] cardType = request.POST["FaKacardType"] return self._set_send_card(old_pwd, new_pwd, cardType) # 卡券启动时间 if 'vCardTime' in request.POST: vCardTime = int(request.POST.get('vCardTime')) if vCardTime != int(self.device['otherConf'].get('vCardTime', 300)): Device.get_collection().update_one({'devNo': self.device['devNo']}, {'$set': {'otherConf.vCardTime': vCardTime}}) Device.invalid_device_cache(self.device.devNo) # 防止盗电的功能参数 elecPrice = request.POST.get("elecPrice", 0) self._set_elec_price(elecPrice) # 折扣参数设置 discount = request.POST.get("discount", 0) self._set_discount(discount) # 对一下参数的设置 new_settings = dict() for _paramKey, _paramValue in lastSetConf.items(): if request.POST.get(_paramKey) is not None: _paramValue = request.POST[_paramKey] new_settings[_paramKey] = _paramValue cardFree = "AA" if new_settings["cardFree"] else "00" new_settings.update({"cardFree": cardFree}) self._set_all_settings(new_settings) maxTime = request.POST.get("maxTime") if maxTime is not None: self.set_max_time(int(maxTime)) if "onlineCardFee" in request.POST: onlineCardFee = request.POST['onlineCardFee'] self.device.update_device_obj(**{ 'otherConf.onlineCardFee': onlineCardFee}) def set_device_function(self, request, lastSetConf): """开关类参数配置""" if "disable" in request.POST: return self.set_dev_disable(request.POST.get('disable')) if "reboot" in request.POST: return self._reboot_device() if "cardFree" in request.POST: cardFree = "AA" if request.POST["cardFree"] else "00" lastSetConf.update({"cardFree": cardFree}) return self._set_all_settings(lastSetConf) 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 get_port_status(self, force=False): ''' 获取设备状态 昌原的状态都是被动获取的 :param force: :return: ''' statusDict = dict() if self.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD: portsInfo = self._get_dev_port_info().get('portInfo',{}) allPorts = len(portsInfo) for portNum in range(allPorts): tempDict = portsInfo.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}) else: devCache = Device.get_dev_control_cache(self._device['devNo']) 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 get_port_status_from_dev(self): return self.get_port_info(False) def get_port_info(self, port): ''' 获取 端口的详细信息 :param port: :return: ''' # 昌源五代机新增在线卡功能,并修改了部分协议 devCache = Device.get_dev_control_cache(self.device.devNo) if self.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD: portsInfo = self._get_dev_port_info() portInfo = portsInfo.get('portInfo',{}).get(port) if portInfo: portCache = devCache.get(port) startTime = portCache.get('startTime') needTime = portCache.get('needTime') leftTime = portInfo.pop('leftTime') usedTime = needTime - leftTime portInfo.update({"startTime": startTime, "usedTime": usedTime}) temperature = portsInfo.get('temperature') devCache['temperature'] = temperature Device.update_dev_control_cache(self.device.devNo, devCache) return portInfo return devCache.get(str(port), dict()) @property def isHaveStopEvent(self): return True def set_dev_disable(self, disable): if disable: status = '00AA' else: status = '0055' result = self._send_data(funCode='DD', data=status) if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')): 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 stop(self, port=None): if not port: raise ServiceException({"result": "2", "description": u"请选择停止端口!"}) self.stop_charging_port(port) def stop_charging_port(self, port): portHex = fill_2_hexByte(hex(int(port)), 2) result = self._send_data('D6', data=portHex) data = result.get('data') if data[6: 8] != '4F': raise ServiceException({'result': 2, 'description': u'停止充电失败,请重新试试'}) # 这里只下发命令 不清理端口缓存,等上报事件清除 # Device.clear_port_control_cache(self.device.devNo,int(port)) def check_dev_status(self, attachParas = None): pass def start_device(self, package, openId, attachParas): chargeIndex = attachParas.get('chargeIndex') if not chargeIndex: raise ServiceException({'result': 2, 'description': u'请选择正确的充电端口'}) coins = round(package.get('coins'), 2) price = round(package.get('price'), 2) rechargeRcdId = attachParas.get('linkedRechargeRecordId') orderNo = attachParas.get('orderNo') if self._vcard_id: vCardTime = self._device.get("otherConf", dict()).get("vCardTime", 300) result = self._start(payMoney='0', port=chargeIndex, time=vCardTime) result['finishedTime'] = int(time.time()) + vCardTime * 60 + 300 else: discount = self.get_discount(rechargeRcdId) result = self._start(payMoney=coins, port=chargeIndex, discount=discount) result['finishedTime'] = int(time.time()) + 12 * 60 * 60 lineInfo = Device.get_dev_control_cache(self.device.devNo).get(chargeIndex, {}) is_continue = lineInfo.get('openId') == openId and lineInfo.get('status', Const.DEV_WORK_STATUS_IDLE) == Const.DEV_WORK_STATUS_WORKING portDict = { 'port': chargeIndex, 'openId': openId, 'isStart': True, 'coins': coins, 'price': price, 'orderNo': orderNo, "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'status': Const.DEV_WORK_STATUS_WORKING, 'consumeType': 'mobile', # 用来显示的 显示在端口管理里面 'payType': '01', } if self._vcard_id: portDict['vCardId'] = self._vcard_id if rechargeRcdId: item = { 'rechargeRcdId': rechargeRcdId } if is_continue: pay_info = lineInfo.get('payInfo', list()) else: pay_info = list() pay_info.append(item) portDict['payInfo'] = pay_info if is_continue: portDict['startTime'] = lineInfo.get('startTime', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) if 'coins' in lineInfo: portDict['coins'] = coins + lineInfo['coins'] if 'price' in lineInfo: portDict['price'] = price + lineInfo['price'] Device.update_port_control_cache(self.device.devNo, portDict) return result def analyze_event_data(self, data): ''' 解析事件 :param data: :return: ''' cmdCode = data[2:4] if cmdCode == 'D3': eventData = ChangyuanFive._parse_D3_data(data) elif cmdCode == 'D4': eventData = ChangyuanFive._parse_D4_data(data) elif cmdCode == 'D7': eventData = ChangyuanFive._parse_D7_data(data) elif cmdCode == 'D8': eventData = ChangyuanFive._parse_D8_data(data) elif cmdCode == 'D9': eventData = ChangyuanFive._parse_D9_data(data) elif cmdCode == 'DA': eventData = ChangyuanFive._parse_DA_data(data) if eventData['reasonCode']== "01": # 在线卡版本设备充满自停和插座掉落分离开 if self.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD: reason = ChangyuanFive.FINISH_REASON_MAP.get("10", u'未知停止方式') # eventData['reasonCode'] == "10" # eventData['reason'] == reason eventData.update({'reasonCode' : "10"}) eventData.update({'reason': reason}) elif cmdCode == 'DE': eventData = ChangyuanFive._parse_DE_data(data) elif cmdCode == 'F0': eventData = ChangyuanFive._parse_F0_data(data) else: logger.error('error cmdCode <{}>, data is <{}>'.format(cmdCode, data)) return eventData.update({'cmdCode': cmdCode}) return eventData def get_device_function_by_key(self, data): if data == 'noRefund': res = self._no_refund_record() if not res: return {} return {'noRefund': res} def active_deactive_port(self, port, active): if not active: self.stop_charging_port(port) else: raise ServiceException({'result': 2, 'description': u'此设备不支持直接打开端口'}) def _check_package(self, package): """ 获取设备启动的发送数据 根据设备的当前模式以及套餐获取 :param package: :return: """ unit = package.get("unit", u"分钟") _time = float(package.get("time", 0)) # 按时间计费 if unit == u"小时": billingType = "time" _time = _time * 60 elif unit == u"天": billingType = "time" _time = _time * 24 * 60 elif unit == u"秒": billingType = "time" _time = _time / 60 elif unit == u"分钟": billingType = "time" _time = _time else: billingType = "elec" _time = _time return _time, unit, billingType def get_discount(self, rechargeRcdId): """ 根据启动方式的不通获取不同的折扣 金币启动的不打折 其余的根据设备的折扣而定 """ if rechargeRcdId: return self._device.get("otherConf", dict()).get("discount", self.DEFAULT_DISCOUNT) else: return self.DEFAULT_DISCOUNT def set_max_time(self, maxTime): data = "{:04X}".format(maxTime) try: self._send_data("F8", data, timeout=5) self.device.update_device_obj(**{ 'otherConf.maxTime': maxTime}) except ServiceException as e: raise ServiceException({"result": 2, "description": u"该设备暂不支持充电时长,请联系厂家进行升级"}) def _set_discount(self, discount): Device.get_collection().update_one({'devNo': self.device['devNo']}, {'$set': {'otherConf.discount': discount}}) Device.invalid_device_cache(self.device.devNo) def _response_F0(self, cardType, cardNo, port,balance): cardType = str(cardType) cardNo = str(cardNo) port = str(port) oldBalance = self.encode_str(float(balance) * 100,6) onlineCardFee = 3 if self.device.otherConf.get('onlineCardFee',0) == 0 else self.device.otherConf.get('onlineCardFee') if RMB(balance) < RMB(onlineCardFee): money = self.encode_str(float(balance) * 100,4) else: onlineCardFee = float(onlineCardFee) * 100 money = self.encode_str(onlineCardFee,4) data = cardType+cardNo+port+money+oldBalance result = self._send_data('F1',data) if result['data'][6:10] != '4F4B': raise ServiceException({'result': 2, 'description': u'支付失败'})