Files
ESP32_COMM/main/relay/ota_relay.h
T
小龙虾 7feef70750 feat(ota): 统一BLE/WiFi OTA协调器接口并优化并发控制
- 添加 beginEsp32OtaWithRetry 统一重试逻辑,防止锁残留导致的并发问题
- 实现 finalizeForReboot 和 restartEsp32 统一封装收尾重启流程
- 修复BLE/WiFi双通道并发OTA的安全隐患,增强系统状态检查

refactor(ota): 优化SHA计算内存管理

- 使用静态缓冲区替代动态分配,减少内存碎片
- 重构OTA协调器状态机,增强错误处理机制

perf(monitor): 增强堆内存监控功能

- 添加最大空闲块大小监控,提供更详细的内存状态信息
- 更新健康监测算法,提高内存异常检测准确性
2026-06-19 03:02:33 +08:00

325 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* ota_relay.h - OTA 中继事件驱动状态机
*
* OtaRelay 将 GD32 SPI OTA 协议封装为事件驱动的状态机:
* 1. 每个协议阶段对应一个状态(共 6 个阶段状态 + ERROR)
* 2. 状态通过 processEvent() 驱动转换,而非顺序赋值
* 3. 外部(OtaCoordinator / transport layer)通过 dispatchQueue 触发中继
* 4. forwardFirmware() 保留为便捷入口,内部驱动状态机循环
*
* 状态转换:
* IDLE → HANDSHAKING → SIZE_NEG → TRANSFERRING → FINISHING → DONE
* 任意状态 ──(error)──→ ERROR
* ERROR/DONE ──(reset)──→ IDLE
*/
#ifndef OTA_RELAY_H
#define OTA_RELAY_H
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "transport/protocol.h"
#include "transport/spi_master.h"
#include "esp_partition.h"
#define OTA_META_NVS_NAMESPACE "ota_meta"
typedef struct {
uint32_t total_size;
uint32_t stored_size;
uint32_t last_seq;
uint8_t checksum[32];
} ota_meta_t;
// ============================================================
// 阶段状态枚举(精简为 7 个状态,原 17 个"微状态"合并为阶段)
// ============================================================
typedef enum {
RELAY_IDLE = 0, // 空闲
RELAY_HANDSHAKING, // 阶段1: 握手 + 挑战认证
RELAY_SIZE_NEG, // 阶段2: 固件大小协商
RELAY_TRANSFERRING, // 阶段3: 数据包传输
RELAY_FINISHING, // 阶段4-5: 结束OTA + APP存活验证
RELAY_DONE, // 完成
RELAY_ERROR = 0xFF // 错误
} relay_state_t;
// ============================================================
// 事件类型(驱动状态转换)
// ============================================================
typedef enum {
RELAY_EVT_START = 0, // 启动中继流程
RELAY_EVT_RESP_OK, // 收到 GD32 有效响应
RELAY_EVT_RESP_ERROR, // 收到 GD32 错误响应
RELAY_EVT_TIMEOUT, // 超时
RELAY_EVT_HEALTH_CRIT, // 健康检查危险
RELAY_EVT_ABORT // 外部中止
} relay_event_t;
typedef enum {
OTA_RESULT_OK = 0,
OTA_RESULT_ERR_TIMEOUT = -1,
OTA_RESULT_ERR_CRC = -2,
OTA_RESULT_ERR_AUTH = -3,
OTA_RESULT_ERR_STORAGE = -4,
OTA_RESULT_ERR_GD32 = -5,
OTA_RESULT_ERR_HEALTH = -6,
OTA_RESULT_ERR_USER_ABORT = -7
} ota_result_t;
class OtaRelay {
public:
/**
* @brief 获取OtaRelay单例引用.
*/
static OtaRelay& instance();
/**
* @brief OTA中继FreeRTOS任务入口.
*/
static void taskEntry(void *pvParameters);
/**
* @brief 从Flash向GD32转发固件(便捷入口,内部驱动状态机循环).
* @return 操作结果码.
*/
ota_result_t forwardFirmware();
// ── 事件驱动接口 ──
/**
* @brief 向状态机投递事件,驱动状态转换.
* @param event 事件类型.
* @param payload 关联数据(响应载荷等),可为nullptr.
* @param len 数据长度.
* @return 当前操作结果码(OTA_RESULT_OK 表示事件已处理且可继续).
*/
ota_result_t processEvent(relay_event_t event, const uint8_t *payload, size_t len);
// ── 状态查询(线程安全)──
/**
* @brief 获取当前中继状态.
*/
static relay_state_t getState();
/**
* @brief 获取中继当前序号.
*/
static uint32_t getRelaySeq();
/**
* @brief 获取中继总包数.
*/
static uint32_t getRelayTotalPackets();
/**
* @brief 获取GD32 APP存活标志位.
*/
static bool getGd32AppAlive();
/**
* @brief 获取GD32固件版本.
*/
static void getGd32Version(gd32_version_t *out);
/**
* @brief 设置中继状态.
*/
static void setState(relay_state_t s);
/**
* @brief 设置中继当前序号.
*/
static void setRelaySeq(uint32_t seq);
/**
* @brief 设置中继总包数.
*/
static void setRelayTotalPackets(uint32_t total);
/**
* @brief 设置GD32 APP存活标志位.
*/
static void setGd32AppAlive(bool alive);
/**
* @brief 设置GD32固件版本.
*/
static void setGd32Version(const gd32_version_t &ver);
/**
* @brief 重置状态机到 IDLE.
*/
void reset();
// ── Flash 操作 ──
/**
* @brief Flash写入接口(支持非4字节对齐写入).
*/
static esp_err_t flashWriteChunk(uint32_t offset, const uint8_t *data, size_t len);
/**
* @brief 从OTA缓存分区读取数据块.
*/
static esp_err_t flashReadChunk(uint32_t offset, uint8_t *data, size_t len);
/**
* @brief 从Flash加载元数据.
*/
static esp_err_t metaLoad(ota_meta_t *meta);
/**
* @brief 保存元数据到Flash.
*/
static esp_err_t metaSave(const ota_meta_t *meta);
/**
* @brief 传输过程中更新元数据.
*/
static esp_err_t metaUpdate(uint32_t stored_size, uint32_t last_seq);
/**
* @brief OTA完成或中止时清除元数据.
*/
static esp_err_t metaClear();
/**
* @brief 计算gd32_ota分区中已存储固件的SHA-256.
*/
static esp_err_t computeFlashSha256(uint8_t out_sha256[32]);
/**
* @brief 固件接收完毕后计算SHA-256并写入元数据.
*/
static esp_err_t metaFinalizeChecksum();
/**
* @brief 校验固件完整性(SHA-256).
*/
static esp_err_t metaVerifyChecksum();
/**
* @brief 校验前端提供的 SHA-256 是否与设备端计算的一致。
* BLE/WiFi 传输层共用:内部完成 metaFinalizeChecksum → metaLoad → memcmp。
* 无论是否匹配,都会通过 out_device_sha 回传设备端 SHA(便于前端核对)。
* @param frontend_sha32 前端 32 字节原始 SHA(非 hex)。
* @param out_device_sha 可为 nullptr;非空时输出设备端 32 字节 SHA。
* @return ESP_OK 匹配;ESP_ERR_INVALID_CRC 不匹配;其他 = 元数据/计算失败。
*/
static esp_err_t verifyFrontendSha(const uint8_t frontend_sha32[32],
uint8_t out_device_sha[32]);
// ── 公开状态成员 ──
static relay_state_t relayState;
static uint32_t relaySeq;
static uint32_t relayTotalPackets;
static volatile bool gd32AppAlive;
static gd32_version_t gd32Version;
static QueueHandle_t dispatchQueue;
static QueueHandle_t resultQueue;
// ── 线程安全锁 ──
static SemaphoreHandle_t s_stateMutex;
static void initMutex();
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(50);
class StateLockGuard {
public:
explicit StateLockGuard(TickType_t timeout = kLockTimeout) {
m_locked = (s_stateMutex && xSemaphoreTake(s_stateMutex, timeout) == pdTRUE);
}
~StateLockGuard() {
if (m_locked && s_stateMutex) { xSemaphoreGive(s_stateMutex); }
}
bool isLocked() const { return m_locked; }
StateLockGuard(const StateLockGuard&) = delete;
StateLockGuard& operator=(const StateLockGuard&) = delete;
private:
bool m_locked = false;
};
// ── 实例状态 ──
const esp_partition_t *m_gd32Partition = NULL;
session_ctx_t m_sessionCtx;
uint8_t m_nonceServer[16] = {0};
uint8_t m_kSession[16] = {0};
uint8_t m_gd32UidRuntime[12] = {0};
static constexpr size_t GD32_UID_LEN = 12;
static constexpr size_t GD32_CHALLENGE_RESPONSE_LEN = 36;
private:
OtaRelay() = default;
~OtaRelay() = default;
OtaRelay(const OtaRelay&) = delete;
OtaRelay& operator=(const OtaRelay&) = delete;
// ── 状态机内部类型 ──
/**
* @brief 状态处理函数类型:接收事件 + 载荷,返回结果码.
* 返回 OTA_RESULT_OK 表示事件已成功处理且状态机可继续;
* 返回负值表示错误,状态机转入 RELAY_ERROR.
*/
typedef ota_result_t (OtaRelay::*StateHandler)(relay_event_t event,
const uint8_t *payload, size_t len);
struct RelayStateDef {
const char *name; // 状态名称(日志用)
StateHandler handler; // 事件处理器
};
// ── 状态表(const,编译期确定)──
static const RelayStateDef s_stateTable[];
// ── 各状态的事件处理器 ──
ota_result_t handleIdle(relay_event_t event, const uint8_t *payload, size_t len);
ota_result_t handleHandshaking(relay_event_t event, const uint8_t *payload, size_t len);
ota_result_t handleSizeNeg(relay_event_t event, const uint8_t *payload, size_t len);
ota_result_t handleTransferring(relay_event_t event, const uint8_t *payload, size_t len);
ota_result_t handleFinishing(relay_event_t event, const uint8_t *payload, size_t len);
ota_result_t handleDone(relay_event_t event, const uint8_t *payload, size_t len);
ota_result_t handleError(relay_event_t event, const uint8_t *payload, size_t len);
// ── 协议子步骤(由状态 handler 内部调用)──
/**
* @brief 请求GD32 APP进入Bootloader.
*/
esp_err_t requestGd32AppEnterBootloader();
/**
* @brief 执行握手请求(发送 HANDSHAKE_REQ).
*/
ota_result_t doHandshakeReq();
/**
* @brief 执行挑战认证.
*/
ota_result_t doChallengeAndAuth();
/**
* @brief 发送固件总大小.
*/
ota_result_t doSendFirmwareSize(uint32_t size);
/**
* @brief 转发下一批数据包(由 TRANSFERRING 状态循环调用).
*/
ota_result_t doRelayPackets(TickType_t deadline_tick);
/**
* @brief 发送结束指令并验证 APP 存活.
*/
ota_result_t doFinishAndVerify();
// ── 传输进度跟踪(状态机内部)──
uint32_t m_transferTotalPackets = 0;
uint32_t m_transferCurrentSeq = 0;
uint32_t m_transferFlashOffset = 0;
uint32_t m_transferRetryCount = 0;
TickType_t m_transferDeadline = 0;
// ── 加密/工具方法(不变)──
static int aes128EcbEncryptBlockLocal(const uint8_t *key, const uint8_t *in, uint8_t *out);
static void logRespPayload(const char *stage, uint8_t resp_type, const uint8_t *payload, size_t len);
esp_err_t waitUntilNotWait(const char *stage, uint8_t *resp_type,
uint8_t *resp_payload, size_t resp_max,
size_t *resp_len, uint32_t timeout_ms,
uint32_t max_wait_rounds);
static bool isStructuredAckWithSeq(const uint8_t *payload, size_t len, uint32_t expected_seq);
static bool parseStructuredError(const uint8_t *payload, size_t len, uint32_t *seq_out, uint8_t *err_out);
static bool isHandshakeEchoAck(const uint8_t *payload, size_t len);
static bool isChallengePayloadAck(const uint8_t *payload, size_t len);
static bool isConfirmPlainAck(const uint8_t *payload, size_t len);
static bool isSizeSeq0Ack(const uint8_t *payload, size_t len);
static bool isFinishPlainAck(const uint8_t *payload, size_t len);
static esp_err_t pollUntilExpectedAckShape(const char *stage,
uint8_t *resp_type, uint8_t *resp_payload,
size_t resp_max, size_t *resp_len,
uint32_t poll_timeout_ms, uint32_t wait_rounds,
uint32_t maxExtraPolls,
bool (*matcher)(const uint8_t *, size_t));
static bool parseAndVerifyChallengePayload(const uint8_t *resp_payload, size_t resp_len,
uint8_t *out_k_session, uint8_t *out_uid);
static bool aes128EcbEncrypt48Bytes(const uint8_t *key, const uint8_t *in, uint8_t *out);
static void calculateCrc32WithKey(const uint8_t *key, const uint8_t *data48, uint8_t *output);
};
#endif // OTA_RELAY_H