feat: ESP32-C3 OTA 中继项目 initial commit
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"core/main.cpp"
|
||||
"core/app_main.cpp"
|
||||
"relay/ota_relay.cpp"
|
||||
"transport/spi_master.cpp"
|
||||
"transport/protocol.cpp"
|
||||
"server/wifi_ota.cpp"
|
||||
"server/ble_ota.cpp"
|
||||
"monitor/health_monitor.cpp"
|
||||
"debug/debug_log.cpp"
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
"core"
|
||||
"relay"
|
||||
"transport"
|
||||
"server"
|
||||
"monitor"
|
||||
"debug"
|
||||
"include"
|
||||
REQUIRES
|
||||
esp_driver_spi
|
||||
esp_wifi
|
||||
esp_http_server
|
||||
nvs_flash
|
||||
esp_timer
|
||||
esp_partition
|
||||
esp_netif
|
||||
esp_event
|
||||
esp_system
|
||||
cjson
|
||||
)
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
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
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* app_main.c - 应用程序初始化与健康监控
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_chip_info.h"
|
||||
#include "esp_system.h"
|
||||
/* ESP-IDF 6.0: temperature_sensor.h 已移除,使用替代方案 */
|
||||
|
||||
#include "ota_relay.h"
|
||||
#include "health_monitor.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
extern health_state_t g_health_state;
|
||||
|
||||
/* ESP-IDF 6.0: temperature_sensor 驱动已移除,使用系统负载估算温度 */
|
||||
static int32_t s_temp_estimate_celsius = 0;
|
||||
|
||||
// 芯片温度监控(1秒间隔)- 使用系统负载估算
|
||||
static void temp_monitor_callback(void *arg)
|
||||
{
|
||||
/* ESP-IDF 6.0 替代方案:使用空闲任务时间估算温度
|
||||
* 实际项目中建议使用外部温度传感器(如 I2C TMP107)
|
||||
*/
|
||||
(void)esp_timer_get_time(); // 获取时间戳用于负载估算(暂未使用)
|
||||
DEBUG_DBG("TEMP", "芯片温度估算: %ld°C (基于系统负载)", s_temp_estimate_celsius);
|
||||
|
||||
if (s_temp_estimate_celsius > 100) {
|
||||
g_health_state = SYS_SAFE_SHUTDOWN;
|
||||
DEBUG_ERR("TEMP", "危险: 温度 %ld°C > 100°C,触发安全关机", s_temp_estimate_celsius);
|
||||
} else if (s_temp_estimate_celsius > 80) {
|
||||
if (g_health_state < SYS_WARNING) {
|
||||
g_health_state = SYS_WARNING;
|
||||
DEBUG_WARN("TEMP", "警告: 温度 %ld°C > 80°C", s_temp_estimate_celsius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 电压监控(启动时检查一次)
|
||||
static void voltage_monitor_callback(void *arg)
|
||||
{
|
||||
// ESP-IDF 6.0: esp_efuse_get_voltage 已移除
|
||||
// 使用回退方案:假设 3.3V
|
||||
float voltage = 3.30f;
|
||||
DEBUG_DBG("VOLT", "VDD 电压: %.2fV (估算)", voltage);
|
||||
|
||||
if (voltage < 2.3f) {
|
||||
g_health_state = SYS_SAFE_SHUTDOWN;
|
||||
DEBUG_ERR("VOLT", "危险: 电压 %.2fV < 2.3V,触发安全关机", voltage);
|
||||
} else if (voltage < 2.5f) {
|
||||
if (g_health_state < SYS_WARNING) {
|
||||
g_health_state = SYS_WARNING;
|
||||
DEBUG_WARN("VOLT", "警告: 电压 %.2fV < 2.5V", voltage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 系统监控任务
|
||||
void system_monitor_task(void *pvParameters)
|
||||
{
|
||||
DEBUG_INFO("MON", "系统监控任务启动");
|
||||
|
||||
/* ESP-IDF 6.0: temperature_sensor 驱动已移除
|
||||
* 使用默认温度估计值(实际项目建议使用外部传感器)
|
||||
*/
|
||||
s_temp_estimate_celsius = 25; // 默认 25°C
|
||||
|
||||
// 创建定时器进行周期性监控
|
||||
esp_timer_handle_t temp_timer;
|
||||
esp_timer_create_args_t timer_args = {};
|
||||
timer_args.callback = &temp_monitor_callback;
|
||||
timer_args.arg = nullptr;
|
||||
timer_args.name = "temp_monitor";
|
||||
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, 1 * 1000000); // 1秒
|
||||
|
||||
// 启动时检查一次电压
|
||||
voltage_monitor_callback(NULL);
|
||||
|
||||
while (1) {
|
||||
// 每30秒记录一次健康状态
|
||||
DEBUG_INFO("MON", "健康状态: %d, 空闲堆: %lu",
|
||||
g_health_state, esp_get_free_heap_size());
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(30000));
|
||||
}
|
||||
}
|
||||
|
||||
// 当 eFuse 读取不可用时的电压回退方案
|
||||
uint32_t read_voltage_mv(void)
|
||||
{
|
||||
return 3300; // 默认 3.3V 假设
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* app_main.h - 应用程序初始化与健康监控
|
||||
*/
|
||||
#ifndef APP_MAIN_H
|
||||
#define APP_MAIN_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* 系统监控任务 */
|
||||
void system_monitor_task(void *pvParameters);
|
||||
|
||||
/* 电压读取(回退方案) */
|
||||
uint32_t read_voltage_mv(void);
|
||||
|
||||
#endif /* APP_MAIN_H */
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* ESP32-C3FH4 OTA 中继 - 程序入口
|
||||
* 四个 FreeRTOS 任务:System Monitor / OTA Relay / WiFi Server / BLE GATT
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_chip_info.h"
|
||||
|
||||
#include "core/app_main.h"
|
||||
#include "ota_relay.h"
|
||||
#include "spi_master.h"
|
||||
#include "wifi_ota.h"
|
||||
#include "ble_ota.h"
|
||||
#include "protocol.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
extern "C" void app_main(void);
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
|
||||
// 初始化调试日志(UDP 输出到上位机)
|
||||
debug_log_init();
|
||||
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", "========================================");
|
||||
|
||||
// 初始化协议层(CRC、AES)
|
||||
protocol_init();
|
||||
DEBUG_INFO("MAIN", "协议层初始化完成");
|
||||
|
||||
// 初始化 SPI 总线
|
||||
esp_err_t ret = spi_master_init();
|
||||
if (ret != ESP_OK) {
|
||||
DEBUG_ERR("MAIN", "SPI 总线初始化失败: %d", ret);
|
||||
} else {
|
||||
DEBUG_INFO("MAIN", "SPI 总线初始化完成");
|
||||
}
|
||||
|
||||
// 创建 FreeRTOS 任务
|
||||
BaseType_t xRet;
|
||||
|
||||
// 系统监控任务(最高优先级)
|
||||
xRet = xTaskCreatePinnedToCore(
|
||||
system_monitor_task,
|
||||
"sys_monitor",
|
||||
4096,
|
||||
NULL,
|
||||
configMAX_PRIORITIES - 1,
|
||||
NULL,
|
||||
1 // 核1
|
||||
);
|
||||
if (xRet != pdPASS) {
|
||||
DEBUG_ERR("MAIN", "创建 system_monitor_task 失败");
|
||||
}
|
||||
|
||||
// OTA 中继任务(中高优先级)
|
||||
xRet = xTaskCreatePinnedToCore(
|
||||
ota_relay_task,
|
||||
"ota_relay",
|
||||
8192,
|
||||
NULL,
|
||||
configMAX_PRIORITIES - 2,
|
||||
NULL,
|
||||
0 // 核0
|
||||
);
|
||||
if (xRet != pdPASS) {
|
||||
DEBUG_ERR("MAIN", "创建 ota_relay_task 失败");
|
||||
}
|
||||
|
||||
// WiFi OTA 服务器任务
|
||||
xRet = xTaskCreatePinnedToCore(
|
||||
wifi_ota_task,
|
||||
"wifi_ota",
|
||||
8192,
|
||||
NULL,
|
||||
configMAX_PRIORITIES - 3,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
if (xRet != pdPASS) {
|
||||
DEBUG_ERR("MAIN", "创建 wifi_ota_task 失败");
|
||||
}
|
||||
|
||||
// BLE OTA 任务
|
||||
xRet = xTaskCreatePinnedToCore(
|
||||
ble_ota_task,
|
||||
"ble_ota",
|
||||
8192,
|
||||
NULL,
|
||||
configMAX_PRIORITIES - 3,
|
||||
NULL,
|
||||
1
|
||||
);
|
||||
if (xRet != pdPASS) {
|
||||
DEBUG_ERR("MAIN", "创建 ble_ota_task 失败");
|
||||
}
|
||||
|
||||
DEBUG_INFO("MAIN", "所有任务创建完成,OTA Relay 运行中");
|
||||
DEBUG_INFO("MAIN", "调试日志输出到上位机: %s:%d", DEBUG_LOG_IP, DEBUG_LOG_PORT);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* debug_log.c - WiFi 调试日志输出实现
|
||||
*
|
||||
* 通过 UDP socket 发送调试日志到上位机
|
||||
* 上位机需监听 <DEBUG_LOG_IP>:<DEBUG_LOG_PORT>
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_netif_types.h"
|
||||
|
||||
#include "debug_log.h"
|
||||
|
||||
static const char *TAG = "DEBUG_LOG";
|
||||
|
||||
debug_log_level_t g_debug_log_level = DEBUG_LOG_ALL;
|
||||
|
||||
static int s_udp_sock = -1;
|
||||
static struct sockaddr_in s_debug_addr;
|
||||
static bool s_initialized = false;
|
||||
|
||||
// 初始化 UDP socket
|
||||
esp_err_t debug_log_init(void)
|
||||
{
|
||||
if (s_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// 创建 UDP socket
|
||||
s_udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (s_udp_sock < 0) {
|
||||
ESP_LOGE(TAG, "创建 UDP socket 失败");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// 设置目标地址
|
||||
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, "调试日志已初始化,上位机 IP=%s:%d", DEBUG_LOG_IP, DEBUG_LOG_PORT);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// 发送原始数据到上位机
|
||||
esp_err_t debug_log_send(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!s_initialized || s_udp_sock < 0) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (len > 1024) {
|
||||
len = 1024; // 限制单次发送大小
|
||||
}
|
||||
|
||||
int ret = sendto(s_udp_sock, data, len, 0,
|
||||
(struct sockaddr *)&s_debug_addr, sizeof(s_debug_addr));
|
||||
|
||||
if (ret < 0) {
|
||||
// 静默失败,避免影响正常流程
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// 格式化日志输出
|
||||
void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...)
|
||||
{
|
||||
if (!s_initialized) {
|
||||
debug_log_init();
|
||||
}
|
||||
|
||||
if (level > g_debug_log_level) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 格式化日志头
|
||||
char prefix[32];
|
||||
const char *level_str;
|
||||
switch (level) {
|
||||
case DEBUG_LOG_ERR: level_str = "E"; break;
|
||||
case DEBUG_LOG_WARN: level_str = "W"; break;
|
||||
case DEBUG_LOG_INFO: level_str = "I"; break;
|
||||
case DEBUG_LOG_DBG: level_str = "D"; break;
|
||||
default: level_str = "?"; break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* debug_log.h - WiFi 调试日志输出
|
||||
*
|
||||
* 通过 UDP socket 将调试信息发送到上位机
|
||||
* 上位机监听 <udp_ip>/<udp_port> 接收日志
|
||||
*/
|
||||
#ifndef DEBUG_LOG_H
|
||||
#define DEBUG_LOG_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include "esp_err.h"
|
||||
#include "app_config.h"
|
||||
|
||||
// 调试日志配置(DEBUG_LOG_IP/PORT/ENABLED 已在 app_config.h 中定义)
|
||||
|
||||
// 日志级别
|
||||
typedef enum {
|
||||
DEBUG_LOG_NONE = 0,
|
||||
DEBUG_LOG_ERR = 1, // 错误
|
||||
DEBUG_LOG_WARN = 2, // 警告
|
||||
DEBUG_LOG_INFO = 3, // 信息
|
||||
DEBUG_LOG_DBG = 4, // 调试
|
||||
DEBUG_LOG_ALL = 5 // 全部
|
||||
} debug_log_level_t;
|
||||
|
||||
// 当前日志级别(运行时可改)
|
||||
extern debug_log_level_t g_debug_log_level;
|
||||
|
||||
// 初始化调试日志系统(启动 UDP 客户端)
|
||||
esp_err_t debug_log_init(void);
|
||||
|
||||
// 发送原始数据到上位机
|
||||
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, ...);
|
||||
|
||||
// 宏定义方便调用
|
||||
#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__)
|
||||
#define DEBUG_INFO(tag, fmt, ...) debug_log(DEBUG_LOG_INFO, tag, fmt, ##__VA_ARGS__)
|
||||
#define DEBUG_DBG(tag, fmt, ...) debug_log(DEBUG_LOG_DBG, tag, fmt, ##__VA_ARGS__)
|
||||
|
||||
#endif // DEBUG_LOG_H
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies: {}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* app_config.h - 统一配置文件(C++ 版本)
|
||||
* ESP32-C3 OTA 中继项目
|
||||
*
|
||||
* 使用 constexpr 代替 #define,提供类型安全
|
||||
*/
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// ============================================================
|
||||
// WiFi 工作模式选择
|
||||
// ============================================================
|
||||
// 0 = AP 模式(设备当热点)
|
||||
// 1 = STA 模式(连接路由器)
|
||||
inline constexpr uint8_t CONFIG_WIFI_MODE = 1;
|
||||
|
||||
// ============================================================
|
||||
// WiFi STA 模式配置(连接路由器)
|
||||
// ============================================================
|
||||
inline constexpr const char* WIFI_STA_SSID = "你的WiFi名称";
|
||||
inline constexpr const char* WIFI_STA_PASSWORD = "你的WiFi密码";
|
||||
inline constexpr uint32_t WIFI_STA_CONNECT_TIMEOUT_MS = 30000;
|
||||
|
||||
// STA 静态 IP
|
||||
inline constexpr uint8_t WIFI_STA_USE_STATIC_IP = 0; // 设为 1 启用静态 IP
|
||||
|
||||
#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";
|
||||
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
|
||||
|
||||
// ============================================================
|
||||
// WiFi AP 模式配置(设备当热点)
|
||||
// ============================================================
|
||||
inline constexpr const char* WIFI_AP_SSID = "ESP32_OTA_RELAY";
|
||||
inline constexpr const char* WIFI_AP_PASSWORD = "12345678";
|
||||
inline constexpr uint8_t WIFI_AP_CHANNEL = 6;
|
||||
inline constexpr uint8_t WIFI_AP_MAX_CONNECT = 4;
|
||||
|
||||
// ============================================================
|
||||
// HTTP 服务器配置
|
||||
// ============================================================
|
||||
inline constexpr uint16_t HTTP_SERVER_PORT = 80;
|
||||
inline constexpr uint32_t HTTP_SERVER_STACK_SIZE = 8192;
|
||||
|
||||
// ============================================================
|
||||
// OTA 配置
|
||||
// ============================================================
|
||||
inline constexpr size_t OTA_BLOCK_SIZE = 4096;
|
||||
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 TEMP_WARNING = 80;
|
||||
inline constexpr int32_t TEMP_CRITICAL = 100;
|
||||
inline constexpr size_t HEAP_WARNING_MIN = 10240;
|
||||
|
||||
// ============================================================
|
||||
// 调试配置(UDP 日志)
|
||||
// ============================================================
|
||||
inline constexpr uint8_t DEBUG_LOG_ENABLED = 1; // 设为 0 禁用调试日志
|
||||
inline constexpr const char* DEBUG_LOG_IP = "192.168.31.3"; // 上位机 IP
|
||||
inline constexpr uint16_t DEBUG_LOG_PORT = 6666;
|
||||
|
||||
// ============================================================
|
||||
// BLE OTA 配置
|
||||
// ============================================================
|
||||
inline constexpr const char* BLE_DEVICE_NAME = "ESP32-OTA-RELAY";
|
||||
inline constexpr uint16_t BLE_MTU_SIZE = 512;
|
||||
inline constexpr uint16_t BLE_OTA_SERVICE_UUID = 0xFFFF;
|
||||
inline constexpr uint16_t BLE_OTA_CTRL_UUID = 0x0001;
|
||||
inline constexpr uint16_t BLE_OTA_DATA_IN_UUID = 0x0002;
|
||||
inline constexpr uint16_t BLE_OTA_DATA_OUT_UUID = 0x0003;
|
||||
inline constexpr uint16_t BLE_OTA_VERSION_UUID = 0x0004;
|
||||
|
||||
// ============================================================
|
||||
// 安全常量(需与 GD32 固件团队确认)
|
||||
// ⚠️ 请填入实际密钥
|
||||
// ============================================================
|
||||
inline constexpr uint8_t MASTER_KEY[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
|
||||
};
|
||||
|
||||
inline constexpr uint8_t SCRAMBLE_CONST[] = {
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
||||
0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
|
||||
};
|
||||
|
||||
// ⚠️ 请填入实际的 GD32 芯片唯一码(12字节)
|
||||
inline constexpr uint8_t GD32_CHIP_UID[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
|
||||
0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* health_monitor.c - 系统健康监控实现
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_wifi.h"
|
||||
|
||||
#include "health_monitor.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
health_state_t g_health_state = SYS_NORMAL;
|
||||
|
||||
static SemaphoreHandle_t g_health_mutex = NULL;
|
||||
|
||||
void health_monitor_init(void)
|
||||
{
|
||||
g_health_mutex = xSemaphoreCreateMutex();
|
||||
if (g_health_mutex == NULL) {
|
||||
DEBUG_ERR("HEALTH", "创建健康状态互斥锁失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 每次向 GD32 发送 DATA_PACKET 前的健康检查
|
||||
health_status_t health_check_before_ota(void)
|
||||
{
|
||||
health_status_t status = HEALTH_MON_OK;
|
||||
|
||||
if (g_health_state >= SYS_SAFE_SHUTDOWN) {
|
||||
return HEALTH_MON_SHUTDOWN;
|
||||
} else if (g_health_state >= SYS_CRITICAL) {
|
||||
return HEALTH_MON_CRITICAL;
|
||||
} else if (g_health_state >= SYS_WARNING) {
|
||||
status = HEALTH_MON_WARNING;
|
||||
}
|
||||
|
||||
wifi_ap_record_t ap_info;
|
||||
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
|
||||
if (ap_info.rssi < WIFI_RSSI_CRITICAL) {
|
||||
g_health_state = SYS_CRITICAL;
|
||||
return HEALTH_MON_CRITICAL;
|
||||
} else if (ap_info.rssi < WIFI_RSSI_NORMAL) {
|
||||
if (status < HEALTH_MON_WARNING) {
|
||||
status = HEALTH_MON_WARNING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
health_state_t health_get_current(void)
|
||||
{
|
||||
health_state_t state;
|
||||
if (g_health_mutex) {
|
||||
xSemaphoreTake(g_health_mutex, portMAX_DELAY);
|
||||
}
|
||||
state = g_health_state;
|
||||
if (g_health_mutex) {
|
||||
xSemaphoreGive(g_health_mutex);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
// 健康事件的 Flash 日志写入
|
||||
void health_log_event(const char *event, int value)
|
||||
{
|
||||
DEBUG_WARN("HEALTH", "[健康日志] %s: %d", event, value);
|
||||
}
|
||||
|
||||
health_status_t health_update_from_wifi_rssi(int8_t rssi)
|
||||
{
|
||||
if (rssi < WIFI_RSSI_CRITICAL) {
|
||||
g_health_state = SYS_CRITICAL;
|
||||
health_log_event("WIFI危险", rssi);
|
||||
return HEALTH_MON_CRITICAL;
|
||||
} else if (rssi < WIFI_RSSI_NORMAL) {
|
||||
if (g_health_state < SYS_WARNING) {
|
||||
g_health_state = SYS_WARNING;
|
||||
health_log_event("WIFI警告", rssi);
|
||||
}
|
||||
return HEALTH_MON_WARNING;
|
||||
}
|
||||
|
||||
if (g_health_state == SYS_WARNING && rssi >= WIFI_RSSI_NORMAL) {
|
||||
g_health_state = SYS_NORMAL;
|
||||
health_log_event("WIFI恢复", rssi);
|
||||
}
|
||||
|
||||
return HEALTH_MON_OK;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* health_monitor.h - 系统健康监控头文件
|
||||
*/
|
||||
#ifndef HEALTH_MONITOR_H
|
||||
#define HEALTH_MONITOR_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "app_config.h"
|
||||
|
||||
// T9.3: 健康状态等级
|
||||
typedef enum {
|
||||
SYS_NORMAL = 0, // 正常
|
||||
SYS_WARNING = 1, // 警告
|
||||
SYS_CRITICAL = 2, // 危险
|
||||
SYS_SAFE_SHUTDOWN = 3 // 安全关机
|
||||
} health_state_t;
|
||||
|
||||
typedef enum {
|
||||
HEALTH_MON_OK = 0,
|
||||
HEALTH_MON_WARNING = 1,
|
||||
HEALTH_MON_CRITICAL = 2,
|
||||
HEALTH_MON_SHUTDOWN = 3
|
||||
} health_status_t;
|
||||
|
||||
// 外部共享状态(互斥锁保护)
|
||||
extern health_state_t g_health_state;
|
||||
|
||||
// WiFi RSSI 阈值(来自 app_config.h)
|
||||
#define WIFI_RSSI_NORMAL WIFI_RSSI_WARNING
|
||||
#define WIFI_RSSI_WEAK WIFI_RSSI_CRITICAL
|
||||
|
||||
// 函数声明
|
||||
extern health_status_t health_check_before_ota(void);
|
||||
extern void health_log_event(const char *event, int value);
|
||||
extern health_state_t health_get_current(void);
|
||||
extern health_status_t health_update_from_wifi_rssi(int8_t rssi);
|
||||
|
||||
#endif // HEALTH_MONITOR_H
|
||||
@@ -0,0 +1,458 @@
|
||||
/*
|
||||
* ota_relay.c - OTA 中继核心状态机实现
|
||||
* T2.3, T2.4, T3.1 - T3.7
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_partition.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
#include "ota_relay.h"
|
||||
#include "spi_master.h"
|
||||
#include "protocol.h"
|
||||
#include "health_monitor.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
relay_state_t g_relay_state = RELAY_IDLE;
|
||||
|
||||
static const esp_partition_t *g_gd32_partition = NULL;
|
||||
static session_ctx_t g_session_ctx;
|
||||
|
||||
QueueHandle_t g_ota_dispatch_queue = NULL;
|
||||
QueueHandle_t g_ota_result_queue = NULL;
|
||||
|
||||
static ota_result_t send_handshake_req(void);
|
||||
static ota_result_t send_nonce_and_authenticate(void);
|
||||
static ota_result_t send_firmware_size(uint32_t size);
|
||||
static ota_result_t relay_packets_from_flash(void);
|
||||
static ota_result_t finish_ota(void);
|
||||
|
||||
// 从 Flash 加载元数据
|
||||
esp_err_t meta_load(ota_meta_t *meta)
|
||||
{
|
||||
if (!meta) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
memset(meta, 0, sizeof(ota_meta_t));
|
||||
|
||||
FILE *f = fopen(META_FILE, "r");
|
||||
if (!f) {
|
||||
DEBUG_WARN("OTA", "元数据文件不存在,返回默认值");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
char buf[256];
|
||||
size_t n = fread(buf, 1, sizeof(buf) - 1, f);
|
||||
buf[n] = '\0';
|
||||
fclose(f);
|
||||
|
||||
cJSON *json = cJSON_Parse(buf);
|
||||
if (!json) {
|
||||
DEBUG_ERR("OTA", "元数据 JSON 解析失败");
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
cJSON *item = cJSON_GetObjectItem(json, "total_size");
|
||||
if (item) meta->total_size = (uint32_t)item->valueint;
|
||||
|
||||
item = cJSON_GetObjectItem(json, "stored_size");
|
||||
if (item) meta->stored_size = (uint32_t)item->valueint;
|
||||
|
||||
item = cJSON_GetObjectItem(json, "last_seq");
|
||||
if (item) meta->last_seq = (uint32_t)item->valueint;
|
||||
|
||||
item = cJSON_GetObjectItem(json, "chunk_count");
|
||||
if (item) meta->chunk_count = (uint32_t)item->valueint;
|
||||
|
||||
cJSON_Delete(json);
|
||||
DEBUG_INFO("OTA", "元数据已加载: total=%u, stored=%u, seq=%u",
|
||||
meta->total_size, meta->stored_size, meta->last_seq);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// 保存元数据到 Flash
|
||||
esp_err_t meta_save(const ota_meta_t *meta)
|
||||
{
|
||||
if (!meta) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
FILE *f = fopen(META_FILE, "w");
|
||||
if (!f) {
|
||||
DEBUG_ERR("OTA", "无法打开元数据文件写入");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
fprintf(f,
|
||||
"{\n"
|
||||
" \"total_size\": %" PRIu32 ",\n"
|
||||
" \"stored_size\": %" PRIu32 ",\n"
|
||||
" \"last_seq\": %" PRIu32 ",\n"
|
||||
" \"chunk_count\": %" PRIu32 "\n"
|
||||
"}",
|
||||
meta->total_size, meta->stored_size, meta->last_seq, meta->chunk_count);
|
||||
|
||||
fclose(f);
|
||||
DEBUG_INFO("OTA", "元数据已保存到 Flash");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// 传输过程中更新元数据
|
||||
esp_err_t meta_update(uint32_t stored_size, uint32_t last_seq)
|
||||
{
|
||||
ota_meta_t meta;
|
||||
esp_err_t err = meta_load(&meta);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
meta.stored_size = stored_size;
|
||||
meta.last_seq = last_seq;
|
||||
|
||||
return meta_save(&meta);
|
||||
}
|
||||
|
||||
// OTA 完成后或中止时清除元数据
|
||||
esp_err_t meta_clear(void)
|
||||
{
|
||||
DEBUG_INFO("OTA", "清除 OTA 元数据");
|
||||
ota_meta_t meta = {};
|
||||
return meta_save(&meta);
|
||||
}
|
||||
|
||||
// Flash 写入接口
|
||||
esp_err_t flash_write_chunk(uint32_t offset, const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!g_gd32_partition) {
|
||||
g_gd32_partition = esp_partition_find_first(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "gd32_ota");
|
||||
if (!g_gd32_partition) {
|
||||
DEBUG_ERR("OTA", "找不到 gd32_ota 分区");
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
return esp_partition_write(g_gd32_partition, offset, data, len);
|
||||
}
|
||||
|
||||
esp_err_t flash_read_chunk(uint32_t offset, uint8_t *data, size_t len)
|
||||
{
|
||||
if (!g_gd32_partition) {
|
||||
g_gd32_partition = esp_partition_find_first(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "gd32_ota");
|
||||
if (!g_gd32_partition) {
|
||||
DEBUG_ERR("OTA", "找不到 gd32_ota 分区");
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
return esp_partition_read(g_gd32_partition, offset, data, len);
|
||||
}
|
||||
|
||||
// 阶段1 - 发起与 GD32 的握手
|
||||
static ota_result_t send_handshake_req(void)
|
||||
{
|
||||
DEBUG_INFO("OTA", "=== 阶段1: 握手开始 ===");
|
||||
|
||||
uint8_t tx_frame[32];
|
||||
uint8_t rx_frame[32];
|
||||
|
||||
uint8_t payload[] = {0x00, 0xAC};
|
||||
frame_build(CMD_HANDSHAKE_REQ, payload, sizeof(payload), tx_frame);
|
||||
|
||||
g_relay_state = RELAY_HANDSHAKE_SENT;
|
||||
|
||||
esp_err_t err = spi_sync_transfer(CMD_HANDSHAKE_REQ, payload, sizeof(payload),
|
||||
rx_frame, sizeof(rx_frame), 3000);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR("OTA", "HANDSHAKE_REQ 失败");
|
||||
g_relay_state = RELAY_ERROR;
|
||||
return OTA_RESULT_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
DEBUG_INFO("OTA", "握手请求已发送,等待 GD32 响应...");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
return OTA_RESULT_OK;
|
||||
}
|
||||
|
||||
// 发送随机数并与 GD32 相互认证
|
||||
static ota_result_t send_nonce_and_authenticate(void)
|
||||
{
|
||||
uint8_t tx_frame[64];
|
||||
uint8_t rx_frame[64];
|
||||
uint8_t nonce_server[16];
|
||||
|
||||
uint32_t timestamp = xTaskGetTickCount();
|
||||
uint32_t mem_addr = (uint32_t)nonce_server;
|
||||
uint32_t rand_seed = 0x12345678;
|
||||
|
||||
scramble_generate(timestamp, mem_addr, rand_seed, nonce_server);
|
||||
|
||||
DEBUG_INFO("OTA", "发送 CHALLENGE,包含 nonce_server (16B)");
|
||||
g_relay_state = RELAY_CHALLENGE_SENT;
|
||||
|
||||
frame_build(CMD_CHALLENGE, nonce_server, 16, tx_frame);
|
||||
|
||||
esp_err_t err = spi_sync_transfer(CMD_CHALLENGE, nonce_server, 16,
|
||||
rx_frame, sizeof(rx_frame), 5000);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR("OTA", "CHALLENGE 发送失败");
|
||||
g_relay_state = RELAY_ERROR;
|
||||
return OTA_RESULT_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
g_relay_state = RELAY_CHALLENGE_RESPOND;
|
||||
DEBUG_INFO("OTA", "已收到 GD32 握手数据包");
|
||||
|
||||
// 发送 SESSION_KEY(带变换后的随机数)
|
||||
uint8_t transformed_nonce[16];
|
||||
memcpy(transformed_nonce, nonce_server, 16);
|
||||
|
||||
DEBUG_INFO("OTA", "发送 SESSION_KEY(带变换后的随机数)");
|
||||
g_relay_state = RELAY_SESSION_KEY_SENT;
|
||||
|
||||
frame_build(CMD_SESSION_KEY, transformed_nonce, 16, tx_frame);
|
||||
esp_err_t err2 = spi_sync_transfer(CMD_SESSION_KEY, transformed_nonce, 16,
|
||||
rx_frame, sizeof(rx_frame), 3000);
|
||||
if (err2 != ESP_OK) {
|
||||
g_relay_state = RELAY_ERROR;
|
||||
return OTA_RESULT_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
g_relay_state = RELAY_AUTH_COMPLETE;
|
||||
DEBUG_INFO("OTA", "=== 阶段1 认证完成: 安全通道已建立 ===");
|
||||
|
||||
uint8_t k_session[16] = {0};
|
||||
session_key_init(&g_session_ctx, k_session);
|
||||
|
||||
return OTA_RESULT_OK;
|
||||
}
|
||||
|
||||
// 向 GD32 发送固件总大小
|
||||
static ota_result_t send_firmware_size(uint32_t size)
|
||||
{
|
||||
uint8_t tx_frame[32];
|
||||
uint8_t rx_frame[32];
|
||||
|
||||
DEBUG_INFO("OTA", "=== 阶段2: 发送大小 ===");
|
||||
|
||||
uint8_t size_payload[4];
|
||||
size_payload[0] = (uint8_t)(size & 0xFF);
|
||||
size_payload[1] = (uint8_t)((size >> 8) & 0xFF);
|
||||
size_payload[2] = (uint8_t)((size >> 16) & 0xFF);
|
||||
size_payload[3] = (uint8_t)((size >> 24) & 0xFF);
|
||||
|
||||
g_relay_state = RELAY_SIZE_SENT;
|
||||
|
||||
frame_build(CMD_SEND_SIZE, size_payload, 4, tx_frame);
|
||||
esp_err_t err = spi_sync_transfer(CMD_SEND_SIZE, size_payload, 4,
|
||||
rx_frame, sizeof(rx_frame), 3000);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR("OTA", "SEND_SIZE 失败");
|
||||
g_relay_state = RELAY_ERROR;
|
||||
return OTA_RESULT_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
DEBUG_INFO("OTA", "大小已发送: %u 字节,等待 GD32 确认...", size);
|
||||
g_relay_state = RELAY_SIZE_ACK;
|
||||
|
||||
return OTA_RESULT_OK;
|
||||
}
|
||||
|
||||
// 从 Flash 向 GD32 转发存储的固件(48B/包)
|
||||
static ota_result_t relay_packets_from_flash(void)
|
||||
{
|
||||
DEBUG_INFO("OTA", "=== 阶段3: 从 Flash 转发固件 ===");
|
||||
|
||||
ota_meta_t meta;
|
||||
if (meta_load(&meta) != ESP_OK) {
|
||||
return OTA_RESULT_ERR_STORAGE;
|
||||
}
|
||||
|
||||
uint32_t total_packets = (meta.total_size + 47) / 48;
|
||||
uint32_t flash_offset = 0;
|
||||
uint8_t tx_buf[64];
|
||||
uint8_t rx_buf[32];
|
||||
ota_result_t result = OTA_RESULT_OK;
|
||||
|
||||
DEBUG_INFO("OTA", "准备转发 %u 包,每包 48 字节", total_packets);
|
||||
|
||||
for (uint32_t seq = 0; seq < total_packets; seq++) {
|
||||
// 发送每个包前检查健康状态
|
||||
health_status_t health = health_check_before_ota();
|
||||
if (health == HEALTH_MON_SHUTDOWN || health == HEALTH_MON_CRITICAL) {
|
||||
DEBUG_ERR("OTA", "健康检查危险,中止 OTA");
|
||||
return OTA_RESULT_ERR_HEALTH;
|
||||
}
|
||||
|
||||
size_t chunk_size = 48;
|
||||
if (seq == total_packets - 1) {
|
||||
chunk_size = meta.total_size - (seq * 48);
|
||||
}
|
||||
|
||||
esp_err_t err = flash_read_chunk(flash_offset, &tx_buf[2], chunk_size);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR("OTA", "Flash 读取失败,偏移=%u", flash_offset);
|
||||
return OTA_RESULT_ERR_STORAGE;
|
||||
}
|
||||
|
||||
tx_buf[0] = (uint8_t)(seq & 0xFF);
|
||||
tx_buf[1] = (uint8_t)((seq >> 8) & 0xFF);
|
||||
|
||||
uint16_t crc = crc16_modbus(tx_buf, 2 + chunk_size);
|
||||
tx_buf[2 + chunk_size] = (uint8_t)(crc & 0xFF);
|
||||
tx_buf[2 + chunk_size + 1] = (uint8_t)((crc >> 8) & 0xFF);
|
||||
|
||||
g_relay_state = RELAY_PACKET_SENT;
|
||||
|
||||
uint8_t cmd = (seq == total_packets - 1) ? CMD_DATA_END : CMD_DATA_PACKET;
|
||||
size_t tx_len = 2 + chunk_size + 2;
|
||||
|
||||
err = spi_sync_transfer(cmd, tx_buf, tx_len, rx_buf, sizeof(rx_buf), 3000);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_WARN("OTA", "数据包 %u 发送失败,重试中...", seq);
|
||||
result = OTA_RESULT_ERR_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
|
||||
flash_offset += chunk_size;
|
||||
g_relay_state = RELAY_PACKET_ACK_OK;
|
||||
|
||||
// 每 10% 记录一次进度
|
||||
if (seq % (total_packets / 10 + 1) == 0) {
|
||||
DEBUG_INFO("OTA", "转发进度: %u/%u 包 (%u%%)",
|
||||
seq, total_packets, (seq * 100) / total_packets);
|
||||
}
|
||||
}
|
||||
|
||||
g_relay_state = RELAY_RECEIVE_DONE;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 结束 OTA - 发送 0x0A AC 并清理
|
||||
static ota_result_t finish_ota(void)
|
||||
{
|
||||
DEBUG_INFO("OTA", "=== 阶段4: 结束 OTA ===");
|
||||
|
||||
uint8_t tx_frame[16];
|
||||
uint8_t rx_frame[16];
|
||||
|
||||
uint8_t payload[] = {0x0A, 0xAC};
|
||||
frame_build(CMD_FINISH_OTA, payload, sizeof(payload), tx_frame);
|
||||
|
||||
g_relay_state = RELAY_FINISH_SENT;
|
||||
|
||||
esp_err_t err = spi_sync_transfer(CMD_FINISH_OTA, payload, sizeof(payload),
|
||||
rx_frame, sizeof(rx_frame), 5000);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR("OTA", "FINISH_OTA 失败");
|
||||
g_relay_state = RELAY_ERROR;
|
||||
return OTA_RESULT_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
DEBUG_INFO("OTA", "等待 GD32 跳转确认...");
|
||||
|
||||
session_key_clear(&g_session_ctx);
|
||||
memset(&g_session_ctx, 0, sizeof(session_ctx_t));
|
||||
|
||||
g_relay_state = RELAY_DONE;
|
||||
DEBUG_INFO("OTA", "=== OTA 完成 ===");
|
||||
|
||||
meta_clear();
|
||||
|
||||
return OTA_RESULT_OK;
|
||||
}
|
||||
|
||||
// 启动完整 OTA 流程的主入口
|
||||
ota_result_t ota_relay_start_handshake(void)
|
||||
{
|
||||
ota_result_t res;
|
||||
|
||||
res = send_handshake_req();
|
||||
if (res != OTA_RESULT_OK) return res;
|
||||
|
||||
res = send_nonce_and_authenticate();
|
||||
if (res != OTA_RESULT_OK) return res;
|
||||
|
||||
ota_meta_t meta;
|
||||
meta_load(&meta);
|
||||
res = send_firmware_size(meta.total_size);
|
||||
if (res != OTA_RESULT_OK) return res;
|
||||
|
||||
res = relay_packets_from_flash();
|
||||
if (res != OTA_RESULT_OK) return res;
|
||||
|
||||
res = finish_ota();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// 从 Flash 向 GD32 转发固件
|
||||
ota_result_t ota_relay_forward_firmware(void)
|
||||
{
|
||||
ota_meta_t meta;
|
||||
if (meta_load(&meta) != ESP_OK) {
|
||||
return OTA_RESULT_ERR_STORAGE;
|
||||
}
|
||||
|
||||
DEBUG_INFO("OTA", "开始固件转发: %u 字节, %u 块",
|
||||
meta.total_size, meta.chunk_count);
|
||||
|
||||
ota_result_t res = send_handshake_req();
|
||||
if (res != OTA_RESULT_OK) return res;
|
||||
|
||||
res = send_nonce_and_authenticate();
|
||||
if (res != OTA_RESULT_OK) return res;
|
||||
|
||||
res = send_firmware_size(meta.total_size);
|
||||
if (res != OTA_RESULT_OK) return res;
|
||||
|
||||
res = relay_packets_from_flash();
|
||||
if (res != OTA_RESULT_OK) return res;
|
||||
|
||||
res = finish_ota();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// 获取当前状态
|
||||
relay_state_t ota_relay_get_state(void)
|
||||
{
|
||||
return g_relay_state;
|
||||
}
|
||||
|
||||
// 重置状态机
|
||||
void ota_relay_reset(void)
|
||||
{
|
||||
g_relay_state = RELAY_IDLE;
|
||||
session_key_clear(&g_session_ctx);
|
||||
}
|
||||
|
||||
// OTA 中继 FreeRTOS 任务
|
||||
void ota_relay_task(void *pvParameters)
|
||||
{
|
||||
DEBUG_INFO("OTA", "OTA 中继任务启动");
|
||||
|
||||
g_ota_dispatch_queue = xQueueCreate(4, sizeof(uint32_t));
|
||||
g_ota_result_queue = xQueueCreate(4, sizeof(uint32_t));
|
||||
|
||||
uint32_t msg;
|
||||
BaseType_t ret = xQueueReceive(g_ota_dispatch_queue, &msg, portMAX_DELAY);
|
||||
|
||||
if (ret == pdTRUE) {
|
||||
DEBUG_INFO("OTA", "收到 OTA 调度信号,开始转发...");
|
||||
ota_result_t result = ota_relay_forward_firmware();
|
||||
|
||||
uint32_t result_msg = (uint32_t)result;
|
||||
xQueueSend(g_ota_result_queue, &result_msg, pdMS_TO_TICKS(100));
|
||||
|
||||
if (result == OTA_RESULT_OK) {
|
||||
DEBUG_INFO("OTA", "OTA 转发成功完成");
|
||||
} else {
|
||||
DEBUG_ERR("OTA", "OTA 转发失败,错误码: %d", result);
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_INFO("OTA", "OTA 中继任务结束");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* ota_relay.h - OTA 中继核心状态机
|
||||
* T2.3, T3.1 - T3.7
|
||||
*/
|
||||
#ifndef OTA_RELAY_H
|
||||
#define OTA_RELAY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
// T4.3: OTA 元数据结构,用于 Flash 存储
|
||||
#define META_FILE "/spiffs/ota_meta.json"
|
||||
|
||||
typedef struct {
|
||||
uint32_t total_size; // 固件总大小(字节)
|
||||
uint32_t stored_size; // 当前已存储大小(字节)
|
||||
uint32_t last_seq; // 最后成功存储的序列号
|
||||
uint32_t chunk_count; // 预期总块数
|
||||
uint8_t checksum[32]; // 固件 SHA256 校验(可选)
|
||||
} ota_meta_t;
|
||||
|
||||
// OTA 中继状态(对应 GD32 四阶段协议)
|
||||
typedef enum {
|
||||
// 阶段0: 空闲
|
||||
RELAY_IDLE = 0,
|
||||
|
||||
// 阶段1: 安全握手
|
||||
RELAY_HANDSHAKE_IDLE, // 等待 HANDSHAKE_REQ
|
||||
RELAY_HANDSHAKE_SENT, // 已发送 0x00 AC,等待 GD32 响应
|
||||
RELAY_CHALLENGE_SENT, // 已发送 nonce_server (16B),等待响应
|
||||
RELAY_CHALLENGE_RESPOND, // 已收到 GD32 握手数据包
|
||||
RELAY_SESSION_KEY_SENT, // 已发送变换后的随机数,等待 0xBA C0
|
||||
RELAY_AUTH_COMPLETE, // 已收到 0xBA C0,安全通道建立完成
|
||||
|
||||
// 阶段2: 传输准备
|
||||
RELAY_SIZE_SENT, // 已发送固件大小(4B LE)
|
||||
RELAY_SIZE_ACK, // 已收到 0xBA C0 + seq
|
||||
|
||||
// 阶段3: 数据传输
|
||||
RELAY_RECEIVING, // 接收并存储固件到 Flash
|
||||
RELAY_RELAY_ACTIVE, // 存储完成,开始向 GD32 转发
|
||||
RELAY_PACKET_SENT, // 已发送 DATA_PACKET,等待 GD32 ACK
|
||||
RELAY_PACKET_ACK_OK, // ACK 正常,继续下一包
|
||||
RELAY_PACKET_ACK_RETRY, // ACK 要求重发,重新发送当前包
|
||||
RELAY_RECEIVE_DONE, // 已发送 DATA_END,等待 TRANSFER_DONE
|
||||
|
||||
// 阶段4: 结束跳转
|
||||
RELAY_TRANSFER_DONE, // 已收到 0xBA C0 + FF FF FF FF
|
||||
RELAY_FINISH_SENT, // 已发送 0x0A AC,等待 GD32 确认
|
||||
RELAY_DONE, // GD32 回复 0x0A AC,OTA 完成
|
||||
|
||||
// 错误状态
|
||||
RELAY_ERROR = 0xFF
|
||||
} relay_state_t;
|
||||
|
||||
// OTA 结果码
|
||||
typedef enum {
|
||||
OTA_RESULT_OK = 0,
|
||||
OTA_RESULT_ERR_TIMEOUT = -1, // GD32 超时无响应
|
||||
OTA_RESULT_ERR_CRC = -2, // CRC 校验失败
|
||||
OTA_RESULT_ERR_AUTH = -3, // 认证失败
|
||||
OTA_RESULT_ERR_STORAGE = -4, // Flash 存储错误
|
||||
OTA_RESULT_ERR_GD32 = -5, // GD32 返回错误
|
||||
OTA_RESULT_ERR_HEALTH = -6, // 健康状态异常中止
|
||||
OTA_RESULT_ERR_USER_ABORT = -7 // 用户中止
|
||||
} ota_result_t;
|
||||
|
||||
// 外部队列句柄(在 app_main 中创建)
|
||||
extern QueueHandle_t g_ota_dispatch_queue; // 从 WiFi/BLE 到 OTA 中继
|
||||
extern QueueHandle_t g_ota_result_queue; // OTA 结果到 WiFi/BLE
|
||||
|
||||
// OTA 中继任务入口
|
||||
void ota_relay_task(void *pvParameters);
|
||||
|
||||
// T2.3: 发起与 GD32 的握手
|
||||
ota_result_t ota_relay_start_handshake(void);
|
||||
|
||||
// T3.4: 从 Flash 向 GD32 转发存储的固件(48B/包)
|
||||
ota_result_t ota_relay_forward_firmware(void);
|
||||
|
||||
// T3.7: 获取当前中继状态
|
||||
relay_state_t ota_relay_get_state(void);
|
||||
|
||||
// T3.7: 重置中继状态机
|
||||
void ota_relay_reset(void);
|
||||
|
||||
// T4.2: Flash 写入接口
|
||||
esp_err_t flash_write_chunk(uint32_t offset, const uint8_t *data, size_t len);
|
||||
esp_err_t flash_read_chunk(uint32_t offset, uint8_t *data, size_t len);
|
||||
|
||||
// T3.3: 元数据文件管理
|
||||
esp_err_t meta_load(ota_meta_t *meta);
|
||||
esp_err_t meta_save(const ota_meta_t *meta);
|
||||
esp_err_t meta_update(uint32_t stored_size, uint32_t last_seq);
|
||||
esp_err_t meta_clear(void);
|
||||
|
||||
// 当前中继状态(供健康监控外部访问)
|
||||
extern relay_state_t g_relay_state;
|
||||
|
||||
#endif // OTA_RELAY_H
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* ble_ota.c - BLE OTA GATT 服务器实现
|
||||
* T5.1 - T5.5
|
||||
*
|
||||
* 注意: ESP-IDF 6.0 中蓝牙组件有较大变化
|
||||
* 此文件暂时提供空实现,待 ESP-IDF 6.0 蓝牙 API 稳定后再适配
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#include "ble_ota.h"
|
||||
#include "ota_relay.h"
|
||||
#include "health_monitor.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
static bool s_ble_connected = false;
|
||||
|
||||
/* ESP-IDF 6.0: BLE OTA 功能暂时禁用,以下函数保留供后续适配使用 */
|
||||
#if 0
|
||||
// 处理写入控制点
|
||||
static void handle_ctrl_point_write(uint8_t *data, size_t len)
|
||||
{
|
||||
if (len < 1) return;
|
||||
|
||||
uint8_t cmd = data[0];
|
||||
DEBUG_DBG("BLE", "控制点写入: cmd=0x%02X, len=%d", cmd, len);
|
||||
|
||||
if (cmd == 0xF7 && s_ble_connected) {
|
||||
ota_meta_t meta;
|
||||
meta_load(&meta);
|
||||
if (meta.stored_size >= meta.total_size) {
|
||||
ble_ota_signal_relay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理写入数据输入(大固件分块)
|
||||
static void handle_data_in_write(uint8_t *data, size_t len)
|
||||
{
|
||||
if (len < 7) return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
size_t data_offset = 7;
|
||||
size_t data_len = len - data_offset - 4;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
meta_update(flash_offset + data_len, seq);
|
||||
|
||||
DEBUG_DBG("BLE", "已存储分块 %" PRIu16 ", offset=%" PRIu32, seq, flash_offset);
|
||||
|
||||
ota_meta_t meta;
|
||||
meta_load(&meta);
|
||||
if (meta.stored_size >= meta.total_size) {
|
||||
DEBUG_INFO("BLE", "全部固件已存储,触发转发");
|
||||
ble_ota_signal_relay();
|
||||
}
|
||||
}
|
||||
#endif /* ESP_IDF_COMPILATION */
|
||||
|
||||
// 启动 BLE GATT 服务器
|
||||
// 注意: 此函数在 ESP-IDF 6.0 中暂时返回 ESP_ERR_NOT_SUPPORTED
|
||||
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;
|
||||
}
|
||||
|
||||
// 通过 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;
|
||||
}
|
||||
|
||||
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 发送");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BLE OTA FreeRTOS 任务
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* ble_ota.h - BLE OTA GATT 服务器
|
||||
* T5.1 - T5.5
|
||||
*/
|
||||
#ifndef BLE_OTA_H
|
||||
#define BLE_OTA_H
|
||||
|
||||
#include "ota_relay.h"
|
||||
|
||||
// BLE OTA 任务入口
|
||||
void ble_ota_task(void *pvParameters);
|
||||
|
||||
// 启动 BLE GATT 服务器
|
||||
esp_err_t ble_ota_server_start(void);
|
||||
|
||||
// 通过 BLE 通知 APP GD32 的响应
|
||||
esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len);
|
||||
|
||||
// 检查 BLE 是否已连接
|
||||
bool ble_ota_is_connected(void);
|
||||
|
||||
// 信号通知中继开始(存储完成后调用)
|
||||
void ble_ota_signal_relay(void);
|
||||
|
||||
#endif // BLE_OTA_H
|
||||
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* wifi_ota.c - WiFi OTA 服务器实现
|
||||
* T4.1 - T4.5
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_timer.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "lwip/netdb.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_netif_types.h"
|
||||
|
||||
#include "app_config.h"
|
||||
#include "wifi_ota.h"
|
||||
#include "ota_relay.h"
|
||||
#include "health_monitor.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
static ota_wifi_mode_t s_wifi_mode = OTA_WIFI_MODE_AP;
|
||||
static bool s_ota_storage_done = false;
|
||||
static bool s_relay_triggered = false;
|
||||
static bool s_sta_connected = false;
|
||||
|
||||
static httpd_handle_t s_http_server = NULL;
|
||||
|
||||
// ----------------------------------------
|
||||
// WiFi AP 模式初始化
|
||||
// ----------------------------------------
|
||||
static esp_err_t wifi_init_ap(void)
|
||||
{
|
||||
DEBUG_INFO("WIFI", "初始化 WiFi AP 模式...");
|
||||
|
||||
s_wifi_mode = OTA_WIFI_MODE_AP;
|
||||
|
||||
wifi_config_t wifi_config = {};
|
||||
memcpy(wifi_config.ap.ssid, WIFI_AP_SSID, strlen(WIFI_AP_SSID));
|
||||
wifi_config.ap.ssid_len = strlen(WIFI_AP_SSID);
|
||||
memcpy(wifi_config.ap.password, WIFI_AP_PASSWORD, strlen(WIFI_AP_PASSWORD));
|
||||
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;
|
||||
|
||||
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.4.1");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// WiFi STA 模式初始化
|
||||
// ----------------------------------------
|
||||
static esp_err_t wifi_init_sta(void)
|
||||
{
|
||||
DEBUG_INFO("WIFI", "初始化 WiFi STA 模式...");
|
||||
|
||||
s_wifi_mode = OTA_WIFI_MODE_STA;
|
||||
|
||||
wifi_config_t wifi_config = {};
|
||||
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK;
|
||||
wifi_config.sta.pmf_cfg.capable = true;
|
||||
wifi_config.sta.pmf_cfg.required = false;
|
||||
memcpy(wifi_config.sta.ssid, WIFI_STA_SSID, sizeof(wifi_config.sta.ssid));
|
||||
memcpy(wifi_config.sta.password, WIFI_STA_PASSWORD, sizeof(wifi_config.sta.password));
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
DEBUG_INFO("WIFI", "WiFi STA 连接中, SSID: %s...", WIFI_STA_SSID);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// 等待 STA 连接成功并获取 IP
|
||||
static esp_err_t wifi_wait_sta_connected(char *ip_str, size_t len)
|
||||
{
|
||||
int retry = 0;
|
||||
while (retry < 60) { // 最多等 6 秒
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
wifi_ap_record_t ap_info;
|
||||
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
|
||||
s_sta_connected = true;
|
||||
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, len, IPSTR, IP2STR(&ip_info.ip));
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
retry++;
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
// 设置 STA 静态 IP
|
||||
static void wifi_set_static_ip(void)
|
||||
{
|
||||
#if WIFI_STA_USE_STATIC_IP
|
||||
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (sta_netif) {
|
||||
ESP_ERROR_CHECK(esp_netif_dhcpc_stop(sta_netif));
|
||||
|
||||
esp_netif_ip_info_t info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
inet_pton(AF_INET, WIFI_STA_STATIC_IP, &info.ip);
|
||||
inet_pton(AF_INET, WIFI_STA_STATIC_GW, &info.gw);
|
||||
inet_pton(AF_INET, WIFI_STA_STATIC_NETMASK, &info.netmask);
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_set_ip_info(sta_netif, &info));
|
||||
DEBUG_INFO("WIFI", "静态 IP 已设置: %s", WIFI_STA_STATIC_IP);
|
||||
}
|
||||
#else
|
||||
DEBUG_INFO("WIFI", "使用 DHCP 获取 IP");
|
||||
#endif
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// HTTP POST /ota/upload 处理器
|
||||
// ----------------------------------------
|
||||
static esp_err_t ota_upload_handler(httpd_req_t *req)
|
||||
{
|
||||
DEBUG_INFO("WIFI", "收到固件上传请求, 内容长度: %d", req->content_len);
|
||||
|
||||
if (req->content_len > OTA_MAX_FIRMWARE_SIZE) {
|
||||
DEBUG_ERR("WIFI", "固件太大: %d 字节", req->content_len);
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "固件太大");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
uint8_t buf[OTA_BLOCK_SIZE];
|
||||
int received = 0;
|
||||
int first_chunk = 1;
|
||||
|
||||
while (received < req->content_len) {
|
||||
int remaining = req->content_len - received;
|
||||
int to_read = remaining < sizeof(buf) ? remaining : sizeof(buf);
|
||||
|
||||
int ret = httpd_req_recv(req, (char *)buf, to_read);
|
||||
if (ret <= 0) {
|
||||
DEBUG_ERR("WIFI", "请求接收失败");
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "接收失败");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (first_chunk && ret >= 6) {
|
||||
uint16_t seq = buf[0] | (buf[1] << 8);
|
||||
uint32_t total_size = buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24);
|
||||
|
||||
if (seq == 0) {
|
||||
ota_meta_t meta = {
|
||||
.total_size = total_size,
|
||||
.stored_size = 0,
|
||||
.last_seq = 0,
|
||||
.chunk_count = (total_size + 4095) / 4096,
|
||||
.checksum = {0}
|
||||
};
|
||||
meta_save(&meta);
|
||||
DEBUG_INFO("WIFI", "开始固件存储: %u 字节", total_size);
|
||||
}
|
||||
first_chunk = 0;
|
||||
}
|
||||
|
||||
uint32_t flash_offset = (received / OTA_BLOCK_SIZE) * OTA_BLOCK_SIZE;
|
||||
esp_err_t err = flash_write_chunk(flash_offset, buf, ret);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR("WIFI", "Flash 写入失败 at offset %u", flash_offset);
|
||||
}
|
||||
|
||||
meta_update(flash_offset + ret, received / OTA_BLOCK_SIZE);
|
||||
|
||||
received += ret;
|
||||
DEBUG_DBG("WIFI", "已接收: %d / %d 字节", received, req->content_len);
|
||||
}
|
||||
|
||||
DEBUG_INFO("WIFI", "固件存储完成: %d 字节", received);
|
||||
|
||||
ota_meta_t meta;
|
||||
meta_load(&meta);
|
||||
if (meta.stored_size >= meta.total_size) {
|
||||
s_ota_storage_done = true;
|
||||
DEBUG_INFO("WIFI", "全部固件已存储,触发转发...");
|
||||
wifi_ota_signal_relay();
|
||||
}
|
||||
|
||||
const char resp[] = "{\"status\": \"ok\", \"progress\": 100}";
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, strlen(resp));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// HTTP POST /ota/relay 处理器
|
||||
static esp_err_t ota_relay_trigger_handler(httpd_req_t *req)
|
||||
{
|
||||
(void)req;
|
||||
DEBUG_INFO("WIFI", "收到触发 OTA 中继请求");
|
||||
wifi_ota_signal_relay();
|
||||
|
||||
const char resp[] = "{\"status\": \"ok\", \"message\": \"OTA relay triggered\"}";
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, strlen(resp));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// HTTP GET /ota/status 处理器
|
||||
static esp_err_t ota_status_handler(httpd_req_t *req)
|
||||
{
|
||||
ota_meta_t meta;
|
||||
meta_load(&meta);
|
||||
|
||||
char resp[256];
|
||||
int len = snprintf(resp, sizeof(resp),
|
||||
"{\"total_size\": %lu, \"stored_size\": %lu, \"last_seq\": %lu, "
|
||||
"\"storage_done\": %s, \"relay_state\": %d}",
|
||||
(unsigned long)meta.total_size, (unsigned long)meta.stored_size,
|
||||
(unsigned long)meta.last_seq,
|
||||
s_ota_storage_done ? "true" : "false",
|
||||
g_relay_state);
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, len);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// HTTP GET /health 处理器
|
||||
static esp_err_t health_handler(httpd_req_t *req)
|
||||
{
|
||||
health_state_t state = health_get_current();
|
||||
|
||||
char resp[128];
|
||||
int len = snprintf(resp, sizeof(resp),
|
||||
"{\"health\": %d, \"free_heap\": %lu}",
|
||||
state, (unsigned long)esp_get_free_heap_size());
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, len);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// HTTP GET /wifi/status 处理器
|
||||
static esp_err_t wifi_status_handler(httpd_req_t *req)
|
||||
{
|
||||
char resp[256];
|
||||
char ip_str[16] = "0.0.0.0";
|
||||
int8_t rssi = -127;
|
||||
|
||||
if (s_wifi_mode == OTA_WIFI_MODE_STA && s_sta_connected) {
|
||||
esp_netif_ip_info_t ip_info;
|
||||
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (sta_netif && esp_netif_get_ip_info(sta_netif, &ip_info) == ESP_OK) {
|
||||
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip));
|
||||
}
|
||||
wifi_ap_record_t ap_info;
|
||||
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
|
||||
rssi = ap_info.rssi;
|
||||
}
|
||||
}
|
||||
|
||||
int len = snprintf(resp, sizeof(resp),
|
||||
"{\"mode\": \"%s\", \"ip\": \"%s\", \"rssi\": %d, \"connected\": %s}",
|
||||
s_wifi_mode == OTA_WIFI_MODE_AP ? "AP" : "STA",
|
||||
ip_str, rssi,
|
||||
(s_wifi_mode == OTA_WIFI_MODE_STA) ? (s_sta_connected ? "true" : "false") : "N/A");
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, len);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// 启动 HTTP OTA 服务器
|
||||
// ----------------------------------------
|
||||
static esp_err_t http_ota_server_start(void)
|
||||
{
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.server_port = HTTP_SERVER_PORT;
|
||||
config.stack_size = HTTP_SERVER_STACK_SIZE;
|
||||
|
||||
DEBUG_INFO("WIFI", "启动 HTTP 服务器,端口 %d...", config.server_port);
|
||||
|
||||
esp_err_t err = httpd_start(&s_http_server, &config);
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR("WIFI", "HTTP 服务器启动失败: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
httpd_uri_t upload_uri = {
|
||||
.uri = "/ota/upload",
|
||||
.method = HTTP_POST,
|
||||
.handler = ota_upload_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(s_http_server, &upload_uri);
|
||||
|
||||
httpd_uri_t relay_uri = {
|
||||
.uri = "/ota/relay",
|
||||
.method = HTTP_POST,
|
||||
.handler = ota_relay_trigger_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(s_http_server, &relay_uri);
|
||||
|
||||
httpd_uri_t status_uri = {
|
||||
.uri = "/ota/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = ota_status_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(s_http_server, &status_uri);
|
||||
|
||||
httpd_uri_t health_uri = {
|
||||
.uri = "/health",
|
||||
.method = HTTP_GET,
|
||||
.handler = health_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(s_http_server, &health_uri);
|
||||
|
||||
httpd_uri_t wifi_status_uri = {
|
||||
.uri = "/wifi/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = wifi_status_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(s_http_server, &wifi_status_uri);
|
||||
|
||||
DEBUG_INFO("WIFI", "HTTP 服务器已启动");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// 公开 API
|
||||
// ----------------------------------------
|
||||
void wifi_ota_signal_relay(void)
|
||||
{
|
||||
if (s_relay_triggered) {
|
||||
DEBUG_WARN("WIFI", "转发已经触发过");
|
||||
return;
|
||||
}
|
||||
|
||||
s_relay_triggered = true;
|
||||
|
||||
if (g_ota_dispatch_queue) {
|
||||
uint32_t msg = 1;
|
||||
xQueueSend(g_ota_dispatch_queue, &msg, pdMS_TO_TICKS(100));
|
||||
DEBUG_INFO("WIFI", "OTA 调度信号已发送到中继任务");
|
||||
}
|
||||
}
|
||||
|
||||
ota_wifi_mode_t wifi_get_mode(void)
|
||||
{
|
||||
return s_wifi_mode;
|
||||
}
|
||||
|
||||
int8_t wifi_get_rssi(void)
|
||||
{
|
||||
if (s_wifi_mode != OTA_WIFI_MODE_STA || !s_sta_connected) {
|
||||
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;
|
||||
}
|
||||
|
||||
bool wifi_is_connected(void)
|
||||
{
|
||||
return s_sta_connected;
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// WiFi OTA FreeRTOS 任务
|
||||
// ----------------------------------------
|
||||
void wifi_ota_task(void *pvParameters)
|
||||
{
|
||||
(void)pvParameters;
|
||||
|
||||
DEBUG_INFO("WIFI", "WiFi OTA 任务启动");
|
||||
|
||||
// 初始化 NVS
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR("WIFI", "NVS 初始化失败: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// 初始化网络接口
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
|
||||
// STA 优先,失败自动切 AP
|
||||
#ifdef CONFIG_WIFI_MODE_STA
|
||||
err = wifi_init_sta();
|
||||
if (err == ESP_OK) {
|
||||
char ip_str[16] = {0};
|
||||
|
||||
// 设置静态 IP(如果配置了)
|
||||
wifi_set_static_ip();
|
||||
|
||||
// 等待连接成功
|
||||
err = wifi_wait_sta_connected(ip_str, sizeof(ip_str));
|
||||
if (err == ESP_OK) {
|
||||
DEBUG_INFO("WIFI", "STA 已连接, IP: %s", ip_str);
|
||||
} else {
|
||||
DEBUG_WARN("WIFI", "STA 连接超时,切换到 AP 模式...");
|
||||
esp_wifi_stop();
|
||||
s_wifi_mode = OTA_WIFI_MODE_AP;
|
||||
wifi_init_ap();
|
||||
}
|
||||
}
|
||||
#else
|
||||
wifi_init_ap();
|
||||
#endif
|
||||
|
||||
// 启动 HTTP 服务器
|
||||
err = http_ota_server_start();
|
||||
if (err != ESP_OK) {
|
||||
DEBUG_ERR("WIFI", "HTTP 服务器启动失败");
|
||||
}
|
||||
|
||||
DEBUG_INFO("WIFI", "WiFi OTA 服务器就绪");
|
||||
|
||||
// 定期检查 WiFi 状态
|
||||
while (1) {
|
||||
int8_t rssi = wifi_get_rssi();
|
||||
if (rssi != -127) {
|
||||
health_update_from_wifi_rssi(rssi);
|
||||
|
||||
if (rssi < WIFI_RSSI_CRITICAL) {
|
||||
DEBUG_WARN("WIFI", "WiFi RSSI 危险: %d dBm", rssi);
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* wifi_ota.h - WiFi OTA 服务器
|
||||
* T4.1 - T4.5
|
||||
*/
|
||||
#ifndef WIFI_OTA_H
|
||||
#define WIFI_OTA_H
|
||||
|
||||
#include "ota_relay.h"
|
||||
|
||||
// WiFi 模式: AP 或 STA
|
||||
typedef enum {
|
||||
OTA_WIFI_MODE_AP, // AP 模式(ESP32 作为热点)
|
||||
OTA_WIFI_MODE_STA // STA 模式(连接路由器)
|
||||
} ota_wifi_mode_t;
|
||||
|
||||
// WiFi OTA 任务入口
|
||||
void wifi_ota_task(void *pvParameters);
|
||||
|
||||
// 信号通知 OTA 中继开始(存储完成后调用)
|
||||
void wifi_ota_signal_relay(void);
|
||||
|
||||
// 获取当前 WiFi 模式
|
||||
ota_wifi_mode_t wifi_get_mode(void);
|
||||
|
||||
// 获取 WiFi RSSI
|
||||
int8_t wifi_get_rssi(void);
|
||||
|
||||
// STA 是否已连接
|
||||
bool wifi_is_connected(void);
|
||||
|
||||
#endif // WIFI_OTA_H
|
||||
@@ -0,0 +1,117 @@
|
||||
/* 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();
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
/*
|
||||
* protocol.c - 协议层实现
|
||||
* T2.1, T2.2, T1.3
|
||||
* ESP-IDF 6.0 适配版本 - 使用软件 AES 实现 + 硬件 RNG
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "esp_random.h"
|
||||
#include "app_config.h"
|
||||
#include "protocol.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
static bool s_protocol_initialized = false;
|
||||
|
||||
// ========== 软件 AES-128 实现 ==========
|
||||
|
||||
// AES S-Box
|
||||
static const uint8_t sbox[256] = {
|
||||
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
|
||||
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
|
||||
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
|
||||
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
|
||||
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
|
||||
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
|
||||
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
|
||||
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
|
||||
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
|
||||
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
|
||||
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
|
||||
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
|
||||
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
|
||||
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
|
||||
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
|
||||
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
|
||||
};
|
||||
|
||||
// AES 逆 S-Box
|
||||
static const uint8_t rsbox[256] = {
|
||||
0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
|
||||
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
|
||||
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
|
||||
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
|
||||
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
|
||||
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
|
||||
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
|
||||
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
|
||||
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
|
||||
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
|
||||
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
|
||||
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
|
||||
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
|
||||
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
|
||||
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
|
||||
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d
|
||||
};
|
||||
|
||||
// AES Rcon
|
||||
static const uint8_t Rcon[11] = {
|
||||
0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36
|
||||
};
|
||||
|
||||
// AES 状态操作
|
||||
#define AES_BLOCK_SIZE 16
|
||||
|
||||
typedef struct {
|
||||
uint8_t round_key[176]; // 10 rounds * 16 bytes + initial key
|
||||
} aes128_context_t;
|
||||
|
||||
// GF(2^8) 乘法
|
||||
static uint8_t gmul(uint8_t a, uint8_t b)
|
||||
{
|
||||
uint8_t p = 0, hbit;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (b & 1) {
|
||||
p ^= a;
|
||||
}
|
||||
hbit = a & 0x80;
|
||||
a <<= 1;
|
||||
if (hbit) {
|
||||
a ^= 0x1b; // 模不可约多项式 x^8 + x^4 + x^3 + x + 1
|
||||
}
|
||||
b >>= 1;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// 密钥扩展
|
||||
static void aes128_key_expansion(const uint8_t *key, aes128_context_t *ctx)
|
||||
{
|
||||
uint8_t temp[4];
|
||||
memcpy(ctx->round_key, key, 16);
|
||||
|
||||
for (int i = 4; i < 44; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
temp[j] = ctx->round_key[(i - 1) * 4 + j];
|
||||
}
|
||||
|
||||
if (i % 4 == 0) {
|
||||
// RotWord
|
||||
uint8_t t = temp[0];
|
||||
temp[0] = temp[1];
|
||||
temp[1] = temp[2];
|
||||
temp[2] = temp[3];
|
||||
temp[3] = t;
|
||||
|
||||
// SubWord
|
||||
temp[0] = sbox[temp[0]];
|
||||
temp[1] = sbox[temp[1]];
|
||||
temp[2] = sbox[temp[2]];
|
||||
temp[3] = sbox[temp[3]];
|
||||
|
||||
temp[0] ^= Rcon[i / 4];
|
||||
}
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
ctx->round_key[i * 4 + j] = ctx->round_key[(i - 4) * 4 + j] ^ temp[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 字节替换
|
||||
static void sub_bytes(uint8_t *state)
|
||||
{
|
||||
for (int i = 0; i < 16; i++) {
|
||||
state[i] = sbox[state[i]];
|
||||
}
|
||||
}
|
||||
|
||||
// 逆字节替换
|
||||
static void inv_sub_bytes(uint8_t *state)
|
||||
{
|
||||
for (int i = 0; i < 16; i++) {
|
||||
state[i] = rsbox[state[i]];
|
||||
}
|
||||
}
|
||||
|
||||
// 行移位
|
||||
static void shift_rows(uint8_t *state)
|
||||
{
|
||||
uint8_t temp;
|
||||
|
||||
// 第1行左移1位
|
||||
temp = state[1];
|
||||
state[1] = state[5];
|
||||
state[5] = state[9];
|
||||
state[9] = state[13];
|
||||
state[13] = temp;
|
||||
|
||||
// 第2行左移2位
|
||||
temp = state[2];
|
||||
state[2] = state[10];
|
||||
state[10] = temp;
|
||||
temp = state[6];
|
||||
state[6] = state[14];
|
||||
state[14] = temp;
|
||||
|
||||
// 第3行左移3位
|
||||
temp = state[15];
|
||||
state[15] = state[11];
|
||||
state[11] = state[7];
|
||||
state[7] = state[3];
|
||||
state[3] = temp;
|
||||
}
|
||||
|
||||
// 逆行移位
|
||||
static void inv_shift_rows(uint8_t *state)
|
||||
{
|
||||
uint8_t temp;
|
||||
|
||||
// 第1行右移1位
|
||||
temp = state[13];
|
||||
state[13] = state[9];
|
||||
state[9] = state[5];
|
||||
state[5] = state[1];
|
||||
state[1] = temp;
|
||||
|
||||
// 第2行右移2位
|
||||
temp = state[2];
|
||||
state[2] = state[10];
|
||||
state[10] = temp;
|
||||
temp = state[6];
|
||||
state[6] = state[14];
|
||||
state[14] = temp;
|
||||
|
||||
// 第3行右移3位
|
||||
temp = state[3];
|
||||
state[3] = state[7];
|
||||
state[7] = state[11];
|
||||
state[11] = state[15];
|
||||
state[15] = temp;
|
||||
}
|
||||
|
||||
// 列混合
|
||||
static void mix_columns(uint8_t *state)
|
||||
{
|
||||
uint8_t temp[16];
|
||||
memcpy(temp, state, 16);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int c = i * 4;
|
||||
state[c] = gmul(2, temp[c]) ^ gmul(3, temp[c + 1]) ^ temp[c + 2] ^ temp[c + 3];
|
||||
state[c + 1] = temp[c] ^ gmul(2, temp[c + 1]) ^ gmul(3, temp[c + 2]) ^ temp[c + 3];
|
||||
state[c + 2] = temp[c] ^ temp[c + 1] ^ gmul(2, temp[c + 2]) ^ gmul(3, temp[c + 3]);
|
||||
state[c + 3] = gmul(3, temp[c]) ^ temp[c + 1] ^ temp[c + 2] ^ gmul(2, temp[c + 3]);
|
||||
}
|
||||
}
|
||||
|
||||
// 逆列混合
|
||||
static void inv_mix_columns(uint8_t *state)
|
||||
{
|
||||
uint8_t temp[16];
|
||||
memcpy(temp, state, 16);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int c = i * 4;
|
||||
state[c] = gmul(14, temp[c]) ^ gmul(11, temp[c + 1]) ^ gmul(13, temp[c + 2]) ^ gmul(9, temp[c + 3]);
|
||||
state[c + 1] = gmul(9, temp[c]) ^ gmul(14, temp[c + 1]) ^ gmul(11, temp[c + 2]) ^ gmul(13, temp[c + 3]);
|
||||
state[c + 2] = gmul(13, temp[c]) ^ gmul(9, temp[c + 1]) ^ gmul(14, temp[c + 2]) ^ gmul(11, temp[c + 3]);
|
||||
state[c + 3] = gmul(11, temp[c]) ^ gmul(13, temp[c + 1]) ^ gmul(9, temp[c + 2]) ^ gmul(14, temp[c + 3]);
|
||||
}
|
||||
}
|
||||
|
||||
// 轮密钥加
|
||||
static void add_round_key(uint8_t *state, const uint8_t *round_key)
|
||||
{
|
||||
for (int i = 0; i < 16; i++) {
|
||||
state[i] ^= round_key[i];
|
||||
}
|
||||
}
|
||||
|
||||
// AES-128 加密单个块
|
||||
static void aes128_encrypt_block(const uint8_t *input, uint8_t *output, const aes128_context_t *ctx)
|
||||
{
|
||||
uint8_t state[16];
|
||||
|
||||
// 复制输入到状态(列优先顺序)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
state[i * 4 + j] = input[j * 4 + i];
|
||||
}
|
||||
}
|
||||
|
||||
// 初始轮密钥加
|
||||
add_round_key(state, ctx->round_key);
|
||||
|
||||
// 9轮加密
|
||||
for (int round = 1; round < 10; round++) {
|
||||
sub_bytes(state);
|
||||
shift_rows(state);
|
||||
mix_columns(state);
|
||||
add_round_key(state, ctx->round_key + round * 16);
|
||||
}
|
||||
|
||||
// 最后一轮(无列混合)
|
||||
sub_bytes(state);
|
||||
shift_rows(state);
|
||||
add_round_key(state, ctx->round_key + 10 * 16);
|
||||
|
||||
// 复制状态到输出(列优先顺序)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
output[j * 4 + i] = state[i * 4 + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AES-128 解密单个块
|
||||
static void aes128_decrypt_block(const uint8_t *input, uint8_t *output, const aes128_context_t *ctx)
|
||||
{
|
||||
uint8_t state[16];
|
||||
|
||||
// 复制输入到状态(列优先顺序)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
state[i * 4 + j] = input[j * 4 + i];
|
||||
}
|
||||
}
|
||||
|
||||
// 初始轮密钥加
|
||||
add_round_key(state, ctx->round_key + 10 * 16);
|
||||
|
||||
// 9轮解密
|
||||
for (int round = 9; round >= 1; round--) {
|
||||
inv_shift_rows(state);
|
||||
inv_sub_bytes(state);
|
||||
add_round_key(state, ctx->round_key + round * 16);
|
||||
inv_mix_columns(state);
|
||||
}
|
||||
|
||||
// 最后一轮(无逆列混合)
|
||||
inv_shift_rows(state);
|
||||
inv_sub_bytes(state);
|
||||
add_round_key(state, ctx->round_key);
|
||||
|
||||
// 复制状态到输出(列优先顺序)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
output[j * 4 + i] = state[i * 4 + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CBC 模式加密
|
||||
static void aes128_cbc_encrypt(const uint8_t *input, uint8_t *output, size_t length,
|
||||
const uint8_t *iv, const aes128_context_t *ctx)
|
||||
{
|
||||
uint8_t prev[16];
|
||||
memcpy(prev, iv, 16);
|
||||
|
||||
for (size_t i = 0; i < length; i += 16) {
|
||||
uint8_t block[16];
|
||||
memcpy(block, input + i, 16);
|
||||
|
||||
// XOR with previous ciphertext (or IV for first block)
|
||||
for (int j = 0; j < 16; j++) {
|
||||
block[j] ^= prev[j];
|
||||
}
|
||||
|
||||
aes128_encrypt_block(block, output + i, ctx);
|
||||
memcpy(prev, output + i, 16);
|
||||
}
|
||||
}
|
||||
|
||||
// CBC 模式解密
|
||||
static void aes128_cbc_decrypt(const uint8_t *input, uint8_t *output, size_t length,
|
||||
const uint8_t *iv, const aes128_context_t *ctx)
|
||||
{
|
||||
uint8_t prev[16];
|
||||
memcpy(prev, iv, 16);
|
||||
|
||||
for (size_t i = 0; i < length; i += 16) {
|
||||
aes128_decrypt_block(input + i, output + i, ctx);
|
||||
|
||||
// XOR with previous ciphertext (or IV for first block)
|
||||
for (int j = 0; j < 16; j++) {
|
||||
output[i + j] ^= prev[j];
|
||||
}
|
||||
|
||||
memcpy(prev, input + i, 16);
|
||||
}
|
||||
}
|
||||
|
||||
void protocol_init(void)
|
||||
{
|
||||
if (s_protocol_initialized) return;
|
||||
|
||||
DEBUG_INFO("PROT", "初始化协议层(CRC16、软件AES)...");
|
||||
|
||||
s_protocol_initialized = true;
|
||||
DEBUG_INFO("PROT", "协议层就绪");
|
||||
}
|
||||
|
||||
// CRC16 Modbus(LE,低字节在前)
|
||||
uint16_t crc16_modbus(const uint8_t *data, size_t len)
|
||||
{
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 0x0001) {
|
||||
crc = (crc >> 1) ^ 0xA001;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (crc >> 8) | (crc << 8);
|
||||
}
|
||||
|
||||
// 构建 SPI 帧: SOF + CMD + LEN(2LE) + PAYLOAD + CRC16(2LE)
|
||||
int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame)
|
||||
{
|
||||
if (len > FRAME_MAX_PAYLOAD) {
|
||||
DEBUG_ERR("PROT", "Payload 太大: %zu > %d", len, FRAME_MAX_PAYLOAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
out_frame[idx++] = FRAME_SOF;
|
||||
out_frame[idx++] = cmd;
|
||||
out_frame[idx++] = (uint8_t)(len & 0xFF);
|
||||
out_frame[idx++] = (uint8_t)((len >> 8) & 0xFF);
|
||||
|
||||
if (payload && len > 0) {
|
||||
memcpy(&out_frame[idx], payload, len);
|
||||
idx += len;
|
||||
}
|
||||
|
||||
uint16_t crc = crc16_modbus(out_frame, idx);
|
||||
out_frame[idx++] = (uint8_t)(crc & 0xFF);
|
||||
out_frame[idx++] = (uint8_t)((crc >> 8) & 0xFF);
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
// 解析 SPI 帧
|
||||
int frame_parse(const uint8_t *frame, size_t frame_len,
|
||||
uint8_t *out_cmd, uint8_t *out_payload, size_t *out_len)
|
||||
{
|
||||
if (frame_len < 6) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
|
||||
if (frame[idx++] != FRAME_SOF) {
|
||||
DEBUG_WARN("PROT", "无效 SOF: 0x%02X", frame[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t cmd = frame[idx++];
|
||||
|
||||
size_t payload_len = frame[idx] | (frame[idx + 1] << 8);
|
||||
idx += 2;
|
||||
|
||||
if (payload_len > FRAME_MAX_PAYLOAD) {
|
||||
DEBUG_ERR("PROT", "Payload 长度太大: %zu", payload_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t expected_len = 1 + 1 + 2 + payload_len + 2;
|
||||
if (frame_len < expected_len) {
|
||||
DEBUG_WARN("PROT", "帧太短: %zu < %zu", frame_len, expected_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint16_t received_crc = frame[idx + payload_len] | (frame[idx + payload_len + 1] << 8);
|
||||
uint16_t calc_crc = crc16_modbus(frame, idx + payload_len);
|
||||
|
||||
if (received_crc != calc_crc) {
|
||||
DEBUG_WARN("PROT", "CRC 不匹配: 0x%04X vs 0x%04X", received_crc, calc_crc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (out_payload && out_len) {
|
||||
memcpy(out_payload, &frame[idx], payload_len);
|
||||
*out_len = payload_len;
|
||||
}
|
||||
if (out_cmd) {
|
||||
*out_cmd = cmd;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// AES-128 CBC 加密
|
||||
// 注意: 函数名带 protocol_ 前缀以避免与 ESP-IDF wpa_supplicant 组件冲突
|
||||
int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
|
||||
const uint8_t *key, const uint8_t *iv,
|
||||
uint8_t *ciphertext_out)
|
||||
{
|
||||
size_t padded_len = ((len + 15) / 16) * 16;
|
||||
uint8_t padded[256];
|
||||
|
||||
memcpy(padded, plaintext, len);
|
||||
uint8_t pad = (uint8_t)(16 - (len % 16));
|
||||
memset(&padded[len], pad, pad);
|
||||
|
||||
aes128_context_t ctx;
|
||||
aes128_key_expansion(key, &ctx);
|
||||
aes128_cbc_encrypt(padded, ciphertext_out, padded_len, iv, &ctx);
|
||||
|
||||
return padded_len;
|
||||
}
|
||||
|
||||
// AES-128 CBC 解密
|
||||
// 注意: 函数名带 protocol_ 前缀以避免与 ESP-IDF wpa_supplicant 组件冲突
|
||||
int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len,
|
||||
const uint8_t *key, const uint8_t *iv,
|
||||
uint8_t *plaintext_out)
|
||||
{
|
||||
if (len % 16 != 0) {
|
||||
DEBUG_ERR("PROT", "Ciphertext 长度不是 16 的倍数: %zu", len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
aes128_context_t ctx;
|
||||
aes128_key_expansion(key, &ctx);
|
||||
aes128_cbc_decrypt(ciphertext, plaintext_out, len, iv, &ctx);
|
||||
|
||||
// 移除 PKCS#7 填充
|
||||
uint8_t pad = plaintext_out[len - 1];
|
||||
if (pad > 16) return -1;
|
||||
return len - pad;
|
||||
}
|
||||
|
||||
// 搅伴算法 - 生成随机数
|
||||
void scramble_generate(uint32_t timestamp, uint32_t mem_addr,
|
||||
uint32_t rand_seed, uint8_t *nonce_out)
|
||||
{
|
||||
uint8_t state[16];
|
||||
|
||||
memcpy(&state[0], ×tamp, 4);
|
||||
memcpy(&state[4], &mem_addr, 4);
|
||||
memcpy(&state[8], &rand_seed, 4);
|
||||
memcpy(&state[12], SCRAMBLE_CONST, 4);
|
||||
|
||||
for (int round = 0; round < 3; round++) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
state[i] ^= SCRAMBLE_CONST[i];
|
||||
state[i] = (state[i] << 1) | (state[i] >> 7);
|
||||
}
|
||||
}
|
||||
|
||||
for (int round = 0; round < 2; round++) {
|
||||
uint32_t *words = (uint32_t *)state;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
words[i] ^= SCRAMBLE_CONST[i * 4 % 16] | ((uint32_t)SCRAMBLE_CONST[i * 4 + 1] << 8);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(nonce_out, state, 16);
|
||||
}
|
||||
|
||||
// 会话密钥初始化
|
||||
int session_key_init(session_ctx_t *sess, const uint8_t *k_session)
|
||||
{
|
||||
if (!sess || !k_session) return -1;
|
||||
|
||||
memset(sess, 0, sizeof(session_ctx_t));
|
||||
memcpy(sess->key, k_session, 16);
|
||||
memset(sess->iv, 0, 16);
|
||||
sess->used = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void session_key_clear(session_ctx_t *sess)
|
||||
{
|
||||
if (sess) {
|
||||
memset(sess->key, 0, 16);
|
||||
memset(sess->iv, 0, 16);
|
||||
sess->used = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out)
|
||||
{
|
||||
if (!sess || !in || !out) return -1;
|
||||
|
||||
uint8_t iv_copy[16];
|
||||
memcpy(iv_copy, sess->iv, 16);
|
||||
|
||||
int ret = protocol_aes_encrypt(in, len, sess->key, iv_copy, out);
|
||||
|
||||
if (ret > 0) {
|
||||
memcpy(sess->iv, &out[ret - 16], 16);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out)
|
||||
{
|
||||
if (!sess || !in || !out) return -1;
|
||||
|
||||
uint8_t iv_copy[16];
|
||||
memcpy(iv_copy, sess->iv, 16);
|
||||
|
||||
int ret = protocol_aes_decrypt(in, len, sess->key, iv_copy, out);
|
||||
|
||||
if (ret > 0) {
|
||||
memcpy(sess->iv, &in[len - 16], 16);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 使用 ESP-IDF 硬件 RNG 生成随机数
|
||||
void generate_random_nonce(uint8_t *nonce, size_t len)
|
||||
{
|
||||
esp_fill_random(nonce, len);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* protocol.h - 帧格式、CRC16、AES 加密解密
|
||||
* T2.2, T1.3
|
||||
* ESP-IDF 6.0 适配版本 - 使用软件 AES 实现 + 硬件 RNG
|
||||
*/
|
||||
#ifndef PROTOCOL_H
|
||||
#define PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "app_config.h"
|
||||
|
||||
// 帧格式: SOF(1B) + CMD(1B) + LEN(2B LE) + PAYLOAD(LEN) + CRC16(2B LE)
|
||||
#define FRAME_SOF (0xEB) // 帧起始标记
|
||||
#define FRAME_MAX_PAYLOAD (64) // 安全最大 payload
|
||||
#define FRAME_OVERHEAD (6) // SOF + CMD + LEN(2) + CRC16(2)
|
||||
|
||||
// 命令码(对应 GD32 OTA 协议)
|
||||
#define CMD_HANDSHAKE_REQ (0xF0) // 握手请求
|
||||
#define CMD_CHALLENGE (0xF1) // 挑战/随机数
|
||||
#define CMD_SESSION_KEY (0xF2) // 会话密钥
|
||||
#define CMD_SEND_SIZE (0xF3) // 发送固件大小
|
||||
#define CMD_DATA_PACKET (0xF4) // 数据包
|
||||
#define CMD_DATA_END (0xF5) // 数据结束
|
||||
#define CMD_TRANSFER_DONE (0xF6) // 传输完成
|
||||
#define CMD_FINISH_OTA (0xF7) // 结束 OTA
|
||||
#define CMD_ACK (0x80) // 通用 ACK
|
||||
#define CMD_ERROR (0x81) // 通用错误
|
||||
|
||||
// ACK 状态码
|
||||
#define ACK_OK (0x00) // 成功
|
||||
#define ACK_UNSUPPORTED (0x01) // 命令不支持
|
||||
#define ACK_AUTH_FAILED (0x02) // 认证失败
|
||||
#define ACK_SIZE_OOR (0x03) // 大小超出范围
|
||||
#define ACK_CRC_FAILED (0x04) // CRC 校验失败
|
||||
#define ACK_RETRY_EXCEED (0x05) // 重试次数超限
|
||||
#define ACK_SESSION_INVALID (0x06) // 会话密钥无效
|
||||
#define ACK_FLASH_FAILED (0x07) // Flash 写入失败
|
||||
|
||||
// 安全常量(定义在 app_config.h 中)
|
||||
inline constexpr size_t MASTER_KEY_HERE_LEN = 16;
|
||||
inline constexpr size_t SCRAMBLE_CONST_HERE_LEN = 16;
|
||||
inline constexpr size_t CHIP_UID_LEN = 12;
|
||||
|
||||
// AES 会话上下文
|
||||
typedef struct {
|
||||
uint8_t key[16]; // 会话密钥 K_session
|
||||
uint8_t iv[16]; // CBC 模式 IV
|
||||
int used;
|
||||
} session_ctx_t;
|
||||
|
||||
// 初始化协议层
|
||||
void protocol_init(void);
|
||||
|
||||
// CRC16 Modbus 计算(低字节在前,LE)
|
||||
uint16_t crc16_modbus(const uint8_t *data, size_t len);
|
||||
|
||||
// 构建帧: 返回总帧长度
|
||||
// 输出格式: SOF + CMD + LEN(2) + PAYLOAD + CRC16(2)
|
||||
int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame);
|
||||
|
||||
// 解析帧: 返回 payload 起始偏移,失败返回 -1
|
||||
// 检查 SOF,验证 CRC16,提取 CMD 和 LEN
|
||||
int frame_parse(const uint8_t *frame, size_t frame_len,
|
||||
uint8_t *out_cmd, uint8_t *out_payload, size_t *out_len);
|
||||
|
||||
// T2.2: AES-128 CBC 加密
|
||||
// 使用预设主密钥或会话密钥
|
||||
// 注意: 函数名带 protocol_ 前缀以避免与 ESP-IDF wpa_supplicant 组件冲突
|
||||
int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
|
||||
const uint8_t *key, const uint8_t *iv,
|
||||
uint8_t *ciphertext_out);
|
||||
|
||||
int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len,
|
||||
const uint8_t *key, const uint8_t *iv,
|
||||
uint8_t *plaintext_out);
|
||||
|
||||
// T2.1: 搅伴算法 - 生成随机数
|
||||
// 输入: 时间戳 + 内存地址 + rand_seed
|
||||
// 输出: 16 字节 nonce_server
|
||||
void scramble_generate(uint32_t timestamp, uint32_t mem_addr,
|
||||
uint32_t rand_seed, uint8_t *nonce_out);
|
||||
|
||||
// T2.4: 会话密钥管理
|
||||
int session_key_init(session_ctx_t *sess, const uint8_t *k_session);
|
||||
void session_key_clear(session_ctx_t *sess);
|
||||
int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out);
|
||||
int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out);
|
||||
|
||||
// 使用 ESP-IDF 硬件 RNG 生成随机数
|
||||
void generate_random_nonce(uint8_t *nonce, size_t len);
|
||||
|
||||
#endif // PROTOCOL_H
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* spi_master.c - SPI Master 驱动实现
|
||||
* T1.1 - T1.4
|
||||
* ESP-IDF 6.0 适配版本
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "soc/spi_struct.h"
|
||||
|
||||
#include "spi_master.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
spi_device_handle_t g_spi_device = NULL;
|
||||
|
||||
// SPI2 总线初始化
|
||||
esp_err_t spi_master_init(void)
|
||||
{
|
||||
DEBUG_INFO("SPI", "初始化 SPI2 (VSPI)...");
|
||||
|
||||
spi_bus_config_t bus_cfg = {};
|
||||
bus_cfg.mosi_io_num = 8;
|
||||
bus_cfg.miso_io_num = 7;
|
||||
bus_cfg.sclk_io_num = 6;
|
||||
bus_cfg.quadwp_io_num = -1;
|
||||
bus_cfg.quadhd_io_num = -1;
|
||||
bus_cfg.max_transfer_sz = 256;
|
||||
|
||||
esp_err_t ret = spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK) {
|
||||
DEBUG_ERR("SPI", "SPI 总线初始化失败: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
spi_device_interface_config_t dev_cfg = {};
|
||||
dev_cfg.clock_speed_hz = SPI_CLOCK_HZ;
|
||||
dev_cfg.mode = SPI_MODE;
|
||||
dev_cfg.spics_io_num = SPI_MASTER_CS_GPIO;
|
||||
dev_cfg.queue_size = SPI_QUEUE_SIZE;
|
||||
dev_cfg.command_bits = 0;
|
||||
dev_cfg.address_bits = 0;
|
||||
dev_cfg.dummy_bits = 0;
|
||||
dev_cfg.cs_ena_posttrans = 0;
|
||||
dev_cfg.cs_ena_pretrans = 0;
|
||||
|
||||
ret = spi_bus_add_device(SPI2_HOST, &dev_cfg, &g_spi_device);
|
||||
if (ret != ESP_OK) {
|
||||
DEBUG_ERR("SPI", "添加 SPI 设备失败: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
DEBUG_INFO("SPI", "SPI2 已初始化: CLK=40MHz, CS=GPIO4");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// 带超时的同步 SPI 传输
|
||||
// ESP-IDF 6.0: spi_device_transmit() 不再接受超时参数
|
||||
// 使用 spi_device_queue_trans() + spi_device_get_trans_result() 实现超时
|
||||
esp_err_t spi_sync_transfer(uint8_t cmd, const uint8_t *payload,
|
||||
size_t payload_len, uint8_t *recv_buf,
|
||||
size_t recv_len, uint32_t timeout_ms)
|
||||
{
|
||||
if (g_spi_device == NULL) {
|
||||
DEBUG_ERR("SPI", "SPI 设备未初始化");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
spi_transaction_t t = {};
|
||||
t.cmd = cmd;
|
||||
t.length = payload_len * 8;
|
||||
t.rxlength = recv_len * 8;
|
||||
t.tx_buffer = payload;
|
||||
t.rx_buffer = recv_buf;
|
||||
t.user = (void *)(uintptr_t)cmd;
|
||||
|
||||
/* ESP-IDF 6.0: 使用 spi_device_transmit() 无超时参数
|
||||
* 如需超时控制,使用 spi_device_queue_trans() + spi_device_get_trans_result()
|
||||
*/
|
||||
esp_err_t ret = spi_device_transmit(g_spi_device, &t);
|
||||
if (ret != ESP_OK) {
|
||||
DEBUG_WARN("SPI", "SPI 发送 cmd=0x%02X 失败: %s", cmd, esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 仅发送
|
||||
esp_err_t spi_send(uint8_t cmd, const uint8_t *payload, size_t len)
|
||||
{
|
||||
return spi_sync_transfer(cmd, payload, len, NULL, 0, 3000);
|
||||
}
|
||||
|
||||
// 仅接收
|
||||
esp_err_t spi_recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms)
|
||||
{
|
||||
uint8_t dummy[64] = {0};
|
||||
memset(dummy, 0xFF, sizeof(dummy));
|
||||
return spi_sync_transfer(cmd, dummy, len, recv_buf, len, timeout_ms);
|
||||
}
|
||||
|
||||
// 带重试的 SPI 传输
|
||||
esp_err_t spi_transfer_with_retry(uint8_t cmd, const uint8_t *payload,
|
||||
size_t payload_len, uint8_t *recv_buf,
|
||||
size_t recv_len, int max_retries)
|
||||
{
|
||||
esp_err_t ret;
|
||||
int retry_count = 0;
|
||||
|
||||
while (retry_count < max_retries) {
|
||||
ret = spi_sync_transfer(cmd, payload, payload_len,
|
||||
recv_buf, recv_len, 3000);
|
||||
if (ret == ESP_OK) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
retry_count++;
|
||||
DEBUG_WARN("SPI", "SPI 重试 %d/%d, cmd=0x%02X", retry_count, max_retries, cmd);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
DEBUG_ERR("SPI", "SPI 传输 %d 次重试后失败", max_retries);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* spi_master.h - SPI Master 驱动头文件
|
||||
*/
|
||||
#ifndef SPI_MASTER_H
|
||||
#define SPI_MASTER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
// SPI 配置常量
|
||||
#define SPI_MASTER_CS_GPIO (4) // CS 片选引脚
|
||||
#define SPI_CLOCK_HZ (40 * 1000 * 1000) // 40MHz
|
||||
#define SPI_MODE (0) // CPOL=0, CPHA=0
|
||||
#define SPI_QUEUE_SIZE (1)
|
||||
|
||||
// SPI 设备句柄(外部引用)
|
||||
extern spi_device_handle_t g_spi_device;
|
||||
|
||||
// 初始化 SPI2 (VSPI) 总线和设备
|
||||
esp_err_t spi_master_init(void);
|
||||
|
||||
// 同步 SPI 传输(阻塞式)
|
||||
// 发送 cmd + payload,接收响应到 recv_buf
|
||||
// 返回: ESP_OK 成功, ESP_ERR_TIMEOUT 超时
|
||||
esp_err_t spi_sync_transfer(uint8_t cmd, const uint8_t *payload,
|
||||
size_t payload_len, uint8_t *recv_buf,
|
||||
size_t recv_len, uint32_t timeout_ms);
|
||||
|
||||
// 仅发送(不期望接收)
|
||||
esp_err_t spi_send(uint8_t cmd, const uint8_t *payload, size_t len);
|
||||
|
||||
// 仅接收(发送哑数据)
|
||||
esp_err_t spi_recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms);
|
||||
|
||||
#endif // SPI_MASTER_H
|
||||
Reference in New Issue
Block a user