feat(ota): 统一BLE/WiFi OTA协调器接口并优化并发控制
- 添加 beginEsp32OtaWithRetry 统一重试逻辑,防止锁残留导致的并发问题 - 实现 finalizeForReboot 和 restartEsp32 统一封装收尾重启流程 - 修复BLE/WiFi双通道并发OTA的安全隐患,增强系统状态检查 refactor(ota): 优化SHA计算内存管理 - 使用静态缓冲区替代动态分配,减少内存碎片 - 重构OTA协调器状态机,增强错误处理机制 perf(monitor): 增强堆内存监控功能 - 添加最大空闲块大小监控,提供更详细的内存状态信息 - 更新健康监测算法,提高内存异常检测准确性
This commit is contained in:
@@ -171,30 +171,24 @@ esp_err_t Esp32OtaCore::computeShaFromPartition()
|
||||
mbedtls_sha256_init(&ctx);
|
||||
mbedtls_sha256_starts(&ctx, 0);
|
||||
|
||||
uint8_t *read_buf = (uint8_t *)malloc(OTA_BLOCK_SIZE);
|
||||
if (!read_buf) {
|
||||
DEBUG_ERR(TAG, "SHA 计算: malloc 失败");
|
||||
mbedtls_sha256_free(&ctx);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
// ★ static 缓冲区:OTA 操作由 OtaCoordinator 互斥串行化,无并发风险
|
||||
static uint8_t s_sha_read_buf[OTA_BLOCK_SIZE];
|
||||
|
||||
size_t offset = 0;
|
||||
while (offset < m_received) {
|
||||
size_t to_read = m_received - offset;
|
||||
if (to_read > OTA_BLOCK_SIZE) to_read = OTA_BLOCK_SIZE;
|
||||
esp_err_t rd_err = esp_partition_read(m_partition, offset, read_buf, to_read);
|
||||
esp_err_t rd_err = esp_partition_read(m_partition, offset, s_sha_read_buf, to_read);
|
||||
if (rd_err != ESP_OK) {
|
||||
DEBUG_ERR(TAG, "SHA 计算: 分区读失败 @offset %u: %s",
|
||||
(unsigned)offset, esp_err_to_name(rd_err));
|
||||
free(read_buf);
|
||||
mbedtls_sha256_free(&ctx);
|
||||
return rd_err;
|
||||
}
|
||||
mbedtls_sha256_update(&ctx, read_buf, to_read);
|
||||
mbedtls_sha256_update(&ctx, s_sha_read_buf, to_read);
|
||||
offset += to_read;
|
||||
}
|
||||
|
||||
free(read_buf);
|
||||
mbedtls_sha256_finish(&ctx, m_sha256);
|
||||
mbedtls_sha256_free(&ctx);
|
||||
m_shaComputed = true;
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
#include "esp32_ota_core.h"
|
||||
#include "relay/ota_relay.h"
|
||||
#include "monitor/debug_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define TAG "OTA_COORD"
|
||||
|
||||
@@ -283,11 +286,18 @@ esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
|
||||
// ★ 卡死自愈检查
|
||||
if (h) watchdogCheckLocked();
|
||||
|
||||
// 系统忙检查
|
||||
// 系统忙检查:GD32 传输/中继进行中,或另一通道的 ESP32 OTA 进行中,均拒绝。
|
||||
// ★ 必须显式拒绝 ESP32_RECEIVING/FINALIZING/REBOOTING:
|
||||
// 若放行,本调用会落到 Esp32OtaCore::begin 去抢全局互斥失败而返回 INVALID_STATE,
|
||||
// 随后 beginEsp32OtaWithRetry 的 forceCleanup 会把另一通道的活跃会话
|
||||
// (esp_ota_abort + 释放互斥)误中止 → 两通道并发写同一 OTA 分区。
|
||||
if (h && (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 ? "存储" : "中继");
|
||||
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());
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
@@ -309,6 +319,29 @@ esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
|
||||
return err;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void OtaCoordinator::notifyEsp32ReceiveComplete()
|
||||
{
|
||||
bool h = lock();
|
||||
@@ -339,6 +372,40 @@ void OtaCoordinator::notifyEsp32Rebooting()
|
||||
unlock(h);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 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; // 其他失败
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 中止
|
||||
// ============================================================
|
||||
|
||||
@@ -35,6 +35,15 @@ typedef enum {
|
||||
OTA_SYS_ERROR // 错误状态
|
||||
} ota_system_state_t;
|
||||
|
||||
/**
|
||||
* @brief ESP32 OTA 收尾结果(由 finalizeForReboot 返回)。
|
||||
* err 为 ESP_OK 时 device_sha_hex 有效;SHA 不匹配时也填充 device_sha_hex 供前端核对。
|
||||
*/
|
||||
typedef struct {
|
||||
esp_err_t err; ///< ESP_OK / ESP_ERR_INVALID_VERSION(SHA不匹配) / 其他
|
||||
char device_sha_hex[65]; ///< 设备端 SHA hex(成功或 SHA 不匹配时填充)
|
||||
} Esp32FinalizeResult;
|
||||
|
||||
/**
|
||||
* @brief 全局 OTA 协调器(单例)
|
||||
*
|
||||
@@ -101,6 +110,14 @@ public:
|
||||
*/
|
||||
esp_err_t beginEsp32Ota(uint32_t total_size);
|
||||
|
||||
/**
|
||||
* @brief 发起 ESP32 OTA,若被 ESP_ERR_INVALID_STATE 拒绝则 forceCleanup 后重试一次。
|
||||
* 封装 BLE/WiFi 两端完全相同的重试模式,调用方只需调一次。
|
||||
* @param total_size 固件总字节数。
|
||||
* @return ESP_OK 成功;其他 = 协调器拒绝或 Esp32OtaCore::begin 失败。
|
||||
*/
|
||||
esp_err_t beginEsp32OtaWithRetry(uint32_t total_size);
|
||||
|
||||
/**
|
||||
* @brief ESP32 OTA 固件接收完毕,准备收尾。
|
||||
* 更新系统状态为 OTA_SYS_ESP32_FINALIZING。
|
||||
@@ -117,6 +134,23 @@ public:
|
||||
*/
|
||||
void notifyEsp32Rebooting();
|
||||
|
||||
/**
|
||||
* @brief ESP32 OTA 收尾(统一封装 BLE/WiFi 两端重复的收尾序列)。
|
||||
* 步骤:notifyEsp32ReceiveComplete → Esp32OtaCore::finalize(reboot=false)
|
||||
* → 成功则 notifyEsp32FinalizeComplete。
|
||||
* 不重启;调用方负责把 device_sha_hex 上报前端后再调 restartEsp32()。
|
||||
* @param frontend_sha_hex 前端 64 字符 hex SHA-256,nullptr 表示跳过比对。
|
||||
* @return Esp32FinalizeResult,err 为 ESP_OK/ESP_ERR_INVALID_VERSION/其他。
|
||||
*/
|
||||
Esp32FinalizeResult finalizeForReboot(const char *frontend_sha_hex);
|
||||
|
||||
/**
|
||||
* @brief 重启 ESP32(统一封装 BLE/WiFi 两端重复的重启序列)。
|
||||
* 步骤:notifyEsp32Rebooting → vTaskDelay(500ms) → esp_restart()。
|
||||
* 应在 finalizeForReboot 成功并完成前端上报后调用。不会返回。
|
||||
*/
|
||||
void restartEsp32();
|
||||
|
||||
// ── 中止 ──
|
||||
/**
|
||||
* @brief 中止当前 OTA 操作(GD32 或 ESP32)。
|
||||
@@ -167,6 +201,14 @@ public:
|
||||
*/
|
||||
void reset();
|
||||
|
||||
// ── ESP32 OTA finalize 错误分类(BLE/WiFi 共用)──
|
||||
/**
|
||||
* @brief 把 finalize 返回的 esp_err_t 映射为前端可读的事件码。
|
||||
* 统一 BLE/WiFi 两端语义,便于日志/状态一致。
|
||||
* @return 0=成功;1=SHA 不匹配;2=其他失败。
|
||||
*/
|
||||
static int esp32FinalizeErrCategory(esp_err_t err);
|
||||
|
||||
private:
|
||||
OtaCoordinator() = default;
|
||||
~OtaCoordinator() = default;
|
||||
|
||||
Reference in New Issue
Block a user