init
This commit is contained in:
91
agents/bedside_lamp_agent/README.md
Normal file
91
agents/bedside_lamp_agent/README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Bedside Lamp Agent
|
||||
|
||||
Moss AI 床头灯控制系统,专门负责 Yeelink 床头灯(yeelink.light.bslamp2)的智能控制。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 💡 电源控制(开/关)
|
||||
- 🌟 亮度调节(0-100%)
|
||||
- 🌡️ 色温设置(暖光/冷光)
|
||||
- 🎨 颜色设置(RGB)
|
||||
- 🎭 场景模式
|
||||
- 📖 阅读模式
|
||||
- 😴 睡眠模式
|
||||
- 💑 浪漫模式
|
||||
- 🌙 夜灯模式
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 安装依赖
|
||||
|
||||
使用 uv(推荐):
|
||||
```bash
|
||||
cd agents/bedside_lamp_agent
|
||||
uv sync
|
||||
```
|
||||
|
||||
### 启动服务
|
||||
|
||||
使用 uv:
|
||||
```bash
|
||||
uv run .
|
||||
```
|
||||
|
||||
或使用 Python 模块方式:
|
||||
```bash
|
||||
python -m .
|
||||
```
|
||||
|
||||
或运行 main.py:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
### 命令行参数
|
||||
|
||||
```bash
|
||||
# 指定主机和端口
|
||||
uv run . --host 0.0.0.0 --port 12001
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
服务配置通过项目根目录的 `config.yaml` 文件管理:
|
||||
|
||||
```yaml
|
||||
agents:
|
||||
bedside_lamp:
|
||||
host: "0.0.0.0"
|
||||
port: 12001
|
||||
```
|
||||
|
||||
## 设备配置
|
||||
|
||||
需要在 `config.yaml` 中配置床头灯的 IP 和 Token:
|
||||
|
||||
```yaml
|
||||
xiaomi_devices:
|
||||
bedside_lamp:
|
||||
ip: "192.168.1.100"
|
||||
token: "your_device_token"
|
||||
model: "yeelink.light.bslamp2"
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
- "打开床头灯"
|
||||
- "调到50%亮度"
|
||||
- "设置暖光"
|
||||
- "变成粉色"
|
||||
- "切换到阅读模式"
|
||||
- "关闭床头灯"
|
||||
|
||||
## 依赖
|
||||
|
||||
- a2a: Agent-to-Agent 协议支持
|
||||
- click: 命令行接口
|
||||
- uvicorn: ASGI 服务器
|
||||
- httpx: HTTP 客户端
|
||||
- PyYAML: YAML 配置解析
|
||||
- python-miio: 小米设备控制库
|
||||
|
||||
7
agents/bedside_lamp_agent/__init__.py
Normal file
7
agents/bedside_lamp_agent/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
Bedside Lamp Agent - Moss AI 床头灯控制系统
|
||||
控制 Yeelink 床头灯(yeelink.light.bslamp2)
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
|
||||
111
agents/bedside_lamp_agent/__main__.py
Normal file
111
agents/bedside_lamp_agent/__main__.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
主入口点 - 用于 `uv run .` 或 `python -m bedside_lamp_agent` 启动服务
|
||||
床头灯 Agent
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import click
|
||||
import logging
|
||||
import uvicorn
|
||||
from a2a.types import (
|
||||
AgentCapabilities,
|
||||
AgentCard,
|
||||
AgentSkill,
|
||||
)
|
||||
from a2a.server.apps import A2AStarletteApplication
|
||||
from a2a.server.request_handlers import DefaultRequestHandler
|
||||
import httpx
|
||||
from a2a.server.tasks import (
|
||||
BasePushNotificationSender,
|
||||
InMemoryPushNotificationConfigStore,
|
||||
InMemoryTaskStore,
|
||||
)
|
||||
|
||||
# 确保当前目录和父目录在 Python 路径中
|
||||
current_dir = Path(__file__).parent
|
||||
parent_dir = current_dir.parent
|
||||
if str(current_dir) not in sys.path:
|
||||
sys.path.insert(0, str(current_dir))
|
||||
if str(parent_dir) not in sys.path:
|
||||
sys.path.insert(0, str(parent_dir))
|
||||
|
||||
from executor import BedsideLampAgentExecutor
|
||||
from agent import BedsideLampAgent
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--host", "host", default=None, help="服务主机地址(默认从config.yaml读取)")
|
||||
@click.option("--port", "port", default=None, type=int, help="服务端口(默认从config.yaml读取)")
|
||||
def main(host, port):
|
||||
"""Starts the Bedside Lamp Agent server."""
|
||||
try:
|
||||
# 从配置文件读取 host 和 port(如果命令行未指定)
|
||||
if host is None or port is None:
|
||||
from config_loader import get_config_loader
|
||||
config_loader = get_config_loader(strict_mode=False)
|
||||
default_host, default_port = config_loader.get_agent_host_port('bedside_lamp')
|
||||
host = host or default_host
|
||||
port = port or default_port
|
||||
|
||||
logger.info(f"📍 Bedside Lamp Agent 启动配置: {host}:{port}")
|
||||
|
||||
capabilities = AgentCapabilities(
|
||||
push_notifications=False,
|
||||
state_transition_history=False,
|
||||
streaming=False,
|
||||
)
|
||||
skill = AgentSkill(
|
||||
id="control_bedside_lamp",
|
||||
name="Bedside Lamp Control",
|
||||
description="控制Yeelink床头灯(yeelink.light.bslamp2),包括电源、亮度、色温、颜色设置,支持阅读、睡眠、浪漫、夜灯等场景模式",
|
||||
tags=["bedside lamp", "yeelink", "lighting", "smart home", "home automation"],
|
||||
examples=[
|
||||
"打开床头灯",
|
||||
"调到50%亮度",
|
||||
"设置暖光",
|
||||
"变成粉色",
|
||||
"切换到阅读模式",
|
||||
"关闭床头灯",
|
||||
],
|
||||
)
|
||||
agent_card = AgentCard(
|
||||
name="Bedside Lamp Agent",
|
||||
description="Yeelink床头灯(yeelink.light.bslamp2)控制的专业助手",
|
||||
url=f"http://{host}:{port}/",
|
||||
version="1.0.0",
|
||||
default_input_modes=BedsideLampAgent.SUPPORTED_CONTENT_TYPES,
|
||||
default_output_modes=BedsideLampAgent.SUPPORTED_CONTENT_TYPES,
|
||||
capabilities=capabilities,
|
||||
skills=[skill],
|
||||
)
|
||||
|
||||
# --8<-- [start:DefaultRequestHandler]
|
||||
httpx_client = httpx.AsyncClient()
|
||||
push_config_store = InMemoryPushNotificationConfigStore()
|
||||
push_sender = BasePushNotificationSender(
|
||||
httpx_client=httpx_client, config_store=push_config_store
|
||||
)
|
||||
request_handler = DefaultRequestHandler(
|
||||
agent_executor=BedsideLampAgentExecutor(),
|
||||
task_store=InMemoryTaskStore(),
|
||||
push_config_store=push_config_store,
|
||||
push_sender=push_sender,
|
||||
)
|
||||
server = A2AStarletteApplication(
|
||||
agent_card=agent_card, http_handler=request_handler
|
||||
)
|
||||
|
||||
uvicorn.run(server.build(), host=host, port=port)
|
||||
# --8<-- [end:DefaultRequestHandler]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred during server startup: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
181
agents/bedside_lamp_agent/agent.py
Normal file
181
agents/bedside_lamp_agent/agent.py
Normal file
@@ -0,0 +1,181 @@
|
||||
from typing import Any
|
||||
from langchain_core.messages import AIMessage, ToolMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加父目录到路径以导入config_loader
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from config_loader import get_config_loader
|
||||
|
||||
from tools import (
|
||||
get_lamp_status,
|
||||
set_lamp_power,
|
||||
set_lamp_brightness,
|
||||
set_lamp_color_temp,
|
||||
set_lamp_color,
|
||||
set_lamp_scene
|
||||
)
|
||||
|
||||
memory = MemorySaver()
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BedsideLampAgent:
|
||||
SUPPORTED_CONTENT_TYPES = ['text', 'text/plain']
|
||||
|
||||
# 默认系统提示词(备用)
|
||||
DEFAULT_SYSTEM_PROMPT = (
|
||||
'你是一个专门的Yeelink床头灯控制助手(型号:yeelink.light.bslamp2)。'
|
||||
'你的唯一目的是帮助用户控制他们的床头灯。'
|
||||
'你可以帮助:开关灯、调节亮度、设置色温、改变颜色、应用预设场景等。'
|
||||
'如果用户询问与床头灯控制无关的内容,'
|
||||
'请礼貌地说明你无法帮助处理该主题,只能协助处理与床头灯相关的问题。'
|
||||
'不要尝试回答无关问题或将工具用于其他目的。'
|
||||
''
|
||||
'工具使用指南:'
|
||||
'1. 查询状态:当用户请求查询设备状态、灯光亮度、颜色等信息时,'
|
||||
' 调用 get_lamp_status 获取最新状态,并用中文友好地展示关键信息。'
|
||||
' 重点关注:电源状态、亮度、色温、颜色模式。'
|
||||
''
|
||||
'2. 电源控制:当用户说"打开/开启/开灯"时,调用 set_lamp_power(power=True);'
|
||||
' 说"关闭/关灯"时,调用 set_lamp_power(power=False)。'
|
||||
''
|
||||
'3. 亮度调节:当用户说"调亮/最亮/亮一点"时设为80-100,"调暗/暗一点"时设为20-40,'
|
||||
' "中等亮度/一半"时设为50,使用 set_lamp_brightness(brightness=1-100)。'
|
||||
' 也可以响应具体百分比,如"50%亮度"。'
|
||||
''
|
||||
'4. 色温控制:当用户说"暖光/暖色"时设为1700-2700K,"中性光/自然光"时设为3500-4500K,'
|
||||
' "冷光/白光"时设为5500-6500K,使用 set_lamp_color_temp(color_temp=1700-6500)。'
|
||||
''
|
||||
'5. 颜色设置:当用户说"红色/粉色/蓝色"等具体颜色时,'
|
||||
' 使用 set_lamp_color(red=0-255, green=0-255, blue=0-255) 设置RGB值。'
|
||||
' 常用颜色参考:红色(255,0,0)、绿色(0,255,0)、蓝色(0,0,255)、'
|
||||
' 黄色(255,255,0)、紫色(128,0,128)、粉色(255,192,203)。'
|
||||
''
|
||||
'6. 场景模式:支持四种预设场景'
|
||||
' - "阅读模式/看书":使用 set_lamp_scene(scene="reading") - 100%亮度,4000K中性光'
|
||||
' - "睡眠模式/睡觉":使用 set_lamp_scene(scene="sleep") - 10%亮度,2000K暖光'
|
||||
' - "浪漫模式/约会":使用 set_lamp_scene(scene="romantic") - 30%亮度,粉红色'
|
||||
' - "夜灯模式/起夜":使用 set_lamp_scene(scene="night") - 5%亮度,1700K极暖光'
|
||||
''
|
||||
'7. 智能场景建议:'
|
||||
' - 阅读/工作:建议100%亮度 + 4000K中性光'
|
||||
' - 睡前放松:建议20-30%亮度 + 2000K暖光'
|
||||
' - 起夜/夜间:建议5-10%亮度 + 1700K极暖光'
|
||||
' - 浪漫氛围:建议30%亮度 + 粉色/紫色'
|
||||
''
|
||||
'始终用友好、简洁的中文回复用户,优先展示用户最关心的信息。'
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
# 从数据库加载配置(严格模式:配置加载失败则退出)
|
||||
try:
|
||||
config_loader = get_config_loader(strict_mode=True)
|
||||
|
||||
# 加载AI模型配置
|
||||
ai_config = config_loader.get_default_ai_model_config()
|
||||
logger.info(f"✅ 成功加载AI模型配置: {ai_config['model']}")
|
||||
self.model = ChatOpenAI(
|
||||
model=ai_config['model'],
|
||||
api_key=ai_config['api_key'],
|
||||
base_url=ai_config['api_base'],
|
||||
temperature=ai_config['temperature'],
|
||||
)
|
||||
|
||||
# 加载系统提示词
|
||||
system_prompt = config_loader.get_agent_prompt('bedside_lamp')
|
||||
logger.info("✅ 成功加载Bedside Lamp系统提示词")
|
||||
self.SYSTEM_PROMPT = system_prompt
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 配置加载失败: {e}")
|
||||
logger.error("⚠️ 请确保:")
|
||||
logger.error(" 1. StarRocks 数据库已启动")
|
||||
logger.error(" 2. 已执行数据库初始化脚本: data/init_config.sql 和 data/ai_config.sql")
|
||||
logger.error(" 3. config.yaml 中的数据库连接配置正确")
|
||||
raise SystemExit(1) from e
|
||||
|
||||
self.tools = [
|
||||
get_lamp_status,
|
||||
set_lamp_power,
|
||||
set_lamp_brightness,
|
||||
set_lamp_color_temp,
|
||||
set_lamp_color,
|
||||
set_lamp_scene
|
||||
]
|
||||
|
||||
self.graph = create_react_agent(
|
||||
self.model,
|
||||
tools=self.tools,
|
||||
checkpointer=memory,
|
||||
prompt=self.SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
async def invoke(self, query, context_id) -> dict[str, Any]:
|
||||
"""非流式调用,直接返回最终结果"""
|
||||
inputs = {'messages': [('user', query)]}
|
||||
config = {'configurable': {'thread_id': context_id}}
|
||||
|
||||
# 直接调用invoke,不使用stream
|
||||
result = self.graph.invoke(inputs, config)
|
||||
|
||||
return self.get_agent_response(config)
|
||||
|
||||
def _extract_text_from_message(self, msg: AIMessage | ToolMessage | Any) -> str:
|
||||
try:
|
||||
content = getattr(msg, 'content', None)
|
||||
if isinstance(content, str):
|
||||
return content
|
||||
if isinstance(content, list):
|
||||
parts = []
|
||||
for part in content:
|
||||
if isinstance(part, dict) and 'text' in part:
|
||||
parts.append(part['text'])
|
||||
if parts:
|
||||
return '\n'.join(parts)
|
||||
except Exception:
|
||||
pass
|
||||
return ''
|
||||
|
||||
def get_agent_response(self, config):
|
||||
current_state = self.graph.get_state(config)
|
||||
messages = current_state.values.get('messages') if hasattr(current_state, 'values') else None
|
||||
|
||||
# 优先返回最近一次工具消息内容
|
||||
if isinstance(messages, list) and messages:
|
||||
for msg in reversed(messages):
|
||||
if isinstance(msg, ToolMessage):
|
||||
tool_text = self._extract_text_from_message(msg)
|
||||
if tool_text:
|
||||
return {
|
||||
'is_task_complete': True,
|
||||
'require_user_input': False,
|
||||
'content': tool_text,
|
||||
}
|
||||
|
||||
# 回退到最后一条 AI 消息
|
||||
final_text = ''
|
||||
if isinstance(messages, list) and messages:
|
||||
last_msg = messages[-1]
|
||||
final_text = self._extract_text_from_message(last_msg)
|
||||
|
||||
if not final_text:
|
||||
return {
|
||||
'is_task_complete': False,
|
||||
'require_user_input': True,
|
||||
'content': '当前无法处理您的请求,请稍后重试。',
|
||||
}
|
||||
|
||||
return {
|
||||
'is_task_complete': True,
|
||||
'require_user_input': False,
|
||||
'content': final_text,
|
||||
}
|
||||
|
||||
96
agents/bedside_lamp_agent/executor.py
Normal file
96
agents/bedside_lamp_agent/executor.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import logging
|
||||
|
||||
from a2a.server.agent_execution import AgentExecutor, RequestContext
|
||||
from a2a.server.events import EventQueue
|
||||
from a2a.server.tasks import TaskUpdater
|
||||
from a2a.types import (
|
||||
InternalError,
|
||||
InvalidParamsError,
|
||||
Part,
|
||||
TaskState,
|
||||
TextPart,
|
||||
UnsupportedOperationError,
|
||||
)
|
||||
from a2a.utils import (
|
||||
new_agent_text_message,
|
||||
new_task,
|
||||
)
|
||||
from a2a.utils.errors import ServerError
|
||||
|
||||
from agent import BedsideLampAgent
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BedsideLampAgentExecutor(AgentExecutor):
|
||||
"""Bedside Lamp AgentExecutor."""
|
||||
|
||||
def __init__(self):
|
||||
self.agent = BedsideLampAgent()
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
context: RequestContext,
|
||||
event_queue: EventQueue,
|
||||
) -> None:
|
||||
error = self._validate_request(context)
|
||||
if error:
|
||||
raise ServerError(error=InvalidParamsError())
|
||||
|
||||
query = context.get_user_input()
|
||||
task = context.current_task
|
||||
if not task:
|
||||
task = new_task(context.message) # type: ignore
|
||||
await event_queue.enqueue_event(task)
|
||||
updater = TaskUpdater(event_queue, task.id, task.context_id)
|
||||
try:
|
||||
# 使用非流式invoke方法
|
||||
result = await self.agent.invoke(query, task.context_id)
|
||||
|
||||
is_task_complete = result.get('is_task_complete', True)
|
||||
require_user_input = result.get('require_user_input', False)
|
||||
content = result.get('content', '处理完成')
|
||||
|
||||
if require_user_input:
|
||||
await updater.update_status(
|
||||
TaskState.input_required,
|
||||
new_agent_text_message(
|
||||
content,
|
||||
task.context_id,
|
||||
task.id,
|
||||
),
|
||||
final=True,
|
||||
)
|
||||
elif is_task_complete:
|
||||
await updater.add_artifact(
|
||||
[Part(root=TextPart(text=content))],
|
||||
name='lamp_status_result',
|
||||
)
|
||||
await updater.complete()
|
||||
else:
|
||||
# 如果既不需要输入也未完成,设置为working状态
|
||||
await updater.update_status(
|
||||
TaskState.working,
|
||||
new_agent_text_message(
|
||||
content,
|
||||
task.context_id,
|
||||
task.id,
|
||||
),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'An error occurred while processing the request: {e}')
|
||||
raise ServerError(error=InternalError()) from e
|
||||
|
||||
def _validate_request(self, context: RequestContext) -> bool:
|
||||
# 这里可以添加请求验证逻辑
|
||||
# 返回 True 表示有错误,False 表示验证通过
|
||||
return False
|
||||
|
||||
async def cancel(
|
||||
self, context: RequestContext, event_queue: EventQueue
|
||||
) -> None:
|
||||
raise ServerError(error=UnsupportedOperationError())
|
||||
|
||||
26
agents/bedside_lamp_agent/pyproject.toml
Normal file
26
agents/bedside_lamp_agent/pyproject.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[project]
|
||||
name = "bedside-lamp-agent"
|
||||
version = "1.0.0"
|
||||
description = "Moss AI 床头灯控制 Agent"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"a2a>=0.1.0",
|
||||
"click>=8.0.0",
|
||||
"uvicorn[standard]>=0.24.0",
|
||||
"httpx>=0.25.0",
|
||||
"PyYAML>=6.0.0",
|
||||
"starlette>=0.27.0",
|
||||
"python-miio>=0.5.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["."]
|
||||
|
||||
229
agents/bedside_lamp_agent/tools.py
Normal file
229
agents/bedside_lamp_agent/tools.py
Normal file
@@ -0,0 +1,229 @@
|
||||
from langchain_core.tools import tool
|
||||
from miio import DeviceFactory
|
||||
from miio.miot_device import MiotDevice
|
||||
import json
|
||||
from pydantic import BaseModel, Field
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
|
||||
# 配置日志
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 设备配置
|
||||
LAMP_IP = "192.168.110.122"
|
||||
LAMP_TOKEN = "4a90f98aaa1273ca34685d66d6e13958"
|
||||
LAMP_MODEL = "yeelink.light.bslamp2"
|
||||
|
||||
try:
|
||||
device = DeviceFactory.create(LAMP_IP, LAMP_TOKEN)
|
||||
logger.info(f"使用 DeviceFactory 创建设备成功: {LAMP_MODEL}")
|
||||
except Exception as e:
|
||||
logger.warning(f"DeviceFactory 创建失败,使用 MiotDevice: {e}")
|
||||
|
||||
# 添加线程锁,确保同一时间只有一个操作
|
||||
device_lock = threading.Lock()
|
||||
|
||||
|
||||
@tool("get_lamp_status", description="获取床头灯当前状态,包括电源、亮度、色温、颜色等信息")
|
||||
def get_lamp_status():
|
||||
"""获取床头灯设备状态并以 JSON 格式返回"""
|
||||
try:
|
||||
with device_lock: # 使用锁确保串行执行
|
||||
# 使用 status() 方法获取 DeviceStatus 对象
|
||||
device_status = device.status()
|
||||
|
||||
# 构建状态字典
|
||||
status = {
|
||||
"power": device_status.power if hasattr(device_status, 'power') else None,
|
||||
"is_on": device_status.is_on if hasattr(device_status, 'is_on') else None,
|
||||
"brightness": device_status.brightness if hasattr(device_status, 'brightness') else None,
|
||||
"color_temp": device_status.color_temp if hasattr(device_status, 'color_temp') else None,
|
||||
"color_mode": device_status.color_mode if hasattr(device_status, 'color_mode') else None,
|
||||
"rgb": device_status.rgb if hasattr(device_status, 'rgb') else None,
|
||||
"online": True,
|
||||
"model": LAMP_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=LAMP_IP),
|
||||
"online": False,
|
||||
"model": LAMP_MODEL
|
||||
}
|
||||
return json.dumps(error_status, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class PowerArgs(BaseModel):
|
||||
power: bool = Field(..., description="床头灯电源状态,true 开启,false 关闭")
|
||||
|
||||
|
||||
@tool("set_lamp_power", args_schema=PowerArgs, description="开启或关闭床头灯。power=true 开启,power=false 关闭")
|
||||
def set_lamp_power(power: bool):
|
||||
"""开启或关闭床头灯"""
|
||||
try:
|
||||
with device_lock: # 使用锁确保串行执行
|
||||
if power:
|
||||
result = device.on()
|
||||
else:
|
||||
result = device.off()
|
||||
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=LAMP_IP),
|
||||
"online": False,
|
||||
"model": LAMP_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class BrightnessArgs(BaseModel):
|
||||
brightness: int = Field(..., ge=1, le=100, description="亮度值,范围 1-100")
|
||||
|
||||
|
||||
@tool("set_lamp_brightness", args_schema=BrightnessArgs, description="设置床头灯亮度(1-100)")
|
||||
def set_lamp_brightness(brightness: int):
|
||||
"""设置床头灯亮度"""
|
||||
try:
|
||||
with device_lock:
|
||||
# 参考 Iot.py - 设置亮度 (siid=2, piid=2)
|
||||
result = device.send("set_bright", [brightness])
|
||||
logger.info(f"亮度已设置为{brightness}%")
|
||||
return json.dumps({
|
||||
"message": f"亮度已设置为{brightness}%",
|
||||
"brightness": brightness,
|
||||
"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=LAMP_IP),
|
||||
"online": False,
|
||||
"model": LAMP_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class ColorTempArgs(BaseModel):
|
||||
color_temp: int = Field(..., ge=1700, le=6500, description="色温值,范围 1700-6500K")
|
||||
|
||||
|
||||
@tool("set_lamp_color_temp", args_schema=ColorTempArgs, description="设置床头灯色温(1700-6500K,暖光到冷光)")
|
||||
def set_lamp_color_temp(color_temp: int):
|
||||
"""设置床头灯色温"""
|
||||
try:
|
||||
with device_lock:
|
||||
result = device.send("set_ct_abx", [color_temp, "smooth", 500])
|
||||
temp_desc = "暖光" if color_temp < 3000 else "中性光" if color_temp < 5000 else "冷光"
|
||||
logger.info(f"色温已设置为{color_temp}K ({temp_desc})")
|
||||
return json.dumps({
|
||||
"message": f"色温已设置为{color_temp}K ({temp_desc})",
|
||||
"color_temp": color_temp,
|
||||
"description": temp_desc,
|
||||
"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=LAMP_IP),
|
||||
"online": False,
|
||||
"model": LAMP_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class ColorArgs(BaseModel):
|
||||
red: int = Field(..., ge=0, le=255, description="红色值,范围 0-255")
|
||||
green: int = Field(..., ge=0, le=255, description="绿色值,范围 0-255")
|
||||
blue: int = Field(..., ge=0, le=255, description="蓝色值,范围 0-255")
|
||||
|
||||
|
||||
@tool("set_lamp_color", args_schema=ColorArgs, description="设置床头灯RGB颜色(红、绿、蓝各0-255)")
|
||||
def set_lamp_color(red: int, green: int, blue: int):
|
||||
"""设置床头灯RGB颜色"""
|
||||
try:
|
||||
with device_lock:
|
||||
color_value = (red << 16) | (green << 8) | blue
|
||||
result = device.send("set_rgb", [color_value])
|
||||
logger.info(f"颜色已设置为 RGB({red}, {green}, {blue})")
|
||||
return json.dumps({
|
||||
"message": f"颜色已设置为 RGB({red}, {green}, {blue})",
|
||||
"red": red,
|
||||
"green": green,
|
||||
"blue": blue,
|
||||
"color_hex": f"#{red:02x}{green:02x}{blue:02x}",
|
||||
"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=LAMP_IP),
|
||||
"online": False,
|
||||
"model": LAMP_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class SceneArgs(BaseModel):
|
||||
scene: str = Field(..., description="场景名称: 'reading' (阅读), 'sleep' (睡眠), 'romantic' (浪漫), 'night' (夜灯)")
|
||||
|
||||
|
||||
@tool("set_lamp_scene", args_schema=SceneArgs, description="设置床头灯预设场景(阅读/睡眠/浪漫/夜灯)")
|
||||
def set_lamp_scene(scene: str):
|
||||
"""设置床头灯预设场景"""
|
||||
# 定义预设场景
|
||||
scenes = {
|
||||
"reading": {"brightness": 100, "color_temp": 4000, "desc": "阅读模式:100%亮度,4000K中性光"},
|
||||
"sleep": {"brightness": 10, "color_temp": 2000, "desc": "睡眠模式:10%亮度,2000K暖光"},
|
||||
"romantic": {"brightness": 30, "color": (255, 100, 100), "desc": "浪漫模式:30%亮度,粉红色"},
|
||||
"night": {"brightness": 5, "color_temp": 1700, "desc": "夜灯模式:5%亮度,1700K极暖光"}
|
||||
}
|
||||
|
||||
if scene not in scenes:
|
||||
return json.dumps({
|
||||
"error": f"未知场景: {scene}",
|
||||
"available_scenes": list(scenes.keys())
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
try:
|
||||
with device_lock:
|
||||
scene_config = scenes[scene]
|
||||
|
||||
# 设置亮度
|
||||
device.set_property_by(2, 2, scene_config["brightness"])
|
||||
time.sleep(0.3) # 给设备一点响应时间
|
||||
|
||||
# 设置色温或颜色
|
||||
if "color_temp" in scene_config:
|
||||
device.set_property_by(2, 3, scene_config["color_temp"])
|
||||
elif "color" in scene_config:
|
||||
r, g, b = scene_config["color"]
|
||||
color_value = (r << 16) | (g << 8) | b
|
||||
device.set_property_by(2, 5, color_value)
|
||||
|
||||
logger.info(f"场景已设置为: {scene}")
|
||||
return json.dumps({
|
||||
"message": f"场景已设置为: {scene}",
|
||||
"scene": scene,
|
||||
"description": scene_config["desc"],
|
||||
"config": scene_config
|
||||
}, 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=LAMP_IP),
|
||||
"online": False,
|
||||
"model": LAMP_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
1574
agents/bedside_lamp_agent/uv.lock
generated
Normal file
1574
agents/bedside_lamp_agent/uv.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user