Compare commits
3 Commits
d5e70dfc8b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c1368962cc | |||
| fa41de0ae8 | |||
| 6983a1f1ba |
93
AGENTS.md
93
AGENTS.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## 项目概述
|
## 项目概述
|
||||||
|
|
||||||
MimiClaw 是一个运行在 ESP32-S3 上的 AI 助手,使用纯 C 语言编写。用户通过 Telegram 与之交互,设备连接 WiFi 后,将消息传递给 LLM(大语言模型)进行处理,并支持工具调用。
|
MimiClaw 是一个运行在 ESP32-S3 上的 AI 助手,使用纯 C 语言编写。用户通过 Telegram 或飞书与设备交互,设备连接 WiFi 后,将消息传递给 LLM(大语言模型)进行处理,并支持工具调用。
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
@@ -14,20 +14,21 @@ mimiclaw/
|
|||||||
│ │ └── context_builder.c # 构建上下文(系统提示、记忆等)
|
│ │ └── context_builder.c # 构建上下文(系统提示、记忆等)
|
||||||
│ ├── llm/ # LLM 代理
|
│ ├── llm/ # LLM 代理
|
||||||
│ │ ├── llm_proxy.c # 处理与 LLM API 的通信
|
│ │ ├── llm_proxy.c # 处理与 LLM API 的通信
|
||||||
│ │ └── llm_proxy.h # LLM 代理的头文件
|
│ │ └── llm_provider.c # 多提供商支持(Anthropic/OpenAI/硅基流动/火山引擎)
|
||||||
│ ├── cli/ # 串口命令行界面
|
│ ├── cli/ # 串口命令行界面
|
||||||
│ │ └── serial_cli.c # 处理运行时配置命令
|
│ │ └── serial_cli.c # 处理运行时配置命令
|
||||||
│ ├── channels/ # 输入/输出通道
|
│ ├── channels/ # 输入/输出通道
|
||||||
│ │ ├── telegram/ # Telegram 机器人集成
|
│ │ ├── telegram/ # Telegram 机器人集成(可选)
|
||||||
│ │ └── feishu/ # 飞书机器人集成
|
│ │ └── feishu/ # 飞书机器人集成(可选)
|
||||||
│ ├── tools/ # LLM 可调用的工具
|
│ ├── tools/ # LLM 可调用的工具
|
||||||
│ ├── memory/ # 记忆和会话管理
|
│ ├── memory/ # 记忆和会话管理
|
||||||
│ ├── proxy/ # HTTP 代理支持
|
│ ├── proxy/ # HTTP/SOCKS5 代理支持
|
||||||
│ ├── cron/ # 定时任务调度
|
│ ├── cron/ # 定时任务调度
|
||||||
│ ├── heartbeat/ # 心跳服务
|
│ ├── heartbeat/ # 心跳服务
|
||||||
│ ├── gateway/ # WebSocket 网关
|
│ ├── gateway/ # WebSocket 网关
|
||||||
│ ├── onboard/ # WiFi 配置门户
|
│ ├── onboard/ # WiFi 配置门户(Captive Portal)
|
||||||
│ ├── skills/ # 技能加载器
|
│ ├── skills/ # 技能加载器
|
||||||
|
│ ├── bus/ # 消息总线(连接通道和代理循环)
|
||||||
│ ├── mimi_config.h # 全局配置定义
|
│ ├── mimi_config.h # 全局配置定义
|
||||||
│ ├── mimi_secrets.h # 构建时密钥(需用户创建)
|
│ ├── mimi_secrets.h # 构建时密钥(需用户创建)
|
||||||
│ └── mimi_secrets.h.example # 密钥模板
|
│ └── mimi_secrets.h.example # 密钥模板
|
||||||
@@ -72,6 +73,71 @@ mimiclaw/
|
|||||||
- 工具注册在 `tool_registry.c`
|
- 工具注册在 `tool_registry.c`
|
||||||
- 支持的工具:`web_search`、`get_current_time`、`cron_add/list/remove`
|
- 支持的工具:`web_search`、`get_current_time`、`cron_add/list/remove`
|
||||||
|
|
||||||
|
## 模块配置系统
|
||||||
|
|
||||||
|
MimiClaw 支持**编译时模块开关**,可以禁用不需要的模块以减少固件体积和内存占用。
|
||||||
|
|
||||||
|
### 可配置模块
|
||||||
|
|
||||||
|
| 模块 | 配置项 | 默认 | 说明 |
|
||||||
|
|------|--------|------|------|
|
||||||
|
| Telegram | `CONFIG_MIMI_CHAN_TELEGRAM` | n | Telegram 机器人集成 |
|
||||||
|
| 飞书 | `CONFIG_MIMI_CHAN_FEISHU` | y | 飞书机器人集成 |
|
||||||
|
| WebSocket | `CONFIG_MIMI_WS_SERVER` | y | WebSocket 网关 |
|
||||||
|
| Web Search | `CONFIG_MIMI_TOOL_WEB_SEARCH` | y | 网络搜索工具 |
|
||||||
|
| GPIO | `CONFIG_MIMI_TOOL_GPIO` | n | GPIO 控制工具 |
|
||||||
|
| WiFi 配置页 | `CONFIG_MIMI_WIFI_ONBOARD` | y | Captive Portal |
|
||||||
|
| OTA | `CONFIG_MIMI_OTA` | n | OTA 升级(未实现) |
|
||||||
|
|
||||||
|
### 修改模块配置
|
||||||
|
|
||||||
|
编辑 `sdkconfig.defaults` 文件,添加或修改配置项:
|
||||||
|
|
||||||
|
```
|
||||||
|
CONFIG_MIMI_CHAN_TELEGRAM=n
|
||||||
|
CONFIG_MIMI_CHAN_FEISHU=y
|
||||||
|
CONFIG_MIMI_TOOL_WEB_SEARCH=y
|
||||||
|
CONFIG_MIMI_TOOL_GPIO=n
|
||||||
|
```
|
||||||
|
|
||||||
|
修改后必须重新编译:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
idf.py fullclean && idf.py build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加新模块的编译时开关
|
||||||
|
|
||||||
|
1. **创建 `main/Kconfig.projbuild`**(关键!没有它配置项不会被识别):
|
||||||
|
```kconfig
|
||||||
|
config MIMI_MODULE_EXAMPLE
|
||||||
|
bool "Example module"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Enable example module for MimiClaw.
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 在 `sdkconfig.defaults` 中添加配置项:
|
||||||
|
```
|
||||||
|
CONFIG_MIMI_MODULE_EXAMPLE=y
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 在 `main/CMakeLists.txt` 中条件编译:
|
||||||
|
```cmake
|
||||||
|
if(CONFIG_MIMI_MODULE_EXAMPLE)
|
||||||
|
list(APPEND srcs "modules/example/example.c")
|
||||||
|
endif()
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 在模块头文件中添加 stub:
|
||||||
|
```c
|
||||||
|
#ifdef CONFIG_MIMI_MODULE_EXAMPLE
|
||||||
|
esp_err_t example_init(void);
|
||||||
|
#else
|
||||||
|
static inline esp_err_t example_init(void) { return ESP_OK; }
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
## 构建和烧录
|
## 构建和烧录
|
||||||
|
|
||||||
### 前提条件
|
### 前提条件
|
||||||
@@ -85,7 +151,9 @@ idf.py set-target esp32s3
|
|||||||
|
|
||||||
# 配置(首次需要)
|
# 配置(首次需要)
|
||||||
cp main/mimi_secrets.h.example main/mimi_secrets.h
|
cp main/mimi_secrets.h.example main/mimi_secrets.h
|
||||||
# 编辑 mimi_secrets.h 填写 WiFi、Telegram、LLM 密钥
|
# 编辑 mimi_secrets.h 填写 WiFi、LLM 密钥
|
||||||
|
|
||||||
|
# 编辑 sdkconfig.defaults 启用/禁用模块(可选)
|
||||||
|
|
||||||
# 清理构建(修改配置后必须执行)
|
# 清理构建(修改配置后必须执行)
|
||||||
idf.py fullclean && idf.py build
|
idf.py fullclean && idf.py build
|
||||||
@@ -108,6 +176,16 @@ config_show # 显示当前配置(脱敏)
|
|||||||
config_reset # 重置为构建时配置
|
config_reset # 重置为构建时配置
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 认知修正
|
||||||
|
|
||||||
|
| 日期 | 问题 | 根因 | 修复 | 详情 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| 2026-04-04 | 模块开关失效(飞书命令消失、HTTP 配置页不可用) | 缺少 `Kconfig.projbuild`,ESP-IDF 不识别自定义配置项 | 创建 `main/Kconfig.projbuild` 声明所有模块开关 | [详细讨论](taolun.md#讨论kconfig-缺失导致模块开关失效) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发注意事项
|
||||||
|
|
||||||
## 开发注意事项
|
## 开发注意事项
|
||||||
|
|
||||||
1. **内存限制**:ESP32-S3 内存有限,使用 `MALLOC_CAP_SPIRAM` 分配大内存
|
1. **内存限制**:ESP32-S3 内存有限,使用 `MALLOC_CAP_SPIRAM` 分配大内存
|
||||||
@@ -115,6 +193,7 @@ config_reset # 重置为构建时配置
|
|||||||
3. **日志**:使用 `ESP_LOG` 宏,标签在每个文件中定义
|
3. **日志**:使用 `ESP_LOG` 宏,标签在每个文件中定义
|
||||||
4. **错误处理**:使用 `esp_err_t` 返回码
|
4. **错误处理**:使用 `esp_err_t` 返回码
|
||||||
5. **JSON 处理**:使用 cJSON 库
|
5. **JSON 处理**:使用 cJSON 库
|
||||||
|
6. **模块开关**:新增模块或可选模块应在 `sdkconfig.defaults` 中添加配置项,并在头文件中添加 `#ifdef` stub
|
||||||
|
|
||||||
## 调试技巧
|
## 调试技巧
|
||||||
|
|
||||||
|
|||||||
28
changelog.md
28
changelog.md
@@ -1,9 +1,33 @@
|
|||||||
# 变更日志
|
# 变更日志
|
||||||
|
|
||||||
## v1.1.0(计划中)
|
## v1.2.0(计划中)
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
- 国内大模型厂商接入支持(硅基流动、火山方舟)— 计划中
|
|
||||||
|
- **编译时模块开关**
|
||||||
|
- 通过 `sdkconfig.defaults` 配置选择启用/禁用模块
|
||||||
|
- 支持的模块开关:
|
||||||
|
- `CONFIG_MIMI_CHAN_TELEGRAM` — Telegram 机器人(默认禁用)
|
||||||
|
- `CONFIG_MIMI_CHAN_FEISHU` — 飞书机器人(默认启用)
|
||||||
|
- `CONFIG_MIMI_TOOL_WEB_SEARCH` — 网络搜索工具
|
||||||
|
- `CONFIG_MIMI_TOOL_GPIO` — GPIO 控制工具(默认禁用)
|
||||||
|
- `CONFIG_MIMI_WS_SERVER` — WebSocket 网关
|
||||||
|
- `CONFIG_MIMI_WIFI_ONBOARD` — WiFi 配置门户
|
||||||
|
- `CONFIG_MIMI_OTA` — OTA 升级(默认禁用)
|
||||||
|
- 禁用模块不参与编译,节省 Flash 和 RAM
|
||||||
|
- 头文件使用 static inline stub,调用方无需修改
|
||||||
|
- CLI 命令和工具注册也支持条件编译
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
- 消除 Telegram 未配置时的 5 秒轮询警告日志
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
- 国内大模型厂商接入支持(硅基流动、火山方舟)
|
||||||
- **时区设置功能**
|
- **时区设置功能**
|
||||||
- 默认时区改为 `CST-8`(中国标准时间 UTC+8)
|
- 默认时区改为 `CST-8`(中国标准时间 UTC+8)
|
||||||
- 新增 `set_timezone` CLI 命令(支持 POSIX 格式和城市名)
|
- 新增 `set_timezone` CLI 命令(支持 POSIX 格式和城市名)
|
||||||
|
|||||||
@@ -1,36 +1,67 @@
|
|||||||
|
# MimiClaw - CMake build configuration
|
||||||
|
# This file is processed by ESP-IDF's CMake build system
|
||||||
|
|
||||||
|
# ─── Core modules (always compiled) ────────────────────────────────────────
|
||||||
|
set(core_srcs
|
||||||
|
"mimi.c"
|
||||||
|
"bus/message_bus.c"
|
||||||
|
"wifi/wifi_manager.c"
|
||||||
|
"llm/llm_proxy.c"
|
||||||
|
"llm/llm_provider.c"
|
||||||
|
"agent/agent_loop.c"
|
||||||
|
"agent/context_builder.c"
|
||||||
|
"memory/memory_store.c"
|
||||||
|
"memory/session_mgr.c"
|
||||||
|
"cli/serial_cli.c"
|
||||||
|
"proxy/http_proxy.c"
|
||||||
|
"cron/cron_service.c"
|
||||||
|
"heartbeat/heartbeat.c"
|
||||||
|
"tools/tool_registry.c"
|
||||||
|
"tools/tool_cron.c"
|
||||||
|
"tools/tool_get_time.c"
|
||||||
|
"tools/tool_set_timezone.c"
|
||||||
|
"tools/tool_files.c"
|
||||||
|
"skills/skill_loader.c"
|
||||||
|
"time_sync/time_sync.c"
|
||||||
|
"nvs_safety/nvs_safety.c"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ─── Channel modules ───────────────────────────────────────────────────────
|
||||||
|
if(CONFIG_MIMI_CHAN_TELEGRAM)
|
||||||
|
list(APPEND core_srcs "channels/telegram/telegram_bot.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CONFIG_MIMI_CHAN_FEISHU)
|
||||||
|
list(APPEND core_srcs "channels/feishu/feishu_bot.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ─── Optional modules ───────────────────────────────────────────────────────
|
||||||
|
if(CONFIG_MIMI_WS_SERVER)
|
||||||
|
list(APPEND core_srcs "gateway/ws_server.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CONFIG_MIMI_WIFI_ONBOARD)
|
||||||
|
list(APPEND core_srcs "onboard/wifi_onboard.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CONFIG_MIMI_OTA)
|
||||||
|
list(APPEND core_srcs "ota/ota_manager.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ─── Tool modules ──────────────────────────────────────────────────────────
|
||||||
|
if(CONFIG_MIMI_TOOL_WEB_SEARCH)
|
||||||
|
list(APPEND core_srcs "tools/tool_web_search.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CONFIG_MIMI_TOOL_GPIO)
|
||||||
|
list(APPEND core_srcs "tools/tool_gpio.c")
|
||||||
|
list(APPEND core_srcs "tools/gpio_policy.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ─── Register component ───────────────────────────────────────────────────
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS
|
SRCS ${core_srcs}
|
||||||
"mimi.c"
|
INCLUDE_DIRS "."
|
||||||
"bus/message_bus.c"
|
|
||||||
"wifi/wifi_manager.c"
|
|
||||||
"channels/telegram/telegram_bot.c"
|
|
||||||
"channels/feishu/feishu_bot.c"
|
|
||||||
"llm/llm_proxy.c"
|
|
||||||
"llm/llm_provider.c"
|
|
||||||
"agent/agent_loop.c"
|
|
||||||
"agent/context_builder.c"
|
|
||||||
"memory/memory_store.c"
|
|
||||||
"memory/session_mgr.c"
|
|
||||||
"gateway/ws_server.c"
|
|
||||||
"cli/serial_cli.c"
|
|
||||||
"proxy/http_proxy.c"
|
|
||||||
"cron/cron_service.c"
|
|
||||||
"heartbeat/heartbeat.c"
|
|
||||||
"tools/tool_registry.c"
|
|
||||||
"tools/tool_cron.c"
|
|
||||||
"tools/tool_web_search.c"
|
|
||||||
"tools/tool_get_time.c"
|
|
||||||
"tools/tool_set_timezone.c"
|
|
||||||
"tools/tool_files.c"
|
|
||||||
"tools/tool_gpio.c"
|
|
||||||
"tools/gpio_policy.c"
|
|
||||||
"skills/skill_loader.c"
|
|
||||||
"onboard/wifi_onboard.c"
|
|
||||||
"ota/ota_manager.c"
|
|
||||||
"time_sync/time_sync.c"
|
|
||||||
"nvs_safety/nvs_safety.c"
|
|
||||||
INCLUDE_DIRS
|
|
||||||
"."
|
|
||||||
REQUIRES
|
REQUIRES
|
||||||
nvs_flash esp_wifi esp_netif esp_http_client esp_http_server
|
nvs_flash esp_wifi esp_netif esp_http_client esp_http_server
|
||||||
esp_https_ota esp_event cjson spiffs console vfs app_update esp-tls
|
esp_https_ota esp_event cjson spiffs console vfs app_update esp-tls
|
||||||
|
|||||||
51
main/Kconfig.projbuild
Normal file
51
main/Kconfig.projbuild
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
menu "MimiClaw Configuration"
|
||||||
|
|
||||||
|
menu "Channel Modules"
|
||||||
|
config MIMI_CHAN_TELEGRAM
|
||||||
|
bool "Telegram bot integration"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Enable Telegram bot integration for MimiClaw.
|
||||||
|
|
||||||
|
config MIMI_CHAN_FEISHU
|
||||||
|
bool "Feishu (Lark) bot integration"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Enable Feishu (Lark) bot integration for MimiClaw.
|
||||||
|
endmenu
|
||||||
|
|
||||||
|
menu "Tool Modules"
|
||||||
|
config MIMI_TOOL_WEB_SEARCH
|
||||||
|
bool "Web search tool"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Enable web search tool (requires search API key).
|
||||||
|
|
||||||
|
config MIMI_TOOL_GPIO
|
||||||
|
bool "GPIO control tool"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Enable GPIO control tool for hardware control.
|
||||||
|
endmenu
|
||||||
|
|
||||||
|
menu "Optional Modules"
|
||||||
|
config MIMI_WS_SERVER
|
||||||
|
bool "WebSocket gateway"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Enable WebSocket gateway for local clients.
|
||||||
|
|
||||||
|
config MIMI_WIFI_ONBOARD
|
||||||
|
bool "WiFi onboarding portal"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Enable Captive Portal for initial WiFi setup.
|
||||||
|
|
||||||
|
config MIMI_OTA
|
||||||
|
bool "OTA firmware update"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Enable OTA firmware update (not fully implemented).
|
||||||
|
endmenu
|
||||||
|
|
||||||
|
endmenu
|
||||||
@@ -2,35 +2,25 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
/**
|
#ifdef CONFIG_MIMI_CHAN_FEISHU
|
||||||
* Initialize the Feishu bot (load credentials from NVS / build-time).
|
|
||||||
*/
|
|
||||||
esp_err_t feishu_bot_init(void);
|
esp_err_t feishu_bot_init(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the Feishu webhook HTTP server for receiving events.
|
|
||||||
* Listens on MIMI_FEISHU_WEBHOOK_PORT.
|
|
||||||
*/
|
|
||||||
esp_err_t feishu_bot_start(void);
|
esp_err_t feishu_bot_start(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a text message to a Feishu chat.
|
|
||||||
* Automatically splits messages longer than MIMI_FEISHU_MAX_MSG_LEN chars.
|
|
||||||
* @param chat_id Feishu chat ID (open_id or chat_id)
|
|
||||||
* @param text Message text
|
|
||||||
*/
|
|
||||||
esp_err_t feishu_send_message(const char *chat_id, const char *text);
|
esp_err_t feishu_send_message(const char *chat_id, const char *text);
|
||||||
|
|
||||||
/**
|
|
||||||
* Reply to a specific message in a Feishu chat.
|
|
||||||
* @param message_id The message_id to reply to
|
|
||||||
* @param text Reply text
|
|
||||||
*/
|
|
||||||
esp_err_t feishu_reply_message(const char *message_id, const char *text);
|
esp_err_t feishu_reply_message(const char *message_id, const char *text);
|
||||||
|
|
||||||
/**
|
|
||||||
* Save Feishu app credentials to NVS.
|
|
||||||
* @param app_id Feishu App ID
|
|
||||||
* @param app_secret Feishu App Secret
|
|
||||||
*/
|
|
||||||
esp_err_t feishu_set_credentials(const char *app_id, const char *app_secret);
|
esp_err_t feishu_set_credentials(const char *app_id, const char *app_secret);
|
||||||
|
#else
|
||||||
|
static inline esp_err_t feishu_bot_init(void) { return ESP_OK; }
|
||||||
|
static inline esp_err_t feishu_bot_start(void) { return ESP_OK; }
|
||||||
|
static inline esp_err_t feishu_send_message(const char *chat_id, const char *text) {
|
||||||
|
(void)chat_id; (void)text;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static inline esp_err_t feishu_reply_message(const char *message_id, const char *text) {
|
||||||
|
(void)message_id; (void)text;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static inline esp_err_t feishu_set_credentials(const char *app_id, const char *app_secret) {
|
||||||
|
(void)app_id; (void)app_secret;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -2,26 +2,20 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
/**
|
#ifdef CONFIG_MIMI_CHAN_TELEGRAM
|
||||||
* Initialize the Telegram bot.
|
|
||||||
*/
|
|
||||||
esp_err_t telegram_bot_init(void);
|
esp_err_t telegram_bot_init(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the Telegram polling task (long polling on Core 0).
|
|
||||||
*/
|
|
||||||
esp_err_t telegram_bot_start(void);
|
esp_err_t telegram_bot_start(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a text message to a Telegram chat.
|
|
||||||
* Automatically splits messages longer than 4096 chars.
|
|
||||||
* @param chat_id Telegram chat ID (numeric string)
|
|
||||||
* @param text Message text (supports Markdown)
|
|
||||||
*/
|
|
||||||
esp_err_t telegram_send_message(const char *chat_id, const char *text);
|
esp_err_t telegram_send_message(const char *chat_id, const char *text);
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the Telegram bot token to NVS.
|
|
||||||
*/
|
|
||||||
esp_err_t telegram_set_token(const char *token);
|
esp_err_t telegram_set_token(const char *token);
|
||||||
|
#else
|
||||||
|
static inline esp_err_t telegram_bot_init(void) { return ESP_OK; }
|
||||||
|
static inline esp_err_t telegram_bot_start(void) { return ESP_OK; }
|
||||||
|
static inline esp_err_t telegram_send_message(const char *chat_id, const char *text) {
|
||||||
|
(void)chat_id; (void)text;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static inline esp_err_t telegram_set_token(const char *token) {
|
||||||
|
(void)token;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1075,6 +1075,7 @@ esp_err_t serial_cli_init(void)
|
|||||||
esp_console_cmd_register(&wifi_scan_cmd);
|
esp_console_cmd_register(&wifi_scan_cmd);
|
||||||
|
|
||||||
/* set_tg_token */
|
/* set_tg_token */
|
||||||
|
#ifdef CONFIG_MIMI_CHAN_TELEGRAM
|
||||||
tg_token_args.token = arg_str1(NULL, NULL, "<token>", "Telegram bot token");
|
tg_token_args.token = arg_str1(NULL, NULL, "<token>", "Telegram bot token");
|
||||||
tg_token_args.end = arg_end(1);
|
tg_token_args.end = arg_end(1);
|
||||||
esp_console_cmd_t tg_token_cmd = {
|
esp_console_cmd_t tg_token_cmd = {
|
||||||
@@ -1084,8 +1085,13 @@ esp_err_t serial_cli_init(void)
|
|||||||
.argtable = &tg_token_args,
|
.argtable = &tg_token_args,
|
||||||
};
|
};
|
||||||
esp_console_cmd_register(&tg_token_cmd);
|
esp_console_cmd_register(&tg_token_cmd);
|
||||||
|
#else
|
||||||
|
(void)cmd_set_tg_token;
|
||||||
|
(void)tg_token_args;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* set_feishu_creds */
|
/* set_feishu_creds */
|
||||||
|
#ifdef CONFIG_MIMI_CHAN_FEISHU
|
||||||
feishu_creds_args.app_id = arg_str1(NULL, NULL, "<app_id>", "Feishu App ID");
|
feishu_creds_args.app_id = arg_str1(NULL, NULL, "<app_id>", "Feishu App ID");
|
||||||
feishu_creds_args.app_secret = arg_str1(NULL, NULL, "<app_secret>", "Feishu App Secret");
|
feishu_creds_args.app_secret = arg_str1(NULL, NULL, "<app_secret>", "Feishu App Secret");
|
||||||
feishu_creds_args.end = arg_end(2);
|
feishu_creds_args.end = arg_end(2);
|
||||||
@@ -1108,6 +1114,12 @@ esp_err_t serial_cli_init(void)
|
|||||||
.argtable = &feishu_send_args,
|
.argtable = &feishu_send_args,
|
||||||
};
|
};
|
||||||
esp_console_cmd_register(&feishu_send_cmd);
|
esp_console_cmd_register(&feishu_send_cmd);
|
||||||
|
#else
|
||||||
|
(void)cmd_set_feishu_creds;
|
||||||
|
(void)cmd_feishu_send;
|
||||||
|
(void)feishu_creds_args;
|
||||||
|
(void)feishu_send_args;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* set_api_key */
|
/* set_api_key */
|
||||||
api_key_args.key = arg_str1(NULL, NULL, "<key>", "LLM API key");
|
api_key_args.key = arg_str1(NULL, NULL, "<key>", "LLM API key");
|
||||||
@@ -1263,6 +1275,7 @@ esp_err_t serial_cli_init(void)
|
|||||||
esp_console_cmd_register(&heap_cmd);
|
esp_console_cmd_register(&heap_cmd);
|
||||||
|
|
||||||
/* set_search_key */
|
/* set_search_key */
|
||||||
|
#ifdef CONFIG_MIMI_TOOL_WEB_SEARCH
|
||||||
search_key_args.key = arg_str1(NULL, NULL, "<key>", "Brave Search API key");
|
search_key_args.key = arg_str1(NULL, NULL, "<key>", "Brave Search API key");
|
||||||
search_key_args.end = arg_end(1);
|
search_key_args.end = arg_end(1);
|
||||||
esp_console_cmd_t search_key_cmd = {
|
esp_console_cmd_t search_key_cmd = {
|
||||||
@@ -1283,6 +1296,12 @@ esp_err_t serial_cli_init(void)
|
|||||||
.argtable = &tavily_key_args,
|
.argtable = &tavily_key_args,
|
||||||
};
|
};
|
||||||
esp_console_cmd_register(&tavily_key_cmd);
|
esp_console_cmd_register(&tavily_key_cmd);
|
||||||
|
#else
|
||||||
|
(void)cmd_set_search_key;
|
||||||
|
(void)cmd_set_tavily_key;
|
||||||
|
(void)search_key_args;
|
||||||
|
(void)tavily_key_args;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* set_proxy */
|
/* set_proxy */
|
||||||
proxy_args.host = arg_str1(NULL, NULL, "<host>", "Proxy host/IP");
|
proxy_args.host = arg_str1(NULL, NULL, "<host>", "Proxy host/IP");
|
||||||
@@ -1384,6 +1403,7 @@ esp_err_t serial_cli_init(void)
|
|||||||
esp_console_cmd_register(&tool_exec_cmd);
|
esp_console_cmd_register(&tool_exec_cmd);
|
||||||
|
|
||||||
/* web_search */
|
/* web_search */
|
||||||
|
#ifdef CONFIG_MIMI_TOOL_WEB_SEARCH
|
||||||
web_search_args.query = arg_str1(NULL, NULL, "<query>", "Search query");
|
web_search_args.query = arg_str1(NULL, NULL, "<query>", "Search query");
|
||||||
web_search_args.end = arg_end(1);
|
web_search_args.end = arg_end(1);
|
||||||
esp_console_cmd_t web_search_cmd = {
|
esp_console_cmd_t web_search_cmd = {
|
||||||
@@ -1393,6 +1413,10 @@ esp_err_t serial_cli_init(void)
|
|||||||
.argtable = &web_search_args,
|
.argtable = &web_search_args,
|
||||||
};
|
};
|
||||||
esp_console_cmd_register(&web_search_cmd);
|
esp_console_cmd_register(&web_search_cmd);
|
||||||
|
#else
|
||||||
|
(void)cmd_web_search;
|
||||||
|
(void)web_search_args;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* restart */
|
/* restart */
|
||||||
esp_console_cmd_t restart_cmd = {
|
esp_console_cmd_t restart_cmd = {
|
||||||
|
|||||||
@@ -2,24 +2,15 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
/**
|
#ifdef CONFIG_MIMI_WS_SERVER
|
||||||
* Initialize and start the WebSocket server on MIMI_WS_PORT.
|
|
||||||
* Allows external clients to interact with the Agent via JSON messages.
|
|
||||||
*
|
|
||||||
* Protocol:
|
|
||||||
* Inbound: {"type":"message","content":"hello","chat_id":"ws_client1"}
|
|
||||||
* Outbound: {"type":"response","content":"Hi!","chat_id":"ws_client1"}
|
|
||||||
*/
|
|
||||||
esp_err_t ws_server_start(void);
|
esp_err_t ws_server_start(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a text message to a specific WebSocket client by chat_id.
|
|
||||||
* @param chat_id Client identifier (assigned on connection)
|
|
||||||
* @param text Message text
|
|
||||||
*/
|
|
||||||
esp_err_t ws_server_send(const char *chat_id, const char *text);
|
esp_err_t ws_server_send(const char *chat_id, const char *text);
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the WebSocket server.
|
|
||||||
*/
|
|
||||||
esp_err_t ws_server_stop(void);
|
esp_err_t ws_server_stop(void);
|
||||||
|
#else
|
||||||
|
static inline esp_err_t ws_server_start(void) { return ESP_OK; }
|
||||||
|
static inline esp_err_t ws_server_send(const char *chat_id, const char *text) {
|
||||||
|
(void)chat_id; (void)text;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static inline esp_err_t ws_server_stop(void) { return ESP_OK; }
|
||||||
|
#endif
|
||||||
|
|||||||
26
main/mimi.c
26
main/mimi.c
@@ -77,24 +77,36 @@ static void outbound_dispatch_task(void *arg)
|
|||||||
ESP_LOGI(TAG, "Dispatching response to %s:%s", msg.channel, msg.chat_id);
|
ESP_LOGI(TAG, "Dispatching response to %s:%s", msg.channel, msg.chat_id);
|
||||||
|
|
||||||
if (strcmp(msg.channel, MIMI_CHAN_TELEGRAM) == 0) {
|
if (strcmp(msg.channel, MIMI_CHAN_TELEGRAM) == 0) {
|
||||||
|
#ifdef CONFIG_MIMI_CHAN_TELEGRAM
|
||||||
esp_err_t send_err = telegram_send_message(msg.chat_id, msg.content);
|
esp_err_t send_err = telegram_send_message(msg.chat_id, msg.content);
|
||||||
if (send_err != ESP_OK) {
|
if (send_err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Telegram send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err));
|
ESP_LOGE(TAG, "Telegram send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err));
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "Telegram send success for %s (%d bytes)", msg.chat_id, (int)strlen(msg.content));
|
ESP_LOGI(TAG, "Telegram send success for %s (%d bytes)", msg.chat_id, (int)strlen(msg.content));
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
ESP_LOGW(TAG, "Telegram disabled, dropping message for %s", msg.chat_id);
|
||||||
|
#endif
|
||||||
} else if (strcmp(msg.channel, MIMI_CHAN_FEISHU) == 0) {
|
} else if (strcmp(msg.channel, MIMI_CHAN_FEISHU) == 0) {
|
||||||
|
#ifdef CONFIG_MIMI_CHAN_FEISHU
|
||||||
esp_err_t send_err = feishu_send_message(msg.chat_id, msg.content);
|
esp_err_t send_err = feishu_send_message(msg.chat_id, msg.content);
|
||||||
if (send_err != ESP_OK) {
|
if (send_err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Feishu send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err));
|
ESP_LOGE(TAG, "Feishu send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err));
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "Feishu send success for %s (%d bytes)", msg.chat_id, (int)strlen(msg.content));
|
ESP_LOGI(TAG, "Feishu send success for %s (%d bytes)", msg.chat_id, (int)strlen(msg.content));
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
ESP_LOGW(TAG, "Feishu disabled, dropping message for %s", msg.chat_id);
|
||||||
|
#endif
|
||||||
} else if (strcmp(msg.channel, MIMI_CHAN_WEBSOCKET) == 0) {
|
} else if (strcmp(msg.channel, MIMI_CHAN_WEBSOCKET) == 0) {
|
||||||
|
#ifdef CONFIG_MIMI_WS_SERVER
|
||||||
esp_err_t ws_err = ws_server_send(msg.chat_id, msg.content);
|
esp_err_t ws_err = ws_server_send(msg.chat_id, msg.content);
|
||||||
if (ws_err != ESP_OK) {
|
if (ws_err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "WS send failed for %s: %s", msg.chat_id, esp_err_to_name(ws_err));
|
ESP_LOGW(TAG, "WS send failed for %s: %s", msg.chat_id, esp_err_to_name(ws_err));
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
ESP_LOGW(TAG, "WebSocket disabled, dropping message for %s", msg.chat_id);
|
||||||
|
#endif
|
||||||
} else if (strcmp(msg.channel, MIMI_CHAN_SYSTEM) == 0) {
|
} else if (strcmp(msg.channel, MIMI_CHAN_SYSTEM) == 0) {
|
||||||
ESP_LOGI(TAG, "System message [%s]: %.128s", msg.chat_id, msg.content);
|
ESP_LOGI(TAG, "System message [%s]: %.128s", msg.chat_id, msg.content);
|
||||||
} else {
|
} else {
|
||||||
@@ -164,13 +176,19 @@ void app_main(void)
|
|||||||
|
|
||||||
if (!wifi_ok) {
|
if (!wifi_ok) {
|
||||||
ESP_LOGW(TAG, "Entering WiFi onboarding mode...");
|
ESP_LOGW(TAG, "Entering WiFi onboarding mode...");
|
||||||
|
#ifdef CONFIG_MIMI_WIFI_ONBOARD
|
||||||
wifi_onboard_start(WIFI_ONBOARD_MODE_CAPTIVE); /* blocks, restarts on success */
|
wifi_onboard_start(WIFI_ONBOARD_MODE_CAPTIVE); /* blocks, restarts on success */
|
||||||
return; /* unreachable */
|
#else
|
||||||
|
ESP_LOGE(TAG, "WiFi onboarding disabled. Configure WiFi via CLI or flash new firmware.");
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_MIMI_WIFI_ONBOARD
|
||||||
if (wifi_onboard_start(WIFI_ONBOARD_MODE_ADMIN) != ESP_OK) {
|
if (wifi_onboard_start(WIFI_ONBOARD_MODE_ADMIN) != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "Local admin portal unavailable; continuing without config hotspot");
|
ESP_LOGW(TAG, "Local admin portal unavailable; continuing without config hotspot");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
{
|
{
|
||||||
/* Outbound dispatch task should start first to avoid dropping early replies. */
|
/* Outbound dispatch task should start first to avoid dropping early replies. */
|
||||||
@@ -182,11 +200,17 @@ void app_main(void)
|
|||||||
|
|
||||||
/* Start network-dependent services */
|
/* Start network-dependent services */
|
||||||
ESP_ERROR_CHECK(agent_loop_start());
|
ESP_ERROR_CHECK(agent_loop_start());
|
||||||
|
#ifdef CONFIG_MIMI_CHAN_TELEGRAM
|
||||||
ESP_ERROR_CHECK(telegram_bot_start());
|
ESP_ERROR_CHECK(telegram_bot_start());
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_MIMI_CHAN_FEISHU
|
||||||
ESP_ERROR_CHECK(feishu_bot_start());
|
ESP_ERROR_CHECK(feishu_bot_start());
|
||||||
|
#endif
|
||||||
cron_service_start();
|
cron_service_start();
|
||||||
heartbeat_start();
|
heartbeat_start();
|
||||||
|
#ifdef CONFIG_MIMI_WS_SERVER
|
||||||
ESP_ERROR_CHECK(ws_server_start());
|
ESP_ERROR_CHECK(ws_server_start());
|
||||||
|
#endif
|
||||||
|
|
||||||
ESP_LOGI(TAG, "All services started!");
|
ESP_LOGI(TAG, "All services started!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,21 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_MIMI_WIFI_ONBOARD
|
||||||
typedef enum {
|
typedef enum {
|
||||||
WIFI_ONBOARD_MODE_CAPTIVE = 0,
|
WIFI_ONBOARD_MODE_CAPTIVE = 0,
|
||||||
WIFI_ONBOARD_MODE_ADMIN,
|
WIFI_ONBOARD_MODE_ADMIN,
|
||||||
} wifi_onboard_mode_t;
|
} wifi_onboard_mode_t;
|
||||||
|
|
||||||
/**
|
|
||||||
* Start WiFi onboarding/configuration portal.
|
|
||||||
* CAPTIVE mode opens DNS hijack + config page and blocks forever.
|
|
||||||
* ADMIN mode keeps a local config hotspot alive without captive redirects.
|
|
||||||
*/
|
|
||||||
esp_err_t wifi_onboard_start(wifi_onboard_mode_t mode);
|
esp_err_t wifi_onboard_start(wifi_onboard_mode_t mode);
|
||||||
|
#else
|
||||||
|
typedef enum {
|
||||||
|
WIFI_ONBOARD_MODE_CAPTIVE = 0,
|
||||||
|
WIFI_ONBOARD_MODE_ADMIN,
|
||||||
|
} wifi_onboard_mode_t;
|
||||||
|
|
||||||
|
static inline esp_err_t wifi_onboard_start(wifi_onboard_mode_t mode) {
|
||||||
|
(void)mode;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
/**
|
#ifdef CONFIG_MIMI_OTA
|
||||||
* Perform OTA firmware update from a URL.
|
|
||||||
* Downloads the firmware binary and applies it. Reboots on success.
|
|
||||||
*
|
|
||||||
* @param url HTTPS URL to the firmware .bin file
|
|
||||||
* @return ESP_OK on success (device will reboot), error code otherwise
|
|
||||||
*/
|
|
||||||
esp_err_t ota_update_from_url(const char *url);
|
esp_err_t ota_update_from_url(const char *url);
|
||||||
|
#else
|
||||||
|
static inline esp_err_t ota_update_from_url(const char *url) {
|
||||||
|
(void)url;
|
||||||
|
return ESP_ERR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -2,26 +2,34 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
/**
|
#ifdef CONFIG_MIMI_TOOL_GPIO
|
||||||
* Initialize GPIO tool — configure allowed pins and directions.
|
|
||||||
*/
|
|
||||||
esp_err_t tool_gpio_init(void);
|
esp_err_t tool_gpio_init(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a GPIO pin HIGH or LOW.
|
|
||||||
* Input JSON: {"pin": <int>, "state": <0|1>}
|
|
||||||
*/
|
|
||||||
esp_err_t tool_gpio_write_execute(const char *input_json, char *output, size_t output_size);
|
esp_err_t tool_gpio_write_execute(const char *input_json, char *output, size_t output_size);
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a single GPIO pin state.
|
|
||||||
* Input JSON: {"pin": <int>}
|
|
||||||
*/
|
|
||||||
esp_err_t tool_gpio_read_execute(const char *input_json, char *output, size_t output_size);
|
esp_err_t tool_gpio_read_execute(const char *input_json, char *output, size_t output_size);
|
||||||
|
|
||||||
/**
|
|
||||||
* Read all allowed GPIO pin states at once.
|
|
||||||
* Input JSON: {} (no parameters)
|
|
||||||
*/
|
|
||||||
esp_err_t tool_gpio_read_all_execute(const char *input_json, char *output, size_t output_size);
|
esp_err_t tool_gpio_read_all_execute(const char *input_json, char *output, size_t output_size);
|
||||||
|
#else
|
||||||
|
static inline esp_err_t tool_gpio_init(void) { return ESP_OK; }
|
||||||
|
static inline esp_err_t tool_gpio_write_execute(const char *input_json, char *output, size_t output_size) {
|
||||||
|
(void)input_json; (void)output; (void)output_size;
|
||||||
|
if (output && output_size > 0) {
|
||||||
|
snprintf(output, output_size, "Error: GPIO tool is disabled in this build.");
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static inline esp_err_t tool_gpio_read_execute(const char *input_json, char *output, size_t output_size) {
|
||||||
|
(void)input_json; (void)output; (void)output_size;
|
||||||
|
if (output && output_size > 0) {
|
||||||
|
snprintf(output, output_size, "Error: GPIO tool is disabled in this build.");
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static inline esp_err_t tool_gpio_read_all_execute(const char *input_json, char *output, size_t output_size) {
|
||||||
|
(void)input_json; (void)output; (void)output_size;
|
||||||
|
if (output && output_size > 0) {
|
||||||
|
snprintf(output, output_size, "Error: GPIO tool is disabled in this build.");
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ esp_err_t tool_registry_init(void)
|
|||||||
s_tool_count = 0;
|
s_tool_count = 0;
|
||||||
|
|
||||||
/* Register web_search */
|
/* Register web_search */
|
||||||
|
#ifdef CONFIG_MIMI_TOOL_WEB_SEARCH
|
||||||
tool_web_search_init();
|
tool_web_search_init();
|
||||||
|
|
||||||
mimi_tool_t ws = {
|
mimi_tool_t ws = {
|
||||||
@@ -71,6 +72,7 @@ esp_err_t tool_registry_init(void)
|
|||||||
.execute = tool_web_search_execute,
|
.execute = tool_web_search_execute,
|
||||||
};
|
};
|
||||||
register_tool(&ws);
|
register_tool(&ws);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Register get_current_time */
|
/* Register get_current_time */
|
||||||
mimi_tool_t gt = {
|
mimi_tool_t gt = {
|
||||||
@@ -192,6 +194,7 @@ esp_err_t tool_registry_init(void)
|
|||||||
register_tool(&cr);
|
register_tool(&cr);
|
||||||
|
|
||||||
/* Register GPIO tools */
|
/* Register GPIO tools */
|
||||||
|
#ifdef CONFIG_MIMI_TOOL_GPIO
|
||||||
tool_gpio_init();
|
tool_gpio_init();
|
||||||
|
|
||||||
mimi_tool_t gw = {
|
mimi_tool_t gw = {
|
||||||
@@ -227,6 +230,7 @@ esp_err_t tool_registry_init(void)
|
|||||||
.execute = tool_gpio_read_all_execute,
|
.execute = tool_gpio_read_all_execute,
|
||||||
};
|
};
|
||||||
register_tool(&ga);
|
register_tool(&ga);
|
||||||
|
#endif
|
||||||
|
|
||||||
build_tools_json();
|
build_tools_json();
|
||||||
|
|
||||||
|
|||||||
@@ -2,28 +2,28 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
/**
|
#ifdef CONFIG_MIMI_TOOL_WEB_SEARCH
|
||||||
* Initialize web search tool.
|
|
||||||
*/
|
|
||||||
esp_err_t tool_web_search_init(void);
|
esp_err_t tool_web_search_init(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a web search.
|
|
||||||
*
|
|
||||||
* @param input_json JSON string with "query" field
|
|
||||||
* @param output Output buffer for formatted search results
|
|
||||||
* @param output_size Size of output buffer
|
|
||||||
* @return ESP_OK on success
|
|
||||||
*/
|
|
||||||
esp_err_t tool_web_search_execute(const char *input_json, char *output, size_t output_size);
|
esp_err_t tool_web_search_execute(const char *input_json, char *output, size_t output_size);
|
||||||
|
|
||||||
/**
|
|
||||||
* Save Brave Search API key to NVS.
|
|
||||||
*/
|
|
||||||
esp_err_t tool_web_search_set_key(const char *api_key);
|
esp_err_t tool_web_search_set_key(const char *api_key);
|
||||||
|
|
||||||
/**
|
|
||||||
* Save Tavily API key to NVS.
|
|
||||||
*/
|
|
||||||
esp_err_t tool_web_search_set_tavily_key(const char *api_key);
|
esp_err_t tool_web_search_set_tavily_key(const char *api_key);
|
||||||
|
#else
|
||||||
|
static inline esp_err_t tool_web_search_init(void) { return ESP_OK; }
|
||||||
|
static inline esp_err_t tool_web_search_execute(const char *input_json, char *output, size_t output_size) {
|
||||||
|
(void)input_json; (void)output; (void)output_size;
|
||||||
|
if (output && output_size > 0) {
|
||||||
|
snprintf(output, output_size, "Error: Web search tool is disabled in this build.");
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static inline esp_err_t tool_web_search_set_key(const char *api_key) {
|
||||||
|
(void)api_key;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static inline esp_err_t tool_web_search_set_tavily_key(const char *api_key) {
|
||||||
|
(void)api_key;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -7,6 +7,26 @@ CONFIG_LWIP_LOCAL_HOSTNAME="mimiclaw"
|
|||||||
# SPIFFS: increase max filename length (default 32 is too short for session files)
|
# SPIFFS: increase max filename length (default 32 is too short for session files)
|
||||||
CONFIG_SPIFFS_OBJ_NAME_LEN=64
|
CONFIG_SPIFFS_OBJ_NAME_LEN=64
|
||||||
|
|
||||||
|
# ─── Channel Modules ──────────────────────────────────────────────
|
||||||
|
# Telegram bot integration (disable if using Feishu only)
|
||||||
|
CONFIG_MIMI_CHAN_TELEGRAM=n
|
||||||
|
# Feishu (Lark) bot integration (default: enabled)
|
||||||
|
CONFIG_MIMI_CHAN_FEISHU=y
|
||||||
|
|
||||||
|
# ─── Tool Modules ─────────────────────────────────────────────────
|
||||||
|
# Web search tool (requires search API key)
|
||||||
|
CONFIG_MIMI_TOOL_WEB_SEARCH=y
|
||||||
|
# GPIO control tool (for hardware control)
|
||||||
|
CONFIG_MIMI_TOOL_GPIO=n
|
||||||
|
|
||||||
|
# ─── Optional Modules ──────────────────────────────────────────────
|
||||||
|
# WebSocket gateway (for local clients)
|
||||||
|
CONFIG_MIMI_WS_SERVER=y
|
||||||
|
# WiFi onboarding portal (Captive Portal for initial setup)
|
||||||
|
CONFIG_MIMI_WIFI_ONBOARD=y
|
||||||
|
# OTA firmware update (not fully implemented)
|
||||||
|
CONFIG_MIMI_OTA=n
|
||||||
|
|
||||||
# Brownout Detection: protect against power drops during Flash/NVS writes
|
# Brownout Detection: protect against power drops during Flash/NVS writes
|
||||||
CONFIG_ESP32S3_BROWNOUT_DET=y
|
CONFIG_ESP32S3_BROWNOUT_DET=y
|
||||||
CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y
|
CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y
|
||||||
@@ -16,3 +36,7 @@ CONFIG_ESP32S3_BROWNOUT_DET_LVL=7
|
|||||||
# NOTE: CONFIG_LWIP_SNTP_MAX_SERVERS may be deprecated in ESP-IDF v6.0
|
# NOTE: CONFIG_LWIP_SNTP_MAX_SERVERS may be deprecated in ESP-IDF v6.0
|
||||||
# If compilation fails, comment out this line or use the new SNTP component config
|
# If compilation fails, comment out this line or use the new SNTP component config
|
||||||
CONFIG_LWIP_SNTP_MAX_SERVERS=4
|
CONFIG_LWIP_SNTP_MAX_SERVERS=4
|
||||||
|
|
||||||
|
# Partition table: use custom partition table with SPIFFS and OTA
|
||||||
|
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||||
|
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||||
|
|||||||
159
taolun.md
159
taolun.md
@@ -371,3 +371,162 @@ timezone_show # 显示当前时区配置和本地时间
|
|||||||
### 支持的时区格式
|
### 支持的时区格式
|
||||||
- POSIX: `CST-8`, `JST-9`, `EST5EDT,M3.2.0,M11.1.0`, `UTC0`
|
- POSIX: `CST-8`, `JST-9`, `EST5EDT,M3.2.0,M11.1.0`, `UTC0`
|
||||||
- 城市名: Asia/Shanghai, Asia/Tokyo, America/New_York 等 18 个预设
|
- 城市名: Asia/Shanghai, Asia/Tokyo, America/New_York 等 18 个预设
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 讨论:编译时模块开关(模块化配置)
|
||||||
|
|
||||||
|
**日期**:2026-04-03
|
||||||
|
**分支**:`feature/module-config`
|
||||||
|
**目标**:通过编译时配置选择性禁用不需要的模块,减少固件体积,消除未配置模块的警告日志
|
||||||
|
|
||||||
|
### 问题背景
|
||||||
|
|
||||||
|
1. **Task Watchdog 超时**:ESP32-S3 运行一天后触发看门狗超时,怀疑是设计问题
|
||||||
|
2. **Telegram 警告日志**:用户使用飞书但未配置 Telegram,控制台每 5 秒打印 "No bot token configured"
|
||||||
|
3. **代码冗余**:Telegram、OpenAI 接口等未使用的模块仍然编译进固件
|
||||||
|
|
||||||
|
### 用户需求
|
||||||
|
|
||||||
|
用户希望:
|
||||||
|
1. 通过编译选项禁用不需要的模块(如 Telegram)
|
||||||
|
2. 从源码层面直接过滤不用的组件,减少代码体积
|
||||||
|
3. 禁用模块后不触发警告日志
|
||||||
|
|
||||||
|
### 实现方案
|
||||||
|
|
||||||
|
#### 方案选择:编译时条件编译(方案 02)
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- 直接减少 Flash 占用
|
||||||
|
- 零 RAM 占用,不创建任务
|
||||||
|
- 从源头消除警告日志
|
||||||
|
- 实现简单
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- 切换模块需要重新编译
|
||||||
|
|
||||||
|
#### 技术实现
|
||||||
|
|
||||||
|
##### 1. 配置文件:`sdkconfig.defaults`
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Channel Modules
|
||||||
|
CONFIG_MIMI_CHAN_TELEGRAM=n // 默认禁用
|
||||||
|
CONFIG_MIMI_CHAN_FEISHU=y // 默认启用
|
||||||
|
|
||||||
|
// Tool Modules
|
||||||
|
CONFIG_MIMI_TOOL_WEB_SEARCH=y
|
||||||
|
CONFIG_MIMI_TOOL_GPIO=n
|
||||||
|
|
||||||
|
// Optional Modules
|
||||||
|
CONFIG_MIMI_WS_SERVER=y
|
||||||
|
CONFIG_MIMI_WIFI_ONBOARD=y
|
||||||
|
CONFIG_MIMI_OTA=n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 2. CMakeLists.txt:条件编译源文件
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
if(CONFIG_MIMI_CHAN_TELEGRAM)
|
||||||
|
list(APPEND srcs "channels/telegram/telegram_bot.c")
|
||||||
|
endif()
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 3. 头文件 stub:调用方无感知
|
||||||
|
|
||||||
|
```c
|
||||||
|
// telegram_bot.h
|
||||||
|
#ifdef CONFIG_MIMI_CHAN_TELEGRAM
|
||||||
|
esp_err_t telegram_bot_init(void);
|
||||||
|
esp_err_t telegram_bot_start(void);
|
||||||
|
#else
|
||||||
|
static inline esp_err_t telegram_bot_init(void) { return ESP_OK; }
|
||||||
|
static inline esp_err_t telegram_bot_start(void) { return ESP_OK; }
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
### 修改文件清单
|
||||||
|
|
||||||
|
| 文件 | 修改内容 |
|
||||||
|
|------|----------|
|
||||||
|
| `sdkconfig.defaults` | 添加模块开关配置 |
|
||||||
|
| `main/CMakeLists.txt` | 条件编译源文件 |
|
||||||
|
| `main/channels/telegram/telegram_bot.h` | 添加 stub |
|
||||||
|
| `main/channels/feishu/feishu_bot.h` | 添加 stub |
|
||||||
|
| `main/gateway/ws_server.h` | 添加 stub |
|
||||||
|
| `main/onboard/wifi_onboard.h` | 添加 stub |
|
||||||
|
| `main/ota/ota_manager.h` | 添加 stub |
|
||||||
|
| `main/tools/tool_web_search.h` | 添加 stub |
|
||||||
|
| `main/tools/tool_gpio.h` | 添加 stub |
|
||||||
|
| `main/tools/tool_registry.c` | 条件注册工具 |
|
||||||
|
| `main/cli/serial_cli.c` | 条件注册 CLI 命令 |
|
||||||
|
| `main/mimi.c` | 条件调用模块 |
|
||||||
|
| `AGENTS.md` | 更新文档 |
|
||||||
|
|
||||||
|
### 模块依赖关系
|
||||||
|
|
||||||
|
- **Channel 模块**(telegram、feishu):
|
||||||
|
- 被 `mimi.c` 调用(init/start)
|
||||||
|
- 被 `outbound_dispatch_task` 调用(send)
|
||||||
|
- CLI 命令也需要条件编译
|
||||||
|
|
||||||
|
- **Tool 模块**(web_search、gpio):
|
||||||
|
- 被 `tool_registry.c` 调用(注册 + 执行)
|
||||||
|
- CLI 命令也需要条件编译
|
||||||
|
|
||||||
|
- **可选模块**(ws_server、wifi_onboard、ota):
|
||||||
|
- 被 `mimi.c` 调用
|
||||||
|
- `wifi_onboard` 禁用时需特殊处理(不能进入 captive portal)
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
|
||||||
|
1. **OTA 模块**:目前未初始化,但已编译
|
||||||
|
2. **工具描述中的渠道提示**:cron_add 工具描述中提到 telegram,可保留(只是描述,不影响功能)
|
||||||
|
3. **get_time 工具**:使用 telegram.org 代理获取时间(技术原因,不属于 telegram_bot 模块)
|
||||||
|
|
||||||
|
### 预期效果
|
||||||
|
|
||||||
|
| 禁用项 | 效果 |
|
||||||
|
|--------|------|
|
||||||
|
| Telegram | 不编译 telegram_bot.c,无 5 秒警告,节省约 15KB+ Flash |
|
||||||
|
| GPIO | 不编译 tool_gpio.c,无 gpio_* 工具,节省约 5KB Flash |
|
||||||
|
| WebSocket | 不编译 ws_server.c,节省约 10KB Flash |
|
||||||
|
| WiFi Onboard | 不能进入 captive portal 模式,需其他方式配置 WiFi |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 讨论:Kconfig 缺失导致模块开关失效
|
||||||
|
|
||||||
|
**日期**:2026-04-04
|
||||||
|
**问题**:编译后飞书命令消失、HTTP 配置页面不可用
|
||||||
|
|
||||||
|
### 现象
|
||||||
|
|
||||||
|
在 `sdkconfig.defaults` 中正确配置了 `CONFIG_MIMI_CHAN_FEISHU=y` 和 `CONFIG_MIMI_WIFI_ONBOARD=y`,执行 `idf.py fullclean && idf.py build` 后:
|
||||||
|
- 烧录后控制台没有飞书相关命令(`set_feishu_creds` 等)。
|
||||||
|
- 设备没有启动 HTTP 配置服务(`192.168.4.1` 无法访问)。
|
||||||
|
- 检查生成的 `sdkconfig` 文件,发现**完全没有**这些自定义配置项。
|
||||||
|
|
||||||
|
### 根因
|
||||||
|
|
||||||
|
ESP-IDF 的构建系统在生成 `sdkconfig` 时,**只会保留有 Kconfig 声明的配置项**。
|
||||||
|
|
||||||
|
- `sdkconfig.defaults` 仅用于提供默认值。
|
||||||
|
- 如果项目缺少 `Kconfig` 或 `Kconfig.projbuild` 文件来声明这些选项,ESP-IDF 会认为它们是无效配置并直接丢弃。
|
||||||
|
- 之前的版本可能 `sdkconfig` 是手动维护的或缓存未清理,但 `fullclean` 后重新生成时就会丢失这些"无名"配置。
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
|
||||||
|
创建 `main/Kconfig.projbuild` 文件,声明所有自定义模块开关。
|
||||||
|
|
||||||
|
### 认知修正
|
||||||
|
|
||||||
|
**ESP-IDF 配置系统工作流**:
|
||||||
|
|
||||||
|
1. `Kconfig.projbuild`:**声明**配置项(告诉系统"这是什么")。
|
||||||
|
2. `sdkconfig.defaults`:提供**默认值**(告诉系统"默认选什么")。
|
||||||
|
3. `sdkconfig`:构建系统根据前两者**自动生成**(实际编译用的配置)。
|
||||||
|
4. `CMakeLists.txt`:读取 `sdkconfig` 中的值**决定编译哪些文件**。
|
||||||
|
|
||||||
|
**结论**:新增模块开关时,**必须**创建 Kconfig 声明,否则 `sdkconfig.defaults` 无效。
|
||||||
|
|||||||
Reference in New Issue
Block a user