feat(ota): 会话 epoch/通道标识 + BLE 中止命令 + NVS 线程安全

- 新增 ota_channel_t(WiFi/BLE) + m_otaEpoch 会话代次,
  abortCurrentIfEpoch 防止同类型重启后旧 handler 误清新会话
- 同通道再次发起 OTA 时自动中止旧会话并重开
- 统一 prepareGd32Storage(validate+begin+erase+meta),
  消除 BLE/WiFi 两路径隐式差异
- OtaRelay 拆分子步骤(doRelayPackets/doFinishAndVerify),
  状态成员私有化消除数据竞争
- Gd32FlashStore NVS 读写加 mutex,metaUpdate 原子化
- BLE OTA 新增 0xFB 中止命令(保持连接)+ 0x20 ABORTED 处理
- HTTP handler 改 per-request buffer 支持并发
- 修复 CRITICAL 状态 RSSI 恢复无法降级的缺陷
- main.cpp 关键任务失败触发看门狗重启
- style.css 增强动画 + prefers-reduced-motion 兼容
This commit is contained in:
小龙虾
2026-06-28 04:32:18 +08:00
parent 6ef97307c3
commit a93dba60d2
41 changed files with 3142 additions and 6349 deletions
+40 -25
View File
@@ -69,7 +69,7 @@ Esp32OtaCore& Esp32OtaCore::instance()
/**
* @brief 复位所有状态变量。
*/
*/
void Esp32OtaCore::resetState()
{
m_active = false;
@@ -81,6 +81,39 @@ void Esp32OtaCore::resetState()
m_ended = false;
}
/**
* @brief begin() 失败后的统一清理。
* 仅释放 mutex + 复位状态(不调 esp_ota_abort,
* begin 失败时 m_handle 未成功创建,无需也不能 abort)。
* @param code 返回给调用方的错误码。
* @return 透传 code。
*/
esp_err_t Esp32OtaCore::beginCleanupAndReturn(esp_err_t code)
{
releaseMutex();
resetState();
return code;
}
/**
* @brief finalize() 失败后的统一清理。
* 若 esp_ota_end 未成功(m_ended=false),调 esp_ota_abort 释放 handle;
* 然后复位 m_ended、释放 mutex、复位状态,返回 code。
* 与现有 abort() 的 m_ended 守卫语义一致,避免 end 成功后误调 abort。
* @param code 返回给调用方的错误码。
* @return 透传 code。
*/
esp_err_t Esp32OtaCore::finalizeCleanupAndReturn(esp_err_t code)
{
if (!m_ended) {
esp_ota_abort(m_handle);
}
m_ended = false;
releaseMutex();
resetState();
return code;
}
/**
* @brief 开启 OTA 会话。
* @param total_size 固件总大小(字节)。
@@ -125,8 +158,7 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
if (m_partition == NULL) {
DEBUG_ERR(TAG, "找不到可用的 OTA 分区");
releaseMutex();
return ESP_FAIL;
return beginCleanupAndReturn(ESP_FAIL);
}
DEBUG_INFO(TAG, "OTA 目标分区: %s, size=%u, address=0x%lx",
@@ -135,9 +167,7 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
if (total_size > m_partition->size) {
DEBUG_ERR(TAG, "固件太大: %u > 分区大小: %u",
(unsigned)total_size, (unsigned)m_partition->size);
m_partition = nullptr;
releaseMutex();
return ESP_FAIL;
return beginCleanupAndReturn(ESP_FAIL);
}
// BLE OTA 修复:传固件实际大小而非分区全尺寸,避免全分区擦除导致
@@ -151,10 +181,7 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
esp_err_t err = esp_ota_begin(m_partition, erase_size, &m_handle);
if (err != ESP_OK) {
DEBUG_ERR(TAG, "esp_ota_begin 失败: %s", esp_err_to_name(err));
m_partition = nullptr;
m_handle = 0;
releaseMutex();
return err;
return beginCleanupAndReturn(err);
}
m_received = 0;
@@ -280,11 +307,7 @@ esp_err_t Esp32OtaCore::finalize(const char *frontend_sha_hex, bool reboot,
esp_err_t sha_err = computeShaFromPartition();
if (sha_err != ESP_OK) {
DEBUG_ERR(TAG, "SHA 计算失败: %s,中止", esp_err_to_name(sha_err));
esp_ota_abort(m_handle);
m_ended = false;
releaseMutex();
resetState();
return sha_err;
return finalizeCleanupAndReturn(sha_err);
}
char device_hex[65] = {0};
@@ -311,11 +334,7 @@ esp_err_t Esp32OtaCore::finalize(const char *frontend_sha_hex, bool reboot,
} else {
DEBUG_ERR(TAG, "SHA-256 不匹配: frontend=%s device=%s",
frontend_sha_hex, device_hex);
esp_ota_abort(m_handle);
m_ended = false;
releaseMutex();
resetState();
return ESP_ERR_INVALID_VERSION;
return finalizeCleanupAndReturn(ESP_ERR_INVALID_VERSION);
}
} else if (out_device_sha_hex) {
// 未提供前端 SHA,但仍计算设备端 SHA 供输出
@@ -332,11 +351,7 @@ esp_err_t Esp32OtaCore::finalize(const char *frontend_sha_hex, bool reboot,
if (err != ESP_OK) {
DEBUG_ERR(TAG, "esp_ota_end 失败: %s (0x%x)", esp_err_to_name(err), (unsigned)err);
// end 失败时仍可 abort(未真正 end)
esp_ota_abort(m_handle);
m_ended = false;
releaseMutex();
resetState();
return err;
return finalizeCleanupAndReturn(err);
}
m_ended = true; // ★ end 成功,后续不得再 abort