From 6983a1f1ba292148fd81d13babae7ebee12ad913 Mon Sep 17 00:00:00 2001 From: titor Date: Fri, 3 Apr 2026 20:15:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E6=97=B6=E6=A8=A1=E5=9D=97=E5=BC=80=E5=85=B3=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过 sdkconfig.defaults 选择性启用/禁用模块,减少固件体积: 新增模块开关: - CONFIG_MIMI_CHAN_TELEGRAM (默认 n) - CONFIG_MIMI_CHAN_FEISHU (默认 y) - CONFIG_MIMI_TOOL_WEB_SEARCH (默认 y) - CONFIG_MIMI_TOOL_GPIO (默认 n) - CONFIG_MIMI_WS_SERVER (默认 y) - CONFIG_MIMI_WIFI_ONBOARD (默认 y) - CONFIG_MIMI_OTA (默认 n) 技术实现: - CMakeLists.txt 条件编译源文件 - 头文件使用 static inline stub - CLI 命令和工具注册也支持条件编译 消除 Telegram 未配置时的 5 秒轮询警告日志 --- AGENTS.md | 76 ++++++++++++++-- changelog.md | 28 +++++- main/CMakeLists.txt | 91 ++++++++++++------- main/channels/feishu/feishu_bot.h | 44 ++++------ main/channels/telegram/telegram_bot.h | 32 +++---- main/cli/serial_cli.c | 24 +++++ main/gateway/ws_server.h | 27 ++---- main/mimi.c | 26 +++++- main/onboard/wifi_onboard.h | 17 ++-- main/ota/ota_manager.h | 14 +-- main/tools/tool_gpio.h | 44 ++++++---- main/tools/tool_registry.c | 4 + main/tools/tool_web_search.h | 40 ++++----- sdkconfig.defaults | 20 +++++ taolun.md | 122 ++++++++++++++++++++++++++ 15 files changed, 455 insertions(+), 154 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c02fe17..57e7743 100644 --- a/AGENTS.md +++ b/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 # 构建上下文(系统提示、记忆等) │ ├── llm/ # LLM 代理 │ │ ├── llm_proxy.c # 处理与 LLM API 的通信 -│ │ └── llm_proxy.h # LLM 代理的头文件 +│ │ └── llm_provider.c # 多提供商支持(Anthropic/OpenAI/硅基流动/火山引擎) │ ├── cli/ # 串口命令行界面 │ │ └── serial_cli.c # 处理运行时配置命令 │ ├── channels/ # 输入/输出通道 -│ │ ├── telegram/ # Telegram 机器人集成 -│ │ └── feishu/ # 飞书机器人集成 +│ │ ├── telegram/ # Telegram 机器人集成(可选) +│ │ └── feishu/ # 飞书机器人集成(可选) │ ├── tools/ # LLM 可调用的工具 │ ├── memory/ # 记忆和会话管理 -│ ├── proxy/ # HTTP 代理支持 +│ ├── proxy/ # HTTP/SOCKS5 代理支持 │ ├── cron/ # 定时任务调度 │ ├── heartbeat/ # 心跳服务 │ ├── gateway/ # WebSocket 网关 -│ ├── onboard/ # WiFi 配置门户 +│ ├── onboard/ # WiFi 配置门户(Captive Portal) │ ├── skills/ # 技能加载器 +│ ├── bus/ # 消息总线(连接通道和代理循环) │ ├── mimi_config.h # 全局配置定义 │ ├── mimi_secrets.h # 构建时密钥(需用户创建) │ └── mimi_secrets.h.example # 密钥模板 @@ -72,6 +73,62 @@ mimiclaw/ - 工具注册在 `tool_registry.c` - 支持的工具:`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. 在 `sdkconfig.defaults` 中添加配置项: + ``` + CONFIG_MIMI_MODULE_EXAMPLE=y + ``` + +2. 在 `main/CMakeLists.txt` 中条件编译: + ```cmake + if(CONFIG_MIMI_MODULE_EXAMPLE) + list(APPEND srcs "modules/example/example.c") + endif() + ``` + +3. 在模块头文件中添加 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 +142,9 @@ idf.py set-target esp32s3 # 配置(首次需要) 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 @@ -110,11 +169,14 @@ config_reset # 重置为构建时配置 ## 开发注意事项 +## 开发注意事项 + 1. **内存限制**:ESP32-S3 内存有限,使用 `MALLOC_CAP_SPIRAM` 分配大内存 2. **堆栈大小**:任务堆栈在 `mimi_config.h` 中定义 3. **日志**:使用 `ESP_LOG` 宏,标签在每个文件中定义 4. **错误处理**:使用 `esp_err_t` 返回码 5. **JSON 处理**:使用 cJSON 库 +6. **模块开关**:新增模块或可选模块应在 `sdkconfig.defaults` 中添加配置项,并在头文件中添加 `#ifdef` stub ## 调试技巧 diff --git a/changelog.md b/changelog.md index e6ff51c..c8c04a0 100644 --- a/changelog.md +++ b/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) - 新增 `set_timezone` CLI 命令(支持 POSIX 格式和城市名) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 1e52783..3f556a6 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,34 +1,65 @@ +# 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" +) + +# ─── 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( - SRCS - "mimi.c" - "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" - INCLUDE_DIRS - "." + SRCS ${core_srcs} + INCLUDE_DIRS "." REQUIRES 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 diff --git a/main/channels/feishu/feishu_bot.h b/main/channels/feishu/feishu_bot.h index aa1b672..799c6fa 100644 --- a/main/channels/feishu/feishu_bot.h +++ b/main/channels/feishu/feishu_bot.h @@ -2,35 +2,25 @@ #include "esp_err.h" -/** - * Initialize the Feishu bot (load credentials from NVS / build-time). - */ +#ifdef CONFIG_MIMI_CHAN_FEISHU 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); - -/** - * 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); - -/** - * 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); - -/** - * 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); +#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 diff --git a/main/channels/telegram/telegram_bot.h b/main/channels/telegram/telegram_bot.h index e0ba36e..d147f99 100644 --- a/main/channels/telegram/telegram_bot.h +++ b/main/channels/telegram/telegram_bot.h @@ -2,26 +2,20 @@ #include "esp_err.h" -/** - * Initialize the Telegram bot. - */ +#ifdef CONFIG_MIMI_CHAN_TELEGRAM esp_err_t telegram_bot_init(void); - -/** - * Start the Telegram polling task (long polling on Core 0). - */ 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); - -/** - * Save the Telegram bot token to NVS. - */ 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 diff --git a/main/cli/serial_cli.c b/main/cli/serial_cli.c index 0a62b24..56d678b 100644 --- a/main/cli/serial_cli.c +++ b/main/cli/serial_cli.c @@ -1012,6 +1012,7 @@ esp_err_t serial_cli_init(void) esp_console_cmd_register(&wifi_scan_cmd); /* set_tg_token */ +#ifdef CONFIG_MIMI_CHAN_TELEGRAM tg_token_args.token = arg_str1(NULL, NULL, "", "Telegram bot token"); tg_token_args.end = arg_end(1); esp_console_cmd_t tg_token_cmd = { @@ -1021,8 +1022,13 @@ esp_err_t serial_cli_init(void) .argtable = &tg_token_args, }; esp_console_cmd_register(&tg_token_cmd); +#else + (void)cmd_set_tg_token; + (void)tg_token_args; +#endif /* set_feishu_creds */ +#ifdef CONFIG_MIMI_CHAN_FEISHU feishu_creds_args.app_id = arg_str1(NULL, NULL, "", "Feishu App ID"); feishu_creds_args.app_secret = arg_str1(NULL, NULL, "", "Feishu App Secret"); feishu_creds_args.end = arg_end(2); @@ -1045,6 +1051,12 @@ esp_err_t serial_cli_init(void) .argtable = &feishu_send_args, }; 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 */ api_key_args.key = arg_str1(NULL, NULL, "", "LLM API key"); @@ -1200,6 +1212,7 @@ esp_err_t serial_cli_init(void) esp_console_cmd_register(&heap_cmd); /* set_search_key */ +#ifdef CONFIG_MIMI_TOOL_WEB_SEARCH search_key_args.key = arg_str1(NULL, NULL, "", "Brave Search API key"); search_key_args.end = arg_end(1); esp_console_cmd_t search_key_cmd = { @@ -1220,6 +1233,12 @@ esp_err_t serial_cli_init(void) .argtable = &tavily_key_args, }; 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 */ proxy_args.host = arg_str1(NULL, NULL, "", "Proxy host/IP"); @@ -1302,6 +1321,7 @@ esp_err_t serial_cli_init(void) esp_console_cmd_register(&tool_exec_cmd); /* web_search */ +#ifdef CONFIG_MIMI_TOOL_WEB_SEARCH web_search_args.query = arg_str1(NULL, NULL, "", "Search query"); web_search_args.end = arg_end(1); esp_console_cmd_t web_search_cmd = { @@ -1311,6 +1331,10 @@ esp_err_t serial_cli_init(void) .argtable = &web_search_args, }; esp_console_cmd_register(&web_search_cmd); +#else + (void)cmd_web_search; + (void)web_search_args; +#endif /* restart */ esp_console_cmd_t restart_cmd = { diff --git a/main/gateway/ws_server.h b/main/gateway/ws_server.h index 22d5c9f..1b1e182 100644 --- a/main/gateway/ws_server.h +++ b/main/gateway/ws_server.h @@ -2,24 +2,15 @@ #include "esp_err.h" -/** - * 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"} - */ +#ifdef CONFIG_MIMI_WS_SERVER 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); - -/** - * Stop the WebSocket server. - */ 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 diff --git a/main/mimi.c b/main/mimi.c index eb589b2..ec140bb 100644 --- a/main/mimi.c +++ b/main/mimi.c @@ -75,24 +75,36 @@ static void outbound_dispatch_task(void *arg) ESP_LOGI(TAG, "Dispatching response to %s:%s", msg.channel, msg.chat_id); 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); if (send_err != ESP_OK) { ESP_LOGE(TAG, "Telegram send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err)); } else { 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) { +#ifdef CONFIG_MIMI_CHAN_FEISHU esp_err_t send_err = feishu_send_message(msg.chat_id, msg.content); if (send_err != ESP_OK) { ESP_LOGE(TAG, "Feishu send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err)); } else { 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) { +#ifdef CONFIG_MIMI_WS_SERVER esp_err_t ws_err = ws_server_send(msg.chat_id, msg.content); if (ws_err != ESP_OK) { 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) { ESP_LOGI(TAG, "System message [%s]: %.128s", msg.chat_id, msg.content); } else { @@ -160,13 +172,19 @@ void app_main(void) if (!wifi_ok) { ESP_LOGW(TAG, "Entering WiFi onboarding mode..."); +#ifdef CONFIG_MIMI_WIFI_ONBOARD 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) { ESP_LOGW(TAG, "Local admin portal unavailable; continuing without config hotspot"); } +#endif { /* Outbound dispatch task should start first to avoid dropping early replies. */ @@ -178,11 +196,17 @@ void app_main(void) /* Start network-dependent services */ ESP_ERROR_CHECK(agent_loop_start()); +#ifdef CONFIG_MIMI_CHAN_TELEGRAM ESP_ERROR_CHECK(telegram_bot_start()); +#endif +#ifdef CONFIG_MIMI_CHAN_FEISHU ESP_ERROR_CHECK(feishu_bot_start()); +#endif cron_service_start(); heartbeat_start(); +#ifdef CONFIG_MIMI_WS_SERVER ESP_ERROR_CHECK(ws_server_start()); +#endif ESP_LOGI(TAG, "All services started!"); } diff --git a/main/onboard/wifi_onboard.h b/main/onboard/wifi_onboard.h index a6e0d66..0ae77a9 100644 --- a/main/onboard/wifi_onboard.h +++ b/main/onboard/wifi_onboard.h @@ -2,14 +2,21 @@ #include "esp_err.h" +#ifdef CONFIG_MIMI_WIFI_ONBOARD typedef enum { WIFI_ONBOARD_MODE_CAPTIVE = 0, WIFI_ONBOARD_MODE_ADMIN, } 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); +#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 diff --git a/main/ota/ota_manager.h b/main/ota/ota_manager.h index 28dd6ff..4fde32d 100644 --- a/main/ota/ota_manager.h +++ b/main/ota/ota_manager.h @@ -2,11 +2,11 @@ #include "esp_err.h" -/** - * 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 - */ +#ifdef CONFIG_MIMI_OTA 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 diff --git a/main/tools/tool_gpio.h b/main/tools/tool_gpio.h index b435b15..20ba9dd 100644 --- a/main/tools/tool_gpio.h +++ b/main/tools/tool_gpio.h @@ -2,26 +2,34 @@ #include "esp_err.h" #include +#include -/** - * Initialize GPIO tool — configure allowed pins and directions. - */ +#ifdef CONFIG_MIMI_TOOL_GPIO esp_err_t tool_gpio_init(void); - -/** - * Write a GPIO pin HIGH or LOW. - * Input JSON: {"pin": , "state": <0|1>} - */ 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": } - */ 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); +#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 diff --git a/main/tools/tool_registry.c b/main/tools/tool_registry.c index e57dcbe..6275ad8 100644 --- a/main/tools/tool_registry.c +++ b/main/tools/tool_registry.c @@ -59,6 +59,7 @@ esp_err_t tool_registry_init(void) s_tool_count = 0; /* Register web_search */ +#ifdef CONFIG_MIMI_TOOL_WEB_SEARCH tool_web_search_init(); mimi_tool_t ws = { @@ -71,6 +72,7 @@ esp_err_t tool_registry_init(void) .execute = tool_web_search_execute, }; register_tool(&ws); +#endif /* Register get_current_time */ mimi_tool_t gt = { @@ -192,6 +194,7 @@ esp_err_t tool_registry_init(void) register_tool(&cr); /* Register GPIO tools */ +#ifdef CONFIG_MIMI_TOOL_GPIO tool_gpio_init(); mimi_tool_t gw = { @@ -227,6 +230,7 @@ esp_err_t tool_registry_init(void) .execute = tool_gpio_read_all_execute, }; register_tool(&ga); +#endif build_tools_json(); diff --git a/main/tools/tool_web_search.h b/main/tools/tool_web_search.h index 28b46f3..6df8c02 100644 --- a/main/tools/tool_web_search.h +++ b/main/tools/tool_web_search.h @@ -2,28 +2,28 @@ #include "esp_err.h" #include +#include -/** - * Initialize web search tool. - */ +#ifdef CONFIG_MIMI_TOOL_WEB_SEARCH 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); - -/** - * Save Brave Search API key to NVS. - */ 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); +#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 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 0f09fe4..b087d4e 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -6,3 +6,23 @@ CONFIG_LWIP_LOCAL_HOSTNAME="mimiclaw" # SPIFFS: increase max filename length (default 32 is too short for session files) 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 diff --git a/taolun.md b/taolun.md index 5b46e8f..cf3431f 100644 --- a/taolun.md +++ b/taolun.md @@ -124,3 +124,125 @@ timezone_show # 显示当前时区配置和本地时间 ### 支持的时区格式 - POSIX: `CST-8`, `JST-9`, `EST5EDT,M3.2.0,M11.1.0`, `UTC0` - 城市名: 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 |