docs(ota_mutex): 添加函数注释说明
This commit is contained in:
@@ -14,6 +14,7 @@ extern volatile bool g_esp32OtaOccupied;
|
||||
|
||||
// 获取全局 OTA 占用标志(带超时),成功返回 true
|
||||
// 内部: Take Mutex → 检查标志 → 若空闲则置 true → Give Mutex
|
||||
/** @brief 获取全局 OTA 占用标志(带超时),成功返回 true。 */
|
||||
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;
|
||||
@@ -28,6 +29,7 @@ static inline bool esp32_ota_mutex_acquire(uint32_t timeout_ms) {
|
||||
|
||||
// 释放全局 OTA 占用标志
|
||||
// 内部: Take Mutex → 清除标志 → Give Mutex
|
||||
/** @brief 释放全局 OTA 占用标志。 */
|
||||
static inline void esp32_ota_mutex_release(void) {
|
||||
if (!g_esp32OtaMutex) return;
|
||||
xSemaphoreTake(g_esp32OtaMutex, portMAX_DELAY);
|
||||
|
||||
@@ -33,6 +33,10 @@ static const char *TAG = "DEBUG_LOG";
|
||||
// ============================================================
|
||||
// 单例
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 获取 DebugLog 单例实例。
|
||||
* @return DebugLog 引用。
|
||||
*/
|
||||
DebugLog& DebugLog::instance()
|
||||
{
|
||||
static DebugLog inst;
|
||||
@@ -42,6 +46,10 @@ DebugLog& DebugLog::instance()
|
||||
// ============================================================
|
||||
// 日志环形缓冲区(供 HTTP /logs 拉取)
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 获取当前环形日志缓冲区中的日志条数。
|
||||
* @return 日志条数。
|
||||
*/
|
||||
int DebugLog::ringCount()
|
||||
{
|
||||
if (!m_logRingMutex) return 0;
|
||||
@@ -51,6 +59,11 @@ int DebugLog::ringCount()
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取并清空环形日志缓冲区,返回 JSON 数组字符串。
|
||||
* @param out_len 输出参数,返回 JSON 字符串长度。
|
||||
* @return 指向 JSON 字符串的指针,需调用者 free。
|
||||
*/
|
||||
char *DebugLog::ringDrainJson(int *out_len)
|
||||
{
|
||||
if (!m_logRingMutex) {
|
||||
@@ -117,6 +130,11 @@ char *DebugLog::ringDrainJson(int *out_len)
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向环形日志缓冲区中推送一条日志数据。
|
||||
* @param data 日志数据指针。
|
||||
* @param len 数据长度。
|
||||
*/
|
||||
void DebugLog::ringPush(const char *data, int len)
|
||||
{
|
||||
if (!m_logRingMutex || len <= 0) return;
|
||||
@@ -139,6 +157,10 @@ void DebugLog::ringPush(const char *data, int len)
|
||||
// ============================================================
|
||||
// UART 调试日志
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 初始化 UART0 调试串口。
|
||||
* @return esp_err_t 错误码。
|
||||
*/
|
||||
esp_err_t DebugLog::uartInit()
|
||||
{
|
||||
if (m_uartInitialized) {
|
||||
@@ -180,6 +202,12 @@ esp_err_t DebugLog::uartInit()
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 通过 UART0 直接发送原始字节数据。
|
||||
* @param data 待发送数据指针。
|
||||
* @param len 数据长度。
|
||||
* @return esp_err_t 错误码。
|
||||
*/
|
||||
esp_err_t DebugLog::uartSend(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!m_uartInitialized) {
|
||||
@@ -197,6 +225,10 @@ esp_err_t DebugLog::uartSend(const uint8_t *data, size_t len)
|
||||
// ============================================================
|
||||
// 初始化(UART 立即可用,UDP socket 延迟到首次发送时创建)
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 初始化日志系统,包括 UART 输出、UDP 目标和内部缓冲区。
|
||||
* @return esp_err_t 错误码。
|
||||
*/
|
||||
esp_err_t DebugLog::init()
|
||||
{
|
||||
if (m_initialized) {
|
||||
@@ -219,6 +251,10 @@ esp_err_t DebugLog::init()
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 确保 UDP socket 已创建(延迟创建)。
|
||||
* @return esp_err_t 错误码。
|
||||
*/
|
||||
esp_err_t DebugLog::ensureUdpSocket()
|
||||
{
|
||||
if (m_udpSock >= 0) {
|
||||
@@ -235,6 +271,12 @@ esp_err_t DebugLog::ensureUdpSocket()
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送原始日志数据到底层输出通道。
|
||||
* @param data 待发送数据指针。
|
||||
* @param len 数据长度。
|
||||
* @return esp_err_t 错误码。
|
||||
*/
|
||||
esp_err_t DebugLog::send(const uint8_t *data, size_t len)
|
||||
{
|
||||
esp_err_t udp_ret = ESP_FAIL;
|
||||
@@ -270,6 +312,13 @@ esp_err_t DebugLog::send(const uint8_t *data, size_t len)
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按日志级别与标签格式化输出一条日志。
|
||||
* @param level 日志级别。
|
||||
* @param tag 日志标签。
|
||||
* @param fmt 格式化字符串。
|
||||
* @param ... 可变参数。
|
||||
*/
|
||||
void DebugLog::log(debug_log_level_t level, const char *tag, const char *fmt, ...)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
|
||||
@@ -48,6 +48,10 @@ typedef enum {
|
||||
*/
|
||||
class DebugLog {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取 DebugLog 单例实例。
|
||||
* @return DebugLog 引用。
|
||||
*/
|
||||
static DebugLog& instance();
|
||||
|
||||
/** @brief 初始化日志系统,包括 UART 输出、UDP 目标和内部缓冲区。 */
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
// ============================================================
|
||||
// 单例
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 获取 HealthMonitor 单例实例。
|
||||
* @return HealthMonitor 引用。
|
||||
*/
|
||||
HealthMonitor& HealthMonitor::instance()
|
||||
{
|
||||
static HealthMonitor inst;
|
||||
@@ -32,6 +36,9 @@ HealthMonitor& HealthMonitor::instance()
|
||||
// ============================================================
|
||||
// 初始化
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 初始化健康监控模块,创建内部互斥锁与基础状态。
|
||||
*/
|
||||
void HealthMonitor::init()
|
||||
{
|
||||
m_mutex = xSemaphoreCreateMutex();
|
||||
@@ -43,6 +50,10 @@ void HealthMonitor::init()
|
||||
// ============================================================
|
||||
// 线程安全的健康状态读写
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 加锁读取当前健康状态。
|
||||
* @return 当前健康状态。
|
||||
*/
|
||||
health_state_t HealthMonitor::getState()
|
||||
{
|
||||
if (m_mutex) xSemaphoreTake(m_mutex, portMAX_DELAY);
|
||||
@@ -51,6 +62,10 @@ health_state_t HealthMonitor::getState()
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加锁设置健康状态。
|
||||
* @param newState 新的健康状态。
|
||||
*/
|
||||
void HealthMonitor::setState(health_state_t newState)
|
||||
{
|
||||
if (m_mutex) xSemaphoreTake(m_mutex, portMAX_DELAY);
|
||||
@@ -61,11 +76,19 @@ void HealthMonitor::setState(health_state_t newState)
|
||||
// ============================================================
|
||||
// 获取当前健康状态(public 接口)
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 获取当前系统健康状态(线程安全)。
|
||||
* @return 当前健康状态。
|
||||
*/
|
||||
health_state_t HealthMonitor::getCurrent()
|
||||
{
|
||||
return getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最近一次芯片温度。
|
||||
* @return 温度值(摄氏度)。
|
||||
*/
|
||||
float HealthMonitor::getLastTemperature()
|
||||
{
|
||||
if (m_mutex) xSemaphoreTake(m_mutex, portMAX_DELAY);
|
||||
@@ -74,6 +97,12 @@ float HealthMonitor::getLastTemperature()
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最近一次 WiFi RSSI 及其年龄。
|
||||
* @param out_rssi 输出参数,返回 RSSI 值。
|
||||
* @param out_age_ms 输出参数,返回样本年龄(毫秒)。
|
||||
* @return 是否有有效数据。
|
||||
*/
|
||||
bool HealthMonitor::getLastWifiRssi(int8_t *out_rssi, uint32_t *out_age_ms)
|
||||
{
|
||||
if (m_mutex) xSemaphoreTake(m_mutex, portMAX_DELAY);
|
||||
@@ -96,6 +125,10 @@ bool HealthMonitor::getLastWifiRssi(int8_t *out_rssi, uint32_t *out_age_ms)
|
||||
// ============================================================
|
||||
// 温度监控(定时器回调)
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 温度监控定时器回调函数。
|
||||
* @param arg 用户参数(未使用)。
|
||||
*/
|
||||
void HealthMonitor::tempMonitorCallback(void *arg)
|
||||
{
|
||||
auto& self = instance();
|
||||
@@ -127,6 +160,10 @@ void HealthMonitor::tempMonitorCallback(void *arg)
|
||||
// ============================================================
|
||||
// FreeRTOS 任务入口
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 健康监控 FreeRTOS 任务入口。
|
||||
* @param pvParameters 任务参数。
|
||||
*/
|
||||
void HealthMonitor::taskEntry(void *pvParameters)
|
||||
{
|
||||
auto& self = instance();
|
||||
@@ -173,6 +210,11 @@ void HealthMonitor::taskEntry(void *pvParameters)
|
||||
// ============================================================
|
||||
// 健康事件日志
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 记录一条健康事件日志。
|
||||
* @param event 事件名称。
|
||||
* @param value 事件值。
|
||||
*/
|
||||
void HealthMonitor::logEvent(const char *event, int value)
|
||||
{
|
||||
DEBUG_INFO("HEALTH", "[健康事件] %s: %d", event, value);
|
||||
@@ -181,6 +223,10 @@ void HealthMonitor::logEvent(const char *event, int value)
|
||||
// ============================================================
|
||||
// OTA 健康检查
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief OTA 传输前执行健康检查。
|
||||
* @return 健康检查状态。
|
||||
*/
|
||||
health_status_t HealthMonitor::checkBeforeOta()
|
||||
{
|
||||
health_status_t status = HEALTH_MON_OK;
|
||||
@@ -227,6 +273,10 @@ health_status_t HealthMonitor::checkBeforeOta()
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief OTA 中继转发阶段执行健康检查(跳过 WiFi 检查)。
|
||||
* @return 健康检查状态。
|
||||
*/
|
||||
health_status_t HealthMonitor::checkBeforeOtaRelay()
|
||||
{
|
||||
health_status_t status = HEALTH_MON_OK;
|
||||
@@ -261,6 +311,11 @@ health_status_t HealthMonitor::checkBeforeOtaRelay()
|
||||
// ============================================================
|
||||
// WiFi RSSI 更新
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 根据 WiFi RSSI 更新健康状态。
|
||||
* @param rssi WiFi RSSI 值。
|
||||
* @return 健康检查状态。
|
||||
*/
|
||||
health_status_t HealthMonitor::updateFromWifiRssi(int8_t rssi)
|
||||
{
|
||||
if (rssi < WIFI_RSSI_CRITICAL) {
|
||||
|
||||
@@ -37,6 +37,10 @@ typedef enum {
|
||||
*/
|
||||
class HealthMonitor {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取 HealthMonitor 单例实例。
|
||||
* @return HealthMonitor 引用。
|
||||
*/
|
||||
static HealthMonitor& instance();
|
||||
|
||||
/** @brief 初始化健康监控模块,创建内部互斥锁与基础状态。 */
|
||||
|
||||
@@ -23,12 +23,19 @@
|
||||
|
||||
#define TAG "OTA_CORE"
|
||||
|
||||
/**
|
||||
* @brief 获取 Esp32OtaCore 单例实例。
|
||||
* @return Esp32OtaCore 引用。
|
||||
*/
|
||||
Esp32OtaCore& Esp32OtaCore::instance()
|
||||
{
|
||||
static Esp32OtaCore inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 复位所有状态变量。
|
||||
*/
|
||||
void Esp32OtaCore::resetState()
|
||||
{
|
||||
m_active = false;
|
||||
@@ -40,6 +47,11 @@ void Esp32OtaCore::resetState()
|
||||
m_ended = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 开启 OTA 会话。
|
||||
* @param total_size 固件总大小(字节)。
|
||||
* @return ESP_OK 成功,ESP_ERR_INVALID_STATE 被占用,ESP_FAIL 其他错误。
|
||||
*/
|
||||
esp_err_t Esp32OtaCore::begin(size_t total_size)
|
||||
{
|
||||
// 若本核心已有未完成会话,先中止并释放全局互斥
|
||||
@@ -109,6 +121,12 @@ esp_err_t Esp32OtaCore::begin(size_t total_size)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写入固件数据块。
|
||||
* @param data 数据缓冲区。
|
||||
* @param len 数据长度。
|
||||
* @return ESP_OK 成功,否则错误码。
|
||||
*/
|
||||
esp_err_t Esp32OtaCore::write(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!m_active || m_partition == nullptr) {
|
||||
@@ -124,6 +142,10 @@ esp_err_t Esp32OtaCore::write(const uint8_t *data, size_t len)
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从 OTA 分区读回数据并计算 SHA-256。
|
||||
* @return ESP_OK 成功,否则错误码。
|
||||
*/
|
||||
esp_err_t Esp32OtaCore::computeShaFromPartition()
|
||||
{
|
||||
if (m_partition == NULL || m_received == 0) {
|
||||
@@ -172,6 +194,11 @@ esp_err_t Esp32OtaCore::computeShaFromPartition()
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算设备端 SHA-256 并输出 hex 字符串。
|
||||
* @param out_device_sha_hex 输出缓冲区,至少 65 字节。
|
||||
* @return ESP_OK 成功,否则错误码。
|
||||
*/
|
||||
esp_err_t Esp32OtaCore::computeDeviceSha(char *out_device_sha_hex)
|
||||
{
|
||||
if (!m_active || m_partition == NULL) {
|
||||
@@ -191,6 +218,13 @@ esp_err_t Esp32OtaCore::computeDeviceSha(char *out_device_sha_hex)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 收尾 OTA 会话:end、校验 SHA、set_boot、可选重启。
|
||||
* @param frontend_sha_hex 前端提供的 64 字符 hex SHA-256。
|
||||
* @param reboot 成功后是否重启。
|
||||
* @param out_device_sha_hex 输出设备端 SHA hex 缓冲区。
|
||||
* @return ESP_OK 成功,ESP_ERR_INVALID_VERSION SHA 不匹配,否则错误码。
|
||||
*/
|
||||
esp_err_t Esp32OtaCore::finalize(const char *frontend_sha_hex, bool reboot,
|
||||
char *out_device_sha_hex)
|
||||
{
|
||||
@@ -287,6 +321,9 @@ esp_err_t Esp32OtaCore::finalize(const char *frontend_sha_hex, bool reboot,
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 中止 OTA 会话并清理资源。
|
||||
*/
|
||||
void Esp32OtaCore::abort()
|
||||
{
|
||||
if (!m_active) {
|
||||
|
||||
@@ -22,15 +22,30 @@
|
||||
|
||||
class Esp32OtaCore {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取 Esp32OtaCore 单例实例。
|
||||
* @return Esp32OtaCore 引用。
|
||||
*/
|
||||
static Esp32OtaCore& instance();
|
||||
|
||||
// ── 阶段 1:开启 OTA 会话 ──
|
||||
// total_size: 固件总大小(字节),用于分区大小校验和传给 esp_ota_begin
|
||||
// 返回 ESP_OK / ESP_ERR_INVALID_STATE(被其它通道占用)/ ESP_FAIL
|
||||
/**
|
||||
* @brief 开启 OTA 会话。
|
||||
* @param total_size 固件总大小(字节)。
|
||||
* @return ESP_OK 成功,ESP_ERR_INVALID_STATE 被占用,ESP_FAIL 其他错误。
|
||||
*/
|
||||
esp_err_t begin(size_t total_size);
|
||||
|
||||
// ── 阶段 2:写入固件数据(可在 begin 后多次调用)──
|
||||
// 纯 esp_ota_write,不做增量 SHA(SHA 在 finalize 时统一从分区读回计算)
|
||||
/**
|
||||
* @brief 写入固件数据块。
|
||||
* @param data 数据缓冲区。
|
||||
* @param len 数据长度。
|
||||
* @return ESP_OK 成功,否则错误码。
|
||||
*/
|
||||
esp_err_t write(const uint8_t *data, size_t len);
|
||||
|
||||
// ── 阶段 3:收尾(end → 校验 SHA → set_boot → 可选重启)──
|
||||
@@ -41,6 +56,13 @@ public:
|
||||
// ESP_OK 成功(若 reboot=true,本调用不会返回)
|
||||
// ESP_ERR_INVALID_VERSION SHA 不匹配(已 abort 并清理)
|
||||
// 其他 end/set_boot 失败(已清理)
|
||||
/**
|
||||
* @brief 收尾 OTA 会话:end、校验 SHA、set_boot、可选重启。
|
||||
* @param frontend_sha_hex 前端提供的 64 字符 hex SHA-256。
|
||||
* @param reboot 成功后是否重启。
|
||||
* @param out_device_sha_hex 输出设备端 SHA hex 缓冲区。
|
||||
* @return ESP_OK 成功,ESP_ERR_INVALID_VERSION SHA 不匹配。
|
||||
*/
|
||||
esp_err_t finalize(const char *frontend_sha_hex, bool reboot,
|
||||
char *out_device_sha_hex);
|
||||
|
||||
@@ -48,25 +70,44 @@ public:
|
||||
// 供前端只读校验场景(如 WiFi verify_only 模式)
|
||||
// out_device_sha_hex: 输出缓冲区,至少 65 字节
|
||||
// 返回 ESP_OK / 失败
|
||||
/**
|
||||
* @brief 仅计算设备端 SHA(不 end、不 set_boot、不重启)。
|
||||
* @param out_device_sha_hex 输出缓冲区,至少 65 字节。
|
||||
* @return ESP_OK 成功,否则错误码。
|
||||
*/
|
||||
esp_err_t computeDeviceSha(char *out_device_sha_hex);
|
||||
|
||||
// ── 中止并清理(abort + 释放 mutex + 状态复位)──
|
||||
// 若 esp_ota_end 已成功(m_ended=true),则跳过 esp_ota_abort 仅复位状态
|
||||
/**
|
||||
* @brief 中止并清理 OTA 会话。
|
||||
*/
|
||||
void abort();
|
||||
|
||||
// ── 状态查询 ──
|
||||
/** @brief 查询 OTA 是否激活。 */
|
||||
bool isActive() const { return m_active; }
|
||||
/** @brief 获取已接收的字节数。 */
|
||||
size_t getReceived() const { return m_received; }
|
||||
/** @brief 查询是否等待重启。 */
|
||||
bool isRebootPending() const { return m_rebootPending; }
|
||||
/** @brief 设置重启待处理标志。 */
|
||||
void setRebootPending(bool v) { m_rebootPending = v; }
|
||||
|
||||
private:
|
||||
Esp32OtaCore() = default;
|
||||
|
||||
// 从 OTA 分区读回计算 SHA-256(WiFi 风格),结果写入 m_sha256,置 m_shaComputed=true
|
||||
/**
|
||||
* @brief 从 OTA 分区读回计算 SHA-256。
|
||||
* @return ESP_OK 成功,否则错误码。
|
||||
*/
|
||||
esp_err_t computeShaFromPartition();
|
||||
|
||||
// 状态全复位(不调 esp_ota_abort,由调用方根据 m_ended 决定)
|
||||
/**
|
||||
* @brief 复位所有状态(不调 esp_ota_abort)。
|
||||
*/
|
||||
void resetState();
|
||||
|
||||
bool m_active = false;
|
||||
|
||||
@@ -36,11 +36,18 @@ QueueHandle_t OtaRelay::dispatchQueue = NULL;
|
||||
QueueHandle_t OtaRelay::resultQueue = NULL;
|
||||
SemaphoreHandle_t OtaRelay::s_stateMutex = NULL;
|
||||
|
||||
/**
|
||||
* @brief 获取OtaRelay单例引用.
|
||||
* @return OtaRelay单例引用.
|
||||
*/
|
||||
OtaRelay& OtaRelay::instance()
|
||||
{
|
||||
static OtaRelay inst;
|
||||
return inst;
|
||||
}
|
||||
/**
|
||||
* @brief 初始化状态互斥锁.
|
||||
*/
|
||||
void OtaRelay::initMutex()
|
||||
{
|
||||
if (!s_stateMutex) {
|
||||
@@ -48,30 +55,50 @@ void OtaRelay::initMutex()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前中继状态.
|
||||
* @return 当前中继状态.
|
||||
*/
|
||||
relay_state_t OtaRelay::getState()
|
||||
{
|
||||
StateLockGuard lock;
|
||||
return relayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取中继当前序号.
|
||||
* @return 当前序号值.
|
||||
*/
|
||||
uint32_t OtaRelay::getRelaySeq()
|
||||
{
|
||||
StateLockGuard lock;
|
||||
return relaySeq;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取中继总包数.
|
||||
* @return 总包数.
|
||||
*/
|
||||
uint32_t OtaRelay::getRelayTotalPackets()
|
||||
{
|
||||
StateLockGuard lock;
|
||||
return relayTotalPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取GD32 APP存活标志位.
|
||||
* @return true 存活,false 未存活.
|
||||
*/
|
||||
bool OtaRelay::getGd32AppAlive()
|
||||
{
|
||||
StateLockGuard lock;
|
||||
return gd32AppAlive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取GD32固件版本.
|
||||
* @param out 输出参数,版本结构体指针.
|
||||
*/
|
||||
void OtaRelay::getGd32Version(gd32_version_t *out)
|
||||
{
|
||||
if (!out) return;
|
||||
@@ -79,30 +106,50 @@ void OtaRelay::getGd32Version(gd32_version_t *out)
|
||||
*out = gd32Version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置中继状态.
|
||||
* @param s 目标状态值.
|
||||
*/
|
||||
void OtaRelay::setState(relay_state_t s)
|
||||
{
|
||||
StateLockGuard lock;
|
||||
relayState = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置中继当前序号.
|
||||
* @param seq 序号值.
|
||||
*/
|
||||
void OtaRelay::setRelaySeq(uint32_t seq)
|
||||
{
|
||||
StateLockGuard lock;
|
||||
relaySeq = seq;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置中继总包数.
|
||||
* @param total 总包数.
|
||||
*/
|
||||
void OtaRelay::setRelayTotalPackets(uint32_t total)
|
||||
{
|
||||
StateLockGuard lock;
|
||||
relayTotalPackets = total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置GD32 APP存活标志位.
|
||||
* @param alive 存活状态.
|
||||
*/
|
||||
void OtaRelay::setGd32AppAlive(bool alive)
|
||||
{
|
||||
StateLockGuard lock;
|
||||
gd32AppAlive = alive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置GD32固件版本.
|
||||
* @param ver 版本结构体引用.
|
||||
*/
|
||||
void OtaRelay::setGd32Version(const gd32_version_t &ver)
|
||||
{
|
||||
StateLockGuard lock;
|
||||
@@ -115,6 +162,13 @@ void OtaRelay::setGd32Version(const gd32_version_t &ver)
|
||||
// 内部工具函数
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* @brief 检查缓冲区是否全部为指定值.
|
||||
* @param buf 缓冲区指针.
|
||||
* @param len 缓冲区长度.
|
||||
* @param value 待检查的值.
|
||||
* @return true 全部为指定值,false 不是.
|
||||
*/
|
||||
static bool bytesAllValue(const uint8_t *buf, size_t len, uint8_t value)
|
||||
{
|
||||
if (!buf || len == 0) return false;
|
||||
@@ -124,6 +178,13 @@ static bool bytesAllValue(const uint8_t *buf, size_t len, uint8_t value)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使用AES-128-ECB加密单个16字节块.
|
||||
* @param key 16字节密钥.
|
||||
* @param in 16字节输入数据.
|
||||
* @param out 16字节输出数据.
|
||||
* @return 0 成功,-1 失败.
|
||||
*/
|
||||
int OtaRelay::aes128EcbEncryptBlockLocal(const uint8_t *key, const uint8_t *in, uint8_t *out)
|
||||
{
|
||||
esp_aes_context ctx;
|
||||
@@ -134,6 +195,13 @@ int OtaRelay::aes128EcbEncryptBlockLocal(const uint8_t *key, const uint8_t *in,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 记录响应载荷日志.
|
||||
* @param stage 阶段名称字符串.
|
||||
* @param resp_type 响应类型.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
*/
|
||||
void OtaRelay::logRespPayload(const char *stage, uint8_t resp_type, const uint8_t *payload, size_t len)
|
||||
{
|
||||
char hex[193] = {0};
|
||||
@@ -145,6 +213,17 @@ void OtaRelay::logRespPayload(const char *stage, uint8_t resp_type, const uint8_
|
||||
n > 0 ? hex : "<empty>", len > n ? "..." : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 循环等待直到响应不再是WAIT状态.
|
||||
* @param stage 阶段名称字符串.
|
||||
* @param resp_type 响应类型输出.
|
||||
* @param resp_payload 响应载荷缓冲区.
|
||||
* @param resp_max 缓冲区最大长度.
|
||||
* @param resp_len 实际响应长度输出.
|
||||
* @param timeout_ms 单次轮询超时毫秒.
|
||||
* @param max_wait_rounds 最大WAIT轮次.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
esp_err_t OtaRelay::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)
|
||||
@@ -162,6 +241,13 @@ esp_err_t OtaRelay::waitUntilNotWait(const char *stage, uint8_t *resp_type,
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断是否为带序号的结构化ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @param expected_seq 期望的序号.
|
||||
* @return true 匹配成功,false 不匹配.
|
||||
*/
|
||||
bool OtaRelay::isStructuredAckWithSeq(const uint8_t *payload, size_t len, uint32_t expected_seq)
|
||||
{
|
||||
if (!payload || len < 6) return false;
|
||||
@@ -171,6 +257,14 @@ bool OtaRelay::isStructuredAckWithSeq(const uint8_t *payload, size_t len, uint32
|
||||
return seq == expected_seq;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析结构化错误帧.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @param seq_out 输出错误序号.
|
||||
* @param err_out 输出错误码.
|
||||
* @return true 解析成功,false 解析失败.
|
||||
*/
|
||||
bool OtaRelay::parseStructuredError(const uint8_t *payload, size_t len, uint32_t *seq_out, uint8_t *err_out)
|
||||
{
|
||||
if (!payload || len < 7) return false;
|
||||
@@ -181,18 +275,48 @@ bool OtaRelay::parseStructuredError(const uint8_t *payload, size_t len, uint32_t
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断是否为握手回显ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是握手回显ACK,false 不是.
|
||||
*/
|
||||
bool OtaRelay::isHandshakeEchoAck(const uint8_t *payload, size_t len)
|
||||
{ return payload && len >= 2U && payload[0] == 0x00U && payload[1] == 0xACU; }
|
||||
|
||||
/**
|
||||
* @brief 判断是否为挑战应答ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是挑战应答ACK,false 不是.
|
||||
*/
|
||||
bool OtaRelay::isChallengePayloadAck(const uint8_t *payload, size_t len)
|
||||
{ return payload && len == GD32_CHALLENGE_RESPONSE_LEN; }
|
||||
|
||||
/**
|
||||
* @brief 判断是否为普通确认ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是确认ACK,false 不是.
|
||||
*/
|
||||
bool OtaRelay::isConfirmPlainAck(const uint8_t *payload, size_t len)
|
||||
{ return payload && len == 2U && payload[0] == 0xBAU && payload[1] == 0xC0U; }
|
||||
|
||||
/**
|
||||
* @brief 判断是否为大小序号0的ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是大小序号0ACK,false 不是.
|
||||
*/
|
||||
bool OtaRelay::isSizeSeq0Ack(const uint8_t *payload, size_t len)
|
||||
{ return isStructuredAckWithSeq(payload, len, 0U); }
|
||||
|
||||
/**
|
||||
* @brief 判断是否为普通的结束ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是结束ACK,false 不是.
|
||||
*/
|
||||
bool OtaRelay::isFinishPlainAck(const uint8_t *payload, size_t len)
|
||||
{
|
||||
if (!payload) return false;
|
||||
@@ -201,6 +325,19 @@ bool OtaRelay::isFinishPlainAck(const uint8_t *payload, size_t len)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 轮询直到收到预期的ACK形状.
|
||||
* @param stage 阶段名称字符串.
|
||||
* @param resp_type 响应类型输出.
|
||||
* @param resp_payload 响应载荷缓冲区.
|
||||
* @param resp_max 缓冲区最大长度.
|
||||
* @param resp_len 实际响应长度输出.
|
||||
* @param poll_timeout_ms 轮询超时毫秒.
|
||||
* @param wait_rounds WAIT状态最大轮询次数.
|
||||
* @param maxExtraPolls 额外轮询次数.
|
||||
* @param matcher 匹配回调函数.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
esp_err_t OtaRelay::pollUntilExpectedAckShape(const char *stage,
|
||||
uint8_t *resp_type, uint8_t *resp_payload,
|
||||
size_t resp_max, size_t *resp_len,
|
||||
@@ -233,6 +370,14 @@ esp_err_t OtaRelay::pollUntilExpectedAckShape(const char *stage,
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析并验证挑战应答载荷.
|
||||
* @param resp_payload 响应载荷指针.
|
||||
* @param resp_len 响应长度.
|
||||
* @param out_k_session 输出会话密钥.
|
||||
* @param out_uid 输出GD32 UID.
|
||||
* @return true 验证通过,false 验证失败.
|
||||
*/
|
||||
bool OtaRelay::parseAndVerifyChallengePayload(const uint8_t *resp_payload, size_t resp_len,
|
||||
uint8_t *out_k_session, uint8_t *out_uid)
|
||||
{
|
||||
@@ -265,6 +410,13 @@ bool OtaRelay::parseAndVerifyChallengePayload(const uint8_t *resp_payload, size_
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使用AES-128-ECB加密48字节数据.
|
||||
* @param key 16字节密钥.
|
||||
* @param in 48字节输入数据.
|
||||
* @param out 48字节输出数据.
|
||||
* @return true 加密成功,false 加密失败.
|
||||
*/
|
||||
bool OtaRelay::aes128EcbEncrypt48Bytes(const uint8_t *key, const uint8_t *in, uint8_t *out)
|
||||
{
|
||||
for (int i = 0; i < 3; ++i)
|
||||
@@ -272,6 +424,12 @@ bool OtaRelay::aes128EcbEncrypt48Bytes(const uint8_t *key, const uint8_t *in, ui
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使用密钥计算48字节数据的CRC32.
|
||||
* @param key 16字节密钥.
|
||||
* @param data48 48字节输入数据.
|
||||
* @param output 输出4字节CRC32结果.
|
||||
*/
|
||||
void OtaRelay::calculateCrc32WithKey(const uint8_t *key, const uint8_t *data48, uint8_t *output)
|
||||
{
|
||||
uint32_t crc = 0xFFFFFFFFU;
|
||||
@@ -613,6 +771,10 @@ esp_err_t OtaRelay::requestGd32AppEnterBootloader()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 阶段1 - 发送握手请求并等待确认.
|
||||
* @return 操作结果码.
|
||||
*/
|
||||
ota_result_t OtaRelay::sendHandshakeReq()
|
||||
{
|
||||
DEBUG_INFO("OTA", "=== 阶段1: 握手开始 ===");
|
||||
|
||||
@@ -57,9 +57,21 @@ typedef enum {
|
||||
|
||||
class OtaRelay {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取OtaRelay单例引用.
|
||||
* @return OtaRelay单例引用.
|
||||
*/
|
||||
static OtaRelay& instance();
|
||||
|
||||
/**
|
||||
* @brief OTA中继FreeRTOS任务入口.
|
||||
* @param pvParameters FreeRTOS任务参数.
|
||||
*/
|
||||
static void taskEntry(void *pvParameters);
|
||||
/**
|
||||
* @brief 从Flash向GD32转发固件(主流程).
|
||||
* @return 操作结果码.
|
||||
*/
|
||||
ota_result_t forwardFirmware();
|
||||
|
||||
/* ---- 线程安全的状态访问接口 ----
|
||||
@@ -67,27 +79,117 @@ public:
|
||||
* 状态机内部(OTA 任务)可直接写 static 成员,
|
||||
* 跨任务读/写必须走这些 API。
|
||||
*/
|
||||
/**
|
||||
* @brief 获取当前中继状态.
|
||||
* @return 当前中继状态.
|
||||
*/
|
||||
static relay_state_t getState();
|
||||
/**
|
||||
* @brief 获取中继当前序号.
|
||||
* @return 当前序号值.
|
||||
*/
|
||||
static uint32_t getRelaySeq();
|
||||
/**
|
||||
* @brief 获取中继总包数.
|
||||
* @return 总包数.
|
||||
*/
|
||||
static uint32_t getRelayTotalPackets();
|
||||
/**
|
||||
* @brief 获取GD32 APP存活标志位.
|
||||
* @return true 存活,false 未存活.
|
||||
*/
|
||||
static bool getGd32AppAlive();
|
||||
/**
|
||||
* @brief 获取GD32固件版本.
|
||||
* @param out 输出参数,版本结构体指针.
|
||||
*/
|
||||
static void getGd32Version(gd32_version_t *out);
|
||||
/**
|
||||
* @brief 设置中继状态.
|
||||
* @param s 目标状态值.
|
||||
*/
|
||||
static void setState(relay_state_t s);
|
||||
/**
|
||||
* @brief 设置中继当前序号.
|
||||
* @param seq 序号值.
|
||||
*/
|
||||
static void setRelaySeq(uint32_t seq);
|
||||
/**
|
||||
* @brief 设置中继总包数.
|
||||
* @param total 总包数.
|
||||
*/
|
||||
static void setRelayTotalPackets(uint32_t total);
|
||||
/**
|
||||
* @brief 设置GD32 APP存活标志位.
|
||||
* @param alive 存活状态.
|
||||
*/
|
||||
static void setGd32AppAlive(bool alive);
|
||||
/**
|
||||
* @brief 获取GD32固件版本.
|
||||
* @param ver 待设置的版本结构体引用.
|
||||
*/
|
||||
static void setGd32Version(const gd32_version_t &ver);
|
||||
|
||||
/**
|
||||
* @brief 重置状态机.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* @brief Flash写入接口(支持非4字节对齐写入).
|
||||
* @param offset 分区内写入偏移.
|
||||
* @param data 待写入的数据指针.
|
||||
* @param len 数据长度(字节).
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
static esp_err_t flashWriteChunk(uint32_t offset, const uint8_t *data, size_t len);
|
||||
/**
|
||||
* @brief 从OTA缓存分区读取数据块.
|
||||
* @param offset 分区内读取偏移.
|
||||
* @param data 读取缓冲区指针.
|
||||
* @param len 读取长度(字节).
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
static esp_err_t flashReadChunk(uint32_t offset, uint8_t *data, size_t len);
|
||||
/**
|
||||
* @brief 从Flash加载元数据.
|
||||
* @param meta 输出参数,加载后的元数据结构体指针.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
static esp_err_t metaLoad(ota_meta_t *meta);
|
||||
/**
|
||||
* @brief 保存元数据到Flash.
|
||||
* @param meta 待保存的元数据结构体指针.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
static esp_err_t metaSave(const ota_meta_t *meta);
|
||||
/**
|
||||
* @brief 传输过程中更新元数据.
|
||||
* @param stored_size 已存储的固件大小(字节).
|
||||
* @param last_seq 最后成功的分块序号.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
static esp_err_t metaUpdate(uint32_t stored_size, uint32_t last_seq);
|
||||
/**
|
||||
* @brief OTA完成或中止时清除元数据.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
static esp_err_t metaClear();
|
||||
/**
|
||||
* @brief 计算gd32_ota分区中已存储固件的SHA-256.
|
||||
* @param out_sha256 输出32字节SHA-256摘要.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
static esp_err_t computeFlashSha256(uint8_t out_sha256[32]);
|
||||
/**
|
||||
* @brief 固件接收完毕后计算SHA-256并写入元数据.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
static esp_err_t metaFinalizeChecksum();
|
||||
/**
|
||||
* @brief 校验固件完整性(SHA-256).
|
||||
* @return ESP_OK 校验通过,ESP_ERR_INVALID_CRC 校验失败.
|
||||
*/
|
||||
static esp_err_t metaVerifyChecksum();
|
||||
|
||||
// 公开状态(状态机内部直接访问;跨任务请用 getter/setter)
|
||||
@@ -101,17 +203,31 @@ public:
|
||||
|
||||
// 线程安全锁
|
||||
static SemaphoreHandle_t s_stateMutex;
|
||||
/**
|
||||
* @brief 初始化状态互斥锁.
|
||||
*/
|
||||
static void initMutex();
|
||||
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(50);
|
||||
|
||||
class StateLockGuard {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造时获取互斥锁.
|
||||
* @param timeout 等待超时(时钟节拍),默认50ms.
|
||||
*/
|
||||
explicit StateLockGuard(TickType_t timeout = kLockTimeout) {
|
||||
m_locked = (s_stateMutex && xSemaphoreTake(s_stateMutex, timeout) == pdTRUE);
|
||||
}
|
||||
/**
|
||||
* @brief 析构时释放互斥锁.
|
||||
*/
|
||||
~StateLockGuard() {
|
||||
if (m_locked && s_stateMutex) { xSemaphoreGive(s_stateMutex); }
|
||||
}
|
||||
/**
|
||||
* @brief 检查是否成功获取锁.
|
||||
* @return true 已锁定,false 未锁定.
|
||||
*/
|
||||
bool isLocked() const { return m_locked; }
|
||||
StateLockGuard(const StateLockGuard&) = delete;
|
||||
StateLockGuard& operator=(const StateLockGuard&) = delete;
|
||||
@@ -129,28 +245,137 @@ public:
|
||||
static constexpr size_t GD32_UID_LEN = 12;
|
||||
static constexpr size_t GD32_CHALLENGE_RESPONSE_LEN = 36;
|
||||
|
||||
/**
|
||||
* @brief 请求GD32 APP进入Bootloader.
|
||||
* @return 操作结果码.
|
||||
*/
|
||||
esp_err_t requestGd32AppEnterBootloader();
|
||||
/**
|
||||
* @brief 发送握手请求.
|
||||
* @return 操作结果码.
|
||||
*/
|
||||
ota_result_t sendHandshakeReq();
|
||||
/**
|
||||
* @brief 发送随机数并与GD32相互认证.
|
||||
* @return 操作结果码.
|
||||
*/
|
||||
ota_result_t sendNonceAndAuthenticate();
|
||||
/**
|
||||
* @brief 向GD32发送固件总大小.
|
||||
* @param size 固件总字节数.
|
||||
* @return 操作结果码.
|
||||
*/
|
||||
ota_result_t sendFirmwareSize(uint32_t size);
|
||||
/**
|
||||
* @brief 从Flash向GD32转发固件包.
|
||||
* @param deadline_tick 截止时间戳(可选),0表示无超时.
|
||||
* @return 操作结果码.
|
||||
*/
|
||||
ota_result_t relayPacketsFromFlash(TickType_t deadline_tick = 0);
|
||||
/**
|
||||
* @brief 完成OTA并发送结束指令.
|
||||
* @return 操作结果码.
|
||||
*/
|
||||
ota_result_t finishOta();
|
||||
|
||||
/**
|
||||
* @brief 使用AES-128-ECB加密单个16字节块.
|
||||
* @param key 16字节密钥.
|
||||
* @param in 16字节输入数据.
|
||||
* @param out 16字节输出数据.
|
||||
* @return 0 成功,-1 失败.
|
||||
*/
|
||||
static int aes128EcbEncryptBlockLocal(const uint8_t *key, const uint8_t *in, uint8_t *out);
|
||||
/**
|
||||
* @brief 记录响应载荷日志.
|
||||
* @param stage 阶段名称字符串.
|
||||
* @param resp_type 响应类型.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
*/
|
||||
static void logRespPayload(const char *stage, uint8_t resp_type, const uint8_t *payload, size_t len);
|
||||
/**
|
||||
* @brief 循环等待直到响应不再是WAIT状态.
|
||||
* @param stage 阶段名称字符串.
|
||||
* @param resp_type 响应类型输出.
|
||||
* @param resp_payload 响应载荷缓冲区.
|
||||
* @param resp_max 缓冲区最大长度.
|
||||
* @param resp_len 实际响应长度输出.
|
||||
* @param timeout_ms 单次轮询超时毫秒.
|
||||
* @param max_wait_rounds 最大WAIT轮次.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* @brief 判断是否为带序号的结构化ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @param expected_seq 期望的序号.
|
||||
* @return true 匹配成功,false 不匹配.
|
||||
*/
|
||||
static bool isStructuredAckWithSeq(const uint8_t *payload, size_t len, uint32_t expected_seq);
|
||||
/**
|
||||
* @brief 解析结构化错误帧.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @param seq_out 输出错误序号.
|
||||
* @param err_out 输出错误码.
|
||||
* @return true 解析成功,false 解析失败.
|
||||
*/
|
||||
static bool parseStructuredError(const uint8_t *payload, size_t len, uint32_t *seq_out, uint8_t *err_out);
|
||||
/**
|
||||
* @brief 判断是否为握手回显ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是握手回显ACK,false 不是.
|
||||
*/
|
||||
static bool isHandshakeEchoAck(const uint8_t *payload, size_t len);
|
||||
/**
|
||||
* @brief 判断是否为挑战应答ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是挑战应答ACK,false 不是.
|
||||
*/
|
||||
static bool isChallengePayloadAck(const uint8_t *payload, size_t len);
|
||||
/**
|
||||
* @brief 判断是否为普通确认ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是确认ACK,false 不是.
|
||||
*/
|
||||
static bool isConfirmPlainAck(const uint8_t *payload, size_t len);
|
||||
/**
|
||||
* @brief 判断是否为大小序号0的ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是大小序号0ACK,false 不是.
|
||||
*/
|
||||
static bool isSizeSeq0Ack(const uint8_t *payload, size_t len);
|
||||
/**
|
||||
* @brief 判断是否为普通的结束ACK.
|
||||
* @param payload 载荷指针.
|
||||
* @param len 载荷长度.
|
||||
* @return true 是结束ACK,false 不是.
|
||||
*/
|
||||
static bool isFinishPlainAck(const uint8_t *payload, size_t len);
|
||||
|
||||
/**
|
||||
* @brief 轮询直到收到预期的ACK形状.
|
||||
* @param stage 阶段名称字符串.
|
||||
* @param resp_type 响应类型输出.
|
||||
* @param resp_payload 响应载荷缓冲区.
|
||||
* @param resp_max 响应缓冲区最大长度.
|
||||
* @param resp_len 实际响应长度输出.
|
||||
* @param poll_timeout_ms 轮询超时毫秒.
|
||||
* @param wait_rounds WAIT状态最大轮询次数.
|
||||
* @param maxExtraPolls 额外轮询次数.
|
||||
* @param matcher 匹配回调函数.
|
||||
* @return ESP_OK 成功,其他失败.
|
||||
*/
|
||||
static esp_err_t pollUntilExpectedAckShape(const char *stage,
|
||||
uint8_t *resp_type,
|
||||
uint8_t *resp_payload,
|
||||
@@ -160,9 +385,30 @@ public:
|
||||
uint32_t wait_rounds,
|
||||
uint32_t maxExtraPolls,
|
||||
bool (*matcher)(const uint8_t *, size_t));
|
||||
/**
|
||||
* @brief 解析并验证挑战应答载荷.
|
||||
* @param resp_payload 响应载荷指针.
|
||||
* @param resp_len 响应长度.
|
||||
* @param out_k_session 输出会话密钥.
|
||||
* @param out_uid 输出GD32 UID.
|
||||
* @return true 验证通过,false 验证失败.
|
||||
*/
|
||||
static bool parseAndVerifyChallengePayload(const uint8_t *resp_payload, size_t resp_len,
|
||||
uint8_t *out_k_session, uint8_t *out_uid);
|
||||
/**
|
||||
* @brief 使用AES-128-ECB加密48字节数据.
|
||||
* @param key 16字节密钥.
|
||||
* @param in 48字节输入数据.
|
||||
* @param out 48字节输出数据.
|
||||
* @return true 加密成功,false 加密失败.
|
||||
*/
|
||||
static bool aes128EcbEncrypt48Bytes(const uint8_t *key, const uint8_t *in, uint8_t *out);
|
||||
/**
|
||||
* @brief 使用密钥计算48字节数据的CRC32.
|
||||
* @param key 16字节密钥.
|
||||
* @param data48 48字节输入数据.
|
||||
* @param output 输出4字节CRC32结果.
|
||||
*/
|
||||
static void calculateCrc32WithKey(const uint8_t *key, const uint8_t *data48, uint8_t *output);
|
||||
|
||||
private:
|
||||
|
||||
@@ -71,6 +71,10 @@ static void sendWindowAck(uint8_t cmd, uint16_t seq, uint32_t completed_bytes);
|
||||
static void sendFinishEvent(uint8_t stage, uint32_t value);
|
||||
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 获取 BleOta 单例实例。
|
||||
* @return BleOta 类引用。
|
||||
*/
|
||||
BleOta& BleOta::instance()
|
||||
{
|
||||
static BleOta inst;
|
||||
@@ -103,6 +107,10 @@ static void ble_esp32_restart_task(void *pvParameters)
|
||||
// 不会返回
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 异步完成 ESP32 OTA 收尾并触发重启。
|
||||
* @param pvParameters 前端 SHA-256 十六进制字符串(堆分配,任务负责释放)。
|
||||
*/
|
||||
static void ble_esp32_finish_and_reboot_task(void *pvParameters)
|
||||
{
|
||||
// pvParameters: heap-allocated char[65] frontend SHA hex string (or nullptr)
|
||||
|
||||
@@ -14,12 +14,37 @@
|
||||
|
||||
class BleOta {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取 BleOta 单例实例。
|
||||
* @return BleOta 类引用。
|
||||
*/
|
||||
static BleOta& instance();
|
||||
|
||||
/**
|
||||
* @brief BLE OTA FreeRTOS 任务入口。
|
||||
* @param pvParameters 任务参数,未使用。
|
||||
*/
|
||||
static void taskEntry(void *pvParameters);
|
||||
/**
|
||||
* @brief 启动 BLE GATT 服务器并注册 OTA 服务。
|
||||
* @return ESP_OK 成功,其他失败。
|
||||
*/
|
||||
esp_err_t serverStart();
|
||||
/**
|
||||
* @brief 通过 BLE Notify 推送响应数据。
|
||||
* @param data 待推送的数据指针。
|
||||
* @param len 数据长度。
|
||||
* @return ESP_OK 成功,其他失败。
|
||||
*/
|
||||
esp_err_t notifyResponse(const uint8_t *data, size_t len);
|
||||
/**
|
||||
* @brief 检查 BLE 客户端连接状态。
|
||||
* @return 已连接返回 true,未连接返回 false。
|
||||
*/
|
||||
bool isConnected();
|
||||
/**
|
||||
* @brief 触发中继模块向 GD32 转发固件。
|
||||
*/
|
||||
void signalRelay();
|
||||
|
||||
// 以下状态为 public,供 C 回调函数访问
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
|
||||
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 获取 WifiOta 单例实例(静态局部变量实现)。
|
||||
* @return WifiOta 单例引用。
|
||||
*/
|
||||
WifiOta& WifiOta::instance()
|
||||
{
|
||||
static WifiOta inst;
|
||||
@@ -42,6 +46,12 @@ WifiOta& WifiOta::instance()
|
||||
|
||||
static constexpr uint32_t GD32_OTA_FLASH_SECTOR_SIZE = 4096U;
|
||||
|
||||
/**
|
||||
* @brief 按指定对齐值向上取整。
|
||||
* @param value 待对齐的值。
|
||||
* @param alignment 对齐字节数。
|
||||
* @return 对齐后的值。
|
||||
*/
|
||||
static uint32_t align_up_u32(uint32_t value, uint32_t alignment)
|
||||
{
|
||||
return ((value + alignment - 1U) / alignment) * alignment;
|
||||
|
||||
@@ -21,31 +21,71 @@ typedef enum {
|
||||
|
||||
class WifiOta {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取 WifiOta 单例实例。
|
||||
* @return WifiOta 单例引用。
|
||||
*/
|
||||
static WifiOta& instance();
|
||||
|
||||
/**
|
||||
* @brief 在调度器启动前初始化 WiFi 并尝试 STA 连接。
|
||||
* @return true 表示 STA 初始化中,false 需回退至 AP 模式。
|
||||
*/
|
||||
bool earlyInit();
|
||||
/**
|
||||
* @brief WiFi OTA FreeRTOS 任务入口,提供 HTTP 上传和中继触发。
|
||||
* @param pvParameters FreeRTOS 任务参数。
|
||||
*/
|
||||
static void taskEntry(void *pvParameters);
|
||||
/**
|
||||
* @brief 通知 OTA 中继开始向 GD32 转发固件。
|
||||
*/
|
||||
void signalRelay();
|
||||
|
||||
/**
|
||||
* @brief 获取当前 WiFi 工作模式。
|
||||
* @return 当前 WiFi 模式(AP 或 STA)。
|
||||
*/
|
||||
static ota_wifi_mode_t getMode();
|
||||
/**
|
||||
* @brief 获取当前 WiFi 信号强度。
|
||||
* @return 信号强度值(dBm)。
|
||||
*/
|
||||
static int8_t getRssi();
|
||||
/**
|
||||
* @brief 判断 STA 是否已连接到上级 WiFi。
|
||||
* @return true 已连接,false 未连接。
|
||||
*/
|
||||
static bool isConnected();
|
||||
|
||||
// ── WiFi 连接状态(WiFi 事件回调 / HTTP / reconnect task 共享,需 mutex 保护)──
|
||||
/** @brief 获取 STA 连接状态。 */
|
||||
bool getStaConnected() const { return m_staConnected.load(); }
|
||||
/** @brief 设置 STA 连接状态。 */
|
||||
void setStaConnected(bool v) { m_staConnected.store(v); }
|
||||
/** @brief 获取 STA 重试次数。 */
|
||||
int getStaRetryCount() const { return m_staRetryCount.load(); }
|
||||
/** @brief 设置 STA 重试次数。 */
|
||||
void setStaRetryCount(int v) { m_staRetryCount.store(v); }
|
||||
/** @brief 获取 STA 重试是否已耗尽。 */
|
||||
bool getStaRetryExhausted() const { return m_staRetryExhausted.load(); }
|
||||
/** @brief 设置 STA 重试耗尽标志。 */
|
||||
void setStaRetryExhausted(bool v) { m_staRetryExhausted.store(v); }
|
||||
/** @brief 获取 GD32 固件存储完成标志。 */
|
||||
bool getGd32StorageDone() const { return m_gd32StorageDone.load(); }
|
||||
/** @brief 设置 GD32 固件存储完成标志。 */
|
||||
void setGd32StorageDone(bool v) { m_gd32StorageDone.store(v); }
|
||||
/** @brief 获取中继触发标志。 */
|
||||
bool getRelayTriggered() const { return m_relayTriggered.load(); }
|
||||
/** @brief 设置中继触发标志。 */
|
||||
void setRelayTriggered(bool v) { m_relayTriggered.store(v); }
|
||||
|
||||
// ── 状态互斥锁(保护复合读-改-写操作)──
|
||||
/** @brief 获取状态互斥锁句柄。 */
|
||||
SemaphoreHandle_t getStateMutex() const { return m_stateMutex; }
|
||||
/** @brief 锁定状态互斥锁。 */
|
||||
void stateLock() { if (m_stateMutex) xSemaphoreTake(m_stateMutex, portMAX_DELAY); }
|
||||
/** @brief 解锁状态互斥锁。 */
|
||||
void stateUnlock() { if (m_stateMutex) xSemaphoreGive(m_stateMutex); }
|
||||
|
||||
// ── 初始化/运行时状态(仅 OTA 任务访问,无需额外锁)──
|
||||
@@ -66,13 +106,44 @@ private:
|
||||
std::atomic<bool> m_relayTriggered{false};
|
||||
SemaphoreHandle_t m_stateMutex = NULL;
|
||||
|
||||
/**
|
||||
* @brief 初始化 SoftAP 模式,供局域网 OTA 使用。
|
||||
*/
|
||||
void initAp();
|
||||
/**
|
||||
* @brief 初始化 STA 模式并连接预配置路由器。
|
||||
* @return ESP_OK 成功,其他失败。
|
||||
*/
|
||||
esp_err_t initSta();
|
||||
/**
|
||||
* @brief 设置 STA 静态 IP、网关和子网掩码。
|
||||
*/
|
||||
void setStaticIp();
|
||||
/**
|
||||
* @brief WiFi/IP 事件回调,处理连接、断开和 IP 获取。
|
||||
* @param arg 回调用户参数。
|
||||
* @param event_base 事件基类。
|
||||
* @param event_id 具体事件 ID。
|
||||
* @param event_data 事件附带数据。
|
||||
*/
|
||||
static void wifiEventHandler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data);
|
||||
/**
|
||||
* @brief STA 后台重连任务,处理退避延迟和重连。
|
||||
* @param pvParameters FreeRTOS 任务参数。
|
||||
*/
|
||||
static void reconnectTask(void *pvParameters);
|
||||
/**
|
||||
* @brief 启动 HTTP OTA 服务器,注册各接口路由。
|
||||
* @return ESP_OK 成功,其他失败。
|
||||
*/
|
||||
esp_err_t httpServerStart();
|
||||
/**
|
||||
* @brief 等待 STA 连接成功并获取 IP 地址。
|
||||
* @param ip_str 输出缓冲区,存放 IP 字符串。
|
||||
* @param ip_len 缓冲区长度。
|
||||
* @return ESP_OK 连接成功,ESP_ERR_TIMEOUT 超时。
|
||||
*/
|
||||
esp_err_t waitStaConnected(char *ip_str, size_t ip_len);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,12 +20,19 @@
|
||||
// ============================================================
|
||||
// Protocol 单例
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 获取 Protocol 单例实例。
|
||||
* @return Protocol 单例引用。
|
||||
*/
|
||||
Protocol& Protocol::instance()
|
||||
{
|
||||
static Protocol inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化协议层,打印初始化信息并设置初始化标志。
|
||||
*/
|
||||
void Protocol::init()
|
||||
{
|
||||
if (m_initialized) return;
|
||||
|
||||
@@ -37,13 +37,37 @@ typedef struct {
|
||||
*/
|
||||
class Protocol {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取 Protocol 单例实例。
|
||||
* @return Protocol 单例引用。
|
||||
*/
|
||||
static Protocol& instance();
|
||||
|
||||
/**
|
||||
* @brief 初始化协议层。
|
||||
*/
|
||||
void init();
|
||||
|
||||
/**
|
||||
* @brief 生成基于时间戳、地址和随机种子的扰码 nonce。
|
||||
* @param timestamp 时间戳。
|
||||
* @param mem_addr 内存地址。
|
||||
* @param rand_seed 随机种子。
|
||||
* @param nonce_out 输出的 16 字节 nonce 缓冲区。
|
||||
*/
|
||||
static void scrambleGenerate(uint32_t timestamp, uint32_t mem_addr,
|
||||
uint32_t rand_seed, uint8_t *nonce_out);
|
||||
/**
|
||||
* @brief 会话密钥初始化,将密钥拷贝到会话上下文。
|
||||
* @param sess 会话上下文指针。
|
||||
* @param k_session 会话密钥数据。
|
||||
* @return 成功返回 0,参数无效返回 -1。
|
||||
*/
|
||||
static int sessionKeyInit(session_ctx_t *sess, const uint8_t *k_session);
|
||||
/**
|
||||
* @brief 清除会话上下文的密钥和 IV 数据。
|
||||
* @param sess 会话上下文指针。
|
||||
*/
|
||||
static void sessionKeyClear(session_ctx_t *sess);
|
||||
|
||||
private:
|
||||
|
||||
@@ -23,6 +23,10 @@ static constexpr size_t kSpiTransferMaxBytes = 256U;
|
||||
// ============================================================
|
||||
// 单例
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 获取 SpiMaster 单例实例(单例模式)。
|
||||
* @return SpiMaster 单例引用。
|
||||
*/
|
||||
SpiMaster& SpiMaster::instance()
|
||||
{
|
||||
static SpiMaster inst;
|
||||
@@ -32,6 +36,15 @@ SpiMaster& SpiMaster::instance()
|
||||
// ============================================================
|
||||
// 内部 SPI 传输
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 执行底层 SPI 时钟同步传输。
|
||||
* @param tx_buf 发送数据缓冲区,可为 NULL。
|
||||
* @param tx_len 发送数据长度。
|
||||
* @param rx_buf 接收数据缓冲区,可为 NULL。
|
||||
* @param rx_len 接收数据长度。
|
||||
* @param timeout_ms 超时时间(毫秒),0 表示无限等待。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::transferClocked(const uint8_t *tx_buf, size_t tx_len,
|
||||
uint8_t *rx_buf, size_t rx_len,
|
||||
uint32_t timeout_ms)
|
||||
@@ -100,6 +113,16 @@ esp_err_t SpiMaster::transferClocked(const uint8_t *tx_buf, size_t tx_len,
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行 SPI 请求-响应组合传输。
|
||||
* @param cmd 命令字节(0 表示无命令)。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param payload_len 负载数据长度。
|
||||
* @param rx_buf 接收数据缓冲区。
|
||||
* @param rx_len 期望接收的数据长度。
|
||||
* @param timeout_ms 超时时间(毫秒),0 表示无限等待。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::transferRequestResponse(uint8_t cmd,
|
||||
const uint8_t *payload, size_t payload_len,
|
||||
uint8_t *rx_buf, size_t rx_len,
|
||||
@@ -146,6 +169,12 @@ esp_err_t SpiMaster::transferRequestResponse(uint8_t cmd,
|
||||
// ============================================================
|
||||
// OTA 帧工具
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 计算 OTA 帧的 8 位 CRC 校验和。
|
||||
* @param data 数据缓冲区。
|
||||
* @param len 数据长度。
|
||||
* @return 计算得到的 CRC 值。
|
||||
*/
|
||||
uint8_t SpiMaster::otaCrc8(const uint8_t *data, size_t len)
|
||||
{
|
||||
uint8_t crc = 0x00;
|
||||
@@ -155,6 +184,15 @@ uint8_t SpiMaster::otaCrc8(const uint8_t *data, size_t len)
|
||||
return crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 构建 OTA 协议帧。
|
||||
* @param type 帧类型。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param payload_len 负载数据长度。
|
||||
* @param out 输出帧缓冲区。
|
||||
* @param out_len 输出实际帧长度。
|
||||
* @return true 表示构建成功,false 表示失败。
|
||||
*/
|
||||
bool SpiMaster::otaBuildFrame(uint8_t type, const uint8_t *payload, size_t payload_len,
|
||||
uint8_t *out, size_t *out_len)
|
||||
{
|
||||
@@ -173,6 +211,15 @@ bool SpiMaster::otaBuildFrame(uint8_t type, const uint8_t *payload, size_t paylo
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析 OTA 协议帧。
|
||||
* @param frame 帧数据缓冲区。
|
||||
* @param frame_len 帧数据长度。
|
||||
* @param type 输出解析得到的帧类型。
|
||||
* @param payload 输出指向负载数据的指针。
|
||||
* @param payload_len 输出负载数据长度。
|
||||
* @return true 表示解析成功,false 表示失败。
|
||||
*/
|
||||
bool SpiMaster::otaParseFrame(const uint8_t *frame, size_t frame_len,
|
||||
uint8_t *type, const uint8_t **payload, size_t *payload_len)
|
||||
{
|
||||
@@ -192,6 +239,16 @@ bool SpiMaster::otaParseFrame(const uint8_t *frame, size_t frame_len,
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 在数据缓冲区中搜索并解析 OTA 帧。
|
||||
* @param buf 数据缓冲区。
|
||||
* @param buf_len 数据缓冲区长度。
|
||||
* @param type 输出帧类型。
|
||||
* @param payload 输出指向负载数据的指针。
|
||||
* @param payload_len 输出负载数据长度。
|
||||
* @param frame_offset 输出帧在缓冲区中的偏移位置。
|
||||
* @return true 表示找到并成功解析,false 表示未找到。
|
||||
*/
|
||||
bool SpiMaster::otaFindFrame(const uint8_t *buf, size_t buf_len,
|
||||
uint8_t *type, const uint8_t **payload, size_t *payload_len,
|
||||
size_t *frame_offset)
|
||||
@@ -222,6 +279,10 @@ bool SpiMaster::otaFindFrame(const uint8_t *buf, size_t buf_len,
|
||||
// ============================================================
|
||||
// 初始化
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 初始化 SPI2 总线与目标从设备。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::init()
|
||||
{
|
||||
LockGuard lock(m_mutex);
|
||||
@@ -274,6 +335,16 @@ esp_err_t SpiMaster::init()
|
||||
// ============================================================
|
||||
// 公开 SPI 传输接口
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 执行一次同步 SPI 事务,先发送请求,再读取响应字节。
|
||||
* @param cmd 命令字节。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param payload_len 负载数据长度。
|
||||
* @param recv_buf 接收数据缓冲区。
|
||||
* @param recv_len 接收数据长度。
|
||||
* @param timeout_ms 超时时间(毫秒)。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::syncTransfer(uint8_t cmd, const uint8_t *payload,
|
||||
size_t payload_len, uint8_t *recv_buf,
|
||||
size_t recv_len, uint32_t timeout_ms)
|
||||
@@ -282,12 +353,27 @@ esp_err_t SpiMaster::syncTransfer(uint8_t cmd, const uint8_t *payload,
|
||||
return transferRequestResponse(cmd, payload, payload_len, recv_buf, recv_len, timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 仅发送 SPI 请求数据,不关心返回内容。
|
||||
* @param cmd 命令字节。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param len 负载数据长度。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::send(uint8_t cmd, const uint8_t *payload, size_t len)
|
||||
{
|
||||
LockGuard lock(m_mutex);
|
||||
return transferRequestResponse(cmd, payload, len, NULL, 0, 0U);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 仅接收 SPI 数据,必要时先发送命令字节。
|
||||
* @param cmd 命令字节。
|
||||
* @param recv_buf 接收数据缓冲区。
|
||||
* @param len 接收数据长度。
|
||||
* @param timeout_ms 超时时间(毫秒)。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms)
|
||||
{
|
||||
LockGuard lock(m_mutex);
|
||||
@@ -297,6 +383,18 @@ esp_err_t SpiMaster::recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t t
|
||||
// ============================================================
|
||||
// SPI OTA 协议
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 按 GD32 SPI OTA 最小协议执行一次请求加响应读取。
|
||||
* @param type 请求帧类型。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param payload_len 负载数据长度。
|
||||
* @param resp_type 输出响应类型。
|
||||
* @param resp_payload 输出响应负载缓冲区。
|
||||
* @param resp_max 响应负载缓冲区最大长度。
|
||||
* @param resp_len 输出实际响应长度。
|
||||
* @param timeout_ms 超时时间(毫秒)。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::otaTransfer(uint8_t type, const uint8_t *payload, size_t payload_len,
|
||||
uint8_t *resp_type, uint8_t *resp_payload,
|
||||
size_t resp_max, size_t *resp_len, uint32_t timeout_ms)
|
||||
@@ -319,6 +417,15 @@ esp_err_t SpiMaster::otaTransfer(uint8_t type, const uint8_t *payload, size_t pa
|
||||
return otaPollResponseUnlocked(resp_type, resp_payload, resp_max, resp_len, timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 仅执行 SPI OTA 响应读取事务,不重复发送请求帧。
|
||||
* @param resp_type 输出响应类型。
|
||||
* @param resp_payload 输出响应负载缓冲区。
|
||||
* @param resp_max 响应负载缓冲区最大长度。
|
||||
* @param resp_len 输出实际响应长度。
|
||||
* @param timeout_ms 超时时间(毫秒)。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::otaPollResponse(uint8_t *resp_type, uint8_t *resp_payload,
|
||||
size_t resp_max, size_t *resp_len, uint32_t timeout_ms)
|
||||
{
|
||||
@@ -326,6 +433,15 @@ esp_err_t SpiMaster::otaPollResponse(uint8_t *resp_type, uint8_t *resp_payload,
|
||||
return otaPollResponseUnlocked(resp_type, resp_payload, resp_max, resp_len, timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 无锁版本的 OTA 响应轮询实现。
|
||||
* @param resp_type 输出响应类型。
|
||||
* @param resp_payload 输出响应负载缓冲区。
|
||||
* @param resp_max 响应负载缓冲区最大长度。
|
||||
* @param resp_len 输出实际响应长度。
|
||||
* @param timeout_ms 超时时间(毫秒)。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::otaPollResponseUnlocked(uint8_t *resp_type, uint8_t *resp_payload,
|
||||
size_t resp_max, size_t *resp_len, uint32_t timeout_ms)
|
||||
{
|
||||
@@ -390,6 +506,10 @@ esp_err_t SpiMaster::otaPollResponseUnlocked(uint8_t *resp_type, uint8_t *resp_p
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 通知 GD32 DCU_APP 设置 OTA 标志并软复位进入 Bootloader。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::appRequestBootloader()
|
||||
{
|
||||
LockGuard lock(m_mutex);
|
||||
@@ -429,6 +549,13 @@ esp_err_t SpiMaster::appRequestBootloader()
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向 GD32 APP 发送存活探测命令,等待 APP 回复 ALIVE ACK。
|
||||
* @param timeout_ms 超时时间(毫秒)。
|
||||
* @param uptime_sec 输出设备运行时间(秒)。
|
||||
* @param version 输出 GD32 版本信息(可选)。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::appPing(uint32_t timeout_ms, uint32_t *uptime_sec, gd32_version_t *version)
|
||||
{
|
||||
LockGuard lock(m_mutex);
|
||||
@@ -546,6 +673,16 @@ esp_err_t SpiMaster::appPing(uint32_t timeout_ms, uint32_t *uptime_sec, gd32_ver
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行带重试的 SPI 同步传输。
|
||||
* @param cmd 命令字节。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param payload_len 负载数据长度。
|
||||
* @param recv_buf 接收数据缓冲区。
|
||||
* @param recv_len 接收数据长度。
|
||||
* @param max_retries 最大重试次数。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::transferWithRetry(uint8_t cmd, const uint8_t *payload,
|
||||
size_t payload_len, uint8_t *recv_buf,
|
||||
size_t recv_len, int max_retries)
|
||||
@@ -579,6 +716,17 @@ esp_err_t SpiMaster::transferWithRetry(uint8_t cmd, const uint8_t *payload,
|
||||
// ============================================================
|
||||
// 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,
|
||||
@@ -608,6 +756,14 @@ int SpiMaster::canSend(can_channel_t channel, uint32_t ext_id,
|
||||
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)
|
||||
{
|
||||
@@ -638,6 +794,16 @@ int SpiMaster::canSendNoReply(can_channel_t channel, uint32_t ext_id,
|
||||
// ============================================================
|
||||
// 无锁内部版本实现
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 无锁版本的同步 SPI 传输实现。
|
||||
* @param cmd 命令字节。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param payload_len 负载数据长度。
|
||||
* @param recv_buf 接收数据缓冲区。
|
||||
* @param recv_len 接收数据长度。
|
||||
* @param timeout_ms 超时时间(毫秒)。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::syncTransferUnlocked(uint8_t cmd, const uint8_t *payload,
|
||||
size_t payload_len, uint8_t *recv_buf,
|
||||
size_t recv_len, uint32_t timeout_ms)
|
||||
@@ -645,6 +811,13 @@ esp_err_t SpiMaster::syncTransferUnlocked(uint8_t cmd, const uint8_t *payload,
|
||||
return transferRequestResponse(cmd, payload, payload_len, recv_buf, recv_len, timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 无锁版本的 SPI 发送实现。
|
||||
* @param cmd 命令字节。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param len 负载数据长度。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t SpiMaster::sendUnlocked(uint8_t cmd, const uint8_t *payload, size_t len)
|
||||
{
|
||||
return transferRequestResponse(cmd, payload, len, NULL, 0, 0U);
|
||||
|
||||
@@ -74,6 +74,10 @@ typedef struct {
|
||||
*/
|
||||
class SpiMaster {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取 SpiMaster 单例实例。
|
||||
* @return SpiMaster 单例引用。
|
||||
*/
|
||||
static SpiMaster& instance();
|
||||
|
||||
/** @brief 初始化 SPI2 总线与目标从设备。 */
|
||||
@@ -136,9 +140,21 @@ public:
|
||||
spi_device_handle_t deviceHandle() const { return m_spiDevice; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 私有默认构造函数。
|
||||
*/
|
||||
SpiMaster() = default;
|
||||
/**
|
||||
* @brief 私有析构函数。
|
||||
*/
|
||||
~SpiMaster() = default;
|
||||
/**
|
||||
* @brief 禁止复制构造。
|
||||
*/
|
||||
SpiMaster(const SpiMaster&) = delete;
|
||||
/**
|
||||
* @brief 禁止复制赋值。
|
||||
*/
|
||||
SpiMaster& operator=(const SpiMaster&) = delete;
|
||||
|
||||
spi_device_handle_t m_spiDevice = NULL;
|
||||
@@ -147,40 +163,132 @@ private:
|
||||
// RAII 锁守卫
|
||||
class LockGuard {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造加锁守卫,获取互斥锁。
|
||||
* @param mutex 互斥锁句柄。
|
||||
*/
|
||||
explicit LockGuard(SemaphoreHandle_t mutex) : m_lock(mutex) {
|
||||
if (m_lock) { xSemaphoreTake(m_lock, portMAX_DELAY); }
|
||||
}
|
||||
/**
|
||||
* @brief 析构解锁守卫,释放互斥锁。
|
||||
*/
|
||||
~LockGuard() {
|
||||
if (m_lock) { xSemaphoreGive(m_lock); }
|
||||
}
|
||||
/**
|
||||
* @brief 禁止复制构造。
|
||||
*/
|
||||
LockGuard(const LockGuard&) = delete;
|
||||
/**
|
||||
* @brief 禁止复制赋值。
|
||||
*/
|
||||
LockGuard& operator=(const LockGuard&) = delete;
|
||||
private:
|
||||
SemaphoreHandle_t m_lock;
|
||||
};
|
||||
|
||||
// 内部 SPI 传输
|
||||
/**
|
||||
* @brief 执行底层 SPI 时钟同步传输。
|
||||
* @param tx_buf 发送数据缓冲区。
|
||||
* @param tx_len 发送数据长度。
|
||||
* @param rx_buf 接收数据缓冲区。
|
||||
* @param rx_len 接收数据长度。
|
||||
* @param timeout_ms 超时时间(毫秒),0 表示无限等待。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t transferClocked(const uint8_t *tx_buf, size_t tx_len,
|
||||
uint8_t *rx_buf, size_t rx_len,
|
||||
uint32_t timeout_ms = 0U);
|
||||
/**
|
||||
* @brief 执行 SPI 请求-响应组合传输。
|
||||
* @param cmd 命令字节(0 表示无命令)。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param payload_len 负载数据长度。
|
||||
* @param rx_buf 接收数据缓冲区。
|
||||
* @param rx_len 期望接收的数据长度。
|
||||
* @param timeout_ms 超时时间(毫秒),0 表示无限等待。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t transferRequestResponse(uint8_t cmd, const uint8_t *payload, size_t payload_len,
|
||||
uint8_t *rx_buf, size_t rx_len,
|
||||
uint32_t timeout_ms = 0U);
|
||||
|
||||
|
||||
// 无锁内部版本(供已持锁的公共方法调用,避免递归死锁)
|
||||
/**
|
||||
* @brief 无锁版本的同步 SPI 传输(调用者须已持有锁)。
|
||||
* @param cmd 命令字节。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param payload_len 负载数据长度。
|
||||
* @param recv_buf 接收数据缓冲区。
|
||||
* @param recv_len 接收数据长度。
|
||||
* @param timeout_ms 超时时间(毫秒)。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t syncTransferUnlocked(uint8_t cmd, const uint8_t *payload,
|
||||
size_t payload_len, uint8_t *recv_buf,
|
||||
size_t recv_len, uint32_t timeout_ms);
|
||||
/**
|
||||
* @brief 无锁版本的 SPI 发送(调用者须已持有锁)。
|
||||
* @param cmd 命令字节。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param len 负载数据长度。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t sendUnlocked(uint8_t cmd, const uint8_t *payload, size_t len);
|
||||
/**
|
||||
* @brief 无锁版本的 OTA 响应轮询(调用者须已持有锁)。
|
||||
* @param resp_type 输出响应类型。
|
||||
* @param resp_payload 输出响应负载缓冲区。
|
||||
* @param resp_max 响应负载缓冲区最大长度。
|
||||
* @param resp_len 输出实际响应长度。
|
||||
* @param timeout_ms 超时时间(毫秒)。
|
||||
* @return ESP_OK 表示成功,否则返回错误码。
|
||||
*/
|
||||
esp_err_t otaPollResponseUnlocked(uint8_t *resp_type, uint8_t *resp_payload,
|
||||
size_t resp_max, size_t *resp_len, uint32_t timeout_ms);
|
||||
// OTA 帧工具(静态,无状态)
|
||||
/**
|
||||
* @brief 计算 OTA 帧的 8 位 CRC 校验和。
|
||||
* @param data 数据缓冲区。
|
||||
* @param len 数据长度。
|
||||
* @return 计算得到的 CRC 值。
|
||||
*/
|
||||
static uint8_t otaCrc8(const uint8_t *data, size_t len);
|
||||
/**
|
||||
* @brief 构建 OTA 协议帧。
|
||||
* @param type 帧类型。
|
||||
* @param payload 负载数据缓冲区。
|
||||
* @param payload_len 负载数据长度。
|
||||
* @param out 输出帧缓冲区。
|
||||
* @param out_len 输出实际帧长度。
|
||||
* @return true 表示构建成功,false 表示失败。
|
||||
*/
|
||||
static bool otaBuildFrame(uint8_t type, const uint8_t *payload, size_t payload_len,
|
||||
uint8_t *out, size_t *out_len);
|
||||
/**
|
||||
* @brief 解析 OTA 协议帧。
|
||||
* @param frame 帧数据缓冲区。
|
||||
* @param frame_len 帧数据长度。
|
||||
* @param type 输出解析得到的帧类型。
|
||||
* @param payload 输出指向负载数据的指针。
|
||||
* @param payload_len 输出负载数据长度。
|
||||
* @return true 表示解析成功,false 表示失败。
|
||||
*/
|
||||
static bool otaParseFrame(const uint8_t *frame, size_t frame_len,
|
||||
uint8_t *type, const uint8_t **payload, size_t *payload_len);
|
||||
/**
|
||||
* @brief 在数据缓冲区中搜索并解析 OTA 帧。
|
||||
* @param buf 数据缓冲区。
|
||||
* @param buf_len 数据缓冲区长度。
|
||||
* @param type 输出帧类型。
|
||||
* @param payload 输出指向负载数据的指针。
|
||||
* @param payload_len 输出负载数据长度。
|
||||
* @param frame_offset 输出帧在缓冲区中的偏移位置。
|
||||
* @return true 表示找到并成功解析,false 表示未找到。
|
||||
*/
|
||||
static bool otaFindFrame(const uint8_t *buf, size_t buf_len,
|
||||
uint8_t *type, const uint8_t **payload, size_t *payload_len,
|
||||
size_t *frame_offset);
|
||||
|
||||
Reference in New Issue
Block a user