base.py 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import binascii
  4. import copy
  5. import datetime
  6. import hashlib
  7. import logging
  8. import random
  9. import string
  10. import struct
  11. import threading
  12. import time
  13. import traceback
  14. from functools import wraps
  15. import simplejson as json
  16. import requests
  17. from bson import ObjectId
  18. from django.conf import settings
  19. from typing import Optional, Dict
  20. from apilib.monetary import RMB
  21. from apilib.utils import is_number
  22. from apilib.utils_datetime import timestamp_to_dt, to_datetime
  23. from apilib.utils_json import JsonResponse, json_dumps
  24. from apilib.utils_string import get_random_str, make_title_from_dict
  25. from apps.web.constant import Const, ErrorCode, DeviceCmdCode, FAULT_CODE, MQTT_TIMEOUT, DeviceErrorCodeDesc, \
  26. DeviceOnlineStatus, CONSUMETYPE
  27. from apps.web.core.exceptions import ServiceException, ClientServiceSerialException, ClientServiceTimeOutException, \
  28. ManagerServiceSerialException, ManagerServiceTimeOutException, TestError, TestTimeOutError, TestSerialError, \
  29. DeviceNetworkTimeoutError, InvalidParameter
  30. from apps.web.core.networking import MessageSender
  31. from apps.web.device.models import Device, Part, DeviceDict, Group, GroupDict
  32. from apps.web.user.constant2 import StartDeviceType
  33. from apps.web.user.models import ConsumeRecord, MyUser, Card
  34. from apps.web.api.models import APIStartDeviceRecord
  35. from apps.web.user.utils2 import notify_user
  36. from apps.web.utils import concat_user_center_entry_url, concat_front_end_url, testcase_point
  37. from apps.web.common.models import ExceptionLog
  38. from django.core.cache import caches
  39. logger = logging.getLogger(__name__)
  40. key = '1c7e3b866ef645760040ddde343601de'
  41. # imei:电子标签
  42. # time:一小时内的描述(0~3599)
  43. # pulseCount:脉冲个数(1~16)
  44. def encode(imei, time, pulseCount):
  45. short_time = time & 0xFFFFFFF0 # 一小时内的描述,需要按16模取整
  46. pulseCount = pulseCount - 1 # 为编码到4位,所以减一
  47. extend = short_time + pulseCount
  48. key_ext = key + datetime.datetime.now().strftime("%Y%m%d%H")
  49. code = hashlib.pbkdf2_hmac('sha1', key_ext.encode('utf-8'), struct.pack(">I", extend).lstrip('\x00'),
  50. pulseCount + 1, 128)
  51. md5_obj = hashlib.md5(imei.encode('utf-8'))
  52. md5_obj.update(code)
  53. md5_digest = md5_obj.digest()
  54. md5_digest_head = int(binascii.hexlify(md5_digest[0]), 16)
  55. digest = md5_digest_head >> 1
  56. digest = (digest << 12) + extend
  57. return '{:0>6}'.format(digest)
  58. def hexbyte_2_bin(hexCode):
  59. binTemp = bin(int(hexCode, 16))
  60. needNum = 8 - len(binTemp) + 2
  61. c = '00000000'
  62. result = c[0:needNum] + binTemp[2::]
  63. return result
  64. def decimal_2_hexByte(decimalCode):
  65. hexTemp = hex(decimalCode)
  66. c = '00'
  67. needNum = 2 - len(hexTemp) + 2
  68. result = c[0:needNum] + hexTemp[2::]
  69. return result
  70. def fill_2_hexByte(hexCode, num = 4, reverse = False):
  71. hexCode = hexCode.replace('L', '')
  72. tList = ['0' for _ in range(num)]
  73. c = ''.join(tList)
  74. needNum = len(c) + 2 - len(hexCode)
  75. if needNum <= 0:
  76. if reverse:
  77. return reverse_hex(hexCode[2::]).upper()
  78. return hexCode[2::].upper()
  79. result = c[0:needNum] + hexCode[2::]
  80. if reverse:
  81. return reverse_hex(result).upper()
  82. return result.upper()
  83. def pack_float(value, reverse = False):
  84. """
  85. 将浮点数转换为16进制转换的 float
  86. :param value: 浮点数
  87. :param reverse: 小端 (True)或者 大端
  88. :return:
  89. """
  90. sign = ">f" if not reverse else "<f"
  91. return struct.pack(sign, value).encode("hex")
  92. def asc_to_string(ascString):
  93. result = ''
  94. for ii in range(len(ascString) / 2):
  95. char = int(ascString[ii * 2:ii * 2 + 2], 16)
  96. if char == 0:
  97. break
  98. result += chr(char)
  99. return result
  100. # 将字符串转为asc码形式的字符串
  101. def string_to_ascstring(strIn, needLenth=-1):
  102. result = ''
  103. for a in strIn:
  104. result += str(hex(ord(a))).replace("0x", "")
  105. if needLenth > len(result):
  106. for ii in range(needLenth-len(result)):
  107. result += '0'
  108. return result.upper()
  109. def unpack_float(value, reverse = False):
  110. """
  111. 解析16进制浮点数
  112. :param value: 16进制浮点数
  113. :param reverse:小端 (True)或者 大端
  114. :return:
  115. """
  116. sign = ">f" if not reverse else "<f"
  117. return struct.unpack(sign, value.decode("hex"))[0]
  118. def reverse_hex(hexNum):
  119. newHexNum = ""
  120. while hexNum:
  121. newHexNum += hexNum[-2:]
  122. hexNum = hexNum[: -2]
  123. return newHexNum
  124. def calc_signal_from_rssi(rssi):
  125. if rssi < 70:
  126. return 31
  127. elif rssi < 85:
  128. return 20
  129. else:
  130. return 5
  131. class ChargingMixin(object):
  132. def active_deactive_port(self):
  133. raise NotImplementedError()
  134. def lock_unlock_port(self, port, lock = True):
  135. raise NotImplementedError()
  136. class _RequestMixin(object):
  137. def get(self, url, data = None, **kwargs):
  138. return self._request(
  139. method = 'get',
  140. url = url,
  141. data = data,
  142. **kwargs)
  143. def post(self, url, data = None, **kwargs):
  144. return self._request(
  145. method = 'post',
  146. url = url,
  147. data = data,
  148. **kwargs
  149. )
  150. def _decode_result(self, res):
  151. try:
  152. import simplejson as json
  153. result = json.loads(res.content.decode('utf-8', 'ignore'), strict = False)
  154. except (TypeError, ValueError):
  155. return res
  156. return result
  157. def _request(self, method, url, data = None, headers = None, **kwargs):
  158. with requests.sessions.Session() as session:
  159. if headers:
  160. headers.update({
  161. 'Content-Type': 'application/json'
  162. })
  163. else:
  164. headers = {
  165. 'Content-Type': 'application/json'
  166. }
  167. if data:
  168. body = json_dumps(data)
  169. body = body.encode('utf-8')
  170. kwargs['data'] = body
  171. res = session.request(
  172. url = url,
  173. method = method,
  174. headers = headers,
  175. **kwargs)
  176. try:
  177. res.raise_for_status()
  178. except requests.RequestException as reqe:
  179. return {
  180. 'errcode': 'EXCEPTION',
  181. 'errmsg': str(reqe)
  182. }
  183. return self._decode_result(res)
  184. class AsyncStartDevice(threading.Thread, _RequestMixin):
  185. def __init__(self, deviceAdapter, record):
  186. # type: (SmartBox, APIStartDeviceRecord)->None
  187. super(AsyncStartDevice, self).__init__()
  188. self.deviceAdapter = deviceAdapter
  189. self.record = record # type:APIStartDeviceRecord
  190. def notify_api_client(self, url, **kwargs):
  191. try:
  192. res = self.post(url = url, data = kwargs, timeout = 15)
  193. logger.debug(str(res))
  194. except Exception as e:
  195. logger.exception(e)
  196. def run(self):
  197. logger.info(repr(self.record))
  198. notify_payload = {
  199. 'sign': self.record.apiConf['mySign'],
  200. 'extOrderNo': self.record.orderNo,
  201. 'deviceCode': self.record.deviceCode,
  202. 'channel': self.record.channel,
  203. 'createTime': self.record.createTime,
  204. 'finishedTime': datetime.datetime.now(),
  205. 'errcode': ErrorCode.EXCEPTION,
  206. 'errmsg': u'系统异常'
  207. }
  208. err_code = ErrorCode.EXCEPTION
  209. err_msg = u'系统异常'
  210. try:
  211. device_result = self.deviceAdapter.start(package = self.record.package,
  212. openId = self.record.userId,
  213. attachParas = self.record.attachParas)
  214. err_code = device_result['rst']
  215. err_msg = device_result.get('desc', '')
  216. except ServiceException as e:
  217. logger.error(str(e))
  218. err_code = e.result.get('result')
  219. err_msg = e.result.get('description')
  220. except Exception as e:
  221. logger.exception(e)
  222. err_code = ErrorCode.EXCEPTION
  223. err_msg = e.message
  224. finally:
  225. try:
  226. self.record.errCode = err_code
  227. self.record.errMsg = err_msg
  228. self.record.save()
  229. except Exception as e:
  230. logger.exception(e)
  231. notify_payload.update({
  232. 'errcode': err_code,
  233. 'errmsg': u'系统异常' if err_code == ErrorCode.EXCEPTION else err_msg
  234. })
  235. return self.notify_api_client(url = self.record.notifyUrl, **notify_payload)
  236. class SmartBox(object):
  237. def __init__(self, device):
  238. # type: (DeviceDict)->None
  239. self._device = device
  240. self._vcard_id = None # 启动方式,None,表示扫码直接启动,如果是虚拟卡,表示用虚拟卡启动,如果是卷也是的
  241. super(SmartBox, self).__init__()
  242. @property
  243. def device(self):
  244. return self._device
  245. def check_dev_status(self, attachParas = None):
  246. pass
  247. def get_dev_info(self):
  248. pass
  249. def check_alarm(self, alarm):
  250. return ''
  251. def set_vcard_id(self, vcard_id):
  252. self._vcard_id = vcard_id
  253. def test(self, coins):
  254. raise NotImplementedError(u'设备未实现 `test`')
  255. def serial_test(self, payload):
  256. """
  257. 串口测试函数
  258. :param payload: 根据个性化配置不同 传入参数也不同
  259. :return:
  260. """
  261. raise NotImplementedError(u"该设备不支持")
  262. def start_device(self, package, openId, attachParas):
  263. raise NotImplementedError('cannot call `start_device` of base class SmartBox')
  264. def start(self, packageId, openId=None, attachParas={}):
  265. washConfig = self.device.get("washConfig", dict())
  266. package = washConfig.get(packageId)
  267. if not package:
  268. raise ServiceException({"result": 2, "description": u"未找到套餐,请联系经销商!"}) # new
  269. if self.device.bill_as_service_feature.support and self.device.bill_as_service_feature.on:
  270. return self.bill_as_service_start_device(package, openId, attachParas)
  271. else:
  272. return self.start_device(package, openId, attachParas)
  273. def start_temp_use(self, packageId, openId = None, attachParas = {}):
  274. package = self._device['tempWashConfig'].get(packageId)
  275. return self.start_device(package, openId, attachParas)
  276. def start_from_api(self, record):
  277. # type: (APIStartDeviceRecord)->Optional[Dict]
  278. if record.notifyUrl:
  279. return AsyncStartDevice(self, record).start()
  280. else:
  281. return self.start_device(package = record.package,
  282. openId = record.userId,
  283. attachParas = copy.deepcopy(record.attachParas))
  284. # 基础的停止接口,返回的是单位是分钟.此接口单独放到业务做,目的是有的设备结算按照分钟,有的设备按照秒。这样统一由业务决定
  285. def stop(self, port = None):
  286. devInfo = MessageSender.send(self.device, DeviceCmdCode.STOP_DEVICE, {'IMEI': self._device['devNo']})
  287. if devInfo.get('rst', 0) != 0:
  288. return JsonResponse({"result": 0, "description": u'网络连接异常,停止设备失败,请您重新尝试停掉设备', "payload": ''})
  289. devInfo['remainder_time'] = int(devInfo['remainder_time'] / 60.0)
  290. return devInfo
  291. def calc_stop_back_coins(self, totalFee, remainderTime, totalTime):
  292. refundFee = round(totalFee * (float(remainderTime) / totalTime), 2)
  293. if refundFee > totalFee:
  294. refundFee = totalFee
  295. if refundFee <= 0.0:
  296. refundFee = 0.00
  297. return refundFee
  298. def remote_charge_card(self, price, rechargeRecord = None):
  299. raise NotImplementedError(u'设备未实现 `remote_charge_card`')
  300. def recharge_card(self, cardNo, money, orderNo = None):
  301. # type:(str,RMB,str)->(dict, RMB)
  302. raise NotImplementedError(u'设备未实现 `recharge_ic_card`')
  303. def recharge_ic_card_realiable(self, cardNo, money, order_no):
  304. # type:(str,RMB,str)->dict
  305. raise NotImplementedError(u'设备未实现 `recharge_ic_card_realiable`')
  306. def response_card_balance(self, cardNo, balance):
  307. raise NotImplementedError(u'设备未实现 `response_card_balance`')
  308. def response_use_card(self, res, leftBalance, uartId = None):
  309. raise NotImplementedError(u'设备未实现 `response_use_card`')
  310. def get_dev_consume_count(self):
  311. pass
  312. def stop_charging_port(self, port):
  313. pass
  314. # 访问设备,获取设备信息
  315. def getDevInfo(self):
  316. pass
  317. # 解析获取设备信息的返回报文
  318. def analyze_event_data(self, data):
  319. return data
  320. def active_deactive_port(self, port, active):
  321. raise ServiceException({'result': 2, 'description': u'此设备不支持直接打开或者管理端口'})
  322. def lock_unlock_port(self, port, lock = True):
  323. raise ServiceException({'result': 2, 'description': u'此设备不支持直接禁用、解禁端口'})
  324. def is_port_can_use(self, port, canAdd=False):
  325. """
  326. 端口是否可用,如果canAdd为True,表示可以追加钱
  327. :param port:
  328. :param canAdd:
  329. :return:
  330. """
  331. try:
  332. portDict = self.get_port_status() # type: Optional[dict, None]
  333. if portDict is None:
  334. return True, ''
  335. port = str(port)
  336. if port in portDict:
  337. if portDict[port]['status'] == Const.DEV_WORK_STATUS_IDLE:
  338. return True, ''
  339. elif portDict[port]['status'] == Const.DEV_WORK_STATUS_FAULT:
  340. return False, u'该线路故障,暂时不能使用,请您使用其他线路'
  341. elif portDict[port]['status'] == Const.DEV_WORK_STATUS_WORKING:
  342. if canAdd:
  343. return True, ''
  344. return False, u'该线路正在工作,暂时不能继续使用,请您使用其他线路,或者等待该线路工作完毕'
  345. elif portDict[port]['status'] == Const.DEV_WORK_STATUS_FORBIDDEN:
  346. return False, u'该线路已被禁止使用,请您使用其他线路'
  347. elif portDict[port]['status'] == Const.DEV_WORK_STATUS_CONNECTED:
  348. return True, u''
  349. else:
  350. return False, u'线路未知状态,暂时不能使用'
  351. return False, u'未知端口,无法使用'
  352. except ServiceException, e:
  353. return False, e.result.get('description')
  354. except Exception, e:
  355. logger.error('error = %s' % e)
  356. return False, u'获取端口状态失败'
  357. def press_down_key(self, keyName):
  358. pass
  359. def get_device_function_by_key(self, keyName):
  360. return ''
  361. def get_port_status(self, force = False):
  362. return None
  363. def dealer_get_port_status(self):
  364. """
  365. 远程上分的时候获取端口状态
  366. :return:
  367. """
  368. return self.get_port_status()
  369. def get_dev_setting(self):
  370. return None
  371. def set_device_function(self, request, lastSetConf):
  372. pass
  373. def set_device_function_param(self, request, lastSetConf):
  374. pass
  375. def get_server_setting(self):
  376. return None
  377. def set_server_setting(self, payload):
  378. pass
  379. def count_down(self, request, dev, agent, group, devType, lastOpenId):
  380. return None
  381. def set_dev_fault(self, fault):
  382. pass
  383. def set_dev_disable(self, disable):
  384. pass
  385. def get_port_static_info(self, portDict):
  386. allPorts, usedPorts = 0, 0
  387. for v in portDict.values():
  388. allPorts += 1
  389. if (v.has_key('isStart') and v['isStart']) or (
  390. v.has_key('status') and v['status'] != Const.DEV_WORK_STATUS_IDLE):
  391. usedPorts += 1
  392. return allPorts, usedPorts, allPorts - usedPorts
  393. def make_random_cmdcode(self):
  394. return random.randint(DeviceCmdCode.RANDOM_START_CODE, DeviceCmdCode.RANDOM_END_CODE)
  395. def get_port_status_from_dev(self):
  396. raise NotImplementedError(u'设备未实现 `get_port_status_from_dev`')
  397. def get_part_info(self):
  398. return {}
  399. def get_port_info(self, port):
  400. return {}
  401. def async_update_portinfo_from_dev(self):
  402. class Sender(threading.Thread):
  403. def __init__(self, smartBox):
  404. super(Sender, self).__init__()
  405. self._smartBox = smartBox
  406. def run(self):
  407. try:
  408. result = self._smartBox.get_port_status_from_dev()
  409. # 将端口数据入库刷新到部件表中
  410. portFromDev = result.keys()
  411. portInDb = [obj.partNo for obj in
  412. Part.objects.filter(logicalCode = self._smartBox._device['logicalCode'],
  413. partName = 'port')]
  414. needAddPartNo = list(set(portFromDev) - set(portInDb))
  415. needAddPart = [{'logicalCode': self._smartBox._device['logicalCode'],
  416. 'ownerId': self._smartBox._device['ownerId'],
  417. 'partNo': portNo, 'partName': 'port',
  418. 'dateTimeAdded': datetime.datetime.now(),
  419. 'dateTimeUpdated': datetime.datetime.now()} for portNo in needAddPartNo]
  420. if needAddPart:
  421. Part.get_collection().insert(needAddPart)
  422. needRemovePartNo = list(set(portInDb) - set(portFromDev))
  423. Part.objects(logicalCode = self._smartBox._device['logicalCode'], partNo__in = needRemovePartNo,
  424. partName = 'port').delete()
  425. # 将其他部件入库到部件表中(目前支持其他部件的只有我们自己的一体板)
  426. partItems = self._smartBox.get_part_info()
  427. for partName, partInfo in partItems.items():
  428. if not partInfo['SN']:
  429. continue
  430. Part.upsert_part(logicalCode = self._smartBox._device['devNo'],
  431. ownerId = self._smartBox._device['ownerId'], partNo = partInfo['SN'],
  432. partName = partName, partType = '9999')
  433. except ServiceException:
  434. return
  435. except Exception as e:
  436. logger.exception(e)
  437. sender = Sender(self)
  438. sender.start()
  439. def format_port_using_detail(self, detailDict):
  440. portData = {}
  441. startTimeStr = detailDict.get('startTime', None)
  442. if startTimeStr is not None and "usedTime" not in detailDict:
  443. startTime = to_datetime(startTimeStr)
  444. usedTime = int(round((datetime.datetime.now() - startTime).total_seconds() / 60.0))
  445. portData['usedTime'] = usedTime
  446. elif detailDict.get("usedTime"):
  447. usedTime = detailDict.get("usedTime")
  448. portData['usedTime'] = usedTime
  449. else:
  450. usedTime = None
  451. if detailDict.has_key('needTime'):
  452. if detailDict['needTime'] == 999:
  453. portData['needTime'] = u'充满自停'
  454. else:
  455. portData['needTime'] = detailDict['needTime']
  456. if detailDict.has_key('leftTime') and (usedTime > 0):
  457. if detailDict['leftTime'] == 65535:
  458. portData['leftTime'] = 65535
  459. detailDict['usedTime'] = 0
  460. detailDict['actualNeedTime'] = 0
  461. else:
  462. portData['actualNeedTime'] = int(detailDict['leftTime']) + int(usedTime)
  463. if detailDict.has_key('needTime') and portData['actualNeedTime'] > detailDict['needTime']:
  464. portData['actualNeedTime'] = portData['needTime']
  465. portData['leftTime'] = detailDict['leftTime']
  466. if detailDict.has_key('coins'):
  467. if self.device.is_auto_refund:
  468. portData['leftMoney'] = round(
  469. float(detailDict['coins']) * int(detailDict['leftTime']) / (
  470. int(detailDict['leftTime']) + int(usedTime)), 2)
  471. portData['consumeMoney'] = round(
  472. float(detailDict['coins']) * int(portData['usedTime']) / (
  473. int(detailDict['leftTime']) + usedTime), 2)
  474. elif detailDict.has_key('leftTime'):
  475. portData['leftTime'] = detailDict['leftTime']
  476. if (not detailDict.has_key('leftTime')) and (usedTime is not None):
  477. if detailDict.has_key('needTime'):
  478. if isinstance(detailDict['needTime'], (int, float)):
  479. portData['leftTime'] = detailDict['needTime'] - usedTime
  480. if detailDict.has_key('coins') and float(detailDict['coins']) != 0: # 只有支持退费的设备才显示可退费数据
  481. if self.device.is_auto_refund:
  482. portData['leftMoney'] = round(
  483. float(detailDict['coins']) * portData['leftTime'] / detailDict['needTime'], 2)
  484. portData['consumeMoney'] = round(
  485. float(detailDict['coins']) * portData['usedTime'] / detailDict['needTime'], 2)
  486. if detailDict.has_key('openId'):
  487. user = MyUser.objects(openId=detailDict['openId'], groupId=self.device.groupId).first()
  488. if user:
  489. portData['nickName'] = user.nickname
  490. if detailDict.has_key('cardId'):
  491. if not detailDict.has_key('consumeType'):
  492. portData['consumeType'] = 'card'
  493. card = Card.objects.get(id=ObjectId(detailDict['cardId']))
  494. if card.cardName:
  495. portData['cardName'] = card.cardName
  496. portData['cardNo'] = card.cardNo
  497. # 注意,如果是IC卡,不支持余额回收,这里也不要显示出来
  498. if card.cardType == 'IC' and portData.has_key('leftMoney'):
  499. portData.pop('leftMoney')
  500. elif 'openId' in detailDict and ('consumeType' not in detailDict):
  501. if detailDict.get('vCardId'):
  502. portData['consumeType'] = 'mobile_vcard'
  503. else:
  504. portData['consumeType'] = 'mobile'
  505. elif 'consumeType' in detailDict:
  506. if detailDict['consumeType'] == 'coin':
  507. portData['consumeType'] = 'coin' # 硬币的都无法退费
  508. if portData.has_key('leftMoney'):
  509. portData.pop('leftMoney')
  510. elif detailDict['consumeType'] == 'server':
  511. portData['consumeType'] = 'mobile'
  512. # 做个特殊处理
  513. if portData.has_key('needTime'):
  514. if portData['needTime'] == '999' or portData['needTime'] == '充满自停':
  515. portData['needTime'] = u'充满自停'
  516. portData.pop('leftTime', None)
  517. else:
  518. portData['needTime'] = u'%s分钟' % portData['needTime']
  519. # 如果剩余时间为65535,表示未接插头
  520. if portData.has_key('leftTime') and portData['leftTime'] == 65535:
  521. portData['leftTime'] = u'(线路空载)'
  522. portData['usedTime'] = 0
  523. portData['needTime'] = 0
  524. detailDict.update(portData)
  525. for k, v in detailDict.items():
  526. if v < 0:
  527. detailDict.pop(k)
  528. # 因为前台显示的开始时间如果带年,就显示不下,这里做个切割
  529. if detailDict.has_key('startTime') and detailDict['startTime'].count('-') == 2:
  530. detailDict['startTime'] = to_datetime(detailDict['startTime']).strftime('%m-%d %H:%M:%S')
  531. return detailDict
  532. def get_port_using_detail(self, port, ctrInfo, isLazy=False):
  533. """
  534. 获取设备端口的详细信息
  535. :param port:
  536. :param ctrInfo:
  537. :param isLazy: 是否延时加载设备信息
  538. :return:
  539. """
  540. detailDict = ctrInfo.get(str(port), {})
  541. try:
  542. if isLazy: # 需要点击再次点击按钮加载
  543. portInfo = {'isLazy': True}
  544. else:
  545. portInfo = self.get_port_info(str(port))
  546. skipPipelineProcessing = portInfo.get('skipPipelineProcessing', False)
  547. if skipPipelineProcessing:
  548. detailDict.update(portInfo)
  549. return detailDict
  550. # 有的主机报的信息leftTime错误
  551. if portInfo.has_key('leftTime') and portInfo['leftTime'] < 0:
  552. portInfo.pop('leftTime')
  553. detailDict.update(portInfo)
  554. except Exception, e:
  555. logger.exception('get port info from dev=%s err=%s' % (self.device.devNo, e))
  556. return detailDict
  557. return self.format_port_using_detail(detailDict)
  558. def support_count_down(self, openId = None, port = None):
  559. """
  560. 是否支持倒计时界面
  561. :return:
  562. """
  563. return False
  564. def get_duration(self, package):
  565. if 'time' not in package or not package['time']:
  566. return 0
  567. unit = package.get('unit', u'分钟')
  568. reserved_time = float(package['time'])
  569. if unit == u'秒':
  570. reserved_time = int(reserved_time)
  571. elif unit == u'分钟':
  572. reserved_time = int(reserved_time * 60)
  573. elif unit == u'小时':
  574. reserved_time = int(reserved_time * 60 * 60)
  575. elif unit == u'天':
  576. reserved_time = int(reserved_time * 24 * 60 * 60)
  577. else:
  578. # 其他都算分钟吧
  579. reserved_time = int(reserved_time * 60)
  580. return reserved_time
  581. def translate_funcode(self, funCode):
  582. return ''
  583. def translate_event_cmdcode(self, cmdCode):
  584. return ''
  585. def translate_smartbox_cmd(self, cmdParas):
  586. cmd = str(cmdParas['cmd'])
  587. descList = []
  588. temp = Const.CMD_CMD_TRANSLATE_DICT.get(cmd, '')
  589. if temp:
  590. descList.append(temp)
  591. if cmdParas.has_key('funCode'):
  592. temp = self.translate_funcode(cmdParas['funCode'])
  593. if temp:
  594. descList.append(temp)
  595. return ' '.join(descList)
  596. def translate_server_cmd(self, cmdParas):
  597. descList = []
  598. for k, v in cmdParas.items():
  599. if k == 'cmd':
  600. temp = Const.CMD_CMD_TRANSLATE_DICT.get(v, '')
  601. if temp:
  602. descList.append(temp)
  603. elif k in Const.CMD_PARAS_TRANSLATE_DICT:
  604. temp = Const.CMD_PARAS_TRANSLATE_DICT.get(k, '')
  605. if temp:
  606. descList.append('%s:%s' % (temp, v))
  607. elif k == 'data' and v:
  608. dataDict = self.analyze_event_data(v)
  609. if dataDict.has_key('cmdCode'):
  610. cmdDesc = self.translate_event_cmdcode(dataDict['cmdCode'])
  611. descList.append(cmdDesc)
  612. for dataKey, dataValue in dataDict.items():
  613. if dataKey == 'cmdCode':
  614. continue
  615. dataDesc = Const.CMD_DATA_TRANSLATE_DICT.get(dataKey)
  616. if dataDesc:
  617. temp = '%s:%s' % (dataDesc, dataValue)
  618. descList.append(temp)
  619. return ' '.join(descList)
  620. # duration 单位:秒
  621. def send_dev_runtime(self, openId, duration):
  622. dev = self._device
  623. now_time = datetime.datetime.now()
  624. devInfo = MessageSender.send(dev, DeviceCmdCode.PAY_MONEY, {'IMEI': dev['devNo'], 'duration': duration,
  625. 't': int(time.mktime(now_time.timetuple()))})
  626. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  627. current_dev_type_name = dev['devType']['name']
  628. if current_dev_type_name == u'其他':
  629. current_dev_type_name = u'自助设备'
  630. if devInfo['rst'] == -1:
  631. description = u'当前' + current_dev_type_name + u'正在玩命找网络,您的金币还在,重试不会扣款,建议您试试旁边其他设备,或者试试投硬币,或者稍后再试哦'
  632. raise ServiceException({'result': 2, 'description': description})
  633. elif devInfo['rst'] == 1:
  634. description = u'当前' + current_dev_type_name + u'正在忙,无响应,您的金币还在,重试不会扣款,请试试其他线路,或者请稍后再试哦'
  635. raise ServiceException({'result': 2, 'description': description})
  636. else:
  637. description = u'系统无响应'
  638. raise ServiceException({'result': 2, 'description': description})
  639. start_timestamp = int(time.time())
  640. devInfo['finishedTime'] = start_timestamp + duration
  641. Device.update_dev_control_cache(dev['devNo'],
  642. {
  643. 'openId': openId,
  644. 'startTime': timestamp_to_dt(start_timestamp).strftime('%Y-%m-%d %H:%M:%S'),
  645. 'needTime': duration,
  646. 'status': Const.DEV_WORK_STATUS_WORKING,
  647. 'finishedTime': devInfo['finishedTime']
  648. })
  649. return devInfo
  650. @property
  651. def isHaveStopEvent(self):
  652. return False
  653. def notify_low_power_to_user(self, user, dealer, port, delay, lowPower = None):
  654. """
  655. 用户的低功率检测 设备启动之后 延后一段时间直接对设备的当前的端口功率进行 主板查询 如果查询结果小于一定值 告警用户
  656. :param user: 用户
  657. :param dealer: 经销商( 无用参数 需要干掉或者删除)
  658. :param port: 延迟检测的端口
  659. :param delay: 延迟秒数
  660. :param lowPower: 检测的功率 可传参,如果为空 直接从设备侧获取
  661. :return:
  662. """
  663. from taskmanager.mediator import task_caller
  664. if lowPower is None:
  665. lowPower = self.device.get("otherConf", dict()).get("lowPowerDetectionPower", 0)
  666. managerialOpenId = user.managerialOpenId
  667. task_caller(
  668. "report_to_user_low_power",
  669. delay = delay,
  670. devNo = self.device.devNo,
  671. line = port,
  672. power = lowPower,
  673. managerialOpenId = managerialOpenId,
  674. dealerId = self.device.ownerId
  675. )
  676. def record_serial_port_for_timeout(self):
  677. pass
  678. # 检查串口超时,电川的板子出现启动命令串口超时,但是获取端口状态却正常。(现网已经抓到几个案例,此函数用于规避止血,但是并不能完全解决)
  679. # 只能解决获取端口状态命令OK,但是启动命令不行的这种情况。
  680. def check_serial_port_for_startcmd(self, port):
  681. # 首先记录到数据库表,用于查询现网问题,然后进行分析
  682. try:
  683. portsInfo = self.get_port_status_from_dev()
  684. ExceptionLog.log(
  685. user = self.device.devNo, exception = '',
  686. extra = {
  687. 'action': 'check_serial_port_for_startcmd:get_port_status_from_dev',
  688. 'result': 'success', 'portsInfo': str(portsInfo)})
  689. except Exception as e:
  690. ExceptionLog.log(user = self.device.devNo, exception = traceback.format_exc(),
  691. extra = {
  692. 'action': 'check_serial_port_for_startcmd:get_port_status_from_dev',
  693. 'result': 'failure'
  694. })
  695. raise e
  696. portStatus = portsInfo.get(str(port), {}).get('status', Const.DEV_WORK_STATUS_IDLE)
  697. if portStatus == Const.DEV_WORK_STATUS_WORKING: # 如果端口处于忙的状态,默认命令成功,会扣费
  698. return
  699. raise ServiceException(
  700. {'result': 2, 'description': u'设备忙无响应。本次操作没有扣除您的金额,您可以稍后重试或者试试附近其他设备。'})
  701. def handle_out_start_error(self, port):
  702. """
  703. 处理 启动错误 超过次数上限
  704. :param port: 端口
  705. :return:
  706. """
  707. pass
  708. def handle_clear_start_error(self, port):
  709. """
  710. 清除启动错误之后 的后续操作
  711. :param port: 端口
  712. :return:
  713. """
  714. pass
  715. @property
  716. def show_pay_unit(self):
  717. """
  718. 前台显示付费的时候,目前有不同的客户希望 显示不同的单位 有的显示金币 有的显示元, 这个地方处理下
  719. :return:
  720. """
  721. return u"元"
  722. def get_many_port_info(self, portList):
  723. return None
  724. def check_order_state(self, openId):
  725. return
  726. def start_device_realiable(self, order):
  727. # type:(ConsumeRecord)->dict
  728. raise NotImplementedError('cannot call `start_device_realiable` of base class SmartBox')
  729. def calc_elec_fee(self, spend_elec):
  730. group = Group.objects.get(id = self.device['groupId'])
  731. return float(group.otherConf.get('elecFee', 0)) * spend_elec
  732. def isHaveCallback(self):
  733. return False
  734. def do_callback(self, *args, **kwargs):
  735. """
  736. 针对状态机确认成功付款后的状态回调使用
  737. """
  738. raise NotImplementedError(u'设备未实现 `dev_callback`')
  739. @staticmethod
  740. def check_device_features(device_features, no_features_to_return = None):
  741. def warpper(func):
  742. @wraps(func)
  743. def inner(self, *args, **kwargs):
  744. retult = False
  745. features = self.device.devType.get("features", {})
  746. if isinstance(device_features, (list, tuple)):
  747. for item in device_features:
  748. if features.get(item) and features.get(item) is True:
  749. retult = True
  750. break
  751. elif isinstance(device_features, dict):
  752. if set(device_features.items()) & set(features.items()):
  753. retult = True
  754. elif isinstance(device_features, (str, unicode, int)):
  755. if features.get(device_features) and features.get(device_features) is True:
  756. retult = True
  757. if retult:
  758. return func(self, *args, **kwargs)
  759. else:
  760. return no_features_to_return
  761. return inner
  762. return warpper
  763. @staticmethod
  764. def life_cycle(before=lambda self, *args, **kwargs: None, after=lambda *args: args):
  765. def warpper(func):
  766. @wraps(func)
  767. def inner(self, *args, **kwargs):
  768. before(self, *args, **kwargs)
  769. data = func(self, *args, **kwargs)
  770. result = after(self, data)
  771. return result or data
  772. return inner
  773. return warpper
  774. def get_signal(self):
  775. result = MessageSender.send(device = self._device, cmd = DeviceCmdCode.GET_DEVINFO,
  776. payload = {'IMEI': self._device['devNo']},
  777. timeout = MQTT_TIMEOUT.SHORT)
  778. return result
  779. def notify_user(self, order, cardStart = False):
  780. pass
  781. def deal_order_money(self, order):
  782. # type: (ConsumeRecord) -> ConsumeRecord
  783. raise NotImplementedError(u'设备未实现 `deal_order_money`')
  784. @property
  785. def support_monthly_package(self):
  786. return False
  787. def _notify_user(self, user, templateName, url = None, **kwargs):
  788. if not user or not user.managerialOpenId:
  789. logger.warning('user is none or managerialOpenId is not exists.')
  790. return
  791. from taskmanager.mediator import task_caller
  792. task_caller('send_msg_to_user_via_wechat', productId = user.productAgentId, openId = user.managerialOpenId,
  793. templateName = templateName, url = url, **kwargs)
  794. def notify_service_start_to_user(self, order, user=None):
  795. # type: (ConsumeRecord, MyUser)->None
  796. if not order.user:
  797. return
  798. dealer = self.device.owner
  799. if dealer.showServicePhone:
  800. remark = u'感谢您的使用!有任何问题请联系客服(联系电话{})!'.format(dealer.service_phone)
  801. else:
  802. remark = u'感谢您的使用!有任何问题请联系客服!'
  803. if order.port != Const.NO_PORT:
  804. service = unicode(
  805. '{}({}/{}-端口{})'.format(self.device.majorDeviceType, self.device.group.address[0:32],
  806. self.device.logicalCode,
  807. order.port))
  808. else:
  809. service = unicode(
  810. '{}({}/{})'.format(self.device.majorDeviceType, self.device.group.address[0:32],
  811. self.device.logicalCode))
  812. kw = {
  813. 'WechatUserManagerApp': {
  814. 'title': u'尊敬的用户,设备已经成功启动',
  815. 'service': service,
  816. 'time': order.device_start_time,
  817. 'remark': remark
  818. },
  819. 'WechatUserSubscribeManagerApp': {
  820. 'title': u'尊敬的用户,设备已经成功启动',
  821. 'service': service,
  822. 'time': order.device_start_time,
  823. 'remark': remark
  824. }
  825. }
  826. user = order.user if not user else user # type: MyUser
  827. if not notify_user:
  828. logger.warning('user<openId={},groupId={}> is not exists.'.format(order.openId, order.groupId))
  829. return
  830. url = self.custom_push_url(order, user)
  831. self._notify_user(user, 'service_start', url = url, **kw)
  832. def notify_service_end_to_user(self, order, url=None): # type:(ConsumeRecord, Optional[str, None]) -> None
  833. service_name = order.package.name or u"充电"
  834. title_list = [
  835. {u'': u'[{}]结束,感谢您的使用!'.format(service_name)},
  836. {u'设备编号': self.device.logicalCode}
  837. ]
  838. port = order.port
  839. if port:
  840. title_list.extend([{u'设备端口': port}, {u'设备地址': order.address}])
  841. else:
  842. title_list.extend([{u'设备地址': order.address}])
  843. reason = order.service.reason
  844. if reason:
  845. title_list.extend([{u'结束原因': reason}])
  846. dealer = order.owner
  847. if dealer.showServicePhone:
  848. remark = u'客服联系电话:{}'.format(dealer.service_phone)
  849. else:
  850. remark = u'我们竭诚为您服务,有任何问题请联系客服!'
  851. finished_time = order.device_end_time
  852. if not finished_time:
  853. finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  854. agent = dealer.my_agent
  855. device = order.device
  856. if agent.customizedUserSubGzhAllowable:
  857. if port:
  858. service = u"{}服务({}-端口{})".format(device.majorDeviceType, device.logicalCode, order.port)
  859. if len(service) > 20:
  860. service = u'{}服务({})'.format(device.majorDeviceType, device.logicalCode)
  861. if len(service) > 20:
  862. service = u'{}服务'.format(device.majorDeviceType)
  863. else:
  864. service = u'{}服务({})'.format(service_name, device.logicalCode)
  865. if len(service) > 20:
  866. service = u'{}服务'.format(device.majorDeviceType)
  867. kw = {
  868. 'service': service,
  869. 'finishTime': finished_time,
  870. 'remark': remark,
  871. }
  872. else:
  873. kw = {
  874. 'title': make_title_from_dict(title_list),
  875. 'service': u'{}服务'.format(service_name),
  876. 'finishTime': finished_time,
  877. 'remark': remark
  878. }
  879. kw.update({'url': url or settings.SERVER_END_BASE_SSL_URL + '/user/index.html?v=1.0.31#/user/consume'})
  880. notify_user(order.openId, order.ownerId, 'service_complete', **kw)
  881. def stop_by_order(self, port, orderNo):
  882. raise NotImplementedError(u'设备未实现 `stop_by_order`')
  883. def isHaveFaultHandle(self):
  884. return False
  885. def faultHandle(self, **kw):
  886. raise NotImplementedError(u'设备未实现 `FaultHandle`')
  887. def format_upload_power(self, power):
  888. return power
  889. def force_stop_order(self,order, **kwargs):
  890. raise NotImplementedError(u'设备未实现 `force_stop_order`')
  891. def get_ports_info(self, **kw):
  892. raise NotImplementedError(u'设备未实现 `get_ports_info`')
  893. def switch_bill_as_service(self, onOrOff):
  894. Device.get_collection().update_one(
  895. {'devNo': self.device.devNo},
  896. {
  897. '$set':
  898. {
  899. 'devType.features.billAsService.on': onOrOff
  900. }
  901. })
  902. Device.invalid_device_cache(self.device.devNo)
  903. def custom_push_url(self, order, user, **kw):
  904. return concat_user_center_entry_url(agentId=user.productAgentId, redirect=concat_front_end_url(
  905. uri='/user/index.html#/user/consumeDetail?id={}'.format(str(order.id))))
  906. def bill_as_service_start_device(self, package, openId, attachParas):
  907. charge_unit = self.device.bill_as_service_feature.charge_unit
  908. price = package['price']
  909. elecCharge = charge_unit["elecCharge"]
  910. serviceCharge = charge_unit["serviceCharge"]
  911. unitPrice = float(elecCharge) + float(serviceCharge)
  912. actualTime = round(price / unitPrice, 2)
  913. package['time'] = actualTime
  914. return self.start_device(package, openId, attachParas)
  915. def prepare_package(self, packageId, attachParas, startType=StartDeviceType.ON_LIEN):
  916. """
  917. 预备套餐模型 普通设备直接由设备解释
  918. """
  919. isTemporary = 'isTemporary' in attachParas and attachParas['isTemporary'] is True
  920. package = self.device.package(packageId, isTemporary=isTemporary)
  921. return package
  922. def prepare_serviced_info(self, package, attach_paras): # type:(dict, dict)->dict
  923. rv = {'chargeIndex': attach_paras['chargeIndex']}
  924. return rv
  925. def get_service_fee_info(self):
  926. ruleList = []
  927. for packageId, rule in self.device['washConfig'].items():
  928. item = {
  929. 'id': packageId,
  930. 'name': rule['name'],
  931. 'coins': rule['coins'],
  932. 'price': rule.get('price', rule['coins']),
  933. 'time': rule.get('time', 20),
  934. 'description': rule.get('description', ''),
  935. 'imgList': rule.get('imgList', []),
  936. 'unit': rule.get('unit', u'分钟'),
  937. 'switch': rule.get('switch', True),
  938. }
  939. if 'sn' in rule:
  940. item['sn'] = rule['sn']
  941. ruleList.append(item)
  942. billAsService = self.device.bill_as_service_feature
  943. devData = {
  944. 'id': self.device.devNo,
  945. 'isManager': True,
  946. 'groupName': self.device.group['groupName'],
  947. 'groupNumber': self.device['groupNumber'],
  948. 'devNo': self.device.devNo,
  949. 'devTypeName': self.device.devTypeName,
  950. 'devTypeCode': self.device.devTypeCode
  951. }
  952. if "displaySwitchs" in self.device.my_obj.otherConf:
  953. displaySwitchs = self.device.my_obj.otherConf.get('displaySwitchs')
  954. else:
  955. displaySwitchs = {
  956. 'displayCoinsSwitch': True,
  957. 'displayTimeSwitch': True,
  958. 'displayPriceSwitch': True,
  959. "setPulseAble": False,
  960. "setBasePriceAble": False
  961. }
  962. ruleList = sorted(ruleList, key=lambda x: (x.get('sn'), x.get('id')))
  963. return {
  964. 'ruleList': ruleList,
  965. 'billAsService': billAsService,
  966. 'devData': devData,
  967. 'displaySwitchs': displaySwitchs,
  968. }
  969. def set_service_fee_info(self, payload):
  970. elecCharge = round(float(payload['billAsService'].get('elecCharge', 0.5)), 2)
  971. serviceCharge = round(float(payload['billAsService'].get('serviceCharge', 0.5)), 2)
  972. # 显示展示给用户信息部分
  973. if payload.get('displaySwitchs'):
  974. displaySwitchs = payload.get('displaySwitchs')
  975. else:
  976. displaySwitchs = {'displayCoinsSwitch': False,
  977. 'displayTimeSwitch': True,
  978. 'displayPriceSwitch': True,
  979. 'setPulseAble': False,
  980. 'setBasePriceAble': False}
  981. # 套餐部分
  982. packages = payload.get('packages')
  983. # 调整SN套餐顺序
  984. for i, item in enumerate(packages):
  985. item['sn'] = i
  986. # 套餐id 去重
  987. existIds = list(map(lambda _: _.get('id'), packages))
  988. washConfig = {}
  989. for rule in packages:
  990. if 'price' in rule:
  991. if not is_number(rule['price'] and is_number(rule.get('time', 0))):
  992. raise InvalidParameter(u'金币数目或者时间或者价格必须是数字')
  993. if RMB(rule['price']) >= self.device.owner.maxPackagePrice:
  994. raise InvalidParameter(u'套餐金额超限')
  995. if len(rule['name']) > 20:
  996. raise InvalidParameter(u'套餐名字只能取1-20位')
  997. if 'id' in rule:
  998. ruleId = rule['id']
  999. else:
  1000. ruleId = list(set(range(1, 71)) - set([int(ruleId) for ruleId in washConfig.keys()]) - set(existIds))[0]
  1001. washConfig[str(ruleId)] = {
  1002. 'billingMethod': CONSUMETYPE.BILL_AS_SERVICE,
  1003. 'name': rule['name'],
  1004. 'coins': float(rule['price']),
  1005. 'price': float(rule['price']),
  1006. 'time': float(rule.get('time', 0)),
  1007. 'description': rule.get('description', ''),
  1008. 'imgList': rule.get('imgList', []),
  1009. 'unit': rule.get('unit', u'分钟'),
  1010. 'switch': rule.get('switch', True),
  1011. 'sn': rule.get('sn')
  1012. }
  1013. self.device.update_device_obj(**{
  1014. 'washConfig': washConfig,
  1015. 'otherConf.displaySwitchs': displaySwitchs,
  1016. 'devType.features.billAsService.elecCharge': elecCharge,
  1017. 'devType.features.billAsService.serviceCharge': serviceCharge
  1018. })
  1019. dealer = self.device.owner
  1020. dealer.defaultWashConfig[self.device.devTypeId] = self.device['washConfig'].values()
  1021. dealer.save()
  1022. def start_device_swap(self, portNo):
  1023. # type:(ConsumeRecord)->dict
  1024. raise NotImplementedError('cannot call `start_device_swap` of base class SmartBox')
  1025. @property
  1026. def support_device_package(self):
  1027. return False
  1028. def get_reg_model(self, dealer, devTypeId, isTemp=False, **kw):
  1029. payload = {}
  1030. if devTypeId in dealer.defaultWashConfig:
  1031. payload = dealer.defaultWashConfig[devTypeId]
  1032. return payload
  1033. def reg_model(self, **kw):
  1034. raise NotImplementedError('cannot call `reg_model` of base class SmartBox')
  1035. def format_device_package(self, isTemp=False, **kw):
  1036. def __generate_id(ids):
  1037. i = 1
  1038. while True:
  1039. if str(i) in ids:
  1040. i += 1
  1041. else:
  1042. return str(i)
  1043. def __formart_ruleList():
  1044. packageList = kw['serviceData']
  1045. for item in packageList:
  1046. item["sn"] = packageList.index(item)
  1047. ids = [str(rule['id']) for rule in packageList if 'id' in rule]
  1048. washConfig = {}
  1049. for i, rule in enumerate(packageList):
  1050. ruleId = str(rule.get('id', ''))
  1051. if not ruleId:
  1052. ruleId = __generate_id(ids)
  1053. ids.append(ruleId)
  1054. washConfig[ruleId] = {}
  1055. if 'switch' in rule:
  1056. washConfig[ruleId].update({'switch': rule['switch']})
  1057. if 'billingMethod' in rule:
  1058. washConfig[ruleId].update({'billingMethod': rule['billingMethod']})
  1059. if 'price' in rule:
  1060. washConfig[ruleId].update({'price': round(float(rule['price']), 2) or 0})
  1061. if 'coins' in rule:
  1062. washConfig[ruleId].update({'coins': round(float(rule['coins']), 2) or 0})
  1063. if 'time' in rule:
  1064. washConfig[ruleId].update({'time': rule['time'] or 0})
  1065. if 'name' in rule:
  1066. washConfig[ruleId].update({'name': rule['name'] or '套餐{}'.format(i + 1)})
  1067. if 'unit' in rule:
  1068. washConfig[ruleId].update({'unit': rule['unit']})
  1069. if 'sn' in rule:
  1070. washConfig[ruleId].update({'sn': rule['sn']})
  1071. if 'autoStop' in rule:
  1072. washConfig[ruleId]['autoStop'] = rule['autoStop']
  1073. if 'autoRefund' in rule:
  1074. washConfig[ruleId]['autoRefund'] = rule['autoRefund']
  1075. if 'minAfterStartCoins' in rule:
  1076. washConfig[ruleId]['minAfterStartCoins'] = rule['minAfterStartCoins']
  1077. if 'minFee' in rule:
  1078. washConfig[ruleId]['minFee'] = rule['minFee']
  1079. if isTemp:
  1080. pass
  1081. # 检验部分
  1082. if RMB(rule.get('price') or 0) > self.device.owner.maxPackagePrice:
  1083. raise ServiceException(
  1084. {'result': 0, 'description': '套餐( {} )金额超限'.format(rule['name']), 'payload': {}})
  1085. return washConfig
  1086. def __formart_displaySwitchs():
  1087. return kw.get('displaySwitchs', {'displayCoinsSwitch': True,
  1088. 'displayTimeSwitch': True,
  1089. 'displayPriceSwitch': True,
  1090. 'setPulseAble': False,
  1091. 'setBasePriceAble': False})
  1092. washConfig = __formart_ruleList()
  1093. displaySwitchs = __formart_displaySwitchs()
  1094. return washConfig, displaySwitchs
  1095. def dealer_show_package(self, isTemp=False, **kw):
  1096. def get_rule_list():
  1097. if isTemp:
  1098. config = self.device.get('tempWashConfig', {})
  1099. else:
  1100. config = self.device['washConfig']
  1101. ruleList = []
  1102. for packageId, rule in config.items():
  1103. item = {
  1104. 'id': packageId
  1105. }
  1106. if 'switch' in rule:
  1107. item['switch'] = rule['switch']
  1108. if 'name' in rule:
  1109. item['name'] = rule['name']
  1110. if 'billingMethod' in rule:
  1111. item['billingMethod'] = rule['billingMethod']
  1112. if 'coins' in rule:
  1113. item['coins'] = rule['coins']
  1114. if 'price' in rule:
  1115. item['price'] = rule['price']
  1116. if 'time' in rule:
  1117. item['time'] = rule['time']
  1118. if 'description' in rule:
  1119. item['description'] = rule['description']
  1120. if 'unit' in rule:
  1121. item['unit'] = rule['unit']
  1122. if 'imgList' in rule:
  1123. item['imgList'] = rule['imgList']
  1124. if 'sn' in rule:
  1125. item['sn'] = rule['sn']
  1126. if 'autoStop' in rule:
  1127. item['autoStop'] = rule['autoStop']
  1128. if 'minAfterStartCoins' in rule:
  1129. item['minAfterStartCoins'] = rule['minAfterStartCoins']
  1130. if 'minFee' in rule:
  1131. item['minFee'] = rule['minFee']
  1132. ruleList.append(item)
  1133. return sorted(ruleList, key=lambda x: (x.get('sn'), x.get('id')))
  1134. def get_display_switchs():
  1135. if "displaySwitchs" in self.device["otherConf"]:
  1136. displaySwitchs = self.device["otherConf"].get('displaySwitchs')
  1137. else:
  1138. displaySwitchs = {'displayCoinsSwitch': True,
  1139. 'displayTimeSwitch': True,
  1140. 'displayPriceSwitch': True,
  1141. "setPulseAble": False,
  1142. "setBasePriceAble": False}
  1143. return displaySwitchs
  1144. displaySwitchs = get_display_switchs()
  1145. try:
  1146. ruleList = get_rule_list()
  1147. return {'ruleList': ruleList, 'displaySwitchs': displaySwitchs}
  1148. except:
  1149. return {'displaySwitchs': displaySwitchs, 'ruleList': []}
  1150. def user_show_package(self, isTemp=False):
  1151. group = self.device.group # type: GroupDict
  1152. # 探测是否地址为免费活动组,默认为否
  1153. is_free_service = group.is_free
  1154. if isTemp:
  1155. config = self.device.get('tempWashConfig', {})
  1156. else:
  1157. config = self.device['washConfig']
  1158. if "displaySwitchs" in self.device.otherConf:
  1159. displaySwitchs = self.device.otherConf.get('displaySwitchs')
  1160. displaySwitchs = dict(filter(lambda x: "display" in x[0], displaySwitchs.items()))
  1161. else:
  1162. displaySwitchs = {
  1163. 'displayCoinsSwitch': True,
  1164. 'displayTimeSwitch': True,
  1165. 'displayPriceSwitch': True
  1166. }
  1167. packages = []
  1168. for packageId, rule in config.items():
  1169. # 没有启用的套餐 直接掠过
  1170. if not rule.get("switch", True):
  1171. continue
  1172. item = {
  1173. 'id': packageId
  1174. }
  1175. if 'name' in rule:
  1176. item['name'] = rule['name']
  1177. if 'coins' in rule:
  1178. item['coins'] = rule['coins']
  1179. if 'price' in rule:
  1180. item['price'] = rule['price']
  1181. if 'time' in rule:
  1182. item['time'] = rule['time']
  1183. if 'description' in rule:
  1184. item['description'] = rule['description']
  1185. if 'unit' in rule:
  1186. item['unit'] = rule['unit']
  1187. if 'sn' in rule:
  1188. item['sn'] = rule['sn']
  1189. if 'minFee' in rule and rule['minFee'] and float(rule['minFee']) > 0:
  1190. item.update({'minFee': rule.get('minFee')})
  1191. if 'minAfterStartCoins' in rule and rule['minAfterStartCoins'] and float(rule['minAfterStartCoins']) > 0:
  1192. item.update({'minAfterStartCoins': rule.get('minAfterStartCoins')})
  1193. if is_free_service:
  1194. item.update({'description': '当前处于免费时段'})
  1195. item.update(displaySwitchs)
  1196. packages.append(item)
  1197. return sorted(packages, key=lambda x: (x.get('sn'), x.get('id')))
  1198. def get_customize_score_unit(self):
  1199. return None
  1200. def do_heartbeat(self, value, ts):
  1201. pass
  1202. class OnlineSmartBox(SmartBox):
  1203. def __init__(self, device):
  1204. super(OnlineSmartBox, self).__init__(device)
  1205. @testcase_point()
  1206. def check_dev_status(self, attachParas = None):
  1207. """
  1208. 如果超过两个心跳周期没有报心跳,并且最后一次更新时间在2个小时内,需要从设备获取状态
  1209. 否则以缓存状态为准。
  1210. :param attachParas:
  1211. :return:
  1212. """
  1213. if not self.device.need_fetch_online:
  1214. raise ServiceException(
  1215. {'result': 2, 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_FAIL)})
  1216. if self.device.online == DeviceOnlineStatus.DEV_STATUS_ONLINE:
  1217. retry = 3
  1218. timeout = 12
  1219. else:
  1220. retry = 2
  1221. timeout = 10
  1222. operation_result = MessageSender.send(device = self.device, cmd = DeviceCmdCode.GET_DEVINFO, payload = {
  1223. 'IMEI': self.device.devNo,
  1224. 'fields': ['signal', 'pulse_open', 'board_volt', 'board_valid']
  1225. }, timeout = timeout, retry = retry)
  1226. if operation_result['rst'] != ErrorCode.DEVICE_SUCCESS:
  1227. if operation_result['rst'] == ErrorCode.DEVICE_CONN_FAIL:
  1228. raise ServiceException(
  1229. {
  1230. 'result': 2,
  1231. 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_CHECK_FAIL)
  1232. })
  1233. else:
  1234. raise ServiceException(
  1235. {
  1236. 'result': 2,
  1237. 'description': u'检测设备状态失败({})'.format(operation_result['rst'])
  1238. })
  1239. else:
  1240. if 'pulse_open' in operation_result and (not operation_result['pulse_open']):
  1241. raise ServiceException(
  1242. {
  1243. 'result': 2,
  1244. 'description': u'检测设备状态失败({})'.format(ErrorCode.PULSE_IS_CLOSE)
  1245. })
  1246. if 'board_valid' in operation_result and 'board_volt' in operation_result and operation_result[
  1247. 'board_valid'] != 2:
  1248. if operation_result['board_volt'] != operation_result['board_valid']:
  1249. raise ServiceException(
  1250. {
  1251. 'result': 2,
  1252. 'description': u'当前设备正在工作,请稍后再试'
  1253. })
  1254. def test(self, coins):
  1255. now_time = datetime.datetime.now()
  1256. return MessageSender.send(device = self.device, cmd = DeviceCmdCode.PAY_MONEY, payload = {
  1257. 't': int(time.mktime(now_time.timetuple())),
  1258. 'duration': coins * 60,
  1259. 'app_pay': coins
  1260. })
  1261. @testcase_point()
  1262. def start_device(self, package, openId, attachParas):
  1263. pay_count = int(package['coins'])
  1264. result = MessageSender.net_pay(self.device, pay_count, timeout = MQTT_TIMEOUT.START_DEVICE)
  1265. if result['rst'] == ErrorCode.DEVICE_CONN_FAIL:
  1266. raise DeviceNetworkTimeoutError()
  1267. elif result['rst'] != ErrorCode.DEVICE_SUCCESS:
  1268. logger.debug('OnlineSmartBox() failed to start, result was=%s' % (json.dumps(result),))
  1269. raise ServiceException({'result': 2, 'description': DeviceErrorCodeDesc.get(result['rst'])})
  1270. try:
  1271. duration = self.get_duration(package)
  1272. result['finishedTime'] = (int(time.time()) + duration)
  1273. Device.update_dev_control_cache(self._device['devNo'],
  1274. {
  1275. 'status': Const.DEV_WORK_STATUS_WORKING,
  1276. 'finishedTime': result['finishedTime']
  1277. })
  1278. except Exception as e:
  1279. logger.exception('error = %s' % e)
  1280. return result
  1281. def get_total_coin(self):
  1282. result = MessageSender.send(self.device, DeviceCmdCode.GET_DEVINFO,
  1283. {'cmd': DeviceCmdCode.GET_DEVINFO, 'IMEI': self._device['devNo']})
  1284. if result['rst'] != ErrorCode.DEVICE_SUCCESS:
  1285. logger.debug('OnlineSmartBox() failed to get total coin, result was=%s' % (json.dumps(result),))
  1286. description = u'当前设备信号弱没有响应,请您稍后重试。'
  1287. raise ServiceException({'result': 2, 'description': description})
  1288. if not result.has_key('total_coin'):
  1289. raise ServiceException({'result': 2, 'description': u'当前设备暂时不支持获取总的硬币数目,待版本自动升级后,会支持'})
  1290. return result['total_coin']
  1291. def check_alarm(self, alarm):
  1292. if alarm.faultCode == FAULT_CODE.OFFLINE:
  1293. dev_info = MessageSender.send(device = self.device, cmd = DeviceCmdCode.GET_DEVINFO,
  1294. payload = {'IMEI': self.device.devNo, 'fields': []},
  1295. timeout = MQTT_TIMEOUT.SHORT)
  1296. if dev_info['rst'] == 0:
  1297. return u'设备状态检查在线,网络通畅,网络可能出现闪断'
  1298. else:
  1299. raise ServiceException({'result': 2, 'description': u'设备玩命也无法找到网络,设备可能不在线'})
  1300. else:
  1301. return u'无法检查该设备的告警状态,建议您用其他方式确认此告警是否正常'
  1302. def async_update_portinfo_from_dev(self):
  1303. return
  1304. class MqttSmartBox(SmartBox):
  1305. def check_dev_status(self, attachParas = None):
  1306. """
  1307. 如果超过两个心跳周期没有报心跳,并且最后一次更新时间在2个小时内,需要从设备获取状态
  1308. 否则以缓存状态为准。
  1309. :param attachParas:
  1310. :return:
  1311. """
  1312. if not self.device.need_fetch_online:
  1313. raise ServiceException(
  1314. {'result': 2, 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_FAIL)})
  1315. operation_result = MessageSender.send(device = self.device, cmd = DeviceCmdCode.GET_DEVINFO, payload = {
  1316. 'IMEI': self.device.devNo,
  1317. 'fields': ['signal']
  1318. }, timeout = MQTT_TIMEOUT.TEST)
  1319. if operation_result['rst'] != ErrorCode.DEVICE_SUCCESS:
  1320. if operation_result['rst'] == ErrorCode.DEVICE_CONN_FAIL:
  1321. raise ServiceException(
  1322. {
  1323. 'result': 2,
  1324. 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_CHECK_FAIL)
  1325. })
  1326. else:
  1327. raise ServiceException(
  1328. {
  1329. 'result': 2,
  1330. 'description': u'检测设备状态失败({})'.format(operation_result['rst'])
  1331. })
  1332. def make_six_bytes_session_id():
  1333. # 至少可以用到2050
  1334. ts = long(time.time() * 100000)
  1335. ts = ts + long(get_random_str(2, seq = string.digits))
  1336. rv = fill_2_hexByte(hex(ts), 12)
  1337. if rv[6:8] == 'AA':
  1338. logger.debug('AA in session id.')
  1339. return rv[0:6] + 'A9' + rv[8:]
  1340. else:
  1341. return rv
  1342. def _record_error_times(devNo, portStr, box):
  1343. """
  1344. 记录 启动错误的 次数 超过次数报警处理 具体处理方式由协议函数实现
  1345. :param devNo: 设备号
  1346. :param portStr: 端口号
  1347. :param box: SmartBox
  1348. :return:
  1349. """
  1350. dev = Device.get_dev(devNo)
  1351. errorTimes = dev.get("otherConf", {}).get("errorTimes", None)
  1352. # 如果设备没有设置启动失败的次数,不做任何处理
  1353. if not errorTimes:
  1354. return
  1355. times = Device.get_error_start_times(devNo, portStr)
  1356. Device.set_error_start_times(devNo, portStr, times + 1)
  1357. # 超过上限次数之后 缓存清除 触发响应操作
  1358. if times + 1 >= errorTimes:
  1359. box.handle_out_start_error(portStr)
  1360. def _clear_error_times(devNo, portStr, box):
  1361. """
  1362. 启动成功之后 删除错误计数 并进行相关的处理
  1363. :param devNo: 设备号
  1364. :param portStr: 端口号
  1365. :param box: SmartBox
  1366. :return:
  1367. """
  1368. dev = Device.get_dev(devNo)
  1369. errorTimes = dev.get("otherConf", {}).get("errorTimes", None)
  1370. # 如果设备没有设置启动失败的次数,不做任何处理
  1371. if not errorTimes:
  1372. return
  1373. times = Device.get_error_start_times(devNo, portStr)
  1374. Device.delete_error_start_times(devNo, portStr)
  1375. if times >= errorTimes:
  1376. box.handle_clear_start_error(portStr)
  1377. def start_error_timer(missMessages=None):
  1378. """
  1379. 装饰器 用来装饰start_device函数,记录失败次数 并且超过一定次数的时候做响应的处理
  1380. :param missMessages: 忽略的消息列表 ex [u"设备端口已经被占用,请稍后再试"]
  1381. :return:
  1382. """
  1383. if missMessages is None or not isinstance(missMessages, list):
  1384. missMessages = list()
  1385. def outFunc(func):
  1386. @wraps(func)
  1387. def inFunc(box, package, openId, attachParas):
  1388. chargeIndex = attachParas.get("chargeIndex", 0)
  1389. try:
  1390. result = func(box, package, openId, attachParas)
  1391. except ServiceException as e:
  1392. message = e.result.get("description")
  1393. if message not in missMessages:
  1394. _record_error_times(box._device["devNo"], chargeIndex, box)
  1395. raise e
  1396. else:
  1397. _clear_error_times(box._device["devNo"], chargeIndex, box)
  1398. return result
  1399. return inFunc
  1400. return outFunc
  1401. # 订单编号,需要根据云快充的协议生成:生成规则为 格式桩号(7bytes)+枪号(1byte)+年月日时分秒(6bytes)+自增序号
  1402. #(2bytes);示例:32010600019236 01 200106180342 3060
  1403. def make_cartcp_order_no(devNo, portIndex):
  1404. increCount = 0
  1405. try:
  1406. increCount = caches['devmgr'].incr('increCount',1)
  1407. except Exception ,e:
  1408. caches['devmgr'].set('increCount',0)
  1409. hexIndex = fill_2_hexByte(hex(int(portIndex)),2)
  1410. hexIncre = fill_2_hexByte(hex(increCount), 4)
  1411. nowTime = datetime.datetime.now()
  1412. year = nowTime.year - 2000
  1413. strTime = str(year) + nowTime.strftime('%m%d%H%M%S')
  1414. orderNo = '%s%s%s%s' % (devNo,hexIndex,strTime,hexIncre)
  1415. return orderNo
  1416. def make_dianchuan_order_no(devNo):
  1417. increCount = 0
  1418. try:
  1419. increCount = caches['devmgr'].incr('increCount',1)
  1420. except Exception ,e:
  1421. caches['devmgr'].set('increCount',0)
  1422. hexIncre = fill_2_hexByte(hex(increCount), 4)
  1423. nowTime = datetime.datetime.now()
  1424. year = nowTime.year - 2000
  1425. strTime = str(year) + nowTime.strftime('%m%d%H%M%S')
  1426. orderNo = '%s%s%s' % (devNo,strTime,hexIncre)
  1427. return orderNo
  1428. class SendManager(object):
  1429. def __init__(self, sender = MessageSender, visitor = "manager"):
  1430. self._sender = sender
  1431. self._rst = None
  1432. self._tError = None
  1433. self._sError = None
  1434. self._cError = None
  1435. add_exc_type = getattr(self, "add_{}_exc_type".format(visitor), self.add_manager_exc_type)
  1436. add_exc_type()
  1437. def __enter__(self):
  1438. return self
  1439. def __exit__(self, exc_type, exc_val, exc_tb):
  1440. if self.rst == 0:
  1441. return
  1442. elif self.rst == -1:
  1443. raise self._tError
  1444. elif self.rst == 1:
  1445. raise self._sError
  1446. else:
  1447. raise self._cError
  1448. def add_client_exc_type(self):
  1449. setattr(self, "_tError", ClientServiceTimeOutException())
  1450. setattr(self, "_sError", ClientServiceSerialException())
  1451. setattr(self, "_cError", ServiceException())
  1452. def add_manager_exc_type(self):
  1453. setattr(self, "_tError", ManagerServiceTimeOutException())
  1454. setattr(self, "_sError", ManagerServiceSerialException())
  1455. setattr(self, "_cError", ServiceException())
  1456. def add_tester_exc_type(self):
  1457. setattr(self, "_tError", TestTimeOutError())
  1458. setattr(self, "_sError", TestSerialError())
  1459. setattr(self, "_cError", TestError())
  1460. def send(self, *args, **kwargs):
  1461. return self._sender.send(*args, **kwargs)
  1462. @property
  1463. def rst(self):
  1464. return self._rst
  1465. @rst.setter
  1466. def rst(self, result):
  1467. self._rst = result.get("rst")