mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 15:43:54 +08:00
* fix: 修复manual模式无法识别 (#404) * feat(docs): 新增Issues模板 * fix: 补全core依赖 * fix: 修复manual模式无法识别 * 去除重复依赖.txt 已经有torch和torchaudio --------- Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com> * 修复iot功能中表达式问题 (#400) * Custom paths asr tts (#388) * #164 自定义asr、tts缓存目录,项目启动自动创建目录 * #164 自定义asr、tts缓存目录,项目启动自动创建目录 * fix:修复语音无法找到新配置项output_file的bug * fix:电脑不支持iot音量控制bug --------- Co-authored-by: Junsen <66542771+Huang-junsen@users.noreply.github.com> Co-authored-by: tang <tangyiyong@gmail.com> Co-authored-by: shudongW <178200623@qq.com> Co-authored-by: hrz <1710360675@qq.com>
370 lines
15 KiB
Python
370 lines
15 KiB
Python
import json
|
|
import asyncio
|
|
from config.logger import setup_logging
|
|
from plugins_func.register import device_type_registry, register_function, ActionResponse, Action, ToolType
|
|
|
|
TAG = __name__
|
|
logger = setup_logging()
|
|
|
|
|
|
def wrap_async_function(async_func):
|
|
"""包装异步函数为同步函数"""
|
|
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
# 获取连接对象(第一个参数)
|
|
conn = args[0]
|
|
if not hasattr(conn, 'loop'):
|
|
logger.bind(tag=TAG).error("Connection对象没有loop属性")
|
|
return ActionResponse(Action.ERROR, "Connection对象没有loop属性",
|
|
"执行操作时出错: Connection对象没有loop属性")
|
|
|
|
# 使用conn对象中的事件循环
|
|
loop = conn.loop
|
|
# 在conn的事件循环中运行异步函数
|
|
future = asyncio.run_coroutine_threadsafe(async_func(*args, **kwargs), loop)
|
|
# 等待结果返回
|
|
return future.result()
|
|
except Exception as e:
|
|
logger.bind(tag=TAG).error(f"运行异步函数时出错: {e}")
|
|
return ActionResponse(Action.ERROR, str(e), f"执行操作时出错: {e}")
|
|
|
|
return wrapper
|
|
|
|
|
|
def create_iot_function(device_name, method_name, method_info):
|
|
"""
|
|
根据IOT设备描述生成通用的控制函数
|
|
"""
|
|
|
|
async def iot_control_function(conn, response_success=None, response_failure=None, **params):
|
|
try:
|
|
# 打印响应参数
|
|
logger.bind(tag=TAG).info(
|
|
f"控制函数接收到的响应参数: success='{response_success}', failure='{response_failure}'")
|
|
|
|
# 发送控制命令
|
|
await send_iot_conn(conn, device_name, method_name, params)
|
|
# 等待一小段时间让状态更新
|
|
await asyncio.sleep(0.1)
|
|
|
|
# 生成结果信息
|
|
result = f"{device_name}的{method_name}操作执行成功"
|
|
|
|
# 根据方法名尝试自动更新状态
|
|
await update_state_by_method(conn, device_name, method_name, params)
|
|
|
|
# 处理响应中可能的占位符
|
|
response = response_success
|
|
# 替换{value}占位符
|
|
for param_name, param_value in params.items():
|
|
# 先尝试直接替换参数值
|
|
if "{" + param_name + "}" in response:
|
|
response = response.replace("{" + param_name + "}", str(param_value))
|
|
|
|
# 如果有{value}占位符,用相关参数替换
|
|
if "{value}" in response:
|
|
response = response.replace("{value}", str(param_value))
|
|
break
|
|
|
|
return ActionResponse(Action.RESPONSE, result, response)
|
|
except Exception as e:
|
|
logger.bind(tag=TAG).error(f"执行{device_name}的{method_name}操作失败: {e}")
|
|
|
|
# 操作失败时使用大模型提供的失败响应
|
|
response = response_failure
|
|
|
|
return ActionResponse(Action.ERROR, str(e), response)
|
|
|
|
return wrap_async_function(iot_control_function)
|
|
|
|
|
|
def create_iot_query_function(device_name, prop_name, prop_info):
|
|
"""
|
|
根据IOT设备属性创建查询函数
|
|
"""
|
|
|
|
async def iot_query_function(conn, response_success=None, response_failure=None):
|
|
try:
|
|
# 打印响应参数
|
|
logger.bind(tag=TAG).info(
|
|
f"查询函数接收到的响应参数: success='{response_success}', failure='{response_failure}'")
|
|
|
|
value = await get_iot_status(conn, device_name, prop_name)
|
|
|
|
# 查询成功,生成结果
|
|
if value is not None:
|
|
# 使用大模型提供的成功响应,并替换其中的占位符
|
|
response = response_success.replace("{value}", str(value))
|
|
|
|
return ActionResponse(Action.RESPONSE, str(value), response)
|
|
else:
|
|
# 查询失败,使用大模型提供的失败响应
|
|
response = response_failure
|
|
|
|
return ActionResponse(Action.ERROR, f"属性{prop_name}不存在", response)
|
|
except Exception as e:
|
|
logger.bind(tag=TAG).error(f"查询{device_name}的{prop_name}时出错: {e}")
|
|
|
|
# 查询出错时使用大模型提供的失败响应
|
|
response = response_failure
|
|
|
|
return ActionResponse(Action.ERROR, str(e), response)
|
|
|
|
return wrap_async_function(iot_query_function)
|
|
|
|
|
|
async def update_state_by_method(conn, device_name, method_name, params):
|
|
"""根据方法和参数自动更新设备状态"""
|
|
try:
|
|
# 规则1: 方法名为TurnOn,设置power为True
|
|
if method_name == "TurnOn":
|
|
await set_iot_status(conn, device_name, "power", True)
|
|
|
|
# 规则2: 方法名为TurnOff,设置power为False
|
|
elif method_name == "TurnOff":
|
|
await set_iot_status(conn, device_name, "power", False)
|
|
|
|
# 规则3: Set开头的方法,尝试更新对应参数
|
|
elif method_name.startswith("Set"):
|
|
# 从参数中找到可能的状态值
|
|
for param_name, param_value in params.items():
|
|
# 尝试更新对应名称的属性
|
|
await set_iot_status(conn, device_name, param_name, param_value)
|
|
|
|
# 其他方法,尝试直接从参数更新状态
|
|
else:
|
|
for param_name, param_value in params.items():
|
|
# 检查设备是否有此属性
|
|
status = await get_iot_status(conn, device_name, param_name)
|
|
if status is not None: # 属性存在
|
|
await set_iot_status(conn, device_name, param_name, param_value)
|
|
except Exception as e:
|
|
logger.bind(tag=TAG).warning(f"自动更新状态失败: {e}")
|
|
|
|
|
|
class IotDescriptor:
|
|
"""
|
|
A class to represent an IoT descriptor.
|
|
"""
|
|
|
|
def __init__(self, name, description, properties, methods):
|
|
self.name = name
|
|
self.description = description
|
|
self.properties = []
|
|
self.methods = []
|
|
|
|
# 根据描述创建属性
|
|
for key, value in properties.items():
|
|
property_item = globals()[key] = {}
|
|
property_item['name'] = key
|
|
property_item["description"] = value["description"]
|
|
if value["type"] == "number":
|
|
property_item["value"] = 0
|
|
elif value["type"] == "boolean":
|
|
property_item["value"] = False
|
|
else:
|
|
property_item["value"] = ""
|
|
self.properties.append(property_item)
|
|
|
|
# 根据描述创建方法
|
|
for key, value in methods.items():
|
|
method = globals()[key] = {}
|
|
method["description"] = value["description"]
|
|
method['name'] = key
|
|
for k, v in value["parameters"].items():
|
|
method[k] = {}
|
|
method[k]["description"] = v["description"]
|
|
if v["type"] == "number":
|
|
method[k]["value"] = 0
|
|
elif v["type"] == "boolean":
|
|
method[k]["value"] = False
|
|
else:
|
|
method[k]["value"] = ""
|
|
self.methods.append(method)
|
|
|
|
|
|
def register_device_type(descriptor):
|
|
"""注册设备类型及其功能"""
|
|
device_name = descriptor["name"]
|
|
type_id = device_type_registry.generate_device_type_id(descriptor)
|
|
|
|
# 如果该类型已注册,直接返回类型ID
|
|
if type_id in device_type_registry.type_functions:
|
|
return type_id
|
|
|
|
functions = {}
|
|
|
|
# 为每个属性创建查询函数
|
|
for prop_name, prop_info in descriptor["properties"].items():
|
|
func_name = f"get_{{device_name.lower()}}_{{prop_name.lower()}}"
|
|
func_desc = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": func_name,
|
|
"description": f"查询{{descriptor['description']}}的{{prop_info['description']}}",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"response_success": {
|
|
"type": "string",
|
|
"description": f"查询成功时的友好回复,必须使用{{value}}作为占位符表示查询到的值"
|
|
},
|
|
"response_failure": {
|
|
"type": "string",
|
|
"description": f"查询失败时的友好回复,例如:'无法获取{{device_name}}的{{prop_info['description']}}'"
|
|
}
|
|
},
|
|
"required": ["response_success", "response_failure"]
|
|
}
|
|
}
|
|
}
|
|
query_func = create_iot_query_function(device_name, prop_name, prop_info)
|
|
decorated_func = register_function(func_name, func_desc, ToolType.IOT_CTL)(query_func)
|
|
functions[func_name] = decorated_func
|
|
|
|
# 为每个方法创建控制函数
|
|
for method_name, method_info in descriptor["methods"].items():
|
|
func_name = f"{device_name.lower()}_{method_name.lower()}"
|
|
|
|
# 创建参数字典,添加原有参数
|
|
parameters = {
|
|
param_name: {
|
|
"type": param_info["type"],
|
|
"description": param_info["description"]
|
|
}
|
|
for param_name, param_info in method_info["parameters"].items()
|
|
}
|
|
|
|
# 添加响应参数
|
|
parameters.update({
|
|
"response_success": {
|
|
"type": "string",
|
|
"description": "操作成功时的友好回复,关于该设备的操作结果,设备名称尽量使用description中的名称"
|
|
},
|
|
"response_failure": {
|
|
"type": "string",
|
|
"description": "操作失败时的友好回复,关于该设备的操作结果,设备名称尽量使用description中的名称"
|
|
}
|
|
})
|
|
|
|
# 构建必须参数列表(原有参数 + 响应参数)
|
|
required_params = list(method_info["parameters"].keys())
|
|
required_params.extend(["response_success", "response_failure"])
|
|
|
|
func_desc = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": func_name,
|
|
"description": f"{{descriptor['description']}} - {{method_info['description']}}",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": parameters,
|
|
"required": required_params
|
|
}
|
|
}
|
|
}
|
|
control_func = create_iot_function(device_name, method_name, method_info)
|
|
decorated_func = register_function(func_name, func_desc, ToolType.IOT_CTL)(control_func)
|
|
functions[func_name] = decorated_func
|
|
|
|
device_type_registry.register_device_type(type_id, functions)
|
|
return type_id
|
|
|
|
|
|
# 用于接受前端设备推送的搜索iot描述
|
|
async def handleIotDescriptors(conn, descriptors):
|
|
"""处理物联网描述"""
|
|
functions_changed = False
|
|
|
|
for descriptor in descriptors:
|
|
# 创建IOT设备描述符
|
|
iot_descriptor = IotDescriptor(descriptor["name"], descriptor["description"], descriptor["properties"],
|
|
descriptor["methods"])
|
|
conn.iot_descriptors[descriptor["name"]] = iot_descriptor
|
|
|
|
if conn.use_function_call_mode:
|
|
# 注册或获取设备类型
|
|
type_id = register_device_type(descriptor)
|
|
device_functions = device_type_registry.get_device_functions(type_id)
|
|
|
|
# 在连接级注册设备函数
|
|
if hasattr(conn, 'func_handler'):
|
|
for func_name in device_functions:
|
|
conn.func_handler.function_registry.register_function(func_name)
|
|
logger.bind(tag=TAG).info(f"注册IOT函数到function handler: {func_name}")
|
|
functions_changed = True
|
|
|
|
# 如果注册了新函数,更新function描述列表
|
|
if functions_changed and hasattr(conn, 'func_handler'):
|
|
conn.func_handler.upload_functions_desc()
|
|
func_names = conn.func_handler.current_support_functions()
|
|
logger.bind(tag=TAG).info(f"设备类型: {type_id}")
|
|
logger.bind(tag=TAG).info(f"更新function描述列表完成,当前支持的函数: {func_names}")
|
|
|
|
|
|
async def handleIotStatus(conn, states):
|
|
"""处理物联网状态"""
|
|
for state in states:
|
|
for key, value in conn.iot_descriptors.items():
|
|
if key == state["name"]:
|
|
for property_item in value.properties:
|
|
for k, v in state["state"].items():
|
|
if property_item["name"] == k:
|
|
if type(v) != type(property_item["value"]):
|
|
logger.bind(tag=TAG).error(f"属性{property_item['name']}的值类型不匹配")
|
|
break
|
|
else:
|
|
property_item["value"] = v
|
|
logger.bind(tag=TAG).info(f"物联网状态更新: {key} , {property_item['name']} = {v}")
|
|
break
|
|
break
|
|
|
|
|
|
async def get_iot_status(conn, name, property_name):
|
|
"""获取物联网状态"""
|
|
for key, value in conn.iot_descriptors.items():
|
|
if key == name:
|
|
for property_item in value.properties:
|
|
if property_item["name"] == property_name:
|
|
return property_item["value"]
|
|
logger.bind(tag=TAG).warning(f"未找到设备 {name} 的属性 {property_name}")
|
|
return None
|
|
|
|
|
|
async def set_iot_status(conn, name, property_name, value):
|
|
"""设置物联网状态"""
|
|
for key, iot_descriptor in conn.iot_descriptors.items():
|
|
if key == name:
|
|
for property_item in iot_descriptor.properties:
|
|
if property_item["name"] == property_name:
|
|
if type(value) != type(property_item["value"]):
|
|
logger.bind(tag=TAG).error(f"属性{property_item['name']}的值类型不匹配")
|
|
return
|
|
property_item["value"] = value
|
|
logger.bind(tag=TAG).info(f"物联网状态更新: {name} , {property_name} = {value}")
|
|
return
|
|
logger.bind(tag=TAG).warning(f"未找到设备 {name} 的属性 {property_name}")
|
|
|
|
|
|
async def send_iot_conn(conn, name, method_name, parameters):
|
|
"""发送物联网指令"""
|
|
for key, value in conn.iot_descriptors.items():
|
|
if key == name:
|
|
# 找到了设备
|
|
for method in value.methods:
|
|
# 找到了方法
|
|
if method["name"] == method_name:
|
|
await conn.websocket.send(json.dumps({
|
|
"type": "iot",
|
|
"commands": [
|
|
{
|
|
"name": name,
|
|
"method": method_name,
|
|
"parameters": parameters
|
|
}
|
|
]
|
|
}))
|
|
return
|
|
logger.bind(tag=TAG).error(f"未找到方法{method_name}")
|