feat: restore NVS runtime config with CLI commands (NVS > build-time priority)

Bring back two-layer configuration: build-time defaults from mimi_secrets.h
with runtime NVS overrides via serial CLI. NVS values take highest priority
so a pre-flashed board can be reconfigured anywhere with just a USB cable.

Restored CLI commands: wifi_set, set_tg_token, set_api_key, set_model,
set_proxy, clear_proxy, set_search_key. Added new commands: config_show
(displays all config with sensitive fields masked), config_reset (clears
all NVS overrides), and help (lists all commands).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
crispyberry
2026-02-08 20:42:10 +08:00
parent 9eb26587ed
commit da43c27003
15 changed files with 568 additions and 20 deletions

View File

@@ -71,7 +71,7 @@ idf.py set-target esp32s3
### Configure ### Configure
All configuration is done through `mimi_secrets.h` at build time: MimiClaw uses a **two-layer config** system: build-time defaults in `mimi_secrets.h`, with runtime overrides via the serial CLI. CLI values are stored in NVS flash and take priority over build-time values.
```bash ```bash
cp main/mimi_secrets.h.example main/mimi_secrets.h cp main/mimi_secrets.h.example main/mimi_secrets.h
@@ -106,7 +106,23 @@ idf.py -p PORT flash monitor
### CLI Commands ### CLI Commands
The serial CLI provides debug and maintenance commands: Connect via serial to configure or debug. **Config commands** let you change settings without recompiling — just plug in a USB cable anywhere.
**Runtime config** (saved to NVS, overrides build-time defaults):
```
mimi> wifi_set MySSID MyPassword # change WiFi network
mimi> set_tg_token 123456:ABC... # change Telegram bot token
mimi> set_api_key sk-ant-api03-... # change Anthropic API key
mimi> set_model claude-sonnet-4-5 # change LLM model
mimi> set_proxy 127.0.0.1 7897 # set HTTP proxy
mimi> clear_proxy # remove proxy
mimi> set_search_key BSA... # set Brave Search API key
mimi> config_show # show all config (masked)
mimi> config_reset # clear NVS, revert to build-time defaults
```
**Debug & maintenance:**
``` ```
mimi> wifi_status # am I connected? mimi> wifi_status # am I connected?

View File

@@ -71,7 +71,7 @@ idf.py set-target esp32s3
### 配置 ### 配置
所有配置通过 `mimi_secrets.h` 在编译时写入: MimiClaw 使用**两层配置**`mimi_secrets.h` 提供编译时默认值,串口 CLI 可在运行时覆盖。CLI 设置的值存在 NVS Flash 中,优先级高于编译时值。
```bash ```bash
cp main/mimi_secrets.h.example main/mimi_secrets.h cp main/mimi_secrets.h.example main/mimi_secrets.h
@@ -110,13 +110,34 @@ idf.py -p PORT flash monitor
**前提**:局域网内有一个支持 HTTP CONNECT 的代理Clash Verge、V2Ray 等),并开启了「允许局域网连接」。 **前提**:局域网内有一个支持 HTTP CONNECT 的代理Clash Verge、V2Ray 等),并开启了「允许局域网连接」。
`mimi_secrets.h`设置 `MIMI_SECRET_PROXY_HOST``MIMI_SECRET_PROXY_PORT`。清除代理只需把这两项改回空字符串 `""`,然后重新编译。 可以`mimi_secrets.h`编译时设置,也可以通过串口 CLI 随时修改:
```
mimi> set_proxy 192.168.1.83 7897 # 设置代理
mimi> clear_proxy # 清除代理
```
> **提示**:确保 ESP32-S3 和代理机器在同一局域网。Clash Verge 在「设置 → 允许局域网」中开启。 > **提示**:确保 ESP32-S3 和代理机器在同一局域网。Clash Verge 在「设置 → 允许局域网」中开启。
### CLI 命令 ### CLI 命令
串口 CLI 提供调试和运维命令: 通过串口连接即可配置和调试。**配置命令**让你无需重新编译就能修改设置 — 随时随地插上 USB 线就能改。
**运行时配置**(存入 NVS覆盖编译时默认值
```
mimi> wifi_set MySSID MyPassword # 换 WiFi
mimi> set_tg_token 123456:ABC... # 换 Telegram Bot Token
mimi> set_api_key sk-ant-api03-... # 换 Anthropic API Key
mimi> set_model claude-sonnet-4-5-20250929 # 换模型
mimi> set_proxy 192.168.1.83 7897 # 设置代理
mimi> clear_proxy # 清除代理
mimi> set_search_key BSA... # 设置 Brave Search API Key
mimi> config_show # 查看所有配置(脱敏显示)
mimi> config_reset # 清除 NVS恢复编译时默认值
```
**调试与运维:**
``` ```
mimi> wifi_status # 连上了吗? mimi> wifi_status # 连上了吗?

View File

@@ -104,9 +104,9 @@
- **MimiClaw**: Not implemented - **MimiClaw**: Not implemented
- **Recommendation**: Requires extra HTTPS request to Whisper API: download Telegram voice -> forward -> get text - **Recommendation**: Requires extra HTTPS request to Whisper API: download Telegram voice -> forward -> get text
### [x] ~~Build-time Config File~~ ### [x] ~~Build-time Config File + Runtime NVS Override~~
- Implemented: `mimi_secrets.h` — sole configuration method (build-time only, no NVS/CLI) - Implemented: `mimi_secrets.h` as build-time defaults, NVS as runtime override via CLI
- Replaces need for YAML config; suitable for MCU workflow - Two-layer config: build-time secrets → NVS fallback, CLI commands to set/show/reset
### [ ] WebSocket Gateway Protocol Enhancement ### [ ] WebSocket Gateway Protocol Enhancement
- **nanobot**: Gateway port 18790 + richer protocol - **nanobot**: Gateway port 18790 + richer protocol
@@ -149,7 +149,7 @@
- [x] OTA Update - [x] OTA Update
- [x] WiFi Manager (build-time credentials, exponential backoff) - [x] WiFi Manager (build-time credentials, exponential backoff)
- [x] SPIFFS storage - [x] SPIFFS storage
- [x] Build-time config (`mimi_secrets.h`, sole configuration method) - [x] Build-time config (`mimi_secrets.h`) + runtime NVS override via CLI
--- ---

View File

@@ -1,8 +1,12 @@
#include "serial_cli.h" #include "serial_cli.h"
#include "mimi_config.h" #include "mimi_config.h"
#include "wifi/wifi_manager.h" #include "wifi/wifi_manager.h"
#include "telegram/telegram_bot.h"
#include "llm/llm_proxy.h"
#include "memory/memory_store.h" #include "memory/memory_store.h"
#include "memory/session_mgr.h" #include "memory/session_mgr.h"
#include "proxy/http_proxy.h"
#include "tools/tool_web_search.h"
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@@ -10,10 +14,32 @@
#include "esp_console.h" #include "esp_console.h"
#include "esp_system.h" #include "esp_system.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "argtable3/argtable3.h" #include "argtable3/argtable3.h"
static const char *TAG = "cli"; static const char *TAG = "cli";
/* --- wifi_set command --- */
static struct {
struct arg_str *ssid;
struct arg_str *password;
struct arg_end *end;
} wifi_set_args;
static int cmd_wifi_set(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&wifi_set_args);
if (nerrors != 0) {
arg_print_errors(stderr, wifi_set_args.end, argv[0]);
return 1;
}
wifi_manager_set_credentials(wifi_set_args.ssid->sval[0],
wifi_set_args.password->sval[0]);
printf("WiFi credentials saved. Restart to apply.\n");
return 0;
}
/* --- wifi_status command --- */ /* --- wifi_status command --- */
static int cmd_wifi_status(int argc, char **argv) static int cmd_wifi_status(int argc, char **argv)
{ {
@@ -22,6 +48,60 @@ static int cmd_wifi_status(int argc, char **argv)
return 0; return 0;
} }
/* --- set_tg_token command --- */
static struct {
struct arg_str *token;
struct arg_end *end;
} tg_token_args;
static int cmd_set_tg_token(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&tg_token_args);
if (nerrors != 0) {
arg_print_errors(stderr, tg_token_args.end, argv[0]);
return 1;
}
telegram_set_token(tg_token_args.token->sval[0]);
printf("Telegram bot token saved.\n");
return 0;
}
/* --- set_api_key command --- */
static struct {
struct arg_str *key;
struct arg_end *end;
} api_key_args;
static int cmd_set_api_key(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&api_key_args);
if (nerrors != 0) {
arg_print_errors(stderr, api_key_args.end, argv[0]);
return 1;
}
llm_set_api_key(api_key_args.key->sval[0]);
printf("API key saved.\n");
return 0;
}
/* --- set_model command --- */
static struct {
struct arg_str *model;
struct arg_end *end;
} model_args;
static int cmd_set_model(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&model_args);
if (nerrors != 0) {
arg_print_errors(stderr, model_args.end, argv[0]);
return 1;
}
llm_set_model(model_args.model->sval[0]);
printf("Model set.\n");
return 0;
}
/* --- memory_read command --- */ /* --- memory_read command --- */
static int cmd_memory_read(int argc, char **argv) static int cmd_memory_read(int argc, char **argv)
{ {
@@ -98,6 +178,116 @@ static int cmd_heap_info(int argc, char **argv)
return 0; return 0;
} }
/* --- set_proxy command --- */
static struct {
struct arg_str *host;
struct arg_int *port;
struct arg_end *end;
} proxy_args;
static int cmd_set_proxy(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&proxy_args);
if (nerrors != 0) {
arg_print_errors(stderr, proxy_args.end, argv[0]);
return 1;
}
http_proxy_set(proxy_args.host->sval[0], (uint16_t)proxy_args.port->ival[0]);
printf("Proxy set. Restart to apply.\n");
return 0;
}
/* --- clear_proxy command --- */
static int cmd_clear_proxy(int argc, char **argv)
{
http_proxy_clear();
printf("Proxy cleared. Restart to apply.\n");
return 0;
}
/* --- set_search_key command --- */
static struct {
struct arg_str *key;
struct arg_end *end;
} search_key_args;
static int cmd_set_search_key(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&search_key_args);
if (nerrors != 0) {
arg_print_errors(stderr, search_key_args.end, argv[0]);
return 1;
}
tool_web_search_set_key(search_key_args.key->sval[0]);
printf("Search API key saved.\n");
return 0;
}
/* --- config_show command --- */
static void print_config(const char *label, const char *ns, const char *key,
const char *build_val, bool mask)
{
char nvs_val[128] = {0};
const char *source = "not set";
const char *display = "(empty)";
/* NVS takes highest priority */
nvs_handle_t nvs;
if (nvs_open(ns, NVS_READONLY, &nvs) == ESP_OK) {
size_t len = sizeof(nvs_val);
if (nvs_get_str(nvs, key, nvs_val, &len) == ESP_OK && nvs_val[0]) {
source = "NVS";
display = nvs_val;
}
nvs_close(nvs);
}
/* Fall back to build-time value */
if (strcmp(source, "not set") == 0 && build_val[0] != '\0') {
source = "build";
display = build_val;
}
if (mask && strlen(display) > 6 && strcmp(display, "(empty)") != 0) {
printf(" %-14s: %.4s**** [%s]\n", label, display, source);
} else {
printf(" %-14s: %s [%s]\n", label, display, source);
}
}
static int cmd_config_show(int argc, char **argv)
{
printf("=== Current Configuration ===\n");
print_config("WiFi SSID", MIMI_NVS_WIFI, MIMI_NVS_KEY_SSID, MIMI_SECRET_WIFI_SSID, false);
print_config("WiFi Pass", MIMI_NVS_WIFI, MIMI_NVS_KEY_PASS, MIMI_SECRET_WIFI_PASS, true);
print_config("TG Token", MIMI_NVS_TG, MIMI_NVS_KEY_TG_TOKEN, MIMI_SECRET_TG_TOKEN, true);
print_config("API Key", MIMI_NVS_LLM, MIMI_NVS_KEY_API_KEY, MIMI_SECRET_API_KEY, true);
print_config("Model", MIMI_NVS_LLM, MIMI_NVS_KEY_MODEL, MIMI_SECRET_MODEL, false);
print_config("Proxy Host", MIMI_NVS_PROXY, MIMI_NVS_KEY_PROXY_HOST, MIMI_SECRET_PROXY_HOST, false);
print_config("Proxy Port", MIMI_NVS_PROXY, MIMI_NVS_KEY_PROXY_PORT, MIMI_SECRET_PROXY_PORT, false);
print_config("Search Key", MIMI_NVS_SEARCH, MIMI_NVS_KEY_API_KEY, MIMI_SECRET_SEARCH_KEY, true);
printf("=============================\n");
return 0;
}
/* --- config_reset command --- */
static int cmd_config_reset(int argc, char **argv)
{
const char *namespaces[] = {
MIMI_NVS_WIFI, MIMI_NVS_TG, MIMI_NVS_LLM, MIMI_NVS_PROXY, MIMI_NVS_SEARCH
};
for (int i = 0; i < 5; i++) {
nvs_handle_t nvs;
if (nvs_open(namespaces[i], NVS_READWRITE, &nvs) == ESP_OK) {
nvs_erase_all(nvs);
nvs_commit(nvs);
nvs_close(nvs);
}
}
printf("All NVS config cleared. Build-time defaults will be used on restart.\n");
return 0;
}
/* --- restart command --- */ /* --- restart command --- */
static int cmd_restart(int argc, char **argv) static int cmd_restart(int argc, char **argv)
{ {
@@ -120,6 +310,19 @@ esp_err_t serial_cli_init(void)
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl)); ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl));
/* Register commands */ /* Register commands */
esp_console_register_help_command();
/* wifi_set */
wifi_set_args.ssid = arg_str1(NULL, NULL, "<ssid>", "WiFi SSID");
wifi_set_args.password = arg_str1(NULL, NULL, "<password>", "WiFi password");
wifi_set_args.end = arg_end(2);
esp_console_cmd_t wifi_set_cmd = {
.command = "wifi_set",
.help = "Set WiFi SSID and password",
.func = &cmd_wifi_set,
.argtable = &wifi_set_args,
};
esp_console_cmd_register(&wifi_set_cmd);
/* wifi_status */ /* wifi_status */
esp_console_cmd_t wifi_status_cmd = { esp_console_cmd_t wifi_status_cmd = {
@@ -129,6 +332,39 @@ esp_err_t serial_cli_init(void)
}; };
esp_console_cmd_register(&wifi_status_cmd); esp_console_cmd_register(&wifi_status_cmd);
/* set_tg_token */
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 = {
.command = "set_tg_token",
.help = "Set Telegram bot token",
.func = &cmd_set_tg_token,
.argtable = &tg_token_args,
};
esp_console_cmd_register(&tg_token_cmd);
/* set_api_key */
api_key_args.key = arg_str1(NULL, NULL, "<key>", "Anthropic API key");
api_key_args.end = arg_end(1);
esp_console_cmd_t api_key_cmd = {
.command = "set_api_key",
.help = "Set Claude API key",
.func = &cmd_set_api_key,
.argtable = &api_key_args,
};
esp_console_cmd_register(&api_key_cmd);
/* set_model */
model_args.model = arg_str1(NULL, NULL, "<model>", "Model identifier");
model_args.end = arg_end(1);
esp_console_cmd_t model_cmd = {
.command = "set_model",
.help = "Set LLM model (default: " MIMI_LLM_DEFAULT_MODEL ")",
.func = &cmd_set_model,
.argtable = &model_args,
};
esp_console_cmd_register(&model_cmd);
/* memory_read */ /* memory_read */
esp_console_cmd_t mem_read_cmd = { esp_console_cmd_t mem_read_cmd = {
.command = "memory_read", .command = "memory_read",
@@ -175,6 +411,53 @@ esp_err_t serial_cli_init(void)
}; };
esp_console_cmd_register(&heap_cmd); esp_console_cmd_register(&heap_cmd);
/* set_search_key */
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 = {
.command = "set_search_key",
.help = "Set Brave Search API key for web_search tool",
.func = &cmd_set_search_key,
.argtable = &search_key_args,
};
esp_console_cmd_register(&search_key_cmd);
/* set_proxy */
proxy_args.host = arg_str1(NULL, NULL, "<host>", "Proxy host/IP");
proxy_args.port = arg_int1(NULL, NULL, "<port>", "Proxy port");
proxy_args.end = arg_end(2);
esp_console_cmd_t proxy_cmd = {
.command = "set_proxy",
.help = "Set HTTP proxy (e.g. set_proxy 192.168.1.83 7897)",
.func = &cmd_set_proxy,
.argtable = &proxy_args,
};
esp_console_cmd_register(&proxy_cmd);
/* clear_proxy */
esp_console_cmd_t clear_proxy_cmd = {
.command = "clear_proxy",
.help = "Remove proxy configuration",
.func = &cmd_clear_proxy,
};
esp_console_cmd_register(&clear_proxy_cmd);
/* config_show */
esp_console_cmd_t config_show_cmd = {
.command = "config_show",
.help = "Show current configuration (build-time + NVS)",
.func = &cmd_config_show,
};
esp_console_cmd_register(&config_show_cmd);
/* config_reset */
esp_console_cmd_t config_reset_cmd = {
.command = "config_reset",
.help = "Clear all NVS overrides, revert to build-time defaults",
.func = &cmd_config_reset,
};
esp_console_cmd_register(&config_reset_cmd);
/* restart */ /* restart */
esp_console_cmd_t restart_cmd = { esp_console_cmd_t restart_cmd = {
.command = "restart", .command = "restart",

View File

@@ -8,6 +8,7 @@
#include "esp_http_client.h" #include "esp_http_client.h"
#include "esp_crt_bundle.h" #include "esp_crt_bundle.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "nvs.h"
#include "cJSON.h" #include "cJSON.h"
static const char *TAG = "llm"; static const char *TAG = "llm";
@@ -70,6 +71,7 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
esp_err_t llm_proxy_init(void) esp_err_t llm_proxy_init(void)
{ {
/* Start with build-time defaults */
if (MIMI_SECRET_API_KEY[0] != '\0') { if (MIMI_SECRET_API_KEY[0] != '\0') {
strncpy(s_api_key, MIMI_SECRET_API_KEY, sizeof(s_api_key) - 1); strncpy(s_api_key, MIMI_SECRET_API_KEY, sizeof(s_api_key) - 1);
} }
@@ -77,10 +79,26 @@ esp_err_t llm_proxy_init(void)
strncpy(s_model, MIMI_SECRET_MODEL, sizeof(s_model) - 1); strncpy(s_model, MIMI_SECRET_MODEL, sizeof(s_model) - 1);
} }
/* NVS overrides take highest priority (set via CLI) */
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_LLM, NVS_READONLY, &nvs) == ESP_OK) {
char tmp[128] = {0};
size_t len = sizeof(tmp);
if (nvs_get_str(nvs, MIMI_NVS_KEY_API_KEY, tmp, &len) == ESP_OK && tmp[0]) {
strncpy(s_api_key, tmp, sizeof(s_api_key) - 1);
}
len = sizeof(tmp);
memset(tmp, 0, sizeof(tmp));
if (nvs_get_str(nvs, MIMI_NVS_KEY_MODEL, tmp, &len) == ESP_OK && tmp[0]) {
strncpy(s_model, tmp, sizeof(s_model) - 1);
}
nvs_close(nvs);
}
if (s_api_key[0]) { if (s_api_key[0]) {
ESP_LOGI(TAG, "LLM proxy initialized (model: %s)", s_model); ESP_LOGI(TAG, "LLM proxy initialized (model: %s)", s_model);
} else { } else {
ESP_LOGW(TAG, "No API key. Set MIMI_SECRET_API_KEY in mimi_secrets.h"); ESP_LOGW(TAG, "No API key. Use CLI: set_api_key <KEY>");
} }
return ESP_OK; return ESP_OK;
} }
@@ -448,3 +466,30 @@ esp_err_t llm_chat_tools(const char *system_prompt,
return ESP_OK; return ESP_OK;
} }
/* ── NVS helpers ──────────────────────────────────────────────── */
esp_err_t llm_set_api_key(const char *api_key)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_API_KEY, api_key));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_api_key, api_key, sizeof(s_api_key) - 1);
ESP_LOGI(TAG, "API key saved");
return ESP_OK;
}
esp_err_t llm_set_model(const char *model)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_MODEL, model));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_model, model, sizeof(s_model) - 1);
ESP_LOGI(TAG, "Model set to: %s", s_model);
return ESP_OK;
}

View File

@@ -8,10 +8,20 @@
#include "mimi_config.h" #include "mimi_config.h"
/** /**
* Initialize the LLM proxy. * Initialize the LLM proxy. Reads API key and model from build-time secrets, then NVS.
*/ */
esp_err_t llm_proxy_init(void); esp_err_t llm_proxy_init(void);
/**
* Save the Anthropic API key to NVS.
*/
esp_err_t llm_set_api_key(const char *api_key);
/**
* Save the model identifier to NVS.
*/
esp_err_t llm_set_model(const char *model);
/** /**
* Send a chat completion request to Anthropic Messages API (streaming). * Send a chat completion request to Anthropic Messages API (streaming).
* *

View File

@@ -2,7 +2,7 @@
/* MimiClaw Global Configuration */ /* MimiClaw Global Configuration */
/* Build-time secrets (sole configuration method) */ /* Build-time secrets (highest priority, override NVS) */
#if __has_include("mimi_secrets.h") #if __has_include("mimi_secrets.h")
#include "mimi_secrets.h" #include "mimi_secrets.h"
#endif #endif
@@ -88,3 +88,19 @@
#define MIMI_CLI_PRIO 3 #define MIMI_CLI_PRIO 3
#define MIMI_CLI_CORE 0 #define MIMI_CLI_CORE 0
/* NVS Namespaces */
#define MIMI_NVS_WIFI "wifi_config"
#define MIMI_NVS_TG "tg_config"
#define MIMI_NVS_LLM "llm_config"
#define MIMI_NVS_PROXY "proxy_config"
#define MIMI_NVS_SEARCH "search_config"
/* NVS Keys */
#define MIMI_NVS_KEY_SSID "ssid"
#define MIMI_NVS_KEY_PASS "password"
#define MIMI_NVS_KEY_TG_TOKEN "bot_token"
#define MIMI_NVS_KEY_API_KEY "api_key"
#define MIMI_NVS_KEY_MODEL "model"
#define MIMI_NVS_KEY_PROXY_HOST "host"
#define MIMI_NVS_KEY_PROXY_PORT "port"

View File

@@ -9,6 +9,7 @@
#include <unistd.h> #include <unistd.h>
#include "esp_log.h" #include "esp_log.h"
#include "nvs.h"
#include "esp_tls.h" #include "esp_tls.h"
#include "esp_crt_bundle.h" #include "esp_crt_bundle.h"
@@ -25,11 +26,27 @@ static uint16_t s_proxy_port = 0;
esp_err_t http_proxy_init(void) esp_err_t http_proxy_init(void)
{ {
/* Start with build-time defaults */
if (MIMI_SECRET_PROXY_HOST[0] != '\0' && MIMI_SECRET_PROXY_PORT[0] != '\0') { if (MIMI_SECRET_PROXY_HOST[0] != '\0' && MIMI_SECRET_PROXY_PORT[0] != '\0') {
strncpy(s_proxy_host, MIMI_SECRET_PROXY_HOST, sizeof(s_proxy_host) - 1); strncpy(s_proxy_host, MIMI_SECRET_PROXY_HOST, sizeof(s_proxy_host) - 1);
s_proxy_port = (uint16_t)atoi(MIMI_SECRET_PROXY_PORT); s_proxy_port = (uint16_t)atoi(MIMI_SECRET_PROXY_PORT);
} }
/* NVS overrides take highest priority (set via CLI) */
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_PROXY, NVS_READONLY, &nvs) == ESP_OK) {
char tmp[64] = {0};
size_t len = sizeof(tmp);
if (nvs_get_str(nvs, MIMI_NVS_KEY_PROXY_HOST, tmp, &len) == ESP_OK && tmp[0]) {
strncpy(s_proxy_host, tmp, sizeof(s_proxy_host) - 1);
uint16_t port = 0;
if (nvs_get_u16(nvs, MIMI_NVS_KEY_PROXY_PORT, &port) == ESP_OK && port) {
s_proxy_port = port;
}
}
nvs_close(nvs);
}
if (s_proxy_host[0] && s_proxy_port) { if (s_proxy_host[0] && s_proxy_port) {
ESP_LOGI(TAG, "Proxy configured: %s:%d", s_proxy_host, s_proxy_port); ESP_LOGI(TAG, "Proxy configured: %s:%d", s_proxy_host, s_proxy_port);
} else { } else {
@@ -38,6 +55,36 @@ esp_err_t http_proxy_init(void)
return ESP_OK; return ESP_OK;
} }
esp_err_t http_proxy_set(const char *host, uint16_t port)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_PROXY, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_PROXY_HOST, host));
ESP_ERROR_CHECK(nvs_set_u16(nvs, MIMI_NVS_KEY_PROXY_PORT, port));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_proxy_host, host, sizeof(s_proxy_host) - 1);
s_proxy_port = port;
ESP_LOGI(TAG, "Proxy set to %s:%d", s_proxy_host, s_proxy_port);
return ESP_OK;
}
esp_err_t http_proxy_clear(void)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_PROXY, NVS_READWRITE, &nvs));
nvs_erase_key(nvs, MIMI_NVS_KEY_PROXY_HOST);
nvs_erase_key(nvs, MIMI_NVS_KEY_PROXY_PORT);
nvs_commit(nvs);
nvs_close(nvs);
s_proxy_host[0] = '\0';
s_proxy_port = 0;
ESP_LOGI(TAG, "Proxy cleared");
return ESP_OK;
}
bool http_proxy_is_enabled(void) bool http_proxy_is_enabled(void)
{ {
return s_proxy_host[0] != '\0' && s_proxy_port != 0; return s_proxy_host[0] != '\0' && s_proxy_port != 0;

View File

@@ -14,6 +14,16 @@ esp_err_t http_proxy_init(void);
*/ */
bool http_proxy_is_enabled(void); bool http_proxy_is_enabled(void);
/**
* Save proxy host and port to NVS.
*/
esp_err_t http_proxy_set(const char *host, uint16_t port);
/**
* Remove proxy config from NVS.
*/
esp_err_t http_proxy_clear(void);
/* ── Proxied HTTPS connection ─────────────────────────────────── */ /* ── Proxied HTTPS connection ─────────────────────────────────── */
typedef struct proxy_conn proxy_conn_t; typedef struct proxy_conn proxy_conn_t;

View File

@@ -8,6 +8,7 @@
#include "esp_log.h" #include "esp_log.h"
#include "esp_http_client.h" #include "esp_http_client.h"
#include "esp_crt_bundle.h" #include "esp_crt_bundle.h"
#include "nvs.h"
#include "cJSON.h" #include "cJSON.h"
static const char *TAG = "telegram"; static const char *TAG = "telegram";
@@ -256,10 +257,23 @@ static void telegram_poll_task(void *arg)
esp_err_t telegram_bot_init(void) esp_err_t telegram_bot_init(void)
{ {
/* NVS overrides take highest priority (set via CLI) */
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_TG, NVS_READONLY, &nvs) == ESP_OK) {
char tmp[128] = {0};
size_t len = sizeof(tmp);
if (nvs_get_str(nvs, MIMI_NVS_KEY_TG_TOKEN, tmp, &len) == ESP_OK && tmp[0]) {
strncpy(s_bot_token, tmp, sizeof(s_bot_token) - 1);
}
nvs_close(nvs);
}
/* s_bot_token is already initialized from MIMI_SECRET_TG_TOKEN as fallback */
if (s_bot_token[0]) { if (s_bot_token[0]) {
ESP_LOGI(TAG, "Telegram bot token loaded (len=%d)", (int)strlen(s_bot_token)); ESP_LOGI(TAG, "Telegram bot token loaded (len=%d)", (int)strlen(s_bot_token));
} else { } else {
ESP_LOGW(TAG, "No Telegram bot token. Set MIMI_SECRET_TG_TOKEN in mimi_secrets.h"); ESP_LOGW(TAG, "No Telegram bot token. Use CLI: set_tg_token <TOKEN>");
} }
return ESP_OK; return ESP_OK;
} }
@@ -357,3 +371,15 @@ esp_err_t telegram_send_message(const char *chat_id, const char *text)
return ESP_OK; return ESP_OK;
} }
esp_err_t telegram_set_token(const char *token)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_TG, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_TG_TOKEN, token));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_bot_token, token, sizeof(s_bot_token) - 1);
ESP_LOGI(TAG, "Telegram bot token saved");
return ESP_OK;
}

View File

@@ -20,3 +20,8 @@ esp_err_t telegram_bot_start(void);
*/ */
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);

View File

@@ -8,6 +8,7 @@
#include "esp_http_client.h" #include "esp_http_client.h"
#include "esp_crt_bundle.h" #include "esp_crt_bundle.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "nvs.h"
#include "cJSON.h" #include "cJSON.h"
static const char *TAG = "web_search"; static const char *TAG = "web_search";
@@ -43,14 +44,26 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
esp_err_t tool_web_search_init(void) esp_err_t tool_web_search_init(void)
{ {
/* Start with build-time default */
if (MIMI_SECRET_SEARCH_KEY[0] != '\0') { if (MIMI_SECRET_SEARCH_KEY[0] != '\0') {
strncpy(s_search_key, MIMI_SECRET_SEARCH_KEY, sizeof(s_search_key) - 1); strncpy(s_search_key, MIMI_SECRET_SEARCH_KEY, sizeof(s_search_key) - 1);
} }
/* NVS overrides take highest priority (set via CLI) */
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_SEARCH, NVS_READONLY, &nvs) == ESP_OK) {
char tmp[128] = {0};
size_t len = sizeof(tmp);
if (nvs_get_str(nvs, MIMI_NVS_KEY_API_KEY, tmp, &len) == ESP_OK && tmp[0]) {
strncpy(s_search_key, tmp, sizeof(s_search_key) - 1);
}
nvs_close(nvs);
}
if (s_search_key[0]) { if (s_search_key[0]) {
ESP_LOGI(TAG, "Web search initialized (key configured)"); ESP_LOGI(TAG, "Web search initialized (key configured)");
} else { } else {
ESP_LOGW(TAG, "No search API key. Set MIMI_SECRET_SEARCH_KEY in mimi_secrets.h"); ESP_LOGW(TAG, "No search API key. Use CLI: set_search_key <KEY>");
} }
return ESP_OK; return ESP_OK;
} }
@@ -283,3 +296,16 @@ esp_err_t tool_web_search_execute(const char *input_json, char *output, size_t o
ESP_LOGI(TAG, "Search complete, %d bytes result", (int)strlen(output)); ESP_LOGI(TAG, "Search complete, %d bytes result", (int)strlen(output));
return ESP_OK; return ESP_OK;
} }
esp_err_t tool_web_search_set_key(const char *api_key)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_SEARCH, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_API_KEY, api_key));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_search_key, api_key, sizeof(s_search_key) - 1);
ESP_LOGI(TAG, "Search API key saved");
return ESP_OK;
}

View File

@@ -18,3 +18,7 @@ esp_err_t tool_web_search_init(void);
*/ */
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);

View File

@@ -6,6 +6,8 @@
#include "esp_log.h" #include "esp_log.h"
#include "esp_wifi.h" #include "esp_wifi.h"
#include "esp_netif.h" #include "esp_netif.h"
#include "nvs_flash.h"
#include "nvs.h"
static const char *TAG = "wifi"; static const char *TAG = "wifi";
@@ -70,14 +72,34 @@ esp_err_t wifi_manager_init(void)
esp_err_t wifi_manager_start(void) esp_err_t wifi_manager_start(void)
{ {
if (MIMI_SECRET_WIFI_SSID[0] == '\0') { wifi_config_t wifi_cfg = {0};
ESP_LOGW(TAG, "No WiFi credentials. Set MIMI_SECRET_WIFI_SSID in mimi_secrets.h"); bool found = false;
return ESP_ERR_NOT_FOUND;
/* NVS overrides take highest priority (set via CLI) */
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_WIFI, NVS_READONLY, &nvs) == ESP_OK) {
size_t len = sizeof(wifi_cfg.sta.ssid);
if (nvs_get_str(nvs, MIMI_NVS_KEY_SSID, (char *)wifi_cfg.sta.ssid, &len) == ESP_OK) {
len = sizeof(wifi_cfg.sta.password);
nvs_get_str(nvs, MIMI_NVS_KEY_PASS, (char *)wifi_cfg.sta.password, &len);
found = true;
}
nvs_close(nvs);
} }
wifi_config_t wifi_cfg = {0}; /* Fall back to build-time secrets */
strncpy((char *)wifi_cfg.sta.ssid, MIMI_SECRET_WIFI_SSID, sizeof(wifi_cfg.sta.ssid) - 1); if (!found) {
strncpy((char *)wifi_cfg.sta.password, MIMI_SECRET_WIFI_PASS, sizeof(wifi_cfg.sta.password) - 1); if (MIMI_SECRET_WIFI_SSID[0] != '\0') {
strncpy((char *)wifi_cfg.sta.ssid, MIMI_SECRET_WIFI_SSID, sizeof(wifi_cfg.sta.ssid) - 1);
strncpy((char *)wifi_cfg.sta.password, MIMI_SECRET_WIFI_PASS, sizeof(wifi_cfg.sta.password) - 1);
found = true;
}
}
if (!found) {
ESP_LOGW(TAG, "No WiFi credentials. Use CLI: wifi_set <SSID> <PASS>");
return ESP_ERR_NOT_FOUND;
}
ESP_LOGI(TAG, "Connecting to SSID: %s", wifi_cfg.sta.ssid); ESP_LOGI(TAG, "Connecting to SSID: %s", wifi_cfg.sta.ssid);
@@ -110,6 +132,18 @@ const char *wifi_manager_get_ip(void)
return s_ip_str; return s_ip_str;
} }
esp_err_t wifi_manager_set_credentials(const char *ssid, const char *password)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_WIFI, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_SSID, ssid));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_PASS, password));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
ESP_LOGI(TAG, "WiFi credentials saved for SSID: %s", ssid);
return ESP_OK;
}
EventGroupHandle_t wifi_manager_get_event_group(void) EventGroupHandle_t wifi_manager_get_event_group(void)
{ {
return s_wifi_event_group; return s_wifi_event_group;

View File

@@ -35,6 +35,11 @@ bool wifi_manager_is_connected(void);
*/ */
const char *wifi_manager_get_ip(void); const char *wifi_manager_get_ip(void);
/**
* Save WiFi credentials to NVS.
*/
esp_err_t wifi_manager_set_credentials(const char *ssid, const char *password);
/** /**
* Get the event group for WiFi state (WIFI_CONNECTED_BIT / WIFI_FAIL_BIT). * Get the event group for WiFi state (WIFI_CONNECTED_BIT / WIFI_FAIL_BIT).
*/ */