Files
moss-ai/agents/air_cleaner_agent/tools.py
雷雨 8635b84b2d init
2025-12-15 22:05:56 +08:00

251 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)