# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import time from apps.web.constant import DeviceCmdCode, MQTT_TIMEOUT from apps.web.core.adapter.base import SmartBox, asc_to_string, reverse_hex, hexbyte_2_bin, fill_2_hexByte from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.device.models import Device, Group from apps.web.user.models import MyUser from taskmanager.mediator import task_caller class FunCode(object): GET_DEV_ATTR = "12" GET_DEV_STATUS = "13" GET_PARAS = "15" SET_PARAS = "16" LOCK_DEVICE = "17" SET_LEFT_CUP = "18" CHARGE_MONEY = "19" RECOVERY = "1A" SET_CONCENTRATION = "1B" REMOTE_CUP = "21" REBOOT = "22" class ZhenguduoBox(SmartBox): DEV_MOD = { 0: u"Boot模式", 1: u"App模式" } DRINK_MOD = { 0 : u"禁止", 1 : u"定量热饮", 2 : u"定量冷饮", # 3 : u"定量冷饮+定量热饮", # 4 : u"定量热水", # 5 : u"定量冷水", # 6 : u"定量热水+定量冷水", # 7 : u"连续热饮", # 8 : u"连续冷饮", # 9 : u"连续冷饮+连续热饮", # 10: u"连续热水", # 11: u"连续冷水", # 12: u"连续热水+连续冷水" } CUP_MOD = { 0: u"全关", # 1: u"杯检测打开", 2: u"落杯+杯检测", # 3: u"落盖+杯检测", 4: u"落杯+落盖+杯检测" } def _send_data(self, funcCode, sendData=None, cmd=None, timeout=MQTT_TIMEOUT.NORMAL): """ 发送 报文 :param funcCode: 串口命令 :param sendData: 发送数据 :param cmd: 报文命令 :param timeout: 超时时间 :return: """ if sendData is None: sendData = "" if cmd is None: cmd = DeviceCmdCode.OPERATE_DEV_SYNC if timeout is None: timeout = MQTT_TIMEOUT.NORMAL result = MessageSender.send(device = self.device, cmd = cmd, payload = { "IMEI": self._device["devNo"], "funCode": funcCode, "data": sendData }, timeout = timeout) if "rst" in result: if result["rst"] == -1: raise ServiceException({'result': 2, 'description': u'饮料机正在玩命找网络,请稍候再试'}) elif result["rst"] == 1: raise ServiceException({'result': 2, 'description': u'饮料机主板连接故障'}) elif result["rst"] == 0: return result else: raise ServiceException({'result': 2, 'description': u'系统错误'}) else: raise ServiceException({'result': 2, 'description': u'系统错误'}) def get_dev_attr(self): """ 获取设备属性 :return: """ result = self._send_data(funcCode=FunCode.GET_DEV_ATTR) data = result.get("data") devMode = self.DEV_MOD.get(int(data[8:10], 16), u"Boot模式") buttonNum = int(data[10: 12], 16) portNum = int(data[12: 14], 16) serialNo = data[14: 30] typeNo = asc_to_string(data[30: 66]) version = asc_to_string(data[66: 82]) return { "devMode": devMode, "buttonNum": buttonNum, "portNum": portNum if portNum <= 5 else 5, "serialNo": serialNo, "typeNo": typeNo, "version": version } def get_dev_status(self): """ 查询设备状态值 :return: """ result = self._send_data(funcCode=FunCode.GET_DEV_STATUS) data = result.get("data") portNum = (len(data) - 28) / 8 leftCupNum = int(reverse_hex(data[8: 16]), 16) if data[8: 16] != "FFFFFFFF" else -1 portCupNumList = list() portCupStatusList = list() for port in xrange(portNum): tempHex = reverse_hex(data[16+port*8: 24+port*8]) portCupNumList.append(int(tempHex, 16)) portBytesNum = portNum * 4 * 2 status0 = hexbyte_2_bin(data[(16 + portBytesNum):(16 + portBytesNum + 2)]) status1 = hexbyte_2_bin(data[(16 + portBytesNum + 2):(16 + portBytesNum + 4)]) status2 = hexbyte_2_bin(data[(16 + portBytesNum + 4):(16 + portBytesNum + 6)]) # 做饮料标志位 1 bin 代表一个口子,最多支持32个口子 for ii in hexbyte_2_bin(data[(24+portBytesNum):(26+portBytesNum)]): portCupStatusList.append(ii) for ii in hexbyte_2_bin(data[(26+portBytesNum):(28+portBytesNum)]): portCupStatusList.append(ii) for ii in hexbyte_2_bin(data[(28+portBytesNum):(30+portBytesNum)]): portCupStatusList.append(ii) for ii in hexbyte_2_bin(data[(30+portBytesNum):(32+portBytesNum)]): portCupStatusList.append(ii) return { 'leftCupNum': leftCupNum, # 设备剩余做杯数 'portCupNumList': portCupNumList, # 各口子累积杯数 'isReady': int(status0[0]), # 上电后完成上电抽水和上电加热的待机标志 'isLackWater': int(status0[1]), # 抽水/缺水标志 'isHotting': int(status0[2]), # 加热标志 'isConnectFault': int(status0[3]), # 按键板和驱动板通信故障标志 'isHottingTimeout': int(status0[4]), # 加热超时标志 'isColdingTimeout': int(status0[5]), # 制冷超时标志 'isHotWaterSensorFault': int(status0[6]), # 热水温度传感器故障标志 'isColdWaterSensorFault': int(status0[7]), # 冷水温度传感器故障标志 'isLock': int(status1[0]), # 童锁标志 'isLackOfCup': int(status1[1]), # 缺杯标志 'isLackOfCap': int(status1[2]), # 缺盖标志 'isLackOfPowder': int(status1[3]), # 缺粉标志 'isLackOfSyrup': int(status1[4]), # 缺浆标志 'isPowderMachineFault': int(status1[5]), # 出粉电机故障标志 'isWaterMachineFault': int(status1[6]), # 抽水超时/抽水电机故障标志 'isMixMachineFault': int(status1[7]), # 搅拌电机故障标志 'isHotWaterValveFault': int(status2[0]), # 冷水电磁阀故障标志 'isColdWaterValveFault': int(status2[1]), # 热水电磁阀故障标志 'isDeviceLock': int(status2[2]), # 设备强制锁定标志 # 'portCupStatusList': portCupStatusList # 做饮料标志 } def get_port_paras(self, port): """ 获取端口设置 :param port: 端口号 :return: """ portHex = fill_2_hexByte(hex(int(port)), 2) result = self._send_data(funcCode=FunCode.GET_PARAS, sendData=portHex) data = result.get("data") portNum = int(reverse_hex(data[8: 10]), 16) makeDrinkMode = int(reverse_hex(data[10: 12]), 16) hotWater = int(reverse_hex(data[12: 16]), 16) coldWater = int(reverse_hex(data[16: 20]), 16) hotPowder = int(reverse_hex(data[20: 24]), 16) coldPowder = int(reverse_hex(data[24: 28]), 16) # 更新缓存中的饮料模式 Device.update_dev_control_cache(self._device["devNo"], {str(portNum+1): {"makeDrinkMode": makeDrinkMode}}) return { "portNum": portNum, "makeDrinkMode": str(makeDrinkMode), "hotWater": hotWater, "coldWater": coldWater, "hotPowder": hotPowder, "coldPowder": coldPowder } def get_dev_paras(self): """ 获取设备参数, 命令字与获取端口参数一样,传递的端口号为 FF :return: """ result = self._send_data(funcCode=FunCode.GET_PARAS, sendData="FF") data = result.get("data")[8: ] hotSwitch = int(reverse_hex(data[2:4]), 16) hotWaterMax = int(reverse_hex(data[4:8]), 16) hotWaterMin = int(reverse_hex(data[8:12]), 16) coldSwitch = int(reverse_hex(data[12:14]), 16) coldWaterMax = int(reverse_hex(data[14:18]), 16) coldWaterMin = int(reverse_hex(data[18:22]), 16) # 第二版协议修改 童锁时间改为2个字节 去掉锁定开关 杯检测和落杯合并为杯模式 lockTime = int(reverse_hex(data[22:26]), 16) payModuleSwitch = int(reverse_hex(data[26:28]), 16) cupModule = int(reverse_hex(data[28:30]), 16) lightSwitch = int(reverse_hex(data[30:32]), 16) leftCupSwitch = int(reverse_hex(data[32:34]), 16) return { 'hotSwitch': hotSwitch, # 加热开关 'hotWaterMax': hotWaterMax, # 热水温上限 'hotWaterMin': hotWaterMin, # 热水温下限 'coldSwitch': coldSwitch, # 制冷开关 'coldWaterMax': coldWaterMax, # 冷水温上限 'coldWaterMin': coldWaterMin, # 冷水温下限 'lockTime': lockTime, # 童锁时间 'payModuleSwitch': payModuleSwitch, # 支付模块开关 'cupModule': cupModule, # 杯模式 'lightSwitch': lightSwitch, # 灯带开关 'leftCupSwitch': leftCupSwitch # 剩余杯数开关 (不支持) } def set_port_paras(self, port, setConf): """ 设置设备参数 :param port: 端口号 :param setConf: 设置参数 :return: """ # 注意:99.9秒(999)>=热水水量>=热水粉量+2秒(20), # 999秒(999)>=冷水水量>=冷水粉量+2秒(20) # 95度>=热水温上限>=热水温下限+5度>=5度, # 20度>=冷水温上限>=冷水温下限+5度>=3度, # 童锁时间<=999秒 hotWater = int(setConf.get("hotWater", 0)) coldWater = int(setConf.get("coldWater", 0)) hotPowder = int(setConf.get("hotPowder", 0)) coldPowder = int(setConf.get("coldPowder", 0)) makeDrinkMode = int(setConf.get("makeDrinkMode")) # 热饮检测 if makeDrinkMode == 1 and not (999 >= hotWater >= hotPowder + 20): raise ServiceException({'result': 2, 'description': u'热水水量以及热粉必须满足如下条件:99.9>=热水水量>=热粉分量+20'}) # 冷饮检测 if makeDrinkMode == 2 and not (999 >= coldWater >= coldPowder + 20): raise ServiceException({'result': 2, 'description': u'冷水水量以及冷粉必须满足如下条件:99.9>=冷水水量>=冷粉分量+20'}) portHex = fill_2_hexByte(hex(int(port)), 2) dataHex = portHex dataHex += fill_2_hexByte(hex(int(setConf.get("makeDrinkMode"))), 2, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf.get("hotWater"))), 4, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf.get("coldWater"))), 4, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf.get("hotPowder"))), 4, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf.get("coldPowder"))), 4, reverse=True) result = self._send_data(funcCode=FunCode.SET_PARAS, sendData=dataHex) data = result.get("data") if data[8: 10] != "01": raise ServiceException({"result": 2, "description": u"参数设置失败"}) # 更新缓存中的饮料模式 Device.update_dev_control_cache(self._device["devNo"], {str(port+1): {"makeDrinkMode": int(setConf.get("makeDrinkMode"))}}) def set_dev_paras(self, setConf): """ 设置设备参数, 命令字与设置设备端口参数一致, 端口号传递 FF :param setConf: 设置参数 :return: """ if setConf.has_key('hotWaterMax') and not (95 >= int(setConf['hotWaterMax']) >= int(setConf['hotWaterMin']) + 5 >= 5): raise ServiceException({'result': 2, 'description': u'热水水温必须满足如下条件:95度>=热水温上限>=热水温下限+5度>=5度'}) if setConf.has_key('coldWaterMax') and not (20 >= int(setConf['coldWaterMax']) >= int(setConf['coldWaterMin']) + 5 >= 3): raise ServiceException({'result': 2, 'description': u'冷水水温必须满足如下条件:20度>=冷水温上限>=冷水温下限+5度>=3度'}) if setConf.has_key('lockTime') and not (int(setConf['lockTime']) <= 999): raise ServiceException({'result': 2, 'description': u'童锁时间必须满足如下条件:童锁时间<=999秒'}) cupModule = setConf["cupModule"] if int(cupModule) not in self.CUP_MOD.keys(): raise ServiceException({'result': 2, 'description': u'无法修改此类杯模式'}) dataHex = 'FF' dataHex += fill_2_hexByte(hex(int(setConf['hotSwitch'])), 2, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['hotWaterMax'])), 4, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['hotWaterMin'])), 4, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['coldSwitch'])), 2, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['coldWaterMax'])), 4, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['coldWaterMin'])), 4, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['lockTime'])), 4, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['payModuleSwitch'])), 2, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['cupModule'])), 2, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['lightSwitch'])), 2, reverse=True) dataHex += fill_2_hexByte(hex(int(setConf['leftCupSwitch'])), 2, reverse=True) dataHex += "0000000000000000000000" result = self._send_data(funcCode=FunCode.SET_PARAS, sendData=dataHex) data = result.get("data") if data[8: 10] != "01": raise ServiceException({"result": 2, "description": u"参数设置失败"}) def lock_dev(self, lock, lockType=None): """ 锁定解锁设备 :param lock: 锁定/解锁 0为解锁 1为上锁 :param lockType: 锁定方式 童锁/远程锁 :return: """ if lockType is None: lockType = 0 lockTypeHex = fill_2_hexByte(hex(int(lockType)), 2) lockHex = fill_2_hexByte(hex(int(lock)), 2) result = self._send_data(funcCode=FunCode.LOCK_DEVICE, sendData=lockTypeHex+lockHex) data = result.get("data") if data[8: 10] != "01": raise ServiceException({"result": 2, "description": u"设置失败"}) # def set_left_cup(self, leftCupSwitch, leftCupNum): # """ # 设置剩余杯数开关以及数量 # :param leftCupSwitch: # :param leftCupNum: # :return: # """ # if not leftCupSwitch: # leftCupNumHex = "00000000" # else: # leftCupNumHex = fill_2_hexByte(hex(int(leftCupNum)), 8, reverse=True) # # leftCupSwitchHex = fill_2_hexByte(hex(int(leftCupSwitch)), 2, reverse=True) # result = self._send_data(funcCode=FunCode.SET_LEFT_CUP, sendData=leftCupSwitchHex+leftCupNumHex) # data = result.get("data") # # if data[8: 10] != "01": # raise ServiceException({"result": 2, "description": u"设置失败"}) # def charge(self, chargeMoney): # """ # 充值/撤销充值 # :param chargeMoney: 金额为正数是充值, 金额为负数是撤销充值 # :return: # """ # chargeHex = fill_2_hexByte(hex(int(chargeMoney)), 8) # result = self._send_data(funcCode=FunCode.CHARGE_MONEY, sendData=chargeHex) # data = result.get("data") # # if data[8: 10] != "01": # raise ServiceException({"result": 2, "description": u"充值失败"}) def recovery(self, mode=None): """ 恢复出厂设置 :param mode: 低级模式 高级模式 :return: """ if mode is None: mode = 0 modeHex = fill_2_hexByte(hex(int(mode)), 2, reverse=True) result = self._send_data(funcCode=FunCode.RECOVERY, sendData=modeHex) data = result.get("data") if data[8: 10] != "01": raise ServiceException({"result": 2, "description": u"恢复出场设置失败"}) # def set_concentration(self, keyNo, value): # """ # 设置 杯的浓度 # :param keyNo: 按键序号 # :param value: 浓度 # :return: # """ # keyHex = fill_2_hexByte(hex(int(keyNo)), 2, reverse=True) # valueHex = fill_2_hexByte(hex(int(value)), 2, reverse=True) # result = self._send_data(funcCode=FunCode.SET_CONCENTRATION, sendData=keyHex+valueHex) # data = result.get("data") # # if data[8: 10] != "01": # raise ServiceException({"result": 2, "description": u"杯浓度设置失败"}) def remote_cup(self, keyNo): """ 远程做杯 :param keyNo: 案件序号 :return: """ keyHex = fill_2_hexByte(hex(int(keyNo)), 2, reverse=True) result = self._send_data(funcCode=FunCode.REMOTE_CUP, sendData=keyHex) data = result.get("data") if data[8: 10] != "01": raise ServiceException({"result": 2, "description": u"做杯失败,请联系经销商!"}) return result def reboot(self, mode=None): """ 重启设备 :param mode: 普通重启/boot重启/app重启 :return: """ if mode is None: mode = "0" modeHex = fill_2_hexByte(hex(int(mode)), 2, reverse=True) result = self._send_data(funcCode=FunCode.REBOOT, sendData=modeHex) data = result.get("data") if data[8: 10] != "01": raise ServiceException({"result": 2, "description": u"重启设备失败"}) def get_dev_setting(self): """ 获取设备属性 对接view接口 :return: """ setConf = dict() devAttr = self.get_dev_attr() devStatus = self.get_dev_status() setConf.update(devAttr) setConf.update(devStatus) return setConf def get_device_function_by_key(self, keyName): """ 获取设备参数 对接view接口 :param keyName: 空情况 设置设备 否则设置端口 :return: """ if isinstance(keyName, list): return self.get_dev_paras() return self.get_port_paras(keyName) def set_device_function_param(self, request, lastSetConf): """ 设置设备参数 :param request: :param lastSetConf: :return: """ # leftCupSwitch = request.POST.get("leftCupSwitch", None) # leftCupNum = request.POST.get("leftCupNum", None) # if leftCupSwitch is not None: # self.set_left_cup(leftCupSwitch, leftCupNum) lock = request.POST.get("lock", None) if lock is not None: print lock self.lock_dev(lock) port = request.POST.get("portIndex", None) if port is not None: setConf = dict() setConf["makeDrinkMode"] = request.POST.get("makeDrinkMode") setConf["hotWater"] = request.POST.get("hotWater") setConf["coldWater"] = request.POST.get("coldWater") setConf["hotPowder"] = request.POST.get("hotPowder") setConf["coldPowder"] = request.POST.get("coldPowder") return self.set_port_paras(port, setConf) cupModule = request.POST.get("cupModule", None) if cupModule is not None: setConf = dict() setConf["hotSwitch"] = request.POST.get("hotSwitch") setConf["hotWaterMax"] = request.POST.get("hotWaterMax") setConf["hotWaterMin"] = request.POST.get("hotWaterMin") setConf["coldSwitch"] = request.POST.get("coldSwitch") setConf["coldWaterMax"] = request.POST.get("coldWaterMax") setConf["coldWaterMin"] = request.POST.get("coldWaterMin") setConf["lockTime"] = request.POST.get("lockTime") setConf["payModuleSwitch"] = request.POST.get("payModuleSwitch") setConf["cupModule"] = cupModule setConf["lightSwitch"] = request.POST.get("lightSwitch") setConf["leftCupSwitch"] = request.POST.get("leftCupSwitch", 1) self.set_dev_paras(setConf) def set_device_function(self, request, lastSetConf): """ 设置设备功能 重启 重置 :param request: :param lastSetConf: :return: """ if request.POST.get("reboot"): self.reboot() if request.POST.get("recover"): self.recovery() def get_port_status(self, force = False): pass def async_update_portinfo_from_dev(self): pass def start_device(self, package, openId, attachParas): """ 根据套餐名称 反推出能加工套餐名称的 出水口 :param package: :param openId: :param attachParas: :return: """ packageName = package.get("name", "") # 获取当前的端口加工模式 devCache = Device.get_dev_control_cache(self._device["devNo"]) portList = list() for port, info in devCache.items(): if isinstance(info, dict) and "makeDrinkMode" in info: modeName = self.DRINK_MOD.get(info.get("makeDrinkMode")) if modeName == packageName: portList.append(int(port)-1) try: needPort = min(portList) except Exception as e: needPort = None if needPort is None or (needPort < 0 or needPort > 7): raise ServiceException({"result": 2, "description": u"设备暂不支持该饮料模式,请联系经销商!"}) # 确定了端口之后,下发做杯 result = self.remote_cup(needPort) attachParas.update({"chargeIndex": str(needPort)}) finishedTime = int(time.time()) result["finishedTime"] = finishedTime user = MyUser.objects.filter(openId=openId, groupId=self._device["groupId"]).first() managerialOpenId = user.managerialOpenId if user else "" group = Group.get_group(self._device["groupId"]) kwargs = { "title": u"\\n\\n模式选择:\\t\\t{mod}\\n\\n设备编号:\\t\\t{logicalCode}\\n\\n服务地址:\\t\\t{group}".format( mod=packageName, logicalCode=self._device["logicalCode"], group=group["address"], ), "service": u"您的饮料正在{port}号口加工,请稍后".format(port=needPort+1), "finishTime": str(datetime.datetime.now())[:19], 'remark': u'谢谢您的支持' } task_caller('report_to_user_via_wechat', openId=managerialOpenId, dealerId=self._device["ownerId"], templateName="service_complete", **kwargs) return result