feat(ota): 添加WiFi OTA期间BLE堆管理机制

- WiFi OTA时自动暂停BLE以腾出堆空间给HTTP收流
- OTA完成后自动恢复BLE栈功能
- 避免WiFi OTA与BLE共存时的堆竞争问题

fix(debug_log): 优化堆紧张时日志处理策略

- 当堆空间小于8KB时,/logs接口仍返回最小JSON避免沉默失败
- 增加堆门控机制,OTA/SPI关键日志优先保留
- UDP发送在堆紧张时跳过,避免与HTTP回包争夺LWIP堆
- 调整日志缓冲区大小,减少栈压力

chore(version): 更新固件版本至0.3.43

- 版本号从0.3.42提升到0.3.43
- 更新编译时间和Git哈希信息

refactor(can_feedback): 优化CAN反馈任务配置

- 将反馈payload最大长度改为SPI_OTA_FRAME_MAX常量
- 使用takeEvent替代takeRingRx方法
This commit is contained in:
小龙虾
2026-07-14 12:10:13 +08:00
parent 76782349a9
commit adb3386da4
18 changed files with 1739 additions and 756 deletions
+7 -4
View File
@@ -213,14 +213,17 @@ esp_err_t Esp32OtaCore::write(const uint8_t *data, size_t len)
return ESP_ERR_INVALID_STATE;
}
/* sequential 模式写时可能触发扇区擦除,喂狗防 WDT */
/* sequential 模式写时可能触发扇区擦除(禁 cache),期间 WiFi 易饿死。
* 写后 yield 一次即可;写前再 yield 会无谓拖慢吞吐(httpd 侧另有堆感知 pacing)。 */
(void)esp_task_wdt_reset();
esp_err_t err = esp_ota_write(m_handle, data, len);
taskYIELD();
if (err == ESP_OK) {
m_received += len;
} else {
DEBUG_ERR(TAG, "esp_ota_write 失败: %s (len=%u offset=%u)",
esp_err_to_name(err), (unsigned)len, (unsigned)m_received);
DEBUG_ERR(TAG, "esp_ota_write 失败: %s (len=%u offset=%u free=%lu)",
esp_err_to_name(err), (unsigned)len, (unsigned)m_received,
(unsigned long)esp_get_free_heap_size());
}
return err;
}
@@ -433,7 +436,7 @@ bool Esp32OtaCore::forceCleanup()
if (!s_mutex) return false;
// 在互斥保护下检查 s_occupied
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return false;
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) != pdTRUE) return false;
bool was_occupied = s_occupied;
if (was_occupied) {
+45 -4
View File
@@ -10,6 +10,7 @@
#include "relay/ota_relay.h"
#include "storage/gd32_flash_store.h"
#include "transport/spi_master.h"
#include "server/ble_ota.h"
#include "monitor/debug_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
@@ -18,6 +19,23 @@
#define TAG "OTA_COORD"
/** WiFi OTA 时挂起 BLE;BLE 通道自身 OTA 不挂。调用勿持 m_mutex。 */
static void wifiOtaSuspendBleIfNeeded(ota_channel_t channel)
{
if (channel != OTA_CHANNEL_WIFI) {
return;
}
(void)BleOta::instance().suspendStack();
}
/** 回到可空闲态时恢复 BLE。调用勿持 m_mutex。 */
static void wifiOtaResumeBleIfNeeded(void)
{
if (BleOta::instance().isSuspendedForWifiOta()) {
(void)BleOta::instance().resumeStack();
}
}
// ============================================================
// 单例
// ============================================================
@@ -190,10 +208,16 @@ esp_err_t OtaCoordinator::prepareGd32Storage(uint32_t total_size, ota_channel_t
return err;
}
/* WiFi GD32 上传前先挂 BLE,腾堆给 HTTP 收流 */
wifiOtaSuspendBleIfNeeded(channel);
// 2. 进入 GD32_STORING 状态(系统忙则拒绝)
err = beginGd32Storage(total_size, channel);
if (err != ESP_OK) {
DEBUG_WARN(TAG, "prepareGd32Storage: 协调器拒绝存储(系统忙)");
if (channel == OTA_CHANNEL_WIFI) {
wifiOtaResumeBleIfNeeded();
}
return err;
}
@@ -223,15 +247,20 @@ esp_err_t OtaCoordinator::prepareGd32Storage(uint32_t total_size, ota_channel_t
void OtaCoordinator::completeGd32Storage()
{
bool h = lock();
bool resume_ble = false;
if (h && m_sysState == OTA_SYS_GD32_STORING) {
m_sysState = OTA_SYS_GD32_STORED;
m_gd32StorageDone = true;
m_busySinceTick = 0; // ★ STORED 是非忙状态
resume_ble = (m_otaChannel == OTA_CHANNEL_WIFI);
DEBUG_INFO(TAG, "GD32 固件存储完成, 系统状态 → GD32_STORED");
} else if (h) {
DEBUG_WARN(TAG, "completeGd32Storage 在非预期状态: %d", (int)m_sysState);
}
unlock(h);
if (resume_ble) {
wifiOtaResumeBleIfNeeded();
}
}
esp_err_t OtaCoordinator::finishGd32StorageAfterReceive()
@@ -315,7 +344,7 @@ esp_err_t OtaCoordinator::triggerGd32Relay()
DEBUG_INFO(TAG, "触发 GD32 中继, 系统状态 → GD32_RELAYING");
uint32_t cmd = 1;
BaseType_t ret = xQueueSend(dq, &cmd, pdMS_TO_TICKS(200));
BaseType_t ret = xQueueSend(dq, &cmd, pdMS_TO_TICKS(100));
if (ret != pdTRUE) {
DEBUG_ERR(TAG, "向 OtaRelay 发送启动消息失败");
m_sysState = OTA_SYS_GD32_STORED;
@@ -360,6 +389,8 @@ void OtaCoordinator::notifyRelayComplete(bool success)
}
}
unlock(h);
/* 中继结束:若 BLE 仍因 WiFi OTA 挂起则恢复 */
wifiOtaResumeBleIfNeeded();
}
// ============================================================
@@ -451,8 +482,6 @@ esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size, ota_channel_t chann
m_otaChannel = channel; // ★ 记录发起通道
m_sysState = OTA_SYS_ESP32_RECEIVING;
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
DEBUG_INFO(TAG, "ESP32 OTA 开始: %" PRIu32 " 字节, 系统状态 → ESP32_RECEIVING (epoch=%u, channel=%d)",
total_size, m_otaEpoch, (int)channel);
pauseCanFeedbackTask(true);
} else {
DEBUG_WARN(TAG, "ESP32 OTA begin 失败: %s", esp_err_to_name(err));
@@ -465,6 +494,9 @@ esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size, ota_channel_t chann
esp_err_t OtaCoordinator::beginEsp32OtaWithRetry(uint32_t total_size, ota_channel_t channel)
{
/* WiFi 路径:在 begin 前挂 BLE,把 BT 堆腾给 OTA 收流(BLE 通道 OTA 不挂)。 */
wifiOtaSuspendBleIfNeeded(channel);
esp_err_t err = beginEsp32Ota(total_size, channel);
if (err == ESP_ERR_INVALID_STATE) {
// ★ 仅当系统确实空闲(无任何活跃 OTA)时才允许 forceCleanup:
@@ -478,6 +510,9 @@ esp_err_t OtaCoordinator::beginEsp32OtaWithRetry(uint32_t total_size, ota_channe
if (!isIdleState(cur) && cur != OTA_SYS_ERROR) {
DEBUG_WARN(TAG, "ESP32 OTA 被拒绝且系统非空闲 (state=%d),不强制清理,直接失败",
(int)cur);
if (channel == OTA_CHANNEL_WIFI) {
wifiOtaResumeBleIfNeeded();
}
return err;
}
@@ -489,6 +524,9 @@ esp_err_t OtaCoordinator::beginEsp32OtaWithRetry(uint32_t total_size, ota_channe
Esp32OtaCore::instance().forceCleanup();
err = beginEsp32Ota(total_size, channel);
}
if (err != ESP_OK && channel == OTA_CHANNEL_WIFI) {
wifiOtaResumeBleIfNeeded();
}
return err;
}
@@ -547,7 +585,7 @@ Esp32FinalizeResult OtaCoordinator::finalizeForReboot(const char *frontend_sha_h
void OtaCoordinator::restartEsp32()
{
notifyEsp32Rebooting();
vTaskDelay(pdMS_TO_TICKS(500));
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
// 不会返回
}
@@ -573,6 +611,7 @@ void OtaCoordinator::doAbortLocked()
m_gd32StorageDone = false;
m_busySinceTick = 0;
DEBUG_INFO(TAG, "已中止, 系统状态 → IDLE");
/* 释放协调器锁后由调用方 resume BLE;doAbortLocked 可能持锁,标记后外层 resume */
}
void OtaCoordinator::abortCurrent()
@@ -582,6 +621,7 @@ void OtaCoordinator::abortCurrent()
doAbortLocked();
}
unlock(h);
wifiOtaResumeBleIfNeeded();
}
uint32_t OtaCoordinator::getOtaEpoch() const
@@ -607,6 +647,7 @@ bool OtaCoordinator::abortCurrentIfEpoch(uint32_t epoch)
doAbortLocked();
unlock(h);
wifiOtaResumeBleIfNeeded();
return true;
}