123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import binascii
- import datetime
- import logging
- import time
- from apilib.monetary import VirtualCoin
- from apps.web.constant import DeviceCmdCode, Const
- from apps.web.core.adapter.base import SmartBox, fill_2_hexByte
- from apps.web.core.device_define.yongxin import DefaultParams, ChargeMode
- 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 MyUser, ConsumeRecord
- logger = logging.getLogger(__name__)
- class ChargingYongxinBox(SmartBox):
- def __init__(self, device):
- super(ChargingYongxinBox, self).__init__(device)
- def _send_data(self, funCode, data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=30):
- result = MessageSender.send(
- device=self.device,
- cmd=cmd,
- payload={"IMEI": self.device.devNo, "funCode": funCode, "data": data},
- timeout=timeout
- )
- if result.get("rst") == 0:
- return result
- if result.get("rst") == -1:
- raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
- if result.get('rst') == 1:
- raise ServiceException({'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
- raise ServiceException({'result': 2, 'description': u'通讯错误'})
- @staticmethod
- def _to_ascii(s):
- return binascii.hexlify(s).upper()
- @staticmethod
- def _to_str(h):
- """
- 将 16进制ascii 转换成 字符串
- :param h:
- :return:
- """
- return binascii.unhexlify(h).lower()
- @staticmethod
- def _suit_package(package):
- """
- 返回充电模式以及充电参数
- """
- unit = package["unit"]
- coins = VirtualCoin(package['coins'])
- userAcquire = package["time"]
- nowTime = int(time.time())
- # 暂缺一种充满自停模式
- if int(userAcquire) == 999 and unit == u"分钟":
- mode = ChargeMode.AUTO_STOP
- finishedTime = nowTime + 12 * 60 * 60
- chargeParam = 0xFFFF
- return mode, chargeParam, finishedTime
- if unit == u"天":
- mode = ChargeMode.TIME_QUOTA
- chargeParam = userAcquire * 24 * 60
- finishedTime = nowTime + chargeParam * 60
- return mode, chargeParam, finishedTime
- elif unit == u"小时":
- mode = ChargeMode.TIME_QUOTA
- chargeParam = userAcquire * 60
- finishedTime = nowTime + chargeParam * 60
- return mode, chargeParam, finishedTime
- elif unit == u"分钟":
- mode = ChargeMode.TIME_QUOTA
- chargeParam = userAcquire
- finishedTime = nowTime + chargeParam * 60
- return mode, chargeParam, finishedTime
- elif unit == u"度":
- mode = ChargeMode.ELEC_QUOTA
- chargeParam = userAcquire
- finishedTime = nowTime + 12 * 60 * 60
- return mode, chargeParam, finishedTime
- elif unit == u"次":
- mode = ChargeMode.COIN_QUOTA
- chargeParam = int((coins * 100).amount)
- finishedTime = nowTime + 12 * 60 * 60
- return mode, chargeParam, finishedTime
- else:
- raise ServiceException({"result": 2, "description": u"错误的套餐配置,请联系经销商"})
- @property
- def isHaveStopEvent(self):
- return True
- def _response_login(self, devType):
- """
- 回复设备的登录
- :return:
- """
- # 生成时间戳
- loginTime = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
- # 生成桩编号
- loginLogicalCode = "01{:0>10}".format(self.device.logicalCode.replace("G", "")) if self.device.logicalCode.startswith("G") else "{:0>12}".format(self.device.logicalCode)
- # 生成 url 的连接
- loginUrl = "{:0<128}".format(self._to_ascii(DefaultParams.DEFAULT_LOGIN_URL_FORMAT))
- otherConf = self.device.get("otherConf", dict())
- # 获取充电口数量 设备上传指令的时候 会上传当前桩的类型 如果没有设置的,就使用桩上传的
- portNum = otherConf.get("portNum", devType % 10)
- # 获取输出功率
- outputPower = otherConf.get("outputPower", 70)
- # 获取电子锁模式
- lockMode = otherConf.get('lockMode', 0)
- hexLockMode = fill_2_hexByte(hex(int(lockMode)), 2)
- hexPortNum = fill_2_hexByte(hex(int(portNum)), 2)
- hexOutputPower = fill_2_hexByte(hex(int(outputPower)), 2)
- data = loginTime + loginLogicalCode + loginUrl + '10' + hexPortNum + hexOutputPower + hexLockMode + '10'
- self._send_data(funCode="12", data=data, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
- def _response_elec_price(self):
- """
- 设备请求电量费用的时候回复电量费用
- """
- priceList = self.device.get("otherConf", dict()).get("priceList")
- if not priceList:
- priceList = [DefaultParams.DEFAULT_ELEC_PRICE] * DefaultParams.DEFAULT_ELEC_PRICE_INTERVAL_NUM
- priceHex = "".join(map(lambda x: "{:0>4X}".format(int(100 * x)), priceList))
- data = "{}0000000000000000".format(priceHex)
- self._send_data(funCode="40", data=data, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
- def _response_card_start(self, port, seqNo):
- portHex = fill_2_hexByte(hex(int(port)), 2)
- data = portHex + seqNo
- return self._send_data("18", data, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, timeout=10)
- def _response_finished(self, port, seqNo, result):
- portHex = fill_2_hexByte(hex(int(port)), 2)
- seqNo = seqNo
- resultHex = fill_2_hexByte(hex(int(result)), 2)
- data = portHex + seqNo + resultHex
- self._send_data("1C", data, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, timeout=5)
- def _control_device(self, fault):
- data = "0001" if fault else "0000"
- result = self._send_data(funCode="20", data=data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC)
- if result["data"][20:22] != "00":
- raise ServiceException({"result": 2, "description": u"重启失败"})
- def _start_remote(self, port, seqNo, mode, chargeParam, balance):
- """
- 远程启动设备
- """
- portHex = fill_2_hexByte(hex(int(port)), 2)
- seqNoHex = seqNo
- modeHex = fill_2_hexByte(hex(int(mode)), 2)
- chargeParamHex = fill_2_hexByte(hex(int(chargeParam)), 4)
- balanceHex = fill_2_hexByte(hex(int(balance*100)), 8)
- data = portHex + seqNoHex + modeHex + chargeParamHex + balanceHex + '{:0>16}'.format(0)
- result = self._send_data("16", data=data)
- code = result["data"][54:56]
- if code == "00":
- return result
- if code == '01':
- raise ServiceException({'result': 2, 'description': u'您没有插充电枪,请先插好充电枪,然后再操作哦!'})
- if code == '02':
- raise ServiceException({'result': 2, 'description': u'您使用的充电枪已经在充电了,请您换其他空闲的充电枪吧!'})
- if code == '03':
- raise ServiceException({'result': 2, 'description': u'设备故障,暂时无法使用,您本次操作不会扣费,您可以试试其他可用的设备。'})
- raise ServiceException({'result': 2, 'description': u'设备返回一个未知的错误,本次操作不会扣费,请您重试看是否能够解决'})
- def _stop_remote(self, port, seqNo):
- """
- 远程停止
- """
- portHex = fill_2_hexByte(hex(int(port)), 2)
- seqNoHex = seqNo
- data = portHex + seqNoHex
- result = self._send_data("1A", data)
- if result['data'][54:56] != "00":
- raise ServiceException({'result': 2, 'description': u'该充电枪已经空闲,没有充电'})
- return result
- def get_elec_price_from_device(self):
- """
- 从设备上获取电价
- :return:
- """
- data = "{:016X}".format(0)
- result = self._send_data("42", data)
- return result.get("data", "")[20:212]
- def compare_elec_price(self, devElecPrice):
- # 首先看版本 低版本直接是正常的
- if self.device.driverVersion != "v2.0.0":
- return True
- localPriceList = self.device.get("otherConf", dict()).get("priceList")
- if not localPriceList:
- localPriceList = [DefaultParams.DEFAULT_ELEC_PRICE] * DefaultParams.DEFAULT_ELEC_PRICE_INTERVAL_NUM
- localPrice = "".join(map(lambda x: "{:0>4X}".format(int(100 * x)), localPriceList))
- # 比较的是原始的报文
- return devElecPrice == localPrice
- def start_device(self, package, openId, attachParas):
- devElecPrice = self.get_elec_price_from_device()
- if not self.compare_elec_price(devElecPrice):
- raise ServiceException({"result": "2", "description": u"启动错误(10008)"})
- chargeIndex = attachParas.get("chargeIndex")
- if not chargeIndex:
- raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'})
- if not openId:
- raise ServiceException({"result": 2, "description": u"本设备暂不支持上分"})
- mode, chargeParam, finishedTime = self._suit_package(package)
- # 余额统统变成 该用户的此地址下的所有的金额
- order = ConsumeRecord.objects.get(orderNo=attachParas["orderNo"]) # type: ConsumeRecord
- user = MyUser.objects.get(openId=openId, groupId=self.device.groupId) # type: MyUser
- userBalance = user.balance
- # 留下订单的钱给service扣除
- # TODO 用心的是将 用户的全部余额下发 然后用完后退还 最好单独起个service 现在没有 所以先简陋处理 临时发布前的补丁
- balance = float(userBalance.amount)
- # seqNo 之前的做法是放到模块侧生成 暂时不明白为什么
- seqNo = "{:0>32}".format(0)
- result = self._start_remote(chargeIndex, seqNo, mode, chargeParam, balance)
- user.pay(userBalance-order.coin)
- result["finishedTime"] = finishedTime
- # 后 4位 的序列号 可能是16进制的 之前直接永序列号当作订单 所以需要将16进制转换为10 进制 现在不需要转换了
- result["sequanceNo"] = result['data'][22:54]
- portDict = {
- 'startTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- 'status': Const.DEV_WORK_STATUS_WORKING,
- 'finishedTime': finishedTime,
- 'coins': float(balance),
- 'isStart': True,
- 'sequanceNo': result["sequanceNo"],
- 'orderNo': attachParas['orderNo'],
- 'useType': 'mobile',
- 'openId': openId,
- 'refunded': False,
- 'chargeMode': mode,
- 'need': chargeParam,
- }
- Device.update_dev_control_cache(self._device['devNo'], {str(chargeIndex): portDict})
- return result
- def stop(self, port=None):
- if not port:
- raise ServiceException({"result": 2, "description": u"请选择需要结束的充电端口"})
- portCache = Device.get_port_control_cache(self.device.devNo, str(port))
- seqNo = portCache.get("sequanceNo")
- if not seqNo:
- raise ServiceException({"result": 2, "description": u"当前端口无工作"})
- return self._stop_remote(port, seqNo)
- def analyze_event_data(self, data):
- cmdCode = data[2:4]
- if cmdCode == '11': # 登录请求
- return {'cmdCode': cmdCode, "devType": int(data[20:22], 16)}
- if cmdCode == "41": # 这个是请求电价数据
- return {"cmdCode": cmdCode}
- if cmdCode == "19": # 充电数据实时上传
- port = int(data[20:22], 16)
- outputVoltage = int(data[22:26], 16) / 10.0
- outputElec = int(data[26:30], 16) / 10.0
- elec = int(data[30:34], 16) / 100.0
- spendMoney = int(data[34:38], 16) / 100.0
- duration = int(data[40:44], 16)
- return {
- 'cmdCode': cmdCode,
- 'port': port,
- 'outputVoltage': outputVoltage,
- 'outputElec': outputElec,
- 'elec': elec,
- 'spendMoney': spendMoney,
- 'duration': duration,
- }
- if cmdCode == '17': # 刷卡启动的 也就是本地启动
- port = int(data[20:22], 16)
- sequanceNo = data[22:54]
- cardNo = data[54:70]
- balance = int(data[70:78], 16) / 100.0
- return {
- 'cmdCode': cmdCode,
- 'port': port,
- 'sequanceNo': sequanceNo,
- 'cardNo': cardNo,
- # 'isStart': True,
- # 'useType': 'card',
- 'balance': balance
- }
- if cmdCode == '1D': # 结算账单上传
- port = int(data[20:22], 16)
- sequanceNo = data[22:54]
- elec = int(data[54:58], 16) / 100.0
- spendMoney = int(data[58:62], 16) / 100.0
- duration = int(data[62:66], 16)
- balance = int(data[98:106], 16) / 100.0
- reason = int(data[106: 108], 16)
- return {
- 'port': port,
- 'sequanceNo': sequanceNo,
- 'elec': elec,
- 'spendMoney': spendMoney,
- 'duration': duration,
- 'cmdCode': '1D',
- 'balance': balance,
- 'reason': reason
- }
- def set_device_function_param(self, request, lastSetConf):
- if request.POST.has_key('lockMode') and request.POST.has_key('outputPower'):
- lockMode = int(request.POST.get('lockMode'))
- portNum = int(request.POST.get('portNum'))
- outputPower = 35 if int(request.POST.get('outputPower')) == '1' else 70
- dev = Device.objects.get(devNo = self._device['devNo'])
- dev.otherConf['lockMode'] = lockMode
- dev.otherConf['portNum'] = portNum
- dev.otherConf['outputPower'] = outputPower
- dev.save()
- def set_device_function(self, request, lastSetConf):
- lock = request.POST.get("lock")
- if lock:
- return self._control_device(True)
- unlock = request.POST.get("unlock")
- if unlock:
- return self._control_device(False)
- def get_dev_setting(self):
- resultDict = {}
- dev = Device.objects.get(devNo = self._device['devNo'])
- resultDict['lockMode'] = dev.otherConf.get('lockMode', 0)
- resultDict['portNum'] = dev.otherConf.get('portNum', 1)
- resultDict['outputPower'] = 1 if dev.otherConf.get('outputPower', 70) == 35 else 2
- return resultDict
- def get_port_info(self, line):
- ctrInfo = Device.get_dev_control_cache(self._device['devNo'])
- value = ctrInfo.get(line, {})
- value.update({'port': line})
- if 'sequanceNo' in value:
- sequanceNo = value['sequanceNo']
- consumeRcd = ConsumeRecord.objects.get(sequanceNo = sequanceNo)
- elif 'orderNo' in value:
- orderNo = value['orderNo']
- consumeRcd = ConsumeRecord.objects.get(orderNo = orderNo)
- else:
- consumeRcd = None
- if consumeRcd:
- order = {'coin': float(consumeRcd.coin)}
- value.update({'order': order})
- return value
- def get_port_status(self, force=False):
- if force:
- return self.get_port_status_from_dev()
- statusDict = {}
- self.get_port_status_from_dev()
- ctrInfo = Device.get_dev_control_cache(self._device['devNo'])
- allPorts = ctrInfo.get('allPorts', 2)
- 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, DeviceCmdCode.GET_DEVINFO, {'IMEI': self._device['devNo']})
- 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'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
- devObj = Device.objects.get(devNo = self._device['devNo'])
- chargerType = devInfo.get('chargerType', None)
- if chargerType is not None:
- devObj['otherConf']['chargerType'] = int(chargerType)
- devObj.save()
- else:
- chargerType = devObj['otherConf'].get('chargerType', None)
- if chargerType is None:
- raise ServiceException({'result': 2, 'description': u'请您重启下充电桩(10001)'})
- if "port1Status" not in devInfo:
- raise ServiceException({'result': 2, 'description': u'请您重启下充电桩(10002)'})
- result = {}
- usedPorts, usePorts = 0, 0
- if int(chargerType) in [129, 145]:
- portNum = 1
- port1Status = devInfo['port1Status']
- if port1Status == 0:
- result['1'] = {'status': Const.DEV_WORK_STATUS_IDLE}
- usePorts += 1
- elif port1Status == 1:
- result['1'] = {'status': Const.DEV_WORK_STATUS_IDLE}
- usePorts += 1
- elif port1Status == 2:
- result['1'] = {'status': Const.DEV_WORK_STATUS_WORKING}
- usedPorts += 1
- else:
- result['1'] = {'status': Const.DEV_WORK_STATUS_FINISHED}
- usedPorts += 1
- else:
- portNum = 2
- port1Status = devInfo['port1Status']
- if port1Status == 0:
- result['1'] = {'status': Const.DEV_WORK_STATUS_IDLE}
- usePorts += 1
- elif port1Status == 1:
- result['1'] = {'status': Const.DEV_WORK_STATUS_CONNECTED}
- usePorts += 1
- else:
- result['1'] = {'status': Const.DEV_WORK_STATUS_WORKING}
- usedPorts += 1
- port2Status = devInfo['port2Status']
- if port2Status == 0:
- result['2'] = {'status': Const.DEV_WORK_STATUS_IDLE}
- usePorts += 1
- elif port2Status == 1:
- result['2'] = {'status': Const.DEV_WORK_STATUS_CONNECTED}
- usePorts += 1
- else:
- result['2'] = {'status': Const.DEV_WORK_STATUS_WORKING}
- usedPorts += 1
- Device.update_dev_control_cache(self._device['devNo'],
- {'allPorts': portNum, '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 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'此设备不支持直接打开端口'})
|