refactor(main): 移除ota_mutex.h全局变量并重构ESP32 OTA互斥锁管理

- 将全局变量g_esp32OtaMutex和g_esp32OtaOccupied内聚到Esp32OtaCore类中
- 通过Esp32OtaCore::initMutex()方法初始化互斥锁替代原main.cpp中的全局初始化
- 重构HealthMonitor中WiFi连接状态管理,推送连接状态到本地成员而非直接查询
- 优化OtaCoordinator中const方法的线程安全实现,移除不必要的const_cast转换
- 封装OtaRelay的队列访问器,提供getDispatchQueue()和getResultQueue()方法
- 解决BLE与WiFi之间的横向耦合问题,通过OtaCoordinator进行射频共存管理
- 移除transport/spi_master.cpp中的CANFD接口实现到独立的canfd_bridge模块
- 优化BLE OTA中ESP32会话的幂等性和新会话处理逻辑

BREAKING CHANGE: 移除了ota_mutex.h全局头文件,相关功能迁移至Esp32OtaCore类中管理
This commit is contained in:
小龙虾
2026-06-22 01:53:16 +08:00
parent 00ce8dbbb5
commit 1491e9a3de
23 changed files with 598 additions and 290 deletions
+2 -5
View File
@@ -6,8 +6,6 @@ idf_component_register(
"ota/ota_coordinator.cpp" # OTA 多通道协调器(状态机/互斥)
"relay/ota_relay.cpp" # GD32 SPI OTA 中继状态机
"storage/gd32_flash_store.cpp" # GD32 Flash 存储抽象层(H1 提取)
"transport/spi_master.cpp" # SPI 总线驱动 + CANFD 接口
"transport/protocol.cpp" # 帧协议 + AES 加密 + CRC
"server/wifi_ota.cpp" # WiFi HTTP OTA 服务器
"server/http_handlers.cpp" # HTTP 路由处理(H2 从 wifi_ota 提取)
"server/ble_ota.cpp" # BLE GATT OTA 服务器
@@ -19,14 +17,13 @@ idf_component_register(
"ota"
"relay"
"storage" # GD32 Flash 存储抽象层头文件
"transport"
"server"
"monitor"
"include" # 公共头文件(app_config.h, version.h, ota_mutex.h)
"include" # 公共头文件(app_config.h, version.h)
REQUIRES
transport # M5: 传输层组件(SPI/CANFD/协议)
app_update # ESP32 OTA API
cjson # JSON 解析/生成
esp_driver_spi # SPI 主设备驱动
esp_driver_tsens # 温度传感器驱动
esp_driver_uart # UART 驱动(调试日志)
esp_wifi # WiFi STA/AP 模式
+5 -8
View File
@@ -22,23 +22,18 @@
#include "transport/protocol.h"
#include "monitor/debug_log.h"
#include "app_config.h"
#include "ota_mutex.h"
#include "ota/esp32_ota_core.h"
/**
* @brief 程序主入口,初始化系统组件并创建各 FreeRTOS 任务。
*/
// 全局 ESP32 OTA 互斥变量(声明在 ota_mutex.h)
SemaphoreHandle_t g_esp32OtaMutex = NULL;
volatile bool g_esp32OtaOccupied = false;
extern "C" void app_main(void)
{
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
// 创建 ESP32 OTA 全局互斥 Mutex
g_esp32OtaMutex = xSemaphoreCreateMutex();
g_esp32OtaOccupied = false;
// ★ M1: ESP32 OTA 分区互斥锁内聚到 Esp32OtaCore(替代旧的全局变量)
Esp32OtaCore::initMutex();
// 初始化网络接口 + 事件循环(必须最早)
ESP_ERROR_CHECK(esp_netif_init());
@@ -82,6 +77,8 @@ extern "C" void app_main(void)
// ★ 全局 OTA 协调器初始化(在 OTA task 创建前,确保 mutex 就绪)
OtaCoordinator::instance().init();
// ★ L1: 注册 BLE 射频共存监听器(WifiOta 实现 IBleCoexListener)
OtaCoordinator::instance().setBleCoexListener(&WifiOta::instance());
DEBUG_INFO("MAIN", "OTA 全局协调器已初始化");
esp_err_t ret = SpiMaster::instance().init();
-41
View File
@@ -1,41 +0,0 @@
/*
* ota_mutex.h - ESP32 OTA 全局互斥(BLE / WiFi 通道共享)
*
* 同一时刻只允许一个 OTA 源(BLE 或 WiFi)操作 ESP32 OTA 分区。
* 变量定义 + Mutex 创建在 main.cpp app_main()。
*/
#pragma once
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <cstdint>
extern SemaphoreHandle_t g_esp32OtaMutex; ///< ESP32 OTA 全局互斥锁句柄(main.cpp 中创建)
extern volatile bool g_esp32OtaOccupied; ///< ESP32 OTA 分区占用标志(true=正在OTA)
/**
* @brief 获取全局 OTA 占用标志(带超时)。
* @param timeout_ms 等待互斥锁的超时时间(毫秒)。
* @return true 成功获取并置占用标志,false 已占用或超时。
*/
static inline bool esp32_ota_mutex_acquire(uint32_t timeout_ms) {
if (!g_esp32OtaMutex) return false;
if (xSemaphoreTake(g_esp32OtaMutex, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) return false;
if (g_esp32OtaOccupied) {
xSemaphoreGive(g_esp32OtaMutex);
return false;
}
g_esp32OtaOccupied = true;
xSemaphoreGive(g_esp32OtaMutex);
return true;
}
/**
* @brief 释放全局 OTA 占用标志。
* @note 内部: Take Mutex → 清除标志 → Give Mutex。
*/
static inline void esp32_ota_mutex_release(void) {
if (!g_esp32OtaMutex) return;
xSemaphoreTake(g_esp32OtaMutex, portMAX_DELAY);
g_esp32OtaOccupied = false;
xSemaphoreGive(g_esp32OtaMutex);
}
+4 -4
View File
@@ -4,11 +4,11 @@
// 固件版本
#define FW_VERSION_MAJOR 0
#define FW_VERSION_MINOR 3
#define FW_VERSION_PATCH 30
#define FW_VERSION_STRING "0.3.30"
#define FW_VERSION_PATCH 31
#define FW_VERSION_STRING "0.3.31"
// 编译时间
#define BUILD_TIME "2026-06-21 23:33:01"
#define BUILD_TIME "2026-06-22 00:43:09"
// Git 信息
#define GIT_HASH "3fff959"
#define GIT_HASH "00ce8db"
+13 -2
View File
@@ -18,7 +18,6 @@
#include "health_monitor.h"
#include "debug_log.h"
#include "server/wifi_ota.h"
// ============================================================
// 单例
@@ -247,7 +246,7 @@ health_status_t HealthMonitor::checkBeforeOta()
DEBUG_WARN("HEALTH", "OTA 前检查: SYS_WARNING,继续传输但需注意");
}
if (!WifiOta::isConnected()) {
if (!m_wifiConnected) {
DEBUG_WARN("HEALTH", "OTA 前检查: WiFi 未连接");
return HEALTH_MON_CRITICAL;
}
@@ -312,6 +311,18 @@ health_status_t HealthMonitor::checkBeforeOtaRelay()
return status;
}
// ============================================================
// WiFi 连接状态推送(由 WiFi 事件回调调用)
// ============================================================
/**
* @brief 由 WiFi 事件回调推送连接状态。
* @param connected true=已连接,false=断开。
*/
void HealthMonitor::setWifiConnected(bool connected)
{
m_wifiConnected = connected;
}
// ============================================================
// WiFi RSSI 更新
// ============================================================
+9
View File
@@ -75,6 +75,14 @@ public:
/** @brief 获取健康监控任务句柄。 */
TaskHandle_t getTaskHandle() const { return m_taskHandle; }
/**
* @brief 由 WiFi 事件回调推送连接状态(解耦 HealthMonitor → WifiOta 依赖)。
* WiFi 连接/断开时由 wifi_ota 事件处理器主动调用,
* HealthMonitor 不再主动查询 WifiOta::isConnected()。
* @param connected true=已连接,false=断开。
*/
void setWifiConnected(bool connected);
private:
/** @brief 加锁读取健康状态。 */
health_state_t getState();
@@ -94,6 +102,7 @@ private:
int8_t m_lastWifiRssi = -127;
int64_t m_lastWifiRssiMs = 0;
TaskHandle_t m_taskHandle = NULL;
volatile bool m_wifiConnected = false; ///< WiFi 连接状态(由 WiFi 事件推送)
static void tempMonitorCallback(void *arg);
};
+54 -20
View File
@@ -17,12 +17,46 @@
#include "mbedtls/private/sha256.h"
#include "esp32_ota_core.h"
#include "ota_mutex.h"
#include "app_config.h"
#include "monitor/debug_log.h"
#define TAG "OTA_CORE"
// ============================================================
// 分区互斥锁(M1: 内聚自 ota_mutex.h 全局变量)
// ============================================================
SemaphoreHandle_t Esp32OtaCore::s_mutex = NULL;
volatile bool Esp32OtaCore::s_occupied = false;
void Esp32OtaCore::initMutex()
{
if (!s_mutex) {
s_mutex = xSemaphoreCreateMutex();
s_occupied = false;
}
}
bool Esp32OtaCore::acquireMutex(uint32_t timeout_ms)
{
if (!s_mutex) return false;
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) return false;
if (s_occupied) {
xSemaphoreGive(s_mutex);
return false;
}
s_occupied = true;
xSemaphoreGive(s_mutex);
return true;
}
void Esp32OtaCore::releaseMutex()
{
if (!s_mutex) return;
xSemaphoreTake(s_mutex, portMAX_DELAY);
s_occupied = false;
xSemaphoreGive(s_mutex);
}
/**
* @brief 获取 Esp32OtaCore 单例实例。
* @return Esp32OtaCore 引用。
@@ -56,7 +90,7 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
{
// ★ 全局互斥:必须在清理旧会话之前检查,
// 否则 WiFi 的 begin() 会先释放 BLE 持有的锁,再自己获取,导致互斥失效
if (!esp32_ota_mutex_acquire(1000)) {
if (!acquireMutex(1000)) {
DEBUG_ERR(TAG, "ESP32 OTA 被其他通道占用,拒绝 begin");
return ESP_ERR_INVALID_STATE;
}
@@ -67,10 +101,10 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
if (!m_ended) {
esp_ota_abort(m_handle);
}
esp32_ota_mutex_release();
releaseMutex();
resetState();
// 重新获取全局互斥(刚刚释放了)
if (!esp32_ota_mutex_acquire(1000)) {
if (!acquireMutex(1000)) {
DEBUG_ERR(TAG, "ESP32 OTA 重新获取互斥失败");
return ESP_ERR_INVALID_STATE;
}
@@ -91,7 +125,7 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
if (m_partition == NULL) {
DEBUG_ERR(TAG, "找不到可用的 OTA 分区");
esp32_ota_mutex_release();
releaseMutex();
return ESP_FAIL;
}
@@ -102,7 +136,7 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
DEBUG_ERR(TAG, "固件太大: %u > 分区大小: %u",
(unsigned)total_size, (unsigned)m_partition->size);
m_partition = nullptr;
esp32_ota_mutex_release();
releaseMutex();
return ESP_FAIL;
}
@@ -119,7 +153,7 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
DEBUG_ERR(TAG, "esp_ota_begin 失败: %s", esp_err_to_name(err));
m_partition = nullptr;
m_handle = 0;
esp32_ota_mutex_release();
releaseMutex();
return err;
}
@@ -248,7 +282,7 @@ esp_err_t Esp32OtaCore::finalize(const char *frontend_sha_hex, bool reboot,
DEBUG_ERR(TAG, "SHA 计算失败: %s,中止", esp_err_to_name(sha_err));
esp_ota_abort(m_handle);
m_ended = false;
esp32_ota_mutex_release();
releaseMutex();
resetState();
return sha_err;
}
@@ -279,7 +313,7 @@ esp_err_t Esp32OtaCore::finalize(const char *frontend_sha_hex, bool reboot,
frontend_sha_hex, device_hex);
esp_ota_abort(m_handle);
m_ended = false;
esp32_ota_mutex_release();
releaseMutex();
resetState();
return ESP_ERR_INVALID_VERSION;
}
@@ -300,7 +334,7 @@ esp_err_t Esp32OtaCore::finalize(const char *frontend_sha_hex, bool reboot,
// end 失败时仍可 abort(未真正 end)
esp_ota_abort(m_handle);
m_ended = false;
esp32_ota_mutex_release();
releaseMutex();
resetState();
return err;
}
@@ -310,12 +344,12 @@ esp_err_t Esp32OtaCore::finalize(const char *frontend_sha_hex, bool reboot,
err = esp_ota_set_boot_partition(m_partition);
if (err != ESP_OK) {
DEBUG_ERR(TAG, "esp_ota_set_boot_partition 失败: %s", esp_err_to_name(err));
esp32_ota_mutex_release();
releaseMutex();
resetState();
return err;
}
esp32_ota_mutex_release();
releaseMutex();
DEBUG_INFO(TAG, "ESP32 OTA 收尾完成,共 %u 字节", (unsigned)m_received);
// 保持 m_active 直到重启或下次 begin;resetState 由 begin/abort 处理
@@ -341,7 +375,7 @@ void Esp32OtaCore::abort()
if (!m_ended) {
esp_ota_abort(m_handle);
}
esp32_ota_mutex_release();
releaseMutex();
resetState();
DEBUG_INFO(TAG, "ESP32 OTA 已中止并清理");
}
@@ -351,7 +385,7 @@ void Esp32OtaCore::abort()
*
* 用于新通道(WiFi/BLE)发现锁被残留占用时,主动走完整的状态机退出流程:
* 1. 若会话活跃(m_active)→ 走 abort() 完整清理(esp_ota_abort + 释放锁 + 复位状态)
* 2. 若会话不活跃但锁被占(g_esp32OtaOccupied)→ 仅释放残留锁
* 2. 若会话不活跃但锁被占(s_occupied)→ 仅释放残留锁
* 3. 否则无需操作
*
* @return true 执行了清理(调用方可重试 begin),false 无需清理。
@@ -366,16 +400,16 @@ bool Esp32OtaCore::forceCleanup()
}
// 会话不活跃,但锁可能被残留占用
if (!g_esp32OtaMutex) return false;
if (!s_mutex) return false;
// 在互斥保护下检查 g_esp32OtaOccupied
if (xSemaphoreTake(g_esp32OtaMutex, pdMS_TO_TICKS(1000)) != pdTRUE) return false;
// 在互斥保护下检查 s_occupied
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return false;
bool was_occupied = g_esp32OtaOccupied;
bool was_occupied = s_occupied;
if (was_occupied) {
g_esp32OtaOccupied = false;
s_occupied = false;
}
xSemaphoreGive(g_esp32OtaMutex);
xSemaphoreGive(s_mutex);
if (was_occupied) {
DEBUG_WARN(TAG, "强制清理: 释放残留 OTA 锁(无活跃会话)");
+15
View File
@@ -19,6 +19,8 @@
#include "esp_err.h"
#include "esp_ota_ops.h"
#include "esp_partition.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
class Esp32OtaCore {
public:
@@ -99,9 +101,22 @@ public:
bool isActive() const { return m_active; }
/** @brief 获取已接收的字节数。 */
size_t getReceived() const { return m_received; }
// ── 分区互斥锁管理(M1: 从 ota_mutex.h 全局变量内聚到此类)──
/**
* @brief 初始化 ESP32 OTA 分区互斥锁(在 app_main 中调用)。
* 替代旧的全局 g_esp32OtaMutex / g_esp32OtaOccupied。
*/
static void initMutex();
private:
Esp32OtaCore() = default;
// ── 分区互斥锁(M1: 内聚自 ota_mutex.h)──
static SemaphoreHandle_t s_mutex; ///< ESP32 OTA 分区互斥锁
static volatile bool s_occupied; ///< 分区占用标志
static bool acquireMutex(uint32_t timeout_ms);
static void releaseMutex();
// 从 OTA 分区读回计算 SHA-256(WiFi 风格),结果写入 m_sha256,置 m_shaComputed=true
/**
* @brief 从 OTA 分区读回计算 SHA-256。
+33 -10
View File
@@ -27,7 +27,7 @@ OtaCoordinator& OtaCoordinator::instance()
// ============================================================
// 线程安全辅助
// ============================================================
bool OtaCoordinator::lock()
bool OtaCoordinator::lock() const
{
// ★ 修复: 返回 take 结果,由调用方传回 unlock()。
// 旧实现用成员 bool m_locked 记录 take 结果,多任务并发 lock() 时
@@ -42,7 +42,7 @@ bool OtaCoordinator::lock()
return ok;
}
void OtaCoordinator::unlock(bool handle)
void OtaCoordinator::unlock(bool handle) const
{
// ★ 修复: 仅在对应的 lock() 成功 take 时才 give。
if (handle && m_mutex) {
@@ -266,7 +266,8 @@ esp_err_t OtaCoordinator::triggerGd32Relay()
}
// 向 OtaRelay task 发送启动消息
if (h && !OtaRelay::dispatchQueue) {
QueueHandle_t dq = OtaRelay::getDispatchQueue();
if (h && !dq) {
DEBUG_ERR(TAG, "OtaRelay dispatchQueue 未就绪");
unlock(h);
return ESP_ERR_INVALID_STATE;
@@ -282,7 +283,7 @@ esp_err_t OtaCoordinator::triggerGd32Relay()
DEBUG_INFO(TAG, "触发 GD32 中继, 系统状态 → GD32_RELAYING");
uint32_t cmd = 1;
BaseType_t ret = xQueueSend(OtaRelay::dispatchQueue, &cmd, pdMS_TO_TICKS(200));
BaseType_t ret = xQueueSend(dq, &cmd, pdMS_TO_TICKS(200));
if (ret != pdTRUE) {
DEBUG_ERR(TAG, "向 OtaRelay 发送启动消息失败");
m_sysState = OTA_SYS_GD32_STORED;
@@ -341,6 +342,28 @@ void OtaCoordinator::onRelayComplete(bool success)
notifyRelayComplete(success);
}
// ============================================================
// BLE 射频共存管理(L1: 消除 ble_ota → wifi_ota 直接依赖)
// ============================================================
void OtaCoordinator::setBleCoexListener(IBleCoexListener *listener)
{
m_bleCoexListener = listener;
}
void OtaCoordinator::notifyBleCoexStart()
{
if (m_bleCoexListener) {
m_bleCoexListener->onBleOtaStart();
}
}
void OtaCoordinator::notifyBleCoexEnd()
{
if (m_bleCoexListener) {
m_bleCoexListener->onBleOtaEnd();
}
}
// ============================================================
// ESP32 OTA 阶段
// ============================================================
@@ -511,10 +534,10 @@ void OtaCoordinator::abortCurrent()
ota_system_state_t OtaCoordinator::getSystemState() const
{
// 对于纯读取,短暂加锁保证一致性
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
bool h = self->lock();
// ★ M1/L3: m_mutex 为 mutable + lock()/unlock() 为 const,无需 const_cast
bool h = lock();
ota_system_state_t s = m_sysState;
self->unlock(h);
unlock(h);
return s;
}
@@ -569,10 +592,10 @@ bool OtaCoordinator::getGd32AppAlive() const
/** @brief 获取 GD32 固件是否已存储完成(线程安全)。 */
bool OtaCoordinator::isGd32StorageDone() const
{
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
bool h = self->lock();
// ★ M1/L3: m_mutex 为 mutable + lock()/unlock() 为 const,无需 const_cast
bool h = lock();
bool done = m_gd32StorageDone;
self->unlock(h);
unlock(h);
return done;
}
+43 -4
View File
@@ -37,6 +37,25 @@ typedef enum {
OTA_SYS_ERROR // 错误状态
} ota_system_state_t;
// ============================================================
// IBleCoexListener - BLE 射频共存通知接口(解决 L1 横向耦合)
// ============================================================
// WifiOta 实现此接口并注册到 OtaCoordinator,
// BLE OTA 开始/结束时通过协调器通知 WiFi 层,
// 消除 ble_ota → wifi_ota 的直接依赖。
/**
* @brief BLE 射频共存监听器接口。
* BLE OTA 开始/结束时,协调器通过此接口通知 WiFi 层暂停/恢复射频活动。
*/
class IBleCoexListener {
public:
virtual ~IBleCoexListener() = default;
/** @brief BLE OTA 开始,暂停 WiFi RSSI 轮询等射频活动。 */
virtual void onBleOtaStart() = 0;
/** @brief BLE OTA 结束,恢复 WiFi 射频活动。 */
virtual void onBleOtaEnd() = 0;
};
/**
* @brief ESP32 OTA 收尾结果(由 finalizeForReboot 返回)。
* err 为 ESP_OK 时 device_sha_hex 有效;SHA 不匹配时也填充 device_sha_hex 供前端核对。
@@ -295,6 +314,23 @@ public:
*/
void onRelayComplete(bool success) override;
// ── BLE 射频共存管理(L1: 替代 ble_ota → wifi_ota 直接调用)──
/**
* @brief 注册 BLE 共存监听器(由 main 在初始化时调用)。
* @param listener WifiOta 实现的监听器指针,nullptr 取消注册。
*/
void setBleCoexListener(IBleCoexListener *listener);
/**
* @brief BLE OTA 开始时调用,通知 WiFi 层暂停 RSSI 轮询。
*/
void notifyBleCoexStart();
/**
* @brief BLE OTA 结束时调用,通知 WiFi 层恢复射频活动。
*/
void notifyBleCoexEnd();
private:
OtaCoordinator() = default;
~OtaCoordinator() = default;
@@ -306,8 +342,8 @@ private:
// 旧实现用共享 bool m_locked 记录 take 结果,多任务并发 lock() 时
// 超时方会把 m_locked 置 false,覆盖持锁方的 true,导致 unlock() 跳过 give,
// mutex 永久死锁 → 后续 take 超时触发 vTaskPriorityDisinherit assert → PANIC。
bool lock();
void unlock(bool handle);
bool lock() const;
void unlock(bool handle) const;
// ★ 卡死自愈检查:若忙状态停留超过 watchdog 超时,强制转为 ERROR。
// 用于在中继 task 因任何原因(崩溃/SPI 死锁/看门狗复位残留)未能回调
@@ -324,8 +360,11 @@ private:
// GD32 存储跟踪
bool m_gd32StorageDone = false;
// 互斥锁
SemaphoreHandle_t m_mutex = nullptr;
// 互斥锁(M1/L3: mutable 允许在 const 方法中加锁,消除 const_cast)
mutable SemaphoreHandle_t m_mutex = nullptr;
// BLE 共存监听器(L1: 由 WifiOta 实现并注册)
IBleCoexListener *m_bleCoexListener = nullptr;
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(100);
// ★ 忙状态 watchdog 超时。GD32 中继理论最大耗时 ≈ 60s(全局超时) + 15s(APP存活轮询) + 1s(bootloader) ≈ 76s,
+11
View File
@@ -52,6 +52,17 @@ IRelayListener* OtaRelay::getRelayListener()
return s_relayListener;
}
// ── 队列访问器(M3: 封装 public static 成员)──
QueueHandle_t OtaRelay::getDispatchQueue()
{
return dispatchQueue;
}
QueueHandle_t OtaRelay::getResultQueue()
{
return resultQueue;
}
// ============================================================
// 状态表:将每个 relay_state_t 映射到对应的 handler
// ============================================================
+18 -2
View File
@@ -186,6 +186,20 @@ public:
*/
static IRelayListener* getRelayListener();
// ── 队列访问器(解决 M3: 封装 public static 成员)──
/**
* @brief 获取事件分发队列句柄。
* 外部模块(OtaCoordinator / BLE)通过 getter 访问,不再直接引用静态成员。
* @return dispatchQueue 句柄,未初始化时为 NULL。
*/
static QueueHandle_t getDispatchQueue();
/**
* @brief 获取结果回传队列句柄。
* @return resultQueue 句柄,未初始化时为 NULL。
*/
static QueueHandle_t getResultQueue();
// ── Flash 操作已迁移至 Gd32FlashStore(storage/gd32_flash_store.h)──
// ── 公开状态成员 ──
@@ -194,8 +208,6 @@ public:
static uint32_t relayTotalPackets; ///< 本次中继总包数
static volatile bool gd32AppAlive; ///< GD32 APP 存活标志
static gd32_version_t gd32Version; ///< GD32 固件版本信息
static QueueHandle_t dispatchQueue; ///< 事件分发队列
static QueueHandle_t resultQueue; ///< 结果回传队列
static TaskHandle_t taskHandle; ///< OTA 中继任务句柄
static IRelayListener *s_relayListener; ///< 中继事件监听器(解决 H4)
@@ -230,6 +242,10 @@ public:
static constexpr size_t GD32_CHALLENGE_RESPONSE_LEN = 36;
private:
// ── 队列(M3: 由 getter 访问,外部不再直接引用)──
static QueueHandle_t dispatchQueue; ///< 事件分发队列
static QueueHandle_t resultQueue; ///< 结果回传队列
OtaRelay() = default;
~OtaRelay() = default;
OtaRelay(const OtaRelay&) = delete;
+34 -20
View File
@@ -38,9 +38,7 @@
// SHA-256 计算已抽离到 Esp32OtaCore,本文件不再直接引用 mbedtls
#include "app_config.h"
#include "ota_mutex.h"
#include "ble_ota.h"
#include "wifi_ota.h"
#include "esp32_ota_core.h"
#include "ota_coordinator.h"
#include "ota_relay.h"
@@ -234,7 +232,8 @@ static void ble_ota_boost_wifi_ps()
{
if (s_wifi_ps_boosted) return;
s_wifi_ps_boosted = true;
WifiOta::instance().setBleOtaActive(true); // ★ 暂停 WiFi RSSI 轮询
// ★ L1: 通过协调器通知 WiFi 层(替代直接调用 WifiOta)
OtaCoordinator::instance().notifyBleCoexStart();
DEBUG_INFO(TAG, "BLE OTA 进行中:暂停 WiFi RSSI 轮询(保持 WIFI_PS_NONE,STA 仍可响应 HTTP)");
}
@@ -245,7 +244,8 @@ static void ble_ota_restore_wifi_ps()
{
if (!s_wifi_ps_boosted) return;
s_wifi_ps_boosted = false;
WifiOta::instance().setBleOtaActive(false); // ★ 恢复 WiFi RSSI 轮询
// ★ L1: 通过协调器通知 WiFi 层(替代直接调用 WifiOta)
OtaCoordinator::instance().notifyBleCoexEnd();
DEBUG_INFO(TAG, "BLE OTA 结束:恢复 WiFi RSSI 轮询");
}
@@ -571,12 +571,10 @@ static esp_err_t ble_esp32_ota_begin(size_t total_size)
*/
static void ble_esp32_ota_abort()
{
// ★ 通知协调器释放 ESP32 通道占用(m_sysState → IDLE),
// 否则后续 begin() 会因协调器仍记为 ESP32_RECEIVING 而假拒绝。
ota_system_state_t cur = OtaCoordinator::instance().getSystemState();
if (cur == OTA_SYS_ESP32_RECEIVING || cur == OTA_SYS_ESP32_FINALIZING) {
OtaCoordinator::instance().abortCurrent();
}
// ★ 无条件通知协调器释放通道占用(m_sysState → IDLE)。
// abortCurrent() 对 IDLE 状态是空操作,对其他状态均复位到 IDLE,
// 确保后续 begin() 不会被残留的 ESP32_RECEIVING / ERROR 等状态假拒绝。
OtaCoordinator::instance().abortCurrent();
Esp32OtaCore::instance().abort();
BleOta::instance().m_esp32RebootPending = false;
// ★ 释放 reorder buffer
@@ -672,8 +670,9 @@ static void ble_gd32_relay_wait_task(void *pvParameters)
uint32_t result_msg = (uint32_t)OTA_RESULT_ERR_TIMEOUT;
bool got_result = false;
if (OtaRelay::resultQueue) {
got_result = (xQueueReceive(OtaRelay::resultQueue, &result_msg, pdMS_TO_TICKS(120000)) == pdTRUE);
QueueHandle_t rq = OtaRelay::getResultQueue();
if (rq) {
got_result = (xQueueReceive(rq, &result_msg, pdMS_TO_TICKS(120000)) == pdTRUE);
}
if (!got_result) {
@@ -783,9 +782,9 @@ static int ble_ota_ctrl_access(uint16_t conn_handle, uint16_t attr_handle,
DEBUG_INFO(TAG, "GD32 固件就绪且 SHA-256 匹配 (%" PRIu32 " / %" PRIu32 " 字节),通过协调器触发中继",
meta.stored_size, meta.total_size);
if (OtaRelay::resultQueue) {
if (OtaRelay::getResultQueue()) {
uint32_t stale = 0;
while (xQueueReceive(OtaRelay::resultQueue, &stale, 0) == pdTRUE) {
while (xQueueReceive(OtaRelay::getResultQueue(), &stale, 0) == pdTRUE) {
DEBUG_WARN(TAG, "清理旧 GD32 中继结果: %" PRIu32, stale);
}
}
@@ -1020,13 +1019,28 @@ static void ble_ota_process_data_batch(const uint8_t *buf, size_t recv_len)
DEBUG_ERR(TAG, "ESP32 固件大小非法: %" PRIu32, total_size);
return;
}
// ★ 幂等保护:若已有活跃 ESP32 OTA 会话,说明这是客户端因 ACK 超时
// 重传的 seq=0(同一会话续传)。若再次走 begin,会被协调器以
// "OTA 已 ESP32_RECEIVING" 拒绝 → 误报"被其他通道占用"并中止整条
// OTA 流程。此处跳过 begin 复用现有会话,后续 reorder buffer 会
// 按 seq 去重(seq=0 < expected 时作为重复包丢弃)。
// ★ 会话判定:若已有活跃 ESP32 OTA 会话,根据已接收字节数区分两种情况:
// ① m_received == 0:seq=0 尚未写入 flash,说明是客户端 ACK 超时后
// 重传的 seq=0(同一会话续传),跳过 begin。
// ② m_received > 0:seq=0 已写入且有后续数据,说明这是新的上传会话
// (前端 abort 后 BLE 断开未完成就发起了新传输),需 abort 旧会话
// 再 begin,否则 reorder buffer 以旧 expected 去重导致全部丢弃。
if (Esp32OtaCore::instance().isActive()) {
DEBUG_INFO(TAG, "ESP32 OTA 会话已激活,seq=0 视为重传,跳过 begin");
if (Esp32OtaCore::instance().getReceived() > 0) {
DEBUG_WARN(TAG, "ESP32 OTA 新会话检测: received=%u > 0, "
"中止旧会话并重新开始",
(unsigned)Esp32OtaCore::instance().getReceived());
ble_esp32_ota_abort();
esp_err_t err = ble_esp32_ota_begin(total_size);
if (err != ESP_OK) {
if (err == ESP_ERR_INVALID_STATE) {
sendFinishEvent(BLE_OTA_FINISH_ERR_MUTEX, 0);
}
return;
}
} else {
DEBUG_INFO(TAG, "ESP32 OTA 会话已激活,seq=0 视为重传,跳过 begin");
}
} else {
// 分区选择/大小校验/互斥由 Esp32OtaCore::begin 内部完成
esp_err_t err = ble_esp32_ota_begin(total_size);
+4 -2
View File
@@ -24,6 +24,7 @@
#include "health_monitor.h"
#include "monitor/debug_log.h"
#include "spi_master.h"
#include "canfd_bridge.h"
#include "ble_ota.h"
static constexpr uint32_t GD32_OTA_FLASH_SECTOR_SIZE = 4096U;
@@ -180,7 +181,8 @@ esp_err_t ota_upload_handler(httpd_req_t *req)
DEBUG_INFO("WIFI", "收到固件上传请求, 内容长度: %d 字节", req->content_len);
esp_wifi_set_ps(WIFI_PS_NONE);
WifiOta::instance().setBleOtaActive(false);
// ★ L1: WiFi OTA 开始时恢复 BLE 共存状态(通过协调器)
OtaCoordinator::instance().notifyBleCoexEnd();
char peek;
int peek_ret = httpd_req_recv(req, &peek, 1);
@@ -563,7 +565,7 @@ esp_err_t can_send_handler(httpd_req_t *req)
cJSON_Delete(root);
uint8_t resp_data[CAN_MAX_DLC] = {0};
int resp_len = SpiMaster::instance().canSend(ch, ext_id, tx_data, tx_dlc,
int resp_len = canFdSend(ch, ext_id, tx_data, tx_dlc,
resp_data, sizeof(resp_data), 1000);
char resp[512];
+17 -1
View File
@@ -22,7 +22,6 @@
#include "esp_mac.h"
#include "app_config.h"
#include "ota_mutex.h"
#include "wifi_ota.h"
#include "esp32_ota_core.h"
#include "ota_coordinator.h"
@@ -149,6 +148,8 @@ void WifiOta::wifiEventHandler(void* arg, esp_event_base_t event_base,
inst.setStaRetryCount(0);
inst.setStaRetryExhausted(false); // ★ 连接成功,重置耗尽标志
inst.stateUnlock();
// ★ M2: 推送 WiFi 连接状态给 HealthMonitor(解耦监控层主动查询)
HealthMonitor::instance().setWifiConnected(true);
if (inst.m_staConnectedSem) {
xSemaphoreGive(inst.m_staConnectedSem);
}
@@ -161,6 +162,8 @@ void WifiOta::wifiEventHandler(void* arg, esp_event_base_t event_base,
bool exhausted = inst.getStaRetryExhausted();
int retryCount = inst.getStaRetryCount();
inst.stateUnlock();
// ★ M2: 推送 WiFi 断开状态给 HealthMonitor
HealthMonitor::instance().setWifiConnected(false);
if (!exhausted) {
DEBUG_WARN("WIFI", "STA 断开, reason=%d, retry=%d",
@@ -602,6 +605,19 @@ bool WifiOta::isConnected()
return WifiOta::instance().getStaConnected();
}
// ============================================================
// IBleCoexListener 接口实现(L1: 通过协调器接收 BLE 共存通知)
// ============================================================
void WifiOta::onBleOtaStart()
{
setBleOtaActive(true);
}
void WifiOta::onBleOtaEnd()
{
setBleOtaActive(false);
}
// ----------------------------------------
// ★ WiFi 早期初始化(在 app_main 调度器启动前调用)
// 包括:NVS → netif → event handler → WiFi driver → 配置 → connect
+9 -1
View File
@@ -14,13 +14,15 @@
#include "freertos/task.h"
#include <atomic>
#include "ota/ota_coordinator.h" // IBleCoexListener 接口定义
/** @brief WiFi 工作模式枚举,区分 AP(设备热点)和 STA(连接路由器)模式。 */
typedef enum {
OTA_WIFI_MODE_AP, ///< AP 模式:设备作为热点供局域网设备连接
OTA_WIFI_MODE_STA ///< STA 模式:设备连接上级路由器
} ota_wifi_mode_t;
class WifiOta {
class WifiOta : public IBleCoexListener {
public:
/**
* @brief 获取 WifiOta 单例实例。
@@ -93,6 +95,12 @@ public:
/** @brief BLE OTA 开始传输时调用,暂停 WiFi RSSI 轮询等射频活动。 */
void setBleOtaActive(bool v) { m_bleOtaActive.store(v); }
// ── IBleCoexListener 接口实现(L1: 通过协调器接收 BLE 共存通知)──
/** @brief BLE OTA 开始,暂停 WiFi RSSI 轮询。 */
void onBleOtaStart() override;
/** @brief BLE OTA 结束,恢复 WiFi 射频活动。 */
void onBleOtaEnd() override;
/** @brief 获取 WiFi OTA 任务句柄。 */
TaskHandle_t getTaskHandle() const { return m_taskHandle; }
+69
View File
@@ -0,0 +1,69 @@
/*
* canfd_bridge.cpp - CANFD 3通道通信桥接实现(M4: 从 SpiMaster 提取)
*/
#include <string.h>
#include "canfd_bridge.h"
#include "spi_master.h"
/**
* @brief 发送一帧 CANFD 数据,可选等待并读取响应。
* 通过 SpiMaster 的公共 syncTransfer() 完成 SPI 传输。
* 注意: syncTransfer 内部会加 SPI 互斥锁,FreeRTOS mutex 为递归锁故安全。
*/
int canFdSend(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc,
uint8_t *recv_buf, uint8_t recv_max,
uint32_t timeout_ms)
{
if (channel >= CAN_CHANNEL_MAX || dlc > CAN_MAX_DLC || !data) {
return -1;
}
uint8_t tx_buf[72];
size_t tx_len = 0;
tx_buf[tx_len++] = (uint8_t)(channel & CAN_CHANNEL_MASK);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 24) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 16) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 8) & 0xFF);
tx_buf[tx_len++] = (uint8_t)(ext_id & 0xFF);
memcpy(&tx_buf[tx_len], data, dlc);
tx_len += dlc;
esp_err_t ret = SpiMaster::instance().syncTransfer(
0, tx_buf, tx_len, recv_buf, recv_max, timeout_ms);
if (ret != ESP_OK) {
return -1;
}
return (int)recv_max;
}
/**
* @brief 发送一帧无需响应的 CANFD 数据。
*/
int canFdSendNoReply(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc)
{
if (channel >= CAN_CHANNEL_MAX || dlc > CAN_MAX_DLC || !data) {
return -1;
}
uint8_t tx_buf[72];
size_t tx_len = 0;
tx_buf[tx_len++] = (uint8_t)(channel & CAN_CHANNEL_MASK);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 24) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 16) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 8) & 0xFF);
tx_buf[tx_len++] = (uint8_t)(ext_id & 0xFF);
memcpy(&tx_buf[tx_len], data, dlc);
tx_len += dlc;
esp_err_t ret = SpiMaster::instance().send(0, tx_buf, tx_len);
if (ret != ESP_OK) {
return -1;
}
return 0;
}
+64
View File
@@ -0,0 +1,64 @@
/*
* canfd_bridge.h - CANFD 3通道通信桥接(M4: 从 SpiMaster 提取)
*
* 封装 CANFD 帧格式构建 + SPI 传输,与 SpiMaster 的 OTA 协议职责解耦。
* 通过 SpiMaster 的公共 SPI 接口完成底层传输。
*/
#ifndef CANFD_BRIDGE_H
#define CANFD_BRIDGE_H
#include <stdint.h>
// ============================================================
// CANFD 帧格式(3通道)
// [通道编号: 1B] [扩展帧ID: 4B BE] [数据: 1~64B]
// ============================================================
#define CAN_EXTENDED_ID_BITS (29) ///< CAN 扩展帧 ID 位数
#define CAN_MAX_DLC (64) ///< 最大数据长度
#define CAN_CHANNEL_MASK (0x03) ///< 通道号掩码 (0~2)
// 通道定义
typedef enum {
CAN_CHANNEL_0 = 0,
CAN_CHANNEL_1 = 1,
CAN_CHANNEL_2 = 2,
CAN_CHANNEL_MAX = 3,
} can_channel_t;
// CANFD 帧结构
typedef struct {
can_channel_t channel; ///< CAN 通道 (0~2)
uint32_t ext_id; ///< 扩展帧 ID (29bit, Big-Endian 存储)
uint8_t data[CAN_MAX_DLC]; ///< CAN 数据负载 (最大 64 字节)
uint8_t dlc; ///< 数据长度 (1~64)
} can_frame_t;
/**
* @brief 发送一帧 CANFD 数据,可选等待并读取响应。
* 内部通过 SpiMaster 的公共 SPI 接口完成传输。
* @param channel CAN 通道编号(0~2)。
* @param ext_id 扩展帧 ID(29 位)。
* @param data CAN 数据负载。
* @param dlc 数据长度。
* @param recv_buf 接收数据缓冲区(可为 nullptr)。
* @param recv_max 接收缓冲区最大长度。
* @param timeout_ms 超时时间(毫秒)。
* @return 成功时返回接收数据长度,失败返回 -1。
*/
int canFdSend(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc,
uint8_t *recv_buf, uint8_t recv_max,
uint32_t timeout_ms);
/**
* @brief 发送一帧无需响应的 CANFD 数据。
* @param channel CAN 通道编号(0~2)。
* @param ext_id 扩展帧 ID(29 位)。
* @param data CAN 数据负载。
* @param dlc 数据长度。
* @return 成功返回 0,失败返回 -1。
*/
int canFdSendNoReply(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc);
#endif // CANFD_BRIDGE_H
+3 -77
View File
@@ -14,10 +14,12 @@
#include "driver/spi_master.h"
#include "soc/spi_struct.h"
#include "esp_err.h"
#include "esp_log.h"
#include "spi_master.h"
#include "monitor/debug_log.h"
static constexpr size_t kSpiTransferMaxBytes = 256U;
// ============================================================
@@ -724,83 +726,7 @@ esp_err_t SpiMaster::transferWithRetry(uint8_t cmd, const uint8_t *payload,
return ESP_ERR_TIMEOUT;
}
// ============================================================
// CANFD 接口
// ============================================================
/**
* @brief 发送一帧 CANFD 数据,可选等待并读取响应内容。
* @param channel CAN 通道编号(0~2)。
* @param ext_id 扩展帧 ID(29 位)。
* @param data CAN 数据负载。
* @param dlc 数据长度。
* @param recv_buf 接收数据缓冲区。
* @param recv_max 接收缓冲区最大长度。
* @param timeout_ms 超时时间(毫秒)。
* @return 成功时返回接收数据长度,失败返回 -1。
*/
int SpiMaster::canSend(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc,
uint8_t *recv_buf, uint8_t recv_max,
uint32_t timeout_ms)
{
LockGuard lock(m_mutex);
if (channel >= CAN_CHANNEL_MAX || dlc > CAN_MAX_DLC || !data) {
return -1;
}
uint8_t tx_buf[72];
size_t tx_len = 0;
tx_buf[tx_len++] = (uint8_t)(channel & CAN_CHANNEL_MASK);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 24) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 16) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 8) & 0xFF);
tx_buf[tx_len++] = (uint8_t)(ext_id & 0xFF);
memcpy(&tx_buf[tx_len], data, dlc);
tx_len += dlc;
esp_err_t ret = syncTransferUnlocked(0, tx_buf, tx_len, recv_buf, recv_max, timeout_ms);
if (ret != ESP_OK) {
return -1;
}
return (int)recv_max;
}
/**
* @brief 发送一帧无需响应的 CANFD 数据。
* @param channel CAN 通道编号(0~2)。
* @param ext_id 扩展帧 ID(29 位)。
* @param data CAN 数据负载。
* @param dlc 数据长度。
* @return 成功返回 0,失败返回 -1。
*/
int SpiMaster::canSendNoReply(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc)
{
LockGuard lock(m_mutex);
if (channel >= CAN_CHANNEL_MAX || dlc > CAN_MAX_DLC || !data) {
return -1;
}
uint8_t tx_buf[72];
size_t tx_len = 0;
tx_buf[tx_len++] = (uint8_t)(channel & CAN_CHANNEL_MASK);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 24) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 16) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 8) & 0xFF);
tx_buf[tx_len++] = (uint8_t)(ext_id & 0xFF);
memcpy(&tx_buf[tx_len], data, dlc);
tx_len += dlc;
esp_err_t ret = sendUnlocked(0, tx_buf, tx_len);
if (ret != ESP_OK) {
return -1;
}
return 0;
}
// CANFD 接口已迁移至 transport/canfd_bridge.cpp(M4 解耦)
// ============================================================
// 无锁内部版本实现
+2 -37
View File
@@ -30,30 +30,7 @@
#define SPI_OTA_RSP_WAIT (0x81)
#define SPI_OTA_RSP_ERROR (0x82)
// ============================================================
// CANFD 帧格式(3通道)
// [通道编号: 1B] [扩展帧ID: 4B BE] [数据: 1~64B]
// 长度自动识别,无需发送 DLC
// ============================================================
#define CAN_EXTENDED_ID_BITS (29) // CAN 扩展帧 ID 位数
#define CAN_MAX_DLC (64) // 最大数据长度
#define CAN_CHANNEL_MASK (0x03) // 通道号掩码 (0~2)
// 通道定义
typedef enum {
CAN_CHANNEL_0 = 0,
CAN_CHANNEL_1 = 1,
CAN_CHANNEL_2 = 2,
CAN_CHANNEL_MAX = 3,
} can_channel_t;
// CANFD 帧结构
typedef struct {
can_channel_t channel; // CAN 通道 (0~2)
uint32_t ext_id; // 扩展帧 ID (29bit, Big-Endian 存储)
uint8_t data[CAN_MAX_DLC]; // CAN 数据负载 (最大 64 字节)
uint8_t dlc; // 数据长度 (1~64)
} can_frame_t;
// CANFD 类型已迁移至 transport/canfd_bridge.h(M4 解耦)
// GD32 版本信息(从 APP PING 响应解析)
typedef struct {
@@ -122,19 +99,7 @@ public:
size_t payload_len, uint8_t *recv_buf,
size_t recv_len, int max_retries);
// ============================================================
// CANFD 接口
// ============================================================
/** @brief 发送一帧 CANFD 数据,可选等待并读取响应内容。 */
int canSend(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc,
uint8_t *recv_buf, uint8_t recv_max,
uint32_t timeout_ms);
/** @brief 发送一帧无需响应的 CANFD 数据。 */
int canSendNoReply(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc);
// ── CANFD 接口已迁移至 transport/canfd_bridge.h(M4 解耦)──
/** @brief 获取 SPI 设备句柄(供外部状态查询)。 */
spi_device_handle_t deviceHandle() const { return m_spiDevice; }