Files
ESP32_COMM/main/server/ble_ota.cpp
T
小龙虾 f9cbcc396b feat(spi): 增强 SPI master:性能剖析与执行耗时跟踪
- 新增 `lastWorkerExecUs()`:获取最近一次 worker 任务执行耗时(µs)。
- 引入 `SpiProfile` 结构体:事务级详细剖析,记录 SPI 事务各阶段的时间戳。
- 实现 `lastProfile()`:诊断用,获取最近一次事务剖析快照。
- SPI 时钟配置提升至 13.3 MHz,改善性能。
- 修改前端 JavaScript 轮询逻辑:新增 uptime 检查,更好地处理 ESP32 重启状态。
- SPI 测速 HTML 的计时单位由毫秒改为微秒显示。
- 新建 `spi_ring_cache.h`:共享 SPI ring push 缓存工具。
- 新增 `http_util.h`:共享 HTTP 处理器工具,含 JSON 拼接与查询参数解析。
- 引入 `json_parse.h`:轻量级 JSON 解析器,针对扁平 JSON 对象设计。
- 新增 Windows 环境下的项目构建批处理脚本。
- 重构 CAN 反馈处理:从推送式改为请求驱动架构,降低内存占用、提升效率。
2026-08-02 17:37:19 +08:00

418 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 收尾并触发重启。
*
* 实际的 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,不返回
}
/**
* @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 干扰新会话。
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 侧残留
esp32_reorder_free();
BleOta::instance().m_esp32LastSeq = 0xFFFFU;
BleOta::instance().m_esp32LastCmd = 0;
ble_ota_restore_wifi_ps();
}
vTaskDelete(NULL);
}
// ============================================================
// BLE OTA 期间 WiFi 共存:保持 WIFI_PS_NONE;暂停 RSSI 轮询
// ============================================================
/**
* @brief BLE OTA 开始时的 WiFi 共存处理(暂停 RSSI 轮询,不切省电模式)。
*/
void ble_ota_boost_wifi_ps()
{
if (s_wifi_ps_boosted) return;
s_wifi_ps_boosted = true;
// 通过协调器通知 WiFi 层
OtaCoordinator::instance().notifyBleCoexStart();
DEBUG_DBG(TAG, "BLE OTA中:暂停RSSI,保持PS_NONE");
}
/**
* @brief BLE OTA 结束/中止时恢复 WiFi RSSI 轮询。
*/
void ble_ota_restore_wifi_ps()
{
if (!s_wifi_ps_boosted) return;
s_wifi_ps_boosted = false;
// 通过协调器通知 WiFi 层
OtaCoordinator::instance().notifyBleCoexEnd();
DEBUG_DBG(TAG, "BLE OTA数据结束:恢复RSSI轮询");
}
// ============================================================
// 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;
}
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));
}
}