123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import binascii
- import datetime
- import logging
- import random
- import time
- from decimal import Decimal
- 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.dealer.models import Dealer
- from apps.web.device.models import Device
- from apps.web.core.device_define.kunyuanCar import DeviceParams, DEFAULT_VERSION, ORDER_VERSION
- from apps.web.user.models import ConsumeRecord
- logger = logging.getLogger(__name__)
- class KunYuanCar(SmartBox):
- """ 坤元模拟的昌原协议 """
- @staticmethod
- def _trans_card(cardNo):
- """
- 暂时不知道 坤元是怎么印制卡片数字的 涉及卡号转换的地方统一处理
- """
- if cardNo != DeviceParams.VOID_CARD_NO:
- return str(int(cardNo, 16))
- return ""
- def _send_data(self, funCode, data, cmd=None, timeout=MQTT_TIMEOUT.NORMAL):
- if not cmd:
- cmd = DeviceCmdCode.OPERATE_DEV_SYNC
- 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 _get_device_settings(self):
- """
- 读取主板设置参数
- 新版本的读取指令 增加版本号以及端口类型
- """
- result = self._send_data("A0", "00")
- data = result["data"]
- isFree = data[8: 10]
- free = 0 if isFree == "00" else 1
- electricPrice = int(data[10: 12], 16) / 100.0
- maxConsume = int(data[12: 16], 16) / 100.0
- maxChargeTime = int(data[16: 20], 16)
- volume = int(data[20: 22], 16)
- if data[6: 8] == "0B":
- version = str(int(data[22: 26], 16) / 100.0)
- port1 = "https://resource.washpayer.com/ky_port2.png" if data[26: 28] == "71" else "https://resource.washpayer.com/ky_port1.png"
- port2 = "https://resource.washpayer.com/ky_port2.png" if data[28: 30] == "71" else "https://resource.washpayer.com/ky_port1.png"
- else:
- version = str(DEFAULT_VERSION)
- port1 = ""
- port2 = ""
- return {
- "is_free": free,
- "electricPrice": electricPrice,
- "maxConsume": maxConsume,
- "maxChargeTime": maxChargeTime,
- "volume": volume,
- "version": version,
- "1": port1,
- "2": port2
- }
- def _set_device_settings(self, conf):
- """
- 设置主板参数
- """
- isFree = conf.pop("is_free", None)
- electricPrice = conf.pop("electricPrice", None)
- maxConsume = conf.pop("maxConsume", None)
- maxChargeTime = conf.pop("maxChargeTime", None)
- volume = conf.pop("volume", None)
- data = ""
- data += "AA" if int(isFree) == 1 else "00"
- data += fill_2_hexByte(hex(int(Decimal(electricPrice) * 100)), 2)
- data += fill_2_hexByte(hex(int(Decimal(maxConsume) * 100)), 4)
- data += fill_2_hexByte(hex(int(Decimal(maxChargeTime))), 4)
- data += fill_2_hexByte(hex(int(Decimal(volume))), 2)
- self._send_data("A1", data)
- def _get_consume_count(self, port=0):
- """
- 获取主板的消费统计 和端口有关
- :return:
- """
- result = self._send_data("A2", "{:02X}".format(port))
- data = result["data"]
- totalConsume = data[10: 16] # 总消费
- totalElec = data[16: 22] # 总使用电量
- cardConsume = data[22: 28] # 卡消费
- return {
- "port": str(port),
- "totalConsume": int(totalConsume, 16) / 100.0,
- "totalElec": int(totalElec, 16) / 100.0,
- "cardConsume": int(cardConsume, 16) / 100.0
- }
- def _clean_consume_count(self, port, consume=False, elec=False, cardConsume=False):
- """
- 清除充电桩上累计信息 和端口有关
- :param consume:
- :param elec:
- :param cardConsume:
- :return:
- """
- if consume and elec and cardConsume:
- sendData = "E3"
- elif elec:
- sendData = "E2"
- elif cardConsume:
- sendData = "E4"
- elif consume:
- sendData = "E1"
- else:
- return
- sendData = "{}{}".format("{:02X}".format(port), sendData)
- self._send_data("A3", sendData)
- def _get_all_port_status(self):
- """ 获取全部端口状态 """
- result = self._send_data("A5", "00")
- data = result["data"][8:-8]
- statusMap = dict()
- for _port in range(1, 1 + len(data) / 2):
- status = DeviceParams.STATUS_MAP.get(data[:2], Const.DEV_WORK_STATUS_IDLE)
- statusMap[str(_port)] = status
- data = data[2:]
- return statusMap
- def _get_device_status(self, port):
- """
- 获取设备端口的运行状态
- :return:
- """
- result = self._send_data("A6", "{:02X}".format(port))
- data = result["data"]
- status = data[10:12]
- cardNo = data[32:40]
- # 如果设备状态
- result = {
- "port": str(port),
- "status": DeviceParams.STATUS_MAP.get(status, Const.DEV_WORK_STATUS_IDLE),
- "voltage": int(data[12:16], 16) / 1.0,
- "power": int(data[16:20], 16) / 1.0,
- "usedElec": int(data[20:24], 16) / 100.0,
- "usedTime": int(data[24:28], 16),
- "leftMoney": int(data[28:32], 16) / 100.0,
- "cardNo": KunYuanCar._trans_card(cardNo)
- }
- return result
- def _start(self, port, money):
- """
- 微信支付命令
- :param money:
- :return:
- """
- sendData = "{:02X}{:04X}0000".format(port, int(money * 100))
- result = self._send_data("A7", sendData, timeout=MQTT_TIMEOUT.START_DEVICE)
- data = result.get("data", dict())
- if data[10:14] != "4F4B":
- raise ServiceException({'result': 2, 'description': u'设备启动失败,请联系设备老板'})
- return result
- def _start_by_sequanceNo(self, port, money, sequanceNo):
- sendData = "{:02X}{:04X}{}".format(port, int(money * 100), sequanceNo)
- result = self._send_data("A7", sendData, timeout=MQTT_TIMEOUT.START_DEVICE)
- data = result.get("data", dict())
- if data[10:14] != "4F4B":
- raise ServiceException({'result': 2, 'description': u'设备启动失败,请联系设备老板'})
- return result
- def _set_device_disable(self, disable):
- """
- 设置 设备的可用
- :param disable:
- :return:
- """
- status = "E9" if disable else "E8"
- result = self._send_data("A9", status)
- data = result["data"]
- if data[8: 12] != "4F4B":
- raise ServiceException({'result': 2, 'description': u'设置失败,请重试'})
- def _stop(self, port):
- """ 停止 """
- self._send_data("AF", "{:02X}".format(port))
- def _restart_device(self):
- """ 重启 """
- self._send_data("B0", "00")
- def _response_ack(self, ackMessage):
- """
- 回复模块 使得模块停止ack 与主板无关
- """
- MessageSender.send(
- self.device,
- DeviceCmdCode.OPERATE_DEV_NO_RESPONSE,
- {
- "IMEI": self._device.devNo,
- "funCode": "FA",
- "data": "",
- "ack": ackMessage
- }
- )
- def _response_card(self, card):
- """ 回复卡的余额 状态 """
- sendData = "{:06X}".format(int(card.balance * 100))
- if card.frozen:
- sendData += "AA"
- else:
- sendData += "00"
- self._send_data("C1", sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
- @staticmethod
- def _parse_event_AE(data):
- """
- 枪把状态改变时候上报
- """
- port = int(data[8:10], 16)
- status = DeviceParams.CONNECT_STATUS_MAP.get(data[10:12], Const.DEV_WORK_STATUS_IDLE)
- return {
- "port": str(port),
- "status": status
- }
- @staticmethod
- def _parse_event_B1(data):
- """
- 充电启动
- """
- try:
- return KunYuanCar._parse_event_B1_by_sequanceNo(data)
- except ValueError:
- port = int(data[8: 10], 16)
- cardNo = data[10: 18]
- money = int(data[18: 22], 16) / 100.0
- return {
- "port": str(port),
- "cardNo": KunYuanCar._trans_card(cardNo),
- "money": money
- }
- @staticmethod
- def _parse_event_B1_by_sequanceNo(data):
- port = int(data[8: 10], 16)
- cardNo = data[10: 18]
- money = int(data[18: 22], 16) / 100.0
- sequanceNo = data[22: 36]
- return {
- "port": str(port),
- "cardNo": KunYuanCar._trans_card(cardNo),
- "money": money,
- "sequanceNo": sequanceNo
- }
- @staticmethod
- def _parse_event_A8(data):
- """
- 充电结束的时候上报
- """
- try:
- return KunYuanCar._parse_event_A8_by_sequanceNo(data)
- except ValueError:
- port = int(data[8: 10], 16)
- reasonCode = data[10:12]
- reason = DeviceParams.FINISH_REASON_MAP.get(reasonCode, u"未知结束方式")
- cardNo = data[12:20]
- usedElec = int(data[20:24], 16) / 100.0
- usedTime = int(data[24:28], 16)
- leftMoney = int(data[28:32], 16) / 100.0
- return {
- "port": str(port),
- "reason": reason,
- "cardNo": KunYuanCar._trans_card(cardNo),
- "usedElec": usedElec,
- "usedTime": usedTime,
- "leftMoney": leftMoney,
- }
- @staticmethod
- def _parse_event_A8_by_sequanceNo(data):
- port = int(data[8: 10], 16)
- reasonCode = data[10:12]
- reason = DeviceParams.FINISH_REASON_MAP.get(reasonCode, u"未知结束方式")
- cardNo = data[12:20]
- usedElec = int(data[20:24], 16) / 100.0
- usedTime = int(data[24:28], 16)
- leftMoney = int(data[28:32], 16) / 100.0
- sequanceNo = data[42: 56]
- return {
- "port": str(port),
- "reason": reason,
- "cardNo": KunYuanCar._trans_card(cardNo),
- "usedElec": usedElec,
- "usedTime": usedTime,
- "leftMoney": leftMoney,
- "sequanceNo": sequanceNo
- }
- @staticmethod
- def _parse_event_B2(data):
- """ 解析状态上报 """
- try:
- return KunYuanCar._parse_event_B2_by_sequanceNo(data)
- except ValueError:
- port = int(data[8: 10], 16)
- leftBalance = int(data[10:14], 16) / 100.0
- usedElec = int(data[14:18], 16) / 100.0
- temperature = int(data[20:22], 16)
- power = int(data[22:26], 16)
- voltage = int(data[30:34], 16)
- temperature = temperature if data[18:20] == "00" else -temperature
- return {
- "port": str(port),
- "leftBalance": leftBalance,
- "usedElec": usedElec,
- "temperature": u"{}度 更新于{}".format(temperature, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
- "power": power,
- "voltage": voltage,
- }
- @staticmethod
- def _parse_event_B2_by_sequanceNo(data):
- port = int(data[8: 10], 16)
- leftBalance = int(data[10:14], 16) / 100.0
- usedElec = int(data[14:18], 16) / 100.0
- temperature = int(data[20:22], 16)
- power = int(data[22:26], 16)
- voltage = int(data[30:34], 16)
- temperature = temperature if data[18:20] == "00" else -temperature
- sequanceNo = data[20: 34]
- return {
- "port": str(port),
- "leftBalance": leftBalance,
- "usedElec": usedElec,
- "temperature": u"{}度 更新于{}".format(temperature, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
- "power": power,
- "voltage": voltage,
- "sequanceNo": sequanceNo
- }
- @staticmethod
- def _parse_event_C1(data):
- """
- 在线卡查询卡费
- """
- cardNo = data[8: 16]
- return {
- "cardNo": KunYuanCar._trans_card(cardNo),
- }
- @staticmethod
- def _parse_event_C2(data):
- """
- 解析 同步参数指令
- """
- return {}
- @staticmethod
- def _parse_event_C3(data):
- """
- 解析 同步参数指令
- """
- port = int(data[8: 10], 16)
- faultCode = data[10: 14]
- return {
- "port": port,
- }
- @staticmethod
- def _parse_event_A0(data):
- if data[6: 8] == "0B":
- version = str(int(data[22: 26], 16) / 100.0)
- else:
- version = str(DEFAULT_VERSION)
- return {
- "version": version
- }
- @property
- def isHaveStopEvent(self):
- """ 是否有结束事件 """
- return True
- def test(self, coins):
- return self._start(1, coins)
- def get_dev_setting(self):
- """ 获取参数 """
- settings = self._get_device_settings()
- otherConf = self.device.get("otherConf", dict())
- disableDevice = otherConf.get("disableDevice", DeviceParams.DEFAULT_DISABLE_DEVICE)
- settings.update({"disableButton": disableDevice})
- Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf)
- Device.invalid_device_cache(self.device.devNo)
- consume = self._get_consume_count()
- settings.update(consume)
- devCache = Device.get_dev_control_cache(self.device.devNo)
- temperature = devCache.get("temperature", "暂无数据")
- settings.update({"temperature": temperature})
- return settings
- def set_device_function_param(self, request, lastSetConf):
- """ 设置设备参数 """
- disable = request.POST.get("disableButton")
- if disable is not None:
- dealer = Dealer.objects.filter(id=self.device.ownerId).first()
- if "dealerDisableDevice" not in dealer.features:
- raise ServiceException({"result": 2, "description": "抱歉,您无此操作权限,请联系厂家获取权限"})
- return self.set_dev_disable(bool(int(disable)))
- newDict = {
- "is_free": request.POST.get("is_free", lastSetConf["is_free"]),
- "electricPrice": request.POST.get("electricPrice", lastSetConf["electricPrice"]),
- "maxConsume": request.POST.get("maxConsume", lastSetConf["maxConsume"]),
- "maxChargeTime": request.POST.get("maxChargeTime", lastSetConf["maxChargeTime"]),
- "volume": request.POST.get("volume", lastSetConf["volume"])
- }
- self._set_device_settings(newDict)
- def set_dev_disable(self, disable):
- """ 禁用设备 """
- self._set_device_disable(disable)
- def set_device_function(self, request, lastSetConf):
- """ 设置设备开关 """
- clearTotalConsume = request.POST.get("clearTotalConsume", False)
- clearTotalElec = request.POST.get("clearTotalElec", False)
- reboot = request.POST.get("reboot", False)
- if reboot:
- self._restart_device()
- # 清除设备计费信息
- if any([clearTotalConsume, clearTotalElec]):
- self._clean_consume_count(clearTotalConsume, clearTotalElec)
- def stop(self, port=None):
- """ 停止设备 """
- return self._stop(int(port))
- def get_port_status_from_dev(self):
- """从设备端更新端口的状态"""
- result = self._get_all_port_status()
- portDict = dict()
- for _port, _status in result.items():
- portDict[_port] = {"status": _status}
- allPorts, usedPorts, usePorts = self.get_port_static_info(portDict)
- portDict.update({"allPorts": allPorts, "usedPorts": usedPorts, "usePorts": usePorts})
- Device.update_dev_control_cache(self.device.devNo, portDict)
- return portDict
- def get_port_status(self, force=False):
- """
- 李成坤强制要求
- """
- # if force:
- portDict = self.get_port_status_from_dev()
- devSet = self._get_device_settings()
- statusMap = dict()
- for _port, _item in portDict.items():
- if isinstance(_port, (unicode, str)) and _port.isdigit():
- statusMap[_port] = {'status': _item.get("status", 0), "img": devSet.get(_port, "")}
- return statusMap
- def check_dev_status(self, attachParas=None):
- """ 检查设备状态 """
- chargeIndex = attachParas.get("chargeIndex")
- result = self.get_port_status(force=True)
- if result[str(chargeIndex)].get("status", 0) != Const.DEV_WORK_STATUS_CONNECTED:
- raise ServiceException({"result": 2, "description": u"请先连接设备与充电枪"})
- def analyze_event_data(self, data):
- """ 解析数据 """
- cmdCode = data[4:6]
- funcName = "_parse_event_{}".format(cmdCode)
- func = getattr(self.__class__, funcName, None)
- if func and callable(func):
- eventData = func(data) or dict()
- eventData.update({"cmdCode": cmdCode})
- return eventData
- def start_device(self, package, openId, attachParas):
- """ 启动设备 """
- chargeIndex = attachParas["chargeIndex"]
- portStatus = Device.get_port_control_cache(self.device.devNo, port=str(chargeIndex))
- if portStatus.get("status") != Const.DEV_WORK_STATUS_CONNECTED:
- self.get_port_status_from_dev()
- portStatus = Device.get_port_control_cache(self.device.devNo, port=str(chargeIndex))
- if portStatus.get("status") != Const.DEV_WORK_STATUS_CONNECTED:
- raise ServiceException({'result': 2, 'description': u'为了充电安全,请先连接设备与充电枪'})
- coins = package.get("coins")
- price = package.get("price")
- orderNo = attachParas.get("orderNo")
- version = self._get_device_settings().get("version", str(DEFAULT_VERSION))
- portCache = {
- "openId": openId,
- "isStart": True,
- "coins": coins,
- "price": price,
- "status": Const.DEV_WORK_STATUS_WORKING,
- "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
- "orderNo": orderNo,
- "version": version
- }
- if version > str(ORDER_VERSION):
- # sequanceNo 表示为时间戳 4个字节 + 端⼝号1个字节 + 随机数 2个字节 共7个字节16进制
- sequanceNo = "{:08X}{:02X}{:04X}".format(int(time.time()), int(chargeIndex), random.randint(1000, 9999))
- order = ConsumeRecord.objects.get(orderNo=orderNo)
- order.update(sequanceNo=sequanceNo)
- result = self._start_by_sequanceNo(int(chargeIndex), price, sequanceNo)
- else:
- result = self._start(int(chargeIndex), price)
- Device.update_dev_control_cache(self.device.devNo, {chargeIndex: portCache})
- result["finishedTime"] = int(time.time()) + 24 * 60 * 60
- return result
- def dealer_get_port_status(self):
- """ 经销商获取端口状态 """
- return self.get_port_status()
- def get_port_info(self, port):
- """ 获取端口信息 """
- result = self._get_device_status(int(port))
- Device.update_dev_control_cache(self.device.devNo, result)
- devCache = Device.get_dev_control_cache(self.device.devNo) or dict()
- return devCache
- def active_deactive_port(self, port, active):
- """ 停用激活端口 """
- if not active:
- self._stop(port)
- else:
- super(KunYuanCar, self).active_deactive_port(port, active)
|