mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
服务端支持function的形式调用homeassistant控制智能设备
This commit is contained in:
@@ -352,7 +352,9 @@ VAD:
|
|||||||
|
|
||||||
### 6、我想通过小智控制电灯、空调、远程开关机等操作 💡
|
### 6、我想通过小智控制电灯、空调、远程开关机等操作 💡
|
||||||
|
|
||||||
建议:在配置文件中将 `LLM` 设置为 `HomeAssistant`,通过 调用`HomeAssistant`接口实现相关控制。
|
方法1:在配置文件中将 `LLM` 设置为 `HomeAssistant`,通过 调用`HomeAssistant`接口实现相关控制。
|
||||||
|
|
||||||
|
方法2:以工具调用的方式控制HomeAssistant设备,LLM中配置HomeAssistant的api_key、base_url,不选择HomeAssistant做LLM,但必须配置一款支持tool调用的LLM,最后在提示词prompt里以示例的格式设置需要控制的设备,音乐播放需要在homeassistant中对接好musicassistant,同时音乐有削刮出媒体信息,对应的媒体播放器配置在设备列表中即可。
|
||||||
|
|
||||||
### 7、更多问题,可联系我们反馈 💬
|
### 7、更多问题,可联系我们反馈 💬
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ prompt: |
|
|||||||
请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。
|
请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。
|
||||||
现在我正在和你进行语音聊天,我们开始吧。
|
现在我正在和你进行语音聊天,我们开始吧。
|
||||||
如果用户希望结束对话,请在最后说“拜拜”或“再见”。
|
如果用户希望结束对话,请在最后说“拜拜”或“再见”。
|
||||||
|
#-- hass 相关function的配置格式 请将你家需要控制的设备以下列格式追加在prompt里--
|
||||||
|
# 下面是我家智能设备,可以通过homeassistant控制
|
||||||
|
# 房间,设备别名,设备id
|
||||||
|
# 书房,吸顶灯,light.649e3159aa36_light
|
||||||
|
# 书房,homepod,media_player.shu_fang
|
||||||
|
# 书房,灯带,light.plug_158df955a6167a
|
||||||
|
# 书房,窗帘,cover.curtain_158d000710f507
|
||||||
|
|
||||||
# 使用完声音文件后删除文件(Delete the sound file when you are done using it)
|
# 使用完声音文件后删除文件(Delete the sound file when you are done using it)
|
||||||
delete_audio: true
|
delete_audio: true
|
||||||
|
|
||||||
@@ -101,6 +109,10 @@ Intent:
|
|||||||
functions:
|
functions:
|
||||||
- change_role
|
- change_role
|
||||||
- get_weather
|
- get_weather
|
||||||
|
- play_music
|
||||||
|
#- hass_play_music
|
||||||
|
#- hass_get_state
|
||||||
|
#- hass_set_state
|
||||||
|
|
||||||
# 插件的基础配置
|
# 插件的基础配置
|
||||||
plugins:
|
plugins:
|
||||||
@@ -469,4 +481,4 @@ manager:
|
|||||||
enabled: false
|
enabled: false
|
||||||
ip: 0.0.0.0
|
ip: 0.0.0.0
|
||||||
port: 8002
|
port: 8002
|
||||||
use_private_config: false
|
use_private_config: false
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class FunctionHandler:
|
|||||||
def register_nessary_functions(self):
|
def register_nessary_functions(self):
|
||||||
"""注册必要的函数"""
|
"""注册必要的函数"""
|
||||||
self.function_registry.register_function("handle_exit_intent")
|
self.function_registry.register_function("handle_exit_intent")
|
||||||
self.function_registry.register_function("play_music")
|
#self.function_registry.register_function("play_music")
|
||||||
self.function_registry.register_function("plugin_loader")
|
self.function_registry.register_function("plugin_loader")
|
||||||
self.function_registry.register_function("get_time")
|
self.function_registry.register_function("get_time")
|
||||||
|
|
||||||
@@ -80,4 +80,4 @@ class FunctionHandler:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"处理function call错误: {e}")
|
logger.bind(tag=TAG).error(f"处理function call错误: {e}")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
from plugins_func.register import register_function,ToolType, ActionResponse, Action
|
||||||
|
from config.logger import setup_logging
|
||||||
|
import asyncio
|
||||||
|
import requests
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
HASS_CACHE = {}
|
||||||
|
|
||||||
|
hass_get_state_function_desc ={
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "hass_get_state",
|
||||||
|
"description": "获取homeassistant里设备的状态,包括灯光亮度,媒体播放器的音量,设备的暂停、继续操作",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"entity_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "需要操作的设备id,homeassistant里的entity_id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["entity_id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@register_function("hass_get_state", hass_get_state_function_desc, ToolType.SYSTEM_CTL)
|
||||||
|
def hass_get_state(conn, entity_id=''):
|
||||||
|
try:
|
||||||
|
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
handle_hass_get_state(conn, entity_id),
|
||||||
|
conn.loop
|
||||||
|
)
|
||||||
|
ha_response = future.result()
|
||||||
|
return ActionResponse(action=Action.REQLLM, result="执行成功", response=ha_response)
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"处理设置属性意图错误: {e}")
|
||||||
|
def initialize_hass_handler(conn):
|
||||||
|
config = conn.config
|
||||||
|
global HASS_CACHE
|
||||||
|
if HASS_CACHE == {}:
|
||||||
|
logger.bind(tag=TAG).info(f"实例化HASS:")
|
||||||
|
if "HomeAssistant" in config["LLM"]:
|
||||||
|
HASS_CACHE['base_url'] = config["LLM"]['HomeAssistant']['base_url']
|
||||||
|
HASS_CACHE['api_key'] = config["LLM"]['HomeAssistant']['api_key']
|
||||||
|
else:
|
||||||
|
logger.bind(tag=TAG).error(f"使用前请在config文件中配置: LLM.HomeAssistant.base_url LLM.HomeAssistant.api_key")
|
||||||
|
async def handle_hass_get_state(conn, entity_id):
|
||||||
|
initialize_hass_handler(conn)
|
||||||
|
global HASS_CACHE
|
||||||
|
api_key = HASS_CACHE['api_key']
|
||||||
|
base_url = HASS_CACHE['base_url']
|
||||||
|
url = f"{base_url}/api/states/{entity_id}"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {api_key}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
#logger.bind(tag=TAG).info(f"获取状态: url:{url},return_code:{response.status_code},{response.json()}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()['state']
|
||||||
|
#return response.json()['attributes']
|
||||||
|
#response.attributes
|
||||||
|
else:
|
||||||
|
return f"切换失败,错误码: {response.status_code}"
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
from plugins_func.register import register_function,ToolType, ActionResponse, Action
|
||||||
|
from config.logger import setup_logging
|
||||||
|
import asyncio
|
||||||
|
import requests
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
HASS_CACHE = {}
|
||||||
|
|
||||||
|
hass_play_music_function_desc = {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "hass_play_music",
|
||||||
|
"description": "用户想听音乐、有声书的时候使用,在房间的媒体播放器(media_player)里播放对应音频",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"media_content_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "可以是音乐或有声书的专辑名称、歌曲名、演唱者,如果未指定就填random"
|
||||||
|
},
|
||||||
|
"entity_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "需要操作的音箱的设备id,homeassistant里的entity_id,media_player开头"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["media_content_id", "entity_id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_function('hass_play_music', hass_play_music_function_desc, ToolType.SYSTEM_CTL)
|
||||||
|
|
||||||
|
def hass_play_music(conn, entity_id='', media_content_id='random'):
|
||||||
|
try:
|
||||||
|
#logger.bind(tag=TAG).error(f"arguments: {arguments}")
|
||||||
|
|
||||||
|
#entity_id = arguments["entity_id"]
|
||||||
|
#media_content_id = arguments["media_content_id"]
|
||||||
|
|
||||||
|
#logger.bind(tag=TAG).error(f"entity_id: {entity_id}")
|
||||||
|
|
||||||
|
|
||||||
|
# 执行音乐播放命令
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
handle_hass_play_music(conn, entity_id, media_content_id),
|
||||||
|
conn.loop
|
||||||
|
)
|
||||||
|
ha_response = future.result()
|
||||||
|
return ActionResponse(action=Action.RESPONSE, result="退出意图已处理", response=ha_response)
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"处理音乐意图错误: {e}")
|
||||||
|
|
||||||
|
def initialize_hass_handler(conn):
|
||||||
|
config = conn.config
|
||||||
|
global HASS_CACHE
|
||||||
|
if HASS_CACHE == {}:
|
||||||
|
logger.bind(tag=TAG).info(f"实例化HASS:")
|
||||||
|
if "HomeAssistant" in config["LLM"]:
|
||||||
|
HASS_CACHE['base_url'] = config["LLM"]['HomeAssistant']['base_url']
|
||||||
|
HASS_CACHE['api_key'] = config["LLM"]['HomeAssistant']['api_key']
|
||||||
|
else:
|
||||||
|
logger.bind(tag=TAG).error(f"使用前请在config文件中配置: LLM.HomeAssistant.base_url LLM.HomeAssistant.api_key")
|
||||||
|
|
||||||
|
async def handle_hass_play_music( conn, entity_id, media_content_id):
|
||||||
|
initialize_hass_handler(conn)
|
||||||
|
global HASS_CACHE
|
||||||
|
api_key = HASS_CACHE['api_key']
|
||||||
|
base_url = HASS_CACHE['base_url']
|
||||||
|
url = f"{base_url}/api/services/music_assistant/play_media"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {api_key}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"entity_id": entity_id,
|
||||||
|
"media_id": media_content_id
|
||||||
|
}
|
||||||
|
response = requests.post(url, headers=headers, json=data)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return f"正在播放{media_content_id}的音乐"
|
||||||
|
else:
|
||||||
|
return f"音乐播放失败,错误码: {response.status_code}"
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
from plugins_func.register import register_function,ToolType, ActionResponse, Action
|
||||||
|
from config.logger import setup_logging
|
||||||
|
import asyncio
|
||||||
|
import requests
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
HASS_CACHE = {}
|
||||||
|
|
||||||
|
hass_set_state_function_desc ={
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "hass_set_state",
|
||||||
|
"description": "设置homeassistant里设备的状态,包括开、关,调整灯光亮度,调整播放器的音量,设备的暂停、继续、静音操作",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"state": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type":{
|
||||||
|
"type":"string",
|
||||||
|
"description":"需要操作的动作,打开设备:turn_on,关闭设备:turn_off,增加亮度:brightness_up,降低亮度:brightness_down,设置亮度:brightness_value,增加>音量:,volume_up降低音量:volume_down,设置音量:volume_set,设备暂停:pause,设备继续:continue,静音/取消静音:volume_mute"
|
||||||
|
},
|
||||||
|
"input":{
|
||||||
|
"type":"int",
|
||||||
|
"description": "只有在设置音量,设置亮度时候才需要,有效值为1-100,对应音量和亮度的1%-100%"
|
||||||
|
},
|
||||||
|
"is_muted":{
|
||||||
|
"type":"string",
|
||||||
|
"description": "只有在设置静音操作时才需要,设置静音的时候该值为true,取消静音时该值为false"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type"]
|
||||||
|
},
|
||||||
|
"entity_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "需要操作的设备id,homeassistant里的entity_id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["state", "entity_id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@register_function('hass_set_state', hass_set_state_function_desc, ToolType.SYSTEM_CTL)
|
||||||
|
def hass_set_state(conn, entity_id='', state={}):
|
||||||
|
try:
|
||||||
|
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
handle_hass_set_state(conn, entity_id, state),
|
||||||
|
conn.loop
|
||||||
|
)
|
||||||
|
ha_response = future.result()
|
||||||
|
return ActionResponse(action=Action.REQLLM, result="执行成功", response=ha_response)
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"处理设置属性意图错误: {e}")
|
||||||
|
def initialize_hass_handler(conn):
|
||||||
|
config = conn.config
|
||||||
|
global HASS_CACHE
|
||||||
|
if HASS_CACHE == {}:
|
||||||
|
logger.bind(tag=TAG).info(f"实例化HASS:")
|
||||||
|
if "HomeAssistant" in config["LLM"]:
|
||||||
|
HASS_CACHE['base_url'] = config["LLM"]['HomeAssistant']['base_url']
|
||||||
|
HASS_CACHE['api_key'] = config["LLM"]['HomeAssistant']['api_key']
|
||||||
|
else:
|
||||||
|
logger.bind(tag=TAG).error(f"使用前请在config文件中配置: LLM.HomeAssistant.base_url LLM.HomeAssistant.api_key")
|
||||||
|
|
||||||
|
async def handle_hass_set_state(conn, entity_id, state):
|
||||||
|
initialize_hass_handler(conn)
|
||||||
|
global HASS_CACHE
|
||||||
|
api_key = HASS_CACHE['api_key']
|
||||||
|
base_url = HASS_CACHE['base_url']
|
||||||
|
'''
|
||||||
|
state = { "type":"brightness_up","input":"80","is_muted":"true"}
|
||||||
|
'''
|
||||||
|
domains = entity_id.split(".")
|
||||||
|
if len(domains) > 1:
|
||||||
|
domain = domains[0]
|
||||||
|
else:
|
||||||
|
return "执行失败,错误的设备id"
|
||||||
|
action = ''
|
||||||
|
arg = ''
|
||||||
|
value = ''
|
||||||
|
if state['type'] == 'turn_on':
|
||||||
|
description = "设备已打开"
|
||||||
|
if domain == "cover":
|
||||||
|
action = "open_cover"
|
||||||
|
elif domain == "vacuum":
|
||||||
|
action = "start"
|
||||||
|
else:
|
||||||
|
action = "turn_on"
|
||||||
|
action = 'turn_on'
|
||||||
|
elif state['type'] == 'turn_off':
|
||||||
|
description = "设备已关闭"
|
||||||
|
if domain == 'cover':
|
||||||
|
action = "close_cover"
|
||||||
|
elif domain == 'vacuum':
|
||||||
|
action = "stop"
|
||||||
|
else:
|
||||||
|
action = "turn_off"
|
||||||
|
elif state['type'] == 'brightness_up':
|
||||||
|
description = "灯光已调亮"
|
||||||
|
action = 'turn_on'
|
||||||
|
arg = 'brightness_step_pct'
|
||||||
|
value = 10
|
||||||
|
elif state['type'] == 'brightness_down':
|
||||||
|
description = "灯光已调暗"
|
||||||
|
action = 'turn_on'
|
||||||
|
arg = 'brightness_step_pct'
|
||||||
|
value = -10
|
||||||
|
elif state['type'] == 'brightness_value':
|
||||||
|
description = f"亮度已调整到{state['input']}"
|
||||||
|
action = 'turn_on'
|
||||||
|
arg = 'brightness_pct'
|
||||||
|
value = state['input']
|
||||||
|
elif state['type'] == 'volume_up':
|
||||||
|
description = "音量已调大"
|
||||||
|
action = state['type']
|
||||||
|
elif state['type'] == 'volume_down':
|
||||||
|
description = "音量已调小"
|
||||||
|
action = state['type']
|
||||||
|
elif state['type'] == 'volume_set':
|
||||||
|
description = f"音量已调整到{state['input']}"
|
||||||
|
action = state['type']
|
||||||
|
arg = 'volume_level'
|
||||||
|
value = state['input']
|
||||||
|
elif state['type'] == 'volume_mute':
|
||||||
|
description = f"设备已静音"
|
||||||
|
action = state['type']
|
||||||
|
arg = 'is_volume_muted'
|
||||||
|
value = state['is_muted']
|
||||||
|
elif state['type'] == 'pause':
|
||||||
|
description = f"设备已暂停"
|
||||||
|
action = state['type']
|
||||||
|
if domain == 'media_player':
|
||||||
|
action = 'media_pause'
|
||||||
|
if domain == 'cover':
|
||||||
|
action = 'stop_cover'
|
||||||
|
if domain == 'vacuum':
|
||||||
|
action = 'pause'
|
||||||
|
elif state['type'] == 'continue':
|
||||||
|
description = f"设备已继续"
|
||||||
|
if domain == 'media_player':
|
||||||
|
action = 'media_play'
|
||||||
|
if domain == 'vacuum':
|
||||||
|
action = 'start'
|
||||||
|
else:
|
||||||
|
return f"{domain} {state.type}功能尚未支持"
|
||||||
|
|
||||||
|
if arg == '':
|
||||||
|
data = {
|
||||||
|
"entity_id": entity_id,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
data = {
|
||||||
|
"entity_id": entity_id,
|
||||||
|
arg: value
|
||||||
|
}
|
||||||
|
url = f"{base_url}/api/services/{domain}/{action}"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {api_key}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
response = requests.post(url, headers=headers, json=data)
|
||||||
|
logger.bind(tag=TAG).info(f"设置状态:url:{url},return_code:{response.status_code}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
return description
|
||||||
|
else:
|
||||||
|
return f"设置失败,错误码: {response.status_code}"
|
||||||
|
|
||||||
Reference in New Issue
Block a user