123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 |
- # -*- 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
|