- 新增 `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 反馈处理:从推送式改为请求驱动架构,降低内存占用、提升效率。
418 lines
14 KiB
C++
418 lines
14 KiB
C++
/*
|
||
* 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));
|
||
}
|
||
}
|
||
|