refactor(arch): 精简架构去除样板代码并修复BLE OTA虚假ACK问题
- 移除OtaCoordinator中重复的状态查询锁操作,改为单次取值后比较 - 将GD32存储wrapper函数改为inline实现并移入头文件,删除对应的cpp实现 - HTTP路由注册改为路由表+循环注册,消除复制粘贴的URI注册代码 - OtaRelay事件分发中提取通用拒绝逻辑到commonReject helper函数 - SpiMaster中折叠加锁/无锁镜像函数,通过内部Impl实现统一路径 - HealthMonitor使用RAII LockGuard替代重复的mutex操作代码 - 修复BLE OTA中ESP32路径的虚假ACK问题,窗口边界ACK仅在实际写入时触发 - 修复BLE OTA中GD32路径的重复包ACK问题,重复包静默跳过不发送ACK - 调整发送端首轮ACK超时从3000ms提升至5000ms,适配设备端flash写入速度 - 清理临时文件和无用的配置宏定义 - 在main.cpp中添加GD32版本探测逻辑
This commit is contained in:
+12
-8
@@ -107,6 +107,16 @@ static void esp32_reorder_free();
|
||||
static void ble_ota_data_worker_task(void *pvParameters);
|
||||
static void ble_gd32_relay_wait_task(void *pvParameters);
|
||||
|
||||
// 清空 s_otaDataQueue 中的残留批次并释放其 data 指针。
|
||||
// 仅用于纯清理场景;带计数/日志的清理请保留内联实现。
|
||||
static void drainOtaDataQueue()
|
||||
{
|
||||
BleOtaDataBatch stale;
|
||||
while (xQueueReceive(s_otaDataQueue, &stale, 0) == pdTRUE) {
|
||||
if (stale.data) free(stale.data);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
/**
|
||||
* @brief 获取 BleOta 单例实例。
|
||||
@@ -1683,10 +1693,7 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg)
|
||||
|
||||
// ★ 断开时清空数据队列,丢弃属于旧会话的残留包
|
||||
if (s_otaDataQueue) {
|
||||
BleOtaDataBatch stale;
|
||||
while (xQueueReceive(s_otaDataQueue, &stale, 0) == pdTRUE) {
|
||||
free(stale.data);
|
||||
}
|
||||
drainOtaDataQueue();
|
||||
}
|
||||
|
||||
BleOta::instance().m_bleConnected = false;
|
||||
@@ -2018,10 +2025,7 @@ esp_err_t BleOta::suspendStack()
|
||||
m_suspendedForWifiOta = true;
|
||||
|
||||
if (s_otaDataQueue) {
|
||||
BleOtaDataBatch drop;
|
||||
while (xQueueReceive(s_otaDataQueue, &drop, 0) == pdTRUE) {
|
||||
free(drop.data);
|
||||
}
|
||||
drainOtaDataQueue();
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "esp32_ota_core.h"
|
||||
#include "ota_coordinator.h"
|
||||
#include "ota_relay.h"
|
||||
#include "ota_snapshot.h"
|
||||
#include "storage/gd32_flash_store.h"
|
||||
#include "health_monitor.h"
|
||||
#include "monitor/debug_log.h"
|
||||
@@ -35,6 +34,25 @@
|
||||
#include "monitor/task_stack_check.h"
|
||||
#include "ble_ota.h"
|
||||
|
||||
static size_t parseHexToBytes(const char *hex, uint8_t *out, size_t max_out)
|
||||
{
|
||||
size_t hex_len = strlen(hex);
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i + 1 < hex_len && n < max_out; i += 2) {
|
||||
char byte_str[3] = { hex[i], hex[i + 1], 0 };
|
||||
out[n++] = (uint8_t)strtol(byte_str, NULL, 16);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static void bytesToHexUpper(const uint8_t *in, size_t n, char *out)
|
||||
{
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
snprintf(out + i * 2, 3, "%02X", in[i]);
|
||||
}
|
||||
out[n * 2] = '\0';
|
||||
}
|
||||
|
||||
void http_set_cors_headers(httpd_req_t *req)
|
||||
{
|
||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||
@@ -761,13 +779,7 @@ esp_err_t can_send_handler(httpd_req_t *req)
|
||||
uint8_t tx_dlc = 0;
|
||||
|
||||
if (data_item && cJSON_IsString(data_item)) {
|
||||
const char *hex_str = cJSON_GetStringValue(data_item);
|
||||
size_t hex_len = strlen(hex_str);
|
||||
for (size_t i = 0; i < hex_len / 2 && i < CAN_MAX_DLC; i++) {
|
||||
char byte_str[3] = { hex_str[i * 2], hex_str[i * 2 + 1], '\0' };
|
||||
tx_data[i] = (uint8_t)strtol(byte_str, NULL, 16);
|
||||
tx_dlc++;
|
||||
}
|
||||
tx_dlc = (uint8_t)parseHexToBytes(cJSON_GetStringValue(data_item), tx_data, CAN_MAX_DLC);
|
||||
}
|
||||
|
||||
if (tx_dlc == 0 && dlc > 0) {
|
||||
@@ -783,9 +795,7 @@ esp_err_t can_send_handler(httpd_req_t *req)
|
||||
char resp[512];
|
||||
if (resp_len >= 0) {
|
||||
char resp_hex[CAN_MAX_DLC * 2 + 1] = {0};
|
||||
for (int i = 0; i < resp_len; i++) {
|
||||
sprintf(&resp_hex[i * 2], "%02X", resp_data[i]);
|
||||
}
|
||||
bytesToHexUpper(resp_data, (size_t)resp_len, resp_hex);
|
||||
snprintf(resp, sizeof(resp),
|
||||
"{\"status\": \"ok\", \"ch\": %d, \"id\": %lu, \"sent\": %d, \"recv_len\": %d, \"recv\": \"%s\"}",
|
||||
ch, (unsigned long)ext_id, tx_dlc, resp_len, resp_hex);
|
||||
@@ -834,13 +844,7 @@ esp_err_t spi_send_handler(httpd_req_t *req)
|
||||
uint8_t payload_data[SPI_OTA_FRAME_MAX] = {0};
|
||||
size_t payload_len = 0;
|
||||
if (data_item && cJSON_IsString(data_item)) {
|
||||
const char *hex_str = cJSON_GetStringValue(data_item);
|
||||
size_t hex_len = strlen(hex_str);
|
||||
for (size_t i = 0; i < hex_len / 2 && i < SPI_OTA_FRAME_MAX; i++) {
|
||||
char byte_str[3] = { hex_str[i * 2], hex_str[i * 2 + 1], '\0' };
|
||||
payload_data[i] = (uint8_t)strtol(byte_str, NULL, 16);
|
||||
payload_len++;
|
||||
}
|
||||
payload_len = parseHexToBytes(cJSON_GetStringValue(data_item), payload_data, SPI_OTA_FRAME_MAX);
|
||||
}
|
||||
|
||||
DEBUG_INFO("SPI", "WEB SPI: type=0x%02X, payload=%zu 字节",
|
||||
@@ -857,9 +861,7 @@ esp_err_t spi_send_handler(httpd_req_t *req)
|
||||
|
||||
if (spi_err == ESP_OK) {
|
||||
char resp_hex[SPI_OTA_FRAME_MAX * 2 + 1] = {0};
|
||||
for (size_t i = 0; i < resp_len; i++) {
|
||||
sprintf(&resp_hex[i * 2], "%02X", resp_payload[i]);
|
||||
}
|
||||
bytesToHexUpper(resp_payload, resp_len, resp_hex);
|
||||
snprintf(resp, sizeof(resp),
|
||||
"{\"status\": \"ok\", \"type\": \"%02X\", \"payload_len\": %zu, \"resp_type\": \"%02X\", \"resp_len\": %zu, \"recv\": \"%s\"}",
|
||||
frame_type, payload_len, resp_type, resp_len, resp_hex);
|
||||
|
||||
+28
-54
@@ -33,6 +33,28 @@
|
||||
#include "http_handlers.h"
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// 文件作用域 static helper
|
||||
// ----------------------------------------
|
||||
/**
|
||||
* @brief 清除指定 NVS 命名空间下的全部键(erase_all + commit + close)。
|
||||
* 纯函数,内部不打日志,由调用方负责日志输出。
|
||||
* @param ns NVS 命名空间字符串(例如 "nvs.net80211"、"phy")。
|
||||
* @return ESP_OK 成功;否则返回 nvs_open 的错误码。
|
||||
*/
|
||||
static esp_err_t clearNvsNamespace(const char *ns)
|
||||
{
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open(ns, NVS_READWRITE, &h);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
nvs_erase_all(h);
|
||||
nvs_commit(h);
|
||||
nvs_close(h);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
/**
|
||||
@@ -45,10 +67,6 @@ WifiOta& WifiOta::instance()
|
||||
return inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 预先声明:为 STA 模式配置静态 IP、网关和子网掩码。
|
||||
*/
|
||||
|
||||
// ----------------------------------------
|
||||
// STA 后台重连任务(避免在系统事件回调中调用 esp_wifi_connect 死锁)
|
||||
// ----------------------------------------
|
||||
@@ -150,9 +168,6 @@ void WifiOta::wifiEventHandler(void* arg, esp_event_base_t event_base,
|
||||
inst.stateUnlock();
|
||||
// ★ M2: 推送 WiFi 连接状态给 HealthMonitor(解耦监控层主动查询)
|
||||
HealthMonitor::instance().setWifiConnected(true);
|
||||
if (inst.m_staConnectedSem) {
|
||||
xSemaphoreGive(inst.m_staConnectedSem);
|
||||
}
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_STA_DISCONNECTED: {
|
||||
@@ -298,8 +313,6 @@ esp_err_t WifiOta::initSta()
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||
|
||||
setStaticIp();
|
||||
|
||||
// ★ 冷启动时在 wifi_start 前增加延迟
|
||||
// 长时间断电后首次上电,WiFi PHY 校准需要额外时间
|
||||
// esp_wifi_start() 内部会触发 PHY 初始化和校准
|
||||
@@ -325,8 +338,8 @@ esp_err_t WifiOta::initSta()
|
||||
*/
|
||||
esp_err_t WifiOta::waitStaConnected(char *ip_str, size_t ip_len)
|
||||
{
|
||||
SemaphoreHandle_t wait_sem = WIFI_STA_USE_STATIC_IP ? WifiOta::instance().m_staConnectedSem : WifiOta::instance().m_staGotIpSem;
|
||||
const char *wait_name = WIFI_STA_USE_STATIC_IP ? "STA_CONNECTED" : "STA_GOT_IP";
|
||||
SemaphoreHandle_t wait_sem = WifiOta::instance().m_staGotIpSem;
|
||||
const char *wait_name = "STA_GOT_IP";
|
||||
|
||||
if (wait_sem == NULL) {
|
||||
DEBUG_ERR("WIFI", "%s 信号量未创建", wait_name);
|
||||
@@ -368,37 +381,6 @@ esp_err_t WifiOta::waitStaConnected(char *ip_str, size_t ip_len)
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置 STA 静态 IP
|
||||
*/
|
||||
void WifiOta::setStaticIp()
|
||||
{
|
||||
if (WIFI_STA_USE_STATIC_IP) {
|
||||
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (sta_netif) {
|
||||
DEBUG_INFO("WIFI", "STA 使用静态 IP 模式,停止 DHCP Client");
|
||||
ESP_ERROR_CHECK(esp_netif_dhcpc_stop(sta_netif));
|
||||
|
||||
esp_netif_ip_info_t info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
inet_pton(AF_INET, WIFI_STA_STATIC_IP, &info.ip);
|
||||
inet_pton(AF_INET, WIFI_STA_STATIC_GW, &info.gw);
|
||||
inet_pton(AF_INET, WIFI_STA_STATIC_NETMASK, &info.netmask);
|
||||
ESP_ERROR_CHECK(esp_netif_set_ip_info(sta_netif, &info));
|
||||
DEBUG_INFO("WIFI", "静态 IP 已设置: %s", WIFI_STA_STATIC_IP);
|
||||
|
||||
// 设置 DNS
|
||||
esp_netif_dns_info_t dns_info;
|
||||
memset(&dns_info, 0, sizeof(dns_info));
|
||||
inet_pton(AF_INET, WIFI_STA_STATIC_DNS, &dns_info.ip.u_addr.ip4.addr);
|
||||
ESP_ERROR_CHECK(esp_netif_set_dns_info(sta_netif, ESP_NETIF_DNS_MAIN, &dns_info));
|
||||
DEBUG_INFO("WIFI", "DNS 已设置: %s", WIFI_STA_STATIC_DNS);
|
||||
}
|
||||
} else {
|
||||
DEBUG_INFO("WIFI", "使用 DHCP 获取 IP");
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// ESP32 OTA 内部函数
|
||||
// 注:begin/write/finalize/abort 已抽离到 Esp32OtaCore(main/ota/esp32_ota_core.{h,cpp})
|
||||
@@ -587,23 +569,16 @@ bool WifiOta::earlyInit()
|
||||
// ★ 冷启动时强制清除 WiFi NVS 缓存,避免残留脏数据
|
||||
// 长时间断电后 NVS 中的 WiFi 配置缓存可能不一致
|
||||
if (WifiOta::instance().m_coldBoot) {
|
||||
nvs_handle_t nvs_handle;
|
||||
err = nvs_open("nvs.net80211", NVS_READWRITE, &nvs_handle);
|
||||
// 清除 WiFi 驱动内部 NVS 缓存键
|
||||
err = clearNvsNamespace("nvs.net80211");
|
||||
if (err == ESP_OK) {
|
||||
// 清除 WiFi 驱动内部 NVS 缓存键
|
||||
nvs_erase_all(nvs_handle);
|
||||
nvs_commit(nvs_handle);
|
||||
nvs_close(nvs_handle);
|
||||
DEBUG_DBG("WIFI", "冷启动: 已清除 WiFi NVS 缓存 (nvs.net80211)");
|
||||
} else {
|
||||
DEBUG_INFO("WIFI", "冷启动: 无 WiFi NVS 缓存分区,跳过清除");
|
||||
}
|
||||
// 同样清除 PHY 校准数据分区,强制下次全量校准
|
||||
err = nvs_open("phy", NVS_READWRITE, &nvs_handle);
|
||||
err = clearNvsNamespace("phy");
|
||||
if (err == ESP_OK) {
|
||||
nvs_erase_all(nvs_handle);
|
||||
nvs_commit(nvs_handle);
|
||||
nvs_close(nvs_handle);
|
||||
DEBUG_DBG("WIFI", "冷启动: 已清除 PHY 校准缓存,将重新校准");
|
||||
}
|
||||
}
|
||||
@@ -620,9 +595,8 @@ bool WifiOta::earlyInit()
|
||||
}
|
||||
}
|
||||
|
||||
WifiOta::instance().m_staConnectedSem = xSemaphoreCreateBinary();
|
||||
WifiOta::instance().m_staGotIpSem = xSemaphoreCreateBinary();
|
||||
if (!WifiOta::instance().m_staConnectedSem || !WifiOta::instance().m_staGotIpSem) {
|
||||
if (!WifiOta::instance().m_staGotIpSem) {
|
||||
DEBUG_ERR("WIFI", "创建信号量失败");
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ public:
|
||||
// ── 初始化/运行时状态(仅 OTA 任务访问,无需额外锁)──
|
||||
ota_wifi_mode_t m_wifiMode = OTA_WIFI_MODE_AP; ///< 当前 WiFi 工作模式
|
||||
bool m_coldBoot = false; ///< 是否为冷启动(首次上电)
|
||||
SemaphoreHandle_t m_staConnectedSem = NULL; ///< STA 连接成功信号量
|
||||
SemaphoreHandle_t m_staGotIpSem = NULL; ///< STA 获取 IP 信号量
|
||||
TaskHandle_t m_reconnectTask = nullptr; ///< STA 后台重连任务句柄
|
||||
|
||||
@@ -117,10 +116,6 @@ private:
|
||||
* @return ESP_OK 成功,其他失败。
|
||||
*/
|
||||
esp_err_t initSta();
|
||||
/**
|
||||
* @brief 设置 STA 静态 IP、网关和子网掩码。
|
||||
*/
|
||||
void setStaticIp();
|
||||
/**
|
||||
* @brief WiFi/IP 事件回调,处理连接、断开和 IP 获取。
|
||||
* @param arg 回调用户参数。
|
||||
|
||||
Reference in New Issue
Block a user