fix(ota): 修复OtaCoordinator互斥锁死锁和SPI appPing首字节丢失
- OtaCoordinator lock/unlock: 移除共享 m_locked 标志,改为传参传递 take 结果,消除多任务并发 lock() 超时覆盖持锁状态导致 mutex 永久死锁的问题 - SPI appPing: 发送帧填充至 16 字节防止首字节丢失,增大轮询间隔至 10ms, 增大接收缓冲区避免字节偏移解析失败 - SPI init: 先创建 mutex 再使用 LockGuard,消除竞态窗口 - 压制 WiFi 框架 INFO 日志 (pp/net80211/wifi/wifi_init),减少启动打印 - 降级 7 条调试日志为 DBG 级别(UART0 初始化、冷启动缓存清理、互斥锁/重连 任务/回调/netif 创建) - GD32 版本号读取增加 5 次重试上限和 OTA 繁忙时跳过逻辑 - 移除 forwardFirmware 中已过时注释
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
#define FW_VERSION_STRING "0.3.12"
|
#define FW_VERSION_STRING "0.3.12"
|
||||||
|
|
||||||
// 编译时间
|
// 编译时间
|
||||||
#define BUILD_TIME "2026-06-17 02:12:38"
|
#define BUILD_TIME "2026-06-17 03:09:19"
|
||||||
|
|
||||||
// Git 信息
|
// Git 信息
|
||||||
#define GIT_HASH "5b2c5f8"
|
#define GIT_HASH "98b5cc1"
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ esp_err_t DebugLog::uartInit()
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_uartInitialized = true;
|
m_uartInitialized = true;
|
||||||
ESP_LOGI(TAG, "UART0 调试输出已初始化 (TX=GPIO%d, RX=GPIO%d, %d baud)",
|
ESP_LOGD(TAG, "UART0 调试输出已初始化 (TX=GPIO%d, RX=GPIO%d, %d baud)",
|
||||||
UART_DEBUG_TX_GPIO, UART_DEBUG_RX_GPIO, UART_DEBUG_BAUD_RATE);
|
UART_DEBUG_TX_GPIO, UART_DEBUG_RX_GPIO, UART_DEBUG_BAUD_RATE);
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
|
|||||||
+129
-119
@@ -23,28 +23,26 @@ OtaCoordinator& OtaCoordinator::instance()
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// 线程安全辅助
|
// 线程安全辅助
|
||||||
// ============================================================
|
// ============================================================
|
||||||
void OtaCoordinator::lock()
|
bool OtaCoordinator::lock()
|
||||||
{
|
{
|
||||||
// ★ 修复:FreeRTOS mutex (xSemaphoreCreateMutex) 只能由 take 的 task give。
|
// ★ 修复: 返回 take 结果,由调用方传回 unlock()。
|
||||||
// 超时后必须在 unlock() 中跳过 give,避免 xTaskPriorityDisinherit assert。
|
// 旧实现用成员 bool m_locked 记录 take 结果,多任务并发 lock() 时
|
||||||
// 此处假设 OtaCoordinator 内部不嵌套 lock(全局已确认:无嵌套调用)。
|
// 超时方会把 m_locked 置 false,覆盖持锁方的 true,导致 unlock() 跳过 give,
|
||||||
m_locked = false;
|
// mutex 永久死锁 → 后续 take 超时触发 vTaskPriorityDisinherit assert → PANIC。
|
||||||
if (m_mutex) {
|
if (!m_mutex) return false;
|
||||||
m_locked = (xSemaphoreTake(m_mutex, kLockTimeout) == pdTRUE);
|
bool ok = (xSemaphoreTake(m_mutex, kLockTimeout) == pdTRUE);
|
||||||
if (!m_locked) {
|
if (!ok) {
|
||||||
DEBUG_WARN(TAG, "lock() 超时 (%u ms),本次状态变更被跳过",
|
DEBUG_WARN(TAG, "lock() 超时 (%u ms),本次状态变更被跳过",
|
||||||
(unsigned)pdTICKS_TO_MS(kLockTimeout));
|
(unsigned)pdTICKS_TO_MS(kLockTimeout));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OtaCoordinator::unlock()
|
void OtaCoordinator::unlock(bool handle)
|
||||||
{
|
{
|
||||||
// ★ 修复:仅在 lock() 成功 take 时才 give。
|
// ★ 修复: 仅在对应的 lock() 成功 take 时才 give。
|
||||||
// 错误的 task 给同一 mutex give 会触发 xTaskPriorityDisinherit assert → PANIC。
|
if (handle && m_mutex) {
|
||||||
if (m_locked && m_mutex) {
|
|
||||||
xSemaphoreGive(m_mutex);
|
xSemaphoreGive(m_mutex);
|
||||||
m_locked = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,51 +117,51 @@ void OtaCoordinator::init()
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
esp_err_t OtaCoordinator::beginGd32Storage(uint32_t total_size)
|
esp_err_t OtaCoordinator::beginGd32Storage(uint32_t total_size)
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
|
|
||||||
// ★ 先做卡死自愈检查:若之前忙状态卡死,自动转 ERROR 继续后续流程
|
// ★ 先做卡死自愈检查:若之前忙状态卡死,自动转 ERROR 继续后续流程
|
||||||
watchdogCheckLocked();
|
if (h) watchdogCheckLocked();
|
||||||
|
|
||||||
// 系统忙检查:仅拒绝真正"在传输"的活跃状态
|
// 系统忙检查:仅拒绝真正"在传输"的活跃状态
|
||||||
// STORED / COMPLETE 是"待中继/已中继"的非活跃状态 → 允许新存储覆盖
|
// STORED / COMPLETE 是"待中继/已中继"的非活跃状态 → 允许新存储覆盖
|
||||||
// ★ 修复:之前把所有非 IDLE 都当忙,导致"Flash 缓存里有固件但没在传输"时无法上传新固件
|
if (h && (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||||
if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
|
||||||
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
||||||
m_sysState == OTA_SYS_ESP32_REBOOTING ||
|
m_sysState == OTA_SYS_ESP32_REBOOTING ||
|
||||||
m_sysState == OTA_SYS_GD32_STORING ||
|
m_sysState == OTA_SYS_GD32_STORING ||
|
||||||
m_sysState == OTA_SYS_GD32_RELAYING) {
|
m_sysState == OTA_SYS_GD32_RELAYING)) {
|
||||||
DEBUG_WARN(TAG, "系统忙 (state=%d),拒绝 GD32 存储请求", (int)m_sysState);
|
DEBUG_WARN(TAG, "系统忙 (state=%d),拒绝 GD32 存储请求", (int)m_sysState);
|
||||||
unlock();
|
unlock(h);
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果之前已有存储但未中继(STORED)或刚中继完(COMPLETE),先清理再开始新存储
|
// 如果之前已有存储但未中继(STORED)或刚中继完(COMPLETE),先清理再开始新存储
|
||||||
if (m_sysState == OTA_SYS_GD32_STORED || m_sysState == OTA_SYS_GD32_COMPLETE) {
|
if (h && (m_sysState == OTA_SYS_GD32_STORED || m_sysState == OTA_SYS_GD32_COMPLETE)) {
|
||||||
DEBUG_INFO(TAG, "清理旧 GD32 存储状态,开始新存储 (size=%" PRIu32 ")", total_size);
|
DEBUG_INFO(TAG, "清理旧 GD32 存储状态,开始新存储 (size=%" PRIu32 ")", total_size);
|
||||||
OtaRelay::metaClear();
|
OtaRelay::metaClear();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_sysState = OTA_SYS_GD32_STORING;
|
if (h) {
|
||||||
m_gd32StorageDone = false;
|
m_sysState = OTA_SYS_GD32_STORING;
|
||||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录忙状态起始时刻
|
m_gd32StorageDone = false;
|
||||||
|
m_busySinceTick = xTaskGetTickCount(); // ★ 记录忙状态起始时刻
|
||||||
DEBUG_INFO(TAG, "GD32 固件存储开始: %" PRIu32 " 字节, 系统状态 → GD32_STORING", total_size);
|
DEBUG_INFO(TAG, "GD32 固件存储开始: %" PRIu32 " 字节, 系统状态 → GD32_STORING", total_size);
|
||||||
unlock();
|
}
|
||||||
return ESP_OK;
|
unlock(h);
|
||||||
|
return h ? ESP_OK : ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OtaCoordinator::completeGd32Storage()
|
void OtaCoordinator::completeGd32Storage()
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
if (m_sysState == OTA_SYS_GD32_STORING) {
|
if (h && m_sysState == OTA_SYS_GD32_STORING) {
|
||||||
m_sysState = OTA_SYS_GD32_STORED;
|
m_sysState = OTA_SYS_GD32_STORED;
|
||||||
m_gd32StorageDone = true;
|
m_gd32StorageDone = true;
|
||||||
m_busySinceTick = 0; // ★ STORED 是非忙状态
|
m_busySinceTick = 0; // ★ STORED 是非忙状态
|
||||||
DEBUG_INFO(TAG, "GD32 固件存储完成, 系统状态 → GD32_STORED");
|
DEBUG_INFO(TAG, "GD32 固件存储完成, 系统状态 → GD32_STORED");
|
||||||
} else {
|
} else if (h) {
|
||||||
DEBUG_WARN(TAG, "completeGd32Storage 在非预期状态: %d", (int)m_sysState);
|
DEBUG_WARN(TAG, "completeGd32Storage 在非预期状态: %d", (int)m_sysState);
|
||||||
}
|
}
|
||||||
unlock();
|
unlock(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -171,59 +169,60 @@ void OtaCoordinator::completeGd32Storage()
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
esp_err_t OtaCoordinator::triggerGd32Relay()
|
esp_err_t OtaCoordinator::triggerGd32Relay()
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
|
|
||||||
// ★ 卡死自愈检查
|
// ★ 卡死自愈检查
|
||||||
watchdogCheckLocked();
|
if (h) watchdogCheckLocked();
|
||||||
|
|
||||||
// 校验:必须处于"已存储"或"已中继完成"状态
|
// 校验:必须处于"已存储"或"已中继完成"状态
|
||||||
// ★ 修复:COMPLETE(旧固件已成功推给 GD32,NVS 元数据已被 doFinishAndVerify 清掉,
|
if (h && m_sysState != OTA_SYS_GD32_STORED && m_sysState != OTA_SYS_IDLE) {
|
||||||
// OtaRelay 状态为 DONE)也能触发——但需要先回滚到 STORED 以保证 m_busySinceTick 一致。
|
|
||||||
// 通常用户应先上传新固件再触发中继,COMPLETE 时元数据已被清,metaLoad 会返回 stored=0
|
|
||||||
// 并在 188 行被拒绝,自然不会走到这里。
|
|
||||||
if (m_sysState != OTA_SYS_GD32_STORED && m_sysState != OTA_SYS_IDLE) {
|
|
||||||
if (m_sysState == OTA_SYS_GD32_STORING) {
|
if (m_sysState == OTA_SYS_GD32_STORING) {
|
||||||
DEBUG_WARN(TAG, "GD32 正在存储中,拒绝触发中继");
|
DEBUG_WARN(TAG, "GD32 正在存储中,拒绝触发中继");
|
||||||
unlock();
|
unlock(h);
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
||||||
DEBUG_WARN(TAG, "GD32 中继已在进行中");
|
DEBUG_WARN(TAG, "GD32 中继已在进行中");
|
||||||
unlock();
|
unlock(h);
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||||
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
m_sysState == OTA_SYS_ESP32_FINALIZING ||
|
||||||
m_sysState == OTA_SYS_ESP32_REBOOTING) {
|
m_sysState == OTA_SYS_ESP32_REBOOTING) {
|
||||||
DEBUG_WARN(TAG, "ESP32 OTA 进行中,拒绝中继触发");
|
DEBUG_WARN(TAG, "ESP32 OTA 进行中,拒绝中继触发");
|
||||||
unlock();
|
unlock(h);
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证固件是否确实已存储(通过 NVS 元数据)
|
// 验证固件是否确实已存储(通过 NVS 元数据)
|
||||||
ota_meta_t meta;
|
ota_meta_t meta;
|
||||||
if (OtaRelay::metaLoad(&meta) != ESP_OK ||
|
if (h && (OtaRelay::metaLoad(&meta) != ESP_OK ||
|
||||||
meta.total_size == 0 ||
|
meta.total_size == 0 ||
|
||||||
meta.stored_size < meta.total_size) {
|
meta.stored_size < meta.total_size)) {
|
||||||
DEBUG_WARN(TAG, "GD32 固件未完整存储,拒绝触发中继");
|
DEBUG_WARN(TAG, "GD32 固件未完整存储,拒绝触发中继");
|
||||||
unlock();
|
unlock(h);
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查 OtaRelay 是否空闲
|
// 检查 OtaRelay 是否空闲
|
||||||
if (OtaRelay::getState() != RELAY_IDLE &&
|
if (h && OtaRelay::getState() != RELAY_IDLE &&
|
||||||
OtaRelay::getState() != RELAY_DONE &&
|
OtaRelay::getState() != RELAY_DONE &&
|
||||||
OtaRelay::getState() != RELAY_ERROR) {
|
OtaRelay::getState() != RELAY_ERROR) {
|
||||||
DEBUG_WARN(TAG, "OtaRelay 正忙 (state=%d),拒绝触发", (int)OtaRelay::getState());
|
DEBUG_WARN(TAG, "OtaRelay 正忙 (state=%d),拒绝触发", (int)OtaRelay::getState());
|
||||||
unlock();
|
unlock(h);
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向 OtaRelay task 发送启动消息
|
// 向 OtaRelay task 发送启动消息
|
||||||
if (!OtaRelay::dispatchQueue) {
|
if (h && !OtaRelay::dispatchQueue) {
|
||||||
DEBUG_ERR(TAG, "OtaRelay dispatchQueue 未就绪");
|
DEBUG_ERR(TAG, "OtaRelay dispatchQueue 未就绪");
|
||||||
unlock();
|
unlock(h);
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h) {
|
||||||
|
unlock(h);
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,39 +236,41 @@ esp_err_t OtaCoordinator::triggerGd32Relay()
|
|||||||
DEBUG_ERR(TAG, "向 OtaRelay 发送启动消息失败");
|
DEBUG_ERR(TAG, "向 OtaRelay 发送启动消息失败");
|
||||||
m_sysState = OTA_SYS_GD32_STORED;
|
m_sysState = OTA_SYS_GD32_STORED;
|
||||||
m_busySinceTick = 0; // 回退到 STORED(非忙)
|
m_busySinceTick = 0; // 回退到 STORED(非忙)
|
||||||
unlock();
|
unlock(h);
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock();
|
unlock(h);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OtaCoordinator::notifyRelayStarted()
|
void OtaCoordinator::notifyRelayStarted()
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
if (m_sysState != OTA_SYS_GD32_RELAYING) {
|
if (h && m_sysState != OTA_SYS_GD32_RELAYING) {
|
||||||
m_sysState = OTA_SYS_GD32_RELAYING;
|
m_sysState = OTA_SYS_GD32_RELAYING;
|
||||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
|
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
|
||||||
DEBUG_INFO(TAG, "中继已开始, 系统状态 → GD32_RELAYING");
|
DEBUG_INFO(TAG, "中继已开始, 系统状态 → GD32_RELAYING");
|
||||||
}
|
}
|
||||||
unlock();
|
unlock(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OtaCoordinator::notifyRelayComplete(bool success)
|
void OtaCoordinator::notifyRelayComplete(bool success)
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
if (success) {
|
if (h) {
|
||||||
m_sysState = OTA_SYS_GD32_COMPLETE;
|
if (success) {
|
||||||
m_gd32StorageDone = false;
|
m_sysState = OTA_SYS_GD32_COMPLETE;
|
||||||
m_busySinceTick = 0; // ★ COMPLETE 非忙
|
m_gd32StorageDone = false;
|
||||||
DEBUG_INFO(TAG, "GD32 中继完成, 系统状态 → GD32_COMPLETE");
|
m_busySinceTick = 0; // ★ COMPLETE 非忙
|
||||||
} else {
|
DEBUG_INFO(TAG, "GD32 中继完成, 系统状态 → GD32_COMPLETE");
|
||||||
m_sysState = OTA_SYS_ERROR;
|
} else {
|
||||||
m_busySinceTick = 0; // ★ ERROR 非忙(可被后续请求恢复)
|
m_sysState = OTA_SYS_ERROR;
|
||||||
DEBUG_ERR(TAG, "GD32 中继失败, 系统状态 → ERROR");
|
m_busySinceTick = 0; // ★ ERROR 非忙(可被后续请求恢复)
|
||||||
|
DEBUG_ERR(TAG, "GD32 中继失败, 系统状态 → ERROR");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
unlock();
|
unlock(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -277,60 +278,65 @@ void OtaCoordinator::notifyRelayComplete(bool success)
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
|
esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
|
|
||||||
// ★ 卡死自愈检查
|
// ★ 卡死自愈检查
|
||||||
watchdogCheckLocked();
|
if (h) watchdogCheckLocked();
|
||||||
|
|
||||||
// 系统忙检查
|
// 系统忙检查
|
||||||
// ★ 修复:STORING/RELAYING 都拒绝(防止中继中改 gd32_ota 分区被 ESP32 OTA 误触发)。
|
if (h && (m_sysState == OTA_SYS_GD32_STORING ||
|
||||||
// STORED/COMPLETE 允许——它们是非活跃状态,ESP32 OTA 与 gd32_ota 分区无冲突。
|
m_sysState == OTA_SYS_GD32_RELAYING)) {
|
||||||
if (m_sysState == OTA_SYS_GD32_STORING ||
|
|
||||||
m_sysState == OTA_SYS_GD32_RELAYING) {
|
|
||||||
DEBUG_WARN(TAG, "GD32 正在 %s,拒绝 ESP32 OTA",
|
DEBUG_WARN(TAG, "GD32 正在 %s,拒绝 ESP32 OTA",
|
||||||
m_sysState == OTA_SYS_GD32_STORING ? "存储" : "中继");
|
m_sysState == OTA_SYS_GD32_STORING ? "存储" : "中继");
|
||||||
unlock();
|
unlock(h);
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 委托 Esp32OtaCore 处理分区/互斥逻辑
|
esp_err_t err = ESP_ERR_INVALID_STATE;
|
||||||
esp_err_t err = Esp32OtaCore::instance().begin(total_size);
|
if (h) {
|
||||||
if (err == ESP_OK) {
|
// 委托 Esp32OtaCore 处理分区/互斥逻辑
|
||||||
m_sysState = OTA_SYS_ESP32_RECEIVING;
|
err = Esp32OtaCore::instance().begin(total_size);
|
||||||
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
|
if (err == ESP_OK) {
|
||||||
DEBUG_INFO(TAG, "ESP32 OTA 开始: %" PRIu32 " 字节, 系统状态 → ESP32_RECEIVING", total_size);
|
m_sysState = OTA_SYS_ESP32_RECEIVING;
|
||||||
} else {
|
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
|
||||||
DEBUG_WARN(TAG, "ESP32 OTA begin 失败: %s", esp_err_to_name(err));
|
DEBUG_INFO(TAG, "ESP32 OTA 开始: %" PRIu32 " 字节, 系统状态 → ESP32_RECEIVING", total_size);
|
||||||
|
} else {
|
||||||
|
DEBUG_WARN(TAG, "ESP32 OTA begin 失败: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock();
|
unlock(h);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OtaCoordinator::notifyEsp32ReceiveComplete()
|
void OtaCoordinator::notifyEsp32ReceiveComplete()
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
if (m_sysState == OTA_SYS_ESP32_RECEIVING) {
|
if (h && m_sysState == OTA_SYS_ESP32_RECEIVING) {
|
||||||
m_sysState = OTA_SYS_ESP32_FINALIZING;
|
m_sysState = OTA_SYS_ESP32_FINALIZING;
|
||||||
DEBUG_INFO(TAG, "ESP32 固件接收完毕, 系统状态 → ESP32_FINALIZING");
|
DEBUG_INFO(TAG, "ESP32 固件接收完毕, 系统状态 → ESP32_FINALIZING");
|
||||||
}
|
}
|
||||||
unlock();
|
unlock(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OtaCoordinator::notifyEsp32FinalizeComplete()
|
void OtaCoordinator::notifyEsp32FinalizeComplete()
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
m_sysState = OTA_SYS_ESP32_REBOOTING;
|
if (h) {
|
||||||
DEBUG_INFO(TAG, "ESP32 OTA 收尾完成, 系统状态 → ESP32_REBOOTING");
|
m_sysState = OTA_SYS_ESP32_REBOOTING;
|
||||||
unlock();
|
DEBUG_INFO(TAG, "ESP32 OTA 收尾完成, 系统状态 → ESP32_REBOOTING");
|
||||||
|
}
|
||||||
|
unlock(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OtaCoordinator::notifyEsp32Rebooting()
|
void OtaCoordinator::notifyEsp32Rebooting()
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
m_sysState = OTA_SYS_ESP32_REBOOTING;
|
if (h) {
|
||||||
DEBUG_INFO(TAG, "ESP32 即将重启, 系统状态 → ESP32_REBOOTING");
|
m_sysState = OTA_SYS_ESP32_REBOOTING;
|
||||||
unlock();
|
DEBUG_INFO(TAG, "ESP32 即将重启, 系统状态 → ESP32_REBOOTING");
|
||||||
|
}
|
||||||
|
unlock(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -338,23 +344,25 @@ void OtaCoordinator::notifyEsp32Rebooting()
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
void OtaCoordinator::abortCurrent()
|
void OtaCoordinator::abortCurrent()
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
DEBUG_INFO(TAG, "中止当前 OTA 操作 (state=%d)", (int)m_sysState);
|
if (h) {
|
||||||
|
DEBUG_INFO(TAG, "中止当前 OTA 操作 (state=%d)", (int)m_sysState);
|
||||||
|
|
||||||
if (m_sysState == OTA_SYS_GD32_STORING) {
|
if (m_sysState == OTA_SYS_GD32_STORING) {
|
||||||
OtaRelay::metaClear();
|
OtaRelay::metaClear();
|
||||||
} else if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
} else if (m_sysState == OTA_SYS_GD32_RELAYING) {
|
||||||
OtaRelay::instance().reset();
|
OtaRelay::instance().reset();
|
||||||
} else if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
} else if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
|
||||||
m_sysState == OTA_SYS_ESP32_FINALIZING) {
|
m_sysState == OTA_SYS_ESP32_FINALIZING) {
|
||||||
Esp32OtaCore::instance().abort();
|
Esp32OtaCore::instance().abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sysState = OTA_SYS_IDLE;
|
||||||
|
m_gd32StorageDone = false;
|
||||||
|
m_busySinceTick = 0; // ★ 离开忙状态
|
||||||
|
DEBUG_INFO(TAG, "已中止, 系统状态 → IDLE");
|
||||||
}
|
}
|
||||||
|
unlock(h);
|
||||||
m_sysState = OTA_SYS_IDLE;
|
|
||||||
m_gd32StorageDone = false;
|
|
||||||
m_busySinceTick = 0; // ★ 离开忙状态
|
|
||||||
DEBUG_INFO(TAG, "已中止, 系统状态 → IDLE");
|
|
||||||
unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -364,9 +372,9 @@ ota_system_state_t OtaCoordinator::getSystemState() const
|
|||||||
{
|
{
|
||||||
// 对于纯读取,短暂加锁保证一致性
|
// 对于纯读取,短暂加锁保证一致性
|
||||||
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
|
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
|
||||||
self->lock();
|
bool h = self->lock();
|
||||||
ota_system_state_t s = m_sysState;
|
ota_system_state_t s = m_sysState;
|
||||||
self->unlock();
|
self->unlock(h);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,9 +424,9 @@ bool OtaCoordinator::getGd32AppAlive() const
|
|||||||
bool OtaCoordinator::isGd32StorageDone() const
|
bool OtaCoordinator::isGd32StorageDone() const
|
||||||
{
|
{
|
||||||
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
|
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
|
||||||
self->lock();
|
bool h = self->lock();
|
||||||
bool done = m_gd32StorageDone;
|
bool done = m_gd32StorageDone;
|
||||||
self->unlock();
|
self->unlock(h);
|
||||||
return done;
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,10 +435,12 @@ bool OtaCoordinator::isGd32StorageDone() const
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
void OtaCoordinator::reset()
|
void OtaCoordinator::reset()
|
||||||
{
|
{
|
||||||
lock();
|
bool h = lock();
|
||||||
m_sysState = OTA_SYS_IDLE;
|
if (h) {
|
||||||
m_gd32StorageDone = false;
|
m_sysState = OTA_SYS_IDLE;
|
||||||
m_busySinceTick = 0; // ★ 复位
|
m_gd32StorageDone = false;
|
||||||
DEBUG_INFO(TAG, "协调器已完全复位");
|
m_busySinceTick = 0; // ★ 复位
|
||||||
unlock();
|
DEBUG_INFO(TAG, "协调器已完全复位");
|
||||||
|
}
|
||||||
|
unlock(h);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,8 +174,12 @@ private:
|
|||||||
OtaCoordinator& operator=(const OtaCoordinator&) = delete;
|
OtaCoordinator& operator=(const OtaCoordinator&) = delete;
|
||||||
|
|
||||||
// 线程安全辅助
|
// 线程安全辅助
|
||||||
void lock();
|
// ★ lock() 返回是否成功 take;unlock(handle) 据此决定是否 give。
|
||||||
void unlock();
|
// 旧实现用共享 bool m_locked 记录 take 结果,多任务并发 lock() 时
|
||||||
|
// 超时方会把 m_locked 置 false,覆盖持锁方的 true,导致 unlock() 跳过 give,
|
||||||
|
// mutex 永久死锁 → 后续 take 超时触发 vTaskPriorityDisinherit assert → PANIC。
|
||||||
|
bool lock();
|
||||||
|
void unlock(bool handle);
|
||||||
|
|
||||||
// ★ 卡死自愈检查:若忙状态停留超过 watchdog 超时,强制转为 ERROR。
|
// ★ 卡死自愈检查:若忙状态停留超过 watchdog 超时,强制转为 ERROR。
|
||||||
// 用于在中继 task 因任何原因(崩溃/SPI 死锁/看门狗复位残留)未能回调
|
// 用于在中继 task 因任何原因(崩溃/SPI 死锁/看门狗复位残留)未能回调
|
||||||
@@ -194,7 +198,6 @@ private:
|
|||||||
|
|
||||||
// 互斥锁
|
// 互斥锁
|
||||||
SemaphoreHandle_t m_mutex = nullptr;
|
SemaphoreHandle_t m_mutex = nullptr;
|
||||||
bool m_locked = false; // ★ 记录最近一次 lock() 是否成功 take;unlock() 据此跳过错误 give
|
|
||||||
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(100);
|
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(100);
|
||||||
|
|
||||||
// ★ 忙状态 watchdog 超时。GD32 中继理论最大耗时 ≈ 60s(全局超时) + 15s(APP存活轮询) + 1s(bootloader) ≈ 76s,
|
// ★ 忙状态 watchdog 超时。GD32 中继理论最大耗时 ≈ 60s(全局超时) + 15s(APP存活轮询) + 1s(bootloader) ≈ 76s,
|
||||||
|
|||||||
@@ -420,8 +420,6 @@ ota_result_t OtaRelay::forwardFirmware()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 全局超时计算
|
// 全局超时计算
|
||||||
// ★ 收敛到 1 min:30.8 KB / 30 KB·s ≈ 1 s 数据 + 握手/认证/等待 ≈ 2~3 s,
|
|
||||||
// 1 min 已是数十倍安全余量。旧 10 min 会在中继异常时把协调器锁住极久。
|
|
||||||
constexpr uint32_t kMinGlobalTimeoutMs = 20U * 1000U; // 20 s 最小超时
|
constexpr uint32_t kMinGlobalTimeoutMs = 20U * 1000U; // 20 s 最小超时
|
||||||
constexpr uint32_t kMaxGlobalTimeoutMs = 60U * 1000U; // 60 s 硬上限
|
constexpr uint32_t kMaxGlobalTimeoutMs = 60U * 1000U; // 60 s 硬上限
|
||||||
uint32_t dynamic_ms = kMinGlobalTimeoutMs + (meta.total_size / 1024U) * 50U; // 50 ms / KB
|
uint32_t dynamic_ms = kMinGlobalTimeoutMs + (meta.total_size / 1024U) * 50U; // 50 ms / KB
|
||||||
@@ -444,10 +442,6 @@ ota_result_t OtaRelay::forwardFirmware()
|
|||||||
if (res != OTA_RESULT_OK) goto cleanup;
|
if (res != OTA_RESULT_OK) goto cleanup;
|
||||||
|
|
||||||
// 步骤2: SIZE_NEG(内部完成大小协商 → TRANSFERRING)
|
// 步骤2: SIZE_NEG(内部完成大小协商 → TRANSFERRING)
|
||||||
// ★ 此处检查在 SIZE_NEG handler 执行之前。从日志看握手刚完成(~0.6 s)就触发超限,
|
|
||||||
// 真实根因:handleSizeNeg 是同步阻塞的——它本身就会跑完整个 doSendFirmwareSize,
|
|
||||||
// 真正的"步骤2 超限"应在 handler 之后检查。修复:把"前已超限"检查全部删除,
|
|
||||||
// 改为在 processEvent 之后用 deadline 校验返回值。handler 内部已经有自己的超时。
|
|
||||||
res = processEvent(RELAY_EVT_START, nullptr, 0);
|
res = processEvent(RELAY_EVT_START, nullptr, 0);
|
||||||
if (res != OTA_RESULT_OK) goto cleanup;
|
if (res != OTA_RESULT_OK) goto cleanup;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "freertos/semphr.h"
|
#include "freertos/semphr.h"
|
||||||
|
#include "esp_log.h"
|
||||||
#include "esp_wifi.h"
|
#include "esp_wifi.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
@@ -1164,21 +1165,37 @@ static esp_err_t system_status_handler(httpd_req_t *req)
|
|||||||
gd32_version_t pingVer = {};
|
gd32_version_t pingVer = {};
|
||||||
OtaRelay::getGd32Version(&pingVer);
|
OtaRelay::getGd32Version(&pingVer);
|
||||||
|
|
||||||
/* 若尚无 GD32 版本信息且 OTA 中继空闲,尝试一次 SPI PING 获取 */
|
/* 若尚无 GD32 版本信息且 OTA 中继空闲,尝试 SPI PING 获取(最多 5 次) */
|
||||||
if (pingVer.app_major == 0 &&
|
// ★ 修复:OTA 活跃期间(存储/中继/ESP32 OTA)跳过 appPing(),
|
||||||
|
// 避免 HTTP 任务长期持有 SPI mutex 与 relay 任务争用,
|
||||||
|
// 导致优先级继承异常 → vTaskPriorityDisinheritAfterTimeout assert。
|
||||||
|
ota_system_state_t curSysState = OtaCoordinator::instance().getSystemState();
|
||||||
|
bool ota_busy = (curSysState == OTA_SYS_GD32_STORING ||
|
||||||
|
curSysState == OTA_SYS_GD32_RELAYING ||
|
||||||
|
curSysState == OTA_SYS_ESP32_RECEIVING ||
|
||||||
|
curSysState == OTA_SYS_ESP32_FINALIZING ||
|
||||||
|
curSysState == OTA_SYS_ESP32_REBOOTING);
|
||||||
|
if (!ota_busy &&
|
||||||
|
pingVer.app_major == 0 &&
|
||||||
pingVer.boot_major == 0 &&
|
pingVer.boot_major == 0 &&
|
||||||
OtaRelay::getState() == RELAY_IDLE) {
|
OtaRelay::getState() == RELAY_IDLE) {
|
||||||
static uint32_t last_ping_ms = 0;
|
static uint32_t last_ping_ms = 0;
|
||||||
|
static uint8_t ping_attempts = 0;
|
||||||
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000);
|
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000);
|
||||||
if (now_ms - last_ping_ms > 5000) { /* 5 秒节流 */
|
if (ping_attempts < 5 && now_ms - last_ping_ms > 5000) { /* 5 秒节流 */
|
||||||
last_ping_ms = now_ms;
|
last_ping_ms = now_ms;
|
||||||
|
++ping_attempts;
|
||||||
uint32_t uptime = 0;
|
uint32_t uptime = 0;
|
||||||
gd32_version_t ver = {};
|
gd32_version_t ver = {};
|
||||||
if (SpiMaster::instance().appPing(500, &uptime, &ver) == ESP_OK) {
|
if (SpiMaster::instance().appPing(500, &uptime, &ver) == ESP_OK) {
|
||||||
OtaRelay::setGd32AppAlive(true);
|
OtaRelay::setGd32AppAlive(true);
|
||||||
OtaRelay::setGd32Version(ver);
|
OtaRelay::setGd32Version(ver);
|
||||||
|
ping_attempts = 0; /* 成功后重置,允许后续更新 */
|
||||||
} else {
|
} else {
|
||||||
OtaRelay::setGd32AppAlive(false);
|
OtaRelay::setGd32AppAlive(false);
|
||||||
|
if (ping_attempts >= 5) {
|
||||||
|
DEBUG_WARN("STATUS", "GD32 版本号读取失败,已达最大重试次数 (5),停止尝试");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1502,7 +1519,7 @@ bool WifiOta::earlyInit()
|
|||||||
nvs_erase_all(nvs_handle);
|
nvs_erase_all(nvs_handle);
|
||||||
nvs_commit(nvs_handle);
|
nvs_commit(nvs_handle);
|
||||||
nvs_close(nvs_handle);
|
nvs_close(nvs_handle);
|
||||||
DEBUG_INFO("WIFI", "冷启动: 已清除 WiFi NVS 缓存 (nvs.net80211)");
|
DEBUG_DBG("WIFI", "冷启动: 已清除 WiFi NVS 缓存 (nvs.net80211)");
|
||||||
} else {
|
} else {
|
||||||
DEBUG_INFO("WIFI", "冷启动: 无 WiFi NVS 缓存分区,跳过清除");
|
DEBUG_INFO("WIFI", "冷启动: 无 WiFi NVS 缓存分区,跳过清除");
|
||||||
}
|
}
|
||||||
@@ -1512,7 +1529,7 @@ bool WifiOta::earlyInit()
|
|||||||
nvs_erase_all(nvs_handle);
|
nvs_erase_all(nvs_handle);
|
||||||
nvs_commit(nvs_handle);
|
nvs_commit(nvs_handle);
|
||||||
nvs_close(nvs_handle);
|
nvs_close(nvs_handle);
|
||||||
DEBUG_INFO("WIFI", "冷启动: 已清除 PHY 校准缓存,将重新校准");
|
DEBUG_DBG("WIFI", "冷启动: 已清除 PHY 校准缓存,将重新校准");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1522,7 +1539,7 @@ bool WifiOta::earlyInit()
|
|||||||
if (mtx) {
|
if (mtx) {
|
||||||
// 通过临时获取再赋值(私有成员在同类内可访问)
|
// 通过临时获取再赋值(私有成员在同类内可访问)
|
||||||
WifiOta::instance().m_stateMutex = mtx;
|
WifiOta::instance().m_stateMutex = mtx;
|
||||||
DEBUG_INFO("WIFI", "状态互斥锁已创建");
|
DEBUG_DBG("WIFI", "状态互斥锁已创建");
|
||||||
} else {
|
} else {
|
||||||
DEBUG_ERR("WIFI", "创建状态互斥锁失败");
|
DEBUG_ERR("WIFI", "创建状态互斥锁失败");
|
||||||
}
|
}
|
||||||
@@ -1542,14 +1559,14 @@ bool WifiOta::earlyInit()
|
|||||||
if (ret != pdPASS) {
|
if (ret != pdPASS) {
|
||||||
DEBUG_ERR("WIFI", "创建重连任务失败");
|
DEBUG_ERR("WIFI", "创建重连任务失败");
|
||||||
} else {
|
} else {
|
||||||
DEBUG_INFO("WIFI", "WiFi 重连任务已创建");
|
DEBUG_DBG("WIFI", "WiFi 重连任务已创建");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册 WiFi/IP 事件回调(必须在 esp_wifi_init 之前)
|
// 注册 WiFi/IP 事件回调(必须在 esp_wifi_init 之前)
|
||||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL));
|
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL));
|
||||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL));
|
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL));
|
||||||
DEBUG_INFO("WIFI", "WiFi/IP 事件回调已注册");
|
DEBUG_DBG("WIFI", "WiFi/IP 事件回调已注册");
|
||||||
|
|
||||||
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||||
if (sta_netif == NULL) {
|
if (sta_netif == NULL) {
|
||||||
@@ -1557,12 +1574,18 @@ bool WifiOta::earlyInit()
|
|||||||
if (sta_netif == NULL) {
|
if (sta_netif == NULL) {
|
||||||
DEBUG_ERR("WIFI", "创建 STA netif 失败");
|
DEBUG_ERR("WIFI", "创建 STA netif 失败");
|
||||||
} else {
|
} else {
|
||||||
DEBUG_INFO("WIFI", "STA netif 已创建");
|
DEBUG_DBG("WIFI", "STA netif 已创建");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DEBUG_INFO("WIFI", "STA netif 已存在,跳过创建");
|
DEBUG_INFO("WIFI", "STA netif 已存在,跳过创建");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ★ 压制 WiFi 框架内部 INFO 日志(pp/net80211/wifi/wifi_init 等 tag 大量启动打印)
|
||||||
|
esp_log_level_set("wifi", ESP_LOG_WARN);
|
||||||
|
esp_log_level_set("wifi_init", ESP_LOG_WARN);
|
||||||
|
esp_log_level_set("pp", ESP_LOG_WARN);
|
||||||
|
esp_log_level_set("net80211", ESP_LOG_WARN);
|
||||||
|
|
||||||
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
|
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg));
|
ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg));
|
||||||
DEBUG_INFO("WIFI", "WiFi driver 初始化成功");
|
DEBUG_INFO("WIFI", "WiFi driver 初始化成功");
|
||||||
|
|||||||
@@ -285,13 +285,18 @@ bool SpiMaster::otaFindFrame(const uint8_t *buf, size_t buf_len,
|
|||||||
*/
|
*/
|
||||||
esp_err_t SpiMaster::init()
|
esp_err_t SpiMaster::init()
|
||||||
{
|
{
|
||||||
LockGuard lock(m_mutex);
|
// ★ 修复:先创建 mutex,再使用 LockGuard。
|
||||||
// 创建 SPI 总线互斥锁(必须在 SPI 设备初始化之前)
|
// 旧代码在 LockGuard 构造时 m_mutex 仍为 NULL,
|
||||||
m_mutex = xSemaphoreCreateMutex();
|
// 虽然 LockGuard 会跳过 NULL take/give,但在极端情况下
|
||||||
if (m_mutex == NULL) {
|
// 其他任务可能在 mutex 创建后立即尝试 take,产生竞态。
|
||||||
DEBUG_ERR("SPI", "SPI 互斥锁创建失败");
|
if (!m_mutex) {
|
||||||
return ESP_ERR_NO_MEM;
|
m_mutex = xSemaphoreCreateMutex();
|
||||||
|
if (m_mutex == NULL) {
|
||||||
|
DEBUG_ERR("SPI", "SPI 互斥锁创建失败");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
LockGuard lock(m_mutex);
|
||||||
|
|
||||||
DEBUG_INFO("SPI", "=== 初始化 SPI2 总线 ===");
|
DEBUG_INFO("SPI", "=== 初始化 SPI2 总线 ===");
|
||||||
|
|
||||||
@@ -560,7 +565,6 @@ esp_err_t SpiMaster::appPing(uint32_t timeout_ms, uint32_t *uptime_sec, gd32_ver
|
|||||||
{
|
{
|
||||||
LockGuard lock(m_mutex);
|
LockGuard lock(m_mutex);
|
||||||
constexpr uint8_t kAppPingType = 0xA1U;
|
constexpr uint8_t kAppPingType = 0xA1U;
|
||||||
constexpr size_t kAppPingPollBytes = 32U;
|
|
||||||
|
|
||||||
uint8_t tx_frame[8] = {0};
|
uint8_t tx_frame[8] = {0};
|
||||||
size_t tx_len = 0U;
|
size_t tx_len = 0U;
|
||||||
@@ -574,7 +578,14 @@ esp_err_t SpiMaster::appPing(uint32_t timeout_ms, uint32_t *uptime_sec, gd32_ver
|
|||||||
while (xTaskGetTickCount() <= deadline) {
|
while (xTaskGetTickCount() <= deadline) {
|
||||||
++attempt;
|
++attempt;
|
||||||
|
|
||||||
esp_err_t err = transferClocked(tx_frame, tx_len, NULL, 0U);
|
/* 发送 PING 帧,填充至 16 字节:
|
||||||
|
* GD32 SPI 从机可能在 CS 断言时丢失首字节(EXTI/SPI 中断竞态),
|
||||||
|
* 填充确保即使首字节丢失,后续仍有完整 0xA5 帧头可供扫描定位。 */
|
||||||
|
uint8_t tx_padded[16];
|
||||||
|
memset(tx_padded, 0xFF, sizeof(tx_padded));
|
||||||
|
memcpy(tx_padded, tx_frame, tx_len); /* [0..4]=PING帧, [5..15]=0xFF */
|
||||||
|
|
||||||
|
esp_err_t err = transferClocked(tx_padded, sizeof(tx_padded), NULL, 0U);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
DEBUG_DBG("SPI", "APP PING 请求发送失败[%lu]: %s",
|
DEBUG_DBG("SPI", "APP PING 请求发送失败[%lu]: %s",
|
||||||
(unsigned long)attempt, esp_err_to_name(err));
|
(unsigned long)attempt, esp_err_to_name(err));
|
||||||
@@ -583,10 +594,10 @@ esp_err_t SpiMaster::appPing(uint32_t timeout_ms, uint32_t *uptime_sec, gd32_ver
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 给 GD32 充足时间处理 CS 释放中断并准备响应帧 */
|
/* 给 GD32 充足时间处理 CS 释放中断并准备响应帧 */
|
||||||
vTaskDelay(pdMS_TO_TICKS(3));
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
|
||||||
uint8_t rx_raw[kAppPingPollBytes];
|
uint8_t rx_raw[48]; /* 增大缓冲区,吸收 SPI 字节偏移 */
|
||||||
uint8_t dummy[kAppPingPollBytes];
|
uint8_t dummy[48];
|
||||||
memset(dummy, 0xFF, sizeof(dummy));
|
memset(dummy, 0xFF, sizeof(dummy));
|
||||||
memset(rx_raw, 0xFF, sizeof(rx_raw));
|
memset(rx_raw, 0xFF, sizeof(rx_raw));
|
||||||
|
|
||||||
|
|||||||
+22
-27
@@ -507,8 +507,8 @@ function updateSystemStatusUi(data) {
|
|||||||
document.getElementById('pillRelayTxt').textContent = relayText;
|
document.getElementById('pillRelayTxt').textContent = relayText;
|
||||||
var pr = document.getElementById('pillRelay');
|
var pr = document.getElementById('pillRelay');
|
||||||
if (rs === 255) pr.className = 'pill offline';
|
if (rs === 255) pr.className = 'pill offline';
|
||||||
else if (rs > 0 && rs < 17) pr.className = 'pill processing';
|
else if (rs > 0 && rs < 5) pr.className = 'pill processing';
|
||||||
else if (rs === 17 || rs === 18) pr.className = 'pill online';
|
else if (rs === 5) pr.className = 'pill online';
|
||||||
else pr.className = 'pill idle';
|
else pr.className = 'pill idle';
|
||||||
// OTA cache progress (GD32 upload to ESP32 flash)
|
// OTA cache progress (GD32 upload to ESP32 flash)
|
||||||
var total = ota.total_size || 0;
|
var total = ota.total_size || 0;
|
||||||
@@ -524,44 +524,39 @@ function updateSystemStatusUi(data) {
|
|||||||
var relayTotal = ota.relay_total_packets || 0;
|
var relayTotal = ota.relay_total_packets || 0;
|
||||||
var relayProgress = relayTotal > 0 ? Math.min(100, Math.round(relaySeq * 100 / relayTotal)) : 0;
|
var relayProgress = relayTotal > 0 ? Math.min(100, Math.round(relaySeq * 100 / relayTotal)) : 0;
|
||||||
var gd32AppAlive = ota.gd32_app_alive;
|
var gd32AppAlive = ota.gd32_app_alive;
|
||||||
if (rs > 0 && rs <= 8) {
|
// ★ 修复:后端实际状态机枚举 (main/relay/ota_relay.h):
|
||||||
// Authentication or size negotiation phase
|
// 0=RELAY_IDLE, 1=RELAY_HANDSHAKING, 2=RELAY_SIZE_NEG,
|
||||||
|
// 3=RELAY_TRANSFERRING, 4=RELAY_FINISHING, 5=RELAY_DONE, 0xFF=RELAY_ERROR
|
||||||
|
// 旧版 1-18 范围判断已与新状态机不匹配,进度条永远停在认证中阶段。
|
||||||
|
if (rs === 1) {
|
||||||
|
// HANDSHAKING: 握手 + 挑战认证
|
||||||
setActivePhase('gd32Relay');
|
setActivePhase('gd32Relay');
|
||||||
if (rs <= 6) {
|
setProgress(0, '握手与认证中...');
|
||||||
setProgress(0, '认证中: ' + (relayStateDetailMap[rs] || ''));
|
} else if (rs === 2) {
|
||||||
} else {
|
// SIZE_NEG: 固件大小协商
|
||||||
setProgress(0, '协商固件大小...');
|
setActivePhase('gd32Relay');
|
||||||
}
|
setProgress(0, '协商固件大小...');
|
||||||
} else if (rs >= 9 && rs <= 13) {
|
} else if (rs === 3) {
|
||||||
// Active data forwarding
|
// TRANSFERRING: 数据包传输
|
||||||
setActivePhase('gd32Relay');
|
setActivePhase('gd32Relay');
|
||||||
setProgress(relayProgress, '已转发 ' + relaySeq + ' / ' + relayTotal + ' 包');
|
setProgress(relayProgress, '已转发 ' + relaySeq + ' / ' + relayTotal + ' 包');
|
||||||
} else if (rs >= 14 && rs <= 15) {
|
} else if (rs === 4) {
|
||||||
// Transfer complete
|
// FINISHING: 结束OTA + APP存活验证
|
||||||
setActivePhase('gd32Relay');
|
|
||||||
setProgress(100, '数据转发完成 (' + relayTotal + ' 包)');
|
|
||||||
} else if (rs === 16) {
|
|
||||||
// OTA end sent
|
|
||||||
setActivePhase('gd32Relay');
|
|
||||||
setProgress(100, 'OTA 刷写中...');
|
|
||||||
} else if (rs === 17) {
|
|
||||||
// OTA complete
|
|
||||||
setActivePhase('gd32Relay');
|
|
||||||
setProgress(100, 'OTA 完成,等待 APP 启动...');
|
|
||||||
} else if (rs === 18) {
|
|
||||||
// APP verify phase
|
|
||||||
setActivePhase('gd32Relay');
|
setActivePhase('gd32Relay');
|
||||||
if (gd32AppAlive) {
|
if (gd32AppAlive) {
|
||||||
markPhaseDone('gd32Relay');
|
markPhaseDone('gd32Relay');
|
||||||
setProgress(100, 'GD32 刷写完成');
|
setProgress(100, 'GD32 刷写完成');
|
||||||
// Mark status bar as success
|
|
||||||
var sb = document.getElementById('stageStatusBar');
|
var sb = document.getElementById('stageStatusBar');
|
||||||
var st = document.getElementById('stageStatusText');
|
var st = document.getElementById('stageStatusText');
|
||||||
if (sb) sb.className = 'stage-status-bar ok';
|
if (sb) sb.className = 'stage-status-bar ok';
|
||||||
if (st) st.textContent = 'GD32 刷写完成';
|
if (st) st.textContent = 'GD32 刷写完成';
|
||||||
} else {
|
} else {
|
||||||
setProgress(100, '转发完成,验证 APP 存活...');
|
setProgress(100, '收尾中,等待 APP 启动...');
|
||||||
}
|
}
|
||||||
|
} else if (rs === 5) {
|
||||||
|
// DONE: 中继完成(metaClear 已清空,relaySeq/relayTotal 会被 reset 为 0)
|
||||||
|
setActivePhase('gd32Relay');
|
||||||
|
setProgress(100, 'OTA 完成');
|
||||||
} else if (total > 0 && stored < total && !isUploading) {
|
} else if (total > 0 && stored < total && !isUploading) {
|
||||||
// GD32 upload to cache in progress (handled by upload progress if uploading)
|
// GD32 upload to cache in progress (handled by upload progress if uploading)
|
||||||
setActivePhase('gd32Upload');
|
setActivePhase('gd32Upload');
|
||||||
|
|||||||
Reference in New Issue
Block a user