Files
ESP32_COMM/main/server/wifi_ota.cpp
T
小龙虾 5a89cec34b 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版本探测逻辑
2026-07-15 02:23:25 +08:00

785 lines
30 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.
/*
* wifi_ota.cpp - WiFi OTA 服务器实现
* WifiOta 类封装 WiFi 初始化、HTTP OTA 服务器和网络状态查询。
* T4.1 - T4.5
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_http_server.h"
#include "esp_system.h"
#include "esp_chip_info.h"
#include "esp_ota_ops.h"
#include "nvs_flash.h"
#include "lwip/netdb.h"
#include "esp_netif.h"
#include "esp_netif_types.h"
#include "esp_event.h"
#include "esp_mac.h"
#include "app_config.h"
#include "wifi_ota.h"
#include "esp32_ota_core.h"
#include "ota_coordinator.h"
#include "ota_relay.h"
#include "health_monitor.h"
#include "monitor/debug_log.h"
#include "spi_master.h"
#include "ble_ota.h"
#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;
}
// ============================================================
/**
* @brief 获取 WifiOta 单例实例(静态局部变量实现)。
* @return WifiOta 单例引用。
*/
WifiOta& WifiOta::instance()
{
static WifiOta inst;
return inst;
}
// ----------------------------------------
// STA 后台重连任务(避免在系统事件回调中调用 esp_wifi_connect 死锁)
// ----------------------------------------
/**
* @brief 独立 FreeRTOS 任务,处理 STA 连接/重连的退避延迟和 esp_wifi_connect() 调用。
* 事件回调仅通过 xTaskNotifyGive 唤醒本任务,不直接在系统事件任务中阻塞或调用 WiFi API。
* @param pvParameters FreeRTOS 任务参数(未使用)。
*/
void WifiOta::reconnectTask(void* pvParameters)
{
(void)pvParameters;
while (1) {
// 等待事件回调通知(阻塞,不消耗 CPU)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
WifiOta& self = WifiOta::instance();
// 已连接或已放弃,无需处理
if (self.getStaConnected() || self.getStaRetryExhausted()) {
continue;
}
self.stateLock();
// 达到最大重试次数
if (self.getStaRetryCount() >= WIFI_STA_MAX_RETRY) {
DEBUG_ERR("WIFI", "STA 重连失败 %d 次,放弃", self.getStaRetryCount());
self.setStaRetryExhausted(true);
self.stateUnlock();
continue;
}
self.setStaRetryCount(self.getStaRetryCount() + 1);
int retryCount = self.getStaRetryCount();
// 计算退避延迟:冷启动前 3 次递增退避,其余固定 1s
int delay_ms = 0;
if (self.m_coldBoot && retryCount <= 3) {
delay_ms = 2000 * retryCount; // 2s, 4s, 6s
DEBUG_INFO("WIFI", "冷启动退避重连第 %d 次 (等 %dms)",
retryCount, delay_ms);
} else if (retryCount > 1) {
delay_ms = 1000; // 普通重连 1s 延迟
DEBUG_INFO("WIFI", "重连第 %d 次...", retryCount);
} else {
DEBUG_INFO("WIFI", "首次连接...");
}
self.stateUnlock();
if (delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(delay_ms));
}
// 延迟后再次检查状态,防止期间已连接或已放弃
if (!self.getStaConnected() && !self.getStaRetryExhausted()) {
esp_err_t err = esp_wifi_connect();
if (err != ESP_OK) {
DEBUG_ERR("WIFI", "esp_wifi_connect() 失败: %s", esp_err_to_name(err));
}
}
}
}
// ----------------------------------------
// WiFi/IP 事件回调(事件驱动连接)
// ----------------------------------------
/**
* @brief WiFi/IP 事件回调,处理 STA 启停、重连和获取 IP 等状态变化。
* @param arg 回调用户参数。
* @param event_base 事件基类(WIFI_EVENT 或 IP_EVENT)。
* @param event_id 具体事件 ID。
* @param event_data 事件附带数据。
*/
void WifiOta::wifiEventHandler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
WifiOta& inst = WifiOta::instance();
if (event_base == WIFI_EVENT) {
switch (event_id) {
case WIFI_EVENT_STA_START:
DEBUG_INFO("WIFI", "STA 已启动,开始连接...");
inst.stateLock();
inst.setStaRetryCount(0);
inst.setStaRetryExhausted(false);
inst.stateUnlock();
if (inst.m_reconnectTask) {
xTaskNotifyGive(inst.m_reconnectTask);
}
break;
case WIFI_EVENT_STA_CONNECTED:
DEBUG_INFO("WIFI", "STA 已关联到 AP");
inst.stateLock();
inst.setStaConnected(true);
inst.setStaRetryCount(0);
inst.setStaRetryExhausted(false); // ★ 连接成功,重置耗尽标志
inst.stateUnlock();
// ★ M2: 推送 WiFi 连接状态给 HealthMonitor(解耦监控层主动查询)
HealthMonitor::instance().setWifiConnected(true);
break;
case WIFI_EVENT_STA_DISCONNECTED: {
wifi_event_sta_disconnected_t* disconn = (wifi_event_sta_disconnected_t*)event_data;
inst.stateLock();
inst.setStaConnected(false);
bool exhausted = inst.getStaRetryExhausted();
int retryCount = inst.getStaRetryCount();
inst.stateUnlock();
// ★ M2: 推送 WiFi 断开状态给 HealthMonitor
HealthMonitor::instance().setWifiConnected(false);
if (!exhausted) {
DEBUG_WARN("WIFI", "STA 断开, reason=%d, retry=%d",
disconn->reason, retryCount);
if (inst.m_reconnectTask) {
xTaskNotifyGive(inst.m_reconnectTask);
}
} else {
DEBUG_WARN("WIFI", "STA 断开, 重试次数已耗尽");
}
break;
}
case WIFI_EVENT_AP_STACONNECTED: {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*)event_data;
DEBUG_INFO("WIFI", "AP: 客户端 " MACSTR " 连入, AID=%d", MAC2STR(event->mac), event->aid);
break;
}
case WIFI_EVENT_AP_STADISCONNECTED: {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*)event_data;
DEBUG_INFO("WIFI", "AP: 客户端 " MACSTR " 离开, AID=%d, reason=%d",
MAC2STR(event->mac), event->aid, event->reason);
break;
}
default:
break;
}
} else if (event_base == IP_EVENT) {
switch (event_id) {
case IP_EVENT_STA_GOT_IP: {
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
DEBUG_INFO("WIFI", "STA 获取 IP: " IPSTR, IP2STR(&event->ip_info.ip));
if (WifiOta::instance().m_staGotIpSem) {
xSemaphoreGive(WifiOta::instance().m_staGotIpSem);
}
break;
}
default:
break;
}
}
}
// ----------------------------------------
// WiFi AP 模式初始化
/**
* @brief 初始化 SoftAP 模式,供局域网 OTA 使用。
*/
void WifiOta::initAp()
{
DEBUG_INFO("WIFI", "=== 初始化 WiFi AP 模式 ===");
WifiOta::instance().m_wifiMode = OTA_WIFI_MODE_AP;
esp_netif_t *ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
if (ap_netif == NULL) {
ap_netif = esp_netif_create_default_wifi_ap();
}
if (ap_netif != NULL) {
esp_netif_dhcps_stop(ap_netif);
esp_netif_ip_info_t ip_info;
memset(&ip_info, 0, sizeof(ip_info));
// 方案 A:SoftAP 使用 192.168.31.0/24 网段
inet_pton(AF_INET, "192.168.31.1", &ip_info.ip);
inet_pton(AF_INET, "192.168.31.1", &ip_info.gw);
inet_pton(AF_INET, "255.255.255.0", &ip_info.netmask);
ESP_ERROR_CHECK(esp_netif_set_ip_info(ap_netif, &ip_info));
esp_netif_dhcps_start(ap_netif);
DEBUG_INFO("WIFI", "AP IP 已设置: 192.168.31.1");
}
wifi_config_t wifi_config = {};
memcpy(wifi_config.ap.ssid, WIFI_AP_SSID, strlen(WIFI_AP_SSID) + 1);
wifi_config.ap.ssid_len = strlen(WIFI_AP_SSID);
memcpy(wifi_config.ap.password, WIFI_AP_PASSWORD, strlen(WIFI_AP_PASSWORD) + 1);
wifi_config.ap.max_connection = WIFI_AP_MAX_CONNECT;
wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
wifi_config.ap.channel = WIFI_AP_CHANNEL;
DEBUG_INFO("WIFI", "AP SSID: %s, Channel: %d", WIFI_AP_SSID, WIFI_AP_CHANNEL);
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
DEBUG_INFO("WIFI", "WiFi AP 已启动: %s", WIFI_AP_SSID);
DEBUG_INFO("WIFI", "AP IP 地址: 192.168.31.1");
DEBUG_INFO("WIFI", "AP 网关地址: 192.168.31.1");
DEBUG_INFO("WIFI", "最大连接数: %d", WIFI_AP_MAX_CONNECT);
}
// ----------------------------------------
// WiFi STA 模式初始化
/**
* @brief 初始化 STA 模式并连接预配置的路由器。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t WifiOta::initSta()
{
DEBUG_INFO("WIFI", "=== 初始化 WiFi STA 模式 ===");
WifiOta::instance().m_wifiMode = OTA_WIFI_MODE_STA;
WifiOta::instance().setStaRetryCount(0);
WifiOta::instance().setStaRetryExhausted(false);
// ★ 设置国家代码:中国,允许信道 1-13
// ESP32 默认国家是 US(只允许 1-11),中国路由可能用 12/13
wifi_country_t country = {};
memcpy(country.cc, "CN", 3);
country.schan = 1;
country.nchan = 13;
country.policy = WIFI_COUNTRY_POLICY_MANUAL;
wifi_config_t wifi_config = {};
wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
wifi_config.sta.pmf_cfg.capable = true;
wifi_config.sta.pmf_cfg.required = false;
wifi_config.sta.listen_interval = 3;
memcpy(wifi_config.sta.ssid, WIFI_STA_SSID, strlen(WIFI_STA_SSID) + 1);
memcpy(wifi_config.sta.password, WIFI_STA_PASSWORD, strlen(WIFI_STA_PASSWORD) + 1);
DEBUG_INFO("WIFI", "STA SSID: %s (全信道扫描, CN 国家代码)", WIFI_STA_SSID);
ESP_ERROR_CHECK(esp_wifi_set_country(&country));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
// ★ 冷启动时在 wifi_start 前增加延迟
// 长时间断电后首次上电,WiFi PHY 校准需要额外时间
// esp_wifi_start() 内部会触发 PHY 初始化和校准
// 如果校准未完成就启动射频,可能导致连接失败
if (WifiOta::instance().m_coldBoot) {
DEBUG_INFO("WIFI", "冷启动: 等待 PHY 就绪...");
vTaskDelay(pdMS_TO_TICKS(500));
}
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
DEBUG_INFO("WIFI", "WiFi STA 启动, SSID: %s, 事件驱动连接...", WIFI_STA_SSID);
return ESP_OK;
}
/**
* @brief 等待 STA 连接成功并获取 IP(静态 IP 等 CONNECTED,DHCP 等 GOT_IP)
* @param ip_str 输出缓冲区,存放获取到的 IP 字符串。
* @param ip_len 缓冲区长度。
* @return ESP_OK 连接成功,其他失败。
*/
esp_err_t WifiOta::waitStaConnected(char *ip_str, size_t ip_len)
{
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);
return ESP_ERR_INVALID_STATE;
}
// ★ 使用轮询等待,检查 WifiOta::instance().m_staRetryExhausted 标志加速退出
// 不再一次性等完整超时,而是每秒检查一次重试耗尽状态
TickType_t timeout_ticks = pdMS_TO_TICKS(WIFI_STA_CONNECT_TIMEOUT_MS);
TickType_t start_tick = xTaskGetTickCount();
TickType_t deadline = start_tick + timeout_ticks;
while (xTaskGetTickCount() < deadline) {
// 每次最多等 1 秒
TickType_t wait_until = xTaskGetTickCount() + pdMS_TO_TICKS(1000);
if (wait_until > deadline) wait_until = deadline;
if (xSemaphoreTake(wait_sem, wait_until - xTaskGetTickCount()) == pdTRUE) {
esp_netif_ip_info_t ip_info;
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif != NULL && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) {
if (ip_str) {
snprintf(ip_str, ip_len, IPSTR, IP2STR(&ip_info.ip));
}
DEBUG_INFO("WIFI", "%s 等待成功, 当前 IP: " IPSTR, wait_name, IP2STR(&ip_info.ip));
}
WifiOta::instance().setStaConnected(true);
return ESP_OK;
}
// ★ 重试已耗尽,不再等待
if (WifiOta::instance().getStaRetryExhausted()) {
DEBUG_ERR("WIFI", "STA 重试已耗尽 (%d 次),立即切换 AP", WifiOta::instance().getStaRetryCount());
return ESP_ERR_TIMEOUT;
}
}
DEBUG_ERR("WIFI", "等待 %s 超时 (%d ms), 重连次数: %d", wait_name, WIFI_STA_CONNECT_TIMEOUT_MS, WifiOta::instance().getStaRetryCount());
return ESP_ERR_TIMEOUT;
}
// ----------------------------------------
// ESP32 OTA 内部函数
// 注:begin/write/finalize/abort 已抽离到 Esp32OtaCore(main/ota/esp32_ota_core.{h,cpp})
// WiFi 与 BLE 通道共用同一核心,本文件仅负责 HTTP 传输层。
// ----------------------------------------
// 启动 HTTP OTA 服务器
/**
* @brief 启动 HTTP OTA 服务器,注册各接口路由。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t WifiOta::httpServerStart()
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = HTTP_SERVER_PORT;
config.stack_size = HTTP_SERVER_STACK_SIZE;
config.max_uri_handlers = 32; // 现有业务端点 + 对应 OPTIONS 预检
/* LWIP_MAX_SOCKETS=12;HTTP 内部约 3 + UDP 日志 1 → 业务 socket 不宜过大。
* 上传期间前端必须停 status/logs(见 app.js pausePollingForUpload)。 */
config.max_open_sockets = 4;
/* 上传中途若再开 status 连接,LRU 会踢掉 /ota/upload → 浏览器卡在 100%。
* 关闭 LRU 踢连接:宁可新连接失败,也不拆正在写 Flash 的长传。 */
config.lru_purge_enable = false;
/* 1MB sequential 擦写时,httpd 在 write 阶段可能 >60s 不调 recv;
* 超时后 handler 内会重试,但过短会放大“卡 100%”观感。 */
config.recv_wait_timeout = 120;
config.send_wait_timeout = 60;
DEBUG_INFO("WIFI", "=== 启动 HTTP OTA 服务器 ===");
DEBUG_INFO("WIFI", "HTTP 服务器端口: %d", config.server_port);
esp_err_t err = httpd_start(&WifiOta::instance().m_httpServer, &config);
if (err != ESP_OK) {
DEBUG_ERR("WIFI", "HTTP 服务器启动失败: %s", esp_err_to_name(err));
return err;
}
// GD32 固件就绪真相源为 OtaCoordinator / NVS meta(/system/status),
// 不再在 WifiOta 上维护影子 storage_done 标志。
{
ota_meta_t boot_meta;
if (OtaCoordinator::instance().loadGd32Meta(&boot_meta) == ESP_OK &&
boot_meta.total_size > 0 && boot_meta.stored_size >= boot_meta.total_size) {
DEBUG_INFO("WIFI", "从 NVS 恢复: GD32 固件已就绪 (%u bytes)", boot_meta.total_size);
}
}
struct UriEntry {
const char *uri;
httpd_method_t method;
esp_err_t (*handler)(httpd_req_t *);
};
static const UriEntry kHttpRoutes[] = {
{"/ota/upload", HTTP_POST, ota_upload_handler},
{"/ota/relay", HTTP_POST, ota_relay_trigger_handler},
{"/ota/update", HTTP_POST, esp32_ota_update_handler},
{"/restart", HTTP_POST, restart_handler},
{"/logs", HTTP_GET, logs_handler},
{"/system/status", HTTP_GET, system_status_handler},
{"/system/probe", HTTP_GET, system_probe_handler},
{"/can/send", HTTP_POST, can_send_handler},
{"/can/feedback", HTTP_GET, can_feedback_handler},
{"/can/mode", HTTP_GET, can_mode_handler},
{"/can/mode", HTTP_POST, can_mode_handler},
{"/spi/send", HTTP_POST, spi_send_handler},
};
for (const auto &r : kHttpRoutes) {
httpd_uri_t main_uri = {};
main_uri.uri = r.uri;
main_uri.method = r.method;
main_uri.handler = r.handler;
main_uri.user_ctx = NULL;
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &main_uri);
httpd_uri_t opts_uri = {};
opts_uri.uri = r.uri;
opts_uri.method = HTTP_OPTIONS;
opts_uri.handler = http_options_handler;
opts_uri.user_ctx = NULL;
httpd_register_uri_handler(WifiOta::instance().m_httpServer, &opts_uri);
}
DEBUG_INFO("WIFI", "注册端点: /ota/upload, /ota/relay, /ota/update, /restart, /logs, /system/status, /system/probe, /can/send, /spi/send, /can/feedback, /can/mode");
return ESP_OK;
}
// ----------------------------------------
// 公开 API
/**
* @brief 获取当前 WiFi 工作模式(AP 或 STA)。
* @return 当前 WiFi 模式枚举值。
*/
ota_wifi_mode_t WifiOta::getMode()
{
return WifiOta::instance().m_wifiMode;
}
/**
* @brief 获取当前 WiFi 信号强度。
* @return 信号强度(dBm),未连接时返回无效值。
*/
int8_t WifiOta::getRssi()
{
if (WifiOta::instance().m_wifiMode != OTA_WIFI_MODE_STA || !WifiOta::instance().getStaConnected()) {
return -127;
}
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
return ap_info.rssi;
}
return -127;
}
/**
* @brief 判断 STA 是否已连接到上级 WiFi。
* @return true 已连接,false 未连接。
*/
bool WifiOta::isConnected()
{
return WifiOta::instance().getStaConnected();
}
// ============================================================
// IBleCoexListener 接口实现(L1: 通过协调器接收 BLE 共存通知)
// ============================================================
void WifiOta::onBleOtaStart()
{
setBleOtaActive(true);
}
void WifiOta::onBleOtaEnd()
{
setBleOtaActive(false);
}
// ----------------------------------------
// ★ WiFi 早期初始化(在 app_main 调度器启动前调用)
// 包括:NVS → netif → event handler → WiFi driver → 配置 → connect
// 返回:true = STA 模式已启动(正在连接),false = 需要切 AP
/**
* @brief 在调度器启动前初始化 WiFi 并尝试 STA 连接。
* @return true STA 初始化中,false 需回退 AP。
*/
bool WifiOta::earlyInit()
{
DEBUG_INFO("WIFI", "=== WiFi 早期初始化 (调度器启动前) ===");
// ★ 冷启动检测:判断是否为上电复位
esp_reset_reason_t reset_reason = esp_reset_reason();
WifiOta::instance().m_coldBoot = (reset_reason == ESP_RST_POWERON || reset_reason == ESP_RST_BROWNOUT);
DEBUG_INFO("WIFI", "复位原因: %d (%s), 冷启动: %s",
reset_reason,
(reset_reason == ESP_RST_POWERON) ? "POWER_ON" :
(reset_reason == ESP_RST_BROWNOUT) ? "BROWNOUT" :
(reset_reason == ESP_RST_SW) ? "SOFTWARE" :
(reset_reason == ESP_RST_PANIC) ? "PANIC" :
(reset_reason == ESP_RST_INT_WDT) ? "INT_WDT" :
(reset_reason == ESP_RST_TASK_WDT) ? "TASK_WDT" :
(reset_reason == ESP_RST_DEEPSLEEP) ? "DEEP_SLEEP" :
"OTHER",
WifiOta::instance().m_coldBoot ? "是" : "否");
esp_err_t err = nvs_flash_init();
bool nvs_erased = false;
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// NVS 分区损坏或版本不匹配,擦除后重试
DEBUG_WARN("WIFI", "NVS 分区异常 (%s),擦除后重试", esp_err_to_name(err));
ESP_ERROR_CHECK(nvs_flash_erase());
nvs_erased = true;
err = nvs_flash_init();
}
if (err != ESP_OK) {
DEBUG_ERR("WIFI", "NVS 初始化失败: %s", esp_err_to_name(err));
} else {
if (nvs_erased) {
DEBUG_WARN("WIFI", "NVS 已擦除重建,WiFi PHY 校准数据已清除,将重新校准");
}
DEBUG_INFO("WIFI", "NVS 初始化成功");
}
// ★ 冷启动时强制清除 WiFi NVS 缓存,避免残留脏数据
// 长时间断电后 NVS 中的 WiFi 配置缓存可能不一致
if (WifiOta::instance().m_coldBoot) {
// 清除 WiFi 驱动内部 NVS 缓存键
err = clearNvsNamespace("nvs.net80211");
if (err == ESP_OK) {
DEBUG_DBG("WIFI", "冷启动: 已清除 WiFi NVS 缓存 (nvs.net80211)");
} else {
DEBUG_INFO("WIFI", "冷启动: 无 WiFi NVS 缓存分区,跳过清除");
}
// 同样清除 PHY 校准数据分区,强制下次全量校准
err = clearNvsNamespace("phy");
if (err == ESP_OK) {
DEBUG_DBG("WIFI", "冷启动: 已清除 PHY 校准缓存,将重新校准");
}
}
// ★ 创建状态互斥锁(保护 WiFi 连接状态、OTA 中继标志等跨任务共享变量)
if (WifiOta::instance().getStateMutex() == NULL) {
SemaphoreHandle_t mtx = xSemaphoreCreateMutex();
if (mtx) {
// 通过临时获取再赋值(私有成员在同类内可访问)
WifiOta::instance().m_stateMutex = mtx;
DEBUG_DBG("WIFI", "状态互斥锁已创建");
} else {
DEBUG_ERR("WIFI", "创建状态互斥锁失败");
}
}
WifiOta::instance().m_staGotIpSem = xSemaphoreCreateBinary();
if (!WifiOta::instance().m_staGotIpSem) {
DEBUG_ERR("WIFI", "创建信号量失败");
}
// 创建后台重连任务:避免在系统事件任务中调用 esp_wifi_connect()/vTaskDelay 导致死锁
if (WifiOta::instance().m_reconnectTask == nullptr) {
BaseType_t ret = xTaskCreate(&WifiOta::reconnectTask, "wifi_reconnect",
4096, NULL, 5,
&WifiOta::instance().m_reconnectTask);
if (ret != pdPASS) {
DEBUG_ERR("WIFI", "创建重连任务失败");
} else {
DEBUG_DBG("WIFI", "WiFi 重连任务已创建");
}
}
// 注册 WiFi/IP 事件回调(必须在 esp_wifi_init 之前)
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL));
DEBUG_DBG("WIFI", "WiFi/IP 事件回调已注册");
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (sta_netif == NULL) {
sta_netif = esp_netif_create_default_wifi_sta();
if (sta_netif == NULL) {
DEBUG_ERR("WIFI", "创建 STA netif 失败");
} else {
DEBUG_DBG("WIFI", "STA netif 已创建");
}
} else {
DEBUG_INFO("WIFI", "STA netif 已存在,跳过创建");
}
// ★ 压制 WiFi 框架内部 INFO 日志(pp/net80211/wifi/wifi_init 等 tag 大量启动打印)
esp_log_level_set("wifi", ESP_LOG_WARN);
esp_log_level_set("wifi_init", ESP_LOG_WARN);
esp_log_level_set("pp", ESP_LOG_WARN);
esp_log_level_set("net80211", ESP_LOG_WARN);
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg));
DEBUG_INFO("WIFI", "WiFi driver 初始化成功");
// STA 优先,失败时返回 false 让 task 切 AP
if (CONFIG_WIFI_MODE == 1) {
DEBUG_INFO("WIFI", "工作模式: STA (优先)");
err = initSta();
if (err == ESP_OK) {
// 连接已触发(STA_START 事件驱动),返回 true
// 实际连接状态由 WifiOta::taskEntry 等待确认
return true;
}
DEBUG_WARN("WIFI", "STA 初始化失败,将切换 AP");
return false;
} else {
DEBUG_INFO("WIFI", "工作模式: AP");
initAp();
return false; // AP 模式,不需要等待连接
}
}
// ----------------------------------------
// WiFi OTA FreeRTOS 任务
// WiFi 初始化已在 app_main 中通过 WifiOta::instance().earlyInit() 完成
// 此任务负责:等待连接结果 → 启动 HTTP 服务器 → 状态监控
/**
* @brief WiFi OTA FreeRTOS 任务,提供 HTTP 上传和中继触发。
* @param pvParameters FreeRTOS 任务参数,未使用。
*/
void WifiOta::taskEntry(void *pvParameters)
{
(void)pvParameters;
WifiOta::instance().m_taskHandle = xTaskGetCurrentTaskHandle();
DEBUG_INFO("WIFI", "=== WiFi OTA 任务启动 ===");
// 等待 STA 连接结果(wifi_early_init 已触发连接)
if (WifiOta::getMode() == OTA_WIFI_MODE_STA) {
char ip_str[16] = {0};
esp_err_t err = instance().waitStaConnected(ip_str, sizeof(ip_str));
if (err == ESP_OK) {
DEBUG_INFO("WIFI", "STA 已连接, IP: %s", ip_str);
} else {
DEBUG_WARN("WIFI", "STA 连接超时,切换到 AP 模式...");
// ★ 完整的 STA→AP 切换流程:
// 1. 停止 WiFi
// 2. 反初始化 WiFi 驱动(释放内部资源,包括 NVS 缓存)
// 3. 重新初始化 WiFi 驱动
// 4. 重新注册事件回调
// 5. 以 AP 模式启动
esp_wifi_stop();
esp_err_t deinit_err = esp_wifi_deinit();
if (deinit_err != ESP_OK) {
DEBUG_WARN("WIFI", "WiFi deinit 失败: %s,仅 stop 后切 AP", esp_err_to_name(deinit_err));
} else {
DEBUG_INFO("WIFI", "WiFi 驱动已反初始化,重新初始化...");
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
deinit_err = esp_wifi_init(&wifi_cfg);
if (deinit_err != ESP_OK) {
DEBUG_ERR("WIFI", "WiFi 驱动重新初始化失败: %s", esp_err_to_name(deinit_err));
} else {
// ★ deinit 后事件回调会丢失,必须重新注册
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL);
esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL);
DEBUG_INFO("WIFI", "WiFi/IP 事件回调已重新注册");
}
}
WifiOta::instance().m_wifiMode = OTA_WIFI_MODE_AP;
instance().initAp();
}
}
// AP 模式:wifi_early_init 已完成,无需额外操作
esp_err_t err = instance().httpServerStart();
if (err != ESP_OK) {
DEBUG_ERR("WIFI", "HTTP 服务器启动失败");
}
// ★ 开机清理:如果 NVS 中有不完整的 OTA 元数据(上次 OTA 中断),
// GD32 可能还卡在 Bootloader 中,导致 SPI 全面超时 → HTTP socket 饥荒。
// 主动清除残留元数据并尝试恢复 GD32 APP。
{
ota_meta_t boot_meta;
if (OtaCoordinator::instance().loadGd32Meta(&boot_meta) == ESP_OK &&
boot_meta.total_size > 0 && boot_meta.stored_size < boot_meta.total_size) {
DEBUG_WARN("WIFI", "检测到不完整 OTA 缓存 (stored=%u total=%u),清除残留状态",
(unsigned)boot_meta.stored_size, (unsigned)boot_meta.total_size);
OtaCoordinator::instance().clearGd32Meta();
OtaCoordinator::instance().abortCurrent();
// 给 GD32 时间从 Bootloader 超时退出并重启到 APP
vTaskDelay(pdMS_TO_TICKS(3000));
// 尝试探测 GD32 APP 是否恢复
gd32_version_t ver = {};
if (OtaCoordinator::instance().probeGd32AppOnce(&ver, nullptr) == ESP_OK) {
DEBUG_INFO("WIFI", "GD32 APP 已恢复: v%d.%d.%d.%d",
ver.app_major, ver.app_minor, ver.app_patch, ver.app_build);
} else {
DEBUG_WARN("WIFI", "GD32 APP 未恢复,SPI 链路可能不可用");
}
}
}
// ★ WiFi 已连接,UDP 从这里开始能通
// 等待网络栈稳定(ARP 表建立、路由就绪),避免首批 UDP 包丢失
vTaskDelay(pdMS_TO_TICKS(500));
// 启动摘要,发两遍确保上位机收到(第一包可能触发 ARP 被丢)
esp_chip_info_t chip;
esp_chip_info(&chip);
for (int i = 0; i < 2; i++) {
DEBUG_INFO("WIFI", "========================================");
DEBUG_INFO("WIFI", "[启动摘要] ESP32-C3 OTA Relay v%s", FW_VERSION);
DEBUG_INFO("WIFI", "[启动摘要] Chip: %s, Rev %d", CONFIG_IDF_TARGET, chip.revision);
DEBUG_INFO("WIFI", "[启动摘要] Free heap: %lu bytes", (unsigned long)esp_get_free_heap_size());
DEBUG_INFO("WIFI", "[启动摘要] WiFi: %s", WifiOta::getMode() == OTA_WIFI_MODE_STA ? "STA" : "AP");
DEBUG_INFO("WIFI", "[启动摘要] NVS + WiFi driver + netif + event handler 初始化完成");
DEBUG_INFO("WIFI", "[启动摘要] HTTP 服务器端口: %d", HTTP_SERVER_PORT);
DEBUG_INFO("WIFI", "========================================");
DEBUG_INFO("WIFI", "=== WiFi OTA 服务器就绪 ===");
if (i == 0) vTaskDelay(pdMS_TO_TICKS(200));
}
// WiFi 状态监控
// 每秒一次循环:每 10 秒检查 RSSI
uint32_t tick = 0;
while (1) {
tick++;
// ★ BLE OTA 传输期间,暂停 WiFi RSSI 轮询,避免 esp_wifi_sta_get_ap_info()
// 触发射频活动干扰 BLE 连接事件
if (WifiOta::instance().m_bleOtaActive.load()) {
vTaskDelay(pdMS_TO_TICKS(1000));
continue;
}
// 每 10 秒检查一次 RSSI
if (tick % 10 == 0) {
int8_t rssi = WifiOta::getRssi();
if (rssi != -127) {
HealthMonitor::instance().updateFromWifiRssi(rssi);
}
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}