feat(ota-relay): 更新ESP32-C3 OTA中继固件配置和功能增强

- 修改WiFi配置默认为STA模式连接路由器,并调整相关参数
- 优化调试日志系统,添加环形缓冲区支持HTTP日志拉取功能
- 重构健康监控,提高温度监控频率至1秒并添加电压监控定时器
- 更新分区表配置以适应4MB Flash布局
- 添加OTA更新组件依赖和蓝牙支持
- 修复UDP socket延迟创建时机,确保网络接口初始化正确
- 调整WiFi信号强度告警阈值,提升连接稳定性
- 更新README文档中的IP地址配置示例
- 修改VSCode端口配置为COM13
This commit is contained in:
小龙虾
2026-05-05 03:28:44 +08:00
parent 064eb7e03f
commit 478e5e5a31
25 changed files with 8342 additions and 1819 deletions
+2
View File
@@ -17,6 +17,7 @@ idf_component_register(
"monitor"
"include"
REQUIRES
app_update
cjson
esp_driver_spi
esp_driver_tsens
@@ -31,4 +32,5 @@ idf_component_register(
nvs_flash
freertos
mbedtls
bt
)
-20
View File
@@ -1,20 +0,0 @@
menu "ESP32_COMM Configuration"
config ESP_WIFI_SSID
string "WiFi SSID"
default "ESP32_OTA_RELAY"
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "12345678"
config ESP_WIFI_CHANNEL
int "WiFi Channel"
range 1 13
default 6
config ESP_MAX_STA_CONN
int "Max STA connections"
default 4
endmenu
+17 -10
View File
@@ -9,8 +9,8 @@
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_system.h"
#include "esp_clk_tree.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "monitor/health_monitor.h"
#include "relay/ota_relay.h"
@@ -25,23 +25,30 @@ static void status_monitor_task(void *pvParameters);
extern "C" void app_main(void)
{
// 【关键】在所有任务创建之前初始化网络接口
// esp_timer 内部依赖 tcpip 线程,health_monitor_task 的 esp_timer_create
// 会在 scheduler 启动后立即执行。如果 esp_netif_init() 没有提前调用,
// tcpip 线程的 mailbox 还未创建,此时 esp_timer 发消息给 tcpip 线程就会崩溃。
// esp_netif_init() 可以安全调用多次,后续 wifi_ota_task 中的调用是多余的但不报错。
ESP_ERROR_CHECK(esp_netif_init());
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
// 初始化调试日志(UDP 输出到上位机 + UART0 串口)
// 初始化网络接口 + 事件循环(必须最早)
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 初始化调试日志(UART0 先就绪,UDP socket 延迟到首次发送时创建)
// 必须在 wifi_early_init 之前,这样 WiFi 初始化日志也能输出到上位机
debug_log_init();
// ★ WiFi 提前初始化(调度器启动前)
// 包括:NVS → netif 创建 → event handler 注册 → WiFi driver init → 配置 → connect
// 连接尝试立即开始,事件排队等调度器启动后处理
bool wifi_sta_ok = wifi_early_init();
health_monitor_init();
DEBUG_INFO("MAIN", "健康监控状态已初始化");
DEBUG_INFO("MAIN", "========================================");
DEBUG_INFO("MAIN", "ESP32-C3 OTA Relay v1.0 启动");
DEBUG_INFO("MAIN", "Chip: %s, Rev %d", CONFIG_IDF_TARGET, chip_info.revision);
DEBUG_INFO("MAIN", "Free heap: %lu bytes", esp_get_free_heap_size());
DEBUG_INFO("MAIN", "CPU Freq: %d MHz", CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ);
DEBUG_INFO("MAIN", "WiFi: %s", wifi_sta_ok ? "STA 连接中" : "将使用 AP 模式");
DEBUG_INFO("MAIN", "========================================");
// 健康监控任务(温度 + 电压 + WiFi RSSI)
-2
View File
@@ -1,2 +0,0 @@
## IDF Component Manager Manifest File
dependencies: {}
+14 -9
View File
@@ -7,12 +7,17 @@
#pragma once
#include <cstdint>
// ============================================================
// 固件版本
// ============================================================
inline constexpr const char *FW_VERSION = "0.0.6";
// ============================================================
// WiFi 工作模式选择
// ============================================================
// 0 = AP 模式(设备当热点)
// 1 = STA 模式(连接路由器)
inline constexpr uint8_t CONFIG_WIFI_MODE = 0;
inline constexpr uint8_t CONFIG_WIFI_MODE = 1;
// ============================================================
// WiFi STA 模式配置(连接路由器)
@@ -20,16 +25,16 @@ inline constexpr uint8_t CONFIG_WIFI_MODE = 0;
inline constexpr const char* WIFI_STA_SSID = "004舰桥";
inline constexpr const char* WIFI_STA_PASSWORD = "q13362875622";
inline constexpr uint32_t WIFI_STA_CONNECT_TIMEOUT_MS = 30000;
inline constexpr uint8_t WIFI_STA_MAX_RETRY = 5; // 最大重连次数,超过后切换 AP 模式
// STA 静态 IP
inline constexpr uint8_t WIFI_STA_USE_STATIC_IP = 1; // 设为 1 启用静态 IP
inline constexpr uint8_t WIFI_STA_USE_STATIC_IP = 0; // 先关闭静态 IP,使用 DHCP 排查连接问题
#if WIFI_STA_USE_STATIC_IP
inline constexpr const char* WIFI_STA_STATIC_IP = "192.168.1.100";
inline constexpr const char* WIFI_STA_STATIC_GW = "192.168.1.1";
// 静态 IP 配置(始终定义,由 WIFI_STA_USE_STATIC_IP 在运行时决定是否使用)
inline constexpr const char* WIFI_STA_STATIC_IP = "192.168.31.32";
inline constexpr const char* WIFI_STA_STATIC_GW = "192.168.31.1";
inline constexpr const char* WIFI_STA_STATIC_NETMASK = "255.255.255.0";
inline constexpr const char* WIFI_STA_STATIC_DNS = "192.168.1.1";
#endif
inline constexpr const char* WIFI_STA_STATIC_DNS = "192.168.31.1";
// ============================================================
// WiFi AP 模式配置(设备当热点)
@@ -54,8 +59,8 @@ inline constexpr size_t OTA_MAX_FIRMWARE_SIZE = 2 * 1024 * 1024; // 2MB
// ============================================================
// 健康监控阈值
// ============================================================
inline constexpr int32_t WIFI_RSSI_CRITICAL = -75;
inline constexpr int32_t WIFI_RSSI_WARNING = -65;
inline constexpr int32_t WIFI_RSSI_CRITICAL = -85;
inline constexpr int32_t WIFI_RSSI_WARNING = -75;
inline constexpr int32_t TEMP_WARNING = 80;
inline constexpr int32_t TEMP_CRITICAL = 100;
inline constexpr size_t HEAP_WARNING_MIN = 10240;
+137 -34
View File
@@ -1,11 +1,17 @@
/*
* debug_log.c - WiFi 调试日志输出实现
*
* 通过 UDP socket 发送调试日志到上位机
* 通过 UDP socket 将调试日志发送到上位机
* 上位机需监听 <DEBUG_LOG_IP>:<DEBUG_LOG_PORT>
*
* 支持双通道输出:UDP + UART0 串口
* UART0: TX=GPIO21, RX=GPIO20, 115200 波特率
*
* 时序说明:
* esp_netif_init() 创建 tcpip 线程后才可安全使用 socket
* 本模块在首次 debug_log_send() 时才创建 socket(延迟创建)
* WiFi 连接前 sendto() 会失败(无路由),日志仅走 UART
* WiFi 连接后 UDP 立即生效,无需额外操作
*/
#include <string.h>
#include <stdio.h>
@@ -13,6 +19,7 @@
#include <netdb.h>
#include <arpa/inet.h>
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/uart.h"
@@ -27,6 +34,116 @@ static int s_udp_sock = -1;
static struct sockaddr_in s_debug_addr;
static bool s_initialized = false;
// ============================================================
// 日志环形缓冲区(供 HTTP /logs 拉取)
// ============================================================
#define LOG_RING_SLOTS 64
#define LOG_RING_SIZE 256
static char s_log_ring[LOG_RING_SLOTS][LOG_RING_SIZE];
static int s_log_ring_lens[LOG_RING_SLOTS];
static int s_log_ring_head = 0; // 下次写入位置
static int s_log_ring_count = 0; // 已存条数(超过 LOG_RING_SLOTS 后不再增长,覆盖旧数据)
static SemaphoreHandle_t s_log_ring_mutex = NULL;
// 获取日志缓冲区中的条数
int debug_log_ring_count(void)
{
if (!s_log_ring_mutex) return 0;
xSemaphoreTake(s_log_ring_mutex, portMAX_DELAY);
int count = s_log_ring_count;
xSemaphoreGive(s_log_ring_mutex);
return count;
}
// 读取并清空日志缓冲区,返回 JSON 数组字符串
// 调用者负责 free 返回值
char *debug_log_ring_drain_json(int *out_len)
{
if (!s_log_ring_mutex) {
*out_len = 0;
return NULL;
}
xSemaphoreTake(s_log_ring_mutex, portMAX_DELAY);
int count = s_log_ring_count;
if (count == 0) {
xSemaphoreGive(s_log_ring_mutex);
*out_len = 0;
return NULL;
}
// 估算 JSON 大小:每条 ~280 字节(转义 + JSON 包装)
size_t buf_size = (size_t)count * 280 + 16;
char *json = (char *)malloc(buf_size);
if (!json) {
xSemaphoreGive(s_log_ring_mutex);
*out_len = 0;
return NULL;
}
int pos = 0;
json[pos++] = '[';
// 从最旧的开始读
int start = (count < LOG_RING_SLOTS) ? 0 : s_log_ring_head;
for (int i = 0; i < count && pos < (int)buf_size - 200; i++) {
int idx = (start + i) % LOG_RING_SLOTS;
int len = s_log_ring_lens[idx];
if (len <= 0) continue;
if (i > 0) json[pos++] = ',';
// JSON 转义日志内容
pos += snprintf(json + pos, buf_size - pos, "\"");
for (int j = 0; j < len && pos < (int)buf_size - 10; j++) {
char c = s_log_ring[idx][j];
if (c == '"' || c == '\\') {
json[pos++] = '\\';
json[pos++] = c;
} else if (c == '\n') {
json[pos++] = '\\';
json[pos++] = 'n';
} else if (c >= 32) {
json[pos++] = c;
}
}
pos += snprintf(json + pos, buf_size - pos, "\"");
}
json[pos++] = ']';
json[pos] = '\0';
// 清空缓冲区
s_log_ring_head = 0;
s_log_ring_count = 0;
xSemaphoreGive(s_log_ring_mutex);
*out_len = pos;
return json;
}
// 写入一条日志到环形缓冲区
static void log_ring_push(const char *data, int len)
{
if (!s_log_ring_mutex || len <= 0) return;
xSemaphoreTake(s_log_ring_mutex, portMAX_DELAY);
int copy_len = len;
if (copy_len >= LOG_RING_SIZE) copy_len = LOG_RING_SIZE - 1;
memcpy(s_log_ring[s_log_ring_head], data, copy_len);
s_log_ring[s_log_ring_head][copy_len] = '\0';
s_log_ring_lens[s_log_ring_head] = copy_len;
s_log_ring_head = (s_log_ring_head + 1) % LOG_RING_SLOTS;
if (s_log_ring_count < LOG_RING_SLOTS) s_log_ring_count++;
xSemaphoreGive(s_log_ring_mutex);
}
// UART0 状态
static bool s_uart_initialized = false;
static uart_port_t s_uart_port = UART_NUM_0;
@@ -95,37 +212,37 @@ esp_err_t uart_debug_send(const uint8_t *data, size_t len)
// UDP 调试日志
// ============================================================
// 初始化 UDP socket(注意:socket 创建延迟到 debug_log_send 首次调用时)
// 因为 esp_netif_init() 会在 wifi_ota_task 里调用,提前创建 socket 会导致 tcpip 线程未就绪而崩溃
// 初始化(UART 立即可用,UDP socket 延迟到首次发送时创建)
esp_err_t debug_log_init(void)
{
if (s_initialized) {
return ESP_OK;
}
// 初始化 UART0(不需要网络)
uart_debug_init();
// 延迟创建 socket:先只初始化目标地址,socket 在 debug_log_send 首次发 UDP 时创建
// 日志环形缓冲区互斥锁
s_log_ring_mutex = xSemaphoreCreateMutex();
// 目标地址:单播到上位机
memset(&s_debug_addr, 0, sizeof(s_debug_addr));
s_debug_addr.sin_family = AF_INET;
s_debug_addr.sin_port = htons(DEBUG_LOG_PORT);
s_debug_addr.sin_addr.s_addr = inet_addr(DEBUG_LOG_IP);
s_initialized = true;
ESP_LOGI(TAG, "调试日志已初始化(UDP socket 延迟创建),上位机 IP=%s:%d", DEBUG_LOG_IP, DEBUG_LOG_PORT);
ESP_LOGI(TAG, "调试日志已初始化,上位机 %s:%d", DEBUG_LOG_IP, DEBUG_LOG_PORT);
return ESP_OK;
}
// 延迟创建 UDP socket(仅在首次发送 UDP 时调用)
// 延迟创建 UDP socket(首次发送时调用)
static esp_err_t ensure_udp_socket(void)
{
if (s_udp_sock >= 0) {
return ESP_OK; // 已创建
return ESP_OK;
}
// 创建 UDP socket
s_udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (s_udp_sock < 0) {
ESP_LOGE(TAG, "创建 UDP socket 失败");
@@ -136,49 +253,39 @@ static esp_err_t ensure_udp_socket(void)
return ESP_OK;
}
// 发送原始数据到上位机(UDP + UART 双通道)
// 发送原始数据(UDP 单播 + UART 双通道)
esp_err_t debug_log_send(const uint8_t *data, size_t len)
{
esp_err_t udp_ret = ESP_FAIL;
esp_err_t uart_ret = ESP_FAIL;
// UART 通道发送
// UART 通道
if (s_uart_initialized) {
uart_ret = uart_debug_send(data, len);
}
// UDP 通道发送(延迟创建 socket)
// UDP 通道(WiFi 未连接时 sendto 失败,静默跳过)
if (s_initialized) {
// 首次 UDP 发送前才创建 socket
if (s_udp_sock < 0) {
if (ensure_udp_socket() != ESP_OK) {
// socket 创建失败,走 UART 通道
} else {
udp_ret = ESP_OK; // 标记为已就绪
}
ensure_udp_socket();
}
if (s_udp_sock >= 0) {
if (len > 1024) {
len = 1024; // 限制单次发送大小
}
int ret = sendto(s_udp_sock, data, len, 0,
size_t send_len = len > 1024 ? 1024 : len;
int ret = sendto(s_udp_sock, data, send_len, 0,
(struct sockaddr *)&s_debug_addr, sizeof(s_debug_addr));
if (ret < 0) {
udp_ret = ESP_FAIL;
} else {
if (ret >= 0) {
udp_ret = ESP_OK;
}
}
}
// 至少一个通道成功即返回成功
// 同时写入环形缓冲区(供 HTTP /logs 拉取)
log_ring_push((const char *)data, (int)len);
if (udp_ret == ESP_OK || uart_ret == ESP_OK) {
return ESP_OK;
}
return ESP_FAIL;
}
@@ -193,7 +300,6 @@ void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...)
return;
}
// 格式化日志头
char prefix[32];
const char *level_str;
switch (level) {
@@ -205,17 +311,14 @@ void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...)
}
snprintf(prefix, sizeof(prefix), "[%s][%s]", level_str, tag);
// 格式化消息体
va_list args;
va_start(args, fmt);
char msg[512];
vsnprintf(msg, sizeof(msg), fmt, args);
va_end(args);
// 组装完整日志帧
char log_buf[600];
int total = snprintf(log_buf, sizeof(log_buf), "%s %s\n", prefix, msg);
// 发送(末尾 \n 会被保留)
debug_log_send((const uint8_t *)log_buf, total);
}
}
+6 -2
View File
@@ -1,8 +1,8 @@
/*
* debug_log.h - WiFi 调试日志输出
*
* 通过 UDP socket 将调试信息发送到上位机
* 上位机监听 <udp_ip>/<udp_port> 接收日志
* 通过 UDP socket 将调试日志发送到上位机
* 上位机监听 <DEBUG_LOG_PORT> 接收日志
*
* 支持双通道输出:UDP + UART0 串口
* UART0: TX=GPIO21, RX=GPIO20, 115200 波特率
@@ -50,6 +50,10 @@ esp_err_t debug_log_send(const uint8_t *data, size_t len);
// 格式化日志输出(类似 printf)
void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...);
// 日志环形缓冲区(供 HTTP /logs 拉取)
int debug_log_ring_count(void);
char *debug_log_ring_drain_json(int *out_len); // 调用者 free
// 宏定义方便调用
#define DEBUG_ERR(tag, fmt, ...) debug_log(DEBUG_LOG_ERR, tag, fmt, ##__VA_ARGS__)
#define DEBUG_WARN(tag, fmt, ...) debug_log(DEBUG_LOG_WARN, tag, fmt, ##__VA_ARGS__)
+34 -29
View File
@@ -19,6 +19,8 @@ health_state_t g_health_state = SYS_NORMAL;
static SemaphoreHandle_t g_health_mutex = NULL;
static temperature_sensor_handle_t s_temp_handle = NULL;
static float s_last_temperature = 0.0f;
static float s_last_voltage = 3.30f;
// ----------------------------------------
// 健康状态管理
@@ -44,6 +46,16 @@ health_state_t health_get_current(void)
return state;
}
float health_get_last_temperature(void)
{
return s_last_temperature;
}
float health_get_last_voltage(void)
{
return s_last_voltage;
}
// ----------------------------------------
// 温度监控(定时器回调)
// ----------------------------------------
@@ -56,7 +68,7 @@ static void temp_monitor_callback(void *arg)
return;
}
DEBUG_DBG("TEMP", "芯片温度: %.1f°C", temp);
s_last_temperature = temp;
if (temp >= TEMP_CRITICAL) {
g_health_state = SYS_SAFE_SHUTDOWN;
@@ -77,7 +89,7 @@ static void temp_monitor_callback(void *arg)
static void voltage_monitor_callback(void *arg)
{
float voltage = 3.30f; // ESP32-C3 无内置电压传感器,使用估算
DEBUG_DBG("VOLT", "VDD 电压: %.2fV (估算)", voltage);
s_last_voltage = voltage;
if (voltage < 2.3f) {
g_health_state = SYS_SAFE_SHUTDOWN;
@@ -89,8 +101,6 @@ static void voltage_monitor_callback(void *arg)
DEBUG_WARN("VOLT", "警告: 电压 %.2fV < 2.5V", voltage);
DEBUG_WARN("VOLT", "健康状态变更为: SYS_WARNING");
}
} else {
DEBUG_INFO("VOLT", "电压正常: %.2fV", voltage);
}
}
@@ -111,7 +121,7 @@ void health_monitor_task(void *pvParameters)
DEBUG_INFO("MON", "温度传感器已启用 (范围 20°C ~ 80°C)");
}
// 定时器,每5秒读取一次温度
// 定时器,每1秒读取一次温度
esp_timer_handle_t temp_timer;
esp_timer_create_args_t timer_args = {};
timer_args.callback = &temp_monitor_callback;
@@ -120,40 +130,35 @@ void health_monitor_task(void *pvParameters)
timer_args.dispatch_method = ESP_TIMER_TASK;
timer_args.skip_unhandled_events = false;
esp_timer_create(&timer_args, &temp_timer);
esp_timer_start_periodic(temp_timer, 5 * 1000000); // 5秒
esp_timer_start_periodic(temp_timer, 1 * 1000000); // 1秒
DEBUG_INFO("MON", "温度监控定时器已启动 (间隔 5 秒)");
DEBUG_INFO("MON", "温度监控定时器已启动 (间隔 1 秒)");
// 启动时检查一次电压
voltage_monitor_callback(NULL);
// 每5秒打印一次健康状态
uint32_t loop_count = 0;
// 每1秒刷新一次估算电压
esp_timer_handle_t volt_timer;
esp_timer_create_args_t volt_timer_args = {};
volt_timer_args.callback = &voltage_monitor_callback;
volt_timer_args.arg = nullptr;
volt_timer_args.name = "volt_monitor";
volt_timer_args.dispatch_method = ESP_TIMER_TASK;
volt_timer_args.skip_unhandled_events = false;
esp_timer_create(&volt_timer_args, &volt_timer);
esp_timer_start_periodic(volt_timer, 1 * 1000000); // 1秒
DEBUG_INFO("MON", "电压监控定时器已启动 (间隔 1 秒)");
// 保活循环:定时器由 esp_timer 任务驱动,此任务只需保持存活
// 状态汇报已由 status_monitor_task 负责,此处只在异常时告警
while (1) {
loop_count++;
uint32_t free_heap = esp_get_free_heap_size();
const char *state_str;
switch (g_health_state) {
case SYS_NORMAL: state_str = "正常"; break;
case SYS_WARNING: state_str = "警告"; break;
case SYS_CRITICAL: state_str = "危险"; break;
case SYS_SAFE_SHUTDOWN: state_str = "安全关机"; break;
default: state_str = "未知"; break;
}
DEBUG_INFO("MON", "--- 健康状态 #%lu ---", (unsigned long)loop_count);
DEBUG_INFO("MON", "健康状态: %s (%d)", state_str, g_health_state);
DEBUG_INFO("MON", "空闲堆: %lu 字节", (unsigned long)free_heap);
// 堆内存警告
if (free_heap < HEAP_WARNING_MIN) {
DEBUG_WARN("MON", "空闲堆内存不足: %lu < %d", (unsigned long)free_heap, HEAP_WARNING_MIN);
}
DEBUG_INFO("MON", "===================");
if (g_health_state >= SYS_CRITICAL) {
DEBUG_ERR("MON", "健康状态危险: %d", g_health_state);
}
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
+2
View File
@@ -35,3 +35,5 @@ health_status_t health_check_before_ota(void);
void health_log_event(const char *event, int value);
health_state_t health_get_current(void);
health_status_t health_update_from_wifi_rssi(int8_t rssi);
float health_get_last_temperature(void);
float health_get_last_voltage(void);
+1 -1
View File
@@ -40,7 +40,7 @@ esp_err_t meta_load(ota_meta_t *meta)
FILE *f = fopen(META_FILE, "r");
if (!f) {
DEBUG_WARN("OTA", "元数据文件不存在,返回默认值");
// 文件不存在是正常状态(首次使用,未刷过 GD32)
return ESP_OK;
}
+538 -70
View File
@@ -1,99 +1,542 @@
/*
* ble_ota.c - BLE OTA GATT 服务器实现
* ble_ota.cpp - BLE OTA GATT 服务器实现 (ESP-IDF 6.0 NimBLE)
* T5.1 - T5.5
*
* 注意: ESP-IDF 6.0 中蓝牙组件有较大变化
* 此文件暂时提供空实现,待 ESP-IDF 6.0 蓝牙 API 稳定后再适配
*
* 通过 BLE GATT 接收上位机固件,存储到 Flash (gd32_ota 分区),
* 接收完成后触发 OTA 中继任务转发到 GD32。
*
* GATT 服务:
* Service UUID: 0xFFFF
* - CTRL (0x0001, Write): 写入 0xF7 触发中继
* - DATA_IN (0x0002, Write): 接收固件数据块
* - DATA_OUT (0x0003, Notify): 推送 GD32 响应
* - VERSION (0x0004, Read): 固件版本
*/
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_err.h"
// NimBLE 协议栈
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/ble_gap.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "app_config.h"
#include "ble_ota.h"
#include "ota_relay.h"
#include "health_monitor.h"
#include "monitor/debug_log.h"
static bool s_ble_connected = false;
#define TAG "BLE_OTA"
/* ESP-IDF 6.0: BLE OTA 功能暂时禁用,以下函数保留供后续适配使用 */
#if 0
// 处理写入控制点
static void handle_ctrl_point_write(uint8_t *data, size_t len)
// ============================================================
// 静态状态
// ============================================================
static bool s_ble_ready = false; // NimBLE 同步完成
static bool s_ble_connected = false; // GATT 客户端已连接
static uint16_t s_conn_handle = BLE_HS_CONN_HANDLE_NONE;
static uint16_t s_data_out_val_handle = 0; // DATA_OUT 值句柄 (供通知用)
// ============================================================
// NimBLE 工具: 从 mbuf 读取原始数据
// ============================================================
static int ble_ota_mbuf_to_buf(struct os_mbuf *om, uint8_t *buf, size_t buf_len)
{
if (len < 1) return;
if (!om || !buf) return -1;
uint16_t om_len = OS_MBUF_PKTLEN(om);
uint16_t copy_len = (om_len > buf_len) ? (uint16_t)buf_len : om_len;
os_mbuf_copydata(om, 0, copy_len, buf);
return (int)copy_len;
}
uint8_t cmd = data[0];
DEBUG_DBG("BLE", "控制点写入: cmd=0x%02X, len=%d", cmd, len);
// ============================================================
// GATT 访问回调
// ============================================================
if (cmd == 0xF7 && s_ble_connected) {
/*
* CTRL (0x0001) - Write
* 写入 0xF7 触发 OTA 中继
*/
static int ble_ota_ctrl_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)conn_handle;
(void)attr_handle;
(void)arg;
if (ctxt->op != BLE_GATT_ACCESS_OP_WRITE_CHR) {
return BLE_ATT_ERR_REQ_NOT_SUPPORTED;
}
uint8_t buf[4] = {0};
int len = ble_ota_mbuf_to_buf(ctxt->om, buf, sizeof(buf));
if (len < 1) {
DEBUG_WARN(TAG, "CTRL 数据为空");
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
DEBUG_INFO(TAG, "CTRL 写入: cmd=0x%02X", buf[0]);
if (buf[0] == 0xF7) {
// 检查固件是否存储完毕
ota_meta_t meta;
meta_load(&meta);
if (meta.stored_size >= meta.total_size) {
esp_err_t err = meta_load(&meta);
if (err == ESP_OK &&
meta.total_size > 0 &&
meta.stored_size >= meta.total_size) {
DEBUG_INFO(TAG, "固件就绪 (%" PRIu32 " / %" PRIu32 " 字节),触发中继",
meta.stored_size, meta.total_size);
ble_ota_signal_relay();
} else {
DEBUG_WARN(TAG, "固件未就绪, 未触发中继 (stored=%" PRIu32 ", total=%" PRIu32 ")",
meta.stored_size, meta.total_size);
}
}
return 0; // BLE_ATT_ERR_OK 在 NimBLE 中就是 0
}
// 处理写入数据输入(大固件分块)
static void handle_data_in_write(uint8_t *data, size_t len)
/*
* DATA_IN (0x0002) - Write
* 数据块格式:
* [0] cmd (0x00=数据)
* [1-2] seq uint16 LE - 分块序号
* [3-6] total_size uint32 LE - 固件总大小 (仅 seq=0 时有效)
* [7..n-4] payload 固件数据
* [n-4..n] crc 校验 (预留)
*/
static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
if (len < 7) return;
(void)conn_handle;
(void)attr_handle;
(void)arg;
uint16_t seq = data[1] | (data[2] << 8);
uint32_t total_size = data[3] | (data[4] << 8) | (data[5] << 16) | (data[6] << 24);
if (seq == 0 && total_size > 0) {
ota_meta_t meta = {
.total_size = total_size,
.stored_size = 0,
.last_seq = 0,
.chunk_count = (total_size + 4095) / 4096
};
meta_save(&meta);
DEBUG_INFO("BLE", "开始固件存储: %" PRIu32 " 字节", total_size);
if (ctxt->op != BLE_GATT_ACCESS_OP_WRITE_CHR) {
return BLE_ATT_ERR_REQ_NOT_SUPPORTED;
}
uint8_t buf[BLE_MTU_SIZE] = {0};
int recv_len = ble_ota_mbuf_to_buf(ctxt->om, buf, sizeof(buf));
if (recv_len < 12) {
DEBUG_WARN(TAG, "DATA_IN 数据太短: %d 字节 (最少 12)", recv_len);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
// 解析包头
uint8_t cmd = buf[0];
uint16_t seq = (uint16_t)(buf[1] | (buf[2] << 8));
uint32_t total_size = (uint32_t)(buf[3] | (buf[4] << 8) |
(buf[5] << 16) | (buf[6] << 24));
size_t data_offset = 7;
size_t data_len = len - data_offset - 4;
size_t data_len = (size_t)recv_len - data_offset;
uint32_t flash_offset = seq * 4096;
esp_err_t err = flash_write_chunk(flash_offset, &data[data_offset], data_len);
if (err != ESP_OK) {
DEBUG_ERR("BLE", "Flash 写入失败 at seq %" PRIu16, seq);
// 尾 4 字节 CRC 预留,实际 payload = data_len - 4
if (data_len < 4) {
DEBUG_WARN(TAG, "DATA_IN 负载不足: data_len=%zu", data_len);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
data_len -= 4; // 减去尾部 CRC 预留
DEBUG_DBG(TAG, "DATA_IN: cmd=0x%02X seq=%u total=%" PRIu32 " payload=%zu",
cmd, seq, total_size, data_len);
// --- 首个分块: 创建存储元数据 ---
if (seq == 0 && total_size > 0) {
if (total_size > OTA_MAX_FIRMWARE_SIZE) {
DEBUG_ERR(TAG, "固件太大: %" PRIu32 " > %zu",
total_size, OTA_MAX_FIRMWARE_SIZE);
return BLE_ATT_ERR_UNLIKELY;
}
ota_meta_t meta = {};
meta.total_size = total_size;
meta.stored_size = 0;
meta.last_seq = 0;
meta.chunk_count = (total_size + (OTA_BLOCK_SIZE - 1)) / OTA_BLOCK_SIZE;
esp_err_t err = meta_save(&meta);
if (err != ESP_OK) {
DEBUG_ERR(TAG, "元数据保存失败");
return BLE_ATT_ERR_UNLIKELY;
}
DEBUG_INFO(TAG, "固件存储开始: total=%" PRIu32 " chunk_count=%" PRIu32,
meta.total_size, meta.chunk_count);
}
meta_update(flash_offset + data_len, seq);
// --- 写入 Flash ---
uint32_t flash_offset = seq * OTA_BLOCK_SIZE;
esp_err_t err = flash_write_chunk(flash_offset, &buf[data_offset], data_len);
if (err != ESP_OK) {
DEBUG_ERR(TAG, "Flash 写入失败: seq=%u offset=%" PRIu32 " err=%s",
seq, flash_offset, esp_err_to_name(err));
return BLE_ATT_ERR_UNLIKELY;
}
DEBUG_DBG("BLE", "已存储分块 %" PRIu16 ", offset=%" PRIu32, seq, flash_offset);
// --- 更新元数据 ---
err = meta_update(flash_offset + data_len, seq);
if (err != ESP_OK) {
DEBUG_WARN(TAG, "元数据更新失败 (seq=%u)", seq);
}
// --- 检查是否接收完毕 ---
ota_meta_t meta;
meta_load(&meta);
if (meta.stored_size >= meta.total_size) {
DEBUG_INFO("BLE", "全部固件已存储,触发转发");
ble_ota_signal_relay();
if (meta_load(&meta) == ESP_OK &&
meta.total_size > 0 &&
meta.stored_size >= meta.total_size) {
DEBUG_INFO(TAG, "★ 固件接收完毕: %" PRIu32 " / %" PRIu32 " 字节 (共 %u 块)",
meta.stored_size, meta.total_size, seq + 1);
}
return 0;
}
/*
* VERSION (0x0004) - Read
* 返回固件版本字符串
*/
static int ble_ota_version_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)conn_handle;
(void)attr_handle;
(void)arg;
if (ctxt->op != BLE_GATT_ACCESS_OP_READ_CHR) {
return BLE_ATT_ERR_REQ_NOT_SUPPORTED;
}
const char *version = "1.0.0";
int rc = os_mbuf_append(ctxt->om, version, strlen(version));
return (rc == 0) ? 0 : BLE_ATT_ERR_UNLIKELY;
}
/*
* DATA_OUT (0x0003) - Notify only
* 访问回调不做读写处理,通知由 ble_ota_notify_response() 发送
*/
static int ble_ota_data_out_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)conn_handle;
(void)attr_handle;
(void)ctxt;
(void)arg;
return 0;
}
// ============================================================
// GATT 服务定义
// ============================================================
// BLE_UUID16_DECLARE 在 C++ 中产生 "taking address of rvalue" 编译错误。
// ESP-IDF 6.0 NimBLE 的 C++ 兼容写法: 先定义 static ble_uuid16_t 变量, 再取 .u 地址。
static const ble_uuid16_t GATT_SVC_UUID = BLE_UUID16_INIT(BLE_OTA_SERVICE_UUID);
static const ble_uuid16_t GATT_CTRL_UUID = BLE_UUID16_INIT(BLE_OTA_CTRL_UUID);
static const ble_uuid16_t GATT_DATA_IN_UUID = BLE_UUID16_INIT(BLE_OTA_DATA_IN_UUID);
static const ble_uuid16_t GATT_DATA_OUT_UUID = BLE_UUID16_INIT(BLE_OTA_DATA_OUT_UUID);
static const ble_uuid16_t GATT_VERSION_UUID = BLE_UUID16_INIT(BLE_OTA_VERSION_UUID);
// NimBLE C 结构体在 C++ designated initializer 不能显式初始化全部字段。
// 用 pragma 局部关闭 -Wmissing-field-initializers 警告。
#ifdef __cplusplus
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
static const struct ble_gatt_chr_def gatt_ota_chrs[] = {
{
.uuid = &GATT_CTRL_UUID.u,
.access_cb = ble_ota_ctrl_access,
.flags = BLE_GATT_CHR_F_WRITE,
},
{
.uuid = &GATT_DATA_IN_UUID.u,
.access_cb = ble_ota_data_in_access,
.flags = BLE_GATT_CHR_F_WRITE,
},
{
.uuid = &GATT_DATA_OUT_UUID.u,
.access_cb = ble_ota_data_out_access,
.flags = BLE_GATT_CHR_F_NOTIFY,
.val_handle = &s_data_out_val_handle,
},
{
.uuid = &GATT_VERSION_UUID.u,
.access_cb = ble_ota_version_access,
.flags = BLE_GATT_CHR_F_READ,
},
{} // 终止符
};
static const struct ble_gatt_svc_def gatt_ota_svcs[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &GATT_SVC_UUID.u,
.characteristics = (struct ble_gatt_chr_def *)gatt_ota_chrs,
},
{} // 终止符
};
#ifdef __cplusplus
#pragma GCC diagnostic pop
#endif
// ============================================================
// GAP 事件回调
// ============================================================
static int ble_ota_gap_event(struct ble_gap_event *event, void *arg)
{
(void)arg;
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0) {
// 连接成功
s_ble_connected = true;
s_conn_handle = event->connect.conn_handle;
DEBUG_INFO(TAG, "BLE 客户端已连接: handle=%d", s_conn_handle);
} else {
DEBUG_WARN(TAG, "BLE 连接失败: status=%d", event->connect.status);
s_ble_connected = false;
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
DEBUG_INFO(TAG, "BLE 客户端已断开: reason=%d",
event->disconnect.reason);
s_ble_connected = false;
s_conn_handle = BLE_HS_CONN_HANDLE_NONE;
// 恢复广播以接受新连接
ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER,
NULL, ble_ota_gap_event, NULL);
return 0;
case BLE_GAP_EVENT_ADV_COMPLETE:
DEBUG_INFO(TAG, "广播周期结束,重新开始");
ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER,
NULL, ble_ota_gap_event, NULL);
return 0;
case BLE_GAP_EVENT_SUBSCRIBE:
DEBUG_INFO(TAG, "CCC 描述符更新: conn=%d attr=%d notify=%d indicate=%d",
event->subscribe.conn_handle,
event->subscribe.attr_handle,
event->subscribe.cur_notify,
event->subscribe.cur_indicate);
return 0;
case BLE_GAP_EVENT_MTU:
DEBUG_INFO(TAG, "MTU 协商: conn=%d mtu=%d",
event->mtu.conn_handle, event->mtu.value);
return 0;
default:
return 0;
}
}
#endif /* ESP_IDF_COMPILATION */
// 启动 BLE GATT 服务器
// 注意: 此函数在 ESP-IDF 6.0 中暂时返回 ESP_ERR_NOT_SUPPORTED
// ============================================================
// BLE 广播
// ============================================================
static void ble_ota_start_advertising(void)
{
uint8_t adv_data[31] = {0};
int adv_pos = 0;
// AD Flags: LE General Discoverable + BR/EDR Not Supported
adv_data[adv_pos++] = 0x02; // 长度
adv_data[adv_pos++] = 0x01; // 类型 = Flags
adv_data[adv_pos++] = 0x06; // 值
// Complete Local Name
const char *name = BLE_DEVICE_NAME;
size_t name_len = strlen(name);
if (name_len > 29 - adv_pos - 2) {
name_len = 29 - adv_pos - 2; // 截断以适配广播包
}
adv_data[adv_pos++] = (uint8_t)(name_len + 1);
adv_data[adv_pos++] = 0x09; // 类型 = Complete Local Name
memcpy(&adv_data[adv_pos], name, name_len);
adv_pos += name_len;
int rc = ble_gap_adv_set_data(adv_data, adv_pos);
if (rc != 0) {
DEBUG_ERR(TAG, "设置广播数据失败: %d", rc);
return;
}
// 扫描响应: 包含 Service UUID 以帮助发现
uint8_t scan_rsp[31] = {0};
int rsp_pos = 0;
scan_rsp[rsp_pos++] = 0x11; // 长度 (2 + 15)
scan_rsp[rsp_pos++] = 0x09; // 类型 = Complete Local Name
name_len = strlen(name);
if (name_len > (size_t)(31 - rsp_pos)) {
name_len = 31 - rsp_pos;
}
memcpy(&scan_rsp[rsp_pos], name, name_len);
rsp_pos += name_len;
rc = ble_gap_adv_rsp_set_data(scan_rsp, rsp_pos);
if (rc != 0) {
DEBUG_WARN(TAG, "设置扫描响应数据失败: %d", rc);
}
// 开始广播
struct ble_gap_adv_params adv_params = {};
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
// 从 kconfig 获取 BLE 地址类型,默认 Public
#ifdef CONFIG_BLE_ADDR_TYPE_PUBLIC
uint8_t own_addr_type = BLE_OWN_ADDR_PUBLIC;
#else
uint8_t own_addr_type = BLE_OWN_ADDR_RANDOM;
#endif
rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, ble_ota_gap_event, NULL);
if (rc != 0) {
DEBUG_ERR(TAG, "启动广播失败: %d", rc);
} else {
DEBUG_INFO(TAG, "BLE 广播已启动: \"%s\"", name);
}
}
// ============================================================
// NimBLE 同步/重置回调
// ============================================================
static void ble_ota_on_sync(void)
{
DEBUG_INFO(TAG, "NimBLE 协议栈同步完成");
// GATT 服务已在 ble_ota_server_start() 中注册,不再重复注册
// 重复调用 ble_gatts_count_cfg / ble_gatts_add_svcs 会导致 BLE_HS_EBUSY
s_ble_ready = true;
ble_ota_start_advertising();
}
static void ble_ota_on_reset(int reason)
{
DEBUG_WARN(TAG, "NimBLE 协议栈重置: reason=%d", reason);
s_ble_ready = false;
}
// ============================================================
// NimBLE Host 任务(独立 FreeRTOS 任务)
// ============================================================
static void ble_ota_nimble_host_task(void *param)
{
(void)param;
DEBUG_INFO(TAG, "NimBLE Host 任务启动");
// nimble_port_run() 阻塞运行 NimBLE 事件循环
nimble_port_run();
// 正常情况下不会到达这里
DEBUG_INFO(TAG, "NimBLE Host 任务退出");
}
// ============================================================
// 公开 API
// ============================================================
esp_err_t ble_ota_server_start(void)
{
DEBUG_WARN("BLE", "BLE OTA 功能在 ESP-IDF 6.0 中暂时不可用");
DEBUG_WARN("BLE", "请使用 WiFi OTA 功能或等待 BLE API 适配");
return ESP_ERR_NOT_SUPPORTED;
int rc;
DEBUG_INFO(TAG, "=== 初始化 BLE OTA GATT 服务器 ===");
DEBUG_INFO(TAG, " Device: %s", BLE_DEVICE_NAME);
DEBUG_INFO(TAG, " MTU: %" PRIu16, BLE_MTU_SIZE);
DEBUG_INFO(TAG, " Svc: 0x%04X", BLE_OTA_SERVICE_UUID);
// 1. 初始化 NimBLE 底层
esp_err_t err = nimble_port_init();
if (err != ESP_OK) {
DEBUG_ERR(TAG, "nimble_port_init 失败: %s", esp_err_to_name(err));
return err;
}
DEBUG_INFO(TAG, "nimble_port_init 完成");
// 2. 初始化 GAP / GATT 服务(无需 BLE 存储回调 — 不做 bonding)
ble_svc_gap_init();
ble_svc_gatt_init();
// 4. 设置设备名称
rc = ble_svc_gap_device_name_set(BLE_DEVICE_NAME);
if (rc != 0) {
DEBUG_WARN(TAG, "设置设备名称失败: %d", rc);
}
// 5. 配置 Host 回调
// sync_cb: NimBLE 事件循环就绪时调用 (开始广播)
// reset_cb: NimBLE 重置时调用
ble_hs_cfg.sync_cb = ble_ota_on_sync;
ble_hs_cfg.reset_cb = ble_ota_on_reset;
// 不做 bonding,不需要 store 回调
// 6. 预注册 GATT 服务(在 sync 回调中也会再注册一次确保)
rc = ble_gatts_count_cfg(gatt_ota_svcs);
if (rc != 0) {
DEBUG_ERR(TAG, "GATT 服务计数失败: %d", rc);
return ESP_FAIL;
}
rc = ble_gatts_add_svcs(gatt_ota_svcs);
if (rc != 0) {
DEBUG_ERR(TAG, "GATT 服务注册失败: %d", rc);
return ESP_FAIL;
}
// 7. 启动 NimBLE Host 任务(内部创建 FreeRTOS 任务跑 nimble_port_run)
nimble_port_freertos_init(ble_ota_nimble_host_task);
DEBUG_INFO(TAG, "BLE OTA 服务器初始化完成");
return ESP_OK;
}
// 通过 BLE 通知 APP GD32 的响应
esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len)
{
(void)data;
(void)len;
return ESP_ERR_NOT_SUPPORTED;
if (!s_ble_connected) {
return ESP_ERR_NOT_SUPPORTED;
}
if (s_data_out_val_handle == 0) {
DEBUG_WARN(TAG, "DATA_OUT 值句柄未设置");
return ESP_ERR_INVALID_STATE;
}
if (len == 0) return ESP_OK;
// 通过 NimBLE mbuf 发送通知
struct os_mbuf *om = ble_hs_mbuf_from_flat(data, len);
if (om == NULL) {
DEBUG_ERR(TAG, "分配 mbuf 失败 (%zu 字节)", len);
return ESP_ERR_NO_MEM;
}
int rc = ble_gattc_notify_custom(s_conn_handle, s_data_out_val_handle, om);
if (rc != 0) {
DEBUG_WARN(TAG, "通知发送失败: rc=%d", rc);
return ESP_FAIL;
}
DEBUG_DBG(TAG, "通知已发送: %zu 字节", len);
return ESP_OK;
}
bool ble_ota_is_connected(void)
@@ -101,31 +544,56 @@ bool ble_ota_is_connected(void)
return s_ble_connected;
}
// 信号通知 OTA 中继开始转发
void ble_ota_signal_relay(void)
{
if (g_ota_dispatch_queue) {
uint32_t msg = 1;
BaseType_t ret = xQueueSend(g_ota_dispatch_queue, &msg, pdMS_TO_TICKS(100));
if (ret == pdTRUE) {
DEBUG_INFO("BLE", "OTA 调度信号已通过 BLE 发送");
}
if (g_ota_dispatch_queue == NULL) {
DEBUG_ERR(TAG, "OTA 调度队列未初始化");
return;
}
uint32_t msg = 1;
BaseType_t ret = xQueueSend(g_ota_dispatch_queue, &msg, pdMS_TO_TICKS(200));
if (ret != pdTRUE) {
DEBUG_ERR(TAG, "OTA 调度信号发送失败: 队列满");
return;
}
DEBUG_INFO(TAG, "OTA 调度信号已发送 → ota_relay_task");
}
// BLE OTA FreeRTOS 任务
// ============================================================
// BLE OTA FreeRTOS 任务入口
//
// 注意: 此任务的职责是初始化 BLE 服务器并保持存活。
// 实际的 BLE 事件循环在独立的 NimBLE Host 任务中运行。
// 为避免与 ota_relay_task 竞争,此任务不从 g_ota_dispatch_queue 消费。
// ============================================================
void ble_ota_task(void *pvParameters)
{
(void)pvParameters;
DEBUG_WARN("BLE", "BLE OTA 任务启动,但功能暂时不可用");
// 等待调度信号
while (1) {
uint32_t msg;
BaseType_t ret = xQueueReceive(g_ota_dispatch_queue, &msg, pdMS_TO_TICKS(1000));
if (ret == pdTRUE) {
DEBUG_INFO("BLE", "收到 OTA 调度信号,但 BLE 功能不可用");
}
vTaskDelay(pdMS_TO_TICKS(100));
DEBUG_INFO(TAG, "=== BLE OTA 任务启动 ===");
// 初始化并启动 BLE 服务器(同步操作,完成后广播开始)
esp_err_t err = ble_ota_server_start();
if (err != ESP_OK) {
DEBUG_ERR(TAG, "BLE 服务器启动失败: %s → BLE OTA 不可用",
esp_err_to_name(err));
}
}
// 保持任务存活 — 所有 BLE 事件由 NimBLE Host 任务处理
uint32_t heartbeat = 0;
while (1) {
heartbeat++;
// 每 60 秒打印一次心跳(确认任务存活)
if (heartbeat % 6 == 0) {
DEBUG_DBG(TAG, "心跳 #%lu | BLE %s | 连接: %s",
(unsigned long)(heartbeat / 6),
s_ble_ready ? "就绪" : "未就绪",
s_ble_connected ? "已连接" : "未连接");
}
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
+783 -131
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -16,6 +16,11 @@ typedef enum {
// WiFi OTA 任务入口
void wifi_ota_task(void *pvParameters);
// ★ WiFi 早期初始化(app_main 调度前调用)
// 包括 NVS、WiFi driver、netif、event handler、配置、连接尝试
// 返回 true = STA 模式启动中,false = 需要在 task 中切换 AP
bool wifi_early_init(void);
// 信号通知 OTA 中继开始(存储完成后调用)
void wifi_ota_signal_relay(void);
-117
View File
@@ -1,117 +0,0 @@
/* WiFi softAP Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_mac.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
/* The examples use WiFi configuration that you can set via project configuration menu.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_WIFI_CHANNEL CONFIG_ESP_WIFI_CHANNEL
#define EXAMPLE_MAX_STA_CONN CONFIG_ESP_MAX_STA_CONN
#if CONFIG_ESP_GTK_REKEYING_ENABLE
#define EXAMPLE_GTK_REKEY_INTERVAL CONFIG_ESP_GTK_REKEY_INTERVAL
#else
#define EXAMPLE_GTK_REKEY_INTERVAL 0
#endif
static const char *TAG = "wifi softAP";
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d, reason=%d",
MAC2STR(event->mac), event->aid, event->reason);
}
}
void wifi_init_softap(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
wifi_config_t wifi_config = {
.ap = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
.channel = EXAMPLE_ESP_WIFI_CHANNEL,
.password = EXAMPLE_ESP_WIFI_PASS,
.max_connection = EXAMPLE_MAX_STA_CONN,
#ifdef CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT
.authmode = WIFI_AUTH_WPA3_PSK,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
#else /* CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT */
.authmode = WIFI_AUTH_WPA2_PSK,
#endif
.pmf_cfg = {
.required = true,
},
#ifdef CONFIG_ESP_WIFI_BSS_MAX_IDLE_SUPPORT
.bss_max_idle_cfg = {
.period = WIFI_AP_DEFAULT_MAX_IDLE_PERIOD,
.protected_keep_alive = 1,
},
#endif
.gtk_rekey_interval = EXAMPLE_GTK_REKEY_INTERVAL,
},
};
if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
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());
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL);
}
void app_main(void)
{
//Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");
wifi_init_softap();
}