Merge branch 'feature/module-config' into main (resolved conflicts)
Some checks failed
Build / idf-build (push) Has been cancelled

This commit is contained in:
2026-04-03 20:17:40 +08:00
17 changed files with 1053 additions and 31 deletions

255
taolun.md
View File

@@ -2,10 +2,214 @@
---
## 讨论ESP-IDF v6.0 编译适配
## 实施记录:时间同步 + NVS 稳定性 + LLM Provider Bug 修复
**日期**2026-03-31
**目标**解决 ESP-IDF v6.0 编译失败问题,完成固件烧录
**日期**2026-04-01
**分支**`feature/time-sync-nvs-stability`
**状态**:已实现,待烧录验证
### 修复的核心 Bug
#### Bug 1: `llm_provider_init()` 无法从 NVS 加载 provider-specific API key
**文件**`main/llm/llm_provider.c`
**问题**`llm_provider_init()` 调用 `llm_provider_get_api_key(s_current_provider->name)` 加载当前 provider 的 key但该函数对当前 provider 直接返回内存中的 `s_api_key`(初始为空字符串),导致 NVS 中的 `siliconflow_api_key``volcengine_api_key` 等永远不会被加载到内存。`s_base_url` 同理。
**修复**:改为直接通过 `get_provider_api_key_nvs_key()` 获取 NVS key 名,然后 `nvs_get_str()` 直接读取,绕过内存缓存。
#### Bug 2: 时间显示 1970
**文件**:新增 `main/time_sync/`
**问题**ESP32 上电后 RTC 从 0 开始,没有 SNTP 客户端自动同步时间。
**修复**:新建 `time_sync` 模块WiFi 连接后自动启动 SNTP`ntp.ntsc.ac.cn` 同步时间。
#### Bug 3: Brownout 导致 NVS 损坏
**文件**:新增 `main/nvs_safety/`,修改 `sdkconfig.defaults`
**问题**:不同 USB 口供电能力不同WiFi 峰值电流 300-500mA供电不足时 Flash 写入中断导致 NVS 损坏。
**修复**:启用 Brownout Detection + 启动时 NVS 完整性校验与自动修复。
### 新增 CLI 命令
| 命令 | 功能 |
|------|------|
| `ntp_status` | 查看完整状态(时区 + 本地时间 + 同步状态 + NTP 服务器 + 上次同步时间) |
| `ntp_sync` | 立即手动同步一次 |
| `ntp_set <server>` | 设置 NTP 服务器(存 NVS重启后生效 |
### 实施过程中的潜在问题与修复
#### 问题 1: `tool_set_timezone.c` 缺少 `<strings.h>`
**发现**`strcasecmp()``strcasestr()` 在 POSIX 标准中定义在 `<strings.h>` 而非 `<string.h>` 中。某些编译器会报错。
**修复**:添加 `#include <strings.h>`
#### 问题 2: `parse_and_set_time_from_date()` 中多余的 `tzset()` 调用
**发现**:在 `mktime()` 之后又调用了 `setenv("TZ", "UTC0", 1); tzset();`,这会把时区重新设为 UTC导致后续 `localtime_r()` 返回 UTC 时间而非用户设置的时区时间。
**修复**:移除 `mktime()` 后的 `setenv`/`tzset()` 调用。时区恢复由调用方(`tool_set_timezone_execute`)在设置完系统时钟后通过 `setenv("TZ", resolved_tz, 1); tzset()` 处理。
#### 问题 3: `serial_cli.c` 中 `vTaskDelay` 的 FreeRTOS 头文件
**确认**`freertos/task.h` 已在文件顶部包含(第 32 行),无需额外添加。
#### 问题 4: `time_sync.c` 中 `sntp_stop()` API 兼容性
**确认**`sntp_stop()` 是 ESP-IDF 标准 SNTP API`esp_sntp.h` 中定义v5.x 和 v6.x 均支持。
#### 问题 5: `nvs_safety.c` 中 NVS 迭代器 API
**确认**`nvs_iterator_t``nvs_entry_find()``nvs_entry_info()``nvs_entry_next()` 是 ESP-IDF 标准 NVS API`nvs.h` 中定义。
### 最终文件变更清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `main/llm/llm_provider.c` | **修复** | 修复 `llm_provider_init()` 加载逻辑 |
| `main/time_sync/time_sync.h` | 新建 | SNTP 时间同步头文件 |
| `main/time_sync/time_sync.c` | 新建 | SNTP 时间同步实现 |
| `main/nvs_safety/nvs_safety.h` | 新建 | NVS 完整性校验头文件 |
| `main/nvs_safety/nvs_safety.c` | 新建 | NVS 损坏检测与修复 |
| `main/mimi.c` | 修改 | WiFi 连接后调用 `time_sync_init()` + 启动时调用 `nvs_safety_check()` |
| `main/cli/serial_cli.c` | 修改 | `timezone_show` 改为 `ntp_status`,新增 `ntp_sync``ntp_set` 命令 |
| `main/tools/tool_set_timezone.c` | 修改 | 添加 `<strings.h>`,设置时区后检测时间有效性 |
| `main/mimi_config.h` | 修改 | 添加 `MIMI_NVS_KEY_NTP_SERVER``MIMI_DEFAULT_NTP_SERVER` |
| `main/CMakeLists.txt` | 修改 | 添加新源文件 |
| `sdkconfig.defaults` | 修改 | 启用 Brownout Detection + SNTP |
### 认知修复(编译错误避坑指南)
#### ESP-IDF v6.0 API 迁移要点
1. **SNTP API**`sntp_*``esp_sntp_*`,组件属于 `lwip` 而非独立组件
2. **NVS API**:迭代器操作需要指针参数,记得释放迭代器
3. **类型系统**`arg_str1` 是函数,结构体成员用 `struct arg_str`
4. **组件依赖**v6.0 中 `esp_sntp` 组件不存在,无需添加
#### 常见错误模式
- **结构体类型错误**:混淆函数名和类型名
- **API 签名变化**:参数数量或类型改变
- **组件重组**:功能移动到其他组件
- **未使用代码**:及时清理未使用的函数和变量
#### 预防措施
1. 使用 `#ifdef` 保护平台相关常量(如 `WIFI_REASON_*`
2. 检查函数返回值,特别是迭代器操作
3. 定期清理未使用的代码
4. 参考官方迁移指南和头文件声明
---
## 编译错误修复速查表
| 问题类型 | 错误示例 | 修复方案 | 涉及文件 |
|----------|----------|----------|----------|
| 结构体类型 | `arg_str1 *server` | 使用 `struct arg_str *server` | `serial_cli.c` |
| SNTP 弃用 | `sntp_init()` | 改用 `esp_sntp_init()` | `time_sync.c` |
| NVS 参数 | `nvs_entry_find(3个参数)` | 添加第4个参数 `&it` | `nvs_safety.c` |
| 未使用函数 | `provider_is_openai` | 删除函数定义 | `llm_proxy.c` |
| 组件依赖 | `esp_sntp` 组件不存在 | 移除依赖声明 | `CMakeLists.txt` |
| WiFi 常量 | `WIFI_REASON_ASSOC_EXPIRE` | 添加 `#ifdef` 保护 | `wifi_manager.c` |
---
## 讨论:系统时间同步 + NVS 配置稳定性修复
**日期**2026-04-01
**目标**:修复两个关键问题 — 时间显示 1970、换 USB 口后配置不生效
### 问题 1时间显示 1970-01-01
**现象**
```
Current timezone: Asia/Shanghai [NVS]
Local time: 1970-01-01 00:00:23 GMT (Thursday)
```
**根因**:代码中没有初始化 SNTP/NTP 客户端。ESP32 上电后 RTC 时钟从 0 开始计时,`time(NULL)` 返回的就是 1970 年以来的秒数。目前只有 LLM 调用 `get_current_time` 工具时才会通过 HTTP 同步时间。
**修复方案**
- 新建 `main/time_sync/time_sync.c`,使用 ESP-IDF 内置 SNTP 组件
- WiFi 连接成功后自动从 `ntp.ntsc.ac.cn` 同步时间
- 同步成功后自动应用已保存的时区配置
- `ntp_status` 命令显示完整时间状态时区、本地时间、同步状态、NTP 服务器、上次同步时间)
### 问题 2换 USB 口/电脑后不工作 + 模型配置不加载
**现象**
- 插入其他 Type-C 口或电脑,设备不正常工作
- 需要在 Web 界面重新填写大模型 ID 和密钥,保存重启后才正常
- 命令行中模型状态显示异常
**根因分析**
#### 2.1 LLM Provider 初始化 Bug核心问题
`llm_provider.c:260-284` 中,`llm_provider_init()` 通过 `llm_provider_get_api_key()` 加载当前 provider 的 API key
```c
void llm_provider_init(void) {
const char *api_key = llm_provider_get_api_key(s_current_provider->name);
if (api_key) {
strncpy(s_api_key, api_key, sizeof(s_api_key) - 1);
}
```
`llm_provider_get_api_key()` 的逻辑是(`llm_provider.c:209-214`
```c
const char *llm_provider_get_api_key(const char *provider_name) {
if (strcmp(provider_name, s_current_provider->name) == 0) {
return s_api_key; // 直接返回内存中的值,不从 NVS 读!
}
// 否则才从 NVS 加载...
```
**形成死循环**`llm_provider_init()` 想从 NVS 加载 key → 调用 `llm_provider_get_api_key` → 发现是当前 provider → 直接返回内存中的 `s_api_key`(初始为空)→ 把空值复制给自己 → **NVS 中的 provider-specific key如 `siliconflow_api_key`)永远不会被加载到内存**
同理,`s_base_url` 也存在相同问题。
**为什么 Web 界面保存后能正常工作?**
Web 界面的 `/save` 处理函数(`wifi_onboard.c:377-408`)会**同时保存两份**
```c
nvs_sync_field(root, "api_key", MIMI_NVS_LLM, MIMI_NVS_KEY_API_KEY); // 通用 key
nvs_sync_field(root, "api_key", MIMI_NVS_LLM, api_key_nvs); // provider-specific key
```
`llm_proxy_init()` 能正确加载 `MIMI_NVS_KEY_API_KEY`(通用 key所以 Web 保存后能用。但如果只通过 CLI 的 `set_siliconflow_key` 设置(只保存到 `siliconflow_api_key`),上电后就不会被加载。
#### 2.2 Brownout欠压导致 NVS 损坏
ESP32-S3 开启 WiFi 时峰值电流可达 300-500mA不同 USB 端口的供电能力差异很大。供电不足时:
- 可能导致静默重启或 Flash 写入中断
- `nvs_commit` 过程中断电会导致 NVS 数据损坏
- WiFi 不稳定但日志看起来"正常"
**修复方案**
1. 修复 `llm_provider_init()` — 直接从 NVS 读取当前 provider 的 key 和 Base URL绕过 `llm_provider_get_api_key` 的内存缓存
2. 新建 `main/nvs_safety/nvs_safety.c` — 启动时校验关键 NVS 命名空间的完整性,检测并修复损坏条目
3.`sdkconfig.defaults` 中启用 ESP32-S3 的 Brownout Detection
### 改动清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `main/llm/llm_provider.c` | **修复** | 修复 `llm_provider_init()` 加载逻辑 |
| `main/time_sync/time_sync.h` | 新建 | SNTP 时间同步头文件 |
| `main/time_sync/time_sync.c` | 新建 | SNTP 时间同步实现 |
| `main/nvs_safety/nvs_safety.h` | 新建 | NVS 完整性校验头文件 |
| `main/nvs_safety/nvs_safety.c` | 新建 | NVS 损坏检测与修复 |
| `main/mimi.c` | 修改 | WiFi 连接后调用 `time_sync_init()` + 启动时调用 `nvs_safety_check()` |
| `main/cli/serial_cli.c` | 修改 | `timezone_show` 增加 SNTP 同步状态 |
| `main/CMakeLists.txt` | 修改 | 添加新源文件和 `esp_netif` 依赖 |
| `sdkconfig.defaults` | 修改 | 启用 Brownout Detection |
### 问题清单
@@ -83,6 +287,49 @@ idf.py -p COMx flash monitor
---
## 讨论NTP 时间管理 CLI 命令
**日期**2026-04-01
**目标**:完善时间管理 CLI支持手动同步、状态查看、自定义 NTP 服务器
### 背景
- 初始方案只有 `timezone_show` 显示时区和时间,缺少 NTP 同步状态和手动同步能力
- 用户需要能手动触发时间同步、查看上次同步时间、自定义 NTP 服务器
### 命令设计
| 命令 | 功能 |
|------|------|
| `ntp_status` | 查看完整状态(时区 + 本地时间 + 同步状态 + NTP 服务器 + 上次同步时间) |
| `ntp_sync` | 立即手动同步一次 |
| `ntp_set <server>` | 设置 NTP 服务器(存 NVS重启后生效 |
- 默认 NTP 服务器:`ntp.ntsc.ac.cn`(中国科学院国家授时中心)
- 同步状态:`synced`(已同步)、`syncing`(同步中)、`not_synced`(未同步)
### `ntp_status` 输出示例
```
Current timezone: Asia/Shanghai [NVS]
Local time: 2026-04-01 18:30:45 CST (Wednesday)
Time sync: synced
NTP server: ntp.ntsc.ac.cn
Last synced: 2026-04-01 18:00:12
```
### `set_timezone` 联动更新
- 设置时区后,如果检测到系统时间仍为 1970未同步自动触发一次 HTTP 时间获取
- 确保用户设置时区后立刻看到正确的本地时间
### 改动文件
| 文件 | 操作 |
|------|------|
| `main/time_sync/time_sync.h` | 增加 `time_sync_get_last_synced()``time_sync_set_server()` |
| `main/time_sync/time_sync.c` | 记录上次同步时间戳,支持自定义 NTP 服务器 |
| `main/cli/serial_cli.c` | 将 `timezone_show` 改为 `ntp_status`,新增 `ntp_sync``ntp_set` 命令 |
| `main/tools/tool_set_timezone.c` | 设置时区后检测时间有效性,必要时触发 HTTP 时间同步 |
---
## 讨论:时区设置功能
**日期**2026-04-01
@@ -118,7 +365,7 @@ timezone_show # 显示当前时区配置和本地时间
| `main/tools/tool_set_timezone.h` | **新建** |
| `main/tools/tool_set_timezone.c` | **新建** |
| `main/tools/tool_registry.c` | include 新头文件 + 注册工具 |
| `main/cli/serial_cli.c` | 添加 `set_timezone` / `timezone_show` 命令 |
| `main/cli/serial_cli.c` | 添加 `set_timezone` / `ntp_status` / `ntp_sync` / `ntp_set` 命令 |
| `main/CMakeLists.txt` | 添加 `tool_set_timezone.c` 到 SRCS |
### 支持的时区格式