From a21a2e34d02c03b8aa5131c01fc93296632754c9 Mon Sep 17 00:00:00 2001 From: 3030332422 <3030332422@qq.com> Date: Wed, 29 Jul 2026 09:58:26 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=90=88=E5=B9=B6=20HA=20=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=8E=A7=E5=88=B6=E6=8F=92=E4=BB=B6=E4=B8=BA=20hass?= =?UTF-8?q?=5Fstate=20=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/db/changelog/202607290930.sql | 46 +++++++++ .../db/changelog/db.changelog-master.yaml | 7 ++ .../src/components/AgentSnapshotDialog.vue | 3 +- main/manager-web/src/i18n/de.js | 3 +- main/manager-web/src/i18n/en.js | 3 +- main/manager-web/src/i18n/pt_BR.js | 3 +- main/manager-web/src/i18n/vi.js | 3 +- main/manager-web/src/i18n/zh_CN.js | 3 +- main/manager-web/src/i18n/zh_TW.js | 3 +- main/xiaozhi-server/config.yaml | 3 +- .../plugins_func/functions/hass_get_state.py | 98 ------------------- .../plugins_func/functions/hass_init.py | 4 +- .../{hass_set_state.py => hass_state.py} | 87 ++++++++++++++++ 13 files changed, 150 insertions(+), 116 deletions(-) create mode 100644 main/manager-api/src/main/resources/db/changelog/202607290930.sql delete mode 100644 main/xiaozhi-server/plugins_func/functions/hass_get_state.py rename main/xiaozhi-server/plugins_func/functions/{hass_set_state.py => hass_state.py} (67%) diff --git a/main/manager-api/src/main/resources/db/changelog/202607290930.sql b/main/manager-api/src/main/resources/db/changelog/202607290930.sql new file mode 100644 index 00000000..67702f50 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202607290930.sql @@ -0,0 +1,46 @@ +-- liquibase formatted sql + +-- 插入新的 hass_state 插件记录(合并 get_state 的配置字段) +INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields, + sort, creator, create_date, updater, update_date) +VALUES ('SYSTEM_PLUGIN_HA_STATE', + 'Plugin', + 'hass_state', + 'HomeAssistant设备控制', + JSON_ARRAY( + JSON_OBJECT( + 'key', 'base_url', + 'type', 'string', + 'label', 'HA 服务器地址', + 'default', + (SELECT param_value FROM sys_params WHERE param_code = 'plugins.home_assistant.base_url') + ), + JSON_OBJECT( + 'key', 'api_key', + 'type', 'string', + 'label', 'HA API 访问令牌', + 'default', + (SELECT param_value FROM sys_params WHERE param_code = 'plugins.home_assistant.api_key') + ), + JSON_OBJECT( + 'key', 'devices', + 'type', 'array', + 'label', '设备列表(名称,实体ID;…)', + 'default', + (SELECT param_value FROM sys_params WHERE param_code = 'plugins.home_assistant.devices') + ) + ), + 50, 0, NOW(), 0, NOW()); + +-- 迁移已配置的 agent:把指向 HA_GET_STATE 的改成 HA_STATE(保留参数) +UPDATE ai_agent_plugin_mapping +SET plugin_id = 'SYSTEM_PLUGIN_HA_STATE' +WHERE plugin_id = 'SYSTEM_PLUGIN_HA_GET_STATE'; + +-- HA_SET_STATE 本来就没有配置字段(fields=[]),直接删除其 mapping +DELETE FROM ai_agent_plugin_mapping +WHERE plugin_id = 'SYSTEM_PLUGIN_HA_SET_STATE'; + +-- 删除旧的插件定义 +DELETE FROM ai_model_provider +WHERE id IN ('SYSTEM_PLUGIN_HA_GET_STATE', 'SYSTEM_PLUGIN_HA_SET_STATE'); diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 3ce5d234..641d3de5 100644 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -711,3 +711,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202607101200.sql + - changeSet: + id: 202607290930 + author: cgd + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202607290930.sql diff --git a/main/manager-web/src/components/AgentSnapshotDialog.vue b/main/manager-web/src/components/AgentSnapshotDialog.vue index c397acc9..908faf1a 100644 --- a/main/manager-web/src/components/AgentSnapshotDialog.vue +++ b/main/manager-web/src/components/AgentSnapshotDialog.vue @@ -497,8 +497,7 @@ const FALLBACK_PLUGIN_NAME_KEYS = { SYSTEM_PLUGIN_MUSIC: "agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC", SYSTEM_PLUGIN_NEWS_CHINANEWS: "agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS", SYSTEM_PLUGIN_NEWS_NEWSNOW: "agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW", - SYSTEM_PLUGIN_HA_GET_STATE: "agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE", - SYSTEM_PLUGIN_HA_SET_STATE: "agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE", + SYSTEM_PLUGIN_HA_STATE: "agentSnapshot.plugin.SYSTEM_PLUGIN_HA_STATE", SYSTEM_PLUGIN_HA_PLAY_MUSIC: "agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC", SYSTEM_PLUGIN_WEB_SEARCH: "agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH", SYSTEM_PLUGIN_CALL_DEVICE: "agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE" diff --git a/main/manager-web/src/i18n/de.js b/main/manager-web/src/i18n/de.js index 6ed091ba..4e5271fa 100644 --- a/main/manager-web/src/i18n/de.js +++ b/main/manager-web/src/i18n/de.js @@ -903,8 +903,7 @@ export default { 'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': 'Server-Musikplayer', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': 'China News', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'NewsNow-Aggregation', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'HomeAssistant-Statusabfrage', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'HomeAssistant-Statusaktualisierung', + 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_STATE': 'HomeAssistant-Gerätesteuerung', 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'HomeAssistant-Musikplayer', 'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': 'Websuche', 'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': 'Geräteanruf', diff --git a/main/manager-web/src/i18n/en.js b/main/manager-web/src/i18n/en.js index bc424cb8..22d7a76c 100644 --- a/main/manager-web/src/i18n/en.js +++ b/main/manager-web/src/i18n/en.js @@ -953,8 +953,7 @@ export default { 'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': 'Server music player', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': 'China News', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'NewsNow aggregation', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'HomeAssistant state query', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'HomeAssistant state update', + 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_STATE': 'HomeAssistant device control', 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'HomeAssistant music player', 'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': 'Web search', 'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': 'Device-to-device call', diff --git a/main/manager-web/src/i18n/pt_BR.js b/main/manager-web/src/i18n/pt_BR.js index 9fe00e4a..95f2ed1d 100644 --- a/main/manager-web/src/i18n/pt_BR.js +++ b/main/manager-web/src/i18n/pt_BR.js @@ -903,8 +903,7 @@ export default { 'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': 'Reprodutor de música do servidor', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': 'China News', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'Agregação NewsNow', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'Consulta de estado do HomeAssistant', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'Atualização de estado do HomeAssistant', + 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_STATE': 'Controle de dispositivo do HomeAssistant', 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'Reprodutor de música do HomeAssistant', 'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': 'Pesquisa na web', 'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': 'Chamada entre dispositivos', diff --git a/main/manager-web/src/i18n/vi.js b/main/manager-web/src/i18n/vi.js index 294abacd..a1e06d67 100644 --- a/main/manager-web/src/i18n/vi.js +++ b/main/manager-web/src/i18n/vi.js @@ -903,8 +903,7 @@ export default { 'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': 'Trình phát nhạc máy chủ', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': 'Tin tức China News', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'Tổng hợp NewsNow', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'Truy vấn trạng thái HomeAssistant', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'Cập nhật trạng thái HomeAssistant', + 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_STATE': 'Điều khiển thiết bị HomeAssistant', 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'Trình phát nhạc HomeAssistant', 'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': 'Tìm kiếm web', 'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': 'Gọi thiết bị', diff --git a/main/manager-web/src/i18n/zh_CN.js b/main/manager-web/src/i18n/zh_CN.js index cf526c1a..ad36061c 100644 --- a/main/manager-web/src/i18n/zh_CN.js +++ b/main/manager-web/src/i18n/zh_CN.js @@ -953,8 +953,7 @@ export default { 'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': '服务器音乐播放', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': '中新网新闻', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'newsnow新闻聚合', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'HomeAssistant设备状态查询', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'HomeAssistant设备状态修改', + 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_STATE': 'HomeAssistant设备控制', 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'HomeAssistant音乐播放', 'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': '联网搜索', 'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': '设备呼叫设备', diff --git a/main/manager-web/src/i18n/zh_TW.js b/main/manager-web/src/i18n/zh_TW.js index f1d51e2e..597d3a0b 100644 --- a/main/manager-web/src/i18n/zh_TW.js +++ b/main/manager-web/src/i18n/zh_TW.js @@ -903,8 +903,7 @@ export default { 'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': '伺服器音樂播放', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': '中新網新聞', 'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'newsnow新聞聚合', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'HomeAssistant設備狀態查詢', - 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'HomeAssistant設備狀態修改', + 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_STATE': 'HomeAssistant設備控制', 'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'HomeAssistant音樂播放', 'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': '聯網搜尋', 'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': '設備呼叫設備', diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 323b562e..444798aa 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -286,8 +286,7 @@ Intent: # play_music是服务器自带的音乐播放,hass_play_music是通过home assistant控制的独立外部程序音乐播放 # 如果用了hass_play_music,就不要开启play_music,两者只留一个 - play_music - #- hass_get_state - #- hass_set_state + #- hass_state #- hass_play_music Memory: diff --git a/main/xiaozhi-server/plugins_func/functions/hass_get_state.py b/main/xiaozhi-server/plugins_func/functions/hass_get_state.py deleted file mode 100644 index e56fe5a8..00000000 --- a/main/xiaozhi-server/plugins_func/functions/hass_get_state.py +++ /dev/null @@ -1,98 +0,0 @@ -import httpx -from config.logger import setup_logging -from plugins_func.functions.hass_init import initialize_hass_handler -from plugins_func.register import register_function, ToolType, ActionResponse, Action -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from core.connection import ConnectionHandler - -TAG = __name__ -logger = setup_logging() - -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) -async def hass_get_state(conn: "ConnectionHandler", entity_id=""): - try: - ha_response = await handle_hass_get_state(conn, entity_id) - return ActionResponse(Action.REQLLM, ha_response, None) - except httpx.TimeoutException: - logger.bind(tag=TAG).error("获取Home Assistant状态超时") - return ActionResponse(Action.ERROR, "请求超时", None) - except Exception as e: - error_msg = f"执行Home Assistant操作失败" - logger.bind(tag=TAG).error(error_msg) - return ActionResponse(Action.ERROR, error_msg, None) - - -async def handle_hass_get_state(conn: "ConnectionHandler", entity_id): - ha_config = initialize_hass_handler(conn) - api_key = ha_config.get("api_key") - base_url = ha_config.get("base_url") - url = f"{base_url}/api/states/{entity_id}" - headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} - - async with httpx.AsyncClient(timeout=httpx.Timeout(5.0, connect=3.0)) as client: - response = await client.get(url, headers=headers) - - if response.status_code == 200: - responsetext = "设备状态:" + response.json()["state"] + " " - logger.bind(tag=TAG).info(f"api返回内容: {response.json()}") - - if "media_title" in response.json()["attributes"]: - responsetext = ( - responsetext - + "正在播放的是:" - + str(response.json()["attributes"]["media_title"]) - + " " - ) - if "volume_level" in response.json()["attributes"]: - responsetext = ( - responsetext - + "音量是:" - + str(response.json()["attributes"]["volume_level"]) - + " " - ) - if "color_temp_kelvin" in response.json()["attributes"]: - responsetext = ( - responsetext - + "色温是:" - + str(response.json()["attributes"]["color_temp_kelvin"]) - + " " - ) - if "rgb_color" in response.json()["attributes"]: - responsetext = ( - responsetext - + "rgb颜色是:" - + str(response.json()["attributes"]["rgb_color"]) - + " " - ) - if "brightness" in response.json()["attributes"]: - responsetext = ( - responsetext - + "亮度是:" - + str(response.json()["attributes"]["brightness"]) - + " " - ) - logger.bind(tag=TAG).info(f"查询返回内容: {responsetext}") - return responsetext - else: - return f"切换失败,错误码: {response.status_code}" diff --git a/main/xiaozhi-server/plugins_func/functions/hass_init.py b/main/xiaozhi-server/plugins_func/functions/hass_init.py index dadb190b..a106d16d 100644 --- a/main/xiaozhi-server/plugins_func/functions/hass_init.py +++ b/main/xiaozhi-server/plugins_func/functions/hass_init.py @@ -16,7 +16,7 @@ def append_devices_to_prompt(conn): config_source = ( "home_assistant" if plugins_config.get("home_assistant") - else "hass_get_state" + else "hass_state" ) if "hass_get_state" in funcs or "hass_set_state" in funcs: @@ -36,7 +36,7 @@ def initialize_hass_handler(conn): plugins_config = conn.config.get("plugins", {}) # 确定配置来源 config_source = ( - "home_assistant" if plugins_config.get("home_assistant") else "hass_get_state" + "home_assistant" if plugins_config.get("home_assistant") else "hass_state" ) if not plugins_config.get(config_source): return ha_config diff --git a/main/xiaozhi-server/plugins_func/functions/hass_set_state.py b/main/xiaozhi-server/plugins_func/functions/hass_state.py similarity index 67% rename from main/xiaozhi-server/plugins_func/functions/hass_set_state.py rename to main/xiaozhi-server/plugins_func/functions/hass_state.py index 293ca9e2..240f7cd3 100644 --- a/main/xiaozhi-server/plugins_func/functions/hass_set_state.py +++ b/main/xiaozhi-server/plugins_func/functions/hass_state.py @@ -10,6 +10,24 @@ if TYPE_CHECKING: TAG = __name__ logger = setup_logging() +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"], + }, + }, +} + hass_set_state_function_desc = { "type": "function", "function": { @@ -52,6 +70,20 @@ hass_set_state_function_desc = { } +@register_function("hass_get_state", hass_get_state_function_desc, ToolType.SYSTEM_CTL) +async def hass_get_state(conn: "ConnectionHandler", entity_id=""): + try: + ha_response = await handle_hass_get_state(conn, entity_id) + return ActionResponse(Action.REQLLM, ha_response, None) + except httpx.TimeoutException: + logger.bind(tag=TAG).error("获取Home Assistant状态超时") + return ActionResponse(Action.ERROR, "请求超时", None) + except Exception as e: + error_msg = f"执行Home Assistant操作失败" + logger.bind(tag=TAG).error(error_msg) + return ActionResponse(Action.ERROR, error_msg, None) + + @register_function("hass_set_state", hass_set_state_function_desc, ToolType.SYSTEM_CTL) async def hass_set_state(conn: "ConnectionHandler", entity_id="", state=None): if state is None: @@ -68,6 +100,61 @@ async def hass_set_state(conn: "ConnectionHandler", entity_id="", state=None): return ActionResponse(Action.ERROR, error_msg, None) +async def handle_hass_get_state(conn: "ConnectionHandler", entity_id): + ha_config = initialize_hass_handler(conn) + api_key = ha_config.get("api_key") + base_url = ha_config.get("base_url") + url = f"{base_url}/api/states/{entity_id}" + headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} + + async with httpx.AsyncClient(timeout=httpx.Timeout(5.0, connect=3.0)) as client: + response = await client.get(url, headers=headers) + + if response.status_code == 200: + responsetext = "设备状态:" + response.json()["state"] + " " + logger.bind(tag=TAG).info(f"api返回内容: {response.json()}") + + if "media_title" in response.json()["attributes"]: + responsetext = ( + responsetext + + "正在播放的是:" + + str(response.json()["attributes"]["media_title"]) + + " " + ) + if "volume_level" in response.json()["attributes"]: + responsetext = ( + responsetext + + "音量是:" + + str(response.json()["attributes"]["volume_level"]) + + " " + ) + if "color_temp_kelvin" in response.json()["attributes"]: + responsetext = ( + responsetext + + "色温是:" + + str(response.json()["attributes"]["color_temp_kelvin"]) + + " " + ) + if "rgb_color" in response.json()["attributes"]: + responsetext = ( + responsetext + + "rgb颜色是:" + + str(response.json()["attributes"]["rgb_color"]) + + " " + ) + if "brightness" in response.json()["attributes"]: + responsetext = ( + responsetext + + "亮度是:" + + str(response.json()["attributes"]["brightness"]) + + " " + ) + logger.bind(tag=TAG).info(f"查询返回内容: {responsetext}") + return responsetext + else: + return f"切换失败,错误码: {response.status_code}" + + async def handle_hass_set_state(conn: "ConnectionHandler", entity_id, state): ha_config = initialize_hass_handler(conn) api_key = ha_config.get("api_key")