From 109754fac990cdafc9708765eb6ee28879de5e67 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Wed, 23 Apr 2025 18:17:56 +0800 Subject: [PATCH 1/6] =?UTF-8?q?update:=E5=A2=9E=E5=8A=A0HomeAssistant?= =?UTF-8?q?=E6=95=99=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/FAQ.md | 36 +----- docs/homeassistant-integration.md | 111 ++++++++++++++++++ .../xiaozhi/common/constant/Constant.java | 2 +- main/xiaozhi-server/config/logger.py | 2 +- .../plugins_func/functions/hass_init.py | 2 +- 5 files changed, 115 insertions(+), 38 deletions(-) create mode 100644 docs/homeassistant-integration.md diff --git a/docs/FAQ.md b/docs/FAQ.md index 9be27089..c859498a 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -102,41 +102,7 @@ VAD: ### 7、我想通过小智控制电灯、空调、远程开关机等操作 💡 -本项目,支持以工具调用的方式控制HomeAssistant设备 - -1、首先选择一款支持function call支持的LLM,例如`ChatGLMLLM`。 - -2、在配置文件中,将 `selected_module.Intent` 设置为 `function_call`。 - -3、登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。 - -在配置文件中,配置好你的home assistant的`devices`(被控制的设备)和`api_key`和`base_url`等信息。例如: - -``` yaml -plugins - home_assistant: - devices: - - 客厅,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1 - - 卧室,台灯,switch.iot_cn_831898993_socn1_on_p_2_1 - base_url: http://你的homeassistant地址:8123 - api_key: 你的home assistant api访问令牌 -``` - -最后,允许function_call 插件在配置文件中启用`hass_get_state`(必须)、`hass_set_state`(必须)、`hass_play_music`(不想用ha听音乐可以不启动),例如: - -``` yaml -Intent: - ... - function_call: - type: nointent - functions: - - change_role - - get_weather - - get_news - - hass_get_state - - hass_set_state - - hass_play_music -``` +参考教程[ESP32设备与HomeAssistant集成指南](./homeassistant-integration.md) ### 8、更多问题,可联系我们反馈 💬 diff --git a/docs/homeassistant-integration.md b/docs/homeassistant-integration.md new file mode 100644 index 00000000..d2dc4f9d --- /dev/null +++ b/docs/homeassistant-integration.md @@ -0,0 +1,111 @@ +# ESP32设备与HomeAssistant集成指南 + +## 简介 + +本文档将指导您如何将ESP32设备与HomeAssistant进行集成。 + +## 前提条件 + +- 已安装并配置好`HomeAssistant` +- 已经在HomeAssistant中集成`Xiaomi Home`,并将米家的设备导入进`HomeAssistant` +- `xiaozhi-esp32-server智控台`能正常使用 +- 本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用 + +## 网络环境 + +1、我的`HomeAssistant`部署在下面,版本是`2025.3.4` +``` +http://192.168.4.7:8123 +``` + +2、我的`xiaozhi-esp32-server智控台`和`HomeAssistant`部署在同一台机器的另一个端口,版本是`0.3.10` +``` +http://192.168.4.7:8002 +``` + + +## 配置步骤 + +### 1. 登录`HomeAssistant`整理需要控制的设备清单 + +登录`HomeAssistant`,点击`左下角的设置`,然后进入`设备与服务`,再点击顶部的`实体`。 + +然后在实体中搜索你相关控制的开关,结果出来后,在列表中,点击其中一个结果,这是会出现一个开关的界面。 + +在开关的界面,我们尝试点击开关,看看是开发会随着我们的点击开/关。如果能操作,说明是正常联网的。 + +接着在开关面板找到设置按钮,点击后,可以查看这个开关的`实体标识符`。 + +我们打开一个记事本,按照这样格式整理一条数据: + +位置+英文逗号+设备名称+英文逗号+`实体标识符`+英文分号 + +例如,我在公司,我有一个玩具灯,他的标识符是switch.cuco_cn_460494544_cp1_on_p_2_1,那么就这个写这一条数据 + +``` +公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1; +``` + +当然最后我可能要操作两个灯,我的最终的结果是: + +``` +公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1; +公司,台灯,switch.iot_cn_831898993_socn1_on_p_2_1; +``` + +这段字符,我们成为“设备清单字符”需要保存好,等一下有用。 + + +### 2. 登录`HomeAssistant`拿到开发密钥 + +登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。 + + +### 3. 登录`智控台` + +使用管理员账号,登录`智控台`。点击顶部菜单`参数管理`,搜索`plugins.home_assistant.`,会有三条结果出来 + +编辑`plugins.home_assistant.devices`,把刚才整理的设备清单字符粘贴进去。 + + +编辑`plugins.home_assistant.base_url`,把你部署的`HomeAssistant`接口地址粘贴进去,我粘贴进去的地址是这样的 + +``` +http://192.168.4.7:8123 +``` + +编辑`plugins.home_assistant.api_key`,把你从`HomeAssistant`复制过来的密钥,粘贴进去 + + +### 4. 设置`意图识别`函数 + +在智控台,点击顶部菜单“模型配置”,在左侧栏,找到“意图识别”,找到id为`Intent_function_call`的意图,点击编辑 + +然后在弹框中,在原来的基础上追加两个函数:“hass_get_state”和“hass_set_state” + +修改前 +``` +change_role;get_weather;get_news;play_music +``` + +修改后 + +``` +change_role;get_weather;get_news;play_music;hass_get_state;hass_set_state +``` + +### 6. 手动重启xiaozhi-server + +重启xiaozhi-server程序 + + +### 5. 确认角色配置是否设置了函数意图识别 + +在智控台,点击顶部菜单“智能体管理”,找到设备所在的智能体,点击“配置角色” + +确认意图识别(Intent),是否选择“函数调用意图识别” + + +### 6. 唤醒设别进行控制 + +尝试和esp32说,“打开XXX灯” \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java index e11d5874..47d45e7e 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java +++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java @@ -172,5 +172,5 @@ public interface Constant { /** * 版本号 */ - public static final String VERSION = "0.3.10"; + public static final String VERSION = "0.3.11"; } \ No newline at end of file diff --git a/main/xiaozhi-server/config/logger.py b/main/xiaozhi-server/config/logger.py index b31fb061..705b244c 100644 --- a/main/xiaozhi-server/config/logger.py +++ b/main/xiaozhi-server/config/logger.py @@ -3,7 +3,7 @@ import sys from loguru import logger from config.config_loader import load_config -SERVER_VERSION = "0.3.10" +SERVER_VERSION = "0.3.11" def get_module_abbreviation(module_name, module_dict): diff --git a/main/xiaozhi-server/plugins_func/functions/hass_init.py b/main/xiaozhi-server/plugins_func/functions/hass_init.py index 4bd2f2ec..06d7c8bc 100644 --- a/main/xiaozhi-server/plugins_func/functions/hass_init.py +++ b/main/xiaozhi-server/plugins_func/functions/hass_init.py @@ -13,7 +13,7 @@ def append_devices_to_prompt(conn): "functions", [] ) if "hass_get_state" in funcs or "hass_set_state" in funcs: - prompt = "下面是我家智能设备,可以通过homeassistant控制\n" + prompt = "\n下面是我家智能设备列表(位置,设备名,entity_id),可以通过homeassistant控制\n" devices = conn.config["plugins"]["home_assistant"].get("devices", []) if len(devices) == 0: return From e6a90c40fb4a8102d2439441ce80cc1913e9fb74 Mon Sep 17 00:00:00 2001 From: TinsFox Date: Wed, 23 Apr 2025 20:25:04 +0800 Subject: [PATCH 2/6] =?UTF-8?q?update:=20=E4=BF=AE=E6=94=B9=E6=99=BA?= =?UTF-8?q?=E6=8E=A7=E5=8F=B0=E7=99=BB=E5=BD=95=E5=9C=B0=E5=9D=80=E4=B8=BA?= =?UTF-8?q?8002?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Deployment_all.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Deployment_all.md b/docs/Deployment_all.md index 21c629f8..a0bb2f16 100644 --- a/docs/Deployment_all.md +++ b/docs/Deployment_all.md @@ -353,7 +353,7 @@ pip install -r requirements.txt ## 8.配置项目文件 -使用超级管理员账号,登录智控台 http://127.0.0.1:8001 ,在顶部菜单找到`参数管理`,找到列表中第三条数据,参数编码是`server.secret`,复制它到`参数值`。 +使用超级管理员账号,登录智控台 http://127.0.0.1:8002 ,在顶部菜单找到`参数管理`,找到列表中第三条数据,参数编码是`server.secret`,复制它到`参数值`。 `server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。 From 951370eee75b65aa1eedb1c0bf28f0fa7a929c67 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Wed, 23 Apr 2025 21:29:42 +0800 Subject: [PATCH 3/6] =?UTF-8?q?update:=E8=AE=A9alibl=E3=80=81=E7=AD=89?= =?UTF-8?q?=E9=9D=9E=E5=8E=9F=E7=94=9Fllm=E4=BD=BF=E7=94=A8function?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E5=8D=A1=E9=A1=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/firmware-build.md | 30 +++++++++-- .../core/providers/llm/AliBL/AliBL.py | 19 +++++-- .../core/providers/llm/fastgpt/fastgpt.py | 51 ++++++++++--------- .../core/providers/llm/gemini/gemini.py | 4 ++ 4 files changed, 73 insertions(+), 31 deletions(-) diff --git a/docs/firmware-build.md b/docs/firmware-build.md index f821805a..ebb7413b 100644 --- a/docs/firmware-build.md +++ b/docs/firmware-build.md @@ -1,16 +1,40 @@ # esp32固件编译 -## 第1步 配置环境 +## 第1步 准备你的ota地址 +如果你按照教程使用的是全模块部署,就应该会有ota地址。 + +此刻,请你用浏览器打开你的ota地址,例如我的ota地址 +``` +http://192.168.1.25:8002/xiaozhi/ota/ +``` + +如果显示“OTA接口运行正常,websocket集群数量:X”。那就往下。 + +如果显示“OTA接口运行不正常”,大概是你还没在`智控台`配置`Websocket`地址。那就: + +- 1、使用超级管理员登录智控台 + +- 2、顶部菜单点击`参数管理` + +- 3、在列表中找到`server.websocket`项目,输入你的`Websocket`地址。例如我的就是 + +``` +ws://192.168.1.25:8000/xiaozhi/v1/ +``` + +配置完后,再使用浏览器刷新你的ota接口地址,看看是不是正常了。如果还不正常就,就再次确认一下Websocket是否正常启动,是否配置了Websocket地址。 + +## 第2步 配置环境 先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf) -## 第2步 打开配置文件 +## 第3步 打开配置文件 配置好编译环境后,下载虾哥iaozhi-esp32项目源码, 从这里下载虾哥[xiaozhi-esp32项目源码](https://github.com/78/xiaozhi-esp32)。 下载后,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件。 -## 第3步 修改OTA地址 +## 第4步 修改OTA地址 找到`OTA_VERSION_URL`的`default`的内容,把`https://api.tenclass.net/xiaozhi/ota/` 改成你自己的地址,例如,我的接口地址是`http://192.168.1.25:8002/xiaozhi/ota/`,就把内容改成这个。 diff --git a/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py b/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py index 51efdc06..769f3117 100644 --- a/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py +++ b/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py @@ -6,6 +6,7 @@ from core.providers.llm.base import LLMProviderBase TAG = __name__ logger = setup_logging() + class LLMProvider(LLMProviderBase): def __init__(self, config): self.api_key = config["api_key"] @@ -19,21 +20,25 @@ class LLMProvider(LLMProviderBase): # 处理dialogue if self.is_No_prompt: dialogue.pop(0) - logger.bind(tag=TAG).debug(f"【阿里百练API服务】处理后的dialogue: {dialogue}") + logger.bind(tag=TAG).debug( + f"【阿里百练API服务】处理后的dialogue: {dialogue}" + ) # 构造调用参数 call_params = { "api_key": self.api_key, "app_id": self.app_id, "session_id": session_id, - "messages": dialogue + "messages": dialogue, } if self.memory_id != False: # 百练memory需要prompt参数 prompt = dialogue[-1].get("content") call_params["memory_id"] = self.memory_id call_params["prompt"] = prompt - logger.bind(tag=TAG).debug(f"【阿里百练API服务】处理后的prompt: {prompt}") + logger.bind(tag=TAG).debug( + f"【阿里百练API服务】处理后的prompt: {prompt}" + ) responses = Application.call(**call_params) if responses.status_code != HTTPStatus.OK: @@ -44,9 +49,15 @@ class LLMProvider(LLMProviderBase): ) yield "【阿里百练API服务响应异常】" else: - logger.bind(tag=TAG).debug(f"【阿里百练API服务】构造参数: {call_params}") + logger.bind(tag=TAG).debug( + f"【阿里百练API服务】构造参数: {call_params}" + ) yield responses.output.text except Exception as e: logger.bind(tag=TAG).error(f"【阿里百练API服务】响应异常: {e}") yield "【LLM服务响应异常】" + + def response_with_functions(self, session_id, dialogue, functions=None): + logger.bind(tag=TAG).info(f"阿里百练暂未实现完整的工具调用(function call)") + return self.response(session_id, dialogue) diff --git a/main/xiaozhi-server/core/providers/llm/fastgpt/fastgpt.py b/main/xiaozhi-server/core/providers/llm/fastgpt/fastgpt.py index 38f9a2c9..e0e6ccc7 100644 --- a/main/xiaozhi-server/core/providers/llm/fastgpt/fastgpt.py +++ b/main/xiaozhi-server/core/providers/llm/fastgpt/fastgpt.py @@ -21,37 +21,36 @@ class LLMProvider(LLMProviderBase): # 发起流式请求 with requests.post( - f"{self.base_url}/chat/completions", - headers={"Authorization": f"Bearer {self.api_key}"}, - json={ - "stream": True, - "chatId": session_id, - "detail": self.detail, - "variables": self.variables, - "messages": [ - { - "role": "user", - "content": last_msg["content"] - } - ] - }, - stream=True + f"{self.base_url}/chat/completions", + headers={"Authorization": f"Bearer {self.api_key}"}, + json={ + "stream": True, + "chatId": session_id, + "detail": self.detail, + "variables": self.variables, + "messages": [{"role": "user", "content": last_msg["content"]}], + }, + stream=True, ) as r: for line in r.iter_lines(): if line: try: - if line.startswith(b'data: '): - if line[6:].decode('utf-8') == '[DONE]': + if line.startswith(b"data: "): + if line[6:].decode("utf-8") == "[DONE]": break data = json.loads(line[6:]) - if 'choices' in data and len(data['choices']) > 0: - delta = data['choices'][0].get('delta', {}) - if delta and 'content' in delta and delta['content'] is not None: - content = delta['content'] - if '' in content: + if "choices" in data and len(data["choices"]) > 0: + delta = data["choices"][0].get("delta", {}) + if ( + delta + and "content" in delta + and delta["content"] is not None + ): + content = delta["content"] + if "" in content: continue - if '' in content: + if "" in content: continue yield content @@ -62,4 +61,8 @@ class LLMProvider(LLMProviderBase): except Exception as e: logger.bind(tag=TAG).error(f"Error in response generation: {e}") - yield "【服务响应异常】" \ No newline at end of file + yield "【服务响应异常】" + + def response_with_functions(self, session_id, dialogue, functions=None): + logger.bind(tag=TAG).info(f"fastgpt暂未实现完整的工具调用(function call)") + return self.response(session_id, dialogue) diff --git a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py index ca78caa9..a91a6bf0 100644 --- a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py +++ b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py @@ -134,3 +134,7 @@ class LLMProvider(LLMProviderBase): yield f"JSON解码错误:{e}" except Exception as e: yield f"发生错误:{e}" + + def response_with_functions(self, session_id, dialogue, functions=None): + logger.bind(tag=TAG).info(f"gemini暂未实现完整的工具调用(function call)") + return self.response(session_id, dialogue) From 3c62adab3c0c1547e07ba277a9a165256a8c3f48 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Wed, 23 Apr 2025 23:23:25 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix:=E5=8F=96=E6=B6=88docker=E7=89=88?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0ota=E5=9B=BA=E4=BB=B6=E6=97=B6=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E7=9A=84=E6=96=87=E4=BB=B6=E5=A4=A7=E5=B0=8F=E9=99=90?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile-web | 2 +- README.md | 5 +- docs/docker/nginx.conf | 67 ++++++++++++------ .../src/components/FirmwareDialog.vue | 68 ++++++++++++++++++- 4 files changed, 116 insertions(+), 26 deletions(-) diff --git a/Dockerfile-web b/Dockerfile-web index 0c4001f1..9e5da678 100644 --- a/Dockerfile-web +++ b/Dockerfile-web @@ -23,7 +23,7 @@ RUN apt-get update && \ rm -rf /var/lib/apt/lists/* # 配置Nginx -COPY docs/docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY docs/docker/nginx.conf /etc/nginx/nginx.conf # 复制前端构建产物 COPY --from=web-builder /app/dist /usr/share/nginx/html diff --git a/README.md b/README.md index 2b6173c3..4f762d97 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

本项目为开源智能硬件项目 xiaozhi-esp32提供后端服务
-根据小智通信协议使用Python实现
+根据小智通信协议使用Python、Java、Vue实现
帮助您快速搭建小智服务器

@@ -15,7 +15,8 @@ · 简体中文 · 更新日志 · 部署文档反馈问题反馈问题常见问题

diff --git a/docs/docker/nginx.conf b/docs/docker/nginx.conf index 6a746260..9b9c3889 100644 --- a/docs/docker/nginx.conf +++ b/docs/docker/nginx.conf @@ -1,26 +1,53 @@ -server { - listen 8002; - server_name localhost; +user root; +worker_processes 4; - # 静态资源服务(Vue项目) - location / { - root /usr/share/nginx/html; - try_files $uri $uri/ /index.html; - } +events { + worker_connections 1024; +} - # API反向代理(Java项目) - location /xiaozhi/ { - proxy_pass http://127.0.0.1:8003; - proxy_set_header Host $host; - proxy_cookie_path /api/ /; - proxy_set_header Referer $http_referer; - proxy_set_header Cookie $http_cookie; +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 300; + client_header_timeout 180s; + client_body_timeout 180s; + client_max_body_size 1024M; - proxy_connect_timeout 10; - proxy_send_timeout 10; - proxy_read_timeout 10; + gzip on; + gzip_buffers 32 4K; + gzip_comp_level 6; + gzip_min_length 100; + gzip_types application/javascript text/css text/xml image/jpeg image/gif image/png; + gzip_disable "MSIE [1-6]\."; + gzip_vary on; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + server { + # 无域名访问,就用localhost + server_name localhost; + # 80端口 + listen 8002; + + # 转发到编译后到web目录 + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } + + # 转发到manager-api + location /xiaozhi/ { + proxy_pass http://127.0.0.1:8003; + proxy_set_header Host $host; + proxy_cookie_path /manager/ /; + proxy_set_header Referer $http_referer; + proxy_set_header Cookie $http_cookie; + + proxy_connect_timeout 10; + proxy_send_timeout 10; + proxy_read_timeout 10; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } } } \ No newline at end of file diff --git a/main/manager-web/src/components/FirmwareDialog.vue b/main/manager-web/src/components/FirmwareDialog.vue index 9839f4a3..2cbf83a1 100644 --- a/main/manager-web/src/components/FirmwareDialog.vue +++ b/main/manager-web/src/components/FirmwareDialog.vue @@ -1,5 +1,5 @@