当ESP32作为BLE主机进行设备扫描时,接收到的广播包并非杂乱无章的二进制流,而是遵循特定格式的结构化数据。每个广播包由多个AD Structure组成,每个结构包含三个字段:
常见的AD Type包括:
| 类型常量 | 值 | 描述 |
|---|---|---|
ESP_BLE_AD_TYPE_NAME_CMPL |
0x09 | 完整设备名 |
ESP_BLE_AD_TYPE_16SRV_CMPL |
0x03 | 完整16位UUID列表 |
ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE |
0xFF | 厂商自定义数据 |
广播包的最大长度为31字节(不包括报头),因此设备通常会精心组织广播内容以包含最有价值的信息。理解这种结构是有效解析数据的第一步。
在开始解析广播数据前,需要正确配置ESP32的扫描行为。以下是一个典型的扫描参数设置示例:
c复制static esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_ACTIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_interval = 0x50, // 约100ms
.scan_window = 0x30, // 约60ms
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
};
关键参数说明:
提示:主动扫描虽然功耗略高,但能获取设备响应包中的额外信息,对于需要完整设备数据的应用推荐使用。
所有扫描结果都通过GAP事件回调函数传递。以下是处理扫描结果的基本框架:
c复制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_SCAN_RESULT_EVT:
handle_scan_result(¶m->scan_rst);
break;
// 处理其他相关事件...
}
}
在handle_scan_result函数中,我们需要区分不同类型的事件:
c复制void handle_scan_result(esp_ble_gap_scan_result_t *scan_result) {
switch (scan_result->search_evt) {
case ESP_GAP_SEARCH_INQ_RES_EVT:
// 发现新设备时的处理
process_new_device(scan_result);
break;
case ESP_GAP_SEARCH_INQ_CMPL_EVT:
// 扫描完成时的处理
ESP_LOGI(TAG, "Scan completed");
break;
}
}
设备名称是最直观的识别信息,使用esp_ble_resolve_adv_data函数可以轻松提取:
c复制uint8_t *name_data;
uint8_t name_len;
name_data = esp_ble_resolve_adv_data(
scan_result->ble_adv,
ESP_BLE_AD_TYPE_NAME_CMPL,
&name_len
);
if (name_len > 0) {
char device_name[name_len + 1];
memcpy(device_name, name_data, name_len);
device_name[name_len] = '\0';
ESP_LOGI(TAG, "Device Name: %s", device_name);
}
注意:设备可能使用短名称(
ESP_BLE_AD_TYPE_NAME_SHORT),实际开发中应同时检查这两种类型。
服务UUID是识别设备功能的关键。BLE支持16位、32位和128位UUID:
c复制// 提取16位UUID
uint8_t *uuid16_data;
uint8_t uuid16_len;
uuid16_data = esp_ble_resolve_adv_data(
scan_result->ble_adv,
ESP_BLE_AD_TYPE_16SRV_CMPL,
&uuid16_len
);
if (uuid16_len > 0) {
ESP_LOGI(TAG, "16-bit UUIDs:");
for (int i = 0; i < uuid16_len; i += 2) {
uint16_t uuid = (uuid16_data[i+1] << 8) | uuid16_data[i];
ESP_LOGI(TAG, " - 0x%04X", uuid);
}
}
对于32位和128位UUID,只需更换对应的AD Type即可。实际应用中,可以根据已知的服务UUID过滤特定类型的设备。
厂商自定义数据通常包含设备特有的信息,格式由厂商定义。以下是解析示例:
c复制uint8_t *manufacturer_data;
uint8_t manufacturer_len;
manufacturer_data = esp_ble_resolve_adv_data(
scan_result->ble_adv,
ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE,
&manufacturer_len
);
if (manufacturer_len >= 4) {
// 前2字节为公司标识码
uint16_t company_id = (manufacturer_data[1] << 8) | manufacturer_data[0];
ESP_LOGI(TAG, "Manufacturer Data (Company: 0x%04X):", company_id);
// 剩余为厂商自定义数据
esp_log_buffer_hex("Custom Data", manufacturer_data + 2, manufacturer_len - 2);
}
许多智能设备(如iBeacon、Eddystone)都利用厂商自定义数据传递设备标识、距离等信息。理解特定设备的格式可以实现更丰富的功能。
结合上述技术,我们可以创建一个实用的智能家居设备发现工具。以下是关键实现步骤:
首先定义结构体来存储解析出的设备信息:
c复制typedef struct {
esp_bd_addr_t address;
char name[32];
uint16_t appearance;
int8_t rssi;
uint16_t service_uuid[5];
uint8_t service_count;
uint8_t manufacturer_data[32];
uint8_t manufacturer_len;
bool is_homekit;
bool is_ibeacon;
} ble_device_t;
扩展基础解析功能,加入智能家居设备特有的检测逻辑:
c复制void parse_adv_data(ble_device_t *device, esp_ble_gap_scan_result_t *scan_result) {
// 解析基础信息
parse_basic_info(device, scan_result);
// 检测特定设备类型
device->is_homekit = check_homekit_device(scan_result);
device->is_ibeacon = esp_ble_is_ibeacon_packet(
scan_result->ble_adv,
scan_result->adv_data_len
);
// 如果有厂商数据,进一步解析
if (device->manufacturer_len > 0) {
parse_manufacturer_data(device);
}
}
根据解析结果对设备进行分类:
c复制void classify_device(ble_device_t *device) {
if (device->is_homekit) {
ESP_LOGI(TAG, "HomeKit Accessory Found");
// 特殊处理HomeKit设备
}
else if (device->is_ibeacon) {
ESP_LOGI(TAG, "iBeacon Found");
// 处理iBeacon
}
else {
// 通用BLE设备处理
for (int i = 0; i < device->service_count; i++) {
switch (device->service_uuid[i]) {
case 0x180D: // 心率服务
ESP_LOGI(TAG, "Heart Rate Monitor");
break;
case 0x180A: // 设备信息服务
ESP_LOGI(TAG, "Device Information Service");
break;
}
}
}
}
最后,将发现的信息以用户友好的方式展示:
c复制void display_device_info(ble_device_t *device) {
printf("\n=== Device Found ===\n");
printf("Address: %02X:%02X:%02X:%02X:%02X:%02X\n",
device->address[0], device->address[1], device->address[2],
device->address[3], device->address[4], device->address[5]);
if (strlen(device->name) > 0) {
printf("Name: %s\n", device->name);
}
printf("RSSI: %d dBm\n", device->rssi);
if (device->service_count > 0) {
printf("Services: ");
for (int i = 0; i < device->service_count; i++) {
printf("0x%04X ", device->service_uuid[i]);
}
printf("\n");
}
if (device->is_homekit) {
printf("Type: Apple HomeKit Accessory\n");
}
else if (device->is_ibeacon) {
printf("Type: iBeacon\n");
}
}
频繁解析广播数据可能消耗大量资源。实现缓存机制可显著提升性能:
c复制#define MAX_DEVICES 20
ble_device_t device_cache[MAX_DEVICES];
int cache_count = 0;
bool device_exists(esp_bd_addr_t addr) {
for (int i = 0; i < cache_count; i++) {
if (memcmp(device_cache[i].address, addr, 6) == 0) {
return true;
}
}
return false;
}
void update_device_cache(ble_device_t *device) {
// 查找现有设备
for (int i = 0; i < cache_count; i++) {
if (memcmp(device_cache[i].address, device->address, 6) == 0) {
// 更新设备信息
device_cache[i] = *device;
return;
}
}
// 添加新设备
if (cache_count < MAX_DEVICES) {
device_cache[cache_count++] = *device;
}
}
RSSI值通常波动较大,采用移动平均滤波可获得更稳定的读数:
c复制typedef struct {
esp_bd_addr_t address;
int8_t rssi_samples[5];
uint8_t sample_count;
int8_t filtered_rssi;
} rssi_filter_t;
void update_rssi_filter(rssi_filter_t *filter, int8_t new_rssi) {
// 移入新样本
if (filter->sample_count < 5) {
filter->rssi_samples[filter->sample_count++] = new_rssi;
} else {
memmove(filter->rssi_samples, filter->rssi_samples + 1, 4);
filter->rssi_samples[4] = new_rssi;
}
// 计算平均值
int16_t sum = 0;
for (int i = 0; i < filter->sample_count; i++) {
sum += filter->rssi_samples[i];
}
filter->filtered_rssi = sum / filter->sample_count;
}
对于电池供电设备,优化扫描策略可延长续航:
c复制void set_low_power_scan_params() {
esp_ble_scan_params_t params = {
.scan_type = BLE_SCAN_TYPE_PASSIVE, // 被动扫描更省电
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ONLY_WLST,
.scan_interval = 0x400, // 约2.56秒
.scan_window = 0x10, // 约10ms
.scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE
};
esp_ble_gap_set_scan_params(¶ms);
}
当设备信息较多时,可能出现广播包无法容纳所有数据的情况。解决方法:
不同厂商可能对广播数据格式有不同解释。调试建议:
确保最佳性能的配置要点:
以下是一个典型的性能对比:
| 配置 | 平均电流 | 设备发现延迟 | 数据完整性 |
|---|---|---|---|
| 主动扫描(100ms/60ms) | 12mA | 快 | 高 |
| 被动扫描(2560ms/10ms) | 2mA | 慢 | 中 |
| 间歇扫描(开1s关5s) | 3mA | 可变 | 低 |
BLE广播不仅用于设备发现,还能实现无连接数据交互。典型应用场景包括:
实现广播数据发送的示例:
c复制void advertise_custom_data() {
uint8_t raw_data[] = {
// 标志AD结构
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
// 设备名AD结构
0x0A, ESP_BLE_AD_TYPE_NAME_CMPL, 'M','Y','_','D','E','V','I','C','E',
// 厂商自定义数据
0x05, ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE,
0xCD, 0xAB, // 公司标识码(0xABCD)
0x01, // 自定义数据
};
esp_ble_gap_config_adv_data_raw(raw_data, sizeof(raw_data));
}
这种无连接模式特别适合以下情况: