# coding=utf-8 import logging import time from typing import TYPE_CHECKING from apilib.utils_json import JsonResponse from apilib.utils_string import cn from apps import serviceCache from apps.web.constant import Const, DeviceCmdCode, ErrorCode, MQTT_TIMEOUT from apps.web.core.adapter.base import SmartBox, hexbyte_2_bin, fill_2_hexByte, SendManager from apps.web.core.exceptions import ServiceException from apps.web.device.models import Device from apps.web.core.exceptions import TestError logger = logging.getLogger(__name__) if TYPE_CHECKING: from apps.web.core.device_define import PackageDict from apps.web.user.models import ConsumeRecord class WasherBox(SmartBox): WASH_NAME_MAP = { Const.WASHER_BOX_SET_DWX: "01", Const.WASHER_BOX_SET_BZX: "02", Const.WASHER_BOX_SET_KSX: "04", Const.WASHER_BOX_SET_DTS: "08", Const.WASHER_BOX_SET_TQJ: "10", } # 匹配烘干机的需求 HONG_GAN = { u"加时烘干": "08", u"快速烘干": "04", u"标准烘干": "02", u"特别烘干": "01" } HONG_GAN_PROGRAM_MAP = { "01": "加时烘干", "02": "快速烘干", "03": "标准烘干", "04": "特别烘干", } DEVICE_STATUS_MAP = { "01": (Const.DEV_WORK_STATUS_IDLE, u"待机"), "02": (Const.DEV_WORK_STATUS_WORKING, u"运转"), "03": (Const.DEV_WORK_STATUS_PAUSE, u"暂停"), "04": (Const.DEV_WORK_STATUS_FAULT, u"故障"), "05": (Const.DEV_WORK_STATUS_WORKING, u"参数设置"), "06": (Const.DEV_WORK_STATUS_WORKING, u"自检") } PROCESS_MAP = { '01': u'预约', '02': u'浸泡', '03': u'洗涤', '04': u'漂洗', '05': u'脱水', '06': u'主进水' } PROGRAM_MAP = { "01": "单脱水", "02": "快速1", "03": "快速2", "04": "标准", "05": "大物", } FAULT_MAP = { "0100": u"进水超时", "0200": u"排水超时", "0400": u"脱水开盖", "0800": u"脱水不平衡", "1000": u"通讯故障", "2000": u"童锁开盖", "4000": u"溢水", "8000": u"水位传感器故障", "0001": u"数据存储故障", "0002": u"温度传感器故障", "0004": u"无水加热故障", "0008": u"电机堵转故障", "0010": u"IPM温度过高故障", "0020": u"电机过流故障", "0040": u"电机缺相故障", "0080": u"驱动器故障" } START_MAP = { 'dtStart': "08", 'ksStart': "04", 'bzStart': "02", 'dwStart': "01", "stop": "20", "pause": "40", "continue": "80" } def __init__(self, *args): super(WasherBox, self).__init__(*args) self.devTypeCode = self.device.get("devType", dict()).get("code", Const.DEVICE_TYPE_CODE_WASHER_CY_HS) def _send_data(self, funCode, data=None, timeout=None, extra=None, visitor="manager"): timeout = timeout or MQTT_TIMEOUT.NORMAL data = data or "" payload = {"funCode": funCode, "data": data} if extra is None: extra = dict() payload.update(extra) with SendManager(visitor=visitor) as sender: result = sender.send( device=self.device, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, payload=payload, timeout=timeout ) sender.rst = result # sender.rst = {"rst": 1} return result def _get_device_info(self, visitor="manager"): """ 获取 洗衣机的状态 :return: """ result = self._send_data("A1", visitor=visitor) data = result.get("data", "") status, statusDesc = WasherBox.DEVICE_STATUS_MAP.get(data[6:8], (Const.DEV_WORK_STATUS_IDLE, u"待机")) if self.device.devTypeCode == "1003053": program = WasherBox.HONG_GAN_PROGRAM_MAP.get(data[8:10]) process = "" else: program = WasherBox.PROGRAM_MAP.get(data[8:10]) process = WasherBox.PROCESS_MAP.get(data[10:12]) leftTime = int(data[14:16] + data[12:14], 16) if data[16:20] != "0000": status = Const.DEV_WORK_STATUS_FAULT statusDesc = WasherBox.FAULT_MAP.get(data[18:20] + data[16:18], u"未知错误") if status == Const.DEV_WORK_STATUS_FAULT: statusInfo = statusDesc elif status == Const.DEV_WORK_STATUS_FORBIDDEN: statusInfo = u'禁用' elif status == Const.DEV_WORK_STATUS_IDLE: statusInfo = u'待机' elif status == Const.DEV_WORK_STATUS_PAUSE: statusInfo = u'暂停' else: statusInfo = u'%s 当前套餐:%s 当前环节:%s' % (statusDesc, program, process) return {'status': status, 'statusInfo': statusInfo, 'leftTime': leftTime} def _check_83_params(self, timeDict): """ 根据设备类型校验 时间是否会超出主板的最大时间设置 """ timeDt = int(timeDict.get("timeDt", 0)) timeKs = int(timeDict.get("timeKs", 0)) timeBz = int(timeDict.get("timeBz", 0)) timeDw = int(timeDict.get("timeDw", 0)) # 滚筒的 if self.device.devType.get("code", Const.DEVICE_TYPE_CODE_WASHER_CY_HS) == Const.DEVICE_TYPE_CODE_WASHER_CY_HJ: if timeDt < 5 or timeDt > 15: raise ServiceException({"result": 2, "description": u"单脱水时间范围为5-15分钟"}) if timeKs < 20 or timeKs > 35: raise ServiceException({"result": 2, "description": u"快速洗时间范围为20-35分钟"}) if timeBz < 35 or timeBz > 50: raise ServiceException({"result": 2, "description": u"标准洗时间范围为35-50分钟"}) if timeDw < 50 or timeDw > 65: raise ServiceException({"result": 2, "description": u"大物洗时间范围为50-65分钟"}) elif self.device.devType.get("code", Const.DEVICE_TYPE_CODE_WASHER_CY_HS) == Const.DEVICE_TYPE_CODE_WASHER_CY_HS: if timeDt < 5 or timeDt > 9: raise ServiceException({"result": 2, "description": u"单脱水时间范围为5-9分钟"}) if timeKs < 20 or timeKs > 32: raise ServiceException({"result": 2, "description": u"快速洗时间范围为20-32分钟"}) if timeBz < 35 or timeBz > 43: raise ServiceException({"result": 2, "description": u"标准洗时间范围为35-43分钟"}) if timeDw < 45 or timeDw > 53: raise ServiceException({"result": 2, "description": u"大物洗时间范围为45-53分钟"}) elif self.device.devTypeCode == Const.DEVICE_TYPE_CODE_WASHER_CY_HJ_1: if timeDt < 5 or timeDt > 15: raise ServiceException({"result": 2, "description": u"单脱水时间范围为5-15分钟"}) if timeKs < 18 or timeKs > 35: raise ServiceException({"result": 2, "description": u"快速洗时间范围为18-35分钟"}) if timeBz < 30 or timeBz > 45: raise ServiceException({"result": 2, "description": u"标准洗时间范围为30-45分钟"}) if timeDw < 40 or timeDw > 55: raise ServiceException({"result": 2, "description": u"大物洗时间范围为40-55分钟"}) else: pass def serial_test(self, payload): """ 串口测试 testMode = WASH_NAME_MAP.keys() :param payload: :return: """ timeout = 20 testMode = payload.get("testMode") funcL = WasherBox.START_MAP.get(testMode) if not testMode or not funcL: raise TestError({"description": u"不支持的测试模式"}) # 获取 洗衣机主板上的时间设定 测试的时候直接返回前台 方便与洗衣机显示屏上的数字相对应 cacheKey = "testTimeData-{}".format(self.device.devNo) testTimeData = serviceCache.get(cacheKey) if not testTimeData: result = self._send_data("A3", timeout=timeout, visitor="tester") timeData = result["data"][6:] timeDt = int(timeData[0:2], 16) timeKs = int(timeData[2:4], 16) timeBz = int(timeData[4:6], 16) timeDw = int(timeData[6:8], 16) testTimeData = { "dtStart": timeDt, "ksStart": timeKs, "bzStart": timeBz, "dwStart": timeDw } # 缓存设定10分钟 serviceCache.set(cacheKey, testTimeData, 600) # 然后下发串口数据 result = self._send_data("81", funcL+"00", timeout=timeout, visitor="tester") modeNameMap = { "dtStart": u"单脱水", "ksStart": u"快速洗", "bzStart": u"标准洗", "dwStart": u"大物洗", "stop": u"停止功能", "continue": u"继续功能", "pause": u"继续功能" } result["description"] = "测试模式:{}".format(modeNameMap.get(testMode)) if testMode in testTimeData.keys(): result["description"] += " 启动时间:{}分钟".format(testTimeData.get(testMode)) return result def start_device(self, package, openId, attachParas): washName = package.get("name", "") # 适配一下烘干 if self.device.devTypeCode == "1003053": m = WasherBox.HONG_GAN else: m = WasherBox.WASH_NAME_MAP funcLHex = m.get(washName) funcHHex = "00" if not funcLHex: raise ServiceException({'result': 2, 'description': u'无效的套餐'}) if openId: result = self._send_data(funCode="81", data=funcLHex+funcHHex, visitor="client", timeout = MQTT_TIMEOUT.START_DEVICE) else: result = self._send_data(funCode="81", data=funcLHex+funcHHex) needTime = package.get("time") devCache = { "openId": openId, "washName": washName, "needTime": needTime, } Device.update_dev_control_cache(self.device.devNo, devCache) result["finishedTime"] = 60 * int(needTime) + int(time.time()) return result def start_device_realiable(self, order): # type:(ConsumeRecord)->dict try: package = order.my_package # type: PackageDict funcLHex = WasherBox.WASH_NAME_MAP.get(package.name) funcHHex = "00" if not funcLHex: raise ServiceException({'result': 2, 'description': u'无效的套餐'}) work_time = package.estimated_duraion result = self._send_data( funCode="81", data=funcLHex+funcHHex, extra={'order_type': 'com_start', 'order_id': order.orderNo, 'time': work_time}, visitor="client", timeout = MQTT_TIMEOUT.START_DEVICE ) if result['rst'] == ErrorCode.DEVICE_CONN_FAIL: result.update({ 'fts': int(time.time()), 'order_type': 'com_start', 'order_id': order.orderNo }) except ServiceException as e: logger.exception(e) return { 'rst': ErrorCode.EXCEPTION, 'fts': int(time.time()), 'errorDesc': cn(e.result.get('description')), 'order_type': 'com_start', 'order_id': order.orderNo } except Exception as e: logger.exception(e) return { 'rst': ErrorCode.EXCEPTION, 'fts': int(time.time()), 'order_type': 'com_start', 'order_id': order.orderNo } return result def check_dev_status(self, attachParas=None): """ 检查洗衣机的当前状态 :param attachParas: :return: """ status = self._get_device_info(visitor="client").get("status") # 适配烘干机 if self.device.devTypeCode == "1003053": deviceName = "烘干机" else: deviceName = "洗衣机" info = self._get_device_info() if info['status'] == Const.DEV_WORK_STATUS_FAULT: raise ServiceException( {'result': 2, 'description': u'当前%s故障:%s' % (deviceName,info['statusInfo'])}) if u'待机' not in info['statusInfo']: raise ServiceException( {'result': 2, 'description': u'当前%s%s,请稍候使用' % (deviceName,info['statusInfo'])}) if status != Const.DEV_WORK_STATUS_IDLE: raise ServiceException({"result": 2, "description": u"请稍后使用"}) # 控制面板 def get_dev_info(self): return self._get_device_info() def get_device_function_by_key(self, keyName): if keyName == 'workingStatus': infoDict = self._get_device_info() return {"workingStatus": infoDict['statusInfo']} return None # 控制面板 def press_down_key(self, keyName): if keyName in ['stop', 'pause', 'continue']: return self.stop_pause_continue(keyName) elif keyName in ['dtStart', 'ksStart', 'bzStart', 'dwStart']: return self._send_data("81", WasherBox.START_MAP.get(keyName) + "00") def stop_pause_continue(self, state): if state == 'stop': cmd = '20' elif state == 'pause': cmd = '40' elif state == 'continue': cmd = '80' else: raise ServiceException({"result": 2, "description": u"参数错误"}) cmd += '00' result = self._send_data("81", cmd) resCode = result['data'][6:8] if resCode == '0F': raise ServiceException({'result': 2, 'description': u'主板通讯失败,请试试投币,并报障给老板哦'}) if state == 'stop': Device.update_dev_control_cache(self._device['devNo'], {'status': Const.DEV_WORK_STATUS_IDLE}) else: Device.update_dev_control_cache(self._device['devNo'], {'status': Const.DEV_WORK_STATUS_WORKING}) return result def get_config_A2(self): # 获取价格设置 result = self._send_data("A2") priceData = result["data"][6:] priceDt = int(priceData[0:2], 16) priceKs = int(priceData[2:4], 16) priceBz = int(priceData[4:6], 16) priceDw = int(priceData[6:8], 16) sourceData = { "priceDt": priceDt, "priceKs": priceKs, "priceBz": priceBz, "priceDw": priceDw } # 保留源数据 otherConf = self.device.get("otherConf", dict()) otherConf.update(sourceData) Device.objects.filter(devNo = self.device.devNo).update(otherConf = otherConf) Device.invalid_device_cache(self.device.devNo) return sourceData def get_config_A3(self): result = self._send_data("A3") timeData = result["data"][6:] timeDt = int(timeData[0:2], 16) timeKs = int(timeData[2:4], 16) timeBz = int(timeData[4:6], 16) timeDw = int(timeData[6:8], 16) sourceData = { "timeDt": timeDt, "timeKs": timeKs, "timeBz": timeBz, "timeDw": timeDw } otherConf = self.device.get("otherConf", dict()) otherConf.update(sourceData) Device.objects.filter(devNo = self.device.devNo).update(otherConf = otherConf) Device.invalid_device_cache(self.device.devNo) return sourceData def get_config_A4(self): result = self._send_data("A4") temp = result['data'][6:16] paramKs = int(temp[0:2], 16) paramBz = int(temp[2:4], 16) paramDw = int(temp[4:6], 16) sourceData = { "paramKs": paramKs, "paramBz": paramBz, "paramDw": paramDw } otherConf = self.device.get("otherConf", dict()) otherConf.update(sourceData) Device.objects.filter(devNo = self.device.devNo).update(otherConf = otherConf) Device.invalid_device_cache(self.device.devNo) return sourceData def get_config_A5(self): result = self._send_data("A5") dataLHex = result['data'][6:8] dataLBin = hexbyte_2_bin(dataLHex)[::-1] return { "ddjy": bool(int(dataLBin[0])), "qcbcbh": bool(int(dataLBin[1])), "tqjgn": bool(int(dataLBin[3])), "aj": bool(int(dataLBin[4])), "tb": bool(int(dataLBin[5])) } def get_config_12(self): """ 自定义指令 一次性将A2-A5参数设置的数据全部取出 :return: """ result = self._send_data("12") data = result.get("data", "") if data[6:8] != "00": raise ServiceException({"result": 2, "description": u"获取参数配置失败"}) priceData = data[8:24] timeData = data[24:40] waterData = data[40:56] switchData = data[56:60] sourceData = dict() priceDt = int(priceData[0:2], 16) priceKs = int(priceData[2:4], 16) priceBz = int(priceData[4:6], 16) priceDw = int(priceData[6:8], 16) sourceData.update({ "priceDt": priceDt, "priceKs": priceKs, "priceBz": priceBz, "priceDw": priceDw }) timeDt = int(timeData[0:2], 16) timeKs = int(timeData[2:4], 16) timeBz = int(timeData[4:6], 16) timeDw = int(timeData[6:8], 16) sourceData.update({ "timeDt": timeDt, "timeKs": timeKs, "timeBz": timeBz, "timeDw": timeDw }) paramKs = int(waterData[0:2], 16) paramBz = int(waterData[2:4], 16) paramDw = int(waterData[4:6], 16) sourceData.update({ "paramKs": paramKs, "paramBz": paramBz, "paramDw": paramDw }) dataLBin = hexbyte_2_bin(switchData[:2])[::-1] sourceData.update({ "ddjy": bool(int(dataLBin[0])), "qcbcbh": bool(int(dataLBin[1])), "tqjgn": bool(int(dataLBin[3])), "aj": bool(int(dataLBin[4])), "tb": bool(int(dataLBin[5])) }) otherConf = self.device.get("otherConf", dict()) otherConf.update(sourceData) Device.objects.filter(devNo = self.device.devNo).update(otherConf = otherConf) Device.invalid_device_cache(self.device.devNo) return sourceData def set_config_82(self, data): priceDt = data.get("priceDt") priceKs = data.get("priceKs") priceBz = data.get("priceBz") priceDw = data.get("priceDw") if int(priceKs) > 15 or int(priceBz) > 15 or int(priceDt) > 15 or int(priceDw) > 15: raise ServiceException({"result": 2, "description": u"模式单价不能超过15元"}) sendData = "" sendData += fill_2_hexByte(hex(int(priceDt)), 2) sendData += fill_2_hexByte(hex(int(priceKs)), 2) sendData += fill_2_hexByte(hex(int(priceBz)), 2) sendData += fill_2_hexByte(hex(int(priceDw)), 2) sendData += "00000000" result = self._send_data("82", sendData) if result.get("data")[6:8] != "F0": raise ServiceException({"result": 2, "description": "主板设置参数失败"}) def set_config_83(self, data): self._check_83_params(data) timeDt = int(data.get("timeDt", 0)) timeKs = int(data.get("timeKs", 0)) timeBz = int(data.get("timeBz", 0)) timeDw = int(data.get("timeDw", 0)) sendData = "" sendData += fill_2_hexByte(hex(int(timeDt)), 2) sendData += fill_2_hexByte(hex(int(timeKs)), 2) sendData += fill_2_hexByte(hex(int(timeBz)), 2) sendData += fill_2_hexByte(hex(int(timeDw)), 2) sendData += "00000000" result = self._send_data("83", sendData) if result.get("data")[6:8] != "F0": raise ServiceException({"result": 2, "description": "主板设置参数失败"}) def set_config_84(self, data): paramKs = data.get("paramKs") paramBz = data.get("paramBz") paramDw = data.get("paramDw") sendData = "" sendData += fill_2_hexByte(hex(int(paramKs)), 2) sendData += fill_2_hexByte(hex(int(paramBz)), 2) sendData += fill_2_hexByte(hex(int(paramDw)), 2) sendData += "0000000000" result = self._send_data("84", sendData) if result.get("data")[6:8] != "F0": raise ServiceException({"result": 2, "description": "主板设置参数失败"}) def set_config_85(self, data): ddjy = data.get("ddjy") qcbcbh = data.get("qcbcbh") tqjgn = data.get("tqjgn") aj = data.get("aj") tb = data.get("tb") sendData = "00" sendData += str(int(tb)) sendData += str(int(aj)) sendData += str(int(tqjgn)) sendData += "0" sendData += str(int(qcbcbh)) sendData += str(int(ddjy)) sendData = fill_2_hexByte(hex(int(sendData, 2)), 2) result = self._send_data("85", sendData + "00") if result.get("data")[6:8] != "F0": raise ServiceException({"result": 2, "description": "主板设置参数失败"}) def get_dev_setting(self): driverVersion = Device.get_dev(self.device.devNo).get("driverVersion", "v1.0.0").rsplit(".", 1)[1] if int(driverVersion) < 3 and self.device.driverVersion.startswith("v1"): confA2 = self.get_config_A2() confA3 = self.get_config_A3() confA4 = self.get_config_A4() confA5 = self.get_config_A5() data = dict(confA2.items() + confA3.items() + confA4.items() + confA5.items()) # data = dict(confA2.items() + confA3.items() + confA4.items()) else: data = self.get_config_12() #服务器参数 if self.device['otherConf'] and self.device['otherConf'].get('cardPrice') and self.device['otherConf'].get('eachCoin'): data['cardPrice'] = self.device['otherConf']['cardPrice'] data['eachCoin'] = self.device['otherConf']['eachCoin'] Device.invalid_device_cache(self.device.devNo) return data def set_device_function(self, request, lastSetConf): if self._get_device_info().get("status") != Const.DEV_WORK_STATUS_IDLE: raise ServiceException({"result": 2, "description": "请先停止使用设备,使设备处于待机状态"}) data = self.get_config_A5() sendData = {k: v if request.POST.get(k) is None else request.POST.get(k) for k, v in data.items()} return self.set_config_85(sendData) def set_device_function_param(self, request, lastSetConf): if self._get_device_info().get("status") != Const.DEV_WORK_STATUS_IDLE: raise ServiceException({"result": 2, "description": "请先停止使用设备,使设备处于待机状态"}) priceSettings = request.POST.get("priceBz") timeSettings = request.POST.get("timeBz") paramSettings = request.POST.get("paramBz") cardPrice = request.POST.get("cardPrice") eachCoin = request.POST.get("eachCoin") #服务器参数 if cardPrice and eachCoin: if not str(cardPrice).isdigit(): raise ServiceException({"result": 2, "description": "刷卡单价必须为整数"}) if not str(eachCoin).isdigit(): raise ServiceException({"result": 2, "description": "脉冲次数必须为整数"}) cardPrice = int(cardPrice) packages = self.device['washConfig'] packages = sorted(packages.values(), key=lambda x: x["coins"]) min_coin = int(packages[0].get("coins")) max_coin = int(packages[-1].get("coins")) eachCoin = int(eachCoin) if min_coin > eachCoin or max_coin 0: faultDesc = Const.EVENT_CODE_DESC[devInfo['faultList'][0]] desc = '出现了%s,%s' % (statusDesc, faultDesc) else: desc = '出现了%s' % statusDesc return JsonResponse({'result': 0, 'description': u'洗衣机' + desc + u'。您联系下老板哦'}) return JsonResponse( { 'result': 1, 'description': '', 'payload': { 'surplus': devInfo['surplus'], 'sum': devInfo['surplus'], 'name': group['groupName'], 'address': group['address'], 'code': devType.get('code'), 'orderProcessing': False, 'logicalCode': dev['logicalCode'], 'user': 'me' if lastOpenId == request.user.openId else 'notme', 'agentFeatures': agent.features, } })