
esp-iot-solution BTHome 组件指南基于 BLE 广播实现传感器上报与 Home Assistant 集成【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution导读本文以 esp-iot-solution 仓库中的 bt_home.rst 文档为主体全面讲解 BTHome 组件components/bluetooth/ble_adv/bthome如何实现 BTHome V2 协议支持传感器数据、二进制传感器数据和事件数据通过低功耗蓝牙BLE广播进行上报支持加密与非加密两种模式并能与 Home Assistant 等智能家居平台无缝集成。读完本文你将掌握 BTHome 实例的创建与配置、加密密钥与对端 MAC 的设置、广播数据的构造与解析、回调存储机制的接入方式以及如何借助仓库中的 bulb/dimmer 示例快速搭建一套可实际运行的 BTHome 设备。BTHome 协议与组件定位BTHome 是一种基于 BLE 广播的轻量级物联网数据格式协议。其核心思路是设备将传感器读数、二进制状态或按钮事件编码进蓝牙广播数据包的服务数据Service Data字段中接收方如 Home Assistant、手机网关或另一块 ESP32 开发板扫描到广播后即可直接解码无需建立连接因此功耗低、部署简单非常适合电池供电的温湿度计、门窗传感器、遥控器等设备。esp-iot-solution 的 BTHome 组件位于 components/bluetooth/ble_adv/bthome对外仅暴露一个头文件 include/bthome_v2.h核心实现集中在 bthome_v2.c。从组件清单 idf_component.yml 可以看到它依赖 IDF5.0支持 esp32、esp32c3、esp32c6、esp32h2、esp32h4、esp32s3、esp32c2 等多种芯片目标。根据 CHANGELOG.md组件自 v0.1.0 起支持 BTHome V2 协议、加密与非加密模式、传感器/二进制传感器/事件上报含按钮与调光器事件并在 v0.1.1 中将加解密实现从 mbedTLS 迁移到 PSA Crypto API以修复编译兼容性问题。组件实现的功能可概括为三类广播数据的构造发送侧把传感器、二进制传感器、事件数据组装成符合 BTHome V2 格式的广播报文可附加设备名称并选择是否加密广播数据的解析接收侧扫描到 BTHome 广播后识别服务 UUID0xFCD2解密如需并逐条拆解出报告report加密密钥与会话计数器的持久化通过用户自定义的回调函数把加密所需的会话计数器写入 NVS 等存储介质保证重启后仍能续用。BTHome 组件的初始化流程使用组件的第一步是创建实例并完成基础配置。官方文档 bt_home.rst 给出如下初始化步骤使用bthome_create创建 BTHome 实例使用bthome_register_callbacks注册存储回调函数使用bthome_set_encrypt_key设置加密密钥可选使用bthome_set_peer_mac_addr设置对端 MAC 地址使用settings_store/settings_load配置存储使用bthome_parse_adv_data解析广播数据、bthome_free_reports释放报告数据。从源码看bthome_createbthome_v2.c会校验入参、初始化 PSA Crypto 子系统并以calloc分配一个bthome_t结构体作为句柄返回。bthome_t内部持有 16 字节加密密钥、本机/对端 MAC 地址、32 位会话计数器、回调函数指针以及 PSA 密钥 ID 等字段具体定义见 bthome_v2.c。与之配套的bthome_deletebthome_v2.c在销毁句柄前会调用psa_destroy_key清理已导入的密钥避免资源泄漏。基础初始化代码来自文档示例可直接编译运行#include bthome_v2.h // 创建 BTHome 实例 bthome_handle_t bthome_recv; ESP_ERROR_CHECK(bthome_create(bthome_recv)); // 注册回调函数 bthome_callbacks_t callbacks { .store settings_store, .load settings_load, }; ESP_ERROR_CHECK(bthome_register_callbacks(bthome_recv, callbacks)); // 设置加密密钥16 字节与发送端保持一致 static const uint8_t encrypt_key[] {0x23, 0x1d, 0x39, 0xc1, 0xd7, 0xcc, 0x1a, 0xb1, 0xae, 0xe2, 0x24, 0xcd, 0x09, 0x6d, 0xb9, 0x32}; ESP_ERROR_CHECK(bthome_set_encrypt_key(bthome_recv, encrypt_key)); // 设置对端 MAC 地址用于解密时构造 nonce static const uint8_t peer_mac[] {0x54, 0x48, 0xE6, 0x8F, 0x80, 0xA5}; ESP_ERROR_CHECK(bthome_set_peer_mac_addr(bthome_recv, peer_mac));回调函数的作用与实现bthome_callbacks_t结构体include/bthome_v2.h包含store与load两个函数指针签名分别如下typedef void (*bthome_store_func_t)(bthome_handle_t handle, const char *key, const uint8_t *data, uint8_t len); typedef void (*bthome_load_func_t)(bthome_handle_t handle, const char *key, uint8_t *data, uint8_t len);组件的调用约定是每当使用加密模式构造出一条广播后bthome_make_adv_data会自增内部会话计数器并调用callbacks.store(handle, counter, counter, sizeof(counter))将新值保存启动时可调用bthome_load_paramsbthome_v2.c通过callbacks.load把计数器读回内存。因此这两个回调的典型实现就是把数据写入/读出 NVS。仓库中的 bulb 示例 examples/bluetooth/ble_adv/bthome/bulb/main/app_main.c 给出了完整可用的 NVS 实现其中settings_store使用nvs_set_blob写入并nvs_commit提交settings_load使用nvs_get_blob读取读取失败时清零填充。测试应用中则使用更简单的 mock 实现test_apps/main/bthome_test.c。需要注意的是bthome_register_callbacks要求store与load都非空否则返回ESP_ERR_INVALID_ARGbthome_create、bthome_set_encrypt_key、bthome_set_peer_mac_addr、bthome_set_local_mac_addr对空指针入参同样有严格校验这些边界行为都被测试用例bthome_error_handling覆盖test_apps/main/bthome_test.c。构造 BTHome 广播报文发送侧发送侧的核心能力由三组 API 提供它们负责把结构化数据写入负载缓冲区并返回新的写入偏移量即已占用字节数调用方只需把每次的返回值作为下一次的偏移继续追加即可。1. 传感器数据uint8_t bthome_payload_add_sensor_data(uint8_t *buffer, uint8_t offset, bthome_sensor_id_t obj_id, uint8_t *data, uint8_t data_len);该函数将obj_id1 字节对象 ID与data_len字节的数据依次写入缓冲区返回offset data_len 1。传感器 ID 全部定义在bthome_sensor_id_t枚举中include/bthome_v2.h常用取值包括BTHOME_SENSOR_ID_BATTERY电池电量1 字节、BTHOME_SENSOR_ID_TEMPERATURE_PRECISE高精度温度2 字节、BTHOME_SENSOR_ID_HUMIDITY_PRECISE高精度湿度2 字节、BTHOME_SENSOR_ID_PRESSURE气压3 字节、BTHOME_SENSOR_ID_ILLUMINANCE光照度3 字节、BTHOME_SENSOR_ID_ENERGY能量3 字节、BTHOME_SENSOR_ID_POWER功率3 字节、BTHOME_SENSOR_ID_VOLTAGE电压2 字节、BTHOME_SENSOR_ID_PM25/BTHOME_SENSOR_ID_PM10颗粒物浓度、BTHOME_SENSOR_ID_CO2、BTHOME_SENSOR_ID_TVOC、BTHOME_SENSOR_ID_CURRENT电流、BTHOME_SENSOR_ID_UV紫外线等。每个传感器对象的数据长度必须与协议一致组件内部维护了一张对象 ID 到数据长度的映射表object_lengthbthome_v2.c解析侧正是依据它来切分数据段的。示例中常见的编码方式是定点缩放温度 23.5°C 编码为(uint16_t)(23.5 * 100) 2350湿度 65.2% 编码为6520详见测试用例bthome_payload_creationtest_apps/main/bthome_test.c。2. 二进制传感器数据uint8_t bthome_payload_adv_add_bin_sensor_data(uint8_t *buffer, uint8_t offset, bthome_bin_sensor_id_t obj_id, uint8_t data);二进制传感器Binary Sensor只有 1 字节数据固定占用offset 2。ID 枚举bthome_bin_sensor_id_tinclude/bthome_v2.h覆盖了BTHOME_BIN_SENSOR_ID_MOTION人体运动、BTHOME_BIN_SENSOR_ID_DOOR/BTHOME_BIN_SENSOR_ID_WINDOW门窗开合、BTHOME_BIN_SENSOR_ID_OPENING、BTHOME_BIN_SENSOR_ID_POWER电源状态、BTHOME_BIN_SENSOR_ID_LIGHT光照、BTHOME_BIN_SENSOR_ID_BATTERY_CHARGING、BTHOME_BIN_SENSOR_ID_MOISTURE漏水、BTHOME_BIN_SENSOR_ID_SMOKE、BTHOME_BIN_SENSOR_ID_OCCUPANCY、BTHOME_BIN_SENSOR_ID_PRESENCE、BTHOME_BIN_SENSOR_ID_TAMPER等常见家庭安防语义。3. 事件数据uint8_t bthome_payload_adv_add_evt_data(uint8_t *buffer, uint8_t offset, bthome_event_id_t obj_id, uint8_t *evt, uint8_t evt_size);事件类型只有两种BTHOME_EVENT_ID_BUTTON按钮事件1 字节事件值如 1 表示单击和BTHOME_EVENT_ID_DIMMER调光器事件2 字节第一字节为方向/类型、第二字节为亮度增量。解析侧bthome_parse_payload会依据 ID 区分按钮事件按 2 字节ID 1 字节值解析调光器事件按 3 字节ID 2 字节值解析bthome_v2.c。4. 组装完整广播报文负载拼装完成后调用bthome_make_adv_data生成可直接交给 BLE 广播的完整报文uint8_t bthome_make_adv_data(bthome_handle_t handle, uint8_t *buffer, uint8_t *name, uint8_t name_len, bthome_device_info_t info, uint8_t *payload, uint8_t payload_len);bthome_device_info_t是一个位域联合体include/bthome_v2.h各 bit 含义为bit0 加密标志encryption_flag、bit2 触发型设备标志trigger_based_flag、bit5-7 BTHome 协议版本bthome_versionV2 填 2。构造报文时组件会自动追加Flags 广播段0x02 0x01 0x06、可选的 Complete Local Name 段类型0x09、Service Data 段UUID0xFCD2小端序写入 设备信息字节 负载。测试用例bthome_adv_data_creationtest_apps/main/bthome_test.c展示了完整的发送侧调用链先bthome_create再设置加密密钥与本机 MAC加密必须用于构造 nonce注册回调追加温湿度与运动传感器数据后生成设备信息bthome_device_info_t device_info { .bit { .encryption_flag 1, // 1 表示加密 .trigger_based_flag 0, // 0 表示周期性上报设备 .bthome_version 2 // BTHome V2 } }; uint8_t adv_len bthome_make_adv_data(handle, adv_data, (uint8_t *)device_name, name_len, device_info, payload, payload_len);并验证了报文前三个字节确实为广播标志段0x02, 0x01, 0x06。如果设置了加密标志但尚未导入密钥bthome_make_adv_data会返回 0构造失败对应测试用例bthome_encrypted_adv_without_keytest_apps/main/bthome_test.c。解析 BTHome 广播报文接收侧接收侧入口是bthome_parse_adv_databthome_reports_t *bthome_parse_adv_data(bthome_handle_t handle, uint8_t *adv, uint8_t len);它逐条遍历广播 AD 结构取长度字段adv[index]若为 0 则结束再取类型字段当类型为0x16Service Data且解析出的 16 位 UUID 等于0xFCD2时进入服务数据解析bthome_v2.c。bthome_parse_service_data会读取设备信息字节检查加密标志若未加密直接对负载调用bthome_parse_payload若加密则先解密再解析bthome_v2.c。解析结果以bthome_reports_t返回typedef struct { uint8_t id; /* 对象 ID传感器 / 二进制传感器 / 事件 ID 之一 */ uint8_t len; /* 数据长度 */ uint8_t *data; /* 数据指针 */ } bthome_report_t; typedef struct { uint8_t num_reports; /* 报告数量 */ bthome_report_t report[BTHOME_REPORTS_MAX]; /* 报告数组最多 10 条 */ } bthome_reports_t;bthome_parse_payloadbthome_v2.c按对象 ID 分流二进制传感器与按钮事件按 2 字节解析、调光器事件按 3 字节解析、Raw/Text 类型先从下一字节读取长度再拷贝数据、普通传感器则查object_length表确定长度后memcpy。每一条报告的数据都是动态分配的因此解析完成后必须调用bthome_free_reports释放防止内存泄漏——该函数会先释放每条 report 的 data 指针再释放 reports 结构体本身bthome_v2.c。同时bthome_reports_t有 10 条上限BTHOME_REPORTS_MAX超出会报 bthome_reports_t overflow 并返回 NULL。官方文档给出的解析侧代码骨架如下// 解析广播数据 bthome_reports_t *reports bthome_parse_adv_data(bthome_recv, result.ble_adv, result.adv_data_len); if (reports ! NULL) { // 处理报告数据 for (int i 0; i reports-num_reports; i) { // 处理每个报告例如 // reports-report[i].id 判断类型 // reports-report[i].data 读取数据 } bthome_free_reports(reports); }注意bthome_parse_adv_data本身不分配持久内存只在解析成功时通过calloc分配 reports失败路径未知对象 ID、报告数溢出、解密失败均返回 NULL测试用例bthome_adv_data_parsingtest_apps/main/bthome_test.c验证了非加密报文的完整解析链路。加密与解密原理BTHome V2 加密模式使用AES-128-CCM算法具体通过 PSA Crypto API 的psa_aead_encrypt/psa_aead_decrypt实现认证标签缩短为 4 字节PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, BTHOME_TAG_LEN)。加密 nonce13 字节的构造顺序为bthome_v2.c本机 BLE MAC 地址6 字节BTHome 服务 UUID0xFCD22 字节小端设备信息字节1 字节4 字节会话计数器bthome-counter。加密输出的密文与 4 字节 tag 一并写入广播的服务数据字段随后计数器自增并通过store回调持久化确保同一 nonce 不会被重复使用。解密时对称地使用对端 MAC、UUID、设备信息字节与报文末尾的计数器重建 noncebthome_v2.c——这正是文档要求设置peer_mac_addr解密侧与local_mac_addr加密侧的原因。若未导入密钥就调用加解密函数会打印 encryption key not set 并返回ESP_ERR_INVALID_STATE。密钥通过bthome_set_encrypt_key导入内部调用psa_crypto_init后以PSA_KEY_TYPE_AES、128 位、允许加密/解密、算法为缩短 tag 的 CCM 的密钥属性执行psa_import_keybthome_v2.c。重复设置新密钥时旧密钥会先被psa_destroy_key销毁。必须强调加密密钥16 字节是发送端与接收端/Home Assistant 之间的共享秘密务必保持一致且应与对端 MAC 一样通过白名单与密钥绑定来防止伪造设备混入。在真实工程中的集成bulb 示例剖析仓库提供了两个可直接运行的端到端示例发送侧的 dimmer 示例旋钮调光器主动广播事件与接收侧的 bulb 示例灯泡被动扫描并按事件控制 WS2812 LED 灯带。两者使用同一把测试密钥0x23, 0x1d, 0x39, ...与对端 MAC0x54, 0x48, 0xE6, 0x8F, 0x80, 0xA5。bulb 示例的整体数据流如下app_main.c初始化 NVSnvs_flash_init失败时擦除重试配置 BLE 扫描调用ble_hci_init/ble_hci_reset/ble_hci_enable_meta_event设置被动扫描参数scan_interval、scan_window均为0x50注册扫描回调ble_hci_scan_cb并把对端 MAC 加入接受列表accept list实现地址过滤最后ble_hci_set_scan_enable(true, true)开启扫描初始化 BTHome 接收实例bthome_create→bthome_register_callbacks(settings_store, settings_load)→bthome_set_encrypt_key→bthome_set_peer_mac_addr主循环消费扫描结果从队列取出扫描结果bthome_parse_adv_data解析若 report 的 id 为BTHOME_EVENT_ID_BUTTON且 data[0] 0x01翻转 LED 开关状态若为BTHOME_EVENT_ID_DIMMER按 data[0] 的方向0x01 增亮 / 0x02 减暗与 data[1] 的幅度更新亮度随后led_strip_set_pixelled_strip_refresh刷新灯带最后bthome_free_reports释放内存。这个例子完整示范了本文前面所有 API 的组合用法也是解析 BTHome 事件并驱动外设的最佳参考模板。编译与烧录bulb 示例的编译流程examples/bluetooth/ble_adv/bthome/bulb/README.mdcd examples/bluetooth/ble_adv/bthome/bulb # 设置目标芯片默认 ESP32-H2亦支持 ESP32-H4 idf.py set-target esp32h2 # 编译并烧录PORT 替换为实际串口 idf.py -p PORT build flash支持的目标芯片为 ESP32-H2默认与 dimmer 示例配对与 ESP32-H4LED 灯带 GPIO 默认 37。硬件上需要一块支持 BLE 的 ESP32 开发板与一根 WS2812 LED 灯带默认数据引脚 GPIO 8可通过 menuconfig 中BTHome Bulb Configuration调整引脚、LED 数量与 RMT 分辨率。验证方式在一台板子上烧录 dimmer 示例另一台烧录 bulb 示例按下 dimmer 按钮即可看到 LED 开关切换旋转旋钮即可调节亮度。测试与验证组件自带一套基于 Unity 框架的完整单元测试位于 components/bluetooth/ble_adv/bthome/test_apps构建与运行方式见 test_apps/README.mdcd components/bluetooth/ble_adv/bthome/test_apps idf.py build idf.py monitor # 进入 Unity 测试菜单运行用例测试用例覆盖用例名验证内容bthome_create_delete实例创建与销毁bthome_encryption_config加密密钥、本机/对端 MAC、回调注册bthome_payload_creation温湿度、气压、光照、能量、功率、电压、PM2.5/PM10、CO2、TVOC、电池等传感器数据编码bthome_binary_sensor_data运动、门磁、电源、光照等二进制传感器编码bthome_event_data按钮与调光器事件编码bthome_adv_data_creation完整广播报文构造与结构校验bthome_adv_data_parsing广播报文解析与报告数量边界bthome_memory_management10 次创建/销毁循环 内存泄漏检测8bit/32bit 堆bthome_error_handlingNULL 入参、空回调、未设密钥等异常路径根据 test_apps/README.md 的说明该测试应用已接入 CI 流水线在 ESP32、ESP32-S3、ESP32-C3 及 IDF 4.4、5.0、5.1、5.2 等多版本组合上自动运行。常见问题与注意事项内存管理bthome_parse_adv_data返回的 reports 及其内部 data 指针均为堆内存必须配对调用bthome_free_reports否则会产生内存泄漏组件 v0.1.0 曾修复过内存泄漏问题见 CHANGELOG.md。报告上限单个广播最多承载BTHOME_REPORTS_MAX10条报告负载较大时需拆分或精简传感器数量。密钥一致性加密模式要求发送端bthome_set_encrypt_keybthome_set_local_mac_addr接收端或 Home Assistant使用同一把密钥 bthome_set_peer_mac_addr任何一端缺失或不同都会导致解析失败解密打印psa_aead_decrypt failed。nonce 唯一性会话计数器必须通过 store/load 回调持久化否则重启后计数器回绕可能造成 nonce 复用削弱加密安全性。IDF 版本组件依赖 PSA Crypto API需要 IDF5.0见 idf_component.yml。回调非空bthome_register_callbacks强制要求 store 与 load 都非空纯发送侧也需要提供存储实现。总结esp-iot-solution 的 BTHome 组件以广播即数据的方式用极少的工程成本把 ESP32 变成 BTHome V2 协议的发送端或接收端发送侧用bthome_payload_add_sensor_data/bthome_payload_adv_add_bin_sensor_data/bthome_payload_adv_add_evt_data拼装负载用bthome_make_adv_data生成报文接收侧用bthome_parse_adv_data拆解报告并用bthome_free_reports释放内存加密模式依赖 AES-128-CCM 与持久化的会话计数器保证数据机密性与完整性。配合 Home Assistant 的 BTHome 集成开发者可以快速构建温湿度计、门磁、人体传感器、智能开关、调光灯泡等低功耗智能家居设备bulb/dimmer 示例则提供了从 BLE 扫描到外设控制的完整参考实现。【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考