Files
ESP32_COMM/main/ota/ota_coordinator.h
T
小龙虾 1491e9a3de refactor(main): 移除ota_mutex.h全局变量并重构ESP32 OTA互斥锁管理
- 将全局变量g_esp32OtaMutex和g_esp32OtaOccupied内聚到Esp32OtaCore类中
- 通过Esp32OtaCore::initMutex()方法初始化互斥锁替代原main.cpp中的全局初始化
- 重构HealthMonitor中WiFi连接状态管理,推送连接状态到本地成员而非直接查询
- 优化OtaCoordinator中const方法的线程安全实现,移除不必要的const_cast转换
- 封装OtaRelay的队列访问器,提供getDispatchQueue()和getResultQueue()方法
- 解决BLE与WiFi之间的横向耦合问题,通过OtaCoordinator进行射频共存管理
- 移除transport/spi_master.cpp中的CANFD接口实现到独立的canfd_bridge模块
- 优化BLE OTA中ESP32会话的幂等性和新会话处理逻辑

BREAKING CHANGE: 移除了ota_mutex.h全局头文件,相关功能迁移至Esp32OtaCore类中管理
2026-06-22 01:53:16 +08:00

376 lines
14 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"
#include "storage/gd32_flash_store.h"
#include "relay/ota_relay.h" // IRelayListener 接口定义(H4 解耦)
// ============================================================
// 系统级 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;
// ============================================================
// IBleCoexListener - BLE 射频共存通知接口(解决 L1 横向耦合)
// ============================================================
// WifiOta 实现此接口并注册到 OtaCoordinator,
// BLE OTA 开始/结束时通过协调器通知 WiFi 层,
// 消除 ble_ota → wifi_ota 的直接依赖。
/**
* @brief BLE 射频共存监听器接口。
* BLE OTA 开始/结束时,协调器通过此接口通知 WiFi 层暂停/恢复射频活动。
*/
class IBleCoexListener {
public:
virtual ~IBleCoexListener() = default;
/** @brief BLE OTA 开始,暂停 WiFi RSSI 轮询等射频活动。 */
virtual void onBleOtaStart() = 0;
/** @brief BLE OTA 结束,恢复 WiFi 射频活动。 */
virtual void onBleOtaEnd() = 0;
};
/**
* @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 IRelayListener {
public:
/**
* @brief 获取 OtaCoordinator 单例实例。
* @return 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 存储操作(传输层通过协调器操作 Flash,解决 H3 绕过问题)──
/**
* @brief 向 GD32 Flash 分区写入固件数据块。
* 传输层(WiFi/BLE)通过此接口写入,不再直接调用 Gd32FlashStore。
* @param offset 写入偏移。
* @param data 数据缓冲区。
* @param len 数据长度。
* @return ESP_OK 成功。
*/
esp_err_t writeGd32Chunk(uint32_t offset, const uint8_t *data, size_t len);
/**
* @brief 保存 GD32 OTA 元数据。
* @param meta 元数据结构体。
* @return ESP_OK 成功。
*/
esp_err_t saveGd32Meta(const ota_meta_t *meta);
/**
* @brief 加载 GD32 OTA 元数据。
* @param meta 输出元数据结构体。
* @return ESP_OK 成功。
*/
esp_err_t loadGd32Meta(ota_meta_t *meta);
/**
* @brief 更新 GD32 OTA 元数据的进度。
* @param stored_size 已存储大小。
* @param last_seq 最后包序号。
* @return ESP_OK 成功。
*/
esp_err_t updateGd32Meta(uint32_t stored_size, uint32_t last_seq);
/**
* @brief 清除 GD32 OTA 元数据。
* @return ESP_OK 成功。
*/
esp_err_t clearGd32Meta();
/**
* @brief 校验前端 SHA-256 与设备端固件是否一致。
* @param frontend_sha32 前端 32 字节 SHA。
* @param out_device_sha 输出设备端 32 字节 SHA(可为 nullptr)。
* @return ESP_OK 匹配,ESP_ERR_INVALID_CRC 不匹配。
*/
esp_err_t verifyGd32Firmware(const uint8_t frontend_sha32[32],
uint8_t out_device_sha[32]);
/**
* @brief 计算 GD32 固件 SHA-256 并写入元数据。
* @return ESP_OK 成功。
*/
esp_err_t finalizeGd32Checksum();
/**
* @brief 擦除 GD32 OTA 分区。
* @param size 擦除大小。
* @return ESP_OK 成功。
*/
esp_err_t eraseGd32Partition(uint32_t size);
// ── 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 状态。
* @return 当前 ota_system_state_t 状态值。
*/
ota_system_state_t getSystemState() const;
/**
* @brief 检查系统是否空闲(可接受新的 OTA 请求)。
* @return true 系统空闲(IDLE/STORED/COMPLETE)。
*/
bool isIdle() const;
/**
* @brief 检查是否有 GD32 相关操作正在进行。
* @return true 表示 STORING 或 RELAYING。
*/
bool isGd32Busy() const;
/**
* @brief 检查是否有 ESP32 OTA 正在进行。
* @return true 表示 RECEIVING/FINALIZING/REBOOTING。
*/
bool isEsp32Busy() const;
/**
* @brief 获取系统状态的可读字符串描述。
* @return 状态名称字符串(如 "IDLE"、"GD32_STORING" 等)。
*/
const char* getSystemStateString() const;
// ── GD32 状态(供 status API 使用,委托给 OtaRelay 查询)──
/**
* @brief 获取 GD32 APP 存活状态。
* @return true 表示 GD32 APP 存活。
*/
bool getGd32AppAlive() const;
/**
* @brief GD32 固件存储完成标志(供状态查询)。
* @return true 表示固件已完整存储。
*/
bool isGd32StorageDone() const;
// ── 完全复位 ──
/**
* @brief 完全复位协调器和所有子系统状态。
*/
void reset();
// ── ESP32 OTA finalize 错误分类(BLE/WiFi 共用)──
/**
* @brief 把 finalize 返回的 esp_err_t 映射为前端可读的事件码。
* 统一 BLE/WiFi 两端语义,便于日志/状态一致。
* @param err finalize 返回的 esp_err_t。
* @return 0=成功;1=SHA 不匹配;2=其他失败。
*/
static int esp32FinalizeErrCategory(esp_err_t err);
// ── IRelayListener 接口实现(H4: OtaRelay 通过接口回调通知协调器)──
/**
* @brief IRelayListener 回调:中继已开始。
* 由 OtaRelay task 在 forwardFirmware() 前通过 s_relayListener 调用。
*/
void onRelayStarted() override;
/**
* @brief IRelayListener 回调:中继已完成。
* 由 OtaRelay task 在 forwardFirmware() 返回后通过 s_relayListener 调用。
* @param success true 成功,false 失败。
*/
void onRelayComplete(bool success) override;
// ── BLE 射频共存管理(L1: 替代 ble_ota → wifi_ota 直接调用)──
/**
* @brief 注册 BLE 共存监听器(由 main 在初始化时调用)。
* @param listener WifiOta 实现的监听器指针,nullptr 取消注册。
*/
void setBleCoexListener(IBleCoexListener *listener);
/**
* @brief BLE OTA 开始时调用,通知 WiFi 层暂停 RSSI 轮询。
*/
void notifyBleCoexStart();
/**
* @brief BLE OTA 结束时调用,通知 WiFi 层恢复射频活动。
*/
void notifyBleCoexEnd();
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() const;
void unlock(bool handle) const;
// ★ 卡死自愈检查:若忙状态停留超过 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;
// 互斥锁(M1/L3: mutable 允许在 const 方法中加锁,消除 const_cast)
mutable SemaphoreHandle_t m_mutex = nullptr;
// BLE 共存监听器(L1: 由 WifiOta 实现并注册)
IBleCoexListener *m_bleCoexListener = 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