feat(ota): 实现 GD32 Flash 存储抽象层和 OTA 协调器接口解耦

- 新增 storage/gd32_flash_store.cpp 组件,提供 GD32 Flash 存储抽象层
- 修改 ota_coordinator.cpp/h 使其继承 IRelayListener 接口,实现与
  OtaRelay 的解耦设计
- 将 GD32 相关存储操作统一通过协调器访问,解决 H3 绕过协调器的问题
- 添加完整的 Doxygen 注释到所有公共接口和关键函数
- 更新 CMakeLists.txt 构建配置,添加 storage 组件依赖
- 重构元数据加载/清除操作使用 Gd32FlashStore 实例而非 OtaRelay 直接访问
- 添加中继事件回调接口实现,使 OtaRelay 可通知协调器状态变化
This commit is contained in:
小龙虾
2026-06-21 23:48:31 +08:00
parent 3fff959a2b
commit 00ce8dbbb5
30 changed files with 2087 additions and 1541 deletions
+83 -4
View File
@@ -7,6 +7,7 @@
#include "ota_coordinator.h"
#include "esp32_ota_core.h"
#include "relay/ota_relay.h"
#include "storage/gd32_flash_store.h"
#include "monitor/debug_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
@@ -102,10 +103,13 @@ void OtaCoordinator::init()
m_gd32StorageDone = false;
m_busySinceTick = 0;
// ★ H4 解耦:注册协调器为 OtaRelay 的中继事件监听器
OtaRelay::setRelayListener(this);
// ★ 重启恢复:如果 NVS 里有完整固件元数据,开机时立即进入 STORED 状态,
// 否则用户重启后看到的状态是 IDLE 但 NVS 里其实有未中继的固件,前后不一致。
ota_meta_t boot_meta;
if (OtaRelay::metaLoad(&boot_meta) == ESP_OK &&
if (Gd32FlashStore::instance().metaLoad(&boot_meta) == ESP_OK &&
boot_meta.total_size > 0 &&
boot_meta.stored_size >= boot_meta.total_size) {
m_sysState = OTA_SYS_GD32_STORED;
@@ -140,7 +144,7 @@ esp_err_t OtaCoordinator::beginGd32Storage(uint32_t total_size)
// 如果之前已有存储但未中继(STORED)或刚中继完(COMPLETE),先清理再开始新存储
if (h && (m_sysState == OTA_SYS_GD32_STORED || m_sysState == OTA_SYS_GD32_COMPLETE)) {
DEBUG_INFO(TAG, "清理旧 GD32 存储状态,开始新存储 (size=%" PRIu32 ")", total_size);
OtaRelay::metaClear();
Gd32FlashStore::instance().metaClear();
}
if (h) {
@@ -167,6 +171,50 @@ void OtaCoordinator::completeGd32Storage()
unlock(h);
}
// ============================================================
// GD32 存储操作(传输层统一入口,解决 H3 绕过协调器问题)
// ============================================================
esp_err_t OtaCoordinator::writeGd32Chunk(uint32_t offset, const uint8_t *data, size_t len)
{
return Gd32FlashStore::instance().flashWriteChunk(offset, data, len);
}
esp_err_t OtaCoordinator::saveGd32Meta(const ota_meta_t *meta)
{
return Gd32FlashStore::instance().metaSave(meta);
}
esp_err_t OtaCoordinator::loadGd32Meta(ota_meta_t *meta)
{
return Gd32FlashStore::instance().metaLoad(meta);
}
esp_err_t OtaCoordinator::updateGd32Meta(uint32_t stored_size, uint32_t last_seq)
{
return Gd32FlashStore::instance().metaUpdate(stored_size, last_seq);
}
esp_err_t OtaCoordinator::clearGd32Meta()
{
return Gd32FlashStore::instance().metaClear();
}
esp_err_t OtaCoordinator::verifyGd32Firmware(const uint8_t frontend_sha32[32],
uint8_t out_device_sha[32])
{
return Gd32FlashStore::instance().verifyFrontendSha(frontend_sha32, out_device_sha);
}
esp_err_t OtaCoordinator::finalizeGd32Checksum()
{
return Gd32FlashStore::instance().metaFinalizeChecksum();
}
esp_err_t OtaCoordinator::eraseGd32Partition(uint32_t size)
{
return Gd32FlashStore::instance().erasePartition(size);
}
// ============================================================
// GD32 中继阶段
// ============================================================
@@ -200,7 +248,7 @@ esp_err_t OtaCoordinator::triggerGd32Relay()
// 验证固件是否确实已存储(通过 NVS 元数据)
ota_meta_t meta;
if (h && (OtaRelay::metaLoad(&meta) != ESP_OK ||
if (h && (Gd32FlashStore::instance().metaLoad(&meta) != ESP_OK ||
meta.total_size == 0 ||
meta.stored_size < meta.total_size)) {
DEBUG_WARN(TAG, "GD32 固件未完整存储,拒绝触发中继");
@@ -247,6 +295,7 @@ esp_err_t OtaCoordinator::triggerGd32Relay()
return ESP_OK;
}
/** @brief 通知协调器:GD32 中继已开始,更新系统状态为 GD32_RELAYING。 */
void OtaCoordinator::notifyRelayStarted()
{
bool h = lock();
@@ -258,6 +307,7 @@ void OtaCoordinator::notifyRelayStarted()
unlock(h);
}
/** @brief 通知协调器:GD32 中继已完成,根据结果转为 COMPLETE 或 ERROR。 */
void OtaCoordinator::notifyRelayComplete(bool success)
{
bool h = lock();
@@ -276,6 +326,21 @@ void OtaCoordinator::notifyRelayComplete(bool success)
unlock(h);
}
// ============================================================
// IRelayListener 接口实现(H4: OtaRelay 通过接口回调通知协调器)
// ============================================================
/** @brief IRelayListener 回调:中继已开始,委托给 notifyRelayStarted()。 */
void OtaCoordinator::onRelayStarted()
{
notifyRelayStarted();
}
/** @brief IRelayListener 回调:中继已完成,委托给 notifyRelayComplete()。 */
void OtaCoordinator::onRelayComplete(bool success)
{
notifyRelayComplete(success);
}
// ============================================================
// ESP32 OTA 阶段
// ============================================================
@@ -342,6 +407,7 @@ esp_err_t OtaCoordinator::beginEsp32OtaWithRetry(uint32_t total_size)
return err;
}
/** @brief 通知协调器:ESP32 固件接收完毕,状态转为 FINALIZING。 */
void OtaCoordinator::notifyEsp32ReceiveComplete()
{
bool h = lock();
@@ -352,6 +418,7 @@ void OtaCoordinator::notifyEsp32ReceiveComplete()
unlock(h);
}
/** @brief 通知协调器:ESP32 OTA 收尾完成(SHA验证+标记启动),状态转为 REBOOTING。 */
void OtaCoordinator::notifyEsp32FinalizeComplete()
{
bool h = lock();
@@ -362,6 +429,7 @@ void OtaCoordinator::notifyEsp32FinalizeComplete()
unlock(h);
}
/** @brief 通知协调器:ESP32 即将重启。 */
void OtaCoordinator::notifyEsp32Rebooting()
{
bool h = lock();
@@ -399,6 +467,11 @@ void OtaCoordinator::restartEsp32()
// 不会返回
}
/**
* @brief 将 ESP32 OTA finalize 错误码分类。
* @param err finalize 返回的 esp_err_t。
* @return 0=成功,1=SHA不匹配,2=其他失败。
*/
int OtaCoordinator::esp32FinalizeErrCategory(esp_err_t err)
{
if (err == ESP_OK) return 0; // 成功
@@ -416,7 +489,7 @@ void OtaCoordinator::abortCurrent()
DEBUG_INFO(TAG, "中止当前 OTA 操作 (state=%d)", (int)m_sysState);
if (m_sysState == OTA_SYS_GD32_STORING) {
OtaRelay::metaClear();
Gd32FlashStore::instance().metaClear();
} else if (m_sysState == OTA_SYS_GD32_RELAYING) {
OtaRelay::instance().reset();
} else if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
@@ -445,6 +518,7 @@ ota_system_state_t OtaCoordinator::getSystemState() const
return s;
}
/** @brief 判断系统是否处于空闲状态(IDLE/STORED/COMPLETE)。 */
bool OtaCoordinator::isIdle() const
{
return (getSystemState() == OTA_SYS_IDLE ||
@@ -452,6 +526,7 @@ bool OtaCoordinator::isIdle() const
getSystemState() == OTA_SYS_GD32_COMPLETE);
}
/** @brief 判断 GD32 通道是否正忙(STORING 或 RELAYING)。 */
bool OtaCoordinator::isGd32Busy() const
{
ota_system_state_t s = getSystemState();
@@ -459,6 +534,7 @@ bool OtaCoordinator::isGd32Busy() const
s == OTA_SYS_GD32_RELAYING);
}
/** @brief 判断 ESP32 通道是否正忙(RECEIVING/FINALIZING/REBOOTING)。 */
bool OtaCoordinator::isEsp32Busy() const
{
ota_system_state_t s = getSystemState();
@@ -467,6 +543,7 @@ bool OtaCoordinator::isEsp32Busy() const
s == OTA_SYS_ESP32_REBOOTING);
}
/** @brief 获取系统状态的字符串名称(用于日志和前端显示)。 */
const char* OtaCoordinator::getSystemStateString() const
{
switch (getSystemState()) {
@@ -483,11 +560,13 @@ const char* OtaCoordinator::getSystemStateString() const
}
}
/** @brief 获取 GD32 APP 存活状态(委托 OtaRelay)。 */
bool OtaCoordinator::getGd32AppAlive() const
{
return OtaRelay::getGd32AppAlive();
}
/** @brief 获取 GD32 固件是否已存储完成(线程安全)。 */
bool OtaCoordinator::isGd32StorageDone() const
{
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
+87 -1
View File
@@ -19,6 +19,8 @@
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "storage/gd32_flash_store.h"
#include "relay/ota_relay.h" // IRelayListener 接口定义(H4 解耦)
// ============================================================
// 系统级 OTA 状态枚举
@@ -51,10 +53,11 @@ typedef struct {
* 传输层(WiFi/BLE)通过本协调器发起所有 OTA 操作,
* 不再直接调用 OtaRelay / Esp32OtaCore 的底层接口。
*/
class OtaCoordinator {
class OtaCoordinator : public IRelayListener {
public:
/**
* @brief 获取 OtaCoordinator 单例实例。
* @return OtaCoordinator 单例引用。
*/
static OtaCoordinator& instance();
@@ -80,6 +83,67 @@ public:
*/
void completeGd32Storage();
// ── GD32 存储操作(传输层通过协调器操作 Flash,解决 H3 绕过问题)──
/**
* @brief 向 GD32 Flash 分区写入固件数据块。
* 传输层(WiFi/BLE)通过此接口写入,不再直接调用 Gd32FlashStore。
* @param offset 写入偏移。
* @param data 数据缓冲区。
* @param len 数据长度。
* @return ESP_OK 成功。
*/
esp_err_t writeGd32Chunk(uint32_t offset, const uint8_t *data, size_t len);
/**
* @brief 保存 GD32 OTA 元数据。
* @param meta 元数据结构体。
* @return ESP_OK 成功。
*/
esp_err_t saveGd32Meta(const ota_meta_t *meta);
/**
* @brief 加载 GD32 OTA 元数据。
* @param meta 输出元数据结构体。
* @return ESP_OK 成功。
*/
esp_err_t loadGd32Meta(ota_meta_t *meta);
/**
* @brief 更新 GD32 OTA 元数据的进度。
* @param stored_size 已存储大小。
* @param last_seq 最后包序号。
* @return ESP_OK 成功。
*/
esp_err_t updateGd32Meta(uint32_t stored_size, uint32_t last_seq);
/**
* @brief 清除 GD32 OTA 元数据。
* @return ESP_OK 成功。
*/
esp_err_t clearGd32Meta();
/**
* @brief 校验前端 SHA-256 与设备端固件是否一致。
* @param frontend_sha32 前端 32 字节 SHA。
* @param out_device_sha 输出设备端 32 字节 SHA(可为 nullptr)。
* @return ESP_OK 匹配,ESP_ERR_INVALID_CRC 不匹配。
*/
esp_err_t verifyGd32Firmware(const uint8_t frontend_sha32[32],
uint8_t out_device_sha[32]);
/**
* @brief 计算 GD32 固件 SHA-256 并写入元数据。
* @return ESP_OK 成功。
*/
esp_err_t finalizeGd32Checksum();
/**
* @brief 擦除 GD32 OTA 分区。
* @param size 擦除大小。
* @return ESP_OK 成功。
*/
esp_err_t eraseGd32Partition(uint32_t size);
// ── GD32 中继阶段 ──
/**
* @brief 触发 GD32 中继转发。
@@ -161,37 +225,44 @@ public:
// ── 状态查询 ──
/**
* @brief 获取当前系统级 OTA 状态。
* @return 当前 ota_system_state_t 状态值。
*/
ota_system_state_t getSystemState() const;
/**
* @brief 检查系统是否空闲(可接受新的 OTA 请求)。
* @return true 系统空闲(IDLE/STORED/COMPLETE)。
*/
bool isIdle() const;
/**
* @brief 检查是否有 GD32 相关操作正在进行。
* @return true 表示 STORING 或 RELAYING。
*/
bool isGd32Busy() const;
/**
* @brief 检查是否有 ESP32 OTA 正在进行。
* @return true 表示 RECEIVING/FINALIZING/REBOOTING。
*/
bool isEsp32Busy() const;
/**
* @brief 获取系统状态的可读字符串描述。
* @return 状态名称字符串(如 "IDLE"、"GD32_STORING" 等)。
*/
const char* getSystemStateString() const;
// ── GD32 状态(供 status API 使用,委托给 OtaRelay 查询)──
/**
* @brief 获取 GD32 APP 存活状态。
* @return true 表示 GD32 APP 存活。
*/
bool getGd32AppAlive() const;
/**
* @brief GD32 固件存储完成标志(供状态查询)。
* @return true 表示固件已完整存储。
*/
bool isGd32StorageDone() const;
@@ -205,10 +276,25 @@ public:
/**
* @brief 把 finalize 返回的 esp_err_t 映射为前端可读的事件码。
* 统一 BLE/WiFi 两端语义,便于日志/状态一致。
* @param err finalize 返回的 esp_err_t。
* @return 0=成功;1=SHA 不匹配;2=其他失败。
*/
static int esp32FinalizeErrCategory(esp_err_t err);
// ── IRelayListener 接口实现(H4: OtaRelay 通过接口回调通知协调器)──
/**
* @brief IRelayListener 回调:中继已开始。
* 由 OtaRelay task 在 forwardFirmware() 前通过 s_relayListener 调用。
*/
void onRelayStarted() override;
/**
* @brief IRelayListener 回调:中继已完成。
* 由 OtaRelay task 在 forwardFirmware() 返回后通过 s_relayListener 调用。
* @param success true 成功,false 失败。
*/
void onRelayComplete(bool success) override;
private:
OtaCoordinator() = default;
~OtaCoordinator() = default;