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:
+40
-25
@@ -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
|
||||
|
||||
|
||||
@@ -130,6 +130,24 @@ private:
|
||||
*/
|
||||
void resetState();
|
||||
|
||||
// begin() 失败后的统一清理:释放 mutex + resetState() + 返回 code
|
||||
// 不调 esp_ota_abort(begin 失败时 m_handle 未成功创建)
|
||||
/**
|
||||
* @brief begin() 失败后的统一清理。
|
||||
* @param code 返回给调用方的错误码。
|
||||
* @return 透传 code。
|
||||
*/
|
||||
esp_err_t beginCleanupAndReturn(esp_err_t code);
|
||||
|
||||
// finalize() 失败后的统一清理:若 m_ended=false 调 esp_ota_abort 释放 handle;
|
||||
// 再复位 m_ended + 释放 mutex + resetState() + 返回 code
|
||||
/**
|
||||
* @brief finalize() 失败后的统一清理。
|
||||
* @param code 返回给调用方的错误码。
|
||||
* @return 透传 code。
|
||||
*/
|
||||
esp_err_t finalizeCleanupAndReturn(esp_err_t code);
|
||||
|
||||
bool m_active = false;
|
||||
esp_ota_handle_t m_handle = 0;
|
||||
const esp_partition_t *m_partition = nullptr;
|
||||
|
||||
+216
-88
@@ -124,41 +124,110 @@ void OtaCoordinator::init()
|
||||
// ============================================================
|
||||
// GD32 存储阶段
|
||||
// ============================================================
|
||||
esp_err_t OtaCoordinator::beginGd32Storage(uint32_t total_size)
|
||||
esp_err_t OtaCoordinator::beginGd32Storage(uint32_t total_size, ota_channel_t channel)
|
||||
{
|
||||
bool h = lock();
|
||||
|
||||
// ★ 先做卡死自愈检查:若之前忙状态卡死,自动转 ERROR 继续后续流程
|
||||
if (h) watchdogCheckLocked();
|
||||
|
||||
// ★ 同类型重启:若上一次 GD32 存储仍在进行中(STORING)且来自同一通道,
|
||||
// 自动中止旧存储并重新开始。覆盖"上传过程中刷新网页再上传"场景。
|
||||
// 跨通道(BLE→WiFi 或 WiFi→BLE)仍拒绝,保持互斥。
|
||||
// RELAYING 是中继阶段,不可中断,也拒绝。
|
||||
if (h && m_sysState == OTA_SYS_GD32_STORING) {
|
||||
if (m_otaChannel != channel) {
|
||||
DEBUG_WARN(TAG, "GD32 存储中(通道=%d),拒绝跨通道请求(通道=%d)",
|
||||
(int)m_otaChannel, (int)channel);
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
DEBUG_WARN(TAG, "GD32 存储仍在进行中(同通道),中止旧存储并重新开始");
|
||||
Gd32FlashStore::instance().metaClear();
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_busySinceTick = 0;
|
||||
// epoch 将在下方设置 GD32_STORING 后递增
|
||||
}
|
||||
|
||||
// 系统忙检查:仅拒绝真正"在传输"的活跃状态
|
||||
// STORED / COMPLETE 是"待中继/已中继"的非活跃状态 → 允许新存储覆盖
|
||||
if (h && (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
||||
m_sysState == OTA_SYS_ESP32_REBOOTING ||
|
||||
m_sysState == OTA_SYS_GD32_STORING ||
|
||||
m_sysState == OTA_SYS_GD32_RELAYING)) {
|
||||
DEBUG_WARN(TAG, "系统忙 (state=%d),拒绝 GD32 存储请求", (int)m_sysState);
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 如果之前已有存储但未中继(STORED)或刚中继完(COMPLETE),先清理再开始新存储
|
||||
// 如果之前已有存储但未中继(STORED)或刚中继完(COMPLETE),允许新存储覆盖。
|
||||
// 注意:清理 NVS meta 不在此处做,避免与紧跟的 prepareGd32Storage 内
|
||||
// clearGd32Meta + saveGd32Meta 重复一次写入,仅做状态注释说明。
|
||||
if (h && (m_sysState == OTA_SYS_GD32_STORED || m_sysState == OTA_SYS_GD32_COMPLETE)) {
|
||||
DEBUG_INFO(TAG, "清理旧 GD32 存储状态,开始新存储 (size=%" PRIu32 ")", total_size);
|
||||
Gd32FlashStore::instance().metaClear();
|
||||
DEBUG_INFO(TAG, "覆盖旧 GD32 存储状态,开始新存储 (size=%" PRIu32 ")", total_size);
|
||||
}
|
||||
|
||||
if (h) {
|
||||
m_otaEpoch++; // ★ 新会话开始,递增 epoch
|
||||
m_otaChannel = channel; // ★ 记录发起通道
|
||||
m_sysState = OTA_SYS_GD32_STORING;
|
||||
m_gd32StorageDone = false;
|
||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录忙状态起始时刻
|
||||
DEBUG_INFO(TAG, "GD32 固件存储开始: %" PRIu32 " 字节, 系统状态 → GD32_STORING", total_size);
|
||||
DEBUG_INFO(TAG, "GD32 固件存储开始: %" PRIu32 " 字节, 系统状态 → GD32_STORING (epoch=%u, channel=%d)",
|
||||
total_size, m_otaEpoch, (int)channel);
|
||||
}
|
||||
unlock(h);
|
||||
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 两路径的大小检查)
|
||||
esp_err_t err = validateGd32Size(total_size);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_WARN(TAG, "prepareGd32Storage: 固件大小非法 raw=%" PRIu32, total_size);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 2. 进入 GD32_STORING 状态(系统忙则拒绝)
|
||||
err = beginGd32Storage(total_size, channel);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_WARN(TAG, "prepareGd32Storage: 协调器拒绝存储(系统忙)");
|
||||
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;
|
||||
}
|
||||
|
||||
// 4. 清空旧 meta + 写入初始 meta(total_size + stored_size=0 + last_seq=0)
|
||||
clearGd32Meta();
|
||||
ota_meta_t meta = {};
|
||||
meta.total_size = total_size;
|
||||
meta.stored_size = 0;
|
||||
meta.last_seq = 0;
|
||||
err = saveGd32Meta(&meta);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR(TAG, "prepareGd32Storage: 元数据保存失败: %s", esp_err_to_name(err));
|
||||
abortCurrent();
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void OtaCoordinator::completeGd32Storage()
|
||||
{
|
||||
bool h = lock();
|
||||
@@ -175,47 +244,9 @@ void OtaCoordinator::completeGd32Storage()
|
||||
|
||||
// ============================================================
|
||||
// GD32 存储操作(传输层统一入口,解决 H3 绕过协调器问题)
|
||||
// ── 以下 8 个 wrapper 已 inline 化至 ota_coordinator.h,直接转发到
|
||||
// Gd32FlashStore::instance(),消除跨 TU 调用开销。
|
||||
// ============================================================
|
||||
esp_err_t OtaCoordinator::writeGd32Chunk(uint32_t offset, const uint8_t *data, size_t len)
|
||||
{
|
||||
return Gd32FlashStore::instance().flashWriteChunk(offset, data, len);
|
||||
}
|
||||
|
||||
esp_err_t OtaCoordinator::saveGd32Meta(const ota_meta_t *meta)
|
||||
{
|
||||
return Gd32FlashStore::instance().metaSave(meta);
|
||||
}
|
||||
|
||||
esp_err_t OtaCoordinator::loadGd32Meta(ota_meta_t *meta)
|
||||
{
|
||||
return Gd32FlashStore::instance().metaLoad(meta);
|
||||
}
|
||||
|
||||
esp_err_t OtaCoordinator::updateGd32Meta(uint32_t stored_size, uint32_t last_seq)
|
||||
{
|
||||
return Gd32FlashStore::instance().metaUpdate(stored_size, last_seq);
|
||||
}
|
||||
|
||||
esp_err_t OtaCoordinator::clearGd32Meta()
|
||||
{
|
||||
return Gd32FlashStore::instance().metaClear();
|
||||
}
|
||||
|
||||
esp_err_t OtaCoordinator::verifyGd32Firmware(const uint8_t frontend_sha32[32],
|
||||
uint8_t out_device_sha[32])
|
||||
{
|
||||
return Gd32FlashStore::instance().verifyFrontendSha(frontend_sha32, out_device_sha);
|
||||
}
|
||||
|
||||
esp_err_t OtaCoordinator::finalizeGd32Checksum()
|
||||
{
|
||||
return Gd32FlashStore::instance().metaFinalizeChecksum();
|
||||
}
|
||||
|
||||
esp_err_t OtaCoordinator::eraseGd32Partition(uint32_t size)
|
||||
{
|
||||
return Gd32FlashStore::instance().erasePartition(size);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GD32 中继阶段
|
||||
@@ -369,25 +400,42 @@ void OtaCoordinator::notifyBleCoexEnd()
|
||||
// ============================================================
|
||||
// ESP32 OTA 阶段
|
||||
// ============================================================
|
||||
esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
|
||||
esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size, ota_channel_t channel)
|
||||
{
|
||||
bool h = lock();
|
||||
|
||||
// ★ 卡死自愈检查
|
||||
if (h) watchdogCheckLocked();
|
||||
|
||||
// 系统忙检查:GD32 传输/中继进行中,或另一通道的 ESP32 OTA 进行中,均拒绝。
|
||||
// ★ 必须显式拒绝 ESP32_RECEIVING/FINALIZING/REBOOTING:
|
||||
// ★ 同类型重启:若上一次 ESP32 OTA 仍在接收中(RECEIVING)且来自同一通道,
|
||||
// 自动中止旧会话并重新开始。覆盖"上传完成后刷新网页再上传"场景。
|
||||
// 跨通道(BLE→WiFi 或 WiFi→BLE)仍拒绝,保持互斥。
|
||||
// FINALIZING/REBOOTING 是不可逆阶段,也拒绝。
|
||||
if (h && m_sysState == OTA_SYS_ESP32_RECEIVING) {
|
||||
if (m_otaChannel != channel) {
|
||||
DEBUG_WARN(TAG, "ESP32 OTA 接收中(通道=%d),拒绝跨通道请求(通道=%d)",
|
||||
(int)m_otaChannel, (int)channel);
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
DEBUG_WARN(TAG, "ESP32 OTA 仍在接收中(同通道),中止旧会话并重新开始");
|
||||
Esp32OtaCore::instance().abort();
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_busySinceTick = 0;
|
||||
// epoch 将在下方 begin 成功后递增,自动使旧 handler 的清理代码失效
|
||||
}
|
||||
|
||||
// 系统忙检查:GD32 传输/中继进行中,或 ESP32 收尾/重启中,均拒绝。
|
||||
// ★ 必须显式拒绝 ESP32_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 ||
|
||||
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());
|
||||
(int)m_sysState, stateToString(m_sysState));
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
@@ -397,9 +445,12 @@ esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
|
||||
// 委托 Esp32OtaCore 处理分区/互斥逻辑
|
||||
err = Esp32OtaCore::instance().begin(total_size);
|
||||
if (err == ESP_OK) {
|
||||
m_otaEpoch++; // ★ 新会话开始,递增 epoch(使旧 handler 的 abortCurrentIfEpoch 失效)
|
||||
m_otaChannel = channel; // ★ 记录发起通道
|
||||
m_sysState = OTA_SYS_ESP32_RECEIVING;
|
||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
|
||||
DEBUG_INFO(TAG, "ESP32 OTA 开始: %" PRIu32 " 字节, 系统状态 → ESP32_RECEIVING", total_size);
|
||||
DEBUG_INFO(TAG, "ESP32 OTA 开始: %" PRIu32 " 字节, 系统状态 → ESP32_RECEIVING (epoch=%u, channel=%d)",
|
||||
total_size, m_otaEpoch, (int)channel);
|
||||
} else {
|
||||
DEBUG_WARN(TAG, "ESP32 OTA begin 失败: %s", esp_err_to_name(err));
|
||||
}
|
||||
@@ -409,9 +460,9 @@ esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t OtaCoordinator::beginEsp32OtaWithRetry(uint32_t total_size)
|
||||
esp_err_t OtaCoordinator::beginEsp32OtaWithRetry(uint32_t total_size, ota_channel_t channel)
|
||||
{
|
||||
esp_err_t err = beginEsp32Ota(total_size);
|
||||
esp_err_t err = beginEsp32Ota(total_size, channel);
|
||||
if (err == ESP_ERR_INVALID_STATE) {
|
||||
// ★ 仅当系统确实空闲(无任何活跃 OTA)时才允许 forceCleanup:
|
||||
// 这种情况只可能是"锁残留"——前一次 OTA 异常退出没释放全局互斥,
|
||||
@@ -419,15 +470,17 @@ esp_err_t OtaCoordinator::beginEsp32OtaWithRetry(uint32_t total_size)
|
||||
// 若系统正处于任何活跃 OTA(ESP32/GD32),绝不强制清理——那会
|
||||
// 把另一通道正在进行的 esp_ota_handle 中止、释放互斥,导致
|
||||
// 两通道并发写同一 OTA 分区(之前 BLE 与 WiFi 可同时 OTA 的根因)。
|
||||
if (!isIdle()) {
|
||||
// 避免连续 isIdle() + getSystemState() 各加锁一次(共 2 次锁)。
|
||||
ota_system_state_t cur = getSystemState();
|
||||
if (!isIdleState(cur)) {
|
||||
DEBUG_WARN(TAG, "ESP32 OTA 被拒绝且系统非空闲 (state=%d),不强制清理,直接失败",
|
||||
(int)getSystemState());
|
||||
(int)cur);
|
||||
return err;
|
||||
}
|
||||
// 协调器空闲但 Esp32OtaCore.begin 仍被拒 → 锁残留,强制清理后重试
|
||||
DEBUG_WARN(TAG, "ESP32 OTA 被拒绝但系统空闲,判定为锁残留,强制清理状态机后重试");
|
||||
Esp32OtaCore::instance().forceCleanup();
|
||||
err = beginEsp32Ota(total_size);
|
||||
err = beginEsp32Ota(total_size, channel);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
@@ -507,29 +560,60 @@ int OtaCoordinator::esp32FinalizeErrCategory(esp_err_t err)
|
||||
// ============================================================
|
||||
// 中止
|
||||
// ============================================================
|
||||
void OtaCoordinator::doAbortLocked()
|
||||
{
|
||||
DEBUG_INFO(TAG, "中止当前 OTA 操作 (state=%d, epoch=%u)", (int)m_sysState, m_otaEpoch);
|
||||
|
||||
if (m_sysState == OTA_SYS_GD32_STORING) {
|
||||
Gd32FlashStore::instance().metaClear();
|
||||
} else if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
||||
OtaRelay::instance().reset();
|
||||
} else if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||
m_sysState == OTA_SYS_ESP32_FINALIZING) {
|
||||
Esp32OtaCore::instance().abort();
|
||||
}
|
||||
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_gd32StorageDone = false;
|
||||
m_busySinceTick = 0;
|
||||
DEBUG_INFO(TAG, "已中止, 系统状态 → IDLE");
|
||||
}
|
||||
|
||||
void OtaCoordinator::abortCurrent()
|
||||
{
|
||||
bool h = lock();
|
||||
if (h) {
|
||||
DEBUG_INFO(TAG, "中止当前 OTA 操作 (state=%d)", (int)m_sysState);
|
||||
|
||||
if (m_sysState == OTA_SYS_GD32_STORING) {
|
||||
Gd32FlashStore::instance().metaClear();
|
||||
} else if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
||||
OtaRelay::instance().reset();
|
||||
} else if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||
m_sysState == OTA_SYS_ESP32_FINALIZING) {
|
||||
Esp32OtaCore::instance().abort();
|
||||
}
|
||||
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_gd32StorageDone = false;
|
||||
m_busySinceTick = 0; // ★ 离开忙状态
|
||||
DEBUG_INFO(TAG, "已中止, 系统状态 → IDLE");
|
||||
doAbortLocked();
|
||||
}
|
||||
unlock(h);
|
||||
}
|
||||
|
||||
uint32_t OtaCoordinator::getOtaEpoch() const
|
||||
{
|
||||
bool h = lock();
|
||||
uint32_t epoch = m_otaEpoch;
|
||||
unlock(h);
|
||||
return epoch;
|
||||
}
|
||||
|
||||
bool OtaCoordinator::abortCurrentIfEpoch(uint32_t epoch)
|
||||
{
|
||||
bool h = lock();
|
||||
if (!h) return false;
|
||||
|
||||
if (m_otaEpoch != epoch) {
|
||||
// ★ epoch 不匹配:新会话已接管,旧 handler 不应中止
|
||||
unlock(h);
|
||||
return false;
|
||||
}
|
||||
|
||||
// epoch 匹配:执行中止(共享 doAbortLocked,已持锁)
|
||||
doAbortLocked();
|
||||
|
||||
unlock(h);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 状态查询
|
||||
// ============================================================
|
||||
@@ -543,35 +627,61 @@ ota_system_state_t OtaCoordinator::getSystemState() const
|
||||
return s;
|
||||
}
|
||||
|
||||
/** @brief 判断系统是否处于空闲状态(IDLE/STORED/COMPLETE)。 */
|
||||
bool OtaCoordinator::isIdle() const
|
||||
ota_channel_t OtaCoordinator::getOtaChannel() const
|
||||
{
|
||||
return (getSystemState() == OTA_SYS_IDLE ||
|
||||
getSystemState() == OTA_SYS_GD32_STORED ||
|
||||
getSystemState() == OTA_SYS_GD32_COMPLETE);
|
||||
bool h = lock();
|
||||
ota_channel_t ch = m_otaChannel;
|
||||
unlock(h);
|
||||
return ch;
|
||||
}
|
||||
|
||||
/** @brief 判断 GD32 通道是否正忙(STORING 或 RELAYING)。 */
|
||||
bool OtaCoordinator::isGd32Busy() const
|
||||
/** @brief 判断给定状态值是否属于空闲态(不加锁,纯判断)。
|
||||
* 供持锁路径或已取一次状态的调用方复用,避免连续加锁。 */
|
||||
bool OtaCoordinator::isIdleState(ota_system_state_t s) const
|
||||
{
|
||||
return (s == OTA_SYS_IDLE ||
|
||||
s == OTA_SYS_GD32_STORED ||
|
||||
s == OTA_SYS_GD32_COMPLETE);
|
||||
}
|
||||
|
||||
/** @brief 判断给定状态值是否属于 GD32 忙态(不加锁,纯判断)。 */
|
||||
bool OtaCoordinator::isGd32BusyState(ota_system_state_t s) const
|
||||
{
|
||||
ota_system_state_t s = getSystemState();
|
||||
return (s == OTA_SYS_GD32_STORING ||
|
||||
s == OTA_SYS_GD32_RELAYING);
|
||||
}
|
||||
|
||||
/** @brief 判断 ESP32 通道是否正忙(RECEIVING/FINALIZING/REBOOTING)。 */
|
||||
bool OtaCoordinator::isEsp32Busy() const
|
||||
/** @brief 判断给定状态值是否属于 ESP32 忙态(不加锁,纯判断)。 */
|
||||
bool OtaCoordinator::isEsp32BusyState(ota_system_state_t s) const
|
||||
{
|
||||
ota_system_state_t s = getSystemState();
|
||||
return (s == OTA_SYS_ESP32_RECEIVING ||
|
||||
s == OTA_SYS_ESP32_FINALIZING ||
|
||||
s == OTA_SYS_ESP32_REBOOTING);
|
||||
}
|
||||
|
||||
/** @brief 获取系统状态的字符串名称(用于日志和前端显示)。 */
|
||||
const char* OtaCoordinator::getSystemStateString() const
|
||||
/** @brief 判断系统是否处于空闲状态(IDLE/STORED/COMPLETE)。 */
|
||||
bool OtaCoordinator::isIdle() const
|
||||
{
|
||||
switch (getSystemState()) {
|
||||
return isIdleState(getSystemState());
|
||||
}
|
||||
|
||||
/** @brief 判断 GD32 通道是否正忙(STORING 或 RELAYING)。 */
|
||||
bool OtaCoordinator::isGd32Busy() const
|
||||
{
|
||||
return isGd32BusyState(getSystemState());
|
||||
}
|
||||
|
||||
/** @brief 判断 ESP32 通道是否正忙(RECEIVING/FINALIZING/REBOOTING)。 */
|
||||
bool OtaCoordinator::isEsp32Busy() const
|
||||
{
|
||||
return isEsp32BusyState(getSystemState());
|
||||
}
|
||||
|
||||
/** @brief 把状态枚举转成可读字符串(不加锁,纯函数)。
|
||||
* 供持锁代码路径和已取一次状态值的调用方复用,避免二次加锁。 */
|
||||
const char* OtaCoordinator::stateToString(ota_system_state_t s)
|
||||
{
|
||||
switch (s) {
|
||||
case OTA_SYS_IDLE: return "IDLE";
|
||||
case OTA_SYS_GD32_STORING: return "GD32_STORING";
|
||||
case OTA_SYS_GD32_STORED: return "GD32_STORED";
|
||||
@@ -585,6 +695,13 @@ const char* OtaCoordinator::getSystemStateString() const
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief 获取系统状态的字符串名称(用于日志和前端显示)。
|
||||
* 委托 stateToString(getSystemState()),保持向后兼容。 */
|
||||
const char* OtaCoordinator::getSystemStateString() const
|
||||
{
|
||||
return stateToString(getSystemState());
|
||||
}
|
||||
|
||||
/** @brief 获取 GD32 APP 存活状态(委托 OtaRelay)。 */
|
||||
bool OtaCoordinator::getGd32AppAlive() const
|
||||
{
|
||||
@@ -608,14 +725,22 @@ OtaSnapshot OtaCoordinator::getSnapshot() const
|
||||
{
|
||||
OtaSnapshot snap{};
|
||||
|
||||
// ── coordinator 自身状态(一次 mutex)──
|
||||
// ── coordinator 自身状态(一次 mutex,避免二次加锁导致快照不一致)──
|
||||
// 持锁时一次性取 m_sysState,并调不加锁的 stateToString()
|
||||
// 就地转字符串。旧实现释放锁后再调 getSystemStateString() 二次加锁,
|
||||
// 两次取值之间状态可能变化,导致 snap.sys_state 与 snap.sys_state_str
|
||||
// 描述不同时刻状态。
|
||||
bool h = lock();
|
||||
if (h) {
|
||||
snap.sys_state = m_sysState;
|
||||
snap.storage_done = m_gd32StorageDone;
|
||||
// 就地把状态枚举转字符串,与 sys_state 来自同一时刻
|
||||
snap.sys_state_str = stateToString(snap.sys_state);
|
||||
unlock(h);
|
||||
} else {
|
||||
// 加锁失败时也填一个与默认 sys_state(=IDLE) 一致的字符串
|
||||
snap.sys_state_str = stateToString(snap.sys_state);
|
||||
}
|
||||
snap.sys_state_str = getSystemStateString();
|
||||
|
||||
// ── GD32 NVS 元数据 ──
|
||||
Gd32FlashStore::instance().metaLoad(&snap.meta);
|
||||
@@ -629,6 +754,9 @@ OtaSnapshot OtaCoordinator::getSnapshot() const
|
||||
// ============================================================
|
||||
// GD32 APP 探测(PR2: 从 /system/status 移出)
|
||||
// ============================================================
|
||||
// 注:本函数与 OtaRelay::probeAndCacheAppAlive 逻辑相似,但因依赖方向
|
||||
// (OtaCoordinator → OtaRelay)不应调用 OtaRelay 的内部实现细节,
|
||||
// 故此处保留独立实现。
|
||||
esp_err_t OtaCoordinator::probeGd32AppOnce(gd32_version_t *out_version, uint32_t *out_uptime)
|
||||
{
|
||||
uint32_t uptime = 0;
|
||||
|
||||
+155
-12
@@ -19,6 +19,7 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_err.h"
|
||||
#include "app_config.h" // OTA_MAX_FIRMWARE_SIZE / GD32_OTA_PACKET_SIZE(validateGd32Size 用)
|
||||
#include "storage/gd32_flash_store.h"
|
||||
#include "relay/ota_relay.h" // IRelayListener 接口定义 + RelaySnapshot(H4 解耦)
|
||||
|
||||
@@ -37,6 +38,14 @@ typedef enum {
|
||||
OTA_SYS_ERROR // 错误状态
|
||||
} ota_system_state_t;
|
||||
|
||||
// ============================================================
|
||||
// OTA 传输通道标识(用于同类型重启时区分同通道/跨通道)
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
OTA_CHANNEL_WIFI = 0,
|
||||
OTA_CHANNEL_BLE = 1,
|
||||
} ota_channel_t;
|
||||
|
||||
// ============================================================
|
||||
// OTA 系统全量只读快照(PR1: 收口状态读取)
|
||||
// ============================================================
|
||||
@@ -108,9 +117,21 @@ public:
|
||||
* @brief 传输层开始接收 GD32 固件前调用。
|
||||
* 若系统正忙(ESP32 OTA 进行中或 GD32 中继中),返回失败。
|
||||
* @param total_size 固件总字节数。
|
||||
* @param channel 发起通道(WiFi/BLE),用于同类型重启时区分同通道/跨通道。
|
||||
* @return ESP_OK 成功,ESP_ERR_INVALID_STATE 系统忙。
|
||||
*/
|
||||
esp_err_t beginGd32Storage(uint32_t total_size);
|
||||
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 清理,调用方无需再清理。
|
||||
* @param total_size 固件总字节数。
|
||||
* @param channel 发起通道(WiFi/BLE)。
|
||||
* @return ESP_OK 成功;ESP_ERR_INVALID_SIZE 大小非法;ESP_ERR_INVALID_STATE 系统忙;其他擦除/写入错误。
|
||||
*/
|
||||
esp_err_t prepareGd32Storage(uint32_t total_size, ota_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief GD32 固件接收完毕时调用。
|
||||
@@ -127,21 +148,30 @@ public:
|
||||
* @param len 数据长度。
|
||||
* @return ESP_OK 成功。
|
||||
*/
|
||||
esp_err_t writeGd32Chunk(uint32_t offset, const uint8_t *data, size_t len);
|
||||
inline esp_err_t writeGd32Chunk(uint32_t offset, const uint8_t *data, size_t len)
|
||||
{
|
||||
return Gd32FlashStore::instance().flashWriteChunk(offset, data, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存 GD32 OTA 元数据。
|
||||
* @param meta 元数据结构体。
|
||||
* @return ESP_OK 成功。
|
||||
*/
|
||||
esp_err_t saveGd32Meta(const ota_meta_t *meta);
|
||||
inline esp_err_t saveGd32Meta(const ota_meta_t *meta)
|
||||
{
|
||||
return Gd32FlashStore::instance().metaSave(meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载 GD32 OTA 元数据。
|
||||
* @param meta 输出元数据结构体。
|
||||
* @return ESP_OK 成功。
|
||||
*/
|
||||
esp_err_t loadGd32Meta(ota_meta_t *meta);
|
||||
inline esp_err_t loadGd32Meta(ota_meta_t *meta)
|
||||
{
|
||||
return Gd32FlashStore::instance().metaLoad(meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新 GD32 OTA 元数据的进度。
|
||||
@@ -149,13 +179,19 @@ public:
|
||||
* @param last_seq 最后包序号。
|
||||
* @return ESP_OK 成功。
|
||||
*/
|
||||
esp_err_t updateGd32Meta(uint32_t stored_size, uint32_t last_seq);
|
||||
inline esp_err_t updateGd32Meta(uint32_t stored_size, uint32_t last_seq)
|
||||
{
|
||||
return Gd32FlashStore::instance().metaUpdate(stored_size, last_seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除 GD32 OTA 元数据。
|
||||
* @return ESP_OK 成功。
|
||||
*/
|
||||
esp_err_t clearGd32Meta();
|
||||
inline esp_err_t clearGd32Meta()
|
||||
{
|
||||
return Gd32FlashStore::instance().metaClear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 校验前端 SHA-256 与设备端固件是否一致。
|
||||
@@ -163,21 +199,52 @@ public:
|
||||
* @param out_device_sha 输出设备端 32 字节 SHA(可为 nullptr)。
|
||||
* @return ESP_OK 匹配,ESP_ERR_INVALID_CRC 不匹配。
|
||||
*/
|
||||
esp_err_t verifyGd32Firmware(const uint8_t frontend_sha32[32],
|
||||
uint8_t out_device_sha[32]);
|
||||
inline esp_err_t verifyGd32Firmware(const uint8_t frontend_sha32[32],
|
||||
uint8_t out_device_sha[32])
|
||||
{
|
||||
return Gd32FlashStore::instance().verifyFrontendSha(frontend_sha32, out_device_sha);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算 GD32 固件 SHA-256 并写入元数据。
|
||||
* @return ESP_OK 成功。
|
||||
*/
|
||||
esp_err_t finalizeGd32Checksum();
|
||||
inline esp_err_t finalizeGd32Checksum()
|
||||
{
|
||||
return Gd32FlashStore::instance().metaFinalizeChecksum();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 擦除 GD32 OTA 分区。
|
||||
* @param size 擦除大小。
|
||||
* @return ESP_OK 成功。
|
||||
*/
|
||||
esp_err_t eraseGd32Partition(uint32_t size);
|
||||
inline esp_err_t eraseGd32Partition(uint32_t size)
|
||||
{
|
||||
return Gd32FlashStore::instance().erasePartition(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 校验 GD32 固件大小是否合法(BLE/HTTP 双通道共用)。
|
||||
*
|
||||
* 统一规则:raw==0 或 raw>MAX 拒绝;按 GD32_OTA_PACKET_SIZE 向上取整后仍>MAX 拒绝。
|
||||
* 调用方各自翻译为通道特定错误码(BLE: BLE_OTA_FINISH_ERR_GD32_SIZE;HTTP: HTTPD_500)。
|
||||
*
|
||||
* @param raw_size 固件原始字节数。
|
||||
* @return ESP_OK 合法;ESP_ERR_INVALID_SIZE 非法。
|
||||
*/
|
||||
inline esp_err_t validateGd32Size(uint32_t raw_size) const
|
||||
{
|
||||
if (raw_size == 0U || raw_size > (uint32_t)OTA_MAX_FIRMWARE_SIZE) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
const uint32_t packet_size = (uint32_t)GD32_OTA_PACKET_SIZE;
|
||||
const uint32_t rounded = ((raw_size + packet_size - 1U) / packet_size) * packet_size;
|
||||
if (rounded > (uint32_t)OTA_MAX_FIRMWARE_SIZE) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ── GD32 中继阶段 ──
|
||||
/**
|
||||
@@ -205,17 +272,19 @@ public:
|
||||
* @brief ESP32 OTA 开始接收固件。
|
||||
* 内部委托 Esp32OtaCore::begin()。
|
||||
* @param total_size 固件总字节数。
|
||||
* @param channel 发起通道(WiFi/BLE),用于同类型重启时区分同通道/跨通道。
|
||||
* @return ESP_OK 成功,ESP_ERR_INVALID_STATE 系统忙或通道被占用。
|
||||
*/
|
||||
esp_err_t beginEsp32Ota(uint32_t total_size);
|
||||
esp_err_t beginEsp32Ota(uint32_t total_size, ota_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief 发起 ESP32 OTA,若被 ESP_ERR_INVALID_STATE 拒绝则 forceCleanup 后重试一次。
|
||||
* 封装 BLE/WiFi 两端完全相同的重试模式,调用方只需调一次。
|
||||
* @param total_size 固件总字节数。
|
||||
* @param channel 发起通道(WiFi/BLE)。
|
||||
* @return ESP_OK 成功;其他 = 协调器拒绝或 Esp32OtaCore::begin 失败。
|
||||
*/
|
||||
esp_err_t beginEsp32OtaWithRetry(uint32_t total_size);
|
||||
esp_err_t beginEsp32OtaWithRetry(uint32_t total_size, ota_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief ESP32 OTA 固件接收完毕,准备收尾。
|
||||
@@ -257,6 +326,25 @@ public:
|
||||
*/
|
||||
void abortCurrent();
|
||||
|
||||
/**
|
||||
* @brief 获取当前 OTA 会话的 epoch(代次)。
|
||||
* 每次 beginEsp32Ota/beginGd32Storage 成功时递增。
|
||||
* 传输层 handler 在 begin 成功后捕获 epoch,
|
||||
* 在失败清理时通过 abortCurrentIfEpoch() 检查是否仍为当前会话,
|
||||
* 避免旧 handler 的清理代码误中止新会话(同类型重启场景)。
|
||||
* @return 当前 epoch 值。
|
||||
*/
|
||||
uint32_t getOtaEpoch() const;
|
||||
|
||||
/**
|
||||
* @brief 仅当 epoch 匹配当前会话时才中止 OTA。
|
||||
* 用于旧 handler 的失败清理路径:若新请求已经接管(epoch 不匹配),
|
||||
* 旧 handler 不应调 abortCurrent,否则会误中止新会话。
|
||||
* @param epoch 调用方在 begin 成功时捕获的 epoch。
|
||||
* @return true 已中止(epoch 匹配),false 未中止(新会话已接管)。
|
||||
*/
|
||||
bool abortCurrentIfEpoch(uint32_t epoch);
|
||||
|
||||
// ── 状态查询 ──
|
||||
/**
|
||||
* @brief 一次性读取系统 OTA 全量状态快照(PR1: 收口状态读取)。
|
||||
@@ -282,26 +370,68 @@ public:
|
||||
*/
|
||||
ota_system_state_t getSystemState() const;
|
||||
|
||||
/**
|
||||
* @brief 获取当前 OTA 会话的发起通道(WiFi/BLE)。
|
||||
* 仅在活跃状态(ESP32_RECEIVING / GD32_STORING)下有意义;
|
||||
* IDLE 等非活跃状态返回值未定义(默认 OTA_CHANNEL_WIFI)。
|
||||
* @return 当前会话通道枚举值。
|
||||
*/
|
||||
ota_channel_t getOtaChannel() const;
|
||||
|
||||
/**
|
||||
* @brief 把状态枚举转成可读字符串(不加锁,纯函数)。
|
||||
* 供持锁代码路径和已取一次状态值的调用方复用,避免二次加锁。
|
||||
* @param s 状态枚举值。
|
||||
* @return 状态名称字符串(如 "IDLE"、"GD32_STORING" 等)。
|
||||
*/
|
||||
static const char* stateToString(ota_system_state_t s);
|
||||
|
||||
/**
|
||||
* @brief 检查给定状态值是否属于空闲态(不加锁,纯判断)。
|
||||
* 调用方先 getSystemState() 取一次再传入,避免连续加锁。
|
||||
* @param s 状态枚举值。
|
||||
* @return true 表示 IDLE/STORED/COMPLETE。
|
||||
*/
|
||||
bool isIdleState(ota_system_state_t s) const;
|
||||
|
||||
/**
|
||||
* @brief 检查给定状态值是否属于 GD32 忙态(不加锁,纯判断)。
|
||||
* @param s 状态枚举值。
|
||||
* @return true 表示 STORING 或 RELAYING。
|
||||
*/
|
||||
bool isGd32BusyState(ota_system_state_t s) const;
|
||||
|
||||
/**
|
||||
* @brief 检查给定状态值是否属于 ESP32 忙态(不加锁,纯判断)。
|
||||
* @param s 状态枚举值。
|
||||
* @return true 表示 RECEIVING/FINALIZING/REBOOTING。
|
||||
*/
|
||||
bool isEsp32BusyState(ota_system_state_t s) const;
|
||||
|
||||
/**
|
||||
* @brief 检查系统是否空闲(可接受新的 OTA 请求)。
|
||||
* 内部仍加锁取一次状态后调 isIdleState()。
|
||||
* @return true 系统空闲(IDLE/STORED/COMPLETE)。
|
||||
*/
|
||||
bool isIdle() const;
|
||||
|
||||
/**
|
||||
* @brief 检查是否有 GD32 相关操作正在进行。
|
||||
* 内部仍加锁取一次状态后调 isGd32BusyState()。
|
||||
* @return true 表示 STORING 或 RELAYING。
|
||||
*/
|
||||
bool isGd32Busy() const;
|
||||
|
||||
/**
|
||||
* @brief 检查是否有 ESP32 OTA 正在进行。
|
||||
* 内部仍加锁取一次状态后调 isEsp32BusyState()。
|
||||
* @return true 表示 RECEIVING/FINALIZING/REBOOTING。
|
||||
*/
|
||||
bool isEsp32Busy() const;
|
||||
|
||||
/**
|
||||
* @brief 获取系统状态的可读字符串描述。
|
||||
* 内部委托 stateToString(getSystemState()),保持向后兼容。
|
||||
* @return 状态名称字符串(如 "IDLE"、"GD32_STORING" 等)。
|
||||
*/
|
||||
const char* getSystemStateString() const;
|
||||
@@ -385,6 +515,10 @@ private:
|
||||
// 必须在已持锁状态下调用。
|
||||
void watchdogCheckLocked();
|
||||
|
||||
// ★ 已持锁版本的中止序列:根据当前 m_sysState 调用对应子模块的清理,
|
||||
// 并把状态机复位为 IDLE。abortCurrent / abortCurrentIfEpoch 共享此实现。
|
||||
void doAbortLocked();
|
||||
|
||||
// 系统状态
|
||||
ota_system_state_t m_sysState = OTA_SYS_IDLE;
|
||||
|
||||
@@ -394,6 +528,15 @@ private:
|
||||
// GD32 存储跟踪
|
||||
bool m_gd32StorageDone = false;
|
||||
|
||||
// OTA 会话代次:每次 beginEsp32Ota/beginGd32Storage 成功时递增。
|
||||
// 传输层 handler 在 begin 后捕获 epoch,失败清理时通过 abortCurrentIfEpoch
|
||||
// 检查 epoch 是否仍匹配,避免旧 handler 误中止同类型重启后的新会话。
|
||||
uint32_t m_otaEpoch = 0;
|
||||
|
||||
// 当前 OTA 会话的发起通道(WiFi/BLE),用于同类型重启时区分同通道/跨通道。
|
||||
// 仅在同类型活跃状态(ESP32_RECEIVING / GD32_STORING)下有意义。
|
||||
ota_channel_t m_otaChannel = OTA_CHANNEL_WIFI;
|
||||
|
||||
// 互斥锁(M1/L3: mutable 允许在 const 方法中加锁,消除 const_cast)
|
||||
mutable SemaphoreHandle_t m_mutex = nullptr;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user