from langchain_core.tools import tool from miio import DeviceFactory from miio.miot_device import MiotDevice import json from pydantic import BaseModel, Field import logging import threading # 配置日志 logger = logging.getLogger(__name__) # 设备配置 PURIFIER_IP = "192.168.110.120" PURIFIER_TOKEN = "569905df67a11d6b67a575097255c798" PURIFIER_MODEL = "zhimi.airp.oa1" # 创建设备实例(使用 DeviceFactory 自动识别设备类型) device = DeviceFactory.create(PURIFIER_IP, PURIFIER_TOKEN) # 同时保留 MiotDevice 实例用于属性设置 miot_device = MiotDevice( ip=PURIFIER_IP, token=PURIFIER_TOKEN, model=PURIFIER_MODEL ) # 添加线程锁,确保同一时间只有一个操作 device_lock = threading.Lock() @tool("get_purifier_status", description="获取空气净化器当前状态,包括电源、PM2.5、湿度、风扇等级、工作模式、滤芯寿命等信息") def get_purifier_status(): """获取空气净化器设备状态并以 JSON 格式返回""" try: with device_lock: # 使用锁确保串行执行 # 使用 status() 方法一次性获取所有状态 status_obj = device.status() # 获取状态数据字典 if hasattr(status_obj, 'data'): status_data = status_obj.data # 添加额外信息 status_data['online'] = True status_data['model'] = PURIFIER_MODEL return json.dumps(status_data, indent=2, ensure_ascii=False, default=str) else: # 降级方案:如果没有 data 属性,手动获取关键属性 logger.warning("设备状态对象没有 data 属性,使用降级方案") power = miot_device.get_property_by(2, 1) fan_level = miot_device.get_property_by(2, 5) # 正确的 PIID led = miot_device.get_property_by(2, 6) status = { "power": power[0].get('value') if isinstance(power, list) and len(power) > 0 else power, "fan_level": fan_level[0].get('value') if isinstance(fan_level, list) and len(fan_level) > 0 else fan_level, "led_brightness": led[0].get('value') if isinstance(led, list) and len(led) > 0 else led, "online": True, "model": PURIFIER_MODEL } return json.dumps(status, indent=2, ensure_ascii=False) except Exception as e: logger.error(f"获取空气净化器状态失败: {e}") error_status = { "error": f"获取设备状态失败: {str(e)}", "message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP), "online": False, "model": PURIFIER_MODEL } return json.dumps(error_status, indent=2, ensure_ascii=False) class PowerArgs(BaseModel): power: bool = Field(..., description="空气净化器电源状态,true 开启,false 关闭") @tool("set_purifier_power", args_schema=PowerArgs, description="开启或关闭空气净化器。power=true 开启,power=false 关闭") def set_purifier_power(power: bool): """开启或关闭空气净化器""" try: with device_lock: # 使用锁确保串行执行 result = miot_device.set_property_by(2, 1, power) action = "开启" if power else "关闭" logger.info(f"空气净化器已{action}") return json.dumps({ "message": f"空气净化器已{action}", "power": power, "result": str(result) }, indent=2, ensure_ascii=False) except Exception as e: logger.error(f"设置空气净化器电源失败: {e}") return json.dumps({ "error": f"设置电源状态失败: {str(e)}", "message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP), "online": False, "model": PURIFIER_MODEL }, indent=2, ensure_ascii=False) class ModeArgs(BaseModel): mode: int = Field(..., ge=0, le=2, description="工作模式:0=自动模式,1=睡眠模式,2=手动模式") @tool("set_purifier_mode", args_schema=ModeArgs, description="设置空气净化器工作模式(0=自动,1=睡眠,2=手动)") def set_purifier_mode(mode: int): """设置空气净化器工作模式""" try: with device_lock: result = miot_device.set_property_by(2, 4, mode) # PIID 4: mode mode_names = {0: "自动模式", 1: "睡眠模式", 2: "手动模式"} mode_name = mode_names.get(mode, f"模式{mode}") logger.info(f"工作模式已设置为{mode_name}") return json.dumps({ "message": f"工作模式已设置为{mode_name}", "mode": mode, "mode_name": mode_name, "result": str(result) }, indent=2, ensure_ascii=False) except Exception as e: logger.error(f"设置空气净化器工作模式失败: {e}") return json.dumps({ "error": f"设置工作模式失败: {str(e)}", "message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP), "online": False, "model": PURIFIER_MODEL }, indent=2, ensure_ascii=False) class FanLevelArgs(BaseModel): level: int = Field(..., ge=1, le=4, description="风扇等级,范围 1-4 (1档、2档、3档、4档)") @tool("set_purifier_fan_level", args_schema=FanLevelArgs, description="设置空气净化器风扇等级(1档、2档、3档、4档)") def set_purifier_fan_level(level: int): """设置空气净化器风扇等级 注意:要手动设置风扇等级,设备必须处于手动模式(mode=2)。 如果设备处于自动模式,会自动先切换到手动模式。 """ try: with device_lock: # 先检查当前模式 try: current_mode_result = miot_device.get_property_by(2, 4) current_mode = current_mode_result[0].get('value') if isinstance(current_mode_result, list) else current_mode_result # 如果不是手动模式(mode != 2),先切换到手动模式 if current_mode != 2: logger.info(f"当前为模式{current_mode},需要先切换到手动模式才能设置风扇等级") mode_result = miot_device.set_property_by(2, 4, 2) # 切换到手动模式 logger.info(f"已自动切换到手动模式: {mode_result}") except Exception as mode_error: logger.warning(f"获取/切换模式时出错,继续尝试设置风扇等级: {mode_error}") # 设置风扇等级 result = miot_device.set_property_by(2, 5, level) # PIID 5: fan_level logger.info(f"风扇等级已设置为{level}档") return json.dumps({ "message": f"风扇等级已设置为{level}档(已切换到手动模式)", "fan_level": level, "mode": "手动模式", "result": str(result) }, indent=2, ensure_ascii=False) except Exception as e: logger.error(f"设置空气净化器风扇等级失败: {e}") return json.dumps({ "error": f"设置风扇等级失败: {str(e)}", "message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP), "online": False, "model": PURIFIER_MODEL }, indent=2, ensure_ascii=False) class LEDBrightnessArgs(BaseModel): brightness: bool = Field(..., description="LED按键亮度开关,true 开启,false 关闭") @tool("set_purifier_led", args_schema=LEDBrightnessArgs, description="设置空气净化器LED按键亮度开关") def set_purifier_led(brightness: bool): """设置空气净化器LED按键亮度""" try: with device_lock: result = miot_device.set_property_by(2, 6, brightness) # 正确的 PIID 是 (2, 6) status = "开启" if brightness else "关闭" logger.info(f"LED亮度已{status}") return json.dumps({ "message": f"LED亮度已{status}", "led_brightness": brightness, "result": str(result) }, indent=2, ensure_ascii=False) except Exception as e: logger.error(f"设置空气净化器LED失败: {e}") return json.dumps({ "error": f"设置LED亮度失败: {str(e)}", "message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP), "online": False, "model": PURIFIER_MODEL }, indent=2, ensure_ascii=False) class AlarmArgs(BaseModel): alarm: bool = Field(..., description="提示音开关,true 开启,false 关闭") @tool("set_purifier_alarm", args_schema=AlarmArgs, description="设置空气净化器提示音开关") def set_purifier_alarm(alarm: bool): """设置空气净化器提示音""" try: with device_lock: result = miot_device.set_property_by(2, 7, alarm) # PIID 7: alarm status = "开启" if alarm else "关闭" logger.info(f"提示音已{status}") return json.dumps({ "message": f"提示音已{status}", "alarm": alarm, "result": str(result) }, indent=2, ensure_ascii=False) except Exception as e: logger.error(f"设置空气净化器提示音失败: {e}") return json.dumps({ "error": f"设置提示音失败: {str(e)}", "message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP), "online": False, "model": PURIFIER_MODEL }, indent=2, ensure_ascii=False) class ChildLockArgs(BaseModel): child_lock: bool = Field(..., description="童锁开关,true 开启,false 关闭") @tool("set_purifier_child_lock", args_schema=ChildLockArgs, description="设置空气净化器童锁(物理控制锁)") def set_purifier_child_lock(child_lock: bool): """设置空气净化器童锁""" try: with device_lock: result = miot_device.set_property_by(2, 9, child_lock) # PIID 9: physical-controls-locked status = "开启" if child_lock else "关闭" logger.info(f"童锁已{status}") return json.dumps({ "message": f"童锁已{status}", "child_lock": child_lock, "result": str(result) }, indent=2, ensure_ascii=False) except Exception as e: logger.error(f"设置空气净化器童锁失败: {e}") return json.dumps({ "error": f"设置童锁失败: {str(e)}", "message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP), "online": False, "model": PURIFIER_MODEL }, indent=2, ensure_ascii=False)