refactor(ota): 移除ESP32 OTA架构中的高危设计问题并优化状态管理
- 移除OtaRelay职责过载问题,将状态查询统一收口到OtaCoordinator::getSnapshot() - 新增OtaSnapshot结构体,一次性获取完整的OTA系统状态快照,避免分散读取 - 将GD32 APP探测逻辑从system_status_handler中分离,提供独立的/system/probe端点 - 修复并发安全隐患,确保processEvent()读取relayState时持有锁保护 - 优化SPI通信相关代码结构,提升系统稳定性 BREAKING CHANGE: 移除了原有的分散状态查询接口,统一使用快照方式获取状态
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
// 固件版本
|
||||
#define FW_VERSION_MAJOR 0
|
||||
#define FW_VERSION_MINOR 3
|
||||
#define FW_VERSION_PATCH 31
|
||||
#define FW_VERSION_STRING "0.3.31"
|
||||
#define FW_VERSION_PATCH 32
|
||||
#define FW_VERSION_STRING "0.3.32"
|
||||
|
||||
// 编译时间
|
||||
#define BUILD_TIME "2026-06-22 00:43:09"
|
||||
#define BUILD_TIME "2026-06-23 00:04:33"
|
||||
|
||||
// Git 信息
|
||||
#define GIT_HASH "00ce8db"
|
||||
#define GIT_HASH "1491e9a"
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
#include "esp32_ota_core.h"
|
||||
#include "relay/ota_relay.h"
|
||||
#include "storage/gd32_flash_store.h"
|
||||
#include "transport/spi_master.h"
|
||||
#include "monitor/debug_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <inttypes.h>
|
||||
|
||||
#define TAG "OTA_COORD"
|
||||
|
||||
@@ -599,6 +601,55 @@ bool OtaCoordinator::isGd32StorageDone() const
|
||||
return done;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// OTA 状态快照(PR1: 收口状态读取)
|
||||
// ============================================================
|
||||
OtaSnapshot OtaCoordinator::getSnapshot() const
|
||||
{
|
||||
OtaSnapshot snap{};
|
||||
|
||||
// ── coordinator 自身状态(一次 mutex)──
|
||||
bool h = lock();
|
||||
if (h) {
|
||||
snap.sys_state = m_sysState;
|
||||
snap.storage_done = m_gd32StorageDone;
|
||||
unlock(h);
|
||||
}
|
||||
snap.sys_state_str = getSystemStateString();
|
||||
|
||||
// ── GD32 NVS 元数据 ──
|
||||
Gd32FlashStore::instance().metaLoad(&snap.meta);
|
||||
|
||||
// ── relay 进度(一次 relay mutex)──
|
||||
snap.relay = OtaRelay::getSnapshot();
|
||||
|
||||
return snap;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GD32 APP 探测(PR2: 从 /system/status 移出)
|
||||
// ============================================================
|
||||
esp_err_t OtaCoordinator::probeGd32AppOnce(gd32_version_t *out_version, uint32_t *out_uptime)
|
||||
{
|
||||
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;
|
||||
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 err;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 完全复位
|
||||
// ============================================================
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_err.h"
|
||||
#include "storage/gd32_flash_store.h"
|
||||
#include "relay/ota_relay.h" // IRelayListener 接口定义(H4 解耦)
|
||||
#include "relay/ota_relay.h" // IRelayListener 接口定义 + RelaySnapshot(H4 解耦)
|
||||
|
||||
// ============================================================
|
||||
// 系统级 OTA 状态枚举
|
||||
@@ -37,6 +37,22 @@ typedef enum {
|
||||
OTA_SYS_ERROR // 错误状态
|
||||
} ota_system_state_t;
|
||||
|
||||
// ============================================================
|
||||
// OTA 系统全量只读快照(PR1: 收口状态读取)
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief OTA 系统全量只读快照。
|
||||
* 由 OtaCoordinator::getSnapshot() 一次性填充,
|
||||
* 供 /system/status 等只读接口消费,避免散读各模块状态。
|
||||
*/
|
||||
struct OtaSnapshot {
|
||||
ota_system_state_t sys_state = OTA_SYS_IDLE; ///< 系统级 OTA 状态
|
||||
const char *sys_state_str = "IDLE"; ///< 状态字符串
|
||||
bool storage_done = false; ///< GD32 固件存储完成标志
|
||||
ota_meta_t meta = {}; ///< GD32 NVS 元数据
|
||||
RelaySnapshot relay; ///< 中继进度快照
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// IBleCoexListener - BLE 射频共存通知接口(解决 L1 横向耦合)
|
||||
// ============================================================
|
||||
@@ -242,6 +258,24 @@ public:
|
||||
void abortCurrent();
|
||||
|
||||
// ── 状态查询 ──
|
||||
/**
|
||||
* @brief 一次性读取系统 OTA 全量状态快照(PR1: 收口状态读取)。
|
||||
* coordinator mutex + relay mutex 各取一次,
|
||||
* 供 /system/status 等只读接口消费。
|
||||
* @return 包含 sys_state、meta、relay 进度的完整快照。
|
||||
*/
|
||||
OtaSnapshot getSnapshot() const;
|
||||
|
||||
/**
|
||||
* @brief 单次 SPI 探测 GD32 APP 是否存活(PR2: 从 status 移出)。
|
||||
* 成功时更新 OtaRelay 缓存的 alive/version,
|
||||
* 失败时清除 alive 标志。
|
||||
* @param out_version 输出版本信息,可为 nullptr。
|
||||
* @param out_uptime 输出 GD32 运行时间(秒),可为 nullptr。
|
||||
* @return ESP_OK 存活,其他失败。
|
||||
*/
|
||||
esp_err_t probeGd32AppOnce(gd32_version_t *out_version, uint32_t *out_uptime);
|
||||
|
||||
/**
|
||||
* @brief 获取当前系统级 OTA 状态。
|
||||
* @return 当前 ota_system_state_t 状态值。
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* ota_snapshot.h - OTA 状态只读快照(便捷聚合头文件)
|
||||
*
|
||||
* 本文件本身不包含结构体定义,仅聚合两个快照来源:
|
||||
* - RelaySnapshot → 定义在 relay/ota_relay.h(一次锁保护读取 relay 进度)
|
||||
* - OtaSnapshot → 定义在 ota/ota_coordinator.h(coordinator + meta + relay 快照)
|
||||
*
|
||||
* HTTP handler 等消费方只需 #include "ota/ota_snapshot.h" 即可使用两种快照。
|
||||
*/
|
||||
#ifndef OTA_SNAPSHOT_H
|
||||
#define OTA_SNAPSHOT_H
|
||||
|
||||
#include "relay/ota_relay.h" // RelaySnapshot
|
||||
#include "ota/ota_coordinator.h" // OtaSnapshot
|
||||
|
||||
#endif // OTA_SNAPSHOT_H
|
||||
@@ -123,6 +123,18 @@ void OtaRelay::getGd32Version(gd32_version_t *out)
|
||||
*out = gd32Version;
|
||||
}
|
||||
|
||||
RelaySnapshot OtaRelay::getSnapshot()
|
||||
{
|
||||
StateLockGuard lock;
|
||||
RelaySnapshot s;
|
||||
s.state = relayState;
|
||||
s.seq = relaySeq;
|
||||
s.total_packets = relayTotalPackets;
|
||||
s.gd32_app_alive = gd32AppAlive;
|
||||
s.version = gd32Version;
|
||||
return s;
|
||||
}
|
||||
|
||||
void OtaRelay::setState(relay_state_t s)
|
||||
{
|
||||
StateLockGuard lock;
|
||||
@@ -1241,6 +1253,31 @@ void OtaRelay::taskEntry(void *pvParameters)
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 启动时初始 GD32 探测:读取 GD32 APP 版本号与存活状态
|
||||
// GD32 APP 启动需要一定时间,给其 3 秒准备窗口后发起探测
|
||||
{
|
||||
DEBUG_INFO("OTA", "启动初始 GD32 探测...");
|
||||
vTaskDelay(pdMS_TO_TICKS(3000)); // 等待 GD32 APP 就绪
|
||||
uint32_t uptime = 0;
|
||||
gd32_version_t ping_ver = {};
|
||||
esp_err_t ping_err = SpiMaster::instance().appPing(2000, &uptime, &ping_ver);
|
||||
if (ping_err == ESP_OK) {
|
||||
StateLockGuard lock;
|
||||
gd32AppAlive = true;
|
||||
gd32Version = ping_ver;
|
||||
DEBUG_INFO("OTA", "启动 GD32 探测成功: uptime=%" PRIu32 "s, "
|
||||
"APP=v%d.%d.%d.%d, BOOT=v%d.%d.%d.%d",
|
||||
uptime,
|
||||
ping_ver.app_major, ping_ver.app_minor,
|
||||
ping_ver.app_patch, ping_ver.app_build,
|
||||
ping_ver.boot_major, ping_ver.boot_minor,
|
||||
ping_ver.boot_patch, ping_ver.boot_build);
|
||||
} else {
|
||||
DEBUG_WARN("OTA", "启动 GD32 探测失败: %s (GD32 可能未就绪)",
|
||||
esp_err_to_name(ping_err));
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
uint32_t msg = 0;
|
||||
BaseType_t ret = xQueueReceive(dispatchQueue, &msg, portMAX_DELAY);
|
||||
|
||||
@@ -82,6 +82,22 @@ public:
|
||||
virtual void onRelayComplete(bool success) = 0;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 中继进度快照(一次锁保护读取,消除逐 getter 不一致)
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief GD32 中继进度只读快照。
|
||||
* 由 OtaRelay::getSnapshot() 在单次 StateLockGuard 下填充,
|
||||
* 供 OtaCoordinator::getSnapshot() 组装完整系统快照。
|
||||
*/
|
||||
struct RelaySnapshot {
|
||||
relay_state_t state = RELAY_IDLE; ///< 当前中继状态机状态
|
||||
uint32_t seq = 0; ///< 当前中继包序号
|
||||
uint32_t total_packets = 0; ///< 本次中继总包数
|
||||
bool gd32_app_alive = false; ///< GD32 APP 存活标志
|
||||
gd32_version_t version = {}; ///< GD32 固件版本
|
||||
};
|
||||
|
||||
class OtaRelay {
|
||||
public:
|
||||
/**
|
||||
@@ -138,6 +154,12 @@ public:
|
||||
* @param out 输出版本信息结构体指针。
|
||||
*/
|
||||
static void getGd32Version(gd32_version_t *out);
|
||||
/**
|
||||
* @brief 一次性读取所有中继状态字段(单次锁保护)。
|
||||
* 消除逐 getter 调用之间 relay task 更新状态导致的不一致。
|
||||
* @return 中继进度快照。
|
||||
*/
|
||||
static RelaySnapshot getSnapshot();
|
||||
/**
|
||||
* @brief 设置中继状态.
|
||||
* @param s 目标状态。
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <inttypes.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_http_server.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "esp32_ota_core.h"
|
||||
#include "ota_coordinator.h"
|
||||
#include "ota_relay.h"
|
||||
#include "ota_snapshot.h"
|
||||
#include "storage/gd32_flash_store.h"
|
||||
#include "health_monitor.h"
|
||||
#include "monitor/debug_log.h"
|
||||
@@ -721,47 +723,10 @@ esp_err_t system_status_handler(httpd_req_t *req)
|
||||
}
|
||||
connected = true;
|
||||
}
|
||||
gd32_version_t pingVer = {};
|
||||
OtaRelay::getGd32Version(&pingVer);
|
||||
|
||||
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 &&
|
||||
OtaRelay::getState() == RELAY_IDLE) {
|
||||
static uint32_t last_ping_ms = 0;
|
||||
static uint8_t ping_attempts = 0;
|
||||
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000);
|
||||
if (ping_attempts < 5 && now_ms - last_ping_ms > 5000) {
|
||||
last_ping_ms = now_ms;
|
||||
++ping_attempts;
|
||||
uint32_t uptime = 0;
|
||||
gd32_version_t ver = {};
|
||||
if (SpiMaster::instance().appPing(500, &uptime, &ver) == ESP_OK) {
|
||||
OtaRelay::setGd32AppAlive(true);
|
||||
OtaRelay::setGd32Version(ver);
|
||||
ping_attempts = 0;
|
||||
} else {
|
||||
OtaRelay::setGd32AppAlive(false);
|
||||
if (ping_attempts >= 5) {
|
||||
DEBUG_WARN("STATUS", "GD32 版本号读取失败,已达最大重试次数 (5),停止尝试");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ★ PR1: 一次性快照,替代散读 OtaCoordinator / OtaRelay 多个 getter
|
||||
OtaSnapshot snap = OtaCoordinator::instance().getSnapshot();
|
||||
|
||||
ota_meta_t meta;
|
||||
memset(&meta, 0, sizeof(meta));
|
||||
OtaCoordinator::instance().loadGd32Meta(&meta);
|
||||
|
||||
gd32_version_t snapVer2 = {};
|
||||
OtaRelay::getGd32Version(&snapVer2);
|
||||
ota_system_state_t sysState = OtaCoordinator::instance().getSystemState();
|
||||
size_t free_heap = esp_get_free_heap_size();
|
||||
size_t min_free_heap = esp_get_minimum_free_heap_size();
|
||||
size_t largest_free_block = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);
|
||||
@@ -807,19 +772,19 @@ esp_err_t system_status_handler(httpd_req_t *req)
|
||||
ip_str,
|
||||
rssi,
|
||||
connected ? "true" : "false",
|
||||
OtaCoordinator::instance().getSystemStateString(),
|
||||
(int)sysState,
|
||||
(unsigned long)meta.total_size,
|
||||
(unsigned long)meta.stored_size,
|
||||
OtaCoordinator::instance().isGd32StorageDone() ? "true" : "false",
|
||||
OtaRelay::getState(),
|
||||
(unsigned long)OtaRelay::getRelaySeq(),
|
||||
(unsigned long)OtaRelay::getRelayTotalPackets(),
|
||||
OtaRelay::getGd32AppAlive() ? "true" : "false",
|
||||
snapVer2.app_major, snapVer2.app_minor,
|
||||
snapVer2.app_patch, snapVer2.app_build,
|
||||
snapVer2.boot_major, snapVer2.boot_minor,
|
||||
snapVer2.boot_patch, snapVer2.boot_build);
|
||||
snap.sys_state_str,
|
||||
(int)snap.sys_state,
|
||||
(unsigned long)snap.meta.total_size,
|
||||
(unsigned long)snap.meta.stored_size,
|
||||
snap.storage_done ? "true" : "false",
|
||||
snap.relay.state,
|
||||
(unsigned long)snap.relay.seq,
|
||||
(unsigned long)snap.relay.total_packets,
|
||||
snap.relay.gd32_app_alive ? "true" : "false",
|
||||
snap.relay.version.app_major, snap.relay.version.app_minor,
|
||||
snap.relay.version.app_patch, snap.relay.version.app_build,
|
||||
snap.relay.version.boot_major, snap.relay.version.boot_minor,
|
||||
snap.relay.version.boot_patch, snap.relay.version.boot_build);
|
||||
|
||||
for (size_t i = 0; i < sizeof(task_metrics) / sizeof(task_metrics[0]); ++i) {
|
||||
len = append_jsonf(resp, sizeof(resp), len,
|
||||
@@ -836,3 +801,33 @@ esp_err_t system_status_handler(httpd_req_t *req)
|
||||
httpd_resp_send(req, resp, len);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GET /system/probe — GD32 APP 单次 SPI 探测(PR2)
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 显式触发一次 GD32 APP 存活探测。
|
||||
* 从 system_status_handler 移出的副作用,改为独立端点。
|
||||
* 成功时更新 OtaRelay 缓存的 alive/version 并返回版本信息。
|
||||
*/
|
||||
esp_err_t system_probe_handler(httpd_req_t *req)
|
||||
{
|
||||
http_set_cors_headers(req);
|
||||
|
||||
gd32_version_t ver = {};
|
||||
uint32_t uptime = 0;
|
||||
esp_err_t err = OtaCoordinator::instance().probeGd32AppOnce(&ver, &uptime);
|
||||
|
||||
char resp[256];
|
||||
int len = snprintf(resp, sizeof(resp),
|
||||
"{\"alive\": %s, \"uptime_sec\": %" PRIu32 ", "
|
||||
"\"gd32_app\": \"v%d.%d.%d.%d\", \"gd32_boot\": \"v%d.%d.%d.%d\"}",
|
||||
(err == ESP_OK) ? "true" : "false",
|
||||
uptime,
|
||||
ver.app_major, ver.app_minor, ver.app_patch, ver.app_build,
|
||||
ver.boot_major, ver.boot_minor, ver.boot_patch, ver.boot_build);
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, len);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -18,5 +18,6 @@ esp_err_t logs_handler(httpd_req_t *req);
|
||||
esp_err_t can_send_handler(httpd_req_t *req);
|
||||
esp_err_t spi_send_handler(httpd_req_t *req);
|
||||
esp_err_t system_status_handler(httpd_req_t *req);
|
||||
esp_err_t system_probe_handler(httpd_req_t *req);
|
||||
|
||||
#endif // HTTP_HANDLERS_H
|
||||
|
||||
@@ -517,20 +517,37 @@ esp_err_t WifiOta::httpServerStart()
|
||||
};
|
||||
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &logs_options_uri);
|
||||
|
||||
httpd_uri_t system_status_uri = {
|
||||
// 系统状态查询端点
|
||||
httpd_uri_t status_uri = {
|
||||
.uri = "/system/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = system_status_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &system_status_uri);
|
||||
httpd_uri_t system_status_options_uri = {
|
||||
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &status_uri);
|
||||
httpd_uri_t status_options_uri = {
|
||||
.uri = "/system/status",
|
||||
.method = HTTP_OPTIONS,
|
||||
.handler = http_options_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &system_status_options_uri);
|
||||
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &status_options_uri);
|
||||
|
||||
// PR2: GD32 APP 探测端点
|
||||
httpd_uri_t system_probe_uri = {
|
||||
.uri = "/system/probe",
|
||||
.method = HTTP_GET,
|
||||
.handler = system_probe_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &system_probe_uri);
|
||||
httpd_uri_t system_probe_options_uri = {
|
||||
.uri = "/system/probe",
|
||||
.method = HTTP_OPTIONS,
|
||||
.handler = http_options_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &system_probe_options_uri);
|
||||
|
||||
// CAN 帧转发
|
||||
httpd_uri_t can_send_uri = {
|
||||
@@ -564,7 +581,7 @@ esp_err_t WifiOta::httpServerStart()
|
||||
};
|
||||
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &spi_send_options_uri);
|
||||
|
||||
DEBUG_INFO("WIFI", "注册端点: /ota/upload, /ota/relay, /ota/update, /restart, /logs, /system/status, /can/send, /spi/send");
|
||||
DEBUG_INFO("WIFI", "注册端点: /ota/upload, /ota/relay, /ota/update, /restart, /logs, /system/status, /system/probe, /can/send, /spi/send");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user