feat: 添加编译时模块开关配置

通过 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 秒轮询警告日志
This commit is contained in:
2026-04-03 20:15:26 +08:00
parent 540bfe825f
commit 6983a1f1ba
15 changed files with 455 additions and 154 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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, "<token>", "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, "<app_id>", "Feishu App ID");
feishu_creds_args.app_secret = arg_str1(NULL, NULL, "<app_secret>", "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, "<key>", "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, "<key>", "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, "<host>", "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, "<query>", "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 = {

View File

@@ -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

View File

@@ -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!");
}

View File

@@ -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

View File

@@ -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

View File

@@ -2,26 +2,34 @@
#include "esp_err.h"
#include <stddef.h>
#include <stdio.h>
/**
* 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": <int>, "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": <int>}
*/
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

View File

@@ -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();

View File

@@ -2,28 +2,28 @@
#include "esp_err.h"
#include <stddef.h>
#include <stdio.h>
/**
* 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