2026-06-17 02:18:42 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* 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"
|
2026-06-19 03:02:33 +08:00
|
|
|
|
#include "esp_system.h"
|
|
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
|
|
|
|
#include "freertos/task.h"
|
2026-06-17 02:18:42 +08:00
|
|
|
|
|
|
|
|
|
|
#define TAG "OTA_COORD"
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 单例
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
OtaCoordinator& OtaCoordinator::instance()
|
|
|
|
|
|
{
|
|
|
|
|
|
static OtaCoordinator inst;
|
|
|
|
|
|
return inst;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 线程安全辅助
|
|
|
|
|
|
// ============================================================
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool OtaCoordinator::lock()
|
2026-06-17 02:18:42 +08:00
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
// ★ 修复: 返回 take 结果,由调用方传回 unlock()。
|
|
|
|
|
|
// 旧实现用成员 bool m_locked 记录 take 结果,多任务并发 lock() 时
|
|
|
|
|
|
// 超时方会把 m_locked 置 false,覆盖持锁方的 true,导致 unlock() 跳过 give,
|
|
|
|
|
|
// mutex 永久死锁 → 后续 take 超时触发 vTaskPriorityDisinherit assert → PANIC。
|
|
|
|
|
|
if (!m_mutex) return false;
|
|
|
|
|
|
bool ok = (xSemaphoreTake(m_mutex, kLockTimeout) == pdTRUE);
|
|
|
|
|
|
if (!ok) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "lock() 超时 (%u ms),本次状态变更被跳过",
|
|
|
|
|
|
(unsigned)pdTICKS_TO_MS(kLockTimeout));
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
2026-06-17 11:52:26 +08:00
|
|
|
|
return ok;
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 11:52:26 +08:00
|
|
|
|
void OtaCoordinator::unlock(bool handle)
|
2026-06-17 02:18:42 +08:00
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
// ★ 修复: 仅在对应的 lock() 成功 take 时才 give。
|
|
|
|
|
|
if (handle && m_mutex) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
xSemaphoreGive(m_mutex);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 卡死自愈检查(必须在持锁状态下调用)
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
2026-06-17 02:18:42 +08:00
|
|
|
|
|
|
|
|
|
|
// ★ 先做卡死自愈检查:若之前忙状态卡死,自动转 ERROR 继续后续流程
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h) watchdogCheckLocked();
|
2026-06-17 02:18:42 +08:00
|
|
|
|
|
|
|
|
|
|
// 系统忙检查:仅拒绝真正"在传输"的活跃状态
|
|
|
|
|
|
// STORED / COMPLETE 是"待中继/已中继"的非活跃状态 → 允许新存储覆盖
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h && (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
2026-06-17 02:18:42 +08:00
|
|
|
|
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
|
|
|
|
|
m_sysState == OTA_SYS_ESP32_REBOOTING ||
|
|
|
|
|
|
m_sysState == OTA_SYS_GD32_STORING ||
|
2026-06-17 11:52:26 +08:00
|
|
|
|
m_sysState == OTA_SYS_GD32_RELAYING)) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
DEBUG_WARN(TAG, "系统忙 (state=%d),拒绝 GD32 存储请求", (int)m_sysState);
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果之前已有存储但未中继(STORED)或刚中继完(COMPLETE),先清理再开始新存储
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h && (m_sysState == OTA_SYS_GD32_STORED || m_sysState == OTA_SYS_GD32_COMPLETE)) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
DEBUG_INFO(TAG, "清理旧 GD32 存储状态,开始新存储 (size=%" PRIu32 ")", total_size);
|
|
|
|
|
|
OtaRelay::metaClear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h) {
|
|
|
|
|
|
m_sysState = OTA_SYS_GD32_STORING;
|
|
|
|
|
|
m_gd32StorageDone = false;
|
|
|
|
|
|
m_busySinceTick = xTaskGetTickCount(); // ★ 记录忙状态起始时刻
|
|
|
|
|
|
DEBUG_INFO(TAG, "GD32 固件存储开始: %" PRIu32 " 字节, 系统状态 → GD32_STORING", total_size);
|
|
|
|
|
|
}
|
|
|
|
|
|
unlock(h);
|
|
|
|
|
|
return h ? ESP_OK : ESP_ERR_INVALID_STATE;
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OtaCoordinator::completeGd32Storage()
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
|
|
|
|
|
if (h && m_sysState == OTA_SYS_GD32_STORING) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
m_sysState = OTA_SYS_GD32_STORED;
|
|
|
|
|
|
m_gd32StorageDone = true;
|
|
|
|
|
|
m_busySinceTick = 0; // ★ STORED 是非忙状态
|
|
|
|
|
|
DEBUG_INFO(TAG, "GD32 固件存储完成, 系统状态 → GD32_STORED");
|
2026-06-17 11:52:26 +08:00
|
|
|
|
} else if (h) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
DEBUG_WARN(TAG, "completeGd32Storage 在非预期状态: %d", (int)m_sysState);
|
|
|
|
|
|
}
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// GD32 中继阶段
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
esp_err_t OtaCoordinator::triggerGd32Relay()
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
2026-06-17 02:18:42 +08:00
|
|
|
|
|
|
|
|
|
|
// ★ 卡死自愈检查
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h) watchdogCheckLocked();
|
2026-06-17 02:18:42 +08:00
|
|
|
|
|
|
|
|
|
|
// 校验:必须处于"已存储"或"已中继完成"状态
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h && m_sysState != OTA_SYS_GD32_STORED && m_sysState != OTA_SYS_IDLE) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
if (m_sysState == OTA_SYS_GD32_STORING) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "GD32 正在存储中,拒绝触发中继");
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "GD32 中继已在进行中");
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
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 进行中,拒绝中继触发");
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证固件是否确实已存储(通过 NVS 元数据)
|
|
|
|
|
|
ota_meta_t meta;
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h && (OtaRelay::metaLoad(&meta) != ESP_OK ||
|
2026-06-17 02:18:42 +08:00
|
|
|
|
meta.total_size == 0 ||
|
2026-06-17 11:52:26 +08:00
|
|
|
|
meta.stored_size < meta.total_size)) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
DEBUG_WARN(TAG, "GD32 固件未完整存储,拒绝触发中继");
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 OtaRelay 是否空闲
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h && OtaRelay::getState() != RELAY_IDLE &&
|
2026-06-17 02:18:42 +08:00
|
|
|
|
OtaRelay::getState() != RELAY_DONE &&
|
|
|
|
|
|
OtaRelay::getState() != RELAY_ERROR) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "OtaRelay 正忙 (state=%d),拒绝触发", (int)OtaRelay::getState());
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 向 OtaRelay task 发送启动消息
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h && !OtaRelay::dispatchQueue) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
DEBUG_ERR(TAG, "OtaRelay dispatchQueue 未就绪");
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
|
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!h) {
|
|
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
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(非忙)
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return ESP_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return ESP_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OtaCoordinator::notifyRelayStarted()
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
|
|
|
|
|
if (h && m_sysState != OTA_SYS_GD32_RELAYING) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
m_sysState = OTA_SYS_GD32_RELAYING;
|
|
|
|
|
|
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
|
|
|
|
|
|
DEBUG_INFO(TAG, "中继已开始, 系统状态 → GD32_RELAYING");
|
|
|
|
|
|
}
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OtaCoordinator::notifyRelayComplete(bool success)
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
|
|
|
|
|
if (h) {
|
|
|
|
|
|
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");
|
|
|
|
|
|
}
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// ESP32 OTA 阶段
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
2026-06-17 02:18:42 +08:00
|
|
|
|
|
|
|
|
|
|
// ★ 卡死自愈检查
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h) watchdogCheckLocked();
|
2026-06-17 02:18:42 +08:00
|
|
|
|
|
2026-06-19 03:02:33 +08:00
|
|
|
|
// 系统忙检查:GD32 传输/中继进行中,或另一通道的 ESP32 OTA 进行中,均拒绝。
|
|
|
|
|
|
// ★ 必须显式拒绝 ESP32_RECEIVING/FINALIZING/REBOOTING:
|
|
|
|
|
|
// 若放行,本调用会落到 Esp32OtaCore::begin 去抢全局互斥失败而返回 INVALID_STATE,
|
|
|
|
|
|
// 随后 beginEsp32OtaWithRetry 的 forceCleanup 会把另一通道的活跃会话
|
|
|
|
|
|
// (esp_ota_abort + 释放互斥)误中止 → 两通道并发写同一 OTA 分区。
|
2026-06-17 11:52:26 +08:00
|
|
|
|
if (h && (m_sysState == OTA_SYS_GD32_STORING ||
|
2026-06-19 03:02:33 +08:00
|
|
|
|
m_sysState == OTA_SYS_GD32_RELAYING ||
|
|
|
|
|
|
m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
|
|
|
|
|
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
|
|
|
|
|
m_sysState == OTA_SYS_ESP32_REBOOTING)) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "系统忙 (state=%d, %s),拒绝 ESP32 OTA",
|
|
|
|
|
|
(int)m_sysState, getSystemStateString());
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 11:52:26 +08:00
|
|
|
|
esp_err_t err = ESP_ERR_INVALID_STATE;
|
|
|
|
|
|
if (h) {
|
|
|
|
|
|
// 委托 Esp32OtaCore 处理分区/互斥逻辑
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-19 03:02:33 +08:00
|
|
|
|
esp_err_t OtaCoordinator::beginEsp32OtaWithRetry(uint32_t total_size)
|
|
|
|
|
|
{
|
|
|
|
|
|
esp_err_t err = beginEsp32Ota(total_size);
|
|
|
|
|
|
if (err == ESP_ERR_INVALID_STATE) {
|
|
|
|
|
|
// ★ 仅当系统确实空闲(无任何活跃 OTA)时才允许 forceCleanup:
|
|
|
|
|
|
// 这种情况只可能是"锁残留"——前一次 OTA 异常退出没释放全局互斥,
|
|
|
|
|
|
// 而协调器状态已回 IDLE。
|
|
|
|
|
|
// 若系统正处于任何活跃 OTA(ESP32/GD32),绝不强制清理——那会
|
|
|
|
|
|
// 把另一通道正在进行的 esp_ota_handle 中止、释放互斥,导致
|
|
|
|
|
|
// 两通道并发写同一 OTA 分区(之前 BLE 与 WiFi 可同时 OTA 的根因)。
|
|
|
|
|
|
if (!isIdle()) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "ESP32 OTA 被拒绝且系统非空闲 (state=%d),不强制清理,直接失败",
|
|
|
|
|
|
(int)getSystemState());
|
|
|
|
|
|
return err;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 协调器空闲但 Esp32OtaCore.begin 仍被拒 → 锁残留,强制清理后重试
|
|
|
|
|
|
DEBUG_WARN(TAG, "ESP32 OTA 被拒绝但系统空闲,判定为锁残留,强制清理状态机后重试");
|
|
|
|
|
|
Esp32OtaCore::instance().forceCleanup();
|
|
|
|
|
|
err = beginEsp32Ota(total_size);
|
|
|
|
|
|
}
|
|
|
|
|
|
return err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 02:18:42 +08:00
|
|
|
|
void OtaCoordinator::notifyEsp32ReceiveComplete()
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
|
|
|
|
|
if (h && m_sysState == OTA_SYS_ESP32_RECEIVING) {
|
2026-06-17 02:18:42 +08:00
|
|
|
|
m_sysState = OTA_SYS_ESP32_FINALIZING;
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 固件接收完毕, 系统状态 → ESP32_FINALIZING");
|
|
|
|
|
|
}
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OtaCoordinator::notifyEsp32FinalizeComplete()
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
|
|
|
|
|
if (h) {
|
|
|
|
|
|
m_sysState = OTA_SYS_ESP32_REBOOTING;
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 OTA 收尾完成, 系统状态 → ESP32_REBOOTING");
|
|
|
|
|
|
}
|
|
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OtaCoordinator::notifyEsp32Rebooting()
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
|
|
|
|
|
if (h) {
|
|
|
|
|
|
m_sysState = OTA_SYS_ESP32_REBOOTING;
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 即将重启, 系统状态 → ESP32_REBOOTING");
|
|
|
|
|
|
}
|
|
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-19 03:02:33 +08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// ESP32 OTA 收尾+重启序列(BLE/WiFi 共用封装)
|
|
|
|
|
|
// 关键:finalize() 内部已 end+set_boot+释放锁,reboot=false 返回后
|
|
|
|
|
|
// m_active 仍为 true、m_ended 为 true,调用方可在重启前自由查询状态。
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
Esp32FinalizeResult OtaCoordinator::finalizeForReboot(const char *frontend_sha_hex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Esp32FinalizeResult r = {};
|
|
|
|
|
|
notifyEsp32ReceiveComplete();
|
|
|
|
|
|
// reboot=false:让 core.finalize 真正返回,由调用方上报 device_sha 后再调 restartEsp32
|
|
|
|
|
|
r.err = Esp32OtaCore::instance().finalize(frontend_sha_hex,
|
|
|
|
|
|
/*reboot=*/false,
|
|
|
|
|
|
r.device_sha_hex);
|
|
|
|
|
|
if (r.err == ESP_OK) {
|
|
|
|
|
|
notifyEsp32FinalizeComplete();
|
|
|
|
|
|
}
|
|
|
|
|
|
return r;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OtaCoordinator::restartEsp32()
|
|
|
|
|
|
{
|
|
|
|
|
|
notifyEsp32Rebooting();
|
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(500));
|
|
|
|
|
|
esp_restart();
|
|
|
|
|
|
// 不会返回
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int OtaCoordinator::esp32FinalizeErrCategory(esp_err_t err)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (err == ESP_OK) return 0; // 成功
|
|
|
|
|
|
if (err == ESP_ERR_INVALID_VERSION) return 1; // SHA 不匹配
|
|
|
|
|
|
return 2; // 其他失败
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 02:18:42 +08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 中止
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
void OtaCoordinator::abortCurrent()
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
|
|
|
|
|
if (h) {
|
|
|
|
|
|
DEBUG_INFO(TAG, "中止当前 OTA 操作 (state=%d)", (int)m_sysState);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
|
2026-06-17 11:52:26 +08:00
|
|
|
|
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");
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
2026-06-17 11:52:26 +08:00
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 状态查询
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
ota_system_state_t OtaCoordinator::getSystemState() const
|
|
|
|
|
|
{
|
|
|
|
|
|
// 对于纯读取,短暂加锁保证一致性
|
|
|
|
|
|
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = self->lock();
|
2026-06-17 02:18:42 +08:00
|
|
|
|
ota_system_state_t s = m_sysState;
|
2026-06-17 11:52:26 +08:00
|
|
|
|
self->unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
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);
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = self->lock();
|
2026-06-17 02:18:42 +08:00
|
|
|
|
bool done = m_gd32StorageDone;
|
2026-06-17 11:52:26 +08:00
|
|
|
|
self->unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
return done;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 完全复位
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
void OtaCoordinator::reset()
|
|
|
|
|
|
{
|
2026-06-17 11:52:26 +08:00
|
|
|
|
bool h = lock();
|
|
|
|
|
|
if (h) {
|
|
|
|
|
|
m_sysState = OTA_SYS_IDLE;
|
|
|
|
|
|
m_gd32StorageDone = false;
|
|
|
|
|
|
m_busySinceTick = 0; // ★ 复位
|
|
|
|
|
|
DEBUG_INFO(TAG, "协调器已完全复位");
|
|
|
|
|
|
}
|
|
|
|
|
|
unlock(h);
|
2026-06-17 02:18:42 +08:00
|
|
|
|
}
|