ESP32 BLE广播包中自定义厂商数据的动态更新与抓包验证实战

一、原理:广播数据与厂商字段

BLE广播包(Advertising Packet)由多个AD Structure组成,每个结构包含长度、类型和数据。其中,类型0xFF表示厂商自定义数据(Manufacturer Specific Data),格式为:

  • 2字节公司ID(Company ID,如0x004C为Apple,0x0059为Nordic)
  • 自定义数据(长度可变)

ESP32的BLE协议栈通过esp_ble_adv_data_t结构体配置广播数据,其中manufacturer_lenmanufacturer_data字段用于设置厂商数据。动态更新即指在广播过程中,通过esp_ble_gap_config_adv_data()函数重新配置广播数据,使设备状态变化实时反映在广播包中。

二、硬件与软件准备

  • 硬件:ESP32开发板(如ESP32-DevKitC)
  • 软件:ESP-IDF v4.4+(或Arduino-ESP32,但本文以ESP-IDF为例)
  • 抓包工具:手机nRF Connect App,或PC端Wireshark + BLE dongle(如nRF52840)

三、配置步骤

1. 初始化BLE并设置广播参数

#include <stdio.h>
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"

// 厂商数据缓冲区(假设公司ID为0xFFFF,自定义数据为4字节状态)
static uint8_t manufacturer_data[6] = {0xFF, 0xFF, 0x01, 0x02, 0x03, 0x04};

// 广播数据配置结构体
static esp_ble_adv_data_t adv_data = {
    .set_scan_rsp = false,
    .include_name = true,
    .include_txpower = true,
    .min_interval = 0x20,
    .max_interval = 0x40,
    .appearance = 0x00,
    .manufacturer_len = sizeof(manufacturer_data),
    .manufacturer_data = manufacturer_data,
};

void ble_init() {
    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    esp_bt_controller_init(&bt_cfg);
    esp_bt_controller_enable(ESP_BT_MODE_BTDM);

    esp_bluedroid_init();
    esp_bluedroid_enable();

    // 注册GAP回调
    esp_ble_gap_register_callback(gap_event_handler);

    // 设置广播参数
    esp_ble_adv_params_t adv_params = {
        .adv_int_min = 0x20,
        .adv_int_max = 0x40,
        .adv_type = ADV_TYPE_IND,
        .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
        .channel_map = ADV_CHNL_ALL,
        .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
    };
    esp_ble_gap_config_adv_data(&adv_data); // 初始配置
    esp_ble_gap_start_advertising(&adv_params);
}

2. 动态更新厂商数据

在需要更新状态时(如传感器读数变化),修改manufacturer_data数组,并重新调用esp_ble_gap_config_adv_data()。注意:必须先停止广播或直接重新配置,ESP-IDF允许在广播中更新,但需确保数据长度一致。

void update_manufacturer_data(uint8_t *new_data, uint8_t len) {
    if (len > 4) return; // 假设最大4字节自定义数据
    memcpy(&manufacturer_data[2], new_data, len); // 跳过公司ID
    adv_data.manufacturer_len = len + 2;
    adv_data.manufacturer_data = manufacturer_data;
    esp_ble_gap_config_adv_data(&adv_data);
}

3. GAP事件处理

gap_event_handler中处理ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT,确认配置完成,然后重新启动广播(如果之前停止了)。

static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
    switch (event) {
        case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
            // 配置完成,确保广播在运行
            esp_ble_gap_start_advertising(&adv_params);
            break;
        case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
            if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
                printf("广告启动失败\n");
            }
            break;
        default:
            break;
    }
}

四、完整代码示例

以下是一个可运行的示例,每5秒更新一次状态值(递增):

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"

static uint8_t manufacturer_data[6] = {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00};
static esp_ble_adv_data_t adv_data;
static esp_ble_adv_params_t adv_params;

void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
    if (event == ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT) {
        esp_ble_gap_start_advertising(&adv_params);
    }
}

void update_task(void *arg) {
    uint8_t counter = 0;
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(5000));
        counter++;
        manufacturer_data[2] = counter; // 更新第3字节
        adv_data.manufacturer_len = sizeof(manufacturer_data);
        adv_data.manufacturer_data = manufacturer_data;
        esp_ble_gap_config_adv_data(&adv_data);
        printf("更新广播数据: counter=%d\n", counter);
    }
}

void app_main() {
    // 初始化BLE...(同前)
    // 配置adv_data和adv_params...
    // 启动初始广播...
    xTaskCreate(update_task, "update", 2048, NULL, 5, NULL);
}

五、抓包验证技巧

1. 使用nRF Connect(手机)

  • 打开App,扫描设备,找到你的ESP32设备名。
  • 点击设备,查看“ADVERTISING DATA”中的“Manufacturer Specific Data”。
  • 观察数据变化:每5秒应看到第3字节递增。

2. 使用Wireshark + BLE dongle

  • 将nRF52840 dongle插入PC,使用Wireshark捕获BLE流量。
  • 过滤btle.advertising_data.manufacturer_specific_data.company_id == 0xffff
  • 展开数据包,查看“Manufacturer Specific Data”字段,确认动态更新。

3. 常见问题排查

  • 数据不更新:检查是否在ADV_DATA_SET_COMPLETE_EVT后重新启动广播?如果广播未停止,某些版本可能不生效。建议先esp_ble_gap_stop_advertising(),再配置,再启动。
  • 数据长度变化:如果长度变化,需确保adv_data.manufacturer_len正确,且广播数据总长度不超过31字节(含其他AD结构)。
  • 抓包看不到:确认广播类型为ADV_TYPE_IND(可扫描),且手机或dongle支持厂商数据解析。

六、注意事项

  • 广播间隔:动态更新频率不宜过高,否则会增加功耗和无线拥塞。建议至少100ms以上。
  • 数据一致性:更新过程中,广播包可能被截获为旧数据,这是正常的,因为广播是异步的。
  • 内存管理manufacturer_data必须保持有效,直到配置完成事件触发。
  • 兼容性:不同ESP-IDF版本API略有差异,请参考对应文档。

七、总结

通过本文,你学会了在ESP32上动态更新BLE广播包中的厂商自定义数据,并掌握了抓包验证的方法。这一技巧在智能家居、信标、传感器网络等场景中非常实用。记住,动态更新广播数据是BLE开发中的高级技巧,合理运用能大幅提升设备交互体验。