feat(protocol): 更新SPI Link协议至v2版本并优化CAN通信
- 将SPI OTA帧格式升级为SPI Link v2,采用新的帧格式: | SOF0 0xA5 | SOF1 0x5A | VER 0x02 | FLAGS | SESSION 4B LE | TX_SEQ | ACK_SEQ | CREDIT | TYPE | LEN 2B LE | RSV 2B | PAYLOAD | CRC32 4B LE | - 新增V2_OVERHEAD=20字节开销,APP最大payload为236字节,BOOT为108字节 - 修改帧类型定义:ENTER_BOOT从0xB5合并至0xB0,新增CAN业务类型0xC0~C3 - 更新CAN协议文档,支持SPI Link v2-only模式,调整最大业务payload计算 - 扩展0xC1帧功能,支持双向通信和1Hz主动推送ID快照 - 添加0xC3完整ID快照帧类型,包含seq/rx_count等完整字段 - 更新协议文档中的错误码定义和反馈payload格式 refactor(main): 重构主程序入口并添加CAN反馈任务 - 在main.cpp中添加CAN反馈任务,更新启动流程注释 - 创建createCriticalTask辅助函数,统一关键任务创建逻辑 - 调整SPI初始化顺序,确保CAN反馈任务依赖正确 - 优化错误处理机制,关键任务失败时立即重启 - 将GD32版本探测移至ota_relay任务处理 - 更新版本号至0.3.48,调整编译时间和Git哈希 config: 更新SPI和BLE配置参数 - 添加SPI主机相关配置参数,包括CS GPIO、READY GPIO等引脚定义 - 配置SPI缓冲区大小:APP为256字节,BOOT为128字节 - 更新BLE滑动窗口参数,ACK窗口设为32,数据队列深度为72 - 调整SPI工作队列大小至5120字节,增加任务堆栈监控说明
This commit is contained in:
+11
-39
@@ -172,36 +172,9 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
|
||||
return beginCleanupAndReturn(ESP_FAIL);
|
||||
}
|
||||
|
||||
/* ★ 整分区预擦(非 SEQUENTIAL 边写边擦):
|
||||
* 1) 扇区循环 erase + 喂狗/yield,避免 TASK_WDT 与 WiFi 饿死
|
||||
* 2) esp_ota_resume(erase_size≠SEQUENTIAL) → need_erase=false,写路径纯 write
|
||||
* 3) 显式 invalidate inactive slot(resume 不像 begin 那样自动做)
|
||||
* 预擦会在 begin 阶段占用数秒~数十秒,之后收流写 Flash 更快更稳。 */
|
||||
const size_t part_size = m_partition->size;
|
||||
const size_t sector = m_partition->erase_size ? m_partition->erase_size : 4096U;
|
||||
DEBUG_INFO(TAG, "整分区预擦: %s size=%u sector=%u free=%lu...",
|
||||
m_partition->label, (unsigned)part_size, (unsigned)sector,
|
||||
(unsigned long)esp_get_free_heap_size());
|
||||
const int64_t t0 = esp_timer_get_time();
|
||||
for (size_t off = 0; off < part_size; ) {
|
||||
size_t chunk = sector;
|
||||
if (off + chunk > part_size) {
|
||||
chunk = part_size - off;
|
||||
}
|
||||
esp_err_t erase_err = esp_partition_erase_range(m_partition, off, chunk);
|
||||
if (erase_err != ESP_OK) {
|
||||
DEBUG_ERR(TAG, "整分区预擦失败 @%u: %s",
|
||||
(unsigned)off, esp_err_to_name(erase_err));
|
||||
return beginCleanupAndReturn(erase_err);
|
||||
}
|
||||
off += chunk;
|
||||
(void)esp_task_wdt_reset();
|
||||
taskYIELD();
|
||||
}
|
||||
const int64_t t1 = esp_timer_get_time();
|
||||
DEBUG_INFO(TAG, "整分区预擦完成 耗时 %lld ms",
|
||||
(long long)((t1 - t0) / 1000));
|
||||
|
||||
/* ★ 边写边擦(OTA_WITH_SEQUENTIAL_WRITES),不再整分区预擦:
|
||||
* 旧方案在 HTTP 只收到 JSON 头后同步擦 1.25MB,浏览器 TCP 窗口堵死、进度卡开头。
|
||||
* sequential:begin 立即返回,擦除摊到 esp_ota_write,与收流并行(对 WiFi 更友好)。 */
|
||||
#if defined(CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE) || defined(CONFIG_APP_ROLLBACK_ENABLE)
|
||||
{
|
||||
esp_err_t inv = esp_ota_invalidate_inactive_ota_data_slot();
|
||||
@@ -212,17 +185,16 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* erase_size 传 0(≠ OTA_WITH_SEQUENTIAL_WRITES)→ 写时不再擦扇区 */
|
||||
esp_err_t err = esp_ota_resume(m_partition, /*erase_size=*/0,
|
||||
/*image_offset=*/0, &m_handle);
|
||||
const int64_t t2 = esp_timer_get_time();
|
||||
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_resume 失败: %s (耗时 %lld ms)",
|
||||
esp_err_to_name(err), (long long)((t2 - t1) / 1000));
|
||||
DEBUG_ERR(TAG, "esp_ota_begin(SEQUENTIAL) 失败: %s (耗时 %lld ms)",
|
||||
esp_err_to_name(err), (long long)((t1 - t0) / 1000));
|
||||
return beginCleanupAndReturn(err);
|
||||
}
|
||||
DEBUG_INFO(TAG, "esp_ota_resume 完成 耗时 %lld ms free=%lu",
|
||||
(long long)((t2 - t1) / 1000),
|
||||
DEBUG_INFO(TAG, "esp_ota_begin(SEQUENTIAL) 完成 耗时 %lld ms free=%lu",
|
||||
(long long)((t1 - t0) / 1000),
|
||||
(unsigned long)esp_get_free_heap_size());
|
||||
|
||||
m_received = 0;
|
||||
@@ -231,7 +203,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 字节 (full-partition erase)",
|
||||
DEBUG_INFO(TAG, "ESP32 OTA 开始,目标分区: %s, 声明大小: %u 字节 (sequential erase-on-write)",
|
||||
m_partition->label, (unsigned)total_size);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* begin → write*(N) → finalize / abort
|
||||
*
|
||||
* 统一以 WiFi 流程为基准:
|
||||
* - begin:整分区预擦后 esp_ota_resume(写路径不再边写边擦)
|
||||
* - begin:esp_ota_begin(OTA_WITH_SEQUENTIAL_WRITES) 边写边擦(避免 HTTP 头卡预擦)
|
||||
* - SHA-256 在 finalize 时从 OTA 分区读回计算(不在上传期间增量计算)
|
||||
* - finalize 顺序: 读回 SHA-256 → 比对前端 SHA → esp_ota_end → esp_ota_set_boot_partition → 可选重启
|
||||
* - m_ended 标志位防止 esp_ota_end 成功后再次 esp_ota_abort 同一 handle
|
||||
|
||||
+84
-103
@@ -9,7 +9,6 @@
|
||||
#include "esp32_ota_core.h"
|
||||
#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"
|
||||
@@ -57,8 +56,8 @@ bool OtaCoordinator::lock() const
|
||||
if (!m_mutex) return false;
|
||||
bool ok = (xSemaphoreTake(m_mutex, kLockTimeout) == pdTRUE);
|
||||
if (!ok) {
|
||||
DEBUG_WARN(TAG, "lock() 超时 (%u ms),本次状态变更被跳过",
|
||||
(unsigned)pdTICKS_TO_MS(kLockTimeout));
|
||||
DEBUG_ERR(TAG, "lock() 超时 (%u ms),本次状态变更被跳过",
|
||||
(unsigned)pdTICKS_TO_MS(kLockTimeout));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@@ -76,16 +75,16 @@ void OtaCoordinator::unlock(bool handle) const
|
||||
// ============================================================
|
||||
void OtaCoordinator::watchdogCheckLocked()
|
||||
{
|
||||
// 仅对"忙"状态(可能卡死的)做检查
|
||||
// 仅对"忙"状态(可能卡死的)做检查。
|
||||
// REBOOTING:已 set_boot,即将 esp_restart,不入狗,避免 abort 成 IDLE 与重启竞态。
|
||||
switch (m_sysState) {
|
||||
case OTA_SYS_GD32_STORING:
|
||||
case OTA_SYS_GD32_RELAYING:
|
||||
case OTA_SYS_ESP32_RECEIVING:
|
||||
case OTA_SYS_ESP32_FINALIZING:
|
||||
case OTA_SYS_ESP32_REBOOTING:
|
||||
break;
|
||||
default:
|
||||
return; // IDLE/STORED/COMPLETE/ERROR 无需检查
|
||||
return; // IDLE/STORED/COMPLETE/ERROR/REBOOTING 无需检查
|
||||
}
|
||||
|
||||
if (m_busySinceTick == 0) {
|
||||
@@ -162,9 +161,7 @@ esp_err_t OtaCoordinator::beginGd32Storage(uint32_t total_size, ota_channel_t ch
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
DEBUG_WARN(TAG, "GD32 存储仍在进行中(同通道),中止旧存储并重新开始");
|
||||
Gd32FlashStore::instance().metaClear();
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_busySinceTick = 0;
|
||||
doAbortLocked(); // 清 meta + IDLE,与 abort 路径一致
|
||||
// epoch 将在下方设置 GD32_STORING 后递增
|
||||
}
|
||||
|
||||
@@ -221,27 +218,17 @@ esp_err_t OtaCoordinator::prepareGd32Storage(uint32_t total_size, ota_channel_t
|
||||
return err;
|
||||
}
|
||||
|
||||
// 3. 整分区预擦(不再边写边擦):prepare 阶段擦完 gd32_ota 分区,
|
||||
// 收流时 flashWriteChunk 仅 write,吞吐更稳。会阻塞数秒~数十秒。
|
||||
// 3. 复位擦除水位;不在 prepare 整区预擦。
|
||||
// 旧方案在 HTTP JSON 头后同步擦 gd32_ota,TCP 堵在开头。
|
||||
// 现由 flashWriteChunk → ensureErasedForWrite 按需擦扇区,与收流交织。
|
||||
Gd32FlashStore::instance().beginWriteSession();
|
||||
{
|
||||
const esp_partition_t *gd32_part = Gd32FlashStore::instance().getPartition();
|
||||
const uint32_t erase_bytes = gd32_part ? (uint32_t)gd32_part->size : 0U;
|
||||
if (erase_bytes == 0U) {
|
||||
DEBUG_ERR(TAG, "prepareGd32Storage: 找不到 gd32_ota 分区");
|
||||
abortCurrent();
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
DEBUG_INFO(TAG, "prepareGd32Storage: 整分区预擦 %u 字节 (fw=%" PRIu32 ")...",
|
||||
(unsigned)erase_bytes, total_size);
|
||||
err = Gd32FlashStore::instance().erasePartition(erase_bytes);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR(TAG, "prepareGd32Storage: 整分区预擦失败: %s", esp_err_to_name(err));
|
||||
abortCurrent();
|
||||
return err;
|
||||
}
|
||||
DEBUG_INFO(TAG, "prepareGd32Storage: 整分区预擦完成");
|
||||
if (Gd32FlashStore::instance().getPartition() == nullptr) {
|
||||
DEBUG_ERR(TAG, "prepareGd32Storage: 找不到 gd32_ota 分区");
|
||||
abortCurrent();
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
DEBUG_INFO(TAG, "prepareGd32Storage: 会话就绪 fw=%" PRIu32 "(按需擦除,无整区预擦)",
|
||||
total_size);
|
||||
|
||||
// 4. 清空旧 meta + 写入初始 meta(total_size + stored_size=0 + last_seq=0)
|
||||
clearGd32Meta();
|
||||
@@ -284,55 +271,39 @@ esp_err_t OtaCoordinator::finishGd32StorageAfterReceive()
|
||||
return finalizeGd32Checksum();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GD32 存储操作(传输层统一入口)
|
||||
// ── 以下 8 个 wrapper 已 inline 化至 ota_coordinator.h,直接转发到
|
||||
// Gd32FlashStore::instance(),消除跨 TU 调用开销。
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// GD32 中继阶段
|
||||
// ============================================================
|
||||
esp_err_t OtaCoordinator::triggerGd32Relay()
|
||||
{
|
||||
bool h = lock();
|
||||
if (!h) {
|
||||
DEBUG_ERR(TAG, "triggerGd32Relay: lock 超时");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// ★ 卡死自愈检查
|
||||
if (h) watchdogCheckLocked();
|
||||
watchdogCheckLocked();
|
||||
|
||||
// 校验:必须处于"已存储"或"已中继完成"状态
|
||||
if (h && m_sysState != OTA_SYS_GD32_STORED && m_sysState != OTA_SYS_IDLE) {
|
||||
if (m_sysState == OTA_SYS_GD32_STORING) {
|
||||
DEBUG_WARN(TAG, "GD32 正在存储中,拒绝触发中继");
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
||||
DEBUG_WARN(TAG, "GD32 中继已在进行中");
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
||||
m_sysState == OTA_SYS_ESP32_REBOOTING) {
|
||||
DEBUG_WARN(TAG, "ESP32 OTA 进行中,拒绝中继触发");
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
/* 白名单:仅 STORED / IDLE(NVS 有完整固件)/ COMPLETE(允许再中继)可触发 */
|
||||
if (m_sysState != OTA_SYS_GD32_STORED &&
|
||||
m_sysState != OTA_SYS_IDLE &&
|
||||
m_sysState != OTA_SYS_GD32_COMPLETE) {
|
||||
DEBUG_WARN(TAG, "拒绝触发中继: 当前状态 %s (%d) 不在白名单",
|
||||
stateToString(m_sysState), (int)m_sysState);
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 验证固件是否确实已存储(通过 NVS 元数据)
|
||||
ota_meta_t meta;
|
||||
if (h && (Gd32FlashStore::instance().metaLoad(&meta) != ESP_OK ||
|
||||
if (Gd32FlashStore::instance().metaLoad(&meta) != ESP_OK ||
|
||||
meta.total_size == 0 ||
|
||||
meta.stored_size < meta.total_size)) {
|
||||
meta.stored_size < meta.total_size) {
|
||||
DEBUG_WARN(TAG, "GD32 固件未完整存储,拒绝触发中继");
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 检查 OtaRelay 是否空闲
|
||||
if (h && OtaRelay::getState() != RELAY_IDLE &&
|
||||
if (OtaRelay::getState() != RELAY_IDLE &&
|
||||
OtaRelay::getState() != RELAY_DONE &&
|
||||
OtaRelay::getState() != RELAY_ERROR) {
|
||||
DEBUG_WARN(TAG, "OtaRelay 正忙 (state=%d),拒绝触发", (int)OtaRelay::getState());
|
||||
@@ -340,47 +311,42 @@ esp_err_t OtaCoordinator::triggerGd32Relay()
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 向 OtaRelay task 发送启动消息
|
||||
QueueHandle_t dq = OtaRelay::getDispatchQueue();
|
||||
if (h && !dq) {
|
||||
if (!dq) {
|
||||
DEBUG_ERR(TAG, "OtaRelay dispatchQueue 未就绪");
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!h) {
|
||||
unlock(h);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
m_sysState = OTA_SYS_GD32_RELAYING;
|
||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录忙状态起始时刻
|
||||
/* 先入队再标 RELAYING:失败时状态与 CAN 反馈都不变 */
|
||||
pauseCanFeedbackTask(true);
|
||||
DEBUG_INFO(TAG, "触发 GD32 中继, 系统状态 → GD32_RELAYING");
|
||||
|
||||
uint32_t cmd = 1;
|
||||
BaseType_t ret = xQueueSend(dq, &cmd, pdMS_TO_TICKS(100));
|
||||
if (ret != pdTRUE) {
|
||||
DEBUG_ERR(TAG, "向 OtaRelay 发送启动消息失败");
|
||||
m_sysState = OTA_SYS_GD32_STORED;
|
||||
m_busySinceTick = 0; // 回退到 STORED(非忙)
|
||||
pauseCanFeedbackTask(false);
|
||||
unlock(h);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
m_sysState = OTA_SYS_GD32_RELAYING;
|
||||
m_busySinceTick = xTaskGetTickCount();
|
||||
DEBUG_INFO(TAG, "触发 GD32 中继, 系统状态 → GD32_RELAYING");
|
||||
unlock(h);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/** @brief 通知协调器:GD32 中继已开始,更新系统状态为 GD32_RELAYING。 */
|
||||
/** @brief 中继 task 已真正开始:若 trigger 已标 RELAYING 则只补 pause;否则兜底设状态。 */
|
||||
void OtaCoordinator::notifyRelayStarted()
|
||||
{
|
||||
bool h = lock();
|
||||
if (h && m_sysState != OTA_SYS_GD32_RELAYING) {
|
||||
m_sysState = OTA_SYS_GD32_RELAYING;
|
||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
|
||||
if (h) {
|
||||
if (m_sysState != OTA_SYS_GD32_RELAYING) {
|
||||
m_sysState = OTA_SYS_GD32_RELAYING;
|
||||
m_busySinceTick = xTaskGetTickCount();
|
||||
DEBUG_INFO(TAG, "中继已开始(兜底), 系统状态 → GD32_RELAYING");
|
||||
}
|
||||
pauseCanFeedbackTask(true);
|
||||
DEBUG_INFO(TAG, "中继已开始, 系统状态 → GD32_RELAYING");
|
||||
}
|
||||
unlock(h);
|
||||
}
|
||||
@@ -466,9 +432,7 @@ esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size, ota_channel_t chann
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
DEBUG_WARN(TAG, "ESP32 OTA 仍在接收中(同通道),中止旧会话并重新开始");
|
||||
Esp32OtaCore::instance().abort();
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_busySinceTick = 0;
|
||||
doAbortLocked(); // abort core + IDLE + 恢复 CAN 反馈
|
||||
// epoch 将在下方 begin 成功后递增,自动使旧 handler 的清理代码失效
|
||||
}
|
||||
|
||||
@@ -558,6 +522,12 @@ esp_err_t OtaCoordinator::beginEsp32OtaWithRetry(uint32_t total_size, ota_channe
|
||||
void OtaCoordinator::setEsp32StateLocked(ota_system_state_t s, const char *dbg_msg)
|
||||
{
|
||||
m_sysState = s;
|
||||
/* REBOOTING 不参与 busy watchdog,清零起点避免误中止 */
|
||||
if (s == OTA_SYS_ESP32_REBOOTING) {
|
||||
m_busySinceTick = 0;
|
||||
} else if (s == OTA_SYS_ESP32_FINALIZING && m_busySinceTick == 0) {
|
||||
m_busySinceTick = xTaskGetTickCount();
|
||||
}
|
||||
DEBUG_INFO(TAG, "%s", dbg_msg);
|
||||
}
|
||||
|
||||
@@ -667,12 +637,24 @@ void OtaCoordinator::healOrphanStates()
|
||||
bool need_resume = false;
|
||||
bool h = lock();
|
||||
if (h) {
|
||||
watchdogCheckLocked();
|
||||
ota_system_state_t before = m_sysState;
|
||||
watchdogCheckLocked(); // 超时则 doAbortLocked → IDLE
|
||||
// finalize 失败后 core 已空、协调器仍 FINALIZING 的假占用:status 轮询即可自愈
|
||||
if (m_sysState == OTA_SYS_ESP32_FINALIZING &&
|
||||
!Esp32OtaCore::instance().isActive()) {
|
||||
DEBUG_WARN(TAG, "status 路径发现 ESP32_FINALIZING 残留,自愈 → IDLE");
|
||||
doAbortLocked();
|
||||
}
|
||||
/* ERROR 非忙:status 轮询软清到 IDLE,避免 UI 长期卡 ERROR(新 begin 本也可覆盖) */
|
||||
if (m_sysState == OTA_SYS_ERROR) {
|
||||
DEBUG_INFO(TAG, "status 路径 ERROR → IDLE(软清)");
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_busySinceTick = 0;
|
||||
m_gd32StorageDone = false;
|
||||
pauseCanFeedbackTask(false);
|
||||
}
|
||||
// 任意自愈到 IDLE:恢复 WiFi OTA 期间挂起的 BLE(watchdog 旧路径漏 resume)
|
||||
if (before != OTA_SYS_IDLE && m_sysState == OTA_SYS_IDLE) {
|
||||
need_resume = true;
|
||||
}
|
||||
unlock(h);
|
||||
@@ -806,28 +788,27 @@ OtaSnapshot OtaCoordinator::getSnapshot() const
|
||||
// ============================================================
|
||||
// GD32 APP 探测
|
||||
// ============================================================
|
||||
// 注:本函数与 OtaRelay::probeAndCacheAppAlive 逻辑相似,但因依赖方向
|
||||
// (OtaCoordinator → OtaRelay)不应调用 OtaRelay 的内部实现细节,
|
||||
// 故此处保留独立实现。
|
||||
esp_err_t OtaCoordinator::probeGd32AppOnce(gd32_version_t *out_version, uint32_t *out_uptime)
|
||||
{
|
||||
/* 统一走 OtaRelay 公开探测,避免与 probeAndCacheAppAlive 双份逻辑 */
|
||||
uint32_t uptime = 0;
|
||||
gd32_version_t ver = {};
|
||||
|
||||
esp_err_t err = SpiMaster::instance().appPing(500, &uptime, &ver);
|
||||
if (err == ESP_OK) {
|
||||
OtaRelay::setGd32AppAlive(true);
|
||||
OtaRelay::setGd32Version(ver);
|
||||
if (out_version) *out_version = ver;
|
||||
if (out_uptime) *out_uptime = uptime;
|
||||
if (OtaRelay::probeAndCacheAppAlive(500, &uptime)) {
|
||||
if (out_version) {
|
||||
*out_version = OtaRelay::getSnapshot().version;
|
||||
}
|
||||
if (out_uptime) {
|
||||
*out_uptime = uptime;
|
||||
}
|
||||
DEBUG_INFO(TAG, "GD32 APP 探测成功: alive, uptime=%" PRIu32 "s", uptime);
|
||||
} else {
|
||||
OtaRelay::setGd32AppAlive(false);
|
||||
if (out_version) *out_version = ver;
|
||||
if (out_uptime) *out_uptime = 0;
|
||||
DEBUG_WARN(TAG, "GD32 APP 探测失败: %s", esp_err_to_name(err));
|
||||
return ESP_OK;
|
||||
}
|
||||
return err;
|
||||
if (out_version) {
|
||||
*out_version = gd32_version_t{};
|
||||
}
|
||||
if (out_uptime) {
|
||||
*out_uptime = 0;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -835,12 +816,12 @@ esp_err_t OtaCoordinator::probeGd32AppOnce(gd32_version_t *out_version, uint32_t
|
||||
// ============================================================
|
||||
void OtaCoordinator::reset()
|
||||
{
|
||||
// ★ 与 abortCurrent 共用 doAbortLocked:清 Esp32OtaCore / relay / meta,勿只改枚举
|
||||
bool h = lock();
|
||||
if (h) {
|
||||
m_sysState = OTA_SYS_IDLE;
|
||||
m_gd32StorageDone = false;
|
||||
m_busySinceTick = 0; // ★ 复位
|
||||
DEBUG_INFO(TAG, "协调器已完全复位");
|
||||
doAbortLocked();
|
||||
DEBUG_INFO(TAG, "协调器已完全复位(含子系统 abort)");
|
||||
}
|
||||
unlock(h);
|
||||
wifiOtaResumeBleIfNeeded();
|
||||
}
|
||||
|
||||
+11
-11
@@ -124,7 +124,8 @@ public:
|
||||
esp_err_t beginGd32Storage(uint32_t total_size, ota_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief 统一 GD32 OTA 写入前准备:校验大小 + begin + 整分区预擦 + 清空 meta + 写入初始 meta。
|
||||
* @brief 统一 GD32 OTA 写入前准备:校验大小 + begin + 复位擦除水位 + 清空 meta + 写入初始 meta。
|
||||
* 扇区擦除由 flashWriteChunk/ensureErasedForWrite 按需进行(避免 HTTP 头卡整区预擦)。
|
||||
* 收流时 write 路径只写 Flash(ensureErased 仅作水位兜底)。
|
||||
* WiFi/BLE 上传入口共用,任意步骤失败时内部 abortCurrent,调用方无需再清理。
|
||||
* @param total_size 固件总字节数。
|
||||
@@ -337,9 +338,9 @@ public:
|
||||
|
||||
/**
|
||||
* @brief 自愈孤儿忙态(供 /system/status 等轮询路径调用)。
|
||||
* - 跑忙状态 watchdog(超时完整 abort → IDLE)
|
||||
* - ESP32_FINALIZING 但 Esp32OtaCore 已空闲:判定为 SHA 失败等残留,复位 IDLE
|
||||
* 不影响真正正在 finalize(core.isActive())的会话。
|
||||
* - 忙状态 watchdog(超时完整 abort → IDLE;不含 REBOOTING)
|
||||
* - ESP32_FINALIZING 但 core 已空闲:残留 → IDLE
|
||||
* - ERROR → IDLE 软清,避免 UI 长期卡错误态
|
||||
*/
|
||||
void healOrphanStates();
|
||||
|
||||
@@ -476,9 +477,7 @@ private:
|
||||
bool lock() const;
|
||||
void unlock(bool handle) const;
|
||||
|
||||
// ★ 卡死自愈检查:若忙状态停留超过 watchdog 超时,强制转为 ERROR。
|
||||
// 用于在中继 task 因任何原因(崩溃/SPI 死锁/看门狗复位残留)未能回调
|
||||
// notifyRelayComplete/notifyEsp32*Complete 时,让协调器自动脱离卡死状态。
|
||||
// ★ 卡死自愈检查:忙状态超时则 doAbortLocked → IDLE(不含 REBOOTING)。
|
||||
// 必须在已持锁状态下调用。
|
||||
void watchdogCheckLocked();
|
||||
|
||||
@@ -513,11 +512,12 @@ private:
|
||||
|
||||
// BLE 共存监听器(由 WifiOta 实现并注册)
|
||||
IBleCoexListener *m_bleCoexListener = nullptr;
|
||||
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(100);
|
||||
static constexpr TickType_t kLockTimeout =
|
||||
pdMS_TO_TICKS(OTA_COORD_LOCK_TIMEOUT_MS);
|
||||
|
||||
// ★ 忙状态 watchdog 超时。GD32 中继理论最大耗时 ≈ 60s(全局超时) + 15s(APP存活轮询) + 1s(bootloader) ≈ 76s,
|
||||
// 取 90 s,正常流程远不会触及,仅在 task 卡死时触发自愈。
|
||||
static constexpr TickType_t kBusyWatchdogTicks = pdMS_TO_TICKS(90U * 1000U);
|
||||
// ★ 忙状态 watchdog:须覆盖最长中继预算;数值见 app_config.h
|
||||
static constexpr TickType_t kBusyWatchdogTicks =
|
||||
pdMS_TO_TICKS(OTA_COORD_BUSY_WATCHDOG_MS);
|
||||
};
|
||||
|
||||
#endif // OTA_COORDINATOR_H
|
||||
|
||||
Reference in New Issue
Block a user