feat(ota): 引入OTA协调器和事件驱动状态机重构
- 添加OtaCoordinator单例模式,提供9状态系统级OTA协调功能 - 重构OtaRelay为事件驱动状态机,从4阶段扩展为6阶段状态机 - 实现系统级状态管理:OTA_SYS_IDLE/GD32_STORING/GD32_RELAYING等9个状态 - 传输层通过OtaCoordinator统一入口管理ESP32/GD32 OTA操作序列化 fix(protocol): 优化协议层安全配置和参数管理 - Scramble常量改为运行时随机生成,提升安全性 - GD32芯片UID从握手包动态获取,移除编译期常量 - 添加运行时SCRAMBLE_CONST参数说明 refactor(esp32_ota): 优化ESP32 OTA分区擦除策略 - BLE OTA采用实际固件大小而非分区全尺寸进行擦除 - 避免全分区擦除导致的Task Watchdog超时问题 - 擦除大小对齐到64KB块边界确保尾部数据干净 feat(transport): 增强SPI传输层功能和可靠性 - 添加Flash元数据记录(total_size/stored_size/last_seq/checksum) - 支持中继恢复判断和完整性校验 - 添加ACK错误码支持(0x04=CRC错误) feat(ble): 实现BLE传输层乱序重排和预检功能 - 添加32槽reorder buffer处理窗口内乱序包 - 新增0xF9命令支持仅SHA校验不入库的预检模式 - 完善BLE OTA收尾流程和状态跟踪 docs(readme): 更新架构文档和API说明 - 详细描述OtaCoordinator系统级状态流转 - 更新OTA流程状态机图表和说明 - 补充新的控制命令和配置参数说明
This commit is contained in:
@@ -106,10 +106,15 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// ★ 必须传分区大小,而非固件实际大小
|
||||
// esp_ota_begin 用 size 参数决定擦除多少 flash 扇区
|
||||
// 传固件大小只擦部分扇区,未擦除区域可能残留脏数据,导致 esp_ota_end 校验失败
|
||||
esp_err_t err = esp_ota_begin(m_partition, m_partition->size, &m_handle);
|
||||
// BLE OTA 修复:传固件实际大小而非分区全尺寸,避免全分区擦除导致
|
||||
// 长时间阻塞触发 Task Watchdog(ESP32-C3 单核 5s 超时)。
|
||||
// esp_ota_begin 按 image_size 擦除;esp_ota_end 仅校验已写数据,
|
||||
// 未擦除残留扇区不影响校验。size 对齐到 64KB 块边界确保尾部干净。
|
||||
size_t erase_size = (total_size + 0xFFFF) & ~0xFFFFULL;
|
||||
if (erase_size > m_partition->size) {
|
||||
erase_size = m_partition->size;
|
||||
}
|
||||
esp_err_t err = esp_ota_begin(m_partition, erase_size, &m_handle);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR(TAG, "esp_ota_begin 失败: %s", esp_err_to_name(err));
|
||||
m_partition = nullptr;
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* ota_coordinator.cpp - 全局 OTA 协调器实现
|
||||
*
|
||||
* OtaCoordinator 是系统级 OTA 状态的唯一权威来源。
|
||||
* 传输层(WiFi/BLE)通过本协调器发起所有 OTA 操作。
|
||||
*/
|
||||
#include "ota_coordinator.h"
|
||||
#include "esp32_ota_core.h"
|
||||
#include "relay/ota_relay.h"
|
||||
#include "monitor/debug_log.h"
|
||||
|
||||
#define TAG "OTA_COORD"
|
||||
|
||||
// ============================================================
|
||||
// 单例
|
||||
// ============================================================
|
||||
OtaCoordinator& OtaCoordinator::instance()
|
||||
{
|
||||
static OtaCoordinator inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 线程安全辅助
|
||||
// ============================================================
|
||||
void OtaCoordinator::lock()
|
||||
{
|
||||
// ★ 修复:FreeRTOS mutex (xSemaphoreCreateMutex) 只能由 take 的 task give。
|
||||
// 超时后必须在 unlock() 中跳过 give,避免 xTaskPriorityDisinherit assert。
|
||||
// 此处假设 OtaCoordinator 内部不嵌套 lock(全局已确认:无嵌套调用)。
|
||||
m_locked = false;
|
||||
if (m_mutex) {
|
||||
m_locked = (xSemaphoreTake(m_mutex, kLockTimeout) == pdTRUE);
|
||||
if (!m_locked) {
|
||||
DEBUG_WARN(TAG, "lock() 超时 (%u ms),本次状态变更被跳过",
|
||||
(unsigned)pdTICKS_TO_MS(kLockTimeout));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OtaCoordinator::unlock()
|
||||
{
|
||||
// ★ 修复:仅在 lock() 成功 take 时才 give。
|
||||
// 错误的 task 给同一 mutex give 会触发 xTaskPriorityDisinherit assert → PANIC。
|
||||
if (m_locked && m_mutex) {
|
||||
xSemaphoreGive(m_mutex);
|
||||
m_locked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 卡死自愈检查(必须在持锁状态下调用)
|
||||
// ============================================================
|
||||
void OtaCoordinator::watchdogCheckLocked()
|
||||
{
|
||||
// 仅对"忙"状态(可能卡死的)做检查
|
||||
switch (m_sysState) {
|
||||
case OTA_SYS_GD32_STORING:
|
||||
case OTA_SYS_GD32_RELAYING:
|
||||
case OTA_SYS_ESP32_RECEIVING:
|
||||
case OTA_SYS_ESP32_FINALIZING:
|
||||
case OTA_SYS_ESP32_REBOOTING:
|
||||
break;
|
||||
default:
|
||||
return; // IDLE/STORED/COMPLETE/ERROR 无需检查
|
||||
}
|
||||
|
||||
if (m_busySinceTick == 0) {
|
||||
// 未记录起始时刻(理论上不应发生),保守不触发
|
||||
return;
|
||||
}
|
||||
|
||||
TickType_t elapsed = xTaskGetTickCount() - m_busySinceTick;
|
||||
if (elapsed < kBusyWatchdogTicks) {
|
||||
return; // 未超时
|
||||
}
|
||||
|
||||
// ★ 超时自愈:强制转 ERROR。
|
||||
// ERROR 不在任何 beginXxx() 的拒绝列表中,下次请求会自动从 ERROR 恢复。
|
||||
// 清理由各子系统自行负责(abortCurrent 中会按 ERROR 不做实际清理,仅复位状态)。
|
||||
DEBUG_ERR(TAG, "忙状态 %d 卡死超过 %" PRIu32 " ms,强制自愈 → ERROR",
|
||||
(int)m_sysState, (uint32_t)pdTICKS_TO_MS(kBusyWatchdogTicks));
|
||||
m_sysState = OTA_SYS_ERROR;
|
||||
m_busySinceTick = 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 初始化
|
||||
// ============================================================
|
||||
void OtaCoordinator::init()
|
||||
{
|
||||
if (!m_mutex) {
|
||||
m_mutex = xSemaphoreCreateMutex();
|
||||
if (m_mutex) {
|
||||
DEBUG_INFO(TAG, "OTA 全局协调器已初始化");
|
||||
} else {
|
||||
DEBUG_ERR(TAG, "协调器 mutex 创建失败");
|
||||
}
|
||||
}
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_gd32StorageDone = false;
|
||||
m_busySinceTick = 0;
|
||||
|
||||
// ★ 重启恢复:如果 NVS 里有完整固件元数据,开机时立即进入 STORED 状态,
|
||||
// 否则用户重启后看到的状态是 IDLE 但 NVS 里其实有未中继的固件,前后不一致。
|
||||
ota_meta_t boot_meta;
|
||||
if (OtaRelay::metaLoad(&boot_meta) == ESP_OK &&
|
||||
boot_meta.total_size > 0 &&
|
||||
boot_meta.stored_size >= boot_meta.total_size) {
|
||||
m_sysState = OTA_SYS_GD32_STORED;
|
||||
m_gd32StorageDone = true;
|
||||
DEBUG_INFO(TAG, "启动恢复: NVS 检测到完整 GD32 固件 (%" PRIu32 " 字节), → GD32_STORED",
|
||||
boot_meta.total_size);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GD32 存储阶段
|
||||
// ============================================================
|
||||
esp_err_t OtaCoordinator::beginGd32Storage(uint32_t total_size)
|
||||
{
|
||||
lock();
|
||||
|
||||
// ★ 先做卡死自愈检查:若之前忙状态卡死,自动转 ERROR 继续后续流程
|
||||
watchdogCheckLocked();
|
||||
|
||||
// 系统忙检查:仅拒绝真正"在传输"的活跃状态
|
||||
// STORED / COMPLETE 是"待中继/已中继"的非活跃状态 → 允许新存储覆盖
|
||||
// ★ 修复:之前把所有非 IDLE 都当忙,导致"Flash 缓存里有固件但没在传输"时无法上传新固件
|
||||
if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
||||
m_sysState == OTA_SYS_ESP32_REBOOTING ||
|
||||
m_sysState == OTA_SYS_GD32_STORING ||
|
||||
m_sysState == OTA_SYS_GD32_RELAYING) {
|
||||
DEBUG_WARN(TAG, "系统忙 (state=%d),拒绝 GD32 存储请求", (int)m_sysState);
|
||||
unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 如果之前已有存储但未中继(STORED)或刚中继完(COMPLETE),先清理再开始新存储
|
||||
if (m_sysState == OTA_SYS_GD32_STORED || m_sysState == OTA_SYS_GD32_COMPLETE) {
|
||||
DEBUG_INFO(TAG, "清理旧 GD32 存储状态,开始新存储 (size=%" PRIu32 ")", total_size);
|
||||
OtaRelay::metaClear();
|
||||
}
|
||||
|
||||
m_sysState = OTA_SYS_GD32_STORING;
|
||||
m_gd32StorageDone = false;
|
||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录忙状态起始时刻
|
||||
|
||||
DEBUG_INFO(TAG, "GD32 固件存储开始: %" PRIu32 " 字节, 系统状态 → GD32_STORING", total_size);
|
||||
unlock();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void OtaCoordinator::completeGd32Storage()
|
||||
{
|
||||
lock();
|
||||
if (m_sysState == OTA_SYS_GD32_STORING) {
|
||||
m_sysState = OTA_SYS_GD32_STORED;
|
||||
m_gd32StorageDone = true;
|
||||
m_busySinceTick = 0; // ★ STORED 是非忙状态
|
||||
DEBUG_INFO(TAG, "GD32 固件存储完成, 系统状态 → GD32_STORED");
|
||||
} else {
|
||||
DEBUG_WARN(TAG, "completeGd32Storage 在非预期状态: %d", (int)m_sysState);
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GD32 中继阶段
|
||||
// ============================================================
|
||||
esp_err_t OtaCoordinator::triggerGd32Relay()
|
||||
{
|
||||
lock();
|
||||
|
||||
// ★ 卡死自愈检查
|
||||
watchdogCheckLocked();
|
||||
|
||||
// 校验:必须处于"已存储"或"已中继完成"状态
|
||||
// ★ 修复:COMPLETE(旧固件已成功推给 GD32,NVS 元数据已被 doFinishAndVerify 清掉,
|
||||
// OtaRelay 状态为 DONE)也能触发——但需要先回滚到 STORED 以保证 m_busySinceTick 一致。
|
||||
// 通常用户应先上传新固件再触发中继,COMPLETE 时元数据已被清,metaLoad 会返回 stored=0
|
||||
// 并在 188 行被拒绝,自然不会走到这里。
|
||||
if (m_sysState != OTA_SYS_GD32_STORED && m_sysState != OTA_SYS_IDLE) {
|
||||
if (m_sysState == OTA_SYS_GD32_STORING) {
|
||||
DEBUG_WARN(TAG, "GD32 正在存储中,拒绝触发中继");
|
||||
unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
||||
DEBUG_WARN(TAG, "GD32 中继已在进行中");
|
||||
unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
||||
m_sysState == OTA_SYS_ESP32_REBOOTING) {
|
||||
DEBUG_WARN(TAG, "ESP32 OTA 进行中,拒绝中继触发");
|
||||
unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证固件是否确实已存储(通过 NVS 元数据)
|
||||
ota_meta_t meta;
|
||||
if (OtaRelay::metaLoad(&meta) != ESP_OK ||
|
||||
meta.total_size == 0 ||
|
||||
meta.stored_size < meta.total_size) {
|
||||
DEBUG_WARN(TAG, "GD32 固件未完整存储,拒绝触发中继");
|
||||
unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 检查 OtaRelay 是否空闲
|
||||
if (OtaRelay::getState() != RELAY_IDLE &&
|
||||
OtaRelay::getState() != RELAY_DONE &&
|
||||
OtaRelay::getState() != RELAY_ERROR) {
|
||||
DEBUG_WARN(TAG, "OtaRelay 正忙 (state=%d),拒绝触发", (int)OtaRelay::getState());
|
||||
unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 向 OtaRelay task 发送启动消息
|
||||
if (!OtaRelay::dispatchQueue) {
|
||||
DEBUG_ERR(TAG, "OtaRelay dispatchQueue 未就绪");
|
||||
unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
m_sysState = OTA_SYS_GD32_RELAYING;
|
||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录忙状态起始时刻
|
||||
DEBUG_INFO(TAG, "触发 GD32 中继, 系统状态 → GD32_RELAYING");
|
||||
|
||||
uint32_t cmd = 1;
|
||||
BaseType_t ret = xQueueSend(OtaRelay::dispatchQueue, &cmd, pdMS_TO_TICKS(200));
|
||||
if (ret != pdTRUE) {
|
||||
DEBUG_ERR(TAG, "向 OtaRelay 发送启动消息失败");
|
||||
m_sysState = OTA_SYS_GD32_STORED;
|
||||
m_busySinceTick = 0; // 回退到 STORED(非忙)
|
||||
unlock();
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
unlock();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void OtaCoordinator::notifyRelayStarted()
|
||||
{
|
||||
lock();
|
||||
if (m_sysState != OTA_SYS_GD32_RELAYING) {
|
||||
m_sysState = OTA_SYS_GD32_RELAYING;
|
||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
|
||||
DEBUG_INFO(TAG, "中继已开始, 系统状态 → GD32_RELAYING");
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
void OtaCoordinator::notifyRelayComplete(bool success)
|
||||
{
|
||||
lock();
|
||||
if (success) {
|
||||
m_sysState = OTA_SYS_GD32_COMPLETE;
|
||||
m_gd32StorageDone = false;
|
||||
m_busySinceTick = 0; // ★ COMPLETE 非忙
|
||||
DEBUG_INFO(TAG, "GD32 中继完成, 系统状态 → GD32_COMPLETE");
|
||||
} else {
|
||||
m_sysState = OTA_SYS_ERROR;
|
||||
m_busySinceTick = 0; // ★ ERROR 非忙(可被后续请求恢复)
|
||||
DEBUG_ERR(TAG, "GD32 中继失败, 系统状态 → ERROR");
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ESP32 OTA 阶段
|
||||
// ============================================================
|
||||
esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
|
||||
{
|
||||
lock();
|
||||
|
||||
// ★ 卡死自愈检查
|
||||
watchdogCheckLocked();
|
||||
|
||||
// 系统忙检查
|
||||
// ★ 修复:STORING/RELAYING 都拒绝(防止中继中改 gd32_ota 分区被 ESP32 OTA 误触发)。
|
||||
// STORED/COMPLETE 允许——它们是非活跃状态,ESP32 OTA 与 gd32_ota 分区无冲突。
|
||||
if (m_sysState == OTA_SYS_GD32_STORING ||
|
||||
m_sysState == OTA_SYS_GD32_RELAYING) {
|
||||
DEBUG_WARN(TAG, "GD32 正在 %s,拒绝 ESP32 OTA",
|
||||
m_sysState == OTA_SYS_GD32_STORING ? "存储" : "中继");
|
||||
unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 委托 Esp32OtaCore 处理分区/互斥逻辑
|
||||
esp_err_t err = Esp32OtaCore::instance().begin(total_size);
|
||||
if (err == ESP_OK) {
|
||||
m_sysState = OTA_SYS_ESP32_RECEIVING;
|
||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
|
||||
DEBUG_INFO(TAG, "ESP32 OTA 开始: %" PRIu32 " 字节, 系统状态 → ESP32_RECEIVING", total_size);
|
||||
} else {
|
||||
DEBUG_WARN(TAG, "ESP32 OTA begin 失败: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
void OtaCoordinator::notifyEsp32ReceiveComplete()
|
||||
{
|
||||
lock();
|
||||
if (m_sysState == OTA_SYS_ESP32_RECEIVING) {
|
||||
m_sysState = OTA_SYS_ESP32_FINALIZING;
|
||||
DEBUG_INFO(TAG, "ESP32 固件接收完毕, 系统状态 → ESP32_FINALIZING");
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
void OtaCoordinator::notifyEsp32FinalizeComplete()
|
||||
{
|
||||
lock();
|
||||
m_sysState = OTA_SYS_ESP32_REBOOTING;
|
||||
DEBUG_INFO(TAG, "ESP32 OTA 收尾完成, 系统状态 → ESP32_REBOOTING");
|
||||
unlock();
|
||||
}
|
||||
|
||||
void OtaCoordinator::notifyEsp32Rebooting()
|
||||
{
|
||||
lock();
|
||||
m_sysState = OTA_SYS_ESP32_REBOOTING;
|
||||
DEBUG_INFO(TAG, "ESP32 即将重启, 系统状态 → ESP32_REBOOTING");
|
||||
unlock();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 中止
|
||||
// ============================================================
|
||||
void OtaCoordinator::abortCurrent()
|
||||
{
|
||||
lock();
|
||||
DEBUG_INFO(TAG, "中止当前 OTA 操作 (state=%d)", (int)m_sysState);
|
||||
|
||||
if (m_sysState == OTA_SYS_GD32_STORING) {
|
||||
OtaRelay::metaClear();
|
||||
} else if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
||||
OtaRelay::instance().reset();
|
||||
} else if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||
m_sysState == OTA_SYS_ESP32_FINALIZING) {
|
||||
Esp32OtaCore::instance().abort();
|
||||
}
|
||||
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_gd32StorageDone = false;
|
||||
m_busySinceTick = 0; // ★ 离开忙状态
|
||||
DEBUG_INFO(TAG, "已中止, 系统状态 → IDLE");
|
||||
unlock();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 状态查询
|
||||
// ============================================================
|
||||
ota_system_state_t OtaCoordinator::getSystemState() const
|
||||
{
|
||||
// 对于纯读取,短暂加锁保证一致性
|
||||
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
|
||||
self->lock();
|
||||
ota_system_state_t s = m_sysState;
|
||||
self->unlock();
|
||||
return s;
|
||||
}
|
||||
|
||||
bool OtaCoordinator::isIdle() const
|
||||
{
|
||||
return (getSystemState() == OTA_SYS_IDLE ||
|
||||
getSystemState() == OTA_SYS_GD32_STORED ||
|
||||
getSystemState() == OTA_SYS_GD32_COMPLETE);
|
||||
}
|
||||
|
||||
bool OtaCoordinator::isGd32Busy() const
|
||||
{
|
||||
ota_system_state_t s = getSystemState();
|
||||
return (s == OTA_SYS_GD32_STORING ||
|
||||
s == OTA_SYS_GD32_RELAYING);
|
||||
}
|
||||
|
||||
bool OtaCoordinator::isEsp32Busy() const
|
||||
{
|
||||
ota_system_state_t s = getSystemState();
|
||||
return (s == OTA_SYS_ESP32_RECEIVING ||
|
||||
s == OTA_SYS_ESP32_FINALIZING ||
|
||||
s == OTA_SYS_ESP32_REBOOTING);
|
||||
}
|
||||
|
||||
const char* OtaCoordinator::getSystemStateString() const
|
||||
{
|
||||
switch (getSystemState()) {
|
||||
case OTA_SYS_IDLE: return "IDLE";
|
||||
case OTA_SYS_GD32_STORING: return "GD32_STORING";
|
||||
case OTA_SYS_GD32_STORED: return "GD32_STORED";
|
||||
case OTA_SYS_GD32_RELAYING: return "GD32_RELAYING";
|
||||
case OTA_SYS_GD32_COMPLETE: return "GD32_COMPLETE";
|
||||
case OTA_SYS_ESP32_RECEIVING: return "ESP32_RECEIVING";
|
||||
case OTA_SYS_ESP32_FINALIZING: return "ESP32_FINALIZING";
|
||||
case OTA_SYS_ESP32_REBOOTING: return "ESP32_REBOOTING";
|
||||
case OTA_SYS_ERROR: return "ERROR";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
bool OtaCoordinator::getGd32AppAlive() const
|
||||
{
|
||||
return OtaRelay::getGd32AppAlive();
|
||||
}
|
||||
|
||||
bool OtaCoordinator::isGd32StorageDone() const
|
||||
{
|
||||
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
|
||||
self->lock();
|
||||
bool done = m_gd32StorageDone;
|
||||
self->unlock();
|
||||
return done;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 完全复位
|
||||
// ============================================================
|
||||
void OtaCoordinator::reset()
|
||||
{
|
||||
lock();
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_gd32StorageDone = false;
|
||||
m_busySinceTick = 0; // ★ 复位
|
||||
DEBUG_INFO(TAG, "协调器已完全复位");
|
||||
unlock();
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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 全局 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 固件接收完毕,准备收尾。
|
||||
* 更新系统状态为 OTA_SYS_ESP32_FINALIZING。
|
||||
*/
|
||||
void notifyEsp32ReceiveComplete();
|
||||
|
||||
/**
|
||||
* @brief ESP32 OTA 收尾完成(finalize 成功)。
|
||||
*/
|
||||
void notifyEsp32FinalizeComplete();
|
||||
|
||||
/**
|
||||
* @brief ESP32 即将重启。
|
||||
*/
|
||||
void notifyEsp32Rebooting();
|
||||
|
||||
// ── 中止 ──
|
||||
/**
|
||||
* @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();
|
||||
|
||||
private:
|
||||
OtaCoordinator() = default;
|
||||
~OtaCoordinator() = default;
|
||||
OtaCoordinator(const OtaCoordinator&) = delete;
|
||||
OtaCoordinator& operator=(const OtaCoordinator&) = delete;
|
||||
|
||||
// 线程安全辅助
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
// ★ 卡死自愈检查:若忙状态停留超过 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;
|
||||
bool m_locked = false; // ★ 记录最近一次 lock() 是否成功 take;unlock() 据此跳过错误 give
|
||||
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
|
||||
Reference in New Issue
Block a user