# -*- coding: utf-8 -*- # !/usr/bin/env python import binascii import copy import datetime import hashlib import logging import random import string import struct import threading import time import traceback from functools import wraps import simplejson as json import requests from bson import ObjectId from django.conf import settings from typing import Optional, Dict from apilib.monetary import RMB from apilib.utils import is_number from apilib.utils_datetime import timestamp_to_dt, to_datetime from apilib.utils_json import JsonResponse, json_dumps from apilib.utils_string import get_random_str, make_title_from_dict from apps.web.constant import Const, ErrorCode, DeviceCmdCode, FAULT_CODE, MQTT_TIMEOUT, DeviceErrorCodeDesc, \ DeviceOnlineStatus, CONSUMETYPE from apps.web.core.exceptions import ServiceException, ClientServiceSerialException, ClientServiceTimeOutException, \ ManagerServiceSerialException, ManagerServiceTimeOutException, TestError, TestTimeOutError, TestSerialError, \ DeviceNetworkTimeoutError, InvalidParameter from apps.web.core.networking import MessageSender from apps.web.device.models import Device, Part, DeviceDict, Group, GroupDict from apps.web.user.constant2 import StartDeviceType from apps.web.user.models import ConsumeRecord, MyUser, Card from apps.web.api.models import APIStartDeviceRecord from apps.web.user.utils2 import notify_user from apps.web.utils import concat_user_center_entry_url, concat_front_end_url, testcase_point from apps.web.common.models import ExceptionLog from django.core.cache import caches logger = logging.getLogger(__name__) key = '1c7e3b866ef645760040ddde343601de' # imei:电子标签 # time:一小时内的描述(0~3599) # pulseCount:脉冲个数(1~16) def encode(imei, time, pulseCount): short_time = time & 0xFFFFFFF0 # 一小时内的描述,需要按16模取整 pulseCount = pulseCount - 1 # 为编码到4位,所以减一 extend = short_time + pulseCount key_ext = key + datetime.datetime.now().strftime("%Y%m%d%H") code = hashlib.pbkdf2_hmac('sha1', key_ext.encode('utf-8'), struct.pack(">I", extend).lstrip('\x00'), pulseCount + 1, 128) md5_obj = hashlib.md5(imei.encode('utf-8')) md5_obj.update(code) md5_digest = md5_obj.digest() md5_digest_head = int(binascii.hexlify(md5_digest[0]), 16) digest = md5_digest_head >> 1 digest = (digest << 12) + extend return '{:0>6}'.format(digest) def hexbyte_2_bin(hexCode): binTemp = bin(int(hexCode, 16)) needNum = 8 - len(binTemp) + 2 c = '00000000' result = c[0:needNum] + binTemp[2::] return result def decimal_2_hexByte(decimalCode): hexTemp = hex(decimalCode) c = '00' needNum = 2 - len(hexTemp) + 2 result = c[0:needNum] + hexTemp[2::] return result def fill_2_hexByte(hexCode, num = 4, reverse = False): hexCode = hexCode.replace('L', '') tList = ['0' for _ in range(num)] c = ''.join(tList) needNum = len(c) + 2 - len(hexCode) if needNum <= 0: if reverse: return reverse_hex(hexCode[2::]).upper() return hexCode[2::].upper() result = c[0:needNum] + hexCode[2::] if reverse: return reverse_hex(result).upper() return result.upper() def pack_float(value, reverse = False): """ 将浮点数转换为16进制转换的 float :param value: 浮点数 :param reverse: 小端 (True)或者 大端 :return: """ sign = ">f" if not reverse else " len(result): for ii in range(needLenth-len(result)): result += '0' return result.upper() def unpack_float(value, reverse = False): """ 解析16进制浮点数 :param value: 16进制浮点数 :param reverse:小端 (True)或者 大端 :return: """ sign = ">f" if not reverse else "None super(AsyncStartDevice, self).__init__() self.deviceAdapter = deviceAdapter self.record = record # type:APIStartDeviceRecord def notify_api_client(self, url, **kwargs): try: res = self.post(url = url, data = kwargs, timeout = 15) logger.debug(str(res)) except Exception as e: logger.exception(e) def run(self): logger.info(repr(self.record)) notify_payload = { 'sign': self.record.apiConf['mySign'], 'extOrderNo': self.record.orderNo, 'deviceCode': self.record.deviceCode, 'channel': self.record.channel, 'createTime': self.record.createTime, 'finishedTime': datetime.datetime.now(), 'errcode': ErrorCode.EXCEPTION, 'errmsg': u'系统异常' } err_code = ErrorCode.EXCEPTION err_msg = u'系统异常' try: device_result = self.deviceAdapter.start(package = self.record.package, openId = self.record.userId, attachParas = self.record.attachParas) err_code = device_result['rst'] err_msg = device_result.get('desc', '') except ServiceException as e: logger.error(str(e)) err_code = e.result.get('result') err_msg = e.result.get('description') except Exception as e: logger.exception(e) err_code = ErrorCode.EXCEPTION err_msg = e.message finally: try: self.record.errCode = err_code self.record.errMsg = err_msg self.record.save() except Exception as e: logger.exception(e) notify_payload.update({ 'errcode': err_code, 'errmsg': u'系统异常' if err_code == ErrorCode.EXCEPTION else err_msg }) return self.notify_api_client(url = self.record.notifyUrl, **notify_payload) class SmartBox(object): def __init__(self, device): # type: (DeviceDict)->None self._device = device self._vcard_id = None # 启动方式,None,表示扫码直接启动,如果是虚拟卡,表示用虚拟卡启动,如果是卷也是的 super(SmartBox, self).__init__() @property def device(self): return self._device def check_dev_status(self, attachParas = None): pass def get_dev_info(self): pass def check_alarm(self, alarm): return '' def set_vcard_id(self, vcard_id): self._vcard_id = vcard_id def test(self, coins): raise NotImplementedError(u'设备未实现 `test`') def serial_test(self, payload): """ 串口测试函数 :param payload: 根据个性化配置不同 传入参数也不同 :return: """ raise NotImplementedError(u"该设备不支持") def start_device(self, package, openId, attachParas): raise NotImplementedError('cannot call `start_device` of base class SmartBox') def start(self, packageId, openId=None, attachParas={}): washConfig = self.device.get("washConfig", dict()) package = washConfig.get(packageId) if not package: raise ServiceException({"result": 2, "description": u"未找到套餐,请联系经销商!"}) # new if self.device.bill_as_service_feature.support and self.device.bill_as_service_feature.on: return self.bill_as_service_start_device(package, openId, attachParas) else: return self.start_device(package, openId, attachParas) def start_temp_use(self, packageId, openId = None, attachParas = {}): package = self._device['tempWashConfig'].get(packageId) return self.start_device(package, openId, attachParas) def start_from_api(self, record): # type: (APIStartDeviceRecord)->Optional[Dict] if record.notifyUrl: return AsyncStartDevice(self, record).start() else: return self.start_device(package = record.package, openId = record.userId, attachParas = copy.deepcopy(record.attachParas)) # 基础的停止接口,返回的是单位是分钟.此接口单独放到业务做,目的是有的设备结算按照分钟,有的设备按照秒。这样统一由业务决定 def stop(self, port = None): devInfo = MessageSender.send(self.device, DeviceCmdCode.STOP_DEVICE, {'IMEI': self._device['devNo']}) if devInfo.get('rst', 0) != 0: return JsonResponse({"result": 0, "description": u'网络连接异常,停止设备失败,请您重新尝试停掉设备', "payload": ''}) devInfo['remainder_time'] = int(devInfo['remainder_time'] / 60.0) return devInfo def calc_stop_back_coins(self, totalFee, remainderTime, totalTime): refundFee = round(totalFee * (float(remainderTime) / totalTime), 2) if refundFee > totalFee: refundFee = totalFee if refundFee <= 0.0: refundFee = 0.00 return refundFee def remote_charge_card(self, price, rechargeRecord = None): raise NotImplementedError(u'设备未实现 `remote_charge_card`') def recharge_card(self, cardNo, money, orderNo = None): # type:(str,RMB,str)->(dict, RMB) raise NotImplementedError(u'设备未实现 `recharge_ic_card`') def recharge_ic_card_realiable(self, cardNo, money, order_no): # type:(str,RMB,str)->dict raise NotImplementedError(u'设备未实现 `recharge_ic_card_realiable`') def response_card_balance(self, cardNo, balance): raise NotImplementedError(u'设备未实现 `response_card_balance`') def response_use_card(self, res, leftBalance, uartId = None): raise NotImplementedError(u'设备未实现 `response_use_card`') def get_dev_consume_count(self): pass def stop_charging_port(self, port): pass # 访问设备,获取设备信息 def getDevInfo(self): pass # 解析获取设备信息的返回报文 def analyze_event_data(self, data): return data def active_deactive_port(self, port, active): raise ServiceException({'result': 2, 'description': u'此设备不支持直接打开或者管理端口'}) def lock_unlock_port(self, port, lock = True): raise ServiceException({'result': 2, 'description': u'此设备不支持直接禁用、解禁端口'}) def is_port_can_use(self, port, canAdd=False): """ 端口是否可用,如果canAdd为True,表示可以追加钱 :param port: :param canAdd: :return: """ try: portDict = self.get_port_status() # type: Optional[dict, None] if portDict is None: return True, '' port = str(port) if port in portDict: if portDict[port]['status'] == Const.DEV_WORK_STATUS_IDLE: return True, '' elif portDict[port]['status'] == Const.DEV_WORK_STATUS_FAULT: return False, u'该线路故障,暂时不能使用,请您使用其他线路' elif portDict[port]['status'] == Const.DEV_WORK_STATUS_WORKING: if canAdd: return True, '' return False, u'该线路正在工作,暂时不能继续使用,请您使用其他线路,或者等待该线路工作完毕' elif portDict[port]['status'] == Const.DEV_WORK_STATUS_FORBIDDEN: return False, u'该线路已被禁止使用,请您使用其他线路' elif portDict[port]['status'] == Const.DEV_WORK_STATUS_CONNECTED: return True, u'' else: return False, u'线路未知状态,暂时不能使用' return False, u'未知端口,无法使用' except ServiceException, e: return False, e.result.get('description') except Exception, e: logger.error('error = %s' % e) return False, u'获取端口状态失败' def press_down_key(self, keyName): pass def get_device_function_by_key(self, keyName): return '' def get_port_status(self, force = False): return None def dealer_get_port_status(self): """ 远程上分的时候获取端口状态 :return: """ return self.get_port_status() def get_dev_setting(self): return None def set_device_function(self, request, lastSetConf): pass def set_device_function_param(self, request, lastSetConf): pass def get_server_setting(self): return None def set_server_setting(self, payload): pass def count_down(self, request, dev, agent, group, devType, lastOpenId): return None def set_dev_fault(self, fault): pass def set_dev_disable(self, disable): pass def get_port_static_info(self, portDict): allPorts, usedPorts = 0, 0 for v in portDict.values(): allPorts += 1 if (v.has_key('isStart') and v['isStart']) or ( v.has_key('status') and v['status'] != Const.DEV_WORK_STATUS_IDLE): usedPorts += 1 return allPorts, usedPorts, allPorts - usedPorts def make_random_cmdcode(self): return random.randint(DeviceCmdCode.RANDOM_START_CODE, DeviceCmdCode.RANDOM_END_CODE) def get_port_status_from_dev(self): raise NotImplementedError(u'设备未实现 `get_port_status_from_dev`') def get_part_info(self): return {} def get_port_info(self, port): return {} def async_update_portinfo_from_dev(self): class Sender(threading.Thread): def __init__(self, smartBox): super(Sender, self).__init__() self._smartBox = smartBox def run(self): try: result = self._smartBox.get_port_status_from_dev() # 将端口数据入库刷新到部件表中 portFromDev = result.keys() portInDb = [obj.partNo for obj in Part.objects.filter(logicalCode = self._smartBox._device['logicalCode'], partName = 'port')] needAddPartNo = list(set(portFromDev) - set(portInDb)) needAddPart = [{'logicalCode': self._smartBox._device['logicalCode'], 'ownerId': self._smartBox._device['ownerId'], 'partNo': portNo, 'partName': 'port', 'dateTimeAdded': datetime.datetime.now(), 'dateTimeUpdated': datetime.datetime.now()} for portNo in needAddPartNo] if needAddPart: Part.get_collection().insert(needAddPart) needRemovePartNo = list(set(portInDb) - set(portFromDev)) Part.objects(logicalCode = self._smartBox._device['logicalCode'], partNo__in = needRemovePartNo, partName = 'port').delete() # 将其他部件入库到部件表中(目前支持其他部件的只有我们自己的一体板) partItems = self._smartBox.get_part_info() for partName, partInfo in partItems.items(): if not partInfo['SN']: continue Part.upsert_part(logicalCode = self._smartBox._device['devNo'], ownerId = self._smartBox._device['ownerId'], partNo = partInfo['SN'], partName = partName, partType = '9999') except ServiceException: return except Exception as e: logger.exception(e) sender = Sender(self) sender.start() def format_port_using_detail(self, detailDict): portData = {} startTimeStr = detailDict.get('startTime', None) if startTimeStr is not None and "usedTime" not in detailDict: startTime = to_datetime(startTimeStr) usedTime = int(round((datetime.datetime.now() - startTime).total_seconds() / 60.0)) portData['usedTime'] = usedTime elif detailDict.get("usedTime"): usedTime = detailDict.get("usedTime") portData['usedTime'] = usedTime else: usedTime = None if detailDict.has_key('needTime'): if detailDict['needTime'] == 999: portData['needTime'] = u'充满自停' else: portData['needTime'] = detailDict['needTime'] if detailDict.has_key('leftTime') and (usedTime > 0): if detailDict['leftTime'] == 65535: portData['leftTime'] = 65535 detailDict['usedTime'] = 0 detailDict['actualNeedTime'] = 0 else: portData['actualNeedTime'] = int(detailDict['leftTime']) + int(usedTime) if detailDict.has_key('needTime') and portData['actualNeedTime'] > detailDict['needTime']: portData['actualNeedTime'] = portData['needTime'] portData['leftTime'] = detailDict['leftTime'] if detailDict.has_key('coins'): if self.device.is_auto_refund: portData['leftMoney'] = round( float(detailDict['coins']) * int(detailDict['leftTime']) / ( int(detailDict['leftTime']) + int(usedTime)), 2) portData['consumeMoney'] = round( float(detailDict['coins']) * int(portData['usedTime']) / ( int(detailDict['leftTime']) + usedTime), 2) elif detailDict.has_key('leftTime'): portData['leftTime'] = detailDict['leftTime'] if (not detailDict.has_key('leftTime')) and (usedTime is not None): if detailDict.has_key('needTime'): if isinstance(detailDict['needTime'], (int, float)): portData['leftTime'] = detailDict['needTime'] - usedTime if detailDict.has_key('coins') and float(detailDict['coins']) != 0: # 只有支持退费的设备才显示可退费数据 if self.device.is_auto_refund: portData['leftMoney'] = round( float(detailDict['coins']) * portData['leftTime'] / detailDict['needTime'], 2) portData['consumeMoney'] = round( float(detailDict['coins']) * portData['usedTime'] / detailDict['needTime'], 2) if detailDict.has_key('openId'): user = MyUser.objects(openId=detailDict['openId'], groupId=self.device.groupId).first() if user: portData['nickName'] = user.nickname if detailDict.has_key('cardId'): if not detailDict.has_key('consumeType'): portData['consumeType'] = 'card' card = Card.objects.get(id=ObjectId(detailDict['cardId'])) if card.cardName: portData['cardName'] = card.cardName portData['cardNo'] = card.cardNo # 注意,如果是IC卡,不支持余额回收,这里也不要显示出来 if card.cardType == 'IC' and portData.has_key('leftMoney'): portData.pop('leftMoney') elif 'openId' in detailDict and ('consumeType' not in detailDict): if detailDict.get('vCardId'): portData['consumeType'] = 'mobile_vcard' else: portData['consumeType'] = 'mobile' elif 'consumeType' in detailDict: if detailDict['consumeType'] == 'coin': portData['consumeType'] = 'coin' # 硬币的都无法退费 if portData.has_key('leftMoney'): portData.pop('leftMoney') elif detailDict['consumeType'] == 'server': portData['consumeType'] = 'mobile' # 做个特殊处理 if portData.has_key('needTime'): if portData['needTime'] == '999' or portData['needTime'] == '充满自停': portData['needTime'] = u'充满自停' portData.pop('leftTime', None) else: portData['needTime'] = u'%s分钟' % portData['needTime'] # 如果剩余时间为65535,表示未接插头 if portData.has_key('leftTime') and portData['leftTime'] == 65535: portData['leftTime'] = u'(线路空载)' portData['usedTime'] = 0 portData['needTime'] = 0 detailDict.update(portData) for k, v in detailDict.items(): if v < 0: detailDict.pop(k) # 因为前台显示的开始时间如果带年,就显示不下,这里做个切割 if detailDict.has_key('startTime') and detailDict['startTime'].count('-') == 2: detailDict['startTime'] = to_datetime(detailDict['startTime']).strftime('%m-%d %H:%M:%S') return detailDict def get_port_using_detail(self, port, ctrInfo, isLazy=False): """ 获取设备端口的详细信息 :param port: :param ctrInfo: :param isLazy: 是否延时加载设备信息 :return: """ detailDict = ctrInfo.get(str(port), {}) try: if isLazy: # 需要点击再次点击按钮加载 portInfo = {'isLazy': True} else: portInfo = self.get_port_info(str(port)) skipPipelineProcessing = portInfo.get('skipPipelineProcessing', False) if skipPipelineProcessing: detailDict.update(portInfo) return detailDict # 有的主机报的信息leftTime错误 if portInfo.has_key('leftTime') and portInfo['leftTime'] < 0: portInfo.pop('leftTime') detailDict.update(portInfo) except Exception, e: logger.exception('get port info from dev=%s err=%s' % (self.device.devNo, e)) return detailDict return self.format_port_using_detail(detailDict) def support_count_down(self, openId = None, port = None): """ 是否支持倒计时界面 :return: """ return False def get_duration(self, package): if 'time' not in package or not package['time']: return 0 unit = package.get('unit', u'分钟') reserved_time = float(package['time']) if unit == u'秒': reserved_time = int(reserved_time) elif unit == u'分钟': reserved_time = int(reserved_time * 60) elif unit == u'小时': reserved_time = int(reserved_time * 60 * 60) elif unit == u'天': reserved_time = int(reserved_time * 24 * 60 * 60) else: # 其他都算分钟吧 reserved_time = int(reserved_time * 60) return reserved_time def translate_funcode(self, funCode): return '' def translate_event_cmdcode(self, cmdCode): return '' def translate_smartbox_cmd(self, cmdParas): cmd = str(cmdParas['cmd']) descList = [] temp = Const.CMD_CMD_TRANSLATE_DICT.get(cmd, '') if temp: descList.append(temp) if cmdParas.has_key('funCode'): temp = self.translate_funcode(cmdParas['funCode']) if temp: descList.append(temp) return ' '.join(descList) def translate_server_cmd(self, cmdParas): descList = [] for k, v in cmdParas.items(): if k == 'cmd': temp = Const.CMD_CMD_TRANSLATE_DICT.get(v, '') if temp: descList.append(temp) elif k in Const.CMD_PARAS_TRANSLATE_DICT: temp = Const.CMD_PARAS_TRANSLATE_DICT.get(k, '') if temp: descList.append('%s:%s' % (temp, v)) elif k == 'data' and v: dataDict = self.analyze_event_data(v) if dataDict.has_key('cmdCode'): cmdDesc = self.translate_event_cmdcode(dataDict['cmdCode']) descList.append(cmdDesc) for dataKey, dataValue in dataDict.items(): if dataKey == 'cmdCode': continue dataDesc = Const.CMD_DATA_TRANSLATE_DICT.get(dataKey) if dataDesc: temp = '%s:%s' % (dataDesc, dataValue) descList.append(temp) return ' '.join(descList) # duration 单位:秒 def send_dev_runtime(self, openId, duration): dev = self._device now_time = datetime.datetime.now() devInfo = MessageSender.send(dev, DeviceCmdCode.PAY_MONEY, {'IMEI': dev['devNo'], 'duration': duration, 't': int(time.mktime(now_time.timetuple()))}) if devInfo.has_key('rst') and devInfo['rst'] != 0: current_dev_type_name = dev['devType']['name'] if current_dev_type_name == u'其他': current_dev_type_name = u'自助设备' if devInfo['rst'] == -1: description = u'当前' + current_dev_type_name + u'正在玩命找网络,您的金币还在,重试不会扣款,建议您试试旁边其他设备,或者试试投硬币,或者稍后再试哦' raise ServiceException({'result': 2, 'description': description}) elif devInfo['rst'] == 1: description = u'当前' + current_dev_type_name + u'正在忙,无响应,您的金币还在,重试不会扣款,请试试其他线路,或者请稍后再试哦' raise ServiceException({'result': 2, 'description': description}) else: description = u'系统无响应' raise ServiceException({'result': 2, 'description': description}) start_timestamp = int(time.time()) devInfo['finishedTime'] = start_timestamp + duration Device.update_dev_control_cache(dev['devNo'], { 'openId': openId, 'startTime': timestamp_to_dt(start_timestamp).strftime('%Y-%m-%d %H:%M:%S'), 'needTime': duration, 'status': Const.DEV_WORK_STATUS_WORKING, 'finishedTime': devInfo['finishedTime'] }) return devInfo @property def isHaveStopEvent(self): return False def notify_low_power_to_user(self, user, dealer, port, delay, lowPower = None): """ 用户的低功率检测 设备启动之后 延后一段时间直接对设备的当前的端口功率进行 主板查询 如果查询结果小于一定值 告警用户 :param user: 用户 :param dealer: 经销商( 无用参数 需要干掉或者删除) :param port: 延迟检测的端口 :param delay: 延迟秒数 :param lowPower: 检测的功率 可传参,如果为空 直接从设备侧获取 :return: """ from taskmanager.mediator import task_caller if lowPower is None: lowPower = self.device.get("otherConf", dict()).get("lowPowerDetectionPower", 0) managerialOpenId = user.managerialOpenId task_caller( "report_to_user_low_power", delay = delay, devNo = self.device.devNo, line = port, power = lowPower, managerialOpenId = managerialOpenId, dealerId = self.device.ownerId ) def record_serial_port_for_timeout(self): pass # 检查串口超时,电川的板子出现启动命令串口超时,但是获取端口状态却正常。(现网已经抓到几个案例,此函数用于规避止血,但是并不能完全解决) # 只能解决获取端口状态命令OK,但是启动命令不行的这种情况。 def check_serial_port_for_startcmd(self, port): # 首先记录到数据库表,用于查询现网问题,然后进行分析 try: portsInfo = self.get_port_status_from_dev() ExceptionLog.log( user = self.device.devNo, exception = '', extra = { 'action': 'check_serial_port_for_startcmd:get_port_status_from_dev', 'result': 'success', 'portsInfo': str(portsInfo)}) except Exception as e: ExceptionLog.log(user = self.device.devNo, exception = traceback.format_exc(), extra = { 'action': 'check_serial_port_for_startcmd:get_port_status_from_dev', 'result': 'failure' }) raise e portStatus = portsInfo.get(str(port), {}).get('status', Const.DEV_WORK_STATUS_IDLE) if portStatus == Const.DEV_WORK_STATUS_WORKING: # 如果端口处于忙的状态,默认命令成功,会扣费 return raise ServiceException( {'result': 2, 'description': u'设备忙无响应。本次操作没有扣除您的金额,您可以稍后重试或者试试附近其他设备。'}) def handle_out_start_error(self, port): """ 处理 启动错误 超过次数上限 :param port: 端口 :return: """ pass def handle_clear_start_error(self, port): """ 清除启动错误之后 的后续操作 :param port: 端口 :return: """ pass @property def show_pay_unit(self): """ 前台显示付费的时候,目前有不同的客户希望 显示不同的单位 有的显示金币 有的显示元, 这个地方处理下 :return: """ return u"元" def get_many_port_info(self, portList): return None def check_order_state(self, openId): return def start_device_realiable(self, order): # type:(ConsumeRecord)->dict raise NotImplementedError('cannot call `start_device_realiable` of base class SmartBox') def calc_elec_fee(self, spend_elec): group = Group.objects.get(id = self.device['groupId']) return float(group.otherConf.get('elecFee', 0)) * spend_elec def isHaveCallback(self): return False def do_callback(self, *args, **kwargs): """ 针对状态机确认成功付款后的状态回调使用 """ raise NotImplementedError(u'设备未实现 `dev_callback`') @staticmethod def check_device_features(device_features, no_features_to_return = None): def warpper(func): @wraps(func) def inner(self, *args, **kwargs): retult = False features = self.device.devType.get("features", {}) if isinstance(device_features, (list, tuple)): for item in device_features: if features.get(item) and features.get(item) is True: retult = True break elif isinstance(device_features, dict): if set(device_features.items()) & set(features.items()): retult = True elif isinstance(device_features, (str, unicode, int)): if features.get(device_features) and features.get(device_features) is True: retult = True if retult: return func(self, *args, **kwargs) else: return no_features_to_return return inner return warpper @staticmethod def life_cycle(before=lambda self, *args, **kwargs: None, after=lambda *args: args): def warpper(func): @wraps(func) def inner(self, *args, **kwargs): before(self, *args, **kwargs) data = func(self, *args, **kwargs) result = after(self, data) return result or data return inner return warpper def get_signal(self): result = MessageSender.send(device = self._device, cmd = DeviceCmdCode.GET_DEVINFO, payload = {'IMEI': self._device['devNo']}, timeout = MQTT_TIMEOUT.SHORT) return result def notify_user(self, order, cardStart = False): pass def deal_order_money(self, order): # type: (ConsumeRecord) -> ConsumeRecord raise NotImplementedError(u'设备未实现 `deal_order_money`') @property def support_monthly_package(self): return False def _notify_user(self, user, templateName, url = None, **kwargs): if not user or not user.managerialOpenId: logger.warning('user is none or managerialOpenId is not exists.') return from taskmanager.mediator import task_caller task_caller('send_msg_to_user_via_wechat', productId = user.productAgentId, openId = user.managerialOpenId, templateName = templateName, url = url, **kwargs) def notify_service_start_to_user(self, order, user=None): # type: (ConsumeRecord, MyUser)->None if not order.user: return dealer = self.device.owner if dealer.showServicePhone: remark = u'感谢您的使用!有任何问题请联系客服(联系电话{})!'.format(dealer.service_phone) else: remark = u'感谢您的使用!有任何问题请联系客服!' if order.port != Const.NO_PORT: service = unicode( '{}({}/{}-端口{})'.format(self.device.majorDeviceType, self.device.group.address[0:32], self.device.logicalCode, order.port)) else: service = unicode( '{}({}/{})'.format(self.device.majorDeviceType, self.device.group.address[0:32], self.device.logicalCode)) kw = { 'WechatUserManagerApp': { 'title': u'尊敬的用户,设备已经成功启动', 'service': service, 'time': order.device_start_time, 'remark': remark }, 'WechatUserSubscribeManagerApp': { 'title': u'尊敬的用户,设备已经成功启动', 'service': service, 'time': order.device_start_time, 'remark': remark } } user = order.user if not user else user # type: MyUser if not notify_user: logger.warning('user is not exists.'.format(order.openId, order.groupId)) return url = self.custom_push_url(order, user) self._notify_user(user, 'service_start', url = url, **kw) def notify_service_end_to_user(self, order, url=None): # type:(ConsumeRecord, Optional[str, None]) -> None service_name = order.package.name or u"充电" title_list = [ {u'': u'[{}]结束,感谢您的使用!'.format(service_name)}, {u'设备编号': self.device.logicalCode} ] port = order.port if port: title_list.extend([{u'设备端口': port}, {u'设备地址': order.address}]) else: title_list.extend([{u'设备地址': order.address}]) reason = order.service.reason if reason: title_list.extend([{u'结束原因': reason}]) dealer = order.owner if dealer.showServicePhone: remark = u'客服联系电话:{}'.format(dealer.service_phone) else: remark = u'我们竭诚为您服务,有任何问题请联系客服!' finished_time = order.device_end_time if not finished_time: finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') agent = dealer.my_agent device = order.device if agent.customizedUserSubGzhAllowable: if port: service = u"{}服务({}-端口{})".format(device.majorDeviceType, device.logicalCode, order.port) if len(service) > 20: service = u'{}服务({})'.format(device.majorDeviceType, device.logicalCode) if len(service) > 20: service = u'{}服务'.format(device.majorDeviceType) else: service = u'{}服务({})'.format(service_name, device.logicalCode) if len(service) > 20: service = u'{}服务'.format(device.majorDeviceType) kw = { 'service': service, 'finishTime': finished_time, 'remark': remark, } else: kw = { 'title': make_title_from_dict(title_list), 'service': u'{}服务'.format(service_name), 'finishTime': finished_time, 'remark': remark } kw.update({'url': url or settings.SERVER_END_BASE_SSL_URL + '/user/index.html?v=1.0.31#/user/consume'}) notify_user(order.openId, order.ownerId, 'service_complete', **kw) def stop_by_order(self, port, orderNo): raise NotImplementedError(u'设备未实现 `stop_by_order`') def isHaveFaultHandle(self): return False def faultHandle(self, **kw): raise NotImplementedError(u'设备未实现 `FaultHandle`') def format_upload_power(self, power): return power def force_stop_order(self,order, **kwargs): raise NotImplementedError(u'设备未实现 `force_stop_order`') def get_ports_info(self, **kw): raise NotImplementedError(u'设备未实现 `get_ports_info`') def switch_bill_as_service(self, onOrOff): Device.get_collection().update_one( {'devNo': self.device.devNo}, { '$set': { 'devType.features.billAsService.on': onOrOff } }) Device.invalid_device_cache(self.device.devNo) def custom_push_url(self, order, user, **kw): return concat_user_center_entry_url(agentId=user.productAgentId, redirect=concat_front_end_url( uri='/user/index.html#/user/consumeDetail?id={}'.format(str(order.id)))) def bill_as_service_start_device(self, package, openId, attachParas): charge_unit = self.device.bill_as_service_feature.charge_unit price = package['price'] elecCharge = charge_unit["elecCharge"] serviceCharge = charge_unit["serviceCharge"] unitPrice = float(elecCharge) + float(serviceCharge) actualTime = round(price / unitPrice, 2) package['time'] = actualTime return self.start_device(package, openId, attachParas) def prepare_package(self, packageId, attachParas, startType=StartDeviceType.ON_LIEN): """ 预备套餐模型 普通设备直接由设备解释 """ isTemporary = 'isTemporary' in attachParas and attachParas['isTemporary'] is True package = self.device.package(packageId, isTemporary=isTemporary) return package def prepare_serviced_info(self, package, attach_paras): # type:(dict, dict)->dict rv = {'chargeIndex': attach_paras['chargeIndex']} return rv def get_service_fee_info(self): ruleList = [] for packageId, rule in self.device['washConfig'].items(): item = { 'id': packageId, 'name': rule['name'], 'coins': rule['coins'], 'price': rule.get('price', rule['coins']), 'time': rule.get('time', 20), 'description': rule.get('description', ''), 'imgList': rule.get('imgList', []), 'unit': rule.get('unit', u'分钟'), 'switch': rule.get('switch', True), } if 'sn' in rule: item['sn'] = rule['sn'] ruleList.append(item) billAsService = self.device.bill_as_service_feature devData = { 'id': self.device.devNo, 'isManager': True, 'groupName': self.device.group['groupName'], 'groupNumber': self.device['groupNumber'], 'devNo': self.device.devNo, 'devTypeName': self.device.devTypeName, 'devTypeCode': self.device.devTypeCode } if "displaySwitchs" in self.device.my_obj.otherConf: displaySwitchs = self.device.my_obj.otherConf.get('displaySwitchs') else: displaySwitchs = { 'displayCoinsSwitch': True, 'displayTimeSwitch': True, 'displayPriceSwitch': True, "setPulseAble": False, "setBasePriceAble": False } ruleList = sorted(ruleList, key=lambda x: (x.get('sn'), x.get('id'))) return { 'ruleList': ruleList, 'billAsService': billAsService, 'devData': devData, 'displaySwitchs': displaySwitchs, } def set_service_fee_info(self, payload): elecCharge = round(float(payload['billAsService'].get('elecCharge', 0.5)), 2) serviceCharge = round(float(payload['billAsService'].get('serviceCharge', 0.5)), 2) # 显示展示给用户信息部分 if payload.get('displaySwitchs'): displaySwitchs = payload.get('displaySwitchs') else: displaySwitchs = {'displayCoinsSwitch': False, 'displayTimeSwitch': True, 'displayPriceSwitch': True, 'setPulseAble': False, 'setBasePriceAble': False} # 套餐部分 packages = payload.get('packages') # 调整SN套餐顺序 for i, item in enumerate(packages): item['sn'] = i # 套餐id 去重 existIds = list(map(lambda _: _.get('id'), packages)) washConfig = {} for rule in packages: if 'price' in rule: if not is_number(rule['price'] and is_number(rule.get('time', 0))): raise InvalidParameter(u'金币数目或者时间或者价格必须是数字') if RMB(rule['price']) >= self.device.owner.maxPackagePrice: raise InvalidParameter(u'套餐金额超限') if len(rule['name']) > 20: raise InvalidParameter(u'套餐名字只能取1-20位') if 'id' in rule: ruleId = rule['id'] else: ruleId = list(set(range(1, 71)) - set([int(ruleId) for ruleId in washConfig.keys()]) - set(existIds))[0] washConfig[str(ruleId)] = { 'billingMethod': CONSUMETYPE.BILL_AS_SERVICE, 'name': rule['name'], 'coins': float(rule['price']), 'price': float(rule['price']), 'time': float(rule.get('time', 0)), 'description': rule.get('description', ''), 'imgList': rule.get('imgList', []), 'unit': rule.get('unit', u'分钟'), 'switch': rule.get('switch', True), 'sn': rule.get('sn') } self.device.update_device_obj(**{ 'washConfig': washConfig, 'otherConf.displaySwitchs': displaySwitchs, 'devType.features.billAsService.elecCharge': elecCharge, 'devType.features.billAsService.serviceCharge': serviceCharge }) dealer = self.device.owner dealer.defaultWashConfig[self.device.devTypeId] = self.device['washConfig'].values() dealer.save() def start_device_swap(self, portNo): # type:(ConsumeRecord)->dict raise NotImplementedError('cannot call `start_device_swap` of base class SmartBox') @property def support_device_package(self): return False def get_reg_model(self, dealer, devTypeId, isTemp=False, **kw): payload = {} if devTypeId in dealer.defaultWashConfig: payload = dealer.defaultWashConfig[devTypeId] return payload def reg_model(self, **kw): raise NotImplementedError('cannot call `reg_model` of base class SmartBox') def format_device_package(self, isTemp=False, **kw): def __generate_id(ids): i = 1 while True: if str(i) in ids: i += 1 else: return str(i) def __formart_ruleList(): packageList = kw['serviceData'] for item in packageList: item["sn"] = packageList.index(item) ids = [str(rule['id']) for rule in packageList if 'id' in rule] washConfig = {} for i, rule in enumerate(packageList): ruleId = str(rule.get('id', '')) if not ruleId: ruleId = __generate_id(ids) ids.append(ruleId) washConfig[ruleId] = {} if 'switch' in rule: washConfig[ruleId].update({'switch': rule['switch']}) if 'billingMethod' in rule: washConfig[ruleId].update({'billingMethod': rule['billingMethod']}) if 'price' in rule: washConfig[ruleId].update({'price': round(float(rule['price']), 2) or 0}) if 'coins' in rule: washConfig[ruleId].update({'coins': round(float(rule['coins']), 2) or 0}) if 'time' in rule: washConfig[ruleId].update({'time': rule['time'] or 0}) if 'name' in rule: washConfig[ruleId].update({'name': rule['name'] or '套餐{}'.format(i + 1)}) if 'unit' in rule: washConfig[ruleId].update({'unit': rule['unit']}) if 'sn' in rule: washConfig[ruleId].update({'sn': rule['sn']}) if 'autoStop' in rule: washConfig[ruleId]['autoStop'] = rule['autoStop'] if 'autoRefund' in rule: washConfig[ruleId]['autoRefund'] = rule['autoRefund'] if 'minAfterStartCoins' in rule: washConfig[ruleId]['minAfterStartCoins'] = rule['minAfterStartCoins'] if 'minFee' in rule: washConfig[ruleId]['minFee'] = rule['minFee'] if isTemp: pass # 检验部分 if RMB(rule.get('price') or 0) > self.device.owner.maxPackagePrice: raise ServiceException( {'result': 0, 'description': '套餐( {} )金额超限'.format(rule['name']), 'payload': {}}) return washConfig def __formart_displaySwitchs(): return kw.get('displaySwitchs', {'displayCoinsSwitch': True, 'displayTimeSwitch': True, 'displayPriceSwitch': True, 'setPulseAble': False, 'setBasePriceAble': False}) washConfig = __formart_ruleList() displaySwitchs = __formart_displaySwitchs() return washConfig, displaySwitchs def dealer_show_package(self, isTemp=False, **kw): def get_rule_list(): if isTemp: config = self.device.get('tempWashConfig', {}) else: config = self.device['washConfig'] ruleList = [] for packageId, rule in config.items(): item = { 'id': packageId } if 'switch' in rule: item['switch'] = rule['switch'] if 'name' in rule: item['name'] = rule['name'] if 'billingMethod' in rule: item['billingMethod'] = rule['billingMethod'] if 'coins' in rule: item['coins'] = rule['coins'] if 'price' in rule: item['price'] = rule['price'] if 'time' in rule: item['time'] = rule['time'] if 'description' in rule: item['description'] = rule['description'] if 'unit' in rule: item['unit'] = rule['unit'] if 'imgList' in rule: item['imgList'] = rule['imgList'] if 'sn' in rule: item['sn'] = rule['sn'] if 'autoStop' in rule: item['autoStop'] = rule['autoStop'] if 'minAfterStartCoins' in rule: item['minAfterStartCoins'] = rule['minAfterStartCoins'] if 'minFee' in rule: item['minFee'] = rule['minFee'] ruleList.append(item) return sorted(ruleList, key=lambda x: (x.get('sn'), x.get('id'))) def get_display_switchs(): if "displaySwitchs" in self.device["otherConf"]: displaySwitchs = self.device["otherConf"].get('displaySwitchs') else: displaySwitchs = {'displayCoinsSwitch': True, 'displayTimeSwitch': True, 'displayPriceSwitch': True, "setPulseAble": False, "setBasePriceAble": False} return displaySwitchs displaySwitchs = get_display_switchs() try: ruleList = get_rule_list() return {'ruleList': ruleList, 'displaySwitchs': displaySwitchs} except: return {'displaySwitchs': displaySwitchs, 'ruleList': []} def user_show_package(self, isTemp=False): group = self.device.group # type: GroupDict # 探测是否地址为免费活动组,默认为否 is_free_service = group.is_free if isTemp: config = self.device.get('tempWashConfig', {}) else: config = self.device['washConfig'] if "displaySwitchs" in self.device.otherConf: displaySwitchs = self.device.otherConf.get('displaySwitchs') displaySwitchs = dict(filter(lambda x: "display" in x[0], displaySwitchs.items())) else: displaySwitchs = { 'displayCoinsSwitch': True, 'displayTimeSwitch': True, 'displayPriceSwitch': True } packages = [] for packageId, rule in config.items(): # 没有启用的套餐 直接掠过 if not rule.get("switch", True): continue item = { 'id': packageId } if 'name' in rule: item['name'] = rule['name'] if 'coins' in rule: item['coins'] = rule['coins'] if 'price' in rule: item['price'] = rule['price'] if 'time' in rule: item['time'] = rule['time'] if 'description' in rule: item['description'] = rule['description'] if 'unit' in rule: item['unit'] = rule['unit'] if 'sn' in rule: item['sn'] = rule['sn'] if 'minFee' in rule and rule['minFee'] and float(rule['minFee']) > 0: item.update({'minFee': rule.get('minFee')}) if 'minAfterStartCoins' in rule and rule['minAfterStartCoins'] and float(rule['minAfterStartCoins']) > 0: item.update({'minAfterStartCoins': rule.get('minAfterStartCoins')}) if is_free_service: item.update({'description': '当前处于免费时段'}) item.update(displaySwitchs) packages.append(item) return sorted(packages, key=lambda x: (x.get('sn'), x.get('id'))) def get_customize_score_unit(self): return None def do_heartbeat(self, value, ts): pass class OnlineSmartBox(SmartBox): def __init__(self, device): super(OnlineSmartBox, self).__init__(device) @testcase_point() def check_dev_status(self, attachParas = None): """ 如果超过两个心跳周期没有报心跳,并且最后一次更新时间在2个小时内,需要从设备获取状态 否则以缓存状态为准。 :param attachParas: :return: """ if not self.device.need_fetch_online: raise ServiceException( {'result': 2, 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_FAIL)}) if self.device.online == DeviceOnlineStatus.DEV_STATUS_ONLINE: retry = 3 timeout = 12 else: retry = 2 timeout = 10 operation_result = MessageSender.send(device = self.device, cmd = DeviceCmdCode.GET_DEVINFO, payload = { 'IMEI': self.device.devNo, 'fields': ['signal', 'pulse_open', 'board_volt', 'board_valid'] }, timeout = timeout, retry = retry) if operation_result['rst'] != ErrorCode.DEVICE_SUCCESS: if operation_result['rst'] == ErrorCode.DEVICE_CONN_FAIL: raise ServiceException( { 'result': 2, 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_CHECK_FAIL) }) else: raise ServiceException( { 'result': 2, 'description': u'检测设备状态失败({})'.format(operation_result['rst']) }) else: if 'pulse_open' in operation_result and (not operation_result['pulse_open']): raise ServiceException( { 'result': 2, 'description': u'检测设备状态失败({})'.format(ErrorCode.PULSE_IS_CLOSE) }) if 'board_valid' in operation_result and 'board_volt' in operation_result and operation_result[ 'board_valid'] != 2: if operation_result['board_volt'] != operation_result['board_valid']: raise ServiceException( { 'result': 2, 'description': u'当前设备正在工作,请稍后再试' }) def test(self, coins): now_time = datetime.datetime.now() return MessageSender.send(device = self.device, cmd = DeviceCmdCode.PAY_MONEY, payload = { 't': int(time.mktime(now_time.timetuple())), 'duration': coins * 60, 'app_pay': coins }) @testcase_point() def start_device(self, package, openId, attachParas): pay_count = int(package['coins']) result = MessageSender.net_pay(self.device, pay_count, timeout = MQTT_TIMEOUT.START_DEVICE) if result['rst'] == ErrorCode.DEVICE_CONN_FAIL: raise DeviceNetworkTimeoutError() elif result['rst'] != ErrorCode.DEVICE_SUCCESS: logger.debug('OnlineSmartBox() failed to start, result was=%s' % (json.dumps(result),)) raise ServiceException({'result': 2, 'description': DeviceErrorCodeDesc.get(result['rst'])}) try: duration = self.get_duration(package) result['finishedTime'] = (int(time.time()) + duration) Device.update_dev_control_cache(self._device['devNo'], { 'status': Const.DEV_WORK_STATUS_WORKING, 'finishedTime': result['finishedTime'] }) except Exception as e: logger.exception('error = %s' % e) return result def get_total_coin(self): result = MessageSender.send(self.device, DeviceCmdCode.GET_DEVINFO, {'cmd': DeviceCmdCode.GET_DEVINFO, 'IMEI': self._device['devNo']}) if result['rst'] != ErrorCode.DEVICE_SUCCESS: logger.debug('OnlineSmartBox() failed to get total coin, result was=%s' % (json.dumps(result),)) description = u'当前设备信号弱没有响应,请您稍后重试。' raise ServiceException({'result': 2, 'description': description}) if not result.has_key('total_coin'): raise ServiceException({'result': 2, 'description': u'当前设备暂时不支持获取总的硬币数目,待版本自动升级后,会支持'}) return result['total_coin'] def check_alarm(self, alarm): if alarm.faultCode == FAULT_CODE.OFFLINE: dev_info = MessageSender.send(device = self.device, cmd = DeviceCmdCode.GET_DEVINFO, payload = {'IMEI': self.device.devNo, 'fields': []}, timeout = MQTT_TIMEOUT.SHORT) if dev_info['rst'] == 0: return u'设备状态检查在线,网络通畅,网络可能出现闪断' else: raise ServiceException({'result': 2, 'description': u'设备玩命也无法找到网络,设备可能不在线'}) else: return u'无法检查该设备的告警状态,建议您用其他方式确认此告警是否正常' def async_update_portinfo_from_dev(self): return class MqttSmartBox(SmartBox): def check_dev_status(self, attachParas = None): """ 如果超过两个心跳周期没有报心跳,并且最后一次更新时间在2个小时内,需要从设备获取状态 否则以缓存状态为准。 :param attachParas: :return: """ if not self.device.need_fetch_online: raise ServiceException( {'result': 2, 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_FAIL)}) operation_result = MessageSender.send(device = self.device, cmd = DeviceCmdCode.GET_DEVINFO, payload = { 'IMEI': self.device.devNo, 'fields': ['signal'] }, timeout = MQTT_TIMEOUT.TEST) if operation_result['rst'] != ErrorCode.DEVICE_SUCCESS: if operation_result['rst'] == ErrorCode.DEVICE_CONN_FAIL: raise ServiceException( { 'result': 2, 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_CHECK_FAIL) }) else: raise ServiceException( { 'result': 2, 'description': u'检测设备状态失败({})'.format(operation_result['rst']) }) def make_six_bytes_session_id(): # 至少可以用到2050 ts = long(time.time() * 100000) ts = ts + long(get_random_str(2, seq = string.digits)) rv = fill_2_hexByte(hex(ts), 12) if rv[6:8] == 'AA': logger.debug('AA in session id.') return rv[0:6] + 'A9' + rv[8:] else: return rv def _record_error_times(devNo, portStr, box): """ 记录 启动错误的 次数 超过次数报警处理 具体处理方式由协议函数实现 :param devNo: 设备号 :param portStr: 端口号 :param box: SmartBox :return: """ dev = Device.get_dev(devNo) errorTimes = dev.get("otherConf", {}).get("errorTimes", None) # 如果设备没有设置启动失败的次数,不做任何处理 if not errorTimes: return times = Device.get_error_start_times(devNo, portStr) Device.set_error_start_times(devNo, portStr, times + 1) # 超过上限次数之后 缓存清除 触发响应操作 if times + 1 >= errorTimes: box.handle_out_start_error(portStr) def _clear_error_times(devNo, portStr, box): """ 启动成功之后 删除错误计数 并进行相关的处理 :param devNo: 设备号 :param portStr: 端口号 :param box: SmartBox :return: """ dev = Device.get_dev(devNo) errorTimes = dev.get("otherConf", {}).get("errorTimes", None) # 如果设备没有设置启动失败的次数,不做任何处理 if not errorTimes: return times = Device.get_error_start_times(devNo, portStr) Device.delete_error_start_times(devNo, portStr) if times >= errorTimes: box.handle_clear_start_error(portStr) def start_error_timer(missMessages=None): """ 装饰器 用来装饰start_device函数,记录失败次数 并且超过一定次数的时候做响应的处理 :param missMessages: 忽略的消息列表 ex [u"设备端口已经被占用,请稍后再试"] :return: """ if missMessages is None or not isinstance(missMessages, list): missMessages = list() def outFunc(func): @wraps(func) def inFunc(box, package, openId, attachParas): chargeIndex = attachParas.get("chargeIndex", 0) try: result = func(box, package, openId, attachParas) except ServiceException as e: message = e.result.get("description") if message not in missMessages: _record_error_times(box._device["devNo"], chargeIndex, box) raise e else: _clear_error_times(box._device["devNo"], chargeIndex, box) return result return inFunc return outFunc # 订单编号,需要根据云快充的协议生成:生成规则为 格式桩号(7bytes)+枪号(1byte)+年月日时分秒(6bytes)+自增序号 #(2bytes);示例:32010600019236 01 200106180342 3060 def make_cartcp_order_no(devNo, portIndex): increCount = 0 try: increCount = caches['devmgr'].incr('increCount',1) except Exception ,e: caches['devmgr'].set('increCount',0) hexIndex = fill_2_hexByte(hex(int(portIndex)),2) hexIncre = fill_2_hexByte(hex(increCount), 4) nowTime = datetime.datetime.now() year = nowTime.year - 2000 strTime = str(year) + nowTime.strftime('%m%d%H%M%S') orderNo = '%s%s%s%s' % (devNo,hexIndex,strTime,hexIncre) return orderNo def make_dianchuan_order_no(devNo): increCount = 0 try: increCount = caches['devmgr'].incr('increCount',1) except Exception ,e: caches['devmgr'].set('increCount',0) hexIncre = fill_2_hexByte(hex(increCount), 4) nowTime = datetime.datetime.now() year = nowTime.year - 2000 strTime = str(year) + nowTime.strftime('%m%d%H%M%S') orderNo = '%s%s%s' % (devNo,strTime,hexIncre) return orderNo class SendManager(object): def __init__(self, sender = MessageSender, visitor = "manager"): self._sender = sender self._rst = None self._tError = None self._sError = None self._cError = None add_exc_type = getattr(self, "add_{}_exc_type".format(visitor), self.add_manager_exc_type) add_exc_type() def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if self.rst == 0: return elif self.rst == -1: raise self._tError elif self.rst == 1: raise self._sError else: raise self._cError def add_client_exc_type(self): setattr(self, "_tError", ClientServiceTimeOutException()) setattr(self, "_sError", ClientServiceSerialException()) setattr(self, "_cError", ServiceException()) def add_manager_exc_type(self): setattr(self, "_tError", ManagerServiceTimeOutException()) setattr(self, "_sError", ManagerServiceSerialException()) setattr(self, "_cError", ServiceException()) def add_tester_exc_type(self): setattr(self, "_tError", TestTimeOutError()) setattr(self, "_sError", TestSerialError()) setattr(self, "_cError", TestError()) def send(self, *args, **kwargs): return self._sender.send(*args, **kwargs) @property def rst(self): return self._rst @rst.setter def rst(self, result): self._rst = result.get("rst")