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:
小龙虾
2026-06-17 02:18:42 +08:00
parent 5b2c5f8ff9
commit 98b5cc1ba1
11 changed files with 1535 additions and 919 deletions
+131 -41
View File
@@ -42,6 +42,7 @@
#include "ble_ota.h"
#include "wifi_ota.h"
#include "esp32_ota_core.h"
#include "ota_coordinator.h"
#include "ota_relay.h"
#include "health_monitor.h"
#include "monitor/debug_log.h"
@@ -59,6 +60,16 @@
#define BLE_OTA_FINISH_ERR_GD32_SIZE 0x84 // GD32 固件超出分区大小
#define BLE_OTA_FINISH_ERR_SHA_MISMATCH 0x85 // ESP32 OTA SHA-256 不匹配
// ── 数据队列:将 OTA 数据处理从 NimBLE host task 卸载到工作线程 ──
// 避免 flash 操作(esp_ota_begin / esp_ota_write)阻塞 BLE 协议栈导致连接断开。
#define BLE_OTA_DATA_QUEUE_LEN 16
struct BleOtaDataBatch {
uint8_t *data;
size_t len;
};
static QueueHandle_t s_otaDataQueue = nullptr;
// ============================================================
// 前向声明
// ============================================================
@@ -66,6 +77,7 @@ static void ble_ota_start_advertising(void);
static void sendWindowAck(uint8_t cmd, uint16_t seq, uint32_t completed_bytes);
static void sendFinishEvent(uint8_t stage, uint32_t value);
static void esp32_reorder_free();
static void ble_ota_data_worker_task(void *pvParameters);
// ============================================================
/**
@@ -100,8 +112,8 @@ static void ble_esp32_restart_task(void *pvParameters)
DEBUG_INFO(TAG, "[RESTART] 推送 FINISH_REBOOT_NOW 后 500ms 重启");
sendFinishEvent(BLE_OTA_FINISH_REBOOT_NOW, 0);
vTaskDelay(pdMS_TO_TICKS(500));
OtaCoordinator::instance().notifyEsp32Rebooting();
esp_restart();
// 不会返回
}
/**
@@ -135,6 +147,7 @@ static void ble_esp32_finish_and_reboot_task(void *pvParameters)
}
DEBUG_INFO(TAG, "ESP32 OTA 收尾: 共 %u 字节", (unsigned)core.getReceived());
OtaCoordinator::instance().notifyEsp32ReceiveComplete();
// 关键:reboot=false,让 finalize 真正返回、拿到 device_sha_hex,
// 再依次推送 SHA 通知 + FINISH_DONE + FINISH_REBOOT_NOW,
@@ -147,6 +160,7 @@ static void ble_esp32_finish_and_reboot_task(void *pvParameters)
device_sha_hex);
if (err == ESP_OK) {
OtaCoordinator::instance().notifyEsp32FinalizeComplete();
// 1) 把设备端 SHA 通过 SHA256 通知推给前端
if (device_sha_hex[0] != '\0') {
uint8_t sha_pkt[33] = {0};
@@ -168,6 +182,7 @@ static void ble_esp32_finish_and_reboot_task(void *pvParameters)
// 4) 通知全部发出后延迟 500ms 重启
DEBUG_INFO(TAG, "[FINISH] 通知已发送,500ms 后重启...");
vTaskDelay(pdMS_TO_TICKS(500));
OtaCoordinator::instance().notifyEsp32Rebooting();
esp_restart();
// 不会返回
} else if (err == ESP_ERR_INVALID_VERSION) {
@@ -524,12 +539,13 @@ static esp_err_t esp32_write_ordered(uint16_t seq, uint8_t cmd,
*/
static esp_err_t ble_esp32_ota_begin(size_t total_size)
{
esp_err_t err = Esp32OtaCore::instance().begin(total_size);
// ★ 通过全局协调器发起 ESP32 OTA
esp_err_t err = OtaCoordinator::instance().beginEsp32Ota(total_size);
if (err == ESP_ERR_INVALID_STATE) {
// ★ OTA 锁被占用,走完整的状态机退出 + 清理锁,然后重试一次
DEBUG_WARN(TAG, "ESP32 OTA 锁被占用,强制清理状态机后重试");
// 协调器拒绝 → 尝试强制清理后重试
DEBUG_WARN(TAG, "ESP32 OTA 被协调器拒绝,强制清理状态机后重试");
Esp32OtaCore::instance().forceCleanup();
err = Esp32OtaCore::instance().begin(total_size);
err = OtaCoordinator::instance().beginEsp32Ota(total_size);
}
if (err != ESP_OK) {
return err;
@@ -645,15 +661,15 @@ static int ble_ota_ctrl_access(uint16_t conn_handle, uint16_t attr_handle,
DEBUG_INFO(TAG, "CTRL 写入: cmd=0x%02X len=%d", buf[0], len);
if (buf[0] == BLE_OTA_CTRL_TRIGGER_GD32) {
// 检查固件是否存储完毕 → 触发 GD32 OTA 中继
// 检查固件是否存储完毕 → 通过协调器触发 GD32 OTA 中继
ota_meta_t meta;
esp_err_t err = OtaRelay::metaLoad(&meta);
if (err == ESP_OK &&
meta.total_size > 0 &&
meta.stored_size >= meta.total_size) {
DEBUG_INFO(TAG, "GD32 固件就绪 (%" PRIu32 " / %" PRIu32 " 字节),触发中继",
DEBUG_INFO(TAG, "GD32 固件就绪 (%" PRIu32 " / %" PRIu32 " 字节),通过协调器触发中继",
meta.stored_size, meta.total_size);
BleOta::instance().signalRelay();
OtaCoordinator::instance().triggerGd32Relay();
} else {
DEBUG_WARN(TAG, "GD32 固件未就绪 (stored=%" PRIu32 ", total=%" PRIu32 ")",
meta.stored_size, meta.total_size);
@@ -770,9 +786,36 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
// 将数据拷贝到堆上并送入工作队列,避免在 NimBLE host task 中执行
// 耗时的 flash 操作(esp_ota_begin / esp_ota_write)导致 BLE 连接断开。
if (s_otaDataQueue) {
uint8_t *heap_buf = (uint8_t *)malloc((size_t)recv_len);
if (heap_buf) {
memcpy(heap_buf, buf, (size_t)recv_len);
BleOtaDataBatch item = { heap_buf, (size_t)recv_len };
if (xQueueSend(s_otaDataQueue, &item, 0) != pdTRUE) {
DEBUG_WARN(TAG, "DATA_IN 工作队列满,丢弃 %d 字节(等待客户端重传)", recv_len);
free(heap_buf);
}
} else {
DEBUG_ERR(TAG, "DATA_IN 堆内存不足 (%d 字节),丢弃", recv_len);
}
}
return 0;
}
/**
* @brief 处理一批 OTA 数据包(在工作线程上下文中调用)。
* 包含 ESP32 和 GD32 的数据接收逻辑。
* @param buf 数据缓冲区(包含一个或多个合并的包)。
* @param recv_len 数据长度。
*/
static void ble_ota_process_data_batch(const uint8_t *buf, size_t recv_len)
{
// 循环处理 mbuf 中的所有完整数据包
size_t offset = 0;
while (offset + 7 <= (size_t)recv_len) {
while (offset + 7 <= recv_len) {
// 解析包头
uint8_t cmd = buf[offset + 0];
uint16_t seq = (uint16_t)(buf[offset + 1] | (buf[offset + 2] << 8));
@@ -790,7 +833,7 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
: (uint32_t)BLE_OTA_CHUNK_PAYLOAD;
size_t data_len = (size_t)expected_payload;
size_t remaining_after_header = (size_t)recv_len - data_offset;
size_t remaining_after_header = recv_len - data_offset;
// 如果缓冲区中实际可用数据不足,截断 data_len
if (remaining_after_header < expected_payload) {
@@ -813,16 +856,15 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
if (seq == 0) {
if (total_size == 0) {
DEBUG_ERR(TAG, "ESP32 固件大小非法: %" PRIu32, total_size);
return BLE_ATT_ERR_UNLIKELY;
return;
}
// 分区选择/大小校验/互斥由 Esp32OtaCore::begin 内部完成
esp_err_t err = ble_esp32_ota_begin(total_size);
if (err != ESP_OK) {
if (err == ESP_ERR_INVALID_STATE) {
sendFinishEvent(BLE_OTA_FINISH_ERR_MUTEX, 0);
return BLE_ATT_ERR_OTA_BUSY;
}
return BLE_ATT_ERR_UNLIKELY;
return;
}
}
@@ -830,7 +872,7 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
if (!Esp32OtaCore::instance().isActive()) {
DEBUG_WARN(TAG, "ESP32 OTA 会话无效,丢弃 seq=%u", seq);
offset += packet_len;
if (offset + 7 > (size_t)recv_len) break;
if (offset + 7 > recv_len) break;
continue;
}
@@ -841,7 +883,7 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
(unsigned)curReceived, total_size, seq);
sendWindowAck(cmd, seq, curReceived);
offset += packet_len;
if (offset + 7 > (size_t)recv_len) break;
if (offset + 7 > recv_len) break;
continue;
}
@@ -851,24 +893,18 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
DEBUG_ERR(TAG, "ESP32 OTA 写入失败: seq=%u err=%s",
seq, esp_err_to_name(err));
ble_esp32_ota_abort(); // core 内部完成 abort + mutex release + 状态复位
return BLE_ATT_ERR_UNLIKELY;
return;
}
DEBUG_DBG(TAG, "ESP32 OTA 写入成功: seq=%u received=%u",
seq, (unsigned int)Esp32OtaCore::instance().getReceived());
// 完成判定:以实际按序写入字节数为准。
// reorder buffer 模式下,尾包可能是乱序到达被缓存的包,
// 此刻 getReceived() 尚未达到 total_size,不能据此判定完成。
// isLastPacket 仅记录"这是最后一包的形态",不单独作为完成条件。
bool isLastPacket = (expected_payload < BLE_OTA_CHUNK_PAYLOAD);
bool transferComplete = (total_size > 0 &&
Esp32OtaCore::instance().getReceived() >= total_size);
// ACK 触发条件:
// - 窗口末尾包 (seq+1 是窗口整数倍)
// - 数据全部按序写完(含 reorder flush 后达到 total_size)
// - 尾包形态(兜底:即使 getReceived 不足也回一个 ACK,让客户端续传)
// ACK 触发条件
if ((((uint32_t)seq + 1U) % BLE_OTA_ACK_WINDOW) == 0U ||
transferComplete ||
isLastPacket) {
@@ -879,17 +915,13 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
sendWindowAck(cmd, seq, (uint32_t)Esp32OtaCore::instance().getReceived());
}
// ★ 仅当数据真正全部按序写完时才标记传输完成。
// 尾包乱序缓存但缺口未填满时,不算完成(等待客户端续传缺口包)。
if (transferComplete) {
DEBUG_INFO(TAG, "★ ESP32 固件接收完毕: %u / %" PRIu32 " 字节 (共 %u 块)",
(unsigned int)Esp32OtaCore::instance().getReceived(), total_size, seq + 1);
OtaCoordinator::instance().notifyEsp32ReceiveComplete();
BleOta::instance().m_esp32LastCmd = cmd;
BleOta::instance().m_esp32LastSeq = seq;
// ★ ESP32 OTA 传输完成:恢复 WiFi 射频
ble_ota_restore_wifi_ps();
// 注:与 WiFi 流程对齐,传输完成后由前端发送 0xF8 + SHA-256 触发 finalize,
// 设备端不再自动创建收尾任务。
}
}
// --- GD32 OTA:首个分块时创建存储元数据 ---
@@ -900,7 +932,12 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
DEBUG_ERR(TAG, "GD32 固件大小非法: raw=%" PRIu32 " rounded48=%" PRIu32 " max=%zu",
total_size, rounded_size, OTA_MAX_FIRMWARE_SIZE);
sendFinishEvent(BLE_OTA_FINISH_ERR_GD32_SIZE, total_size);
return BLE_ATT_ERR_UNLIKELY;
return;
}
if (OtaCoordinator::instance().beginGd32Storage(total_size) != ESP_OK) {
DEBUG_ERR(TAG, "GD32 OTA 协调器拒绝存储(系统忙)");
return;
}
ota_meta_t meta = {};
@@ -911,49 +948,48 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
esp_err_t err = OtaRelay::metaSave(&meta);
if (err != ESP_OK) {
DEBUG_ERR(TAG, "元数据保存失败");
return BLE_ATT_ERR_UNLIKELY;
OtaCoordinator::instance().abortCurrent();
return;
}
DEBUG_INFO(TAG, "GD32 固件存储开始: total=%" PRIu32 ", payload=%zu",
meta.total_size, data_len);
// ★ GD32 BLE OTA 开始:降低 WiFi 射频占用
ble_ota_boost_wifi_ps();
}
ota_meta_t meta = {};
if (OtaRelay::metaLoad(&meta) != ESP_OK || meta.total_size == 0) {
DEBUG_ERR(TAG, "GD32 元数据无效,拒绝写入: seq=%u", seq);
return BLE_ATT_ERR_UNLIKELY;
return;
}
if (seq > 0 && meta.last_seq != (uint32_t)(seq - 1)) {
DEBUG_ERR(TAG, "GD32 分包序号不连续: seq=%u last_seq=%" PRIu32,
seq, meta.last_seq);
return BLE_ATT_ERR_UNLIKELY;
return;
}
// --- GD32 写入 Flash ---
uint32_t flash_offset = meta.stored_size;
// ★ 防重复写入保护:如果已存储字节 >= 声明大小,跳过重试引起的重复包
if (flash_offset >= meta.total_size) {
DEBUG_WARN(TAG, "GD32 OTA 已收满 (%" PRIu32 " >= %" PRIu32 "),跳过重复 seq=%u",
flash_offset, meta.total_size, seq);
sendWindowAck(cmd, seq, flash_offset);
offset += packet_len;
if (offset + 7 > (size_t)recv_len) break;
if (offset + 7 > recv_len) break;
continue;
}
if (flash_offset + (uint32_t)data_len > meta.total_size) {
DEBUG_ERR(TAG, "GD32 写入越界: offset=%" PRIu32 " len=%zu total=%" PRIu32,
flash_offset, data_len, meta.total_size);
return BLE_ATT_ERR_UNLIKELY;
return;
}
esp_err_t err = OtaRelay::flashWriteChunk(flash_offset, &buf[data_offset], data_len);
if (err != ESP_OK) {
DEBUG_ERR(TAG, "Flash 写入失败: seq=%u offset=%" PRIu32 " err=%s",
seq, flash_offset, esp_err_to_name(err));
return BLE_ATT_ERR_UNLIKELY;
return;
}
uint32_t stored_size = flash_offset + (uint32_t)data_len;
@@ -977,9 +1013,8 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
meta.stored_size >= meta.total_size) {
DEBUG_INFO(TAG, "★ GD32 固件接收完毕: %" PRIu32 " / %" PRIu32 " 字节 (共 %u 块)",
meta.stored_size, meta.total_size, seq + 1);
// ★ GD32 BLE OTA 传输完成:恢复 WiFi 射频
OtaCoordinator::instance().completeGd32Storage();
ble_ota_restore_wifi_ps();
// 计算并存储 SHA-256 校验和
esp_err_t sha_err = OtaRelay::metaFinalizeChecksum();
if (sha_err != ESP_OK) {
DEBUG_ERR(TAG, "GD32 固件 SHA-256 计算失败: %s", esp_err_to_name(sha_err));
@@ -993,10 +1028,38 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
offset += packet_len;
// 检查剩余数据是否足够组成一个完整的包头
if (offset + 7 > (size_t)recv_len) break;
if (offset + 7 > recv_len) break;
}
}
return 0;
/**
* @brief OTA 数据工作线程:从队列接收数据并调用处理函数。
* 独立于 NimBLE host task,避免 flash 操作阻塞 BLE 协议栈。
*/
static void ble_ota_data_worker_task(void *pvParameters)
{
(void)pvParameters;
DEBUG_INFO(TAG, "OTA 数据工作线程已启动");
while (1) {
BleOtaDataBatch item;
if (xQueueReceive(s_otaDataQueue, &item, portMAX_DELAY) == pdTRUE) {
if (!item.data || item.len == 0) {
free(item.data);
continue;
}
// 若 BLE 已断开,丢弃队列中的残留数据
if (!BleOta::instance().isConnected()) {
DEBUG_DBG(TAG, "BLE 已断开,丢弃 %zu 字节队列数据", item.len);
free(item.data);
continue;
}
ble_ota_process_data_batch(item.data, item.len);
free(item.data);
}
}
}
/*
@@ -1166,6 +1229,15 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg)
case BLE_GAP_EVENT_DISCONNECT:
DEBUG_INFO(TAG, "BLE 客户端已断开: reason=%d",
event->disconnect.reason);
// ★ 断开时清空数据队列,丢弃属于旧会话的残留包
if (s_otaDataQueue) {
BleOtaDataBatch stale;
while (xQueueReceive(s_otaDataQueue, &stale, 0) == pdTRUE) {
free(stale.data);
}
}
BleOta::instance().m_bleConnected = false;
BleOta::instance().m_connHandle = BLE_HS_CONN_HANDLE_NONE;
// ★ BLE 断开时恢复 WiFi 射频,并中止未完成的 ESP32 OTA
@@ -1479,6 +1551,24 @@ void BleOta::taskEntry(void *pvParameters)
(void)pvParameters;
DEBUG_INFO(TAG, "=== BLE OTA 任务启动 ===");
// 创建 OTA 数据队列和工作线程(在 NimBLE 初始化之前完成)
s_otaDataQueue = xQueueCreate(BLE_OTA_DATA_QUEUE_LEN, sizeof(BleOtaDataBatch));
if (s_otaDataQueue) {
BaseType_t wk_ok = xTaskCreate(ble_ota_data_worker_task,
"ble_ota_worker",
8192, // 6KB→8KB,flash 操作需要足够栈空间
NULL,
10, // 中等优先级,避免被 NimBLE host task 过度抢占
NULL);
if (wk_ok != pdPASS) {
DEBUG_ERR(TAG, "OTA 数据工作线程创建失败,BLE OTA 数据传输不可用");
vQueueDelete(s_otaDataQueue);
s_otaDataQueue = nullptr;
}
} else {
DEBUG_ERR(TAG, "OTA 数据队列创建失败,BLE OTA 数据传输不可用");
}
esp_err_t err = BleOta::instance().serverStart();
if (err != ESP_OK) {
DEBUG_ERR(TAG, "BLE 服务器启动失败: %s → BLE OTA 不可用",
+45 -60
View File
@@ -25,6 +25,7 @@
#include "ota_mutex.h"
#include "wifi_ota.h"
#include "esp32_ota_core.h"
#include "ota_coordinator.h"
#include "ota_relay.h"
#include "health_monitor.h"
#include "monitor/debug_log.h"
@@ -513,11 +514,13 @@ static esp_err_t esp32_ota_update_handler(httpd_req_t *req)
// 若 reboot=true,core 内部直接 esp_restart(),HTTP 响应永远发不出去,
// 前端会得到 "Failed to fetch" 而错过核对结果。
DEBUG_INFO("OTA", "ESP32 OTA finalize, %u bytes...", (unsigned)core.getReceived());
OtaCoordinator::instance().notifyEsp32ReceiveComplete();
esp_err_t err = core.finalize(has_frontend_sha ? frontend_sha_hex : nullptr,
/*reboot=*/false,
device_sha_hex);
if (err == ESP_OK) {
OtaCoordinator::instance().notifyEsp32FinalizeComplete();
// 1) 把包含设备端 SHA 的响应发回前端
char resp[320] = {0};
snprintf(resp, sizeof(resp),
@@ -530,6 +533,7 @@ static esp_err_t esp32_ota_update_handler(httpd_req_t *req)
// 2) 响应发完后延迟 500ms 重启,给 TCP 缓冲区时间 flush 到客户端
DEBUG_INFO("OTA", "响应已发送,500ms 后重启...");
vTaskDelay(pdMS_TO_TICKS(500));
OtaCoordinator::instance().notifyEsp32Rebooting();
esp_restart();
// 不会返回
return ESP_OK;
@@ -566,6 +570,7 @@ static esp_err_t restart_handler(httpd_req_t *req)
httpd_resp_send(req, resp, strlen(resp));
vTaskDelay(pdMS_TO_TICKS(500));
OtaCoordinator::instance().notifyEsp32Rebooting();
esp_restart();
return ESP_OK;
}
@@ -665,13 +670,13 @@ static esp_err_t ota_upload_handler(httpd_req_t *req)
return ESP_FAIL;
}
Esp32OtaCore &core = Esp32OtaCore::instance();
esp_err_t err = core.begin(total_size);
// ★ 通过全局协调器发起 ESP32 OTA
esp_err_t err = OtaCoordinator::instance().beginEsp32Ota(total_size);
if (err == ESP_ERR_INVALID_STATE) {
// ★ OTA 锁被占用,走完整的状态机退出 + 清理锁,然后重试一次
DEBUG_WARN("WIFI", "ESP32 OTA 锁被占用,强制清理状态机后重试");
core.forceCleanup();
err = core.begin(total_size);
// 协调器拒绝(系统忙或通道被占用)→ 尝试强制清理后重试
DEBUG_WARN("WIFI", "ESP32 OTA 被协调器拒绝,强制清理状态机后重试");
Esp32OtaCore::instance().forceCleanup();
err = OtaCoordinator::instance().beginEsp32Ota(total_size);
}
if (err != ESP_OK) {
if (err == ESP_ERR_INVALID_STATE) {
@@ -682,6 +687,8 @@ static esp_err_t ota_upload_handler(httpd_req_t *req)
return ESP_FAIL;
}
Esp32OtaCore &core = Esp32OtaCore::instance();
// 堆分配缓冲区,避免栈溢出(HTTP server 栈仅 8KB)
uint8_t *fw_buf = (uint8_t *)malloc(OTA_BLOCK_SIZE);
if (!fw_buf) {
@@ -757,6 +764,14 @@ static esp_err_t ota_upload_handler(httpd_req_t *req)
DEBUG_WARN("WIFI", "GD32 OTA 开始前复位中继状态: %d", OtaRelay::getState());
OtaRelay::instance().reset();
}
// ★ 通过全局协调器注册 GD32 存储开始
if (OtaCoordinator::instance().beginGd32Storage(gd32_valid_size) != ESP_OK) {
DEBUG_ERR("WIFI", "GD32 OTA 协调器拒绝存储(系统忙)");
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "system busy with another OTA operation");
return ESP_FAIL;
}
WifiOta::instance().stateLock();
WifiOta::instance().setGd32StorageDone(false);
WifiOta::instance().setRelayTriggered(false);
@@ -870,6 +885,8 @@ static esp_err_t ota_upload_handler(httpd_req_t *req)
ota_meta_t final_meta;
OtaRelay::metaLoad(&final_meta);
if (final_meta.total_size > 0 && final_meta.stored_size >= final_meta.total_size) {
// ★ 通知协调器 GD32 存储完成
OtaCoordinator::instance().completeGd32Storage();
WifiOta::instance().stateLock();
WifiOta::instance().setGd32StorageDone(true);
WifiOta::instance().stateUnlock();
@@ -880,11 +897,8 @@ static esp_err_t ota_upload_handler(httpd_req_t *req)
DEBUG_ERR("WIFI", "GD32 固件 SHA-256 计算失败: %s", esp_err_to_name(sha_err));
}
DEBUG_INFO("WIFI", "auto-triggering relay");
// 自动触发中继
if (OtaRelay::dispatchQueue) {
uint32_t cmd = 1; // 1 = start relay
xQueueSend(OtaRelay::dispatchQueue, &cmd, 0);
}
// ★ 通过协调器触发中继
OtaCoordinator::instance().triggerGd32Relay();
}
const char resp[] = "{\"status\": \"ok\", \"progress\": 100, \"target\": \"gd32\"}";
@@ -911,27 +925,10 @@ static esp_err_t ota_relay_trigger_handler(httpd_req_t *req)
return ESP_FAIL;
}
relay_state_t curState = OtaRelay::getState();
WifiOta::instance().stateLock();
if (WifiOta::instance().getRelayTriggered() && curState != RELAY_IDLE &&
curState != RELAY_DONE && curState != RELAY_APP_VERIFY && curState != RELAY_ERROR) {
WifiOta::instance().stateUnlock();
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "relay already triggered");
return ESP_FAIL;
}
/* 上一轮中继已结束/失败/空闲,重置标志允许重新触发 */
WifiOta::instance().setRelayTriggered(false);
WifiOta::instance().setGd32StorageDone(false);
WifiOta::instance().stateUnlock();
if (!OtaRelay::dispatchQueue) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "relay queue not ready");
return ESP_FAIL;
}
WifiOta::instance().signalRelay();
if (!WifiOta::instance().getRelayTriggered()) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "relay trigger failed");
// ★ 通过全局协调器触发 GD32 中继
esp_err_t trigger_err = OtaCoordinator::instance().triggerGd32Relay();
if (trigger_err != ESP_OK) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "relay trigger failed - system busy or firmware not ready");
return ESP_FAIL;
}
@@ -1192,9 +1189,10 @@ static esp_err_t system_status_handler(httpd_req_t *req)
gd32_version_t snapVer2 = {};
OtaRelay::getGd32Version(&snapVer2);
char resp[1024];
ota_system_state_t sysState = OtaCoordinator::instance().getSystemState();
char resp[1280];
int len = snprintf(resp, sizeof(resp),
"{\"version\": \"%s\", \"build_time\": \"%s\", \"git_hash\": \"%s\", \"health\": %d, \"free_heap\": %lu, \"min_free_heap\": %lu, \"temperature\": %.1f, \"uptime_sec\": %lu, \"wifi\": {\"mode\": \"%s\", \"ip\": \"%s\", \"rssi\": %d, \"connected\": %s}, \"ota\": {\"total_size\": %lu, \"stored_size\": %lu, \"storage_done\": %s, \"relay_state\": %d, \"relay_seq\": %lu, \"relay_total_packets\": %lu, \"gd32_app_alive\": %s, \"gd32_app\": \"v%d.%d.%d.%d\", \"gd32_boot\": \"v%d.%d.%d.%d\"}}",
"{\"version\": \"%s\", \"build_time\": \"%s\", \"git_hash\": \"%s\", \"health\": %d, \"free_heap\": %lu, \"min_free_heap\": %lu, \"temperature\": %.1f, \"uptime_sec\": %lu, \"wifi\": {\"mode\": \"%s\", \"ip\": \"%s\", \"rssi\": %d, \"connected\": %s}, \"ota\": {\"sys_state\": \"%s\", \"sys_state_code\": %d, \"total_size\": %lu, \"stored_size\": %lu, \"storage_done\": %s, \"relay_state\": %d, \"relay_seq\": %lu, \"relay_total_packets\": %lu, \"gd32_app_alive\": %s, \"gd32_app\": \"v%d.%d.%d.%d\", \"gd32_boot\": \"v%d.%d.%d.%d\"}}",
FW_VERSION, FW_BUILD_TIME, FW_GIT_HASH, HealthMonitor::instance().getCurrent(),
(unsigned long)esp_get_free_heap_size(),
(unsigned long)esp_get_minimum_free_heap_size(),
@@ -1204,9 +1202,11 @@ static esp_err_t system_status_handler(httpd_req_t *req)
ip_str,
rssi,
connected ? "true" : "false",
OtaCoordinator::instance().getSystemStateString(),
(int)sysState,
(unsigned long)meta.total_size,
(unsigned long)meta.stored_size,
WifiOta::instance().getGd32StorageDone() ? "true" : "false",
OtaCoordinator::instance().isGd32StorageDone() ? "true" : "false",
OtaRelay::getState(),
(unsigned long)OtaRelay::getRelaySeq(),
(unsigned long)OtaRelay::getRelayTotalPackets(),
@@ -1394,35 +1394,20 @@ void WifiOta::signalRelay()
ota_meta_t meta;
memset(&meta, 0, sizeof(meta));
if (OtaRelay::metaLoad(&meta) != ESP_OK || meta.total_size == 0 || meta.stored_size < meta.total_size) {
DEBUG_WARN("WIFI", "GD32 固件未完整存储,拒绝触发转发: stored=%lu total=%lu",
(unsigned long)meta.stored_size, (unsigned long)meta.total_size);
DEBUG_WARN("WIFI", "GD32 固件未完整存储,拒绝触发转发: stored=%" PRIu32 " total=%" PRIu32,
meta.stored_size, meta.total_size);
return;
}
relay_state_t st = OtaRelay::getState();
WifiOta::instance().stateLock();
if (WifiOta::instance().getRelayTriggered() && st != RELAY_IDLE &&
st != RELAY_DONE && st != RELAY_APP_VERIFY && st != RELAY_ERROR) {
WifiOta::instance().stateUnlock();
DEBUG_WARN("WIFI", "转发正在执行中,状态: %d", st);
return;
}
/* 上一轮中继已结束/失败/空闲,重置标志 */
WifiOta::instance().setRelayTriggered(false);
WifiOta::instance().setGd32StorageDone(false);
WifiOta::instance().setRelayTriggered(true);
WifiOta::instance().stateUnlock();
if (OtaRelay::dispatchQueue) {
uint32_t msg = 1;
BaseType_t ret = xQueueSend(OtaRelay::dispatchQueue, &msg, pdMS_TO_TICKS(100));
if (ret == pdTRUE) {
DEBUG_INFO("WIFI", "OTA 调度信号已发送到中继任务");
} else {
DEBUG_ERR("WIFI", "OTA 调度信号发送失败");
WifiOta::instance().setRelayTriggered(false);
}
// ★ 通过全局协调器触发中继
esp_err_t err = OtaCoordinator::instance().triggerGd32Relay();
if (err == ESP_OK) {
WifiOta::instance().stateLock();
WifiOta::instance().setRelayTriggered(true);
WifiOta::instance().stateUnlock();
DEBUG_INFO("WIFI", "OTA 中继已通过协调器触发");
} else {
DEBUG_ERR("WIFI", "OTA 中继队列尚未就绪");
DEBUG_ERR("WIFI", "OTA 中继触发失败: %s", esp_err_to_name(err));
WifiOta::instance().setRelayTriggered(false);
}
}