127 lines
6.3 KiB
Markdown
127 lines
6.3 KiB
Markdown
# ESP32-C3 OTA 中继固件 — 架构分析报告
|
||||
|
|
|
|||
|
|
## 总体评价
|
|||
|
|
|
|||
|
|
项目核心设计思路**方向正确**:单例模式、协调器串行化、事件驱动状态机、ESP32 OTA 核心复用 — 这些都是优秀的嵌入式架构选择。但在职责分配和模块耦合方面存在显著问题,主要集中在 **OtaRelay 职责过载** 和 **传输层绕过协调器** 两个核心缺陷上。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 高危问题 (4 个)
|
|||
|
|
|
|||
|
|
### H1. `OtaRelay` 职责严重超载(9 项职责)
|
|||
|
|
- **文件**: [ota_relay.h](file:///g:/Robot_v2/DCU/ESP32_COMM/main/relay/ota_relay.h) — 375 行,30+ public 方法
|
|||
|
|
- **问题**: 同时承担:①事件驱动状态机 ②Flash 分区读写 ③NVS 元数据持久化 ④SHA-256 校验 ⑤AES-128-ECB 加密 ⑥CRC32 计算 ⑦协议帧解析 ⑧挑战认证 ⑨GD32 Bootloader 协商
|
|||
|
|
- **影响**: 任何存储/加密层的修改都可能波及状态机逻辑,测试和维护成本高
|
|||
|
|
|
|||
|
|
### H2. `wifi_ota.cpp` 是 God Class(1827 行,8+ 职责)
|
|||
|
|
- **文件**: [wifi_ota.cpp](file:///g:/Robot_v2/DCU/ESP32_COMM/main/server/wifi_ota.cpp)
|
|||
|
|
- **问题**: 同时管理 WiFi STA/AP、HTTP 服务器全部路由、GD32 固件接收+Flash 写入、状态查询 API、RSSI 采集、BLE 共存、日志 HTTP 接口
|
|||
|
|
- **影响**: 修改任何单一功能都需要理解整个文件,回归风险极高
|
|||
|
|
|
|||
|
|
### H3. 传输层绕过协调器直接访问 OtaRelay
|
|||
|
|
- **证据**:
|
|||
|
|
- `wifi_ota.cpp` 直接调用 `OtaRelay::flashWriteChunk()`、`OtaRelay::metaSave()`、`OtaRelay::metaUpdate()`
|
|||
|
|
- `ble_ota.cpp` 直接访问 `OtaRelay::resultQueue`、`OtaRelay::metaLoad()`、`OtaRelay::verifyFrontendSha()`
|
|||
|
|
- **矛盾**: [ota_coordinator.h](file:///g:/Robot_v2/DCU/ESP32_COMM/main/ota/ota_coordinator.h) L52 注释明确要求"传输层不再直接调用 OtaRelay / Esp32OtaCore 的底层接口",但实际被违反
|
|||
|
|
- **影响**: 协调器的"唯一入口"设计意图落空,状态一致性无法保证
|
|||
|
|
|
|||
|
|
### H4. OtaCoordinator ↔ OtaRelay 双向紧耦合
|
|||
|
|
- **证据**: 协调器直接操作 `OtaRelay::dispatchQueue`、`getState()`、`metaLoad()`、`metaClear()`([ota_coordinator.cpp](file:///g:/Robot_v2/DCU/ESP32_COMM/main/ota/ota_coordinator.cpp) 多处);OtaRelay 回调协调器 `notifyRelayStarted/Complete()`
|
|||
|
|
- **影响**: 任一端修改都可能波及另一端,违反依赖倒置原则
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 中危问题 (5 个)
|
|||
|
|
|
|||
|
|
### M1. 全局互斥机制双重
|
|||
|
|
- `ota_mutex.h` 全局锁 + `OtaCoordinator` 状态锁两套并行,`Esp32OtaCore::begin()` 被两层锁保护
|
|||
|
|
- 增加死锁面和调试难度
|
|||
|
|
|
|||
|
|
### M2. HealthMonitor 向上依赖 WifiOta
|
|||
|
|
- [health_monitor.cpp](file:///g:/Robot_v2/DCU/ESP32_COMM/main/monitor/health_monitor.cpp) `#include "server/wifi_ota.h"` — 底层监控模块依赖上层服务器模块
|
|||
|
|
- 违反层次化架构原则
|
|||
|
|
|
|||
|
|
### M3. OtaRelay 大量公开静态成员
|
|||
|
|
- `relayState`、`dispatchQueue`、`resultQueue`、`gd32AppAlive` 等 7 个 public static 成员实质是全局变量
|
|||
|
|
- 虽有 mutex 保护但破坏封装性
|
|||
|
|
|
|||
|
|
### M4. SpiMaster 职责偏多(836 行)
|
|||
|
|
- [spi_master.cpp](file:///g:/Robot_v2/DCU/ESP32_COMM/main/transport/spi_master.cpp) 同时承担 SPI 总线驱动、OTA 帧协议、CANFD 通信、APP Ping 协议解析
|
|||
|
|
- CANFD 与 OTA 帧协议是不同抽象层
|
|||
|
|
|
|||
|
|
### M5. 构建系统无模块隔离
|
|||
|
|
- [main/CMakeLists.txt](file:///g:/Robot_v2/DCU/ESP32_COMM/main/CMakeLists.txt) 所有 10 个 .cpp 文件注册在单一 `idf_component_register` 中
|
|||
|
|
- 任何模块可 `#include` 任何其他模块头文件,无编译期依赖约束
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 低危问题 (3 个)
|
|||
|
|
|
|||
|
|
### L1. BleOta → WifiOta 横向耦合
|
|||
|
|
- BLE 直接调用 `WifiOta::instance().setBleOtaActive()` 管理射频共存,应由协调器管理
|
|||
|
|
|
|||
|
|
### L2. WiFi 密码硬编码
|
|||
|
|
- [app_config.h](file:///g:/Robot_v2/DCU/ESP32_COMM/main/include/app_config.h) 明文写入源码,应使用 NVS 或 Kconfig
|
|||
|
|
|
|||
|
|
### L3. `const_cast` 绕过 const
|
|||
|
|
- [ota_coordinator.cpp](file:///g:/Robot_v2/DCU/ESP32_COMM/main/ota/ota_coordinator.cpp) `getSystemState() const` 中 `const_cast` 获取锁,`m_mutex` 应声明为 `mutable`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 并发安全问题
|
|||
|
|
|
|||
|
|
| 问题 | 位置 | 风险 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `processEvent()` 读 `relayState` 未持锁 | ota_relay.cpp | 状态撕裂读 |
|
|||
|
|
| BLE `m_bleConnected`/`m_connHandle` 无保护 | ble_ota.cpp/h | 并发读写 |
|
|||
|
|
| `DebugLog::log()` 自动初始化非线程安全 | debug_log.cpp | 竞态初始化 |
|
|||
|
|
| `DebugLog::log()` 1.1KB 栈帧 | debug_log.cpp | 4096 栈任务中栈溢出高风险 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 架构亮点(值得保留)
|
|||
|
|
|
|||
|
|
- **协调器模式**:串行化 GD32/ESP32 操作的思路正确
|
|||
|
|
- **事件驱动状态机**:OtaRelay 的状态表设计清晰,7 状态 + 事件驱动模式优于线性流程
|
|||
|
|
- **看门狗自愈**:协调器 90s watchdog 自动脱离卡死状态
|
|||
|
|
- **RAII 守卫**:StateLockGuard 等模式保证锁释放
|
|||
|
|
- **ESP32 OTA 核心复用**:BLE/WiFi 共用 Esp32OtaCore,正确隔离传输层差异
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 改进路线图(按优先级)
|
|||
|
|
|
|||
|
|
### Task 1: 提取 GD32 Flash 存储抽象层 [解决 H1 + H3]
|
|||
|
|
- 创建 `storage/gd32_flash_store.h/.cpp`
|
|||
|
|
- 封装 `flashWriteChunk()`、`flashReadChunk()`、`metaLoad/Save/Update/Clear()`、`computeFlashSha256()`
|
|||
|
|
- 从 OtaRelay 中剥离存储职责
|
|||
|
|
|
|||
|
|
### Task 2: 协调器补齐 GD32 存储 API [解决 H3]
|
|||
|
|
- 在 OtaCoordinator 中新增 `writeGd32Chunk()`、`verifyGd32Firmware()` 等接口
|
|||
|
|
- 传输层(WiFi/BLE)只通过协调器操作,消除绕过
|
|||
|
|
|
|||
|
|
### Task 3: 拆分 wifi_ota.cpp [解决 H2]
|
|||
|
|
- HTTP 路由处理提取为 `server/http_handlers.cpp`
|
|||
|
|
- GD32 固件接收逻辑移入协调器或独立 receiver
|
|||
|
|
|
|||
|
|
### Task 4: 统一互斥机制 [解决 M1]
|
|||
|
|
- 移除 `ota_mutex.h` 全局锁,全部由 OtaCoordinator 管理
|
|||
|
|
|
|||
|
|
### Task 5: 解耦 HealthMonitor [解决 M2]
|
|||
|
|
- 提供 `setWifiConnected(bool)` 推送接口,替代主动查询 WifiOta
|
|||
|
|
|
|||
|
|
### Task 6: 修复并发安全 [解决并发问题]
|
|||
|
|
- `processEvent()` 读 `relayState` 加锁
|
|||
|
|
- BLE 状态变量加 mutex 或 atomic 保护
|
|||
|
|
- `m_mutex` 改为 `mutable`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 被否决的方案
|
|||
|
|
|
|||
|
|
| 方案 | 否决原因 |
|
|||
|
|
|---|---|
|
|||
|
|
| 将 CMakeLists 拆分为多个 ESP-IDF 组件 | 嵌入式资源受限,多组件增加构建复杂度和 Flash 占用,收益有限 |
|
|||
|
|
| 拆分 SpiMaster 为多个类 | SPI 驱动+帧协议+CANFD 在嵌入式场景下紧耦合合理,强行拆分会增加调用开销 |
|
|||
|
|
| 引入接口/抽象基类 | C++ 虚函数在 ESP32-C3 上增加 Flash 和 RAM 开销,当前规模下不值得 |
|