Files
ESP32_COMM/main/ota/ota_coordinator.h
T
小龙虾 7feef70750 feat(ota): 统一BLE/WiFi OTA协调器接口并优化并发控制
- 添加 beginEsp32OtaWithRetry 统一重试逻辑,防止锁残留导致的并发问题
- 实现 finalizeForReboot 和 restartEsp32 统一封装收尾重启流程
- 修复BLE/WiFi双通道并发OTA的安全隐患,增强系统状态检查

refactor(ota): 优化SHA计算内存管理

- 使用静态缓冲区替代动态分配,减少内存碎片
- 重构OTA协调器状态机,增强错误处理机制

perf(monitor): 增强堆内存监控功能

- 添加最大空闲块大小监控,提供更详细的内存状态信息
- 更新健康监测算法,提高内存异常检测准确性
2026-06-19 03:02:33 +08:00

251 lines
9.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* ota_coordinator.h - 全局 OTA 协调器
*
* OtaCoordinator 是系统级 OTA 状态的唯一权威来源,负责:
* 1. 串行化 GD32 中继与 ESP32 OTA 操作(同一时刻只能进行一种)
* 2. 为传输层(WiFi/BLE)提供统一的 OTA 操作入口
* 3. 通过 FreeRTOS 队列向 OtaRelay task 投递中继命令
* 4. 向上层(web UI / status API)暴露系统级状态
*
* 线程模型:
* - 状态读写由内部 mutex 保护
* - begin/complete 接口可在任意任务中调用
* - triggerGd32Relay() 向 OtaRelay task 的 dispatchQueue 发送消息
*/
#ifndef OTA_COORDINATOR_H
#define OTA_COORDINATOR_H
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_err.h"
// ============================================================
// 系统级 OTA 状态枚举
// ============================================================
typedef enum {
OTA_SYS_IDLE = 0,
OTA_SYS_GD32_STORING, // 正在从传输层接收 GD32 固件
OTA_SYS_GD32_STORED, // GD32 固件已完整存储,等待中继触发
OTA_SYS_GD32_RELAYING, // 正在通过 SPI 向 GD32 中继固件
OTA_SYS_GD32_COMPLETE, // GD32 OTA 完成(APP 已存活确认)
OTA_SYS_ESP32_RECEIVING, // 正在接收 ESP32 自身固件
OTA_SYS_ESP32_FINALIZING, // 正在收尾 ESP32 OTA(SHA校验 + end + set_boot)
OTA_SYS_ESP32_REBOOTING, // 即将重启 ESP32
OTA_SYS_ERROR // 错误状态
} ota_system_state_t;
/**
* @brief ESP32 OTA 收尾结果(由 finalizeForReboot 返回)。
* err 为 ESP_OK 时 device_sha_hex 有效;SHA 不匹配时也填充 device_sha_hex 供前端核对。
*/
typedef struct {
esp_err_t err; ///< ESP_OK / ESP_ERR_INVALID_VERSION(SHA不匹配) / 其他
char device_sha_hex[65]; ///< 设备端 SHA hex(成功或 SHA 不匹配时填充)
} Esp32FinalizeResult;
/**
* @brief 全局 OTA 协调器(单例)
*
* 提供系统级 OTA 状态管理与操作串行化。
* 传输层(WiFi/BLE)通过本协调器发起所有 OTA 操作,
* 不再直接调用 OtaRelay / Esp32OtaCore 的底层接口。
*/
class OtaCoordinator {
public:
/**
* @brief 获取 OtaCoordinator 单例实例。
*/
static OtaCoordinator& instance();
// ── 初始化 ──
/**
* @brief 初始化协调器(创建 mutex 和队列)。
* 在 OtaRelay task 创建前调用。
*/
void init();
// ── GD32 存储阶段(传输层 → Flash)──
/**
* @brief 传输层开始接收 GD32 固件前调用。
* 若系统正忙(ESP32 OTA 进行中或 GD32 中继中),返回失败。
* @param total_size 固件总字节数。
* @return ESP_OK 成功,ESP_ERR_INVALID_STATE 系统忙。
*/
esp_err_t beginGd32Storage(uint32_t total_size);
/**
* @brief GD32 固件接收完毕时调用。
* 更新系统状态为 OTA_SYS_GD32_STORED。
*/
void completeGd32Storage();
// ── GD32 中继阶段 ──
/**
* @brief 触发 GD32 中继转发。
* 向 OtaRelay task 的 dispatchQueue 发送启动消息。
* @return ESP_OK 消息已投递,ESP_ERR_INVALID_STATE 不允许触发。
*/
esp_err_t triggerGd32Relay();
/**
* @brief OtaRelay task 回调:中继已开始。
* 由 OtaRelay 在开始 forwardFirmware() 时调用。
*/
void notifyRelayStarted();
/**
* @brief OtaRelay task 回调:中继已完成。
* 由 OtaRelay 在 forwardFirmware() 返回后调用。
* @param success true 成功,false 失败。
*/
void notifyRelayComplete(bool success);
// ── ESP32 OTA 阶段 ──
/**
* @brief ESP32 OTA 开始接收固件。
* 内部委托 Esp32OtaCore::begin()。
* @param total_size 固件总字节数。
* @return ESP_OK 成功,ESP_ERR_INVALID_STATE 系统忙或通道被占用。
*/
esp_err_t beginEsp32Ota(uint32_t total_size);
/**
* @brief 发起 ESP32 OTA,若被 ESP_ERR_INVALID_STATE 拒绝则 forceCleanup 后重试一次。
* 封装 BLE/WiFi 两端完全相同的重试模式,调用方只需调一次。
* @param total_size 固件总字节数。
* @return ESP_OK 成功;其他 = 协调器拒绝或 Esp32OtaCore::begin 失败。
*/
esp_err_t beginEsp32OtaWithRetry(uint32_t total_size);
/**
* @brief ESP32 OTA 固件接收完毕,准备收尾。
* 更新系统状态为 OTA_SYS_ESP32_FINALIZING。
*/
void notifyEsp32ReceiveComplete();
/**
* @brief ESP32 OTA 收尾完成(finalize 成功)。
*/
void notifyEsp32FinalizeComplete();
/**
* @brief ESP32 即将重启。
*/
void notifyEsp32Rebooting();
/**
* @brief ESP32 OTA 收尾(统一封装 BLE/WiFi 两端重复的收尾序列)。
* 步骤:notifyEsp32ReceiveComplete → Esp32OtaCore::finalize(reboot=false)
* → 成功则 notifyEsp32FinalizeComplete。
* 不重启;调用方负责把 device_sha_hex 上报前端后再调 restartEsp32()。
* @param frontend_sha_hex 前端 64 字符 hex SHA-256,nullptr 表示跳过比对。
* @return Esp32FinalizeResult,err 为 ESP_OK/ESP_ERR_INVALID_VERSION/其他。
*/
Esp32FinalizeResult finalizeForReboot(const char *frontend_sha_hex);
/**
* @brief 重启 ESP32(统一封装 BLE/WiFi 两端重复的重启序列)。
* 步骤:notifyEsp32Rebooting → vTaskDelay(500ms) → esp_restart()。
* 应在 finalizeForReboot 成功并完成前端上报后调用。不会返回。
*/
void restartEsp32();
// ── 中止 ──
/**
* @brief 中止当前 OTA 操作(GD32 或 ESP32)。
* 内部调用对应子系统的 abort/reset。
*/
void abortCurrent();
// ── 状态查询 ──
/**
* @brief 获取当前系统级 OTA 状态。
*/
ota_system_state_t getSystemState() const;
/**
* @brief 检查系统是否空闲(可接受新的 OTA 请求)。
*/
bool isIdle() const;
/**
* @brief 检查是否有 GD32 相关操作正在进行。
*/
bool isGd32Busy() const;
/**
* @brief 检查是否有 ESP32 OTA 正在进行。
*/
bool isEsp32Busy() const;
/**
* @brief 获取系统状态的可读字符串描述。
*/
const char* getSystemStateString() const;
// ── GD32 状态(供 status API 使用,委托给 OtaRelay 查询)──
/**
* @brief 获取 GD32 APP 存活状态。
*/
bool getGd32AppAlive() const;
/**
* @brief GD32 固件存储完成标志(供状态查询)。
*/
bool isGd32StorageDone() const;
// ── 完全复位 ──
/**
* @brief 完全复位协调器和所有子系统状态。
*/
void reset();
// ── ESP32 OTA finalize 错误分类(BLE/WiFi 共用)──
/**
* @brief 把 finalize 返回的 esp_err_t 映射为前端可读的事件码。
* 统一 BLE/WiFi 两端语义,便于日志/状态一致。
* @return 0=成功;1=SHA 不匹配;2=其他失败。
*/
static int esp32FinalizeErrCategory(esp_err_t err);
private:
OtaCoordinator() = default;
~OtaCoordinator() = default;
OtaCoordinator(const OtaCoordinator&) = delete;
OtaCoordinator& operator=(const OtaCoordinator&) = delete;
// 线程安全辅助
// ★ lock() 返回是否成功 take;unlock(handle) 据此决定是否 give。
// 旧实现用共享 bool m_locked 记录 take 结果,多任务并发 lock() 时
// 超时方会把 m_locked 置 false,覆盖持锁方的 true,导致 unlock() 跳过 give,
// mutex 永久死锁 → 后续 take 超时触发 vTaskPriorityDisinherit assert → PANIC。
bool lock();
void unlock(bool handle);
// ★ 卡死自愈检查:若忙状态停留超过 watchdog 超时,强制转为 ERROR。
// 用于在中继 task 因任何原因(崩溃/SPI 死锁/看门狗复位残留)未能回调
// notifyRelayComplete/notifyEsp32*Complete 时,让协调器自动脱离卡死状态。
// 必须在已持锁状态下调用。
void watchdogCheckLocked();
// 系统状态
ota_system_state_t m_sysState = OTA_SYS_IDLE;
// 进入当前忙状态的时刻(tick)。IDLE/STORED/COMPLETE/ERROR 时为 0。
TickType_t m_busySinceTick = 0;
// GD32 存储跟踪
bool m_gd32StorageDone = false;
// 互斥锁
SemaphoreHandle_t m_mutex = nullptr;
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(100);
// ★ 忙状态 watchdog 超时。GD32 中继理论最大耗时 ≈ 60s(全局超时) + 15s(APP存活轮询) + 1s(bootloader) ≈ 76s,
// 取 90 s,正常流程远不会触及,仅在 task 卡死时触发自愈。
static constexpr TickType_t kBusyWatchdogTicks = pdMS_TO_TICKS(90U * 1000U);
};
#endif // OTA_COORDINATOR_H