Files
ESP32_COMM/main/server/ble_ota.cpp
T

418 lines
14 KiB
C++
Raw Normal View History

/*
* ble_ota.cpp - BLE OTA 公开 API + 收尾任务 + WiFi 共存
* 数据路径:ble_ota_data.cpp;GATT/GAP:ble_ota_host.cpp
*/
#include "ble_ota_priv.h"
QueueHandle_t s_otaDataQueue = nullptr;
bool s_wifi_ps_boosted = false;
void drainOtaDataQueue()
{
BleOtaDataBatch stale;
while (s_otaDataQueue && xQueueReceive(s_otaDataQueue, &stale, 0) == pdTRUE) {
if (stale.pool_idx >= 0) {
ble_ota_pool_release(stale.pool_idx); // 静态池槽:归还
} else if (stale.data) {
free(stale.data); // heap 回退路径:释放
}
}
}
uint32_t ble_ota_get_data_queue_depth()
{
return s_otaDataQueue ? (uint32_t)uxQueueMessagesWaiting(s_otaDataQueue) : 0;
}
BleOta& BleOta::instance()
{
static BleOta inst;
return inst;
}
// File-scope val_handle for GATT service definition (struct literal needs compile-time address)
// ESP32 自身 OTA 收尾任务(OTA 写入/校验/重启逻辑委托给 Esp32OtaCore)
/**
* @brief 异步完成 ESP32 OTA 收尾并触发重启。
*
2026-06-16 23:17:06 +08:00
* 实际的 SHA 校验 / esp_ota_end / esp_ota_set_boot_partition 全部由
* Esp32OtaCore::finalize 完成。本任务仅负责:补发 ACK → 调 finalize → 上报 SHA → 通知前端。
* @param pvParameters 任务参数,当前未使用。
*/
/**
* @brief 0xFA 重启任务:为了不卡 NimBLE host task,仅在这里推送 FINISH 后 500ms 重启。
* @param pvParameters 任务参数,未使用。
*/
void ble_esp32_restart_task(void *pvParameters)
{
(void)pvParameters;
DEBUG_INFO(TAG, "[RESTART] 推送 FINISH_REBOOT_NOW 后 500ms 重启");
sendFinishEvent(BLE_OTA_FINISH_REBOOT_NOW, 0);
OtaCoordinator::instance().restartEsp32(); // 内部 notify + delay + esp_restart,不返回
}
2026-06-15 01:14:29 +08:00
/**
* @brief 异步完成 ESP32 OTA 收尾并触发重启。
* @param pvParameters BleEsp32FinishCtx(堆分配,任务负责释放)。
*/
void ble_esp32_finish_and_reboot_task(void *pvParameters)
{
BleEsp32FinishCtx *ctx = (BleEsp32FinishCtx *)pvParameters;
// RAII:保证所有 return 路径自动释放 ctx(restartEsp32 不返回路径除外)
BleFinishCtxDeleter ctx_guard(ctx);
char *frontend_sha_hex = ctx ? ctx->sha_hex : nullptr;
uint16_t session_id = ctx ? ctx->session_id : 0;
DEBUG_DBG(TAG, "ESP32 OTA 异步收尾任务启动");
Esp32OtaCore &core = Esp32OtaCore::instance();
// 补发最终 ACK,让前端尽快得知最后一包已收到
if (BleOta::instance().m_esp32LastSeq != 0xFFFFU) {
sendWindowAck(BleOta::instance().m_esp32LastCmd,
BleOta::instance().m_esp32LastSeq,
(uint32_t)core.getReceived());
DEBUG_DBG(TAG, "ESP32 最终 ACK 已补发: seq=%u", BleOta::instance().m_esp32LastSeq);
}
vTaskDelay(pdMS_TO_TICKS(150));
if (!core.isActive()) {
DEBUG_WARN(TAG, "异步收尾时 OTA 状态无效,放弃重启");
sendFinishEvent(BLE_OTA_FINISH_ERR_FINALIZE,
packBleFinishValue(session_id, 1));
BleOta::instance().m_esp32RebootPending = false;
// 无效会话:协调器可能仍假占用,走统一会话复位
ble_ota_session_reset();
ble_ota_restore_wifi_ps();
vTaskDelete(NULL);
return;
}
DEBUG_DBG(TAG, "ESP32 OTA 收尾: 共 %u 字节", (unsigned)core.getReceived());
// ★ 收尾序列(notify + finalize)由协调器统一封装,reboot=false 让 device_sha 可被读取
Esp32FinalizeResult fr = OtaCoordinator::instance().finalizeForReboot(frontend_sha_hex);
if (fr.err == ESP_OK) {
// 1) 把设备端 SHA 通过 SHA256 通知推给前端
if (fr.device_sha_hex[0] != '\0') {
uint8_t device_sha[32] = {0};
for (int i = 0; i < 32; ++i) {
unsigned int byte = 0;
sscanf(&fr.device_sha_hex[i * 2], "%02x", &byte);
device_sha[i] = (uint8_t)byte;
}
sendSha256Notify(device_sha, session_id);
}
// 2) 等待 SHA 通知从 NimBLE host 队列送出
vTaskDelay(pdMS_TO_TICKS(150));
// 3) 推送"OTA 收尾完成"事件
sendFinishEvent(BLE_OTA_FINISH_DONE, packBleFinishValue(session_id, 0));
sendFinishEvent(BLE_OTA_FINISH_REBOOT_NOW, packBleFinishValue(session_id, 0));
// 4) 通知全部发出后由协调器统一重启(notify + delay + esp_restart)
DEBUG_INFO(TAG, "[FINISH] ESP32 OTA 收尾完成,重启中");
OtaCoordinator::instance().restartEsp32(); // 不返回,ctx 由系统回收
} else if (fr.err == ESP_ERR_INVALID_VERSION) {
DEBUG_ERR(TAG, "[FINISH] SHA-256 不匹配,OTA 已中止");
sendFinishEvent(BLE_OTA_FINISH_ERR_SHA_MISMATCH, packBleFinishValue(session_id, 0));
BleOta::instance().m_esp32RebootPending = false;
// ★ finalizeForReboot 失败路径已 abortCurrent(协调器→IDLE + core 释放)。
// 此处只清 BLE 会话侧 reorder,避免二次 abort 干扰新会话。
2026-06-16 23:17:06 +08:00
esp32_reorder_free();
BleOta::instance().m_esp32LastSeq = 0xFFFFU;
BleOta::instance().m_esp32LastCmd = 0;
ble_ota_restore_wifi_ps();
} else {
DEBUG_ERR(TAG, "[FINISH] OTA 收尾失败: %s", esp_err_to_name(fr.err));
sendFinishEvent(BLE_OTA_FINISH_ERR_FINALIZE,
packBleFinishValue(session_id, 1));
BleOta::instance().m_esp32RebootPending = false;
// ★ 同上:协调器已由 finalizeForReboot 复位,仅清 BLE 侧残留
2026-06-16 23:17:06 +08:00
esp32_reorder_free();
BleOta::instance().m_esp32LastSeq = 0xFFFFU;
BleOta::instance().m_esp32LastCmd = 0;
ble_ota_restore_wifi_ps();
}
vTaskDelete(NULL);
}
2026-06-16 23:17:06 +08:00
// ============================================================
// BLE OTA 期间 WiFi 共存:保持 WIFI_PS_NONE;暂停 RSSI 轮询
2026-06-16 23:17:06 +08:00
// ============================================================
2026-06-16 23:17:06 +08:00
/**
* @brief BLE OTA 开始时的 WiFi 共存处理(暂停 RSSI 轮询,不切省电模式)。
2026-06-16 23:17:06 +08:00
*/
void ble_ota_boost_wifi_ps()
2026-06-16 23:17:06 +08:00
{
if (s_wifi_ps_boosted) return;
s_wifi_ps_boosted = true;
// 通过协调器通知 WiFi 层
OtaCoordinator::instance().notifyBleCoexStart();
DEBUG_DBG(TAG, "BLE OTA中:暂停RSSI,保持PS_NONE");
2026-06-16 23:17:06 +08:00
}
/**
* @brief BLE OTA 结束/中止时恢复 WiFi RSSI 轮询。
2026-06-16 23:17:06 +08:00
*/
void ble_ota_restore_wifi_ps()
2026-06-16 23:17:06 +08:00
{
if (!s_wifi_ps_boosted) return;
s_wifi_ps_boosted = false;
// 通过协调器通知 WiFi 层
OtaCoordinator::instance().notifyBleCoexEnd();
DEBUG_DBG(TAG, "BLE OTA数据结束:恢复RSSI轮询");
2026-06-16 23:17:06 +08:00
}
// ============================================================
// NimBLE 工具: 从 mbuf 读取原始数据
/**
* @brief 将 NimBLE mbuf 中的数据拷贝到普通字节缓冲区。
* @param om NimBLE mbuf 数据指针。
* @param buf 输出缓冲区。
* @param buf_len 缓冲区最大长度。
* @return 拷贝的字节数,失败返回 -1。
*/
int ble_ota_mbuf_to_buf(struct os_mbuf *om, uint8_t *buf, size_t buf_len)
{
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-06-16 23:17:06 +08:00
esp_err_t BleOta::serverStart()
{
int rc;
if (m_stackRunning) {
DEBUG_INFO(TAG, "serverStart: 栈已在运行,跳过");
return ESP_OK;
}
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);
}
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);
}
// 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);
// s_data_out_val_handle 由 on_sync 回调填充,此处不复制
BleOta::instance().m_stackRunning = true;
BleOta::instance().m_suspendedForWifiOta = false;
DEBUG_INFO(TAG, "BLE OTA 服务器初始化完成");
return ESP_OK;
}
esp_err_t BleOta::suspendStack()
{
/*
* ESP32-C3 WiFi/BLE 共用射频:此处仅软挂起(停广播、断连、拒收新数据),
* 不调用 nimble_port_stop / deinit / controller_disable。
*/
if (m_suspendedForWifiOta) {
DEBUG_DBG(TAG, "suspendStack: 已挂起,跳过");
return ESP_OK;
}
if (m_stackRunning) {
(void)ble_gap_adv_stop();
if (m_bleConnected && m_connHandle != 0xFFFFU) {
(void)ble_gap_terminate(m_connHandle, BLE_ERR_REM_USER_CONN_TERM);
/* 短等即可,勿长时间阻塞 httpd */
vTaskDelay(pdMS_TO_TICKS(30));
}
}
m_bleConnected = false;
m_connHandle = 0xFFFFU;
/* 保持 m_bleReady / m_stackRunning:栈仍在,仅业务拒绝连接与写 */
m_suspendedForWifiOta = true;
if (s_otaDataQueue) {
drainOtaDataQueue();
}
return ESP_OK;
}
esp_err_t BleOta::resumeStack()
{
if (!m_suspendedForWifiOta) {
return ESP_OK;
}
m_suspendedForWifiOta = false;
if (!m_stackRunning) {
/* 仅当曾 hard-deinit 过才需要完整 serverStart */
DEBUG_DBG(TAG, "WiFi共存:BLE栈未运行,serverStart free=%lu",
(unsigned long)esp_get_free_heap_size());
esp_err_t err = serverStart();
if (err != ESP_OK) {
DEBUG_ERR(TAG, "resumeStack/serverStart 失败: %s", esp_err_to_name(err));
m_suspendedForWifiOta = true;
return err;
}
return ESP_OK;
}
/* 软挂起恢复:重新开广播即可(非 BLE OTA 会话,仅射频共存;默认 INFO 不刷) */
DEBUG_DBG(TAG, "WiFi共存:恢复BLE广播 free=%lu",
(unsigned long)esp_get_free_heap_size());
m_bleReady = true;
ble_ota_start_advertising();
return ESP_OK;
}
/**
* @brief 通过 BLE Notify 将 GD32 响应推送给上位机。
* @param data 待推送的数据指针。
* @param len 数据长度(字节)。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t BleOta::notifyResponse(const uint8_t *data, size_t len)
{
if (!BleOta::instance().m_bleConnected) {
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(BleOta::instance().m_connHandle, 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;
}
/**
* @brief 检查当前是否有 BLE 客户端连接。
* @return true 已连接,false 未连接。
*/
bool BleOta::isConnected()
{
return BleOta::instance().m_bleConnected;
}
// ============================================================
// BLE OTA FreeRTOS 任务入口
//
// 注意: 此任务的职责是初始化 BLE 服务器并保持存活。
// 实际的 BLE 事件循环在独立的 NimBLE Host 任务中运行。
// 为避免与 ota_relay_task 竞争,此任务不从 OtaRelay::dispatchQueue 消费。
// ============================================================
/**
* @brief BLE OTA FreeRTOS 任务,初始化 NimBLE 并运行 GATT 服务。
* @param pvParameters FreeRTOS 任务参数,未使用。
*/
void BleOta::taskEntry(void *pvParameters)
{
(void)pvParameters;
BleOta::instance().m_taskHandle = xTaskGetCurrentTaskHandle();
DEBUG_INFO(TAG, "=== BLE OTA 任务启动 ===");
// 创建 OTA 数据队列和工作线程(在 NimBLE 初始化之前完成)
s_otaDataQueue = xQueueCreate(BLE_OTA_DATA_QUEUE_LEN, sizeof(BleOtaDataBatch));
if (s_otaDataQueue) {
BaseType_t wk_ok = xTaskCreate(ble_ota_data_worker_task,
"ble_ota_worker",
TASK_STACK_BLE_WORKER, // 实测 peak~1.1KB,负载峰值曾~1.9KB
NULL,
12, // 略提高:尽快排空队列,减少 withoutResponse 连发丢包
NULL);
if (wk_ok != pdPASS) {
DEBUG_ERR(TAG, "OTA工作线程创建失败,BLE不可用");
vQueueDelete(s_otaDataQueue);
s_otaDataQueue = nullptr;
}
} else {
DEBUG_ERR(TAG, "OTA队列创建失败,BLE不可用");
}
esp_err_t err = BleOta::instance().serverStart();
if (err != ESP_OK) {
DEBUG_ERR(TAG, "BLE serverStart失败:%s",
esp_err_to_name(err));
}
// 保持任务存活 — 所有 BLE 事件由 NimBLE Host 任务处理
uint32_t heartbeat = 0;
while (1) {
heartbeat++;
// 每 60 秒打印一次心跳(确认任务存活)
if (heartbeat % 6 == 0) {
DEBUG_DBG(TAG, "心跳 #%lu | BLE %s | 连接: %s",
(unsigned long)(heartbeat / 6),
BleOta::instance().m_bleReady ? "就绪" : "未就绪",
BleOta::instance().m_bleConnected ? "已连接" : "未连接");
}
vTaskDelay(pdMS_TO_TICKS(10000));
}
2026-06-16 23:17:06 +08:00
}