2026-04-27 00:42:26 +08:00
|
|
|
|
/*
|
2026-05-05 03:28:44 +08:00
|
|
|
|
* ble_ota.cpp - BLE OTA GATT 服务器实现 (ESP-IDF 6.0 NimBLE)
|
2026-04-27 00:42:26 +08:00
|
|
|
|
* T5.1 - T5.5
|
2026-05-05 03:28:44 +08:00
|
|
|
|
*
|
2026-05-07 23:50:04 +08:00
|
|
|
|
* 通过 BLE GATT 接收上位机固件,支持两种目标:
|
|
|
|
|
|
* - GD32: 存入 Flash → 触发 SPI 中继转发到 GD32
|
|
|
|
|
|
* - ESP32: 直接写入 OTA 分区 → 触发重启升级自身
|
2026-05-05 03:28:44 +08:00
|
|
|
|
*
|
|
|
|
|
|
* GATT 服务:
|
|
|
|
|
|
* Service UUID: 0xFFFF
|
2026-05-07 23:50:04 +08:00
|
|
|
|
* - CTRL (0x0001, Write): 0xF7=触发GD32中继, 0xF8=触发ESP32 OTA升级
|
|
|
|
|
|
* - DATA_IN (0x0002, Write): cmd=0x00=GD32数据, cmd=0x01=ESP32数据
|
|
|
|
|
|
* - DATA_OUT (0x0003, Notify): 推送响应
|
2026-05-05 03:28:44 +08:00
|
|
|
|
* - VERSION (0x0004, Read): 固件版本
|
2026-04-27 00:42:26 +08:00
|
|
|
|
*/
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
#include <inttypes.h>
|
|
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
|
|
|
|
#include "freertos/task.h"
|
|
|
|
|
|
#include "freertos/semphr.h"
|
2026-05-05 03:28:44 +08:00
|
|
|
|
#include "freertos/queue.h"
|
2026-04-27 00:42:26 +08:00
|
|
|
|
#include "esp_err.h"
|
2026-05-07 23:50:04 +08:00
|
|
|
|
#include "esp_ota_ops.h"
|
|
|
|
|
|
#include "esp_system.h"
|
|
|
|
|
|
#include "esp_timer.h"
|
2026-04-27 00:42:26 +08:00
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
// 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"
|
2026-05-07 23:50:04 +08:00
|
|
|
|
#include "host/ble_att.h"
|
2026-05-05 03:28:44 +08:00
|
|
|
|
|
|
|
|
|
|
#include "app_config.h"
|
2026-04-27 00:42:26 +08:00
|
|
|
|
#include "ble_ota.h"
|
|
|
|
|
|
#include "ota_relay.h"
|
|
|
|
|
|
#include "health_monitor.h"
|
2026-04-27 02:34:47 +08:00
|
|
|
|
#include "monitor/debug_log.h"
|
2026-04-27 00:42:26 +08:00
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
#define TAG "BLE_OTA"
|
2026-05-07 23:50:04 +08:00
|
|
|
|
#define BLE_OTA_NOTIFY_ACK 0xA1
|
|
|
|
|
|
#define BLE_OTA_NOTIFY_FINISH 0xB1
|
|
|
|
|
|
#define BLE_OTA_FINISH_BEGIN_OTA_END 0x01
|
|
|
|
|
|
#define BLE_OTA_FINISH_DONE_OTA_END 0x02
|
|
|
|
|
|
#define BLE_OTA_FINISH_BEGIN_SET_BOOT 0x03
|
|
|
|
|
|
#define BLE_OTA_FINISH_DONE_SET_BOOT 0x04
|
|
|
|
|
|
#define BLE_OTA_FINISH_BEGIN_DELAY 0x05
|
|
|
|
|
|
#define BLE_OTA_FINISH_DONE_DELAY 0x06
|
|
|
|
|
|
#define BLE_OTA_FINISH_REBOOT_NOW 0x07
|
|
|
|
|
|
#define BLE_OTA_FINISH_ERR_OTA_END 0x81
|
|
|
|
|
|
#define BLE_OTA_FINISH_ERR_SET_BOOT 0x82
|
|
|
|
|
|
#define BLE_OTA_ACK_WINDOW 8
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 前向声明(GAP event handler 在广播函数之前定义,需要声明)
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
static void ble_ota_start_advertising(void);
|
|
|
|
|
|
static void ble_ota_send_window_ack(uint8_t cmd, uint16_t seq, uint32_t completed_bytes);
|
|
|
|
|
|
static void ble_ota_send_finish_event(uint8_t stage, uint32_t value);
|
2026-04-27 00:42:26 +08:00
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 静态状态
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
static bool s_ble_ready = false; // NimBLE 同步完成
|
|
|
|
|
|
static bool s_ble_connected = false; // GATT 客户端已连接
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief 当前 BLE 连接句柄。 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static uint16_t s_conn_handle = BLE_HS_CONN_HANDLE_NONE;
|
|
|
|
|
|
static uint16_t s_data_out_val_handle = 0; // DATA_OUT 值句柄 (供通知用)
|
|
|
|
|
|
|
2026-05-07 23:50:04 +08:00
|
|
|
|
// ESP32 自身 OTA 状态
|
|
|
|
|
|
static bool s_esp32_ota_active = false;
|
|
|
|
|
|
static esp_ota_handle_t s_esp32_ota_handle = 0;
|
|
|
|
|
|
static const esp_partition_t *s_esp32_update_partition = NULL;
|
|
|
|
|
|
static size_t s_esp32_received = 0;
|
|
|
|
|
|
static bool s_esp32_reboot_pending = false;
|
|
|
|
|
|
static uint8_t s_esp32_last_cmd = 0;
|
|
|
|
|
|
static uint16_t s_esp32_last_seq = 0xFFFFU;
|
|
|
|
|
|
|
2026-05-26 02:44:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 异步完成 ESP32 OTA 收尾并触发重启。
|
|
|
|
|
|
* @param pvParameters 任务参数,当前未使用。
|
|
|
|
|
|
*/
|
2026-05-07 23:50:04 +08:00
|
|
|
|
static void ble_esp32_finish_and_reboot_task(void *pvParameters)
|
|
|
|
|
|
{
|
|
|
|
|
|
(void)pvParameters;
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 OTA 异步收尾任务启动");
|
|
|
|
|
|
|
|
|
|
|
|
if (s_esp32_last_seq != 0xFFFFU) {
|
|
|
|
|
|
ble_ota_send_window_ack(s_esp32_last_cmd, s_esp32_last_seq, (uint32_t)s_esp32_received);
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 最终 ACK 已补发: seq=%u", s_esp32_last_seq);
|
|
|
|
|
|
}
|
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(150));
|
|
|
|
|
|
|
|
|
|
|
|
if (!s_esp32_ota_active || s_esp32_update_partition == NULL) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "异步收尾时 OTA 状态无效,放弃重启");
|
|
|
|
|
|
s_esp32_reboot_pending = false;
|
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 OTA 收尾: 共 %u 字节, 设置启动分区...", (unsigned int)s_esp32_received);
|
|
|
|
|
|
|
|
|
|
|
|
int64_t t0_us = esp_timer_get_time();
|
|
|
|
|
|
DEBUG_INFO(TAG, "[FINISH] 即将执行 esp_ota_end()");
|
|
|
|
|
|
ble_ota_send_finish_event(BLE_OTA_FINISH_BEGIN_OTA_END, 0);
|
|
|
|
|
|
esp_err_t err = esp_ota_end(s_esp32_ota_handle);
|
|
|
|
|
|
int64_t t1_us = esp_timer_get_time();
|
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "[FINISH] esp_ota_end 失败: %s (耗时=%lld ms)",
|
|
|
|
|
|
esp_err_to_name(err), (long long)((t1_us - t0_us) / 1000));
|
|
|
|
|
|
ble_ota_send_finish_event(BLE_OTA_FINISH_ERR_OTA_END, (uint32_t)err);
|
|
|
|
|
|
esp_ota_abort(s_esp32_ota_handle);
|
|
|
|
|
|
s_esp32_ota_active = false;
|
|
|
|
|
|
s_esp32_ota_handle = 0;
|
|
|
|
|
|
s_esp32_update_partition = NULL;
|
|
|
|
|
|
s_esp32_received = 0;
|
|
|
|
|
|
s_esp32_reboot_pending = false;
|
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
DEBUG_INFO(TAG, "[FINISH] esp_ota_end 完成 (耗时=%lld ms)",
|
|
|
|
|
|
(long long)((t1_us - t0_us) / 1000));
|
|
|
|
|
|
ble_ota_send_finish_event(BLE_OTA_FINISH_DONE_OTA_END, (uint32_t)((t1_us - t0_us) / 1000));
|
|
|
|
|
|
|
|
|
|
|
|
int64_t t2_us = esp_timer_get_time();
|
|
|
|
|
|
DEBUG_INFO(TAG, "[FINISH] 即将执行 esp_ota_set_boot_partition()");
|
|
|
|
|
|
ble_ota_send_finish_event(BLE_OTA_FINISH_BEGIN_SET_BOOT, 0);
|
|
|
|
|
|
err = esp_ota_set_boot_partition(s_esp32_update_partition);
|
|
|
|
|
|
int64_t t3_us = esp_timer_get_time();
|
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "[FINISH] 设置启动分区失败: %s (耗时=%lld ms)",
|
|
|
|
|
|
esp_err_to_name(err), (long long)((t3_us - t2_us) / 1000));
|
|
|
|
|
|
ble_ota_send_finish_event(BLE_OTA_FINISH_ERR_SET_BOOT, (uint32_t)err);
|
|
|
|
|
|
s_esp32_ota_active = false;
|
|
|
|
|
|
s_esp32_ota_handle = 0;
|
|
|
|
|
|
s_esp32_update_partition = NULL;
|
|
|
|
|
|
s_esp32_received = 0;
|
|
|
|
|
|
s_esp32_reboot_pending = false;
|
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
DEBUG_INFO(TAG, "[FINISH] esp_ota_set_boot_partition 完成 (耗时=%lld ms)",
|
|
|
|
|
|
(long long)((t3_us - t2_us) / 1000));
|
|
|
|
|
|
ble_ota_send_finish_event(BLE_OTA_FINISH_DONE_SET_BOOT, (uint32_t)((t3_us - t2_us) / 1000));
|
|
|
|
|
|
|
|
|
|
|
|
s_esp32_ota_active = false;
|
|
|
|
|
|
s_esp32_ota_handle = 0;
|
|
|
|
|
|
s_esp32_update_partition = NULL;
|
|
|
|
|
|
s_esp32_received = 0;
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 OTA 收尾完成,800ms 后重启...");
|
|
|
|
|
|
int64_t t4_us = esp_timer_get_time();
|
|
|
|
|
|
DEBUG_INFO(TAG, "[FINISH] 即将进入 800ms 重启前等待");
|
|
|
|
|
|
ble_ota_send_finish_event(BLE_OTA_FINISH_BEGIN_DELAY, 800);
|
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(800));
|
|
|
|
|
|
int64_t t5_us = esp_timer_get_time();
|
|
|
|
|
|
DEBUG_INFO(TAG, "[FINISH] 800ms 等待结束 (实际耗时=%lld ms)",
|
|
|
|
|
|
(long long)((t5_us - t4_us) / 1000));
|
|
|
|
|
|
ble_ota_send_finish_event(BLE_OTA_FINISH_DONE_DELAY, (uint32_t)((t5_us - t4_us) / 1000));
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 即将重启");
|
|
|
|
|
|
ble_ota_send_finish_event(BLE_OTA_FINISH_REBOOT_NOW, 0);
|
|
|
|
|
|
esp_restart();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// NimBLE 工具: 从 mbuf 读取原始数据
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 将 NimBLE mbuf 中的数据拷贝到普通字节缓冲区。
|
|
|
|
|
|
* @param om NimBLE mbuf 数据指针。
|
|
|
|
|
|
* @param buf 输出缓冲区。
|
|
|
|
|
|
* @param buf_len 缓冲区最大长度。
|
|
|
|
|
|
* @return 拷贝的字节数,失败返回 -1。
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static int ble_ota_mbuf_to_buf(struct os_mbuf *om, uint8_t *buf, size_t buf_len)
|
2026-04-27 00:42:26 +08:00
|
|
|
|
{
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-04-27 00:42:26 +08:00
|
|
|
|
|
2026-05-26 02:44:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 初始化 ESP32 本机 OTA 写入会话并选择目标分区。
|
|
|
|
|
|
* @param total_size 固件总大小(字节)。
|
|
|
|
|
|
* @return ESP_OK 成功,其他为失败错误码。
|
|
|
|
|
|
*/
|
2026-05-07 23:50:04 +08:00
|
|
|
|
static esp_err_t ble_esp32_ota_begin(size_t total_size)
|
|
|
|
|
|
{
|
|
|
|
|
|
const esp_partition_t *boot_partition = esp_ota_get_running_partition();
|
|
|
|
|
|
DEBUG_INFO(TAG, "当前启动分区: %s, size=%u, address=0x%lx",
|
|
|
|
|
|
boot_partition->label, boot_partition->size, boot_partition->address);
|
|
|
|
|
|
|
|
|
|
|
|
if (s_esp32_ota_active) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "检测到未完成的 ESP32 OTA,会先中止旧会话");
|
|
|
|
|
|
esp_ota_abort(s_esp32_ota_handle);
|
|
|
|
|
|
s_esp32_ota_active = false;
|
|
|
|
|
|
s_esp32_ota_handle = 0;
|
|
|
|
|
|
s_esp32_update_partition = NULL;
|
|
|
|
|
|
s_esp32_received = 0;
|
|
|
|
|
|
s_esp32_reboot_pending = false;
|
|
|
|
|
|
s_esp32_last_seq = 0xFFFFU;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s_esp32_update_partition = esp_partition_find_first(
|
|
|
|
|
|
ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);
|
|
|
|
|
|
if (s_esp32_update_partition == NULL ||
|
|
|
|
|
|
s_esp32_update_partition->address == boot_partition->address) {
|
|
|
|
|
|
s_esp32_update_partition = esp_partition_find_first(
|
|
|
|
|
|
ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (s_esp32_update_partition == NULL) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "找不到可用的 OTA 分区");
|
|
|
|
|
|
return ESP_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 OTA 目标分区: %s, size=%u, address=0x%lx",
|
|
|
|
|
|
s_esp32_update_partition->label,
|
|
|
|
|
|
s_esp32_update_partition->size,
|
|
|
|
|
|
s_esp32_update_partition->address);
|
|
|
|
|
|
|
|
|
|
|
|
if (total_size > s_esp32_update_partition->size) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "ESP32 固件太大: %u > 分区大小: %u",
|
|
|
|
|
|
total_size, s_esp32_update_partition->size);
|
|
|
|
|
|
s_esp32_update_partition = NULL;
|
|
|
|
|
|
return ESP_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
esp_err_t err = esp_ota_begin(s_esp32_update_partition, total_size, &s_esp32_ota_handle);
|
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "esp_ota_begin 失败: %s", esp_err_to_name(err));
|
|
|
|
|
|
s_esp32_update_partition = NULL;
|
|
|
|
|
|
s_esp32_ota_handle = 0;
|
|
|
|
|
|
return err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s_esp32_received = 0;
|
|
|
|
|
|
s_esp32_ota_active = true;
|
|
|
|
|
|
s_esp32_reboot_pending = false;
|
|
|
|
|
|
s_esp32_last_cmd = 0;
|
|
|
|
|
|
s_esp32_last_seq = 0xFFFFU;
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 OTA 开始,目标分区: %s", s_esp32_update_partition->label);
|
|
|
|
|
|
return ESP_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 02:44:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 向 ESP32 OTA 分区写入一段固件数据。
|
|
|
|
|
|
* @param data 待写入数据指针。
|
|
|
|
|
|
* @param len 写入长度(字节)。
|
|
|
|
|
|
* @return ESP_OK 成功,其他为失败错误码。
|
|
|
|
|
|
*/
|
2026-05-07 23:50:04 +08:00
|
|
|
|
static esp_err_t ble_esp32_ota_write(const uint8_t *data, size_t len)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!s_esp32_ota_active || s_esp32_update_partition == NULL) {
|
|
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
|
}
|
|
|
|
|
|
esp_err_t err = esp_ota_write(s_esp32_ota_handle, data, len);
|
|
|
|
|
|
if (err == ESP_OK) {
|
|
|
|
|
|
s_esp32_received += len;
|
|
|
|
|
|
}
|
|
|
|
|
|
return err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 02:44:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 发送窗口 ACK 通知,回报已处理的分块序号与累计字节数。
|
|
|
|
|
|
* @param cmd 对应的数据命令字节。
|
|
|
|
|
|
* @param seq 当前确认的分块序号。
|
|
|
|
|
|
* @param completed_bytes 已完成写入的累计字节数。
|
|
|
|
|
|
*/
|
2026-05-07 23:50:04 +08:00
|
|
|
|
static void ble_ota_send_window_ack(uint8_t cmd, uint16_t seq, uint32_t completed_bytes)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint8_t ack[8] = {0};
|
|
|
|
|
|
ack[0] = BLE_OTA_NOTIFY_ACK;
|
|
|
|
|
|
ack[1] = cmd;
|
|
|
|
|
|
ack[2] = (uint8_t)(seq & 0xFFU);
|
|
|
|
|
|
ack[3] = (uint8_t)((seq >> 8) & 0xFFU);
|
|
|
|
|
|
ack[4] = (uint8_t)(completed_bytes & 0xFFU);
|
|
|
|
|
|
ack[5] = (uint8_t)((completed_bytes >> 8) & 0xFFU);
|
|
|
|
|
|
ack[6] = (uint8_t)((completed_bytes >> 16) & 0xFFU);
|
|
|
|
|
|
ack[7] = (uint8_t)((completed_bytes >> 24) & 0xFFU);
|
|
|
|
|
|
|
|
|
|
|
|
esp_err_t err = ble_ota_notify_response(ack, sizeof(ack));
|
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "发送 ACK 失败: cmd=0x%02X seq=%u err=%s",
|
|
|
|
|
|
cmd, seq, esp_err_to_name(err));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
DEBUG_DBG(TAG, "已发送 ACK: cmd=0x%02X seq=%u bytes=%lu",
|
|
|
|
|
|
cmd, seq, (unsigned long)completed_bytes);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 02:44:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 发送收尾阶段事件通知(开始/完成/失败/重启等)。
|
|
|
|
|
|
* @param stage 阶段标识码。
|
|
|
|
|
|
* @param value 阶段附带值(错误码或耗时等)。
|
|
|
|
|
|
*/
|
2026-05-07 23:50:04 +08:00
|
|
|
|
static void ble_ota_send_finish_event(uint8_t stage, uint32_t value)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint8_t pkt[6] = {0};
|
|
|
|
|
|
pkt[0] = BLE_OTA_NOTIFY_FINISH;
|
|
|
|
|
|
pkt[1] = stage;
|
|
|
|
|
|
pkt[2] = (uint8_t)(value & 0xFFU);
|
|
|
|
|
|
pkt[3] = (uint8_t)((value >> 8) & 0xFFU);
|
|
|
|
|
|
pkt[4] = (uint8_t)((value >> 16) & 0xFFU);
|
|
|
|
|
|
pkt[5] = (uint8_t)((value >> 24) & 0xFFU);
|
|
|
|
|
|
esp_err_t err = ble_ota_notify_response(pkt, sizeof(pkt));
|
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "发送 FINISH 事件失败: stage=0x%02X err=%s",
|
|
|
|
|
|
stage, esp_err_to_name(err));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// GATT 访问回调
|
|
|
|
|
|
// ============================================================
|
2026-04-27 00:42:26 +08:00
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* CTRL (0x0001) - Write
|
|
|
|
|
|
* 写入 0xF7 触发 OTA 中继
|
|
|
|
|
|
*/
|
2026-05-26 02:44:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief GATT CTRL 特征访问回调,处理控制命令写入。
|
|
|
|
|
|
* @param conn_handle BLE 连接句柄。
|
|
|
|
|
|
* @param attr_handle GATT 属性句柄。
|
|
|
|
|
|
* @param ctxt GATT 访问上下文。
|
|
|
|
|
|
* @param arg 回调用户参数。
|
|
|
|
|
|
* @return NimBLE ATT 错误码,0 表示成功。
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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]);
|
|
|
|
|
|
|
2026-05-07 23:50:04 +08:00
|
|
|
|
if (buf[0] == BLE_OTA_CTRL_TRIGGER_GD32) {
|
|
|
|
|
|
// 检查固件是否存储完毕 → 触发 GD32 OTA 中继
|
2026-04-27 00:42:26 +08:00
|
|
|
|
ota_meta_t meta;
|
2026-05-05 03:28:44 +08:00
|
|
|
|
esp_err_t err = meta_load(&meta);
|
|
|
|
|
|
if (err == ESP_OK &&
|
|
|
|
|
|
meta.total_size > 0 &&
|
|
|
|
|
|
meta.stored_size >= meta.total_size) {
|
2026-05-07 23:50:04 +08:00
|
|
|
|
DEBUG_INFO(TAG, "GD32 固件就绪 (%" PRIu32 " / %" PRIu32 " 字节),触发中继",
|
2026-05-05 03:28:44 +08:00
|
|
|
|
meta.stored_size, meta.total_size);
|
2026-04-27 00:42:26 +08:00
|
|
|
|
ble_ota_signal_relay();
|
2026-05-05 03:28:44 +08:00
|
|
|
|
} else {
|
2026-05-07 23:50:04 +08:00
|
|
|
|
DEBUG_WARN(TAG, "GD32 固件未就绪 (stored=%" PRIu32 ", total=%" PRIu32 ")",
|
2026-05-05 03:28:44 +08:00
|
|
|
|
meta.stored_size, meta.total_size);
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
2026-05-07 23:50:04 +08:00
|
|
|
|
} else if (buf[0] == BLE_OTA_CTRL_TRIGGER_ESP32) {
|
|
|
|
|
|
if (!s_esp32_ota_active || s_esp32_update_partition == NULL) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "ESP32 OTA 未激活,无法触发升级");
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (s_esp32_reboot_pending) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "ESP32 OTA 收尾已在进行中,忽略重复 CTRL");
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s_esp32_reboot_pending = true;
|
|
|
|
|
|
BaseType_t ok = xTaskCreate(ble_esp32_finish_and_reboot_task,
|
|
|
|
|
|
"ble_ota_finish",
|
|
|
|
|
|
4096,
|
|
|
|
|
|
NULL,
|
|
|
|
|
|
5,
|
|
|
|
|
|
NULL);
|
|
|
|
|
|
if (ok != pdPASS) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "创建 ESP32 OTA 收尾任务失败");
|
|
|
|
|
|
s_esp32_reboot_pending = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 OTA 收尾任务已创建(由设备端异步完成)");
|
|
|
|
|
|
}
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
2026-05-05 03:28:44 +08:00
|
|
|
|
|
|
|
|
|
|
return 0; // BLE_ATT_ERR_OK 在 NimBLE 中就是 0
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* 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 校验 (预留)
|
|
|
|
|
|
*/
|
2026-05-26 02:44:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief GATT DATA_IN 特征访问回调,接收并处理 OTA 数据块。
|
|
|
|
|
|
* @param conn_handle BLE 连接句柄。
|
|
|
|
|
|
* @param attr_handle GATT 属性句柄。
|
|
|
|
|
|
* @param ctxt GATT 访问上下文。
|
|
|
|
|
|
* @param arg 回调用户参数。
|
|
|
|
|
|
* @return NimBLE ATT 错误码,0 表示成功。
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
|
|
|
|
|
|
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
2026-04-27 00:42:26 +08:00
|
|
|
|
{
|
2026-05-05 03:28:44 +08:00
|
|
|
|
(void)conn_handle;
|
|
|
|
|
|
(void)attr_handle;
|
|
|
|
|
|
(void)arg;
|
2026-04-27 00:42:26 +08:00
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
if (ctxt->op != BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
|
|
|
|
|
return BLE_ATT_ERR_REQ_NOT_SUPPORTED;
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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));
|
|
|
|
|
|
|
2026-04-27 00:42:26 +08:00
|
|
|
|
size_t data_offset = 7;
|
2026-05-05 03:28:44 +08:00
|
|
|
|
size_t data_len = (size_t)recv_len - data_offset;
|
2026-04-27 00:42:26 +08:00
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
// 尾 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);
|
|
|
|
|
|
|
2026-05-07 23:50:04 +08:00
|
|
|
|
// --- ESP32 自身 OTA:直接写 OTA 分区 ---
|
|
|
|
|
|
if (cmd == BLE_OTA_CMD_ESP32_DATA) {
|
|
|
|
|
|
if (seq == 0) {
|
|
|
|
|
|
if (total_size == 0) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "ESP32 固件大小非法: %" PRIu32, total_size);
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
const esp_partition_t *boot_partition = esp_ota_get_running_partition();
|
|
|
|
|
|
const esp_partition_t *check_partition = esp_partition_find_first(
|
|
|
|
|
|
ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);
|
|
|
|
|
|
if (check_partition == NULL || check_partition->address == boot_partition->address) {
|
|
|
|
|
|
check_partition = esp_partition_find_first(
|
|
|
|
|
|
ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (check_partition == NULL) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "找不到 ESP32 OTA 目标分区");
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (total_size > check_partition->size) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "ESP32 固件太大: %" PRIu32 " > OTA分区 %u",
|
|
|
|
|
|
total_size, check_partition->size);
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
esp_err_t err = ble_esp32_ota_begin(total_size);
|
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
esp_err_t err = ble_esp32_ota_write(&buf[data_offset], data_len);
|
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "ESP32 OTA 写入失败: seq=%u err=%s",
|
|
|
|
|
|
seq, esp_err_to_name(err));
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_DBG(TAG, "ESP32 OTA 写入成功: seq=%u received=%u",
|
|
|
|
|
|
seq, (unsigned int)s_esp32_received);
|
|
|
|
|
|
|
|
|
|
|
|
if ((((uint32_t)seq + 1U) % BLE_OTA_ACK_WINDOW) == 0U ||
|
|
|
|
|
|
(total_size > 0 && s_esp32_received >= total_size)) {
|
|
|
|
|
|
ble_ota_send_window_ack(cmd, seq, (uint32_t)s_esp32_received);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (total_size > 0 && s_esp32_received >= total_size) {
|
|
|
|
|
|
DEBUG_INFO(TAG, "★ ESP32 固件接收完毕: %u / %" PRIu32 " 字节 (共 %u 块)",
|
|
|
|
|
|
(unsigned int)s_esp32_received, total_size, seq + 1);
|
|
|
|
|
|
s_esp32_last_cmd = cmd;
|
|
|
|
|
|
s_esp32_last_seq = seq;
|
|
|
|
|
|
|
|
|
|
|
|
if (!s_esp32_reboot_pending) {
|
|
|
|
|
|
s_esp32_reboot_pending = true;
|
|
|
|
|
|
BaseType_t ok = xTaskCreate(ble_esp32_finish_and_reboot_task,
|
|
|
|
|
|
"ble_ota_finish",
|
|
|
|
|
|
4096,
|
|
|
|
|
|
NULL,
|
|
|
|
|
|
5,
|
|
|
|
|
|
NULL);
|
|
|
|
|
|
if (ok != pdPASS) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "创建 ESP32 OTA 收尾任务失败");
|
|
|
|
|
|
s_esp32_reboot_pending = false;
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
DEBUG_INFO(TAG, "ESP32 OTA 收尾任务已创建,等待设备端自动重启");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
DEBUG_WARN(TAG, "ESP32 OTA 收尾任务已存在,忽略重复完成触发");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- GD32 OTA:首个分块时创建存储元数据 ---
|
2026-05-05 03:28:44 +08:00
|
|
|
|
if (seq == 0 && total_size > 0) {
|
2026-05-26 02:44:57 +08:00
|
|
|
|
uint32_t rounded_size = (uint32_t)(((total_size + (uint32_t)GD32_OTA_PACKET_SIZE - 1U) / (uint32_t)GD32_OTA_PACKET_SIZE) * (uint32_t)GD32_OTA_PACKET_SIZE);
|
|
|
|
|
|
if (total_size == 0U || total_size > OTA_MAX_FIRMWARE_SIZE || rounded_size > OTA_MAX_FIRMWARE_SIZE) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "GD32 固件大小非法: raw=%" PRIu32 " rounded48=%" PRIu32 " max=%zu",
|
|
|
|
|
|
total_size, rounded_size, OTA_MAX_FIRMWARE_SIZE);
|
2026-05-05 03:28:44 +08:00
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ota_meta_t meta = {};
|
|
|
|
|
|
meta.total_size = total_size;
|
|
|
|
|
|
meta.stored_size = 0;
|
|
|
|
|
|
meta.last_seq = 0;
|
2026-05-07 23:50:04 +08:00
|
|
|
|
meta.chunk_count = (total_size + (uint32_t)data_len - 1U) / (uint32_t)data_len;
|
2026-05-05 03:28:44 +08:00
|
|
|
|
|
|
|
|
|
|
esp_err_t err = meta_save(&meta);
|
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "元数据保存失败");
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 23:50:04 +08:00
|
|
|
|
DEBUG_INFO(TAG, "GD32 固件存储开始: total=%" PRIu32 " chunk_count=%" PRIu32 ", payload=%zu",
|
|
|
|
|
|
meta.total_size, meta.chunk_count, data_len);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ota_meta_t meta = {};
|
|
|
|
|
|
if (meta_load(&meta) != ESP_OK || meta.total_size == 0) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "GD32 元数据无效,拒绝写入: seq=%u", seq);
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (seq > 0 && meta.last_seq != (uint32_t)(seq - 1)) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "GD32 分包序号不连续: seq=%u last_seq=%" PRIu32,
|
|
|
|
|
|
seq, meta.last_seq);
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- GD32 写入 Flash ---
|
|
|
|
|
|
// 按已存储字节数顺序追加,兼容更大的 BLE payload,避免把步长硬编码死。
|
|
|
|
|
|
uint32_t flash_offset = meta.stored_size;
|
|
|
|
|
|
if (flash_offset + (uint32_t)data_len > meta.total_size) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "GD32 写入越界: offset=%" PRIu32 " len=%zu total=%" PRIu32,
|
|
|
|
|
|
flash_offset, data_len, meta.total_size);
|
|
|
|
|
|
return BLE_ATT_ERR_UNLIKELY;
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-04-27 00:42:26 +08:00
|
|
|
|
|
2026-05-07 23:50:04 +08:00
|
|
|
|
uint32_t stored_size = flash_offset + (uint32_t)data_len;
|
|
|
|
|
|
err = meta_update(stored_size, seq);
|
2026-05-05 03:28:44 +08:00
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "元数据更新失败 (seq=%u)", seq);
|
|
|
|
|
|
}
|
2026-04-27 00:42:26 +08:00
|
|
|
|
|
2026-05-07 23:50:04 +08:00
|
|
|
|
if ((((uint32_t)seq + 1U) % BLE_OTA_ACK_WINDOW) == 0U ||
|
|
|
|
|
|
(meta.total_size > 0 && stored_size >= meta.total_size)) {
|
|
|
|
|
|
ble_ota_send_window_ack(cmd, seq, stored_size);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_DBG(TAG, "Flash 写入成功: seq=%u offset=%" PRIu32 " stored=%" PRIu32,
|
|
|
|
|
|
seq, flash_offset, stored_size);
|
|
|
|
|
|
|
|
|
|
|
|
// --- 检查 GD32 固件是否接收完毕 ---
|
2026-05-05 03:28:44 +08:00
|
|
|
|
if (meta_load(&meta) == ESP_OK &&
|
|
|
|
|
|
meta.total_size > 0 &&
|
|
|
|
|
|
meta.stored_size >= meta.total_size) {
|
2026-05-07 23:50:04 +08:00
|
|
|
|
DEBUG_INFO(TAG, "★ GD32 固件接收完毕: %" PRIu32 " / %" PRIu32 " 字节 (共 %u 块)",
|
2026-05-05 03:28:44 +08:00
|
|
|
|
meta.stored_size, meta.total_size, seq + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* VERSION (0x0004) - Read
|
|
|
|
|
|
* 返回固件版本字符串
|
|
|
|
|
|
*/
|
2026-05-26 02:44:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief GATT VERSION 特征访问回调,返回固件版本字符串。
|
|
|
|
|
|
* @param conn_handle BLE 连接句柄。
|
|
|
|
|
|
* @param attr_handle GATT 属性句柄。
|
|
|
|
|
|
* @param ctxt GATT 访问上下文。
|
|
|
|
|
|
* @param arg 回调用户参数。
|
|
|
|
|
|
* @return NimBLE ATT 错误码,0 表示成功。
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 23:50:04 +08:00
|
|
|
|
const char *version = FW_VERSION;
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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() 发送
|
|
|
|
|
|
*/
|
2026-05-26 02:44:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief GATT DATA_OUT 特征访问回调(Notify-only,占位实现)。
|
|
|
|
|
|
* @param conn_handle BLE 连接句柄。
|
|
|
|
|
|
* @param attr_handle GATT 属性句柄。
|
|
|
|
|
|
* @param ctxt GATT 访问上下文。
|
|
|
|
|
|
* @param arg 回调用户参数。
|
|
|
|
|
|
* @return 0。
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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" 编译错误。
|
2026-05-05 04:48:31 +08:00
|
|
|
|
// 改用 static ble_uuid16_t 变量,再取 .u 地址。
|
|
|
|
|
|
/** @brief OTA GATT 服务 UUID */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static const ble_uuid16_t GATT_SVC_UUID = BLE_UUID16_INIT(BLE_OTA_SERVICE_UUID);
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief CTRL 特征 UUID,写入 0xF7 触发中继 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static const ble_uuid16_t GATT_CTRL_UUID = BLE_UUID16_INIT(BLE_OTA_CTRL_UUID);
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief DATA_IN 特征 UUID,接收固件数据块 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static const ble_uuid16_t GATT_DATA_IN_UUID = BLE_UUID16_INIT(BLE_OTA_DATA_IN_UUID);
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief DATA_OUT 特征 UUID,Notify 推送 GD32 响应 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static const ble_uuid16_t GATT_DATA_OUT_UUID = BLE_UUID16_INIT(BLE_OTA_DATA_OUT_UUID);
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief VERSION 特征 UUID,返回固件版本字符串 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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 事件回调
|
|
|
|
|
|
// ============================================================
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief BLE GAP 事件回调,处理连接和断开事件。
|
|
|
|
|
|
* @param event
|
|
|
|
|
|
* @param arg
|
|
|
|
|
|
* @return 0 成功。
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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;
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief 当前 BLE 连接句柄。 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
s_conn_handle = event->connect.conn_handle;
|
|
|
|
|
|
DEBUG_INFO(TAG, "BLE 客户端已连接: handle=%d", s_conn_handle);
|
2026-05-07 23:50:04 +08:00
|
|
|
|
|
|
|
|
|
|
// 向对端请求更快的连接参数,提升 OTA 吞吐
|
|
|
|
|
|
struct ble_gap_upd_params up = {};
|
|
|
|
|
|
up.itvl_min = 6; // 7.5 ms 最小连接间隔
|
|
|
|
|
|
up.itvl_max = 12; // 15 ms 最大连接间隔
|
|
|
|
|
|
up.latency = 0; // 无延迟
|
|
|
|
|
|
up.supervision_timeout = 2000; // 2 秒监控超时
|
|
|
|
|
|
up.min_ce_len = 0; // 控制器自行决定
|
|
|
|
|
|
up.max_ce_len = 0;
|
|
|
|
|
|
int rc = ble_gap_update_params(s_conn_handle, &up);
|
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "请求更新连接参数失败: rc=%d(对端不一定接受,继续运行)", rc);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
DEBUG_INFO(TAG, "已请求更快的连接参数: itvl=%.1f-%.1fms latency=0",
|
|
|
|
|
|
up.itvl_min * 1.25f, up.itvl_max * 1.25f);
|
|
|
|
|
|
}
|
2026-05-05 03:28:44 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
DEBUG_WARN(TAG, "BLE 连接失败: status=%d", event->connect.status);
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief BLE GATT 客户端连接状态。 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
s_ble_connected = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
|
|
case BLE_GAP_EVENT_DISCONNECT:
|
|
|
|
|
|
DEBUG_INFO(TAG, "BLE 客户端已断开: reason=%d",
|
|
|
|
|
|
event->disconnect.reason);
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief BLE GATT 客户端连接状态。 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
s_ble_connected = false;
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief 当前 BLE 连接句柄。 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
s_conn_handle = BLE_HS_CONN_HANDLE_NONE;
|
|
|
|
|
|
// 恢复广播以接受新连接
|
2026-05-07 23:50:04 +08:00
|
|
|
|
ble_ota_start_advertising();
|
2026-05-05 03:28:44 +08:00
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
|
|
case BLE_GAP_EVENT_ADV_COMPLETE:
|
|
|
|
|
|
DEBUG_INFO(TAG, "广播周期结束,重新开始");
|
2026-05-07 23:50:04 +08:00
|
|
|
|
ble_ota_start_advertising();
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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;
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// BLE 广播
|
|
|
|
|
|
// ============================================================
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 启动 BLE 广播,使设备可被发现和连接。
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static void ble_ota_start_advertising(void)
|
|
|
|
|
|
{
|
2026-05-07 23:50:04 +08:00
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
|
|
// 自动推断可用地址类型(优先 Public,不可用则生成 Random)
|
|
|
|
|
|
uint8_t own_addr_type;
|
|
|
|
|
|
rc = ble_hs_id_infer_auto(0, &own_addr_type);
|
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "推断地址类型失败: rc=%d, 尝试生成随机地址", rc);
|
|
|
|
|
|
// 生成不可解析随机地址作为 fallback
|
|
|
|
|
|
ble_addr_t rnd_addr;
|
|
|
|
|
|
rc = ble_hs_id_gen_rnd(0, &rnd_addr);
|
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "生成随机地址也失败: rc=%d, BLE 广播无法启动", rc);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
own_addr_type = rnd_addr.type;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_INFO(TAG, "使用地址类型: %s", own_addr_type == BLE_OWN_ADDR_PUBLIC ? "Public" : "Random");
|
|
|
|
|
|
|
|
|
|
|
|
// --- 构建广播数据 ---
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-05-07 23:50:04 +08:00
|
|
|
|
rc = ble_gap_adv_set_data(adv_data, adv_pos);
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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;
|
2026-05-07 23:50:04 +08:00
|
|
|
|
// own_addr_type 已在上面由 ble_hs_id_infer_auto 推断
|
2026-05-05 03:28:44 +08:00
|
|
|
|
|
|
|
|
|
|
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 同步/重置回调
|
|
|
|
|
|
// ============================================================
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief NimBLE 协议栈同步完成回调,初始化广播。
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief NimBLE 协议栈重置回调。
|
|
|
|
|
|
* @param reason
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static void ble_ota_on_reset(int reason)
|
|
|
|
|
|
{
|
|
|
|
|
|
DEBUG_WARN(TAG, "NimBLE 协议栈重置: reason=%d", reason);
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief NimBLE 协议栈同步完成标志。 */
|
2026-05-05 03:28:44 +08:00
|
|
|
|
s_ble_ready = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// NimBLE Host 任务(独立 FreeRTOS 任务)
|
|
|
|
|
|
// ============================================================
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief NimBLE 主机任务入口,处理协议栈事件循环。
|
|
|
|
|
|
* @param param
|
|
|
|
|
|
*/
|
2026-05-05 03:28:44 +08:00
|
|
|
|
static void ble_ota_nimble_host_task(void *param)
|
|
|
|
|
|
{
|
|
|
|
|
|
(void)param;
|
|
|
|
|
|
DEBUG_INFO(TAG, "NimBLE Host 任务启动");
|
|
|
|
|
|
nimble_port_run();
|
|
|
|
|
|
// 正常情况下不会到达这里
|
|
|
|
|
|
DEBUG_INFO(TAG, "NimBLE Host 任务退出");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 公开 API
|
|
|
|
|
|
// ============================================================
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 启动 BLE GATT 服务器并注册 OTA 服务。
|
|
|
|
|
|
* @return ESP_OK 成功,其他失败。
|
|
|
|
|
|
*/
|
2026-04-27 00:42:26 +08:00
|
|
|
|
esp_err_t ble_ota_server_start(void)
|
|
|
|
|
|
{
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
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 完成");
|
|
|
|
|
|
|
|
|
|
|
|
ble_svc_gap_init();
|
|
|
|
|
|
ble_svc_gatt_init();
|
|
|
|
|
|
|
|
|
|
|
|
rc = ble_svc_gap_device_name_set(BLE_DEVICE_NAME);
|
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "设置设备名称失败: %d", rc);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 23:50:04 +08:00
|
|
|
|
rc = ble_att_set_preferred_mtu(BLE_MTU_SIZE);
|
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
|
DEBUG_WARN(TAG, "设置首选 MTU 失败: %d", rc);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
DEBUG_INFO(TAG, "首选 ATT MTU 已设置: %u", (unsigned)BLE_MTU_SIZE);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
// 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 回调
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nimble_port_freertos_init(ble_ota_nimble_host_task);
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_INFO(TAG, "BLE OTA 服务器初始化完成");
|
|
|
|
|
|
return ESP_OK;
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 通过 BLE Notify 将 GD32 响应推送给上位机。
|
|
|
|
|
|
* @param data 待推送的数据指针。
|
|
|
|
|
|
* @param len 数据长度(字节)。
|
|
|
|
|
|
* @return ESP_OK 成功,其他失败。
|
|
|
|
|
|
*/
|
2026-04-27 00:42:26 +08:00
|
|
|
|
esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len)
|
|
|
|
|
|
{
|
2026-05-05 03:28:44 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 检查当前是否有 BLE 客户端连接。
|
|
|
|
|
|
* @return true 已连接,false 未连接。
|
|
|
|
|
|
*/
|
2026-04-27 00:42:26 +08:00
|
|
|
|
bool ble_ota_is_connected(void)
|
|
|
|
|
|
{
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/** @brief BLE GATT 客户端连接状态。 */
|
2026-04-27 00:42:26 +08:00
|
|
|
|
return s_ble_connected;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 通知中继模块开始向 GD32 转发固件。
|
|
|
|
|
|
*/
|
2026-04-27 00:42:26 +08:00
|
|
|
|
void ble_ota_signal_relay(void)
|
|
|
|
|
|
{
|
2026-05-05 03:28:44 +08:00
|
|
|
|
if (g_ota_dispatch_queue == NULL) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "OTA 调度队列未初始化");
|
|
|
|
|
|
return;
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
2026-05-05 03:28:44 +08:00
|
|
|
|
|
|
|
|
|
|
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");
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 03:28:44 +08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// BLE OTA FreeRTOS 任务入口
|
|
|
|
|
|
//
|
|
|
|
|
|
// 注意: 此任务的职责是初始化 BLE 服务器并保持存活。
|
|
|
|
|
|
// 实际的 BLE 事件循环在独立的 NimBLE Host 任务中运行。
|
|
|
|
|
|
// 为避免与 ota_relay_task 竞争,此任务不从 g_ota_dispatch_queue 消费。
|
|
|
|
|
|
// ============================================================
|
2026-05-05 04:48:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief BLE OTA FreeRTOS 任务,初始化 NimBLE 并运行 GATT 服务。
|
|
|
|
|
|
* @param pvParameters FreeRTOS 任务参数,未使用。
|
|
|
|
|
|
*/
|
2026-04-27 00:42:26 +08:00
|
|
|
|
void ble_ota_task(void *pvParameters)
|
|
|
|
|
|
{
|
|
|
|
|
|
(void)pvParameters;
|
2026-05-05 03:28:44 +08:00
|
|
|
|
DEBUG_INFO(TAG, "=== BLE OTA 任务启动 ===");
|
|
|
|
|
|
|
|
|
|
|
|
esp_err_t err = ble_ota_server_start();
|
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
|
DEBUG_ERR(TAG, "BLE 服务器启动失败: %s → BLE OTA 不可用",
|
|
|
|
|
|
esp_err_to_name(err));
|
2026-04-27 00:42:26 +08:00
|
|
|
|
}
|
2026-05-05 03:28:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 保持任务存活 — 所有 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));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|