1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087 |
- # coding=utf-8
- """
- 清远粤万通充电桩 2020-09-03修改
- 计费模式分为 按功率计费(服务器不断的算钱有没有用完, 服务器下发停止)
- 按时间计费(一次性下发时间,不需要服务器停止)
- 按电量计费(用到最后收集电量 进行判定)
- chargeType
- 收费模式分为 预付费方式(服务器先收钱, 收完了之后在启动设备, 如果存在没有使用完的钱 需要退还到用户的账户)
- 后付费方式(先不收钱, 等用户使用完了之后再计算总的钱,然后结束这笔订单,将所需的费用写入, 同时执行订单支付,如账户余额不足支付, 标记等待下次支付)
- 套餐模式分为 正常套餐(标记好多少钱,最多就用这么多)
- 充满自停套餐(不知道多少钱,反正一直充,充满为止,一般的处理方式是下发一个极大值)
- fill_2_hexByte 函数统一修改为 int_to_hex
- 在发送断电指令之后,主板不会新上报端口信息,而是直接结束
- """
- import binascii
- import datetime
- import logging
- import simplejson as json
- from pandas import Interval
- from apilib.monetary import RMB
- from apps.common.utils import int_to_hex
- from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT
- from apps.web.core.adapter.base import SmartBox, reverse_hex
- from apps.web.core.exceptions import ServiceException
- from apps.web.core.networking import MessageSender
- from apps.web.device.models import Device, DevicePortReport
- from apps.web.user.models import ServiceProgress
- from apps.web.utils import concat_user_login_entry_url
- logger = logging.getLogger(__name__)
- class YueWanTong(SmartBox):
- PORT_STATUS_MAP = {
- "0": Const.DEV_WORK_STATUS_IDLE,
- "1": Const.DEV_WORK_STATUS_WORKING,
- "2": Const.DEV_WORK_STATUS_APPOINTMENT,
- "3": Const.DEV_WORK_STATUS_FAULT,
- "4": Const.DEV_WORK_STATUS_FAULT,
- "5": Const.DEV_WORK_STATUS_FAULT,
- "6": Const.DEV_WORK_STATUS_FAULT
- }
- DEFAULT_MIN_CHARGE_TIME = 5
- DEFAULT_PORT_NUM = 12
- DEFAULT_MIN_POWER = 20
- DEFAULT_MAX_POWER = 500
- DEFAULT_FLOAT_TIME = 2
- DEFAULT_IC_CODE = "0001"
- DEFAULT_MIN_CONSUME = 0
- DEFAULT_POWER_PACKAGE = [
- {
- "lowLimit": 0,
- "upLimit": 250,
- "price": 1,
- },
- {
- "lowLimit": 250,
- "upLimit": 360,
- "price": 1,
- },
- {
- "lowLimit": 360,
- "upLimit": 500,
- "price": 1,
- }
- ]
- DEFAULT_ELEC_PRICE = 1
- DEFAULT_PAY_AFTER_USE = False
- DEFAULT_CHARGE_TYPE = "time" # 默认的计费模式 是时间计费 (功率不分档)
- # 服务费电费
- DEFAULT_ELEC_CHARGE = 0.5
- DEFAULT_SERVICE_CHARGE = 0.5
- @staticmethod
- def _suit_power_package(powerPackage):
- """
- 对于功率套餐的适配
- 旧版本是 100-200w x 元 y 分钟
- 新版本是 100-200w x 元 1 小时
- :param powerPackage:
- :return:
- """
- newPowerPackage = list()
- for _item in powerPackage:
- # 旧版本的
- if "time" not in _item:
- newPowerPackage.append(_item)
- else:
- # 做一个数据的适配
- price = float(_item["price"]) / (int(_item["time"]) / 60.0)
- newPowerPackage.append({
- "upLimit": _item["upLimit"], "lowLimit": _item["lowLimit"], "price": str(RMB(price))
- })
- return newPowerPackage
- @staticmethod
- def _to_ascii(s):
- """
- 将 字符串 转换为 16进制ascii 表达形式
- :param s: 原始字符串
- :return:
- """
- return binascii.hexlify(s)
- @staticmethod
- def _to_str(h):
- """
- 将 16进制ascii 转换成 字符串
- :param h:
- :return:
- """
- return binascii.unhexlify(h)
- @staticmethod
- def _transform_time(needTime, unit):
- """
- 根据单位 将套餐时间转换成分钟
- :param needTime: int
- :param unit: unicode
- :return:
- """
- if unit == u"小时":
- needTime = needTime * 60
- elif unit == u"分钟":
- needTime = needTime
- elif unit == u"秒":
- needTime = needTime // 60
- elif unit == u"次":
- needTime = None
- else:
- raise ServiceException({'result': 2, 'description': u'套餐错误'})
- return needTime
- def _send_data(self, funCode, sendData, cmd=None, timeout=MQTT_TIMEOUT.NORMAL, orderNo=None):
- """
- 发送报文封装
- :param funCode:
- :param sendData:
- :param cmd:
- :param timeout:
- :param orderNo:
- :return:
- """
- 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": sendData
- }, timeout = timeout)
- if result.has_key("rst"):
- if result["rst"] == -1:
- raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请稍候再试'})
- elif result["rst"] == 1:
- raise ServiceException({'result': 2, 'description': u'充电桩主板连接故障'})
- elif result["rst"] == 0:
- return result
- else:
- raise ServiceException({'result': 2, 'description': u'系统错误'})
- else:
- raise ServiceException({'result': 2, 'description': u'系统错误'})
- def _start_device(self, orderNo, port, chargeTime=None):
- """
- 开始充电
- :param orderNo: 下发的订单编号
- :param port: 开启的端口号
- :param chargeTime: 充电的最大时间,如果是充满自停,则设置为最大时间
- :return:
- """
- if chargeTime is None:
- chargeTime = 0xFFFF
- if orderNo is None:
- orderNo = 0
- orderNoHex = int_to_hex(int(orderNo), 14, reverse=True)
- portHex = int_to_hex(int(port), 2, reverse=True)
- chargeTimeHex = int_to_hex(int(chargeTime), 4, reverse=True)
- sendData = "{}{}{}010000".format(orderNoHex, portHex, chargeTimeHex)
- result = self._send_data("B2", sendData, timeout=MQTT_TIMEOUT.START_DEVICE, orderNo=orderNo)
- return result
- def _stop_device(self, orderNo, port):
- """
- 停止充电
- :param orderNo: 提供开始充电的时候的订单号
- :param port: 端口号
- :return:
- """
- orderNoHex = int_to_hex(int(orderNo), 14, reverse=True)
- portHex = int_to_hex(int(port), 2, reverse=True)
- sendData = "{}{}0000120000".format(orderNoHex, portHex)
- result = self._send_data("B2", sendData, timeout=MQTT_TIMEOUT.START_DEVICE, orderNo=orderNo)
- return result
- @staticmethod
- def _parse_device_stop(data):
- """
- 主动下发停止指令设备的回复 与结束上报不一样
- :param data:
- :return:
- """
- statusMap = {
- "00": Const.DEV_WORK_STATUS_IDLE,
- "01": Const.DEV_WORK_STATUS_WORKING,
- "02": Const.DEV_WORK_STATUS_APPOINTMENT,
- "03": Const.DEV_WORK_STATUS_FAULT, # 故障
- "04": Const.DEV_WORK_STATUS_FAULT, # 烟感
- "05": Const.DEV_WORK_STATUS_FAULT, # 雨感
- "06": Const.DEV_WORK_STATUS_FAULT # 设备锁定
- }
- orderHexNo = reverse_hex(data[8:22])
- portHex = data[22:24]
- voltage = int(reverse_hex(data[24:28]), 16)
- power = int(reverse_hex(data[32:36]), 16)
- elec = int(reverse_hex(data[36:44]), 16) / 1000.0
- chargeTime = int(reverse_hex(data[44:48]), 16)
- status = statusMap.get(data[52: 54], Const.DEV_WORK_STATUS_IDLE)
- return {
- "orderNoHex": orderHexNo,
- "portHex": portHex,
- "orderNo": int(orderHexNo, 16),
- "portStr": str(int(portHex, 16)),
- "voltage": voltage,
- "power": power,
- "elec": elec,
- "chargeTime": chargeTime,
- "status": status
- }
- def _read_port_status(self):
- """
- 从设备端读取端口状态 仅仅只包含状态量
- :return:
- """
- result = self._send_data("B3", sendData="00")
- data = result.get("data", "")[8: -4]
- lockPorts = self.device.otherConf.get("lockPorts") or list()
- portInfo = dict()
- while data:
- tempPort = str(int(data[0: 2], 16))
- tempStatus = self.PORT_STATUS_MAP.get(data[34: 36], Const.DEV_WORK_STATUS_IDLE) if tempPort not in lockPorts else Const.DEV_WORK_STATUS_FORBIDDEN
- portInfo.update({tempPort: tempStatus})
- data = data[4: ]
- return portInfo
- def _read_port_charge_status(self):
- """
- 从设备读取设备端口的状态 详细数据,包含电流 功率 电压 电量 时间 等等
- :return:
- """
- result = self._send_data("B4", sendData="00")
- data = result.get("data", "")[8: -4]
- lockPorts = self.device.otherConf.get("lockPorts") or list()
- portInfo = dict()
- while data:
- tempPort = str(int(data[0: 2], 16))
- tempVoltage = int(reverse_hex(data[2: 6]), 16)
- tempPower = int(reverse_hex(data[10: 14]), 16)
- tempElec = int(reverse_hex(data[14: 22]), 16) / 1000.0
- tempTime = int(reverse_hex(data[22: 26]), 16)
- tempTemper = int(reverse_hex(data[30: 34]), 16)
- tempStatus = self.PORT_STATUS_MAP.get(data[34: 36], Const.DEV_WORK_STATUS_IDLE) if tempPort not in lockPorts else Const.DEV_WORK_STATUS_FORBIDDEN
- portInfo.update({
- tempPort: {
- "voltage": tempVoltage,
- "elec": tempElec,
- "power": tempPower,
- "chargeTime": tempTime,
- "temperature": tempTemper,
- "status": tempStatus
- }
- })
- data = data[36: ]
- return portInfo
- def _reboot_device(self):
- """
- 重启设备
- :return:
- """
- result = self._send_data("B5", sendData="00")
- status = result["data"][8: 10]
- if status != "01":
- raise ServiceException({'result': 2, 'description': u'设备重启失败,请重新操作试试'})
- def _lock_unlock_device(self, lock):
- """
- 锁定/解锁设备
- :param lock: True / False
- :return:
- """
- # 设备锁定发送 00 / 解锁发送 01
- sendData = int_to_hex(int(lock), 2, reverse=True)
- message = "设备锁定失败" if lock else "设备解锁失败"
- result = self._send_data("B6", sendData=sendData)
- status = result["data"][8: 10]
- if status != "01":
- raise ServiceException({'result': 2, 'description': u'{},请重新操作试试'.format(message)})
- def _sync_device_time(self):
- """
- 开机后设备向服务器请求同步时间,服务器回复设备
- :return:
- """
- nowTime = datetime.datetime.now()
- yearHex = int_to_hex(nowTime.year, 4, reverse=True)
- monthHex = int_to_hex(nowTime.month, 2)
- dayHex = int_to_hex(nowTime.day, 2)
- hourHex = int_to_hex(nowTime.hour, 2)
- minuteHex = int_to_hex(nowTime.minute, 2)
- secondHex = int_to_hex(nowTime.second, 2)
- sendData = yearHex + monthHex + dayHex + hourHex + minuteHex + secondHex + "00"
- self._send_data("A1", sendData=sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
- def _sync_device_settings(self):
- """
- 开机后设备向服务器请求同步参数 包括需要扫描的二维码,刷卡前缀 最小功率最大功率等
- :return:
- """
- otherConf = self._device.get("otherConf", dict())
- # 扫描的设备二维码
- qr_code_url = concat_user_login_entry_url(l = self._device["logicalCode"])
- snCode = otherConf.get("snCode", self._device["logicalCode"].replace("G", ""))
- minPower = otherConf.get("minPower", YueWanTong.DEFAULT_MIN_POWER)
- maxPower = otherConf.get("maxPower", YueWanTong.DEFAULT_MAX_POWER)
- floatTime = otherConf.get("floatTime", YueWanTong.DEFAULT_FLOAT_TIME)
- ICSetupCode = otherConf.get("ICSetupCode", YueWanTong.DEFAULT_IC_CODE)
- qrCodeLenHex = int_to_hex(len(qr_code_url), 2)
- minPowerHex = int_to_hex(int(minPower), 4, reverse=True)
- maxPowerHex = int_to_hex(int(maxPower), 4, reverse=True)
- floatTimeHex = int_to_hex(int(floatTime), 4, reverse=True)
- qrCodeHex = self._to_ascii(qr_code_url)
- snCodeHex = self._to_ascii(snCode)
- ICSetupCodeHex = self._to_ascii(ICSetupCode)
- sendData = snCodeHex + minPowerHex + maxPowerHex + floatTimeHex + ICSetupCodeHex + qrCodeLenHex + qrCodeHex
- self._send_data("A2", sendData=sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
- @staticmethod
- def _parse_card_start(data):
- """
- 解析刷卡启动的数据
- :param data:
- :return:
- """
- cardPre = YueWanTong._to_str(data[8: 16])
- cardNo = YueWanTong._to_str(data[16: 32])
- portStr = str(int(data[32: 34], 16))
- return {
- "cardPre": cardPre,
- "cardNo": cardNo,
- "portStr": portStr,
- "portStrHex": data[32:34],
- "cardCode": data[8: 32]
- }
- @staticmethod
- def _parse_card_stop(data):
- """
- 解析刷卡结束 数据
- :param data:
- :return:
- """
- cardInfo = YueWanTong._parse_card_start(data)
- orderNoHex = data[34: 48]
- orderNo = str(int(reverse_hex(orderNoHex), 16))
- cardInfo.update({
- "orderNo": orderNo,
- "orderNoHex": orderNoHex
- })
- return cardInfo
- @staticmethod
- def _parse_report(data):
- """
- 设备 每10分钟 每个端口 上报一次充电状态
- 电压 电流 功率 端口状态 温度 是 当前值
- 电量 时间(断电保护) 是累计值
- 电流 暂时用不上 不用解析
- :param data:
- :return:
- """
- statusMap = {
- "00": Const.DEV_WORK_STATUS_IDLE,
- "01": Const.DEV_WORK_STATUS_WORKING,
- "02": Const.DEV_WORK_STATUS_APPOINTMENT,
- "03": Const.DEV_WORK_STATUS_FAULT, # 故障
- "04": Const.DEV_WORK_STATUS_FAULT, # 烟感
- "05": Const.DEV_WORK_STATUS_FAULT, # 雨感
- "06": Const.DEV_WORK_STATUS_FAULT # 设备锁定
- }
- orderNoHex = data[8: 22]
- orderNo = str(int(reverse_hex(orderNoHex), 16))
- portStr = str(int(data[22: 24], 16))
- voltage = int(reverse_hex(data[24: 28]), 16)
- power = int(reverse_hex(data[32: 36]), 16)
- elec = int(reverse_hex(data[36: 44]), 16) / 1000.0
- chargeTime = int(reverse_hex(data[44: 48]), 16)
- temperature = int(reverse_hex(data[52: 56]), 16)
- status = statusMap.get(data[56: 58], Const.DEV_WORK_STATUS_IDLE)
- return {
- "orderNo": orderNo,
- "orderNoHex": orderNoHex,
- "portStr": portStr,
- "voltage": voltage,
- "power": power,
- "elec": elec,
- "chargeTime": chargeTime,
- "temperature": temperature,
- "status": status
- }
- @staticmethod
- def _parse_all_report(data):
- data = data[8: -4]
- result = list()
- while data:
- orderNoHex = data[: 14]
- orderNo = str(int(reverse_hex(data[:14]), 16))
- portStr = str(int(data[14: 16], 16))
- voltage = int(reverse_hex(data[16: 20]), 16)
- power = int(reverse_hex(data[24: 28]), 16)
- elec = int(reverse_hex(data[28: 36]), 16) / 1000.0
- chargeTime = int(reverse_hex(data[36: 40]), 16)
- temperature = int(reverse_hex(data[44: 48]), 16)
- status = YueWanTong.PORT_STATUS_MAP.get(data[48: 50], Const.DEV_WORK_STATUS_IDLE)
- data = data[50: ]
- result.append({
- "orderNo": orderNo,
- "orderNoHex": orderNoHex,
- "portStr": portStr,
- "voltage": voltage,
- "power": power,
- "elec": elec,
- "chargeTime": chargeTime,
- "temperature": temperature,
- "status": status
- })
- return {"subChargeList": result}
- @staticmethod
- def _parse_fault(data):
- """
- 解析故障上报数据
- :param data:
- :return:
- """
- # TODO zjl 目前只有三个,后续可能会增加
- faultMap = {
- "1001": u"烟雾报警",
- "1002": u"雨感报警",
- "1003": u"温度报警"
- }
- portStr = str(int(data[8: 10], 16))
- faultHex = data[10: 14]
- fault = str(int(faultHex[2: 4] + faultHex[: 2], 16))
- faultReason = faultMap.get(fault)
- return {
- "portHex": data[8: 10],
- "portStr": portStr,
- "faultCode": fault,
- "statusInfo": faultReason
- }
- @staticmethod
- def _parse_stop(data):
- """
- 解析充电结束上报事件,与状态上报类似
- :param data:
- :return:
- """
- reasonMap = {
- "00": u"充满自停",
- "01": u"未检测到充电设备",
- "02": u"过载结束",
- "03": u"电压输出故障",
- "04": u"刷卡结束",
- "05": u"充满自停",
- "06": u"密码开锁结束订单"
- }
- orderNoHex = data[8: 22]
- orderNo = str(int(reverse_hex(orderNoHex), 16))
- portHex = data[22: 24]
- portStr = str(int(data[22: 24], 16))
- voltage = int(reverse_hex(data[24: 28]), 16)
- power = int(reverse_hex(data[32: 36]), 16)
- elec = int(reverse_hex(data[36: 44]), 16) / 1000.0
- chargeTime = int(reverse_hex(data[44: 48]), 16)
- temperature = int(reverse_hex(data[52: 56]), 16)
- reasonCode = data[56: 58]
- reason = reasonMap.get(reasonCode)
- return {
- "orderNoHex": orderNoHex,
- "orderNo": orderNo,
- "portHex": portHex,
- "portStr": portStr,
- "voltage": voltage,
- "power": power,
- "elec": elec,
- "chargeTime": chargeTime,
- "temperature": temperature,
- "reasonCode": reasonCode,
- "reason": reason
- }
- def _ack(self, ack_id):
- self._send_data(funCode='AK', sendData=ack_id, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
- def get_port_status_from_dev(self):
- """
- 从设备端 获取端口信息 充电/空闲
- :return:
- """
- portInfo = self._read_port_status()
- portDict = dict()
- for portStr, status in portInfo.items():
- portDict[portStr] = {"status": status}
- # 更新可用端口数量
- allPorts, usedPorts, usePorts = self.get_port_static_info(portDict)
- Device.update_dev_control_cache(
- self._device["devNo"],
- {
- "allPorts": allPorts,
- "usedPorts": usedPorts,
- "usePorts": usePorts
- }
- )
- # 更新端口状态
- devCache = Device.get_dev_control_cache(self._device["devNo"])
- for port, info in portDict.items():
- if port in devCache and isinstance(info, dict):
- devCache[port].update({"status": info["status"]})
- else:
- devCache[port] = info
- Device.update_dev_control_cache(self._device["devNo"], devCache)
- return portDict
- def get_port_status(self, force = False):
- """
- 获取端口状态
- :param force:
- :return:
- """
- self.get_port_status_from_dev()
- # 获取端口缓存
- devCache = Device.get_dev_control_cache(self._device["devNo"])
- if "allPorts" not in devCache:
- self.get_port_status_from_dev()
- devCache = Device.get_dev_control_cache(self._device["devNo"])
- allPorts = devCache.get("allPorts")
- if allPorts is None:
- raise ServiceException({'result': 2, 'description': u'充电端口信息获取失败'})
- # 获取显示端口的数量 客户要求可以设置 显示给用户多少个端口
- showPortNum = self._device.get("otherConf", {}).get("actualPortNum", self.DEFAULT_PORT_NUM)
- statusDict = dict()
- showStatusDict = dict()
- for portNum in xrange(allPorts):
- portStr = str(portNum + 1)
- tempDict = devCache.get(portStr, {})
- if "status" in tempDict:
- statusDict[portStr] = {"status": tempDict["status"]}
- elif "isStart" in tempDict:
- if tempDict["isStart"]:
- statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_WORKING}
- else:
- statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_IDLE}
- else:
- statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_IDLE}
- if int(portStr) <= int(showPortNum):
- showStatusDict[portStr] = {"status": statusDict[portStr]["status"]}
- allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict)
- portsDict = {"allPorts": allPorts, "usedPorts": usedPorts, "usePorts": usePorts}
- Device.update_dev_control_cache(self._device["devNo"], portsDict)
- # 返还的是要显示的端口数量
- return showStatusDict
- def get_port_info(self, line=None):
- """
- 获取单一的端口状态
- :param line:
- :return:
- """
- if line is None:
- return
- portStr = str(line)
- # 设备端读取端口状态
- result = self._read_port_charge_status()
- portInfo = result.get(portStr, {})
- # 缓存端读取设备端口状态
- devCache = Device.get_dev_control_cache(self._device["devNo"])
- portCache = devCache.get(portStr, {})
- portCache.update(portInfo)
- Device.update_dev_control_cache(self._device["devNo"], {portStr: portCache})
- if portCache.get('chargeType') == 'billAsService':
- elecCharge = round(self.device.otherConf.get("elecCharge", YueWanTong.DEFAULT_ELEC_CHARGE), 2)
- serviceCharge = round(self.device.otherConf.get("serviceCharge", YueWanTong.DEFAULT_SERVICE_CHARGE), 2)
- portCache['elecFee'] = round(portInfo.get('elec', 0) * elecCharge, 2)
- portCache['serviceFee'] = round(portInfo.get('elec', 0) * serviceCharge, 2)
- return portCache
- def get_dev_setting(self):
- """
- 读取设备设置
- 显示的有: IC卡设置码 最小充电功率 最大充电功率 浮充时间 显示的端口数量
- :return:
- """
- otherConf = self._device.get("otherConf", {})
- powerPackage = otherConf.get("powerPackage", YueWanTong.DEFAULT_POWER_PACKAGE)
- newPowerPackage = YueWanTong._suit_power_package(powerPackage)
- payAfterUse = int(otherConf.get("payAfterUse", False)) # 前台接受的是数值模型
- # 电费 + 服务费
- elecCharge = round(otherConf.get("elecCharge", YueWanTong.DEFAULT_ELEC_CHARGE), 2)
- serviceCharge = round(otherConf.get("serviceCharge", YueWanTong.DEFAULT_SERVICE_CHARGE), 2)
- return {
- "minPower": otherConf.get("minPower", YueWanTong.DEFAULT_MIN_POWER),
- "maxPower": otherConf.get("maxPower", YueWanTong.DEFAULT_MAX_POWER),
- "floatTime": otherConf.get("floatTime", YueWanTong.DEFAULT_FLOAT_TIME),
- "ICSetupCode": otherConf.get("ICSetupCode", YueWanTong.DEFAULT_IC_CODE),
- "actualPortNum": otherConf.get("actualPortNum", YueWanTong.DEFAULT_PORT_NUM),
- "minConsume": otherConf.get("minConsume", YueWanTong.DEFAULT_MIN_CONSUME),
- "refundProtectTime": otherConf.get("refundProtectTime", YueWanTong.DEFAULT_MIN_CHARGE_TIME),
- "minAfterStartCoins": otherConf.get("minAfterStartCoins", 2),
- "payAfterUse": payAfterUse,
- "chargeType": otherConf.get("chargeType", YueWanTong.DEFAULT_CHARGE_TYPE),
- "elecPrice": otherConf.get("elecPrice", YueWanTong.DEFAULT_ELEC_PRICE),
- "powerPackage": newPowerPackage,
- # 电费 + 服务费
- "elecCharge": elecCharge,
- "serviceCharge": serviceCharge,
- }
- def update_device_conf(self, data):
- minPower = data.get("minPower")
- maxPower = data.get("maxPower")
- floatTime = data.get("floatTime")
- ICSetupCode = data.get("ICSetupCode")
- actualPortNum = data.get("actualPortNum")
- minConsume = data.get("minConsume")
- payAfterUse = data.get("payAfterUse")
- refundProtectTime = data.get("refundProtectTime")
- minAfterStartCoins = data.get("minAfterStartCoins")
- powerPackage = data.get("powerPackage")
- chargeType = data.get("chargeType")
- elecPrice = data.get("elecPrice")
- # 电费 + 服务费
- elecCharge = round(data.get("elecCharge", YueWanTong.DEFAULT_ELEC_CHARGE), 2)
- serviceCharge = round(data.get("serviceCharge", YueWanTong.DEFAULT_SERVICE_CHARGE), 2)
- otherConf = self._device.get("otherConf", {})
- otherConf.update({
- "minPower": int(minPower),
- "maxPower": int(maxPower),
- "floatTime": int(floatTime),
- "ICSetupCode": ICSetupCode,
- "actualPortNum": int(actualPortNum),
- "minConsume": float(minConsume),
- "minAfterStartCoins": float(minAfterStartCoins),
- "refundProtectTime": int(refundProtectTime),
- "payAfterUse": bool(int(payAfterUse)),
- "chargeType": chargeType,
- "elecPrice": float(elecPrice),
- "powerPackage": powerPackage,
- # 服务费 + 电量
- "elecCharge": elecCharge,
- "serviceCharge": serviceCharge,
- })
- devNo = self._device["devNo"]
- try:
- Device.objects.filter(devNo=devNo).update(otherConf=otherConf)
- except Exception as e:
- logger.error(e)
- raise ServiceException({'result': 2, 'description': u'设置错误,请重新操作试试'})
- Device.invalid_device_cache(devNo)
- def set_device_function(self, request, lastSetConf):
- """
- 设备功能
- :param request:
- :param lastSetConf:
- :return:
- """
- lockDevice = request.POST.get("lockDevice", None)
- rebootDevice = request.POST.get("rebootDevice", None)
- setToDevice = request.POST.get("setToDevice", None)
- if rebootDevice is not None:
- self._reboot_device()
- return
- if lockDevice is not None and lockDevice in ("false", "true"):
- lockDevice = json.loads(lockDevice)
- self._lock_unlock_device(lockDevice)
- return
- # 下发所有参数到设备,这个地方做一个保护 ,设备正在工作的时候不让设置参数
- if setToDevice is not None:
- self._sync_device_settings()
- return
- def set_device_function_param(self, request, lastSetConf):
- """
- 设置设备参数 需要对参数规则做校验
- :param request:
- :param lastSetConf:
- :return:
- """
- # 校验端口数量
- actualPortNum = request.POST.get("actualPortNum", None)
- if actualPortNum is not None and int(actualPortNum) > 36:
- raise ServiceException({"result": 2, "description": u"实际端口数量最大36"})
- powerPackage = request.POST.get("powerPackage", None)
- if powerPackage is not None:
- if len(powerPackage) > 6:
- raise ServiceException({"result": 2, "description": u"功率计费不得大于六档"})
- # 这个地方开始各种计费模式收费模式
- chargeType = request.POST.get("chargeType")
- if chargeType not in ["power", "time", "elec", "billAsService"]:
- raise ServiceException({"result": 2, "description": u"不支持的计费模式"})
- self.update_device_conf(request.POST)
- def start_device(self, package, openId, attachParas):
- """
- :param package:
- :param openId:
- :param attachParas:
- :return:
- """
- # 在此次改版的时候, 凡是 payAfterUse == True 的设备 都说明是使用了按功率付费的设备,这个地方做一个转换 为下一步切换做准备
- otherConf = self.device.get("otherConf", dict())
- if "chargeType" not in otherConf:
- if otherConf.get("payAfterUse", YueWanTong.DEFAULT_PAY_AFTER_USE):
- otherConf["chargeType"] = "power"
- else:
- otherConf["chargeType"] = "time"
- Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf)
- Device.invalid_device_cache(self.device.devNo)
- # 由于参数重新更新过了 这个地方就在加载一次
- dev = Device.get_dev(self.device.devNo)
- self._device = dev
- # 从启动参数中获取相关的 参数 订单号 以及 充电端口等等
- portStr = attachParas.get("chargeIndex")
- orderNo = attachParas.get("orderNo")
- if portStr is None:
- raise ServiceException({'result': 2, 'description': u'未知端口'})
- lockPorts = self.device.otherConf.get("lockPorts") or list()
- if str(portStr) in lockPorts:
- raise ServiceException({"result": 2, "description": u"当前端口已被禁用"})
- # 计算充电时间 如果套餐是充满自停 则会在启动的时候设置为最大值
- unit = package.get("unit")
- needTime = package.get("time")
- coins = package.get("coins")
- needTime = self._transform_time(needTime, unit)
- # 获取当前的计费模式
- otherConf = self.device.get("otherConf", dict())
- chargeType = otherConf.get("chargeType")
- payAfterUse = otherConf.get("payAfterUse")
- if chargeType == "time" and not needTime:
- raise ServiceException({"result": 2, "description": u"时间套餐设置错误,请联系经销商解决"})
- # 发送指令
- result = self._start_device(orderNo=orderNo, port=portStr, chargeTime=needTime)
- portDict = {
- "status": Const.DEV_WORK_STATUS_WORKING,
- "vCardId": self._vcard_id,
- "isStart": True,
- "openId": openId,
- "orderNo": orderNo,
- "coins": coins,
- "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
- "chargeType": chargeType,
- "payAfterUse": payAfterUse
- }
- if needTime:
- portDict["needTime"] = needTime
- Device.update_dev_control_cache(self._device["devNo"], {str(portStr): portDict})
- result["consumeOrderNo"] = orderNo
- return result
- def analyze_event_data(self, data):
- """
- 解析设备上报的各种事件
- :param data:
- :return:
- """
- funcCode = data[6: 8]
- # 主板请求同步时间
- if funcCode == "A1":
- self._sync_device_time()
- return
- # 主板请求同步设备参数
- elif funcCode == "A2":
- self._sync_device_settings()
- return
- # 刷卡请求充电
- elif funcCode == "B0":
- ret = self._parse_card_start(data)
- ret.update({'cmdCode': 'B0'})
- return ret
- # 刷卡结束充电
- elif funcCode == "B1":
- ret = self._parse_card_stop(data)
- ret.update({'cmdCode': 'B1'})
- return ret
- # 设备上报充电结束
- elif funcCode == "C1":
- ret = self._parse_stop(data)
- ret.update({'cmdCode': 'C1'})
- return ret
- # 端口状态上报
- elif funcCode == "C0":
- ret = self._parse_report(data)
- ret.update({'cmdCode': 'C0'})
- return ret
- elif funcCode == "CA":
- ret = self._parse_all_report(data)
- ret.update({'cmdCode': 'CA'})
- return ret
- # 故障上报
- elif funcCode == "E0":
- ret = self._parse_fault(data)
- ret.update({'cmdCode': 'E0'})
- return ret
- else:
- logger.error("error event! funCode is {}".format(funcCode))
- def _get_time_use_money(self, reportRecord): # type:(DevicePortReport) -> float
- """
- 时间计费模式下 预付费的充电时间由 下发的参数进行控制 这个地方不再予以控制
- :param reportRecord:
- :return:
- """
- portCache = Device.get_port_control_cache(self.device.devNo, reportRecord.port)
- needTime = portCache.get("needTime") # 单位分钟
- coins = portCache["coins"]
-
- # 时间模式下一定会有 needTime 如果没有needTime 一定是出错了 做个保护
- if not needTime:
- return coins
- return coins * float(reportRecord.chargeTime) / float(needTime)
- def _get_elec_use_money(self, reportRecord): # type:(DevicePortReport) -> float
- """
- 电量计费模式下 预付费模式下的已经使用的金额
- :param reportRecord:
- :return:
- """
- usedElec = reportRecord.elec
- elecPrice = self.device.get("otherConf", dict()).get("elecPrice", self.DEFAULT_ELEC_PRICE)
- return usedElec * elecPrice
- def _get_power_use_money(self, reportRecord): # type:(DevicePortReport) -> float
- records = DevicePortReport.billing_records(reportRecord.orderNo)
- intervalMap = self._get_power_interval_map()
- consumeMoney = DevicePortReport.calculate(records, intervalMap)
- return consumeMoney
- def _get_billAsService_use_money(self, reportRecord):
- """
- 服务费 + 电费模式
- :param reportRecord:
- :return:
- """
- usedElec = reportRecord.elec
- elecCharge = round(
- float(self.device.get("otherConf", dict()).get("elecCharge", self.DEFAULT_ELEC_CHARGE)) * usedElec, 2)
- serviceCharge = round(float(
- self.device.get("otherConf", dict()).get("serviceCharge", self.DEFAULT_SERVICE_CHARGE)) * usedElec, 2)
- return elecCharge, serviceCharge
- def _get_power_interval_map(self):
- """
- 获取计费区间---单价映射
- :return:
- """
- if hasattr(self, "_interval_map"):
- return self._interval_map
- powerPackage = self.device.get(
- "otherConf", dict()
- ).get("powerPackage", self.DEFAULT_POWER_PACKAGE)
- powerPackage = self._suit_power_package(powerPackage)
- mapDict = dict()
- for package in powerPackage:
- lowLimit = package.get("lowLimit")
- upLimit = package.get("upLimit")
- price = package.get("price")
- _time = package.get("time")
- mapDict.update({
- Interval(int(lowLimit), int(upLimit), closed = "left"):
- {
- "unitPrice": float(price),
- "time": 0
- }
- })
- setattr(self, "_interval_map", mapDict)
- return mapDict
- def stop(self, port = None):
- portCache = Device.get_port_control_cache(self.device.devNo, str(port))
- orderNo = portCache.get('orderNo', 0)
- openId = portCache.get('openId', None)
- if orderNo == 0 or openId is None:
- raise ServiceException({'result': 2, 'description': u'未知订单无法停止,请稍后再试。'})
- result = self._stop_device(orderNo, str(port))
- event_data = self.__parse_B2(result)
- from apps.web.eventer.yuewantong import YueWanTongWorkerEvent
- ywt = YueWanTongWorkerEvent(self,event_data)
- ywt.do()
- ServiceProgress.update_progress_and_consume_rcd(
- self.device['ownerId'],
- {
- 'open_id': openId, 'device_imei': self.device['devNo'],
- 'port': int(port), 'isFinished': False
- },{})
- infoDict = dict()
- infoDict['remainder_time'] = 0
- return infoDict
- @property
- def isHaveStopEvent(self):
- return True
- def __parse_B2(self,data):
- data = data.get("data")
- dataDict = {
- "cmdCode" : data[6:8],
- "orderNoHex" : data[8:22],
- "orderNo" :str(int(reverse_hex(data[8:22]), 16)),
- "portHex" : data[22:24],
- "portStr" : str(int(reverse_hex(data[22:24]), 16)),
- "voltage" : str(int(reverse_hex(data[24:28]), 16)),
- "power" : str(int(reverse_hex(data[32:36]), 16)),
- "elec" : int(reverse_hex(data[36:44]), 16) / 1000.0,
- "chargeTime" : int(reverse_hex(data[44:48]), 16),
- "reason" : u"远程手动停止充电!"
- }
- return dataDict
- def lock_unlock_port(self, port, lock=True):
- """
- 禁用端口
- 主板不支持 禁用端口 只能服务器实现
- """
- otherConf = self.device.get("otherConf")
- lockPorts = otherConf.get("lockPorts", list())
- port = str(port)
- try:
- if lock:
- if port not in lockPorts:
- lockPorts.append(port)
- else:
- lockPorts.remove(port)
- except Exception as e:
- logger.error(e)
- otherConf.update({"lockPorts": lockPorts})
- try:
- Device.objects(devNo=self.device.devNo).update(otherConf=otherConf)
- Device.invalid_device_cache(self.device.devNo)
- except Exception as e:
- logger.error("update device %s lockPorts error the reason is %s " % (self.device.devNo, e))
- raise ServiceException({'result': 2, 'description': u'操作失败,请重新试试'})
|