# -*- coding: utf-8 -*- #!/usr/bin/env python import copy import time from apps.web.constant import DeviceCmdCode, Const, FAULT_CODE, ErrorCode, MQTT_TIMEOUT from apps.web.core.adapter.base import SmartBox from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.device.models import Device from apps.web.user.models import ConsumeRecord, Card from apilib.utils_AES import EncryptDate cardKey = 'FR4e1OFCnDdrYA7u' class WashingWEIFULEBox(SmartBox): def __init__(self, device): super(WashingWEIFULEBox, self).__init__(device) def translate_funcode(self, funCode): funCodeDict = { '01': u'查询详细信息', '02': u'启动洗衣订单', '03': u'结束洗衣订单', '04': u'查询订单', '05': u'查询统计信息', '06': u'清除统计信息', '07': u'下发配置信息', '08': u'查询配置信息', '09': u'设置卡模式', '0A': u'查询卡模式', } return funCodeDict.get(funCode, '') def get_desc_from_code(self, errCode): errCode = hex(errCode) descDict = { '0xe1':u'获取水位失败', '0xe2':u'进水阀操作失败', '0xe3':u'排水阀操作失败', '0xe4':u'门状态获取失败', '0xe5':u'操作电机失败', '0xe6':u'蜂鸣器失败', '0xe7':u'获取电机状态失败', '0xe8':u'获取进水阀状态失败', '0xe9':u'获取出水阀的状态失败', '0xea':u'LeD错误', '0xeb':u'串口通讯失败', '0xec':u'暂时不用,没有对应的命令码', '0xed':u'操作总的继电器失败', '0xee':u'获取总继电器状态失败', '0xef':u'碰撞发生关闭订单', '0xf1':u'开门暂停订单', '0xf2':u'断电失败', '0xf3':u'水位异常', '0xf4':u'漫水事件', '0xf5':u'排水超时', '0xf6':u'脱水不平衡,暂停任务,进行平衡修正', '0xf7':u'水位相差较大,暂停任务,等待进水', '0xf8':u'脱水不平衡,建议用户手工开门修正,然后合盖运行', '0xf9':u'人工关闭订单', '0xfa':u'注水超时', '0xfb':u'设备处于禁用状态' } return descDict.get(str(errCode), u'正常完成') def translate_event_cmdcode(self, cmdCode): cmdDict = { } return cmdDict.get(cmdCode, '') def test(self, coins): data = {'funCode':0x14} devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': data}) if devInfo['rst'] not in [0, 1, -1]: devInfo['desc'] = self.get_desc_from_code(devInfo['rst']) return devInfo def check_feedback_result(self, devInfo): if not devInfo.has_key('rst'): raise ServiceException({'result': 2, 'description': u'报文异常'}) if devInfo['rst'] == -1: raise ServiceException({'result': 2, 'description': u'洗衣机正在玩命找网络,请您稍候再试'}) if devInfo['rst'] == 1: raise ServiceException({'result': 2, 'description': u'串口通讯失败,您稍候再试,或者联系客服'}) if devInfo['rst'] == 2: raise ServiceException({'result': 2, 'description': u'没有找到对应的套餐'}) if devInfo['rst'] == 3: raise ServiceException({'result': 2, 'description': u'创建订单失败'}) if devInfo['rst'] == 5: raise ServiceException({'result': 2, 'description': u'设备正在自检'}) if devInfo['rst'] == 6: raise ServiceException({'result': 2, 'description': u'任务订单已经满了,请等当前订单结束后再使用'}) def check_dev_status(self, attachParas=None): data = {'funCode':0x01} devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': data}) self.check_feedback_result(devInfo) data = devInfo['data'] if data.has_key('order') and data['order']: raise ServiceException({"result": 2, "description": u"有任务正在运行,不允许使用"}) if data['devStatus'].has_key('errors') and data['devStatus']['errors']: descList = [] for err in data['devStatus']['errors']: descList.append(self.get_desc_from_code(err)) raise ServiceException({"result": 2, "description": u"洗衣机传感器异常:%s" % ','.join(descList)}) if data['devStatus'].has_key('status') and data['devStatus']['status'] == 'forbidden': raise ServiceException({"result": 2, "description": u"当前洗衣机处于禁用状态,无法使用,请您使用其他洗衣机"}) if data['devStatus']['door'] == 1: raise ServiceException({"result": 2, "description": u"请您先关闭洗衣机盖子,然后在付费使用"}) def start_device(self, package, openId, attachParas): if attachParas is None: raise ServiceException({'result': 2, 'description': u'请您选择合适的套餐信息'}) coins = int(package['coins']) # 单位为元 onPoints = attachParas.get('onPoints') if onPoints: # 远程上分 self.stop_charging_port() orderNo = ConsumeRecord.make_no() else: orderNo = str(attachParas.get("orderNo")) data = {'funCode':0x02, 'orderNo':orderNo, 'coins':coins} devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': data}, timeout=MQTT_TIMEOUT.START_DEVICE) self.check_feedback_result(devInfo) data = devInfo['data'] if devInfo['rst'] != 0: # 成功 desc = self.get_desc_from_code(devInfo['rst']) raise ServiceException({'result': 2, 'description': u'洗衣机响应异常:%s,请您稍后再试哦' % desc}) finishedTime = int(time.time()) + 24*60*60 Device.update_dev_control_cache(self._device['devNo'], {'finishedTime':finishedTime,'status':Const.DEV_WORK_STATUS_WORKING}) devInfo['consumeOrderNo'] = orderNo devInfo['finished_time'] = finishedTime # 这个时间实际上不需要,因为用户打开个人中心的时候,可以直接到设备上获取时间 return devInfo def analyze_event_data(self, data): if data['funCode'] == 34: # 如果是结束事件,需要把reason翻译出来 order = data['order'] order['reason'] = self.get_desc_from_code(order['desc']) data['order'] = order return data def get_dev_consume_count(self): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': {'funCode': 0x05}}) self.check_feedback_result(devInfo) data = devInfo['data'] return {'cardFee': data['total_card'] / 100.0, 'coinFee': data['total_coin'] / 100.0} # 单位为分 def __translate_status_from_str(self, status): dictConf = { 'idle':Const.DEV_WORK_STATUS_IDLE, 'busy':Const.DEV_WORK_STATUS_WORKING, 'forbid':Const.DEV_WORK_STATUS_FORBIDDEN, 'fault':Const.DEV_WORK_STATUS_FAULT, 'running':Const.DEV_WORK_STATUS_WORKING } return dictConf.get(status, Const.DEV_WORK_STATUS_IDLE) # 停止该端口下的所有任务 def stop_charging_port(self, port=-2): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': {'funCode': 0x03}}) self.check_feedback_result(devInfo) if devInfo['rst'] == 0: Device.update_dev_control_cache(self._device['devNo'], {str(port): {'status': Const.DEV_WORK_STATUS_IDLE}}) return True if devInfo['rst'] == 0 else False def get_order(self, order_no): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': {'funCode': 0x04, 'orderNo': order_no}}) self.check_feedback_result(devInfo) orders = devInfo['data'].get('orders', None) curOrder = None for tmpOrder in orders: if tmpOrder['id'] == order_no: curOrder = tmpOrder break if not curOrder: return None result = {} if not curOrder.has_key('orderType'): # 有可能没有订单,应该返回空 return result result['status'] = curOrder['status'] if curOrder['orderType'] == 'apps_start': result['consumeType'] = 'mobile' try: rcd = ConsumeRecord.objects.get(orderNo=curOrder['id']) if u'虚拟卡' in rcd.remarks: result['consumeType'] = 'mobile_vcard' except Exception, e: pass elif curOrder['orderType'] == 'coin_start': result['consumeType'] = 'coin' elif curOrder['orderType'] == 'card_start': result['consumeType'] = 'card' if curOrder.has_key('needTime'): result['needTime'] = round(curOrder['needTime'] / 60000.0, 1) if curOrder.has_key('passedTime'): result['duration'] = round(curOrder['passedTime'] / 60000.0, 1) if curOrder.has_key('leftTime'): result['leftTime'] = round(curOrder['leftTime'] / 60000.0, 1) if curOrder.has_key('card_no'): result['cardNo'] = str(int(curOrder['card_no'], 16)) if curOrder.has_key('id') and (curOrder['orderType'] not in ['coin_start', 'card_charge']) : # card_charge try: rcd = ConsumeRecord.objects.get(orderNo=curOrder['id']) result['openId'] = rcd['openId'] result['coins'] = float(str(rcd['coin'])) # 都用coins result['orderNo'] = str(curOrder['id']) except Exception, e: return None return result def response_card_balance(self, cardNo, balance, result): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': {'funCode':0x0F, 'card_no':cardNo, 'balance':int(100 * float(balance)), 'result':result}}) self.check_feedback_result(devInfo) # 获取设备配置参数 def get_dev_setting(self): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': {'funCode': 0x08}}) self.check_feedback_result(devInfo) conf = devInfo['data'] nameDict = { 'dantuoshui':u'单脱水', 'kuaishuxi':u'快速洗', 'biaozhunxi':u'标准洗', 'dawuxi':u'大物洗', } pkgConf = conf['package_work_conf'] # 单位转为分钟 for name,tempConf in pkgConf.items(): tempConf['needTime'] =tempConf['needTime'] / 60000 tempConf['name'] = nameDict.get(name,'') conf['package_work_conf'].pop('tongqingjie') # 先不要显示桶清洁 result = { 'motify_dewater_unbalance': conf['motify_dewater_unbalance'], 'order_memory':conf['order_memory'], 'protect_conf':conf['protect_conf'], 'max_unbalance_time':conf['max_unbalance_time'], 'washer_status':conf['washer_status'], 'zero_level':conf['water_level_conf']['zero_level'], 'drain_low_level':conf['water_level_conf']['drain_low_level'], 'drain_delay_time':conf['water_level_conf']['drain_delay_time']/60000, 'fulled_level':conf['water_level_conf']['fulled_level'], 'water_levels':conf['water_level_conf']['water_levels'], 'package_work_conf':pkgConf, } return result # 获取设备配置参数 def set_dev_setting(self, setConf): data = {'funCode':0x01} devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': data}) self.check_feedback_result(devInfo) data = devInfo['data'] if data.has_key('order') and data['order']: raise ServiceException({"result": 2, "description": u"有任务正在运行,不允许下发配置"}) data = {'funCode':0x07,'water_level_conf':{},'package_work_conf':{}} # 如果是卡密码,直接进行修改 if setConf.has_key('card_pwd'): return self.set_card_pwd(setConf['card_pwd']) if setConf.has_key('motify_dewater_unbalance'): data['motify_dewater_unbalance'] = setConf['motify_dewater_unbalance'] if setConf.has_key('order_memory'): data['order_memory'] = setConf['order_memory'] if setConf.has_key('zero_level'): # 0水位一定要比刻度1要大。 if setConf.has_key('water_levels'): firstLevel =setConf['water_levels'][0] if setConf['zero_level'] <= firstLevel: raise ServiceException({'result': 2, 'description': u'零水位一定要比水位刻度1的值要大。(值越大,水位越低)'}) data['water_level_conf']['zero_level'] = setConf['zero_level'] if setConf.has_key('water_levels'): # 水位刻度的检查。必须一个个比一个小。 levels = setConf['water_levels'] for ii in range(len(levels)-1): if levels[ii+1] >= levels[ii]: raise ServiceException({'result': 2, 'description': u'刻度%s的值,必须大于刻度%s的值(值越大,水位越低)' % ((ii +1),(ii +2))}) data['water_level_conf']['water_levels'] = setConf['water_levels'] if setConf.has_key('fulled_level'): data['water_level_conf']['fulled_level'] = setConf['fulled_level'] if setConf.has_key('drain_low_level'): data['water_level_conf']['drain_low_level'] = setConf['drain_low_level'] if setConf.has_key('drain_delay_time'): if setConf['drain_delay_time'] <= 0 or setConf['drain_delay_time'] >= 20: raise ServiceException({'result': 2, 'description': u'排水低于低水位后的等待时间不允许为0分钟,不允许超过20分钟。'}) data['water_level_conf']['drain_delay_time'] = setConf['drain_delay_time'] * 60000 if setConf.has_key('protect_conf'): data['protect_conf'] = setConf['protect_conf'] if setConf.has_key('washer_status'): data['washer_status'] = setConf['washer_status'] if setConf.has_key('max_unbalance_time'): data['max_unbalance_time'] = setConf['max_unbalance_time'] #检查套餐的时间,并转为ms if setConf.has_key('package_work_conf'): pkgConf = setConf['package_work_conf'] for name,conf in pkgConf.items(): if name == 'dantuoshui' and conf['needTime'] < 5: raise ServiceException({'result': 2, 'description': u'单脱水的时间不允许小于5分钟'}) if name == 'kuaishuxi' and conf['needTime'] < 25: raise ServiceException({'result': 2, 'description': u'快速洗的时间不允许小于25分钟'}) if name == 'biaozhunxi' and conf['needTime'] < 35: raise ServiceException({'result': 2, 'description': u'标准洗的时间不允许小于35分钟'}) if name == 'dawuxi' and conf['needTime'] < 45: raise ServiceException({'result': 2, 'description': u'大物洗的时间不允许小于45分钟'}) conf['needTime'] = conf['needTime'] * 60000 data['package_work_conf'][name] = conf devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': data}) self.check_feedback_result(devInfo) def ack_event(self, orderNo, funCode): devInfo = MessageSender.send(self.device, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE, {'IMEI': self._device['devNo'], 'data': {'funCode':funCode, 'orderNo':orderNo}}) self.check_feedback_result(devInfo) def clear_dev_feecount(self): devInfo = MessageSender.send(self.device, self.make_random_cmdcode(), {'IMEI': self._device['devNo'], 'data': {'funCode':0x06, 'total_coin':True, 'total_card':True}}) self.check_feedback_result(devInfo) def response_card_charge_result(self, cardNo, result): MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SERVER_ASYNC, {'IMEI': self._device['devNo'], 'data': {'funCode':0x0F, 'card_no':cardNo, 'result':result}}) def reboot_device(self): data = {'funCode':0x0B, 'reset_mcu':True} MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': data}) MessageSender.async_send(self.device, DeviceCmdCode.SET_DEVINFO, {'IMEI': self._device['devNo'], 'restart': True}) def send_water_cmd(self,oper): devInfo = MessageSender.send(self.device, self.make_random_cmdcode(), {'IMEI': self._device['devNo'], 'data': {'funCode':0x10, 'oper':oper}}) self.check_feedback_result(devInfo) def recovery_water_conf(self): devInfo = MessageSender.send(self.device, self.make_random_cmdcode(), {'IMEI': self._device['devNo'], 'data': {'funCode':0x13}}) self.check_feedback_result(devInfo) def set_device_function(self, request, lastSetConf): if request.POST.has_key('clearSum'): self.clear_dev_feecount() elif request.POST.has_key('reboot'): self.reboot_device() elif request.POST.has_key('ksps') and request.POST.get('ksps'): self.send_water_cmd('drain') elif request.POST.has_key('tzps') and request.POST.get('tzps'): self.send_water_cmd('stop_drain') elif request.POST.has_key('ksjs') and request.POST.get('ksjs'): self.send_water_cmd('inject') elif request.POST.has_key('tzjs') and request.POST.get('tzjs'): self.send_water_cmd('stop_inject') elif request.POST.has_key('hfsw') and request.POST.get('hfsw'): self.recovery_water_conf() elif request.POST.has_key('tzyx') and request.POST.get('tzyx'): self.stop_charging_port() elif request.POST.has_key('xtzj') and request.POST.get('xtzj'): devInfo = self.test(1) self.check_feedback_result(devInfo) if devInfo['rst'] != 0: desc = self.get_desc_from_code(devInfo['rst']) raise ServiceException({'result': 2, 'description': u'系统自检报错:%s' % desc}) def set_device_function_param(self, request, lastSetConf): newConf = copy.deepcopy(request.POST) newConf.pop('logicalCode', None) self.set_dev_setting(newConf) def get_card_pwd(self): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': {'funCode': 0x11}}) self.check_feedback_result(devInfo) result = devInfo['data'] enObj = EncryptDate(cardKey) cardPwd = enObj.decrypt(result['card_pwd']) if not cardPwd: cardPwd = '' return {'card_pwd':cardPwd} def translante_card_no(self, hexCardNo): return int(hexCardNo, 16) def check_pwd(self, pwd): if len(pwd) != 6: raise ServiceException({'result': 2, 'description': u'密码必须是6位纯数字密码'}) for char in pwd: if not (char >= '0' and char <= '9'): raise ServiceException({'result': 2, 'description': u'密码必须是6位纯数字密码'}) return def set_card_pwd(self, pwd): self.check_pwd(pwd) enObj = EncryptDate(cardKey) devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': {'funCode':0x0B, 'card_pwd':enObj.encrypt(pwd)}}) self.check_feedback_result(devInfo) def set_card_mode(self, setConf): cardMode = int(setConf.get('card_mode')) valueDict = {'funCode':0x10} if cardMode == 0: valueDict.update({'mode':cardMode}) elif cardMode == 1: # 格式化为离线卡 self.check_pwd(setConf['new_pwd']) newPwd = setConf['new_pwd'] if not str(setConf['balance']).isdigit(): raise ServiceException({'result': 2, 'description': u'余额必须是数字'}) balance = int(setConf['balance']) if not (balance >= 0 and balance <= 5000): raise ServiceException({'result': 2, 'description': u'余额必须在0和5000元之间'}) balance = balance * 10 # 硬件模块记录的单位是角 enObj = EncryptDate(cardKey) valueDict.update({'mode':1, 'new_pwd':enObj.encrypt(str(newPwd)), 'balance':balance}) elif cardMode == 2: self.check_pwd(setConf['old_pwd']) self.check_pwd(setConf['new_pwd']) newPwd = setConf['new_pwd'] oldPwd = setConf['old_pwd'] enObj = EncryptDate(cardKey) valueDict.update({'mode':2, 'old_pwd':enObj.encrypt(str(oldPwd)), 'new_pwd':enObj.encrypt(str(newPwd))}) devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': valueDict}) self.check_feedback_result(devInfo) def get_card_mode(self): devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': {'funCode': 0x11}}) self.check_feedback_result(devInfo) cardMode = devInfo['data']['card_mode'] return {'card_mode':cardMode} def check_alarm(self, alarm): if alarm.faultCode == FAULT_CODE.MCU_REBOOT: return u'此告警,建议您多观察,如果比较频繁不停上报单台设备的告警,可能会运行不稳定。需要您联系技术支持确认。' elif alarm.faultCode == FAULT_CODE.COUNTER_FAULT: portInfo = self.get_port_info(alarm.portNo) if portInfo['status'] == Const.DEV_WORK_STATUS_FAULT: return u'此端口目前已经为故障状态,继电器可能运行不稳定,建议您联系技术支持,以确认设备运行情况。' else: return u'端口状态检查正常,继电器或存偶尔无法获取数据。暂不影响使用。' elif alarm.faultCode == FAULT_CODE.RELAY_FAULT: return u'无法进行远程诊断,建议您到现场,直接插上插座,然后检查是否不用付款,就能够充电。如果是的,就属于继电器粘连。' elif alarm.faultCode == FAULT_CODE.DEV_OVERLOAD: return u'整机功率最大限定为7500瓦,接入的负载超过此负载,为了安全,将强行关闭所有充电端口。' elif alarm.faultCode == FAULT_CODE.COPY_CARD: return u'出现一模一样的卡,可能是用户复制了另外一张离线卡,然后使用,会给您造成经济上的损失,建议冻结。' return '' def recharge_card(self, cardNo, money, orderNo=None): hex_cardNo = hex(int(cardNo))[2::].replace('L', '').upper() result = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SERVER_ASYNC, {'IMEI': self.device['devNo'], 'data': {'funCode': 37, 'result': 1, 'card_no': hex_cardNo, 'charge': int(money * 100), 'orderNo': orderNo}}) # 返回验证 self.check_feedback_result(result) card = Card.objects.filter(cardNo=cardNo, dealerId=self.device.ownerId).first() balance = card.balance + money return { 'result': ErrorCode.SUCCESS, 'description': '' }, balance def get_device_function_by_key(self,keyName): if 'waterlevel' in keyName: data = {'funCode':0x01} devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {'IMEI': self._device['devNo'], 'data': data}, timeout=MQTT_TIMEOUT.START_DEVICE) self.check_feedback_result(devInfo) devStatus = devInfo['data']['devStatus'] desc = '' if devStatus.has_key('errors') and devStatus['errors']: descList = [] for err in devStatus['errors']: descList.append(self.get_desc_from_code(err)) desc = ';'.join(descList) return { "waterlevel": devStatus['waterlevel'], "watervalue":devStatus['watervalue'], "drain":devStatus['drain'], 'door':devStatus['door'], 'machine':devStatus['machine'], 'inject':devStatus['inject'], 'desc':desc, 'status':devInfo['data']['status'] } return None