123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681 |
- # -*- coding: utf-8 -*-
- # 超辰充电桩业务流程描述
- # 整个业务比较简单,算是经典的充电桩业务对接,但有以下问题需要注意
- # 1. 超辰的通信方式为485通信,485通信中有一项为通信地址,即板位地址,属于硬件开关
- # 2. 该板位地址经多次与该技术方沟通,确定了以下的规则:
- # 2.1 有端口操作指令的时候,该板位地址使用端口号来代替,如操作10号端口开启,则板位地址数据为0A
- # 2.2 无端口指令操作的时候,该板位地址 固定为01,例如参数获取以及参数设置等不涉及端口的指令
- # 3. 超辰充电桩指令目前看起来是不完备的,缺少了一条 查看当前充电方式的指令,原因如下
- # 3.1 充电模式分为 电量计费 以及 时间计费, 该模式可远程切换
- # 3.2 切换模式后,主板结束的时候上报的指令数据含义不一样,电量模式下需要使用 EA 指令结算退款 时间模式下 使用C9指令退款
- # 3.2 由于服务器无法知道此时主板到底处于什么计费方式下,所以只能以经销商上一次设置的 充电模式为准,主板默认出厂 模式为时间模式
- # 3.3 所以服务器结束的时候需要读取当前的充电方式 默认也为时间模式
- # 4. 超辰寄过来打样的主板 是10路,实际有2 路 4 路 以及 10路的,2路4路没有实际测试,但是姚总坚持认为三个主板协议一样。
- import logging
- import random
- import time
- from apilib.utils_datetime import timestamp_to_dt
- from apps.web.common.transaction import UserConsumeSubType
- from apps.web.constant import DeviceCmdCode, MQTT_TIMEOUT, 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
- from apps.web.user.models import ConsumeRecord, MyUser
- logger = logging.getLogger(__name__)
- class ChaoChenBox(SmartBox):
- DEFAULT_CHARGE_TYPE = "time"
- DEFAULT_FULL_STOP = 0
- def _send_data(self, funCode, content, cmd=None, timeout=MQTT_TIMEOUT.NORMAL):
- if cmd is None:
- cmd = DeviceCmdCode.OPERATE_DEV_SYNC
- result = MessageSender.send(
- device=self.device,
- cmd=cmd,
- payload={
- "IMEI": self._device["devNo"],
- "funCode": funCode,
- "data": content
- },
- timeout=timeout
- )
- if result.get("rst") == -1:
- raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请稍候再试'})
- elif result.get("rst") == 1:
- raise ServiceException({'result': 2, 'description': u'充电桩串口故障,请稍后再试'})
- elif result.get("rst") == 0:
- # data = result.get("data", "")
- # if data[10: 12] != "01":
- # raise ServiceException({'result': 2, 'description': u'执行命令失败,请稍后再试'})
- return result
- else:
- raise ServiceException({'result': 2, 'description': u'系统错误'})
- def _start_device(self, portStr, orderNo, money, leftMoney):
- if not leftMoney:
- leftMoney = 0x000000
- orderHex = "{:016X}".format(int(orderNo))
- moneyHex = "{:06X}".format(int(float(money) * 100))
- leftMoneyHex = "{:06X}".format(int(float(leftMoney) * 100))
- result = self._send_data(
- funCode="{:02X}A7".format(int(portStr)),
- content=orderHex + moneyHex + leftMoneyHex
- )
- return result
- def _stop(self, portStr):
- result = self._send_data(
- funCode="{:02X}D6".format(int(portStr)),
- content="01"
- )
- return result
- @property
- def poweroffTime(self):
- """ TODO 设置不起作用"""
- result = self._send_data("01B2", "01")
- return int(result["data"][12: 20], 16)
- @property
- def curElec(self):
- result = self._send_data("01B3", "01")
- return int(result['data'][-4:-2], 16) / 1000.0
- @property
- def standElec(self):
- result = self._send_data("01C3", "01")
- data = result["data"]
- m = int(data[14: 16], 16) / 10.0
- s = int(data[12: 14], 16) / 10.0
- return {
- "maxElec": m,
- "standElec": s
- }
- @property
- def totalCoinConsume(self):
- result = self._send_data("01C1", "01")
- return int(result["data"][12: 18], 16) / 100.0
- @property
- def totalCardConsume(self):
- result = self._send_data("01C2", "01")
- return int(result["data"][12: 18], 16) / 100.0
- @property
- def emptyTime(self):
- result = self._send_data("01E6", "01")
- return int(result["data"][12: 16], 16)
- @property
- def emptyPower(self):
- result = self._send_data("01E4", "01")
- return int(result["data"][12: 16], 16)
- # ------这三个暂时设置为只读属性------------------
- @poweroffTime.setter
- def poweroffTime(self, value):
- self._send_data("01B1", "{:08X}".format(int(value)))
- @emptyPower.setter
- def emptyPower(self, value):
- self._send_data("01E3", "{:04X}".format(int(value)))
- @emptyTime.setter
- def emptyTime(self, value):
- self._send_data("01E5", "{:04X}".format(int(value)))
- @standElec.setter
- def standElec(self, value):
- standElec = value['standElec']
- maxElec = value['maxElec']
- dataHex = "{:0>2X}{:0>2X}".format(int(float(standElec) * 10), int(float(maxElec) * 10))
- self._send_data("01A4", dataHex)
- # ----------浮充功率不需要 主板回复也不对---------
- @property
- def fuChongPower(self):
- # TODO 未按协议格式回执报文
- # result = self._send_data("01D2", "01")
- # data = result["data"]
- # return int(data[12: 16], 16)
- return
- @fuChongPower.setter
- def fuChongPower(self, value):
- # self._send_data("01D1", "{:04X}".format(int(value)))
- pass
- # --------------------------------------------
- def _set_recover(self):
- self._send_data("01F5", "01")
- def _set_clear_line_time(self):
- raise ServiceException({"result": "2", "description": u"暂不支持此项功能"})
- def _set_reboot(self):
- self._send_data("01D7", "01")
- @property
- def chargeType(self):
- """ 该主板没有获取充电模式的 接口"""
- return self.device.get("otherConf", dict()).get("chargeType", self.DEFAULT_CHARGE_TYPE)
- @chargeType.setter
- def chargeType(self, value):
- if value == "time":
- result = self._send_data("01E8", "00")
- else:
- result = self._send_data("01E8", "01")
- resultCode = result['data'][10:12]
- if resultCode == "00":
- raise ServiceException({"result": 2, "description": u"切换模式失败,请等待所有充电订单结束<{}>".format(resultCode)})
- if resultCode == "02":
- raise ServiceException({"result": 2, "description": u"切换模式失败,<{}>".format(resultCode)})
- otherConf = self.device.get("otherConf", dict())
- otherConf['chargeType'] = value
- Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf)
- Device.invalid_device_cache(self.device.devNo)
- @property
- def stepPower(self):
- result = self._send_data("01F2", "01")
- data = result['data'][12: -2]
- step = dict()
- for _index in range(0, 24, 6):
- # step.append(
- # {"power": int(data[_index: _index+4], 16), "discount": int(data[_index+4: _index+6], 16)}
- # )
- step["power{}".format(_index/6 + 1)] = int(data[_index: _index+4], 16)
- step["discount{}".format(_index/6 + 1)] = int(data[_index+4: _index+6], 16)
- return step
- @stepPower.setter
- def stepPower(self, value):
- content = ""
- for _item in value:
- content += "{:04X}{:02X}".format(int(_item["power"]), int(_item["discount"]))
- self._send_data("01F1", content)
- @property
- def timeRule(self):
- result = self._send_data("01B8", "01")
- mobilePrice = int(result["data"][12: 16], 16)
- mobileTime = int(result["data"][16: 20], 16)
- mobileOnsale = int(result["data"][20: 22], 16)
- icPrice = int(result["data"][22: 26], 16)
- icTime = int(result["data"][26: 30], 16)
- coinPrice = int(result["data"][30: 34], 16)
- coinTime = int(result["data"][34: 38], 16)
- return {
- "timePrice": mobilePrice,
- "timeNum": mobileTime,
- "timeOnsale": mobileOnsale,
- "mobilePrice": mobilePrice,
- "mobileTime": mobileTime,
- "mobileOnsale": mobileOnsale,
- "icPrice": icPrice,
- "icTime": icTime,
- "coinPrice": coinPrice,
- "coinTime": coinTime
- }
- @timeRule.setter
- def timeRule(self, value):
- mobilePrice = value["mobilePrice"]
- mobileTime = value["mobileTime"]
- mobileOnsale = value["mobileOnsale"]
- icPrice = value["icPrice"]
- icTime = value["icTime"]
- coinPrice = value['coinPrice']
- coinTime = value['coinTime']
- self._send_data(
- "01B7",
- "{:04X}{:04X}{:02X}{:04X}{:04X}{:04X}{:04X}".format(int(mobilePrice), int(mobileTime), int(mobileOnsale), int(icPrice), int(icTime), int(coinPrice), int(coinTime))
- )
- @property
- def elecRule(self):
- result = self._send_data("01F7", "01")
- elecPrice = int(result['data'][12:16], 16)
- elecNum = int(result['data'][16:20], 16) / 100.0
- elecOnsale = int(result['data'][20:22], 16)
- return {"elecPrice": elecPrice, "elecNum": elecNum, "elecOnsale": elecOnsale}
- @elecRule.setter
- def elecRule(self, value):
- """设置电量 前台过来单位为度 发送主板需要修改为0.1度"""
- elecPrice = value["elecPrice"]
- elecNum = value["elecNum"]
- elecOnsale = value["elecOnsale"]
- self._send_data(
- "01F8",
- "{:04X}{:04X}{:02X}".format(int(elecPrice), int(elecNum)*100, int(elecOnsale))
- )
- def _get_all_status(self):
- result = self._send_data("01ED", "0100")
- portCount = int(result["data"][10: 12], 16)
- portData = result["data"][12: 12+portCount*4]
- portDict = dict()
- for _i in range(portCount):
- _port = str(int(portData[_i*4: _i*4+2], 16))
- _status = portData[_i*4+2: _i*4+4]
- if _status == "01":
- _tStatus = Const.DEV_WORK_STATUS_IDLE
- elif _status == "02":
- _tStatus = Const.DEV_WORK_STATUS_WORKING
- elif _status == "03":
- _tStatus = Const.DEV_WORK_STATUS_FORBIDDEN
- elif _status == "04":
- _tStatus = Const.DEV_WORK_STATUS_FAULT
- else:
- _tStatus = Const.DEV_WORK_STATUS_FAULT
- portDict[_port] = {"status": _tStatus}
- return portDict
- def _get_port_info(self, port):
- # TODO 电流单位未知
- result = self._send_data("{:02X}EF".format(int(port)), "00")
- _status = result["data"][12: 14]
- if _status == "01":
- status = Const.DEV_WORK_STATUS_IDLE
- elif _status == "02":
- status = Const.DEV_WORK_STATUS_WORKING
- elif _status == "03":
- status = Const.DEV_WORK_STATUS_FORBIDDEN
- elif _status == "04":
- status = Const.DEV_WORK_STATUS_FAULT
- else:
- status = Const.DEV_WORK_STATUS_FAULT
- left = int(result["data"][14: 18], 16)
- power = int(result["data"][18: 22], 16) / 10.0
- otherConf = self.device.get("otherConf", dict())
- chargeMode = otherConf.get("chargeType", self.DEFAULT_CHARGE_TYPE)
- if chargeMode == "elec":
- left /= 100.0
- return {
- "status": status,
- "power": power,
- "left{}".format(chargeMode.capitalize()): left
- }
- def _get_port_elec(self):
- """获取端口实时用电量"""
- result = self._send_data("01E9", "01")
- data = result['data'][12: -2]
- for _index in range(0, len(data), 4):
- tUsedElec = int(data[_index: _index+4], 16)
- Device.update_dev_control_cache(self.device.devNo, {str(_index/4+1): {"usedElec": tUsedElec}})
- @staticmethod
- def _parse_event_C9(data):
- """
- :param data:
- :return:
- """
- # TODO zjl 这个订单序号有什么用
- portStr = str(int(data[2: 4], 16))
- orderNo = str(int(data[10:26], 16))
- serialNo = str(int(data[26:30], 16))
- usedCoins = str(int(data[30:36], 16) / 100.0)
- usedTime = str(int(data[36: 40], 16))
- leftTime = str(int(data[40:48], 16))
- return {
- 'portStr': portStr,
- 'orderNo': orderNo,
- 'serialNo': serialNo,
- 'usedCoins': usedCoins,
- 'usedTime': usedTime,
- 'leftTime': leftTime
- }
- @staticmethod
- def _parse_event_C8(data):
- # TODO zjl 目前没有刷卡消费的订单 事分缓急 先不做
- return {}
- @staticmethod
- def _parse_event_C4(data):
- # TODO 故障码 55-255
- portStr = str(int(data[2: 4], 16))
- status = int(data[10:12], 16)
- return {
- "status": status,
- "portStr": portStr
- }
- @staticmethod
- def _parse_event_B5(data):
- # TODO 测试 感觉单位像是分钟而不是秒
- port = int(data[2:4], 16)
- serialNo = int(data[10:14], 16)
- usedCoins = str(int(data[14:22], 16) / 100.0)
- usedTime = str(int(data[22:30], 16))
- finishedStamp = str(int(data[30:38], 16))
- return {
- 'portStr': port,
- 'serialNo': serialNo,
- 'usedCoins': usedCoins,
- 'usedTime': usedTime,
- 'finishedStamp': finishedStamp
- }
- @staticmethod
- def _parse_event_B6(data):
- port = int(data[2:4], 16)
- serialNo = int(data[10:14], 16)
- cardNo = data[14:24]
- usedCoins = str(int(data[24:32], 16) / 100.0)
- balance = str(int(data[32:40], 16) / 100.0)
- usedTime = str(int(data[40:48], 16))
- finishedStamp = str(int(data[48:56], 16))
- return {
- 'portStr': port,
- 'serialNo': serialNo,
- 'cardNo': cardNo,
- 'usedCoins': usedCoins,
- 'balance': balance,
- 'usedTime': usedTime,
- 'finishedStamp': finishedStamp
- }
- @staticmethod
- def _parse_event_EA(data):
- portStr = str(int(data[2: 4], 16))
- reasonCode = int(data[10:12], 16)
- leftElec = int(data[12:16], 16) / 100.0
- return {
- "reasonCode": reasonCode,
- "leftElec": leftElec,
- "portStr": portStr
- }
- @staticmethod
- def _parse_event_F0(data):
- totalElec = int(data[10:14], 16) / 1000.0
- return {"totalElec": totalElec}
- @staticmethod
- def _parse_event_F9(data):
- port = int(data[2:4], 16)
- cardNo = int(data[10:18], 16)
- consumeMoney = round(int(data[19:22], 16) * 0.01, 2)
- return {"port": port, "cardNo": cardNo, "consumeMoney": consumeMoney}
- @staticmethod
- def _parse_event_FA(data):
- port = int(data[2:4], 16)
- cardNo = int(data[10:18], 16)
- refundMoney = round(int(data[19:22], 16) * 0.01, 2)
- return {"port": port, "cardNo": cardNo, "refundMoney": refundMoney}
-
- def test(self, coins):
- orderNo = random.randint(10000, 99999)
- return self._start_device("1", orderNo, coins, coins)
- def get_device_function_by_key(self, keyName):
- if "poweroffTime" in keyName:
- return {"poweroffTime": self.poweroffTime}
- if "curElec" in keyName:
- return {"curElec": self.curElec}
- if "totalCoinConsume" in keyName:
- return {"totalCoinConsume": self.totalCoinConsume}
- if "totalCardConsume" in keyName:
- return {"totalCardConsume": self.totalCardConsume}
- if "emptyTime" in keyName:
- return {"emptyTime": self.emptyTime}
- if "emptyPower" in keyName:
- return {"emptyPower": self.emptyPower}
- if "consumeMode" in keyName:
- return {"consumeMode": self.chargeType}
- if "mobilePrice" in keyName:
- return self.timeRule
- if "elecPrice" in keyName:
- return self.elecRule
- if "power1" in keyName:
- return self.stepPower
- if "standElec" in keyName:
- return self.standElec
- return {}
- def set_device_function(self, request, lastSetConf):
- if "recover" in request.POST:
- self._set_recover()
- if "clearLineTime" in request.POST:
- self._set_clear_line_time()
- if "reboot" in request.POST:
- self._set_reboot()
- def set_device_function_param(self, request, lastSetConf):
- if "consumeMode" in request.POST:
- self.chargeType = request.POST.get("consumeMode")
- if "mobilePrice" in request.POST:
- mobilePrice = request.POST.get("mobilePrice")
- mobileTime = request.POST.get("mobileTime")
- mobileOnsale = request.POST.get("mobileOnsale")
- icPrice = request.POST.get("icPrice")
- icTime = request.POST.get("icTime")
- coinPrice = request.POST.get("coinPrice")
- coinTime = request.POST.get("coinTime")
- self.timeRule = {"mobilePrice": mobilePrice, "mobileTime": mobileTime, "mobileOnsale": mobileOnsale, "icPrice": icPrice, "icTime": icTime, "coinPrice": coinPrice, "coinTime": coinTime}
- if "elecPrice" in request.POST:
- elecPrice = request.POST.get("elecPrice")
- elecNum = request.POST.get("elecNum")
- elecOnsale = request.POST.get("elecOnsale")
- self.elecRule = {"elecPrice": elecPrice, "elecNum": elecNum, "elecOnsale": elecOnsale}
- if "power1" in request.POST:
- step = list()
- for _index in range(1, 5):
- step.append({"power": request.POST['power{}'.format(_index)], "discount": request.POST["discount{}".format(_index)]})
- self.stepPower = step
-
- if "standElec" in request.POST or "maxElec" in request.POST:
- standElec = request.POST.get("standElec")
- maxElec = request.POST.get("maxElec")
- self.standElec = {"standElec": standElec, "maxElec": maxElec}
-
- if "poweroffTime" in request.POST:
- poweroffTime = request.POST.get("poweroffTime")
- self.poweroffTime = poweroffTime
-
- if "emptyTime" in request.POST:
- emptyTime = request.POST.get("emptyTime")
- self.emptyTime = emptyTime
-
- if "emptyPower" in request.POST:
- emptyPower = request.POST.get("emptyPower")
- self.emptyPower = emptyPower
-
- def get_port_status_from_dev(self):
- result = self._get_all_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 strPort in ctrInfo:
- 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):
- """强制改变状态 这个板子的路数总是改变 每次都直接重新获取吧"""
- self.get_port_status_from_dev()
- ctrInfo = Device.get_dev_control_cache(self._device['devNo'])
- if 'allPorts' not in ctrInfo:
- 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 get_port_info(self, line):
- result = self._get_port_info(line)
- result.update({"port": line})
- return result
- def start_device(self, package, openId, attachParas):
- chargeIndex = attachParas.get("chargeIndex")
- if not chargeIndex:
- raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'})
- orderNo = attachParas.get("orderNo") or ConsumeRecord.make_no(self.device.logicalCode, UserConsumeSubType.NETPAY)
- coins = package.get("coins")
- balance = None
- if openId:
- user = MyUser.objects.get(groupId=self._device['groupId'], openId=openId)
- balance = user.balance
- start_timestamp = int(time.time())
- otherConf = self.device.get("otherConf", dict())
- chargeMode = otherConf.get("chargeType", self.DEFAULT_CHARGE_TYPE)
- portCache = {
- 'startTime': timestamp_to_dt(start_timestamp).strftime('%Y-%m-%d %H:%M:%S'),
- 'status': Const.DEV_WORK_STATUS_WORKING,
- 'coins': coins,
- 'isStart': True,
- 'openId': openId,
- 'refunded': False,
- 'vCardId': self._vcard_id,
- "chargeType": chargeMode,
- }
- if chargeMode == self.DEFAULT_CHARGE_TYPE:
- rule = self.timeRule
- price = rule["timePrice"]
- needTime = float(coins) / float(price / 100.0) * rule["timeNum"]
- portCache.update({"needTime": needTime})
- else:
- rule = self.elecRule
- price = rule["elecPrice"]
- needElec = float(coins) / float(price / 100.0) * rule["elecNum"] / 10.0
- portCache.update({"needElec": needElec})
- result = self._start_device(portStr=chargeIndex, orderNo=orderNo, money=coins, leftMoney=balance)
- Device.update_dev_control_cache(self.device.devNo, {chargeIndex: portCache})
- result['consumeOrderNo'] = orderNo
- result["finishedTime"] = start_timestamp + 24 * 60 * 60
- return result
- def stop(self, port=None):
- if not port:
- return
- self._stop(str(port))
- @property
- def isHaveStopEvent(self):
- return True
- def active_deactive_port(self, port, active):
- if not active:
- return self._stop(str(port))
- def analyze_event_data(self, data):
- cmdCode = data[6:8]
- func = getattr(self, "_parse_event_{}".format(cmdCode.upper()), None)
- if func:
- result = func(data)
- result["cmdCode"] = cmdCode
- return result
- else:
- logger.info("no <{}> parser".format(cmdCode))
- def dealer_get_port_status(self):
- self._get_port_elec()
- data = self.get_port_status()
- return data
|