ESP32 BLE扫描实战:手把手教你用ESP-IDF API解析广播包里的设备名、UUID和自定义数据
ESP32 BLE广播数据解析实战:从设备名到自定义数据的完整指南
在智能家居和物联网应用中,BLE(低功耗蓝牙)设备间的通信已成为标配。作为开发者,我们经常需要从BLE设备的广播包中提取关键信息,比如设备名称、服务UUID以及厂商自定义数据。本文将深入探讨如何利用ESP32的ESP-IDF框架,高效解析这些隐藏在广播数据中的宝贵信息。
1. BLE广播数据基础解析
BLE设备通过广播包向外传递信息,这些数据包遵循特定的结构。每个广播数据单元由长度、类型和值三部分组成:
[长度][类型][值][长度][类型][值]...ESP-IDF提供了esp_ble_resolve_adv_data()函数来简化解析过程。这个函数接收原始广播数据、目标数据类型和长度指针,返回对应数据的指针。
常见广播数据类型(AD Type)包括:
| 类型常量 | 值 | 描述 |
|---|---|---|
| ESP_BLE_AD_TYPE_NAME_CMPL | 0x09 | 完整设备名 |
| ESP_BLE_AD_TYPE_16SRV_PART | 0x02 | 16位UUID部分列表 |
| ESP_BLE_AD_TYPE_16SRV_CMPL | 0x03 | 16位UUID完整列表 |
| ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE | 0xFF | 厂商自定义数据 |
在智能家居场景中,温湿度传感器可能通过厂商自定义数据字段直接发送当前读数,而门磁传感器则可能用特定UUID标识设备类型。
2. 实战:解析设备名称
设备名称是最基础也是最重要的信息之一,它帮助用户识别设备。以下是提取设备名的完整代码示例:
uint8_t *adv_name = NULL; uint8_t adv_name_len = 0; void handle_scan_result(esp_ble_gap_cb_param_t *scan_result) { adv_name = esp_ble_resolve_adv_data( scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len ); if(adv_name_len > 0) { ESP_LOGI("BLE_SCAN", "发现设备: %.*s", adv_name_len, (char*)adv_name); } }关键点说明:
- 当设备名超过31字节时,部分设备可能只发送缩短的名称(AD Type 0x08)
- 设备名不一定以null结尾,必须依赖返回的长度参数
- 某些设备可能将名称放在扫描响应包而非广播包中
提示:实际项目中建议同时检查0x08和0x09两种类型,并优先使用完整名称。
3. 深入UUID提取技巧
服务UUID是识别设备功能的关键。BLE设备可能公布部分或全部服务UUID:
uint8_t *uuid_list; uint8_t uuid_len; void extract_uuids(esp_ble_gap_cb_param_t *scan_result) { // 尝试获取完整UUID列表 uuid_list = esp_ble_resolve_adv_data( scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_16SRV_CMPL, &uuid_len ); if(uuid_len == 0) { // 回退到部分UUID列表 uuid_list = esp_ble_resolve_adv_data( scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_16SRV_PART, &uuid_len ); } if(uuid_len > 0) { ESP_LOGI("BLE_SCAN", "发现UUID列表:"); for(int i = 0; i < uuid_len; i += 2) { uint16_t uuid = (uuid_list[i+1] << 8) | uuid_list[i]; ESP_LOGI("BLE_SCAN", " - 0x%04X", uuid); } } }UUID处理注意事项:
- 16位UUID是蓝牙SIG定义的标准化服务标识
- 每个UUID占2字节,列表长度应为偶数
- 对于32位和128位UUID,需要使用对应的AD Type
4. 厂商自定义数据解析实战
厂商自定义数据(Manufacturer Specific Data)是设备厂商的"自由发挥"空间,通常包含设备特有的信息。以下是解析示例:
typedef struct { uint16_t company_id; // 蓝牙公司标识符 uint8_t data[]; // 厂商自定义数据 } manufacturer_data_t; void handle_manufacturer_data(esp_ble_gap_cb_param_t *scan_result) { uint8_t *mfg_data; uint8_t mfg_data_len; mfg_data = esp_ble_resolve_adv_data( scan_result->scan_rst.ble_adv, ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE, &mfg_data_len ); if(mfg_data_len >= 2) { // 至少包含2字节的公司ID manufacturer_data_t *data = (manufacturer_data_t*)mfg_data; ESP_LOGI("BLE_SCAN", "厂商ID: 0x%04X",>void handle_scan_result_complete(esp_ble_gap_cb_param_t *scan_result) { // 合并广播数据和扫描响应数据 uint8_t total_len = scan_result->scan_rst.adv_data_len + scan_result->scan_rst.scan_rsp_len; uint8_t *combined_data = malloc(total_len); memcpy(combined_data, scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len); memcpy(combined_data + scan_result->scan_rst.adv_data_len, scan_result->scan_rst.ble_adv + scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); // 从合并数据中解析 uint8_t *name; uint8_t name_len; name = esp_ble_resolve_adv_data(combined_data, ESP_BLE_AD_TYPE_NAME_CMPL, &name_len); free(combined_data); }5.2 信号强度与距离估算
RSSI(接收信号强度指示)可用于粗略估计设备距离:
void log_device_distance(esp_ble_gap_cb_param_t *scan_result) { int8_t rssi = scan_result->scan_rst.rssi; uint8_t *tx_power = esp_ble_resolve_adv_data( scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_TX_PWR, NULL ); if(tx_power) { int8_t tx_pwr = *tx_power; // 简单距离估算(米) double distance = pow(10, (tx_pwr - rssi) / 20.0); ESP_LOGI("BLE_SCAN", "预估距离: %.2f米", distance); } }5.3 过滤重复设备
连续扫描时,可以使用设备地址和部分特征过滤重复报告:
#define MAX_DEVICES 20 typedef struct { esp_bd_addr_t addr; uint32_t last_seen; } seen_device_t; seen_device_t seen_devices[MAX_DEVICES]; bool is_new_device(esp_bd_addr_t addr) { for(int i = 0; i < MAX_DEVICES; i++) { if(memcmp(seen_devices[i].addr, addr, ESP_BD_ADDR_LEN) == 0) { seen_devices[i].last_seen = xTaskGetTickCount(); return false; } } // 找到空位或最旧的记录 int oldest = 0; for(int i = 1; i < MAX_DEVICES; i++) { if(seen_devices[i].last_seen < seen_devices[oldest].last_seen) { oldest = i; } } memcpy(seen_devices[oldest].addr, addr, ESP_BD_ADDR_LEN); seen_devices[oldest].last_seen = xTaskGetTickCount(); return true; }6. 实战案例:智能家居网关实现
结合以上技术,我们可以实现一个完整的智能家居网关示例:
typedef enum { DEVICE_UNKNOWN, DEVICE_THERMOMETER, DEVICE_DOOR_SENSOR, DEVICE_SMART_PLUG } device_type_t; typedef struct { esp_bd_addr_t addr; device_type_t type; char name[32]; time_t last_update; union { struct { float temperature; float humidity; } thermo; struct { bool is_open; } door; } data; } smart_device_t; void handle_smart_home_device(esp_ble_gap_cb_param_t *scan_result) { // 1. 获取设备基本信息 smart_device_t device; memcpy(device.addr, scan_result->scan_rst.bda, ESP_BD_ADDR_LEN); // 2. 解析设备名 uint8_t *name = esp_ble_resolve_adv_data( scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &name_len ); if(name_len > 0) { strncpy(device.name, (char*)name, MIN(name_len, sizeof(device.name)-1)); } // 3. 解析厂商数据确定设备类型 uint8_t *mfg_data; uint8_t mfg_data_len; mfg_data = esp_ble_resolve_adv_data( scan_result->scan_rst.ble_adv, ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE, &mfg_data_len ); if(mfg_data_len >= 3 && ((manufacturer_data_t*)mfg_data)->company_id == 0x02A5) { uint8_t *data = mfg_data + 2; // 跳过公司ID switch(data[0]) { case 0x01: device.type = DEVICE_THERMOMETER; device.data.thermo.temperature = ((data[1] << 8) | data[2]) / 10.0; device.data.thermo.humidity = ((data[3] << 8) | data[4]) / 10.0; break; case 0x02: device.type = DEVICE_DOOR_SENSOR; device.data.door.is_open = data[1]; break; } } device.last_update = time(NULL); update_device_in_database(&device); }这个示例展示了如何将各种BLE数据解析技术整合到一个实际的智能家居网关应用中,实现了设备发现、识别和状态更新的完整流程。
