添加了SPI链接V2支持文件,重构OTA协调器的存储准备流程,
统一WiFi/BLE传输路径的收尾处理逻辑。 feat(ota): 优化OTA性能并增强系统监控功能 - 修改RSSI警告阈值从-75dBm调整为-80dBm,更新相关文档说明 - 增加HTTP服务器任务栈大小至10240字节,解决栈溢出问题 - 将OTA块大小从4096字节降至2048字节以降低峰值堆内存占用 - 实现按需擦除机制替代预擦除,在GD32 OTA中按扇区动态擦除 - 添加任务栈水位主动巡检功能,监控栈使用情况并告警 - 集成FreeRTOS栈溢出钩子,提供栈溢出检测和告警 - 优化ESP32 OTA实现使用顺序写入模式,消除长时间擦除阻塞 - 改进堆内存不足告警机制,添加限流避免刷屏 - 移除不必要的调试日志输出,精简健康监控模块
This commit is contained in:
+37
-22
@@ -12,6 +12,8 @@
|
||||
#include "esp_system.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#define MBEDTLS_DECLARE_PRIVATE_IDENTIFIERS
|
||||
#include "mbedtls/private/sha256.h"
|
||||
@@ -170,19 +172,23 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
|
||||
return beginCleanupAndReturn(ESP_FAIL);
|
||||
}
|
||||
|
||||
// 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);
|
||||
/* ★ 用 OTA_WITH_SEQUENTIAL_WRITES:边写边按扇区擦除,begin 几乎瞬时返回。
|
||||
* 旧逻辑按 64KB 对齐预擦 ~1MB,会在 /ota/upload 开头阻塞几十秒~数分钟:
|
||||
* 浏览器 upload 进度已到 100%,设备还在擦 Flash → 表现为“卡在 100% 很久才成功”。 */
|
||||
DEBUG_INFO(TAG, "esp_ota_begin(SEQUENTIAL) 目标=%s size=%u free=%lu...",
|
||||
m_partition->label, (unsigned)total_size,
|
||||
(unsigned long)esp_get_free_heap_size());
|
||||
const int64_t t0 = esp_timer_get_time();
|
||||
esp_err_t err = esp_ota_begin(m_partition, OTA_WITH_SEQUENTIAL_WRITES, &m_handle);
|
||||
const int64_t t1 = esp_timer_get_time();
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR(TAG, "esp_ota_begin 失败: %s", esp_err_to_name(err));
|
||||
DEBUG_ERR(TAG, "esp_ota_begin 失败: %s (耗时 %lld ms)",
|
||||
esp_err_to_name(err), (long long)((t1 - t0) / 1000));
|
||||
return beginCleanupAndReturn(err);
|
||||
}
|
||||
DEBUG_INFO(TAG, "esp_ota_begin 完成 耗时 %lld ms free=%lu",
|
||||
(long long)((t1 - t0) / 1000),
|
||||
(unsigned long)esp_get_free_heap_size());
|
||||
|
||||
m_received = 0;
|
||||
m_active = true;
|
||||
@@ -190,7 +196,7 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
|
||||
m_ended = false;
|
||||
memset(m_sha256, 0, sizeof(m_sha256));
|
||||
|
||||
DEBUG_INFO(TAG, "ESP32 OTA 开始,目标分区: %s, 声明大小: %u 字节",
|
||||
DEBUG_INFO(TAG, "ESP32 OTA 开始,目标分区: %s, 声明大小: %u 字节 (sequential erase)",
|
||||
m_partition->label, (unsigned)total_size);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -207,11 +213,14 @@ esp_err_t Esp32OtaCore::write(const uint8_t *data, size_t len)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* sequential 模式写时可能触发扇区擦除,喂狗防 WDT */
|
||||
(void)esp_task_wdt_reset();
|
||||
esp_err_t err = esp_ota_write(m_handle, data, len);
|
||||
if (err == ESP_OK) {
|
||||
m_received += len;
|
||||
} else {
|
||||
DEBUG_ERR(TAG, "esp_ota_write 失败: %s (len=%u)", esp_err_to_name(err), (unsigned)len);
|
||||
DEBUG_ERR(TAG, "esp_ota_write 失败: %s (len=%u offset=%u)",
|
||||
esp_err_to_name(err), (unsigned)len, (unsigned)m_received);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
@@ -228,30 +237,36 @@ esp_err_t Esp32OtaCore::computeShaFromPartition()
|
||||
|
||||
DEBUG_INFO(TAG, "从 OTA 分区读回计算 SHA-256 (%u 字节)...", (unsigned)m_received);
|
||||
|
||||
mbedtls_sha256_context ctx;
|
||||
mbedtls_sha256_init(&ctx);
|
||||
mbedtls_sha256_starts(&ctx, 0);
|
||||
/* static context + 小读缓冲:勿占 httpd 栈,也勿再静态吃 4KB BSS */
|
||||
static mbedtls_sha256_context s_sha_ctx;
|
||||
static uint8_t s_sha_read_buf[1024];
|
||||
|
||||
// ★ static 缓冲区:OTA 操作由 OtaCoordinator 互斥串行化,无并发风险
|
||||
static uint8_t s_sha_read_buf[OTA_BLOCK_SIZE];
|
||||
mbedtls_sha256_init(&s_sha_ctx);
|
||||
mbedtls_sha256_starts(&s_sha_ctx, 0);
|
||||
|
||||
size_t offset = 0;
|
||||
uint32_t wdt_chunk = 0;
|
||||
while (offset < m_received) {
|
||||
size_t to_read = m_received - offset;
|
||||
if (to_read > OTA_BLOCK_SIZE) to_read = OTA_BLOCK_SIZE;
|
||||
if (to_read > sizeof(s_sha_read_buf)) to_read = sizeof(s_sha_read_buf);
|
||||
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));
|
||||
mbedtls_sha256_free(&ctx);
|
||||
mbedtls_sha256_free(&s_sha_ctx);
|
||||
return rd_err;
|
||||
}
|
||||
mbedtls_sha256_update(&ctx, s_sha_read_buf, to_read);
|
||||
mbedtls_sha256_update(&s_sha_ctx, s_sha_read_buf, to_read);
|
||||
offset += to_read;
|
||||
/* 长固件 SHA 可能 > TASK_WDT(5s);在 httpd 上跑时必须喂狗 */
|
||||
if ((++wdt_chunk & 0x0FU) == 0U) {
|
||||
(void)esp_task_wdt_reset();
|
||||
taskYIELD();
|
||||
}
|
||||
}
|
||||
|
||||
mbedtls_sha256_finish(&ctx, m_sha256);
|
||||
mbedtls_sha256_free(&ctx);
|
||||
mbedtls_sha256_finish(&s_sha_ctx, m_sha256);
|
||||
mbedtls_sha256_free(&s_sha_ctx);
|
||||
m_shaComputed = true;
|
||||
|
||||
char hex[65] = {0};
|
||||
|
||||
@@ -181,9 +181,6 @@ esp_err_t OtaCoordinator::beginGd32Storage(uint32_t total_size, ota_channel_t ch
|
||||
return h ? ESP_OK : ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// GD32 Flash 扇区大小(与 http_handlers.cpp 中的常量保持一致,用于擦除时向上对齐)
|
||||
static constexpr uint32_t kGd32FlashSectorSize = 4096U;
|
||||
|
||||
esp_err_t OtaCoordinator::prepareGd32Storage(uint32_t total_size, ota_channel_t channel)
|
||||
{
|
||||
// 1. 校验固件大小(统一 BLE/HTTP 两路径的大小检查)
|
||||
@@ -200,18 +197,12 @@ esp_err_t OtaCoordinator::prepareGd32Storage(uint32_t total_size, ota_channel_t
|
||||
return err;
|
||||
}
|
||||
|
||||
// 3. 擦除 GD32 OTA 分区(按扇区大小向上对齐)
|
||||
// ★ 原 BLE 路径未擦除分区,此处统一补齐,确保两路径行为一致。
|
||||
const uint32_t erase_size =
|
||||
((total_size + kGd32FlashSectorSize - 1U) / kGd32FlashSectorSize) * kGd32FlashSectorSize;
|
||||
DEBUG_INFO(TAG, "prepareGd32Storage: 擦除 gd32_ota 分区 %u bytes (fw=%" PRIu32 ")",
|
||||
(unsigned)erase_size, total_size);
|
||||
err = eraseGd32Partition(erase_size);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR(TAG, "prepareGd32Storage: 擦除失败: %s", esp_err_to_name(err));
|
||||
abortCurrent();
|
||||
return err;
|
||||
}
|
||||
// 3. 复位按需擦除水位(不在此处整段预擦)
|
||||
// 旧实现在 prepare 阶段 erase 整包固件对应分区:HTTP 主线程阻塞数秒~数十秒,
|
||||
// 期间不 httpd_req_recv → TCP 窗口塞满后浏览器进度先到 100%,再空等擦+写+SHA。
|
||||
// 现改为 flashWriteChunk 内按扇区 ensureErased,边收边擦写,进度与真实处理对齐。
|
||||
Gd32FlashStore::instance().beginWriteSession();
|
||||
DEBUG_INFO(TAG, "prepareGd32Storage: 按需擦除会话已复位 (fw=%" PRIu32 ")", total_size);
|
||||
|
||||
// 4. 清空旧 meta + 写入初始 meta(total_size + stored_size=0 + last_seq=0)
|
||||
clearGd32Meta();
|
||||
@@ -243,6 +234,12 @@ void OtaCoordinator::completeGd32Storage()
|
||||
unlock(h);
|
||||
}
|
||||
|
||||
esp_err_t OtaCoordinator::finishGd32StorageAfterReceive()
|
||||
{
|
||||
completeGd32Storage();
|
||||
return finalizeGd32Checksum();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GD32 存储操作(传输层统一入口,解决 H3 绕过协调器问题)
|
||||
// ── 以下 8 个 wrapper 已 inline 化至 ota_coordinator.h,直接转发到
|
||||
|
||||
@@ -124,10 +124,9 @@ public:
|
||||
esp_err_t beginGd32Storage(uint32_t total_size, ota_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief 统一 GD32 OTA 写入前的准备工作:校验大小 + begin + 擦除分区 + 清空 meta + 写入初始 meta。
|
||||
* 供 BLE seq=0 首包 / HTTP GD32 upload(非 prepared 复用路径)调用,
|
||||
* 消除两路径对"擦除/meta 初始化"序列的隐式差异(原 BLE 路径未擦除分区)。
|
||||
* 任意步骤失败时内部调 abortCurrent 清理,调用方无需再清理。
|
||||
* @brief 统一 GD32 OTA 写入前准备:校验大小 + begin + 复位按需擦除水位 + 清空 meta + 写入初始 meta。
|
||||
* 扇区擦除在 writeGd32Chunk/flashWriteChunk 内按写入范围推进(非整段预擦)。
|
||||
* WiFi/BLE 上传入口共用,任意步骤失败时内部 abortCurrent,调用方无需再清理。
|
||||
* @param total_size 固件总字节数。
|
||||
* @param channel 发起通道(WiFi/BLE)。
|
||||
* @return ESP_OK 成功;ESP_ERR_INVALID_SIZE 大小非法;ESP_ERR_INVALID_STATE 系统忙;其他擦除/写入错误。
|
||||
@@ -140,6 +139,13 @@ public:
|
||||
*/
|
||||
void completeGd32Storage();
|
||||
|
||||
/**
|
||||
* @brief 收流完成后的统一收尾:completeGd32Storage + finalizeGd32Checksum。
|
||||
* WiFi/BLE 传输层共用,避免一端只 complete、另一端漏算 SHA 的分叉。
|
||||
* @return finalizeGd32Checksum 的返回值(complete 本身无错误码)。
|
||||
*/
|
||||
esp_err_t finishGd32StorageAfterReceive();
|
||||
|
||||
// ── GD32 存储操作(传输层通过协调器操作 Flash,解决 H3 绕过问题)──
|
||||
/**
|
||||
* @brief 向 GD32 Flash 分区写入固件数据块。
|
||||
|
||||
Reference in New Issue
Block a user