123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- # 整个业务流程的 控制的最关键的三个函数 _open, _charge, _stop
- # 粤万通 充电柜 基本的业务流程可以描述为
- # 1. 扫码/刷卡 启动设备 此时调用 _open 函数,并创建相应的订单以及开锁密码 开锁密码在整个的充电过程中不能改变 用于设备离线时候直接从触控屏解锁
- # 2. 门锁打开的时候 等候用户下一步操作(放入电池、关闭门锁)
- # 3. 主板方检测到门锁关闭之后 上报门锁关闭信号 服务器接收到关锁信号之后 调用_charge 函数 ,注意此时仅仅充电不开锁 开启端口的充电
- # 4. 在整个充电的过程中 主板会不断地上报 该端口的充电数据 C0指令,其中 power 为瞬时功率 chargeTime 为累计充电时间 stayTime 为累计占位时间
- # 5. 在 到达最大充电时间 或者 主板检测电池充满 之后, 会上报充电结束的指令 C1,同时主板停止充电,开始进入 占位状态
- # 6. 需要注意的是,在充电柜业务流程中,停止充电并不意味着 流程结束,也不代表可以对其进行 费用结算
- # 7. 关于充电柜的业务结束一共有以下 3种情况:
- # 7.1 用户再次扫码,并点击停止充电按钮,此时触发 stop 函数,服务器通过 _stop 发起对主板的开锁,主板接收到之后 开锁并同时上报最后一次数据状态
- # 7.2 用户刷卡启动的设备,需要用户刷卡结束,用户第二次刷卡 后,主板上报刷卡请求充电指令,服务器接收到之后 通过 _stop 发起对于主板的开锁 同时上报最后一次充电状态
- # 7.3 用户直接在主板上选择密码开锁, 输入密码(开启充电时候下发的密码),主板接收到之后,无条件开锁,同时上报最后一次充电状态。一般用于设备离线的时候使用
- # 8. 用户以任何方式 结束充电柜业务之后,需要对其订单进行结算,并将整个过程中的费用记录到相应订单上
- # 关于粤万通新式充电柜的计费
- # 计费公式为: 总费用 = 充电费用 + 占位费用
- # 对于占位费用,目前的计费方式就是 占位时间 ✖ 占位单价
- # 对于充电费用,目前一共有 两种计费方式可供选择,以下
- # 充电计费方式一:按时间计费:
- # 本次粤万通计费将时间计费修改为按时段计费,例如
- # 经销商设置8:00-9:00 单价为1小时1元;9:00-10:00 单价为1小时2元;默认单价为1小时0.5元
- # 某用户从7:00充电一直到10:30 共四个小时
- # 7:00-8:00时间段经销商没有设置单价, 则费用为默认单价0.5元
- # 8:00-9:00时间段设置了单价,则费用为1元
- # 9:00-10:00时间段设置了单价,则费用为2元
- # 10:00-10:30 时间段没有设置单价,则费用为默认单价 0.5*0.5小时 为0.25元
- # 此次按时间充电的费用为 0.5+1+2+0.25 = 3.75元
- # 充电计费方式二:按功率计费,即经销商预设功率挡位以及相应单价 例如
- # 经销商设置为100-200w 1小时1元;200-400w 1小时2元,默认单价为0.5元
- # 用户充电过程中一共1小时,其中半小时功率为95w,半小时功率为 240w,则充电总费用为 1.5元
- # 充电计费的注意事项:
- # 1.按时间计费的时间是订单的初始时间-结束时间,也就是说只要最关键的报文结束报文 服务器能够接收到,就一定能计算出费用
- # 2.按功率计费需要依赖每次的充电状态上报,即C0指令,C0指令主板会有ack机制(最新的修改已经将ack置于模块侧回复),但若某次没有收到,则以最近一次的功率为主计算该段的费用
- # 3.计费有最低费用和最高费用,假设经销商设置的最低费用为1元,最高费用为10元,假设某次用户总费用为0.5元,则系统收费为1元;同理也不能超过最大费用
- import binascii
- import datetime
- import logging
- import os
- import random
- import time
- import simplejson as json
- from mongoengine import DoesNotExist
- from apilib.monetary import VirtualCoin, RMB
- from apilib.utils_string import make_title_from_dict
- from apps.web.common.proxy import ClientConsumeModelProxy
- from apps.web.constant import DeviceCmdCode, Const, MQTT_TIMEOUT
- from apps.web.core.adapter.base import SmartBox, reverse_hex, fill_2_hexByte
- from apps.web.core.exceptions import ServiceException
- from apps.web.core.networking import MessageSender
- from apps.web.device.models import Group, Device, DevicePortLastReport, DeviceType
- from apps.web.user.models import ConsumeRecord, ServiceProgress, MyUser
- from apps.web.core.device_define.ywt_chongdiangui_new import DefaultParams, Calculater
- from apps.web.utils import concat_user_login_entry_url
- from taskmanager.mediator import task_caller
- logger = logging.getLogger(__name__)
- class ChargeCabinet(SmartBox):
- def __init__(self, *args, **kwargs):
- super(ChargeCabinet, self).__init__(*args, **kwargs)
- # 主要用于计算费用
- self.devNo = self.device.devNo
- @property
- def is_support_auto_charge(self):
- """
- 是否支持自动供电
- 主板的新特性 发送新的指令不需要发送充电 关门后直接启动充电
- 兼容之前的设备特性和现在的设备类型特性
- """
- # 先找设备
- if self.device.support_dev_type_features("support_auto_charge_after_close_door"):
- return True
- return False
- @property
- def password(self):
- return random.randint(0x2710, 0xFFFF)
- 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'系统错误'})
- @staticmethod
- def _to_str(data):
- return binascii.unhexlify(data)
- @staticmethod
- def _to_ascii(data):
- return binascii.hexlify(data)
- @staticmethod
- def _suit_power_package(package):
- """适配之前的 功率计费规则"""
- if not package or "lowLimit" not in package[0].keys():
- return package
- newPackage = list()
- for _item in package:
- newPackage.append({"max": _item["upLimit"], "min": _item["lowLimit"], "price": _item["price"] * 60 / _item["time"]})
- newPackage.append({"max": "default", "min": "default", "price": 0})
- return newPackage
- @staticmethod
- def _suit_time_package(package):
- """适配之前的 时间计费规则"""
- if not package or not isinstance(package, (int, str)):
- return package
- return [{"max": "default", "min": "default", "price": int(package)}]
- def _notify_user_service_over(self, managerialOpenId, extra, isPaid=True):
- title = make_title_from_dict(extra)
- notifyData = {
- "title": title,
- "service": u"已使用账户余额自动结算此次消费" if isPaid else u"您的账户余额已不足以抵扣此次消费,请前往账单中心进行支付",
- "finishTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
- "remark": u'谢谢您的支持'
- }
- task_caller(
- func_name='report_to_user_via_wechat',
- openId=managerialOpenId,
- dealerId=self.device.get("ownerId"),
- templateName="service_complete",
- **notifyData
- )
- @staticmethod
- def _parse_event_A1(data):
- return dict()
- @staticmethod
- def _parse_event_A2(data):
- return dict()
- @staticmethod
- def _parse_event_B0(data):
- cardPre = ChargeCabinet._to_str(data[8: 16])
- cardNo = ChargeCabinet._to_str(data[16: 32])
- portStr = str(int(data[32: 34], 16))
- return {
- "cardPre": cardPre,
- "cardNo": cardNo,
- "cardNoHex": data[8: 32], # zjl 16->8
- "portStr": portStr,
- "portHex": data[32: 34] # zjl new
- }
- @staticmethod
- def _parse_event_B1(data):
- cardPre = ChargeCabinet._to_str(data[8: 16])
- cardNo = ChargeCabinet._to_str(data[16: 32])
- portStr = str(int(data[32: 34], 16))
- orderNoHex = data[34: 48]
- orderNo = str(int(reverse_hex(orderNoHex), 16))
- return {
- "cardPre": cardPre,
- "cardNo": cardNo,
- "cardNoHex": data[8: 32], # zjl 16->8
- "portStr": portStr,
- "portHex": data[32:34], # zjl
- "orderNoHex": orderNoHex,
- "orderNo": orderNo
- }
- @staticmethod
- def _parse_event_C0(data):
- 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)
- stayTime = int(reverse_hex(data[48:52]), 16)
- temperature = int(reverse_hex(data[52: 56]), 16)
- status = DefaultParams.STATUS_MAP.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,
- "stayTime": stayTime
- }
- @staticmethod
- def _parse_event_E0(data):
- portStr = str(int(data[8: 10], 16))
- faultHex = data[10: 14]
- fault = str(int(faultHex[2:4]+faultHex[:2], 16))
- faultReason = DefaultParams.FAULT_MAP.get(fault)
- return {
- "portHex": data[8: 10],
- "portStr": portStr,
- "faultCode": fault,
- "statusInfo": faultReason
- }
- @staticmethod
- def _parse_event_C1(data):
- 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)
- stayTime = int(reverse_hex(data[48:52]), 16)
- temperature = int(reverse_hex(data[52: 56]), 16)
- reasonCode = data[56: 58]
- reason = DefaultParams.STOP_REASON_MAP.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,
- "stayTime": stayTime
- }
- @staticmethod
- def _parse_event_C3(data):
- portStr = str(int(data[8:10], 16))
- doorStatus = data[10: 12]
- return {
- "portStr": portStr,
- "doorStatus": doorStatus
- }
- @staticmethod
- def _parse_result_B2(data):
- 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)
- stayTime = int(reverse_hex(data[48:52]), 16)
- return {
- "orderNoHex": orderNoHex,
- "orderNo": orderNo,
- "portHex": portHex,
- "portStr": portStr,
- "voltage": voltage,
- "power": power,
- "elec": elec,
- "chargeTime": chargeTime,
- "stayTime": stayTime
- }
- @staticmethod
- def _parse_event_CA(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)
- stayTime = int(reverse_hex(data[40: 44]), 16)
- temperature = int(reverse_hex(data[44: 48]), 16)
- status = DefaultParams.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,
- "stayTime": stayTime
- })
- return {"subChargeList": result}
- # -----------------------------------------------将开门、充电以及停止充电区分开--------------------------------------------------------
- def _open(self, port, orderNo, pw):
- """
- 打开柜门 不启动充电 为用户使用的第一步
- :param port: 端口号
- :param orderNo: 订单编号
- :param pw: 开锁密码
- :return:
- """
- operHex = "03"
- chargeTimeHex = "0000"
- orderNoHex = fill_2_hexByte(hex(int(orderNo)), 14, reverse=True)
- portHex = fill_2_hexByte(hex(int(port)), 2, reverse=True)
- pwHex = fill_2_hexByte(hex(int(pw)), 4, reverse=True)
- data = orderNoHex+portHex+chargeTimeHex+operHex+pwHex
- return self._send_data("B2", data, timeout=15)
- def _charge(self, port, orderNo, pw, door=False, chargeTime=0x0258):
- """
- 直接下发的充电指令 不打开柜门
- :param port: 端口号
- :param orderNo: 订单编号
- :param pw: 开锁密码
- :param door: true 表示开锁 false 表示不开所
- :param chargeTime: 充电时间 最大充电时间为600分钟
- :return:
- """
- operHex = "01" if not door else "02"
- orderNoHex = fill_2_hexByte(hex(int(orderNo)), 14, reverse=True)
- portHex = fill_2_hexByte(hex(int(port)), 2, reverse=True)
- pwHex = fill_2_hexByte(hex(int(pw)), 4, reverse=True)
- chargeTimeHex = fill_2_hexByte(hex(int(chargeTime)), 4, reverse=True)
- data = orderNoHex+portHex+chargeTimeHex+operHex+pwHex
- return self._send_data("B2", data, timeout=15)
- def _new_charge(self,port, orderNo, pw, chargeTime=0x0258):
- operHex = "02"
- orderNoHex = fill_2_hexByte(hex(int(orderNo)), 14, reverse=True)
- portHex = fill_2_hexByte(hex(int(port)), 2, reverse=True)
- pwHex = fill_2_hexByte(hex(int(pw)), 4, reverse=True)
- chargeTimeHex = fill_2_hexByte(hex(int(chargeTime)), 4, reverse=True)
- data = orderNoHex + portHex + chargeTimeHex + operHex + pwHex
- return self._send_data("B2", data, timeout=15)
- def _stop(self, port, orderNo, pw, door=False):
- """
- 终止设备运行
- :param port: 端口号
- :param orderNo: 订单编号
- :param pw: 开锁密码
- :param door: true表示断电的同时开锁 false 表示仅仅断电
- :return:
- """
- operHex = "11" if not door else "12"
- orderNoHex = fill_2_hexByte(hex(int(orderNo or 0)), 14, reverse=True)
- portHex = fill_2_hexByte(hex(int(port)), 2, reverse=True)
- pwHex = fill_2_hexByte(hex(int(pw)), 4, reverse=True)
- data = orderNoHex+portHex+"0000"+operHex+pwHex
- return self._send_data("B2", data)
- # ----------------------------------------------------------------------------------------------------------------------
- def _get_door_status(self):
- try:
- result = self._send_data("BD", "00", timeout=5)
- except ServiceException:
- return dict()
- data = result.get("data", "")[8: -4]
- doorStatus = dict()
- port = 0
- while data:
- port = port + 1
- _door = u"打开" if data[: 2] == "01" else u"关闭"
- doorStatus[str(port)] = _door
- data = data[2:]
- return doorStatus
- def _get_port_status(self):
- result = self._send_data("B3", "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 = DefaultParams.STATUS_MAP.get(data[2:4]) if tempPort not in lockPorts else Const.DEV_WORK_STATUS_FORBIDDEN
- portInfo.update({tempPort: tempStatus})
- data = data[4: ]
- return portInfo
- def _get_port_status_detail(self):
- result = self._send_data("B4", "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)
- tempAmpere = int(reverse_hex(data[6:10]), 16) / 1000.0
- 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)
- tempOccTime = int(reverse_hex(data[26:30]), 16)
- tempTemper = int(reverse_hex(data[30: 34]), 16)
- tempStatus = DefaultParams.STATUS_MAP.get(data[34:36]) if tempPort not in lockPorts else Const.DEV_WORK_STATUS_FORBIDDEN
- portInfo.update({
- tempPort: {
- "voltage": tempVoltage,
- "elec": tempElec,
- "ampere": tempAmpere,
- "power": tempPower,
- "chargeTime": tempTime,
- "temperature": tempTemper,
- "status": tempStatus,
- "occTime": tempOccTime
- }
- })
- data = data[36: ]
- return portInfo
- def _reboot_device(self):
- return self._send_data("B5", "00")
- def _lock_device(self):
- operateHex = "01"
- return self._send_data("B6", operateHex)
- def _unlock_device(self):
- operateHex = "00"
- return self._send_data("B6", operateHex)
- def _sync_device_time(self):
- nowTime = datetime.datetime.now()
- yearHex = fill_2_hexByte(hex(nowTime.year), 4, reverse=True)
- monthHex = fill_2_hexByte(hex(nowTime.month), 2)
- dayHex = fill_2_hexByte(hex(nowTime.day), 2)
- hourHex = fill_2_hexByte(hex(nowTime.hour), 2)
- minuteHex = fill_2_hexByte(hex(nowTime.minute), 2)
- secondHex = fill_2_hexByte(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):
- domain = os.environ.get("MY_DOMAIN")
- 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", DefaultParams.DEFAULT_MIN_POWER)
- maxPower = otherConf.get("maxPower", DefaultParams.DEFAULT_MAX_POWER)
- floatTime = otherConf.get("floatTime", DefaultParams.DEFAULT_FLOAT_TIME)
- ICSetupCode = otherConf.get("ICSetupCode", DefaultParams.DEFAULT_IC_CODE)
- qrCodeLenHex = fill_2_hexByte(hex(len(qr_code_url)), 2)
- minPowerHex = fill_2_hexByte(hex(int(minPower)), 4, reverse=True)
- maxPowerHex = fill_2_hexByte(hex(int(maxPower)), 4, reverse=True)
- floatTimeHex = fill_2_hexByte(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)
- # 添加特性 去掉前台的金额显示按钮
- otherConf.update({"payAfterUse": True})
- Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf)
- Device.invalid_device_cache(self.device.devNo)
- def _update_device_conf(self, data):
- minPower = data.get("minPower", DefaultParams.DEFAULT_MIN_POWER)
- maxPower = data.get("maxPower", DefaultParams.DEFAULT_MAX_POWER)
- floatTime = data.get("floatTime", DefaultParams.DEFAULT_FLOAT_TIME)
- ICSetupCode = data.get("ICSetupCode", DefaultParams.DEFAULT_IC_CODE)
- actualPortNum = data.get("actualPortNum", DefaultParams.DEFAULT_PORT_NUM)
- chargeType = data.get("chargeType", DefaultParams.DEFAULT_CHARGE_TYPE)
- minAfterStartCoins = data.get("minAfterStartCoins", DefaultParams.DEFAULT_MIN_START_COINS)
- stayTimeUnitPrice = data.get("stayTimeUnitPrice", DefaultParams.DEFAULT_STAY_TIME_UNIT_PRICE)
- freeStayTime = data.get("freeStayTime", DefaultParams.DEFAULT_FREE_STAY_TIME)
- minConsume = data.get("minConsume", DefaultParams.DEFAULT_MIN_CONSUME)
- maxConsume = data.get("maxConsume", DefaultParams.DEFAULT_MAX_CONSUME)
- powerPackage = data.get("powerPackage", DefaultParams.DEFAULT_POWER_PACKAGE)
- timePricePackage = data.get("timePricePackage", DefaultParams.DEFAULT_TIME_PACKAGE)
- timeUnitPrice = data.get("timePrice", DefaultParams.DEFAULT_TIME_UNIT_PRICE)
- powerUnitPrice = data.get("powerPrice", DefaultParams.DEFAULT_POWER_UNIT_PRICE)
- elecCharge = data.get("elecCharge", DefaultParams.DEFAULT_ELEC_CHARGE)
- serviceCharge = data.get("serviceCharge", DefaultParams.DEFAULT_SERVICE_CHARGE)
- otherConf = self._device.get("otherConf", {})
- otherConf.update({
- "minAfterStartCoins": int(minAfterStartCoins),
- "minPower": int(minPower),
- "maxPower": int(maxPower),
- "floatTime": int(floatTime),
- "ICSetupCode": ICSetupCode,
- "actualPortNum": int(actualPortNum),
- "chargeType": chargeType,
- "stayTimeUnitPrice": float(stayTimeUnitPrice),
- "freeStayTime": float(freeStayTime),
- "minConsume": float(minConsume),
- "maxConsume": float(maxConsume),
- "timeUnitPrice": float(timeUnitPrice),
- "powerUnitPrice": float(powerUnitPrice),
- "powerPackage": powerPackage,
- "timePricePackage": timePricePackage,
- "elecCharge": float(elecCharge),
- "serviceCharge": float(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 _dealer_start_device(self, package, attachParas):
- """
- 如果 是经销商启动 就当是取消当前端口的订单
- :param package:
- :param attachParas:
- :return:
- """
- portStr = attachParas.get("chargeIndex")
- if not portStr:
- raise ServiceException({"result": "2", "description": u"请选择插座号!"})
- devCache = Device.get_dev_control_cache(self.devNo)
- portCache = devCache.get(portStr, dict())
- orderNo = portCache.get("orderNo", 0xFFFFFFFFFFFFFF)
- pw = portCache.get("pw", 0)
- self._stop(portStr, orderNo, pw, door=True)
- # 清空端口的缓存
- Device.clear_port_control_cache(self.devNo, portStr)
- # 然后将订单直接取消
- try:
- consumeRecord = ConsumeRecord.objects.get(orderNo=orderNo, isNormal=True)
- except DoesNotExist:
- return
- consumeRecord.isNormal = False
- consumeRecord.status = "finished"
- consumeRecord.errorDesc = u"经销商关闭订单"
- consumeRecord.finishedTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- consumeRecord.save()
- sp = ServiceProgress.objects.filter(
- **{"device_imei": self.devNo, "isFinished": False, "open_id": consumeRecord.openId, "port": int(portStr)}
- ).first()
- if not sp:
- return
- sp.isFinished = True
- sp.finished_time = int(time.time())
- sp.status = "finished"
- sp.save()
- def _stage_billing(self, devNo, port, orderNo, **kwargs):
- try:
- order = ConsumeRecord.objects.get(orderNo=orderNo)
- except DoesNotExist:
- logger.warning("[{}._stage_billing] not find order, devNo = {}, orderNo = {}".format(self.__class__.__name__, devNo, orderNo))
- return
- lastReport = DevicePortLastReport.get_last(devNo=devNo, port=port, orderNo=orderNo)
- newReport = DevicePortLastReport.save_last(devNo=devNo, port=port, orderNo=orderNo, **kwargs)
- cal = Calculater(device=self.device, order=order)
- chargeConsume, stayConsume = cal.stage_billing(lastReport, newReport)
- newReport.update_consume(chargeConsume, stayConsume)
- def _ack(self, ack_id):
- self._send_data(funCode='AK', sendData=ack_id, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
- def check_order_state(self, openId):
- """
- 通过 openId 以及设备来鉴别 订单
- :param openId:
- :return:
- """
- dealerId = self.device.ownerId
- devTypeCode = self.device.devType.get("code")
- return ClientConsumeModelProxy.get_not_finished_record(ownerId = dealerId,
- openId = openId,
- devTypeCode = devTypeCode)
- def analyze_event_data(self, data):
- cmdCode = data[6:8]
- funcName = "_parse_event_{}".format(cmdCode.upper())
- func = getattr(ChargeCabinet, funcName, None)
- if func and callable(func):
- eventData = func(data)
- eventData.update({"cmdCode": cmdCode})
- return eventData
- else:
- logger.warning("<{}> device receive an undefined cmd <{}>, data is <{}>".format(self.devNo, cmdCode, data))
- def get_dev_setting(self):
- otherConf = self._device.get("otherConf", {})
- return {
- "minPower": otherConf.get("minPower", DefaultParams.DEFAULT_MIN_POWER),
- "maxPower": otherConf.get("maxPower", DefaultParams.DEFAULT_MAX_POWER),
- "floatTime": otherConf.get("floatTime", DefaultParams.DEFAULT_FLOAT_TIME),
- "ICSetupCode": otherConf.get("ICSetupCode", DefaultParams.DEFAULT_IC_CODE),
- "actualPortNum": otherConf.get("actualPortNum", DefaultParams.DEFAULT_PORT_NUM),
- "minAfterStartCoins": otherConf.get("minAfterStartCoins", DefaultParams.DEFAULT_MIN_START_COINS),
- "stayTimeUnitPrice": otherConf.get("stayTimeUnitPrice", DefaultParams.DEFAULT_STAY_TIME_UNIT_PRICE),
- "freeStayTime": otherConf.get("freeStayTime", DefaultParams.DEFAULT_FREE_STAY_TIME),
- "minConsume": otherConf.get("minConsume", DefaultParams.DEFAULT_MIN_CONSUME),
- "maxConsume": otherConf.get("maxConsume", DefaultParams.DEFAULT_MAX_CONSUME),
- "chargeType": otherConf.get("chargeType", DefaultParams.DEFAULT_CHARGE_TYPE),
- "powerPackage": otherConf.get("powerPackage", DefaultParams.DEFAULT_POWER_PACKAGE),
- "timePricePackage": otherConf.get("timePricePackage", DefaultParams.DEFAULT_TIME_PACKAGE),
- "timePricePackageDefaultPrice": otherConf.get("powerUnitPrice", DefaultParams.DEFAULT_POWER_UNIT_PRICE),
- "powerPackageDefaultPrice": otherConf.get("timeUnitPrice", DefaultParams.DEFAULT_TIME_UNIT_PRICE),
- # 服务费 + 电量
- "elecCharge": otherConf.get("elecCharge", DefaultParams.DEFAULT_ELEC_CHARGE),
- "serviceCharge": otherConf.get("serviceCharge", DefaultParams.DEFAULT_SERVICE_CHARGE),
- }
- def set_device_function_param(self, request, lastSetConf):
- actualPortNum = request.POST.get("actualPortNum", None)
- if actualPortNum is not None and int(actualPortNum) > 30:
- raise ServiceException({"result": 2, "description": u"实际端口数量最大30"})
- chargeType = request.POST.get("chargeType")
- if chargeType not in ("time", "power", "elec"):
- raise ServiceException({"result": 2, "description": u"暂不支持此消费模式"})
- # 校验计费套餐
- maxConsume = request.POST.get("maxConsume")
- minConsume = request.POST.get("minConsume")
- if float(minConsume) < 0 or float(maxConsume) < 0:
- raise ServiceException({"result": 2, "description": u"最低消费金额和最高消费金额不能小于0"})
- if float(minConsume) > float(maxConsume):
- raise ServiceException({"result": 2, "description": u"最低消费金额不得大于最高消费金额"})
- # 时间计费规则校验
- timePricePackage = request.POST.get("timePricePackage")
- for _index, _package in enumerate(timePricePackage):
- if int(_package["max"]) <= int(_package["min"]):
- raise ServiceException({"result": 2, "description": u"第 {} 阶段 时间开始 {} 小于 时间结束 {}".format(_index+1, _package["max"], _package["min"])})
- powerPackage = request.POST.get("powerPackage")
- for _index, _package in enumerate(powerPackage):
- if int(_package["max"]) <= int(_package["min"]):
- raise ServiceException({"result": 2, "description": u"第 {} 阶段 功率开始 {} 小于 功率结束 {}".format(_index+1, _package["max"], _package["min"])})
- self._update_device_conf(request.POST)
- def set_device_function(self, request, lastSetConf):
- 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_device() if lockDevice else self._unlock_device()
- return
- # 下发所有参数到设备,这个地方做一个保护 ,设备正在工作的时候不让设置参数
- if setToDevice is not None:
- return self._sync_device_settings()
- def get_port_status(self, force=False):
- if force:
- 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", DefaultParams.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_status_from_dev(self):
- portInfo = self._get_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 start_device(self, package, openId, attachParas):
- if not openId:
- return self._dealer_start_device(package, attachParas)
- portStr = attachParas.get("chargeIndex")
- orderNo = attachParas.get("orderNo")
- pw = self.password
- lockPorts = self.device.otherConf.get("lockPorts") or list()
- if str(portStr) in lockPorts:
- raise ServiceException({"result": 2, "description": u"当前端口已被禁用"})
- devCache = Device.get_dev_control_cache(self.device.devNo) or dict()
- if devCache.get(portStr, dict()).get("status") == Const.DEV_WORK_STATUS_WORKING:
- raise ServiceException({"result": 2, "description": u"当前端口已被占用"})
- if portStr is None:
- raise ServiceException({'result': 2, 'description': u'请选择充电端口'})
- if orderNo is None:
- return ServiceException({'result': 2, "description": u"订单创建失败,请重新尝试"})
- # 支持发送关门自动断电的 发送新指令
- if self.is_support_auto_charge:
- result = self._new_charge(orderNo=orderNo, port=portStr, pw=pw)
- # 否则由服务器控制充电
- else:
- result = self._open(orderNo=orderNo, port=portStr, pw=pw)
- portDict = {
- "status": Const.DEV_WORK_STATUS_WORKING,
- "vCardId": self._vcard_id,
- "isStart": True,
- "openId": openId,
- "orderNo": orderNo,
- "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
- "pw": pw,
- }
- Device.update_dev_control_cache(self._device["devNo"], {str(portStr): portDict})
- otherConf = self.device.get("otherConf") or dict()
- result["consumeOrderNo"] = orderNo
- result["servicedInfo"] = {"pw": pw, "chargeType": otherConf.get("chargeType", DefaultParams.DEFAULT_CHARGE_TYPE)}
- result["finishedTime"] = int(time.time()) + 7 * 24 * 60 * 60
- return result
- def stop(self, port=None):
- """
- 端口停止功能 用户主动停止了该端口
- 对于充电柜的业务来说 结束充电就意味着 需要打开柜门 同时结算订单
- 收到停止指令之后
- 服务器首先下发B2-12指令
- 然后根据返还的信息进行扣费处理
- 最后结存订单
- :param port:
- :return:
- """
- portStr = str(port)
- devCache = Device.get_dev_control_cache(self.device.devNo)
- portCache = devCache.get(portStr, dict())
- # 校验订单状态 非正在运行的订单不能结束 考虑 是否 加锁
- if not portCache:
- return
- if portCache.get("cardNo"):
- raise ServiceException({"result": 2, "description": u"刷卡启动的设备请使用刷卡结束"})
- orderNo = portCache.get("orderNo", "")
- consumeOrder = ConsumeRecord.objects.filter(orderNo=orderNo).first()
- if consumeOrder is None or not consumeOrder.is_running():
- return
- logger.info("device stop order, device = {}, orderNo = {}".format(self.device.devNo, orderNo))
- pw = portCache.get("pw")
- try:
- result = self._stop(port, orderNo, pw, True)
- # 然后去读取主板的的最后一次的数据
- data = result.get("data")
- curInfo = ChargeCabinet._parse_result_B2(data)
- curInfo.update({"orderNo": orderNo})
- # 记录最后一次的上报 然后获取订单的金额
- self._stage_billing(self.devNo, portStr, **curInfo)
- except ServiceException:
- # 保证一定能够结单 当真正门锁未开的时候,直接找经销商上分即可
- logger.error("device open door error! devNo = {}, orderNo = {}".format(self.device.devNo, orderNo))
- order = DevicePortLastReport.get_last_by_order(orderNo)
- curInfo = {
- "chargeTime": order.chargeTime,
- "elec": 0,
- "stayTime": order.stayTime,
- }
- consumeMoney = Calculater.get_consume_by_order(self.device, orderNo)
- consumeOrder.update(money=RMB(consumeMoney).mongo_amount, coin=VirtualCoin(consumeMoney).mongo_amount)
- consumeOrder.reload()
- consumeOrder.s_to_e()
- consumeDict = {
- "chargeIndex": portStr,
- 'actualNeedTime': curInfo.get("chargeTime"),
- 'elec': curInfo.get("elec"),
- 'stayTime': curInfo.get("stayTime"),
- 'chargeTime': curInfo.get("chargeTime")
- }
- if consumeOrder.servicedInfo and isinstance(consumeOrder.servicedInfo, dict):
- consumeDict.update(consumeOrder.servicedInfo)
- if consumeOrder.servicedInfo.get('chargeType') == 'elec':
- elec_charge, service_charge = self.calc_elecFee_and_serviceFee(consumeMoney)
- consumeDict.update({
- 'elecCharge': elec_charge.mongo_amount,
- 'serviceCharge': service_charge.mongo_amount,
- })
- ServiceProgress.update_progress_and_consume_rcd(
- self._device["ownerId"],
- {
- "open_id": consumeOrder.openId,
- "device_imei": self.device["devNo"],
- "port": int(portStr),
- "isFinished": False
- },
- consumeDict
- )
- # 通知用户充电柜业务结束 如果订单并没有使用余额结算 还需要通知用户及时去付款
- try:
- openId = portCache.get("openId")
- user = MyUser.objects.filter(openId=openId, groupId=self.device.get("groupId")).first()
- extra = [
- {u"设备编号": u"{}-{}".format(self.device["logicalCode"], port)},
- {u"服务地址": u"{}".format(self.device.group["address"])},
- {u"使用时长": u"{}分钟".format(curInfo.get("chargeTime"))},
- {u"占位时长": u"{}分钟".format(curInfo.get("stayTime"))},
- ]
- if consumeOrder.servicedInfo.get('chargeType') == 'elec':
- elec_charge, service_charge = self.calc_elecFee_and_serviceFee(consumeMoney)
- extra.extend([
- {u"使用电量": u"{}度".format(consumeDict['elec'])},
- {u"电量费用": u"{}".format(elec_charge)},
- {u"服务费用": u"{}".format(service_charge)},
- ])
- else:
- extra.append({u"本单消费": u"{}".format(consumeMoney)})
- self._notify_user_service_over(
- user.managerialOpenId,
- extra,
- consumeOrder.is_finished()
- )
- except Exception as e:
- logger.exception(e)
- Device.clear_port_control_cache(self.device.devNo, portStr)
- return
- def calc_elecFee_and_serviceFee(self, consumeMoney):
- elecCharge = float(self.device.otherConf.get("elecCharge", DefaultParams.DEFAULT_ELEC_CHARGE))
- serviceCharge = float(self.device.otherConf.get("serviceCharge", DefaultParams.DEFAULT_SERVICE_CHARGE))
- elec_charge = RMB(consumeMoney * (elecCharge / (elecCharge + serviceCharge)))
- service_charge = RMB(consumeMoney) - elec_charge
- return elec_charge, service_charge
- @property
- def isHaveStopEvent(self):
- return True
- def dealer_get_port_status(self):
- showPortNum = self._device.get("otherConf", {}).get("actualPortNum", DefaultParams.DEFAULT_PORT_NUM)
- showStatusDict = dict()
- portInfo = self._get_port_status_detail()
- devCache = Device.get_dev_control_cache(self.device.devNo)
- for port, item in portInfo.items():
- if int(port) > showPortNum:
- continue
- # 始终以 设备的状态为准
- portCache = devCache.get(port, dict())
- portCache.update(item)
- if portCache.get("status", Const.DEV_WORK_STATUS_IDLE) in (Const.DEV_WORK_STATUS_WORKING, Const.DEV_WORK_STATUS_OCCUPY):
- portCache["status"] = Const.DEV_WORK_STATUS_WORKING
- portCache["usedTime"] = portCache.get("chargeTime")
- # 如果是电量 + 服务费模式: 添加用户昵称, 添加电费金额 添加服务费金额
- try:
- order = ConsumeRecord.objects.get(orderNo=portCache['orderNo'])
- portCache['nickName'] = order.nickname
- portCache['startTime'] = portCache['startTime'][-14:]
- if order.servicedInfo.get('chargeType') == 'elec': # 电量 + 服务费模式
- consumeMoney = Calculater.get_consume_by_order(self.device, order.orderNo)
- portCache['elecFee'], portCache['serviceFee'] = self.calc_elecFee_and_serviceFee(consumeMoney)
- except:
- pass
- showStatusDict[port] = portCache
- else:
- showStatusDict[port] = {"status": portCache["status"]}
- return showStatusDict
- def get_current_use(self, order): # type: (ConsumeRecord) -> dict
- group = Group.get_group(order.groupId)
- item = ServiceProgress.objects.filter(
- device_imei=order.devNo,
- open_id=order.openId,
- attachParas__orderNo=order.orderNo,
- isFinished=False
- ).first()
- if not item:
- return dict()
- data = {
- 'startTime': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(item.start_time)),
- 'order': item.consumeOrder,
- 'address': group.get('address', ''),
- 'groupName': group.get('groupName', ''),
- 'devType': self.device['devType'].get('name'),
- 'devTypeCode': self.device['devType'].get('code'),
- 'logicalCode': self.device['logicalCode'],
- 'status': Const.DEV_WORK_STATUS_WORKING,
- 'devNo': self.devNo,
- "port": item.port
- }
- data.update(DeviceType.get_services_button(self.device['devType']['id']))
- devCache = Device.get_dev_control_cache(self.devNo)
- portCache = devCache.get(str(item.port)) or dict()
- data.update(portCache)
- return data
- def active_deactive_port(self, port, active):
- if not active:
- devCache = Device.get_dev_control_cache(self.devNo)
- portCache = devCache.get(str(port))
- orderNo = portCache.get("orderNo") or 0
- pw = portCache.get("pw") or 0
- self._stop(port, orderNo, pw)
- @property
- def support_monthly_package(self):
- return True
- 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'操作失败,请重新试试'})
|