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:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies: {}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user