当前位置: 首页 > news >正文

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_CMPL0x09完整设备名
ESP_BLE_AD_TYPE_16SRV_PART0x0216位UUID部分列表
ESP_BLE_AD_TYPE_16SRV_CMPL0x0316位UUID完整列表
ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE0xFF厂商自定义数据

在智能家居场景中,温湿度传感器可能通过厂商自定义数据字段直接发送当前读数,而门磁传感器则可能用特定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数据解析技术整合到一个实际的智能家居网关应用中,实现了设备发现、识别和状态更新的完整流程。

http://www.cnnetsun.cn/news/1983563.html

相关文章:

  • 3步解锁电脑玩手机游戏:scrcpy让你的Android设备变身游戏主机
  • 手写企业级 Starter:ark-redis-starter(缓存 + 开关 + 降级策略)
  • 别再只盯着HTTP了!用Wireshark亲手抓一封邮件,看看SMTP/POP3协议是怎么“裸奔”的
  • 【NOIP】2000真题解析 luogu-P1017 进制转换
  • 相控阵天线(十三):旋转矢量法校准的工程化仿真与优化策略
  • 从LSTM到LLM-to-Action:SITS2026发布游戏智能演进年表(2018–2026),标注3次范式跃迁时刻及对应算力/数据拐点)
  • AGI语言生成可靠性危机(2024实测数据曝光:幻觉率仍高达37.6%)
  • 你的STM32键盘会“粘键”吗?深入解析USB HID报告发送时序与防误触技巧
  • Qt/C++ 信号阻塞的RAII实践:QSignalBlocker的进阶用法与场景剖析
  • 【虚幻引擎】UE4/UE5 容器实战指南:Map、Set、Array 的核心操作与性能考量
  • Sage-Husa自适应滤波:从理论到实战,如何应对动态噪声的挑战
  • GD32F105RBT6 Keil工程模板搭建全攻略(附LED闪烁调试)
  • 树莓派国内镜像源配置全攻略:从原理到实践
  • 中科院信工所复试避坑指南:零项目经验如何靠实习和简历准备逆袭?
  • 抖音无水印下载器:免费批量下载视频图集音乐的终极指南
  • HFSS实战:手把手教你设计一个2.4GHz高增益矩形喇叭天线(附模型文件)
  • Windows网络音频共享的完整解决方案:Scream虚拟声卡实用指南
  • DevEco Studio:快速覆写父类的方法
  • 别再只盯着Linear层了!用torch.nn.Parameter给你的PyTorch模型加点‘私货’(附ViT实战代码)
  • 不只是开台虚拟机:用Azure虚拟网络+VNet对等互联,低成本搭建你的第一个跨区域微服务测试环境
  • 告别乱码与格式之争:在Visual Studio C++项目中全面启用UTF-8与.editorconfig
  • 从结构到实战:深度解析Xilinx Transceiver的ibert自测与性能验证
  • OpenCore技术革命:重新定义旧Mac硬件再生的开源创新范式
  • 如何用Zotero Better Notes打造终极学术笔记管理系统:3步完整指南
  • OpenClaw怎么搭建?2026年阿里云部署OpenClaw、大模型Coding Plan配置流程
  • Mybatis日志框架实战:从SLF4J门面到Log4j2配置详解
  • 华硕天选 Air 2024 FA401U 原厂Win11 23H2系统分享下载-宇程系统站
  • STM32CubeIDE开发实战:ADC多模式采集与DMA高效传输全解析
  • 告别盲调!用VOFA+可视化PID参数,让电机控制调试效率翻倍(STM32F4实战)
  • HKMG工艺里的‘隐形天花板’:为什么SiON界面层和量子化效应成了制程继续微缩的拦路虎?