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:
+131
-41
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user