ESP-IoT-Solution BLE WSS 示例实战:基于 NimBLE 与 ble_conn_mgr 构建体重秤 GATT 服务端
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
本篇技术指南围绕 esp-iot-solution 仓库中的examples/bluetooth/ble_services/ble_wss示例展开,讲解如何在乐鑫 ESP32 系列芯片上,基于 NimBLE 协议栈与 ble_conn_mgr 连接管理组件,以最少样板代码实现一个符合蓝牙 SIG 规范的 Weight Scale Service(WSS,体重秤服务,UUID 0x181D)GATT 服务端。读完本文,你将掌握该示例的工程结构、编译烧录步骤、menuconfig 配置项,以及从服务注册、测量值打包到主动 Indicate 上报的完整源码级实现链路。
示例定位:一个开箱即用的 BLE 体重秤服务端
ble_wss示例的核心作用是在设备端创建一个 GATT 服务器(GATT Server),启动广播后等待 GATT 客户端(如手机上的 BLE 调试 App)连接。设备通过 Weight Scale Service 对外暴露体重测量能力:客户端可以读取设备的 Weight Feature(体重秤特性,UUID 0x2A9E),并订阅 Weight Measurement(体重测量值,UUID 0x2A9D)的 Indicate 通知,实时获取体重、时间戳、用户索引、BMI 与身高数据。
该示例在架构上复用了两个关键组件:
- ble_conn_mgr:BLE 连接管理器,封装了 NimBLE 的 GAP/GATT 细节,通过事件驱动模型(
BLE_CONN_MGR_EVENTS)与应用交互; - ble_services/wss:Weight Scale Service 的标准服务实现,直接向 ble_conn_mgr 注册服务与特征。
因此,示例的app_main.c非常精简——应用层只需要完成初始化与事件回调,服务逻辑全部收敛在组件内部。
支持的芯片与软硬件前提
根据示例 README 的说明,本示例支持以下芯片目标:
| 支持的芯片 | ESP32 | ESP32-C3 | ESP32-C2 | ESP32-S3 | ESP32-H2 |
|---|
硬件上只需:
- 一块搭载上述任一 SoC 的开发板;
- 一根用于供电与程序烧录的 USB 线;
- 用于测试的手机 BLE 扫描/连接 App(任意支持 GATT 的 BLE 调试工具即可)。
软件方面,main/idf_component.yml 声明了依赖约束:idf >= 4.3,组件ble_conn_mgr版本~1.*,组件ble_services版本~1.*,并统一通过override_path指向仓库内的本地组件目录,保证示例与仓库源码同步。
工程结构速览
examples/bluetooth/ble_services/ble_wss/ ├── CMakeLists.txt # 顶层工程文件,project(ble_wss) ├── README.md # 示例说明文档 ├── sdkconfig.defaults # 默认 sdkconfig(启用 BT/NimBLE/WSS) ├── sdkconfig.ci.nimble # CI 配置(强制 NimBLE) └── main/ ├── CMakeLists.txt # 组件注册,仅含 app_main.c ├── Kconfig.projbuild # 示例级配置项(广播名等) ├── app_main.c # 应用入口与事件回调 └── idf_component.yml # 组件依赖声明顶层 CMakeLists.txt 是标准的 ESP-IDF 工程骨架,project(ble_wss)声明工程名;main/CMakeLists.txt 只注册了一个源文件app_main.c,可见全部业务逻辑都集中在此。
配置工程:三步完成设置
第一步:设置目标芯片
在工程目录下,先用idf.py set-target指定目标芯片(根据你的开发板从支持列表中选择):
idf.py set-target <chip_name>例如 ESP32-S3 开发板执行idf.py set-target esp32s3。
第二步:打开菜单配置
idf.py menuconfig第三步:按需修改配置项
Example Configuration 菜单下有两个示例级配置项(定义于 main/Kconfig.projbuild):
Advertisement name:设备广播名。README 中记录的默认值为BLE_WSS;需要说明的是,当前仓库快照的 Kconfig 文件中实际默认值为BLE_CTS(该字段沿用了同目录其他服务示例的模板),如果你在复现时希望广播名为BLE_WSS,可在此处手动修改;Subsequent advertisement data:后续广播数据,默认SUB_ADV,对应源码中esp_ble_conn_config_t.broadcast_data字段(见 app_main.c)。
BLE Standard Services 菜单下:
GATT Weight Scale Service:是否启用 WSS 服务。其底层开关为组件 Kconfig.in 中定义的menuconfig BLE_WSS,默认为关闭;本示例的 sdkconfig.defaults 中已预置CONFIG_BLE_WSS=y将其打开。
此外,示例的 sdkconfig.defaults 预置了运行所需的关键开关,编译时无需手工逐个打开:
# 启用蓝牙协议栈 CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y # 启用 ble_conn_mgr 的外设角色 CONFIG_BLE_CONN_MGR_ROLE_PERIPHERAL=y # 启用 Weight Scale Service 组件 CONFIG_BLE_WSS=y其中CONFIG_BLE_CONN_MGR_ROLE_PERIPHERAL=y指定本设备作为 GATT 外设(Peripheral);sdkconfig.ci.nimble 则用于 CI 场景强制启用 NimBLE。
编译、烧录与运行
一条命令完成编译、烧录并打开串口监视器:
idf.py -p PORT flash monitor将PORT替换为开发板的串口设备名(如/dev/ttyUSB0)。退出串口监视器请按Ctrl-]。
运行输出解读
设备上电后,串口会输出类似下面的日志(节选自示例 README):
I (330) BLE_INIT: BT controller compile version [9359a4d] I (340) system_api: Base MAC address is not set I (340) system_api: read default base MAC address from EFUSE I (350) BLE_INIT: Bluetooth MAC: 58:cf:79:1e:9e:de I (350) phy_init: phy_version 1150,7c3c08f,Jan 24 2024,17:32:21 I (410) blecm_nimble: BLE Host Task Started I (410) blecm_nimble: No characteristic(0x2a00) found I (410) blecm_nimble: No characteristic(0x2a01) found I (410) blecm_nimble: No characteristic(0x2a05) found I (420) NimBLE: GAP procedure initiated: stop advertising. I (430) NimBLE: GAP procedure initiated: advertise; I (430) NimBLE: disc_mode=2 I (440) NimBLE: adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=256 adv_itvl_max=256 I (450) NimBLE: I (450) main_task: Returned from app_main()解读要点:
BLE Host Task Started:NimBLE 主机任务已启动,blecm_nimble组件完成 GATT 数据库初始化;- 三行
No characteristic(...):表示 ble_conn_mgr 在注册服务前会尝试查找 GATT 通用特征(如 Device Name 0x2a00、Appearance 0x2a01、Service Changed 0x2a05),本例未注册这些特征,属正常提示; GAP procedure initiated: advertise:设备已进入广播状态(disc_mode=2 为可发现模式),此时即可用手机 BLE 工具扫描到设备并连接;main_task: Returned from app_main():app_main正常返回,后续逻辑全部由事件驱动。
源码级原理剖析
1. 应用入口:事件驱动 + 三步初始化
app_main.c 的初始化流程清晰可分四步:
- NVS 初始化:
nvs_flash_init(),并在 NVS 空间不足/版本变更时先擦除再初始化(ESP-IDF 标准范式); - 事件循环与回调注册:
esp_event_loop_create_default()创建默认事件循环,esp_event_handler_register(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, ...)注册连接管理事件回调; - 连接管理器初始化:
esp_ble_conn_init(&config),其中device_name取自CONFIG_EXAMPLE_BLE_ADV_NAME,broadcast_data取自CONFIG_EXAMPLE_BLE_SUB_ADV; - 服务注册与启动:
esp_ble_wss_init()注册 WSS 服务,esp_ble_conn_start()启动广播;启动失败时依次执行stop → deinit → unregister的清理回退。
2. WSS 服务注册:特征查找表驱动
WSS 组件通过 esp_wss.c 定义了一张特征查找表nu_lookup_table,并通过esp_ble_conn_add_svc()将服务注册进 ble_conn_mgr:
static const esp_ble_conn_character_t nu_lookup_table[] = { { "weight feature", BLE_CONN_UUID_TYPE_16, BLE_CONN_GATT_CHR_READ , { BLE_WSS_CHR_UUID16_WEIGHT_FEATURE }, wss_feature_cb }, { "weight measurement", BLE_CONN_UUID_TYPE_16, BLE_CONN_GATT_CHR_INDICATE , { BLE_WSS_CHR_UUID16_WEIGHT_MEASUREMENT }, NULL }, };对照 esp_wss.h 中的 UUID 定义:服务 UUIDBLE_WSS_UUID16 = 0x181D,Weight Feature 特征0x2A9E(可读,带回调),Weight Measurement 特征0x2A9D(Indicate,无回调、由应用主动上报)。esp_ble_wss_init()的实现即一行:return esp_ble_conn_add_svc(&svc);。
3. Weight Feature:可读特征与位域编码
Weight Feature 特征在客户端读取时触发wss_feature_cb回调(esp_wss.c),回调将内部维护的s_wss_feature拷贝返回。其数据结构esp_ble_wss_feature_t按位域紧凑编码(esp_wss.h):
timestamp/user_id/bmi/weight/height:各 1 bit,标记对应能力是否支持;w_resolution(3 bit):体重分辨率,取值定义见 esp_wss.h,从0x0(无)到0x7(0.005 kg);h_resolution(2 bit):身高分辨率,取值从0x0(无)到0x3(0.001 m),见 esp_wss.h。
组件默认将全部能力置 1(esp_wss.c),并在每次设置测量值时依据测量值的 Flag 动态重建 Feature 位(见下节)。
4. 测量值结构:Flag + 可选字段
esp_ble_wss_measurement_t(esp_wss.h)是核心数据载体,采用蓝牙 SIG 规定的紧凑布局:
flag位域决定可选字段是否出现在报文中:measurement_unit(0 表示 kg/m 单位,1 表示按分辨率解释)、time_present(是否含时间戳)、user_present(是否含用户索引)、bmi_height_present(是否含 BMI 与身高);weight(uint16):体重值;timestamp(打包结构体):年 1582~9999、月 1~12、日 1~31、时 0~23、分/秒 0~59;user_id(uint8):用户索引;bmi(uint8)与height(uint16):BMI 与身高;weight_resolution/height_resolution:当measurement_unit=1时引用。
5. 测量值打包与主动上报
应用在连接建立后调用esp_ble_wss_set_measurement(&wss_measurement, true)(app_main.c),第二个参数need_send=true表示同时通过 Indicate 上报给客户端。该函数(esp_wss.c)做三件事:
- 保存测量值副本到静态变量
s_wss_measurement; - 根据测量值的 Flag 重建
s_wss_feature(例如time_present=1则置timestamp位,bmi_height_present=1则置bmi与height位); - 调用内部
build_wss_ind_buf()(esp_wss.c)按 Flag 顺序依次写入 Flag(4 字节)、weight(2 字节)、可选 timestamp、可选 user_id、可选 BMI+height,生成符合规范的 Indicate 报文,最后通过esp_ble_conn_write()发送给远端客户端。
esp_ble_wss_get_measurement()(esp_wss.c)则提供对称的读取接口,供应用查询最近一次测量值。
6. 连接事件驱动模型
app_main.c 中的事件回调app_ble_conn_event_handler监听BLE_CONN_MGR_EVENTS:
ESP_BLE_CONN_EVENT_CONNECTED:客户端连入,立即用一份预置的演示测量数据(年份 2024、用户 ID 1、BMI 0x18、身高 0x33 等)调用esp_ble_wss_set_measurement(..., true),主动向客户端 Indicate 一次体重测量;ESP_BLE_CONN_EVENT_DISCONNECTED:连接断开,仅打日志,等待下一次连接。
这就是"连接即上报"的典型 BLE 外设交互模式——实测中,手机 BLE 工具连接设备后即可直接收到一条 Weight Measurement 通知。
常见问题与排查思路
- 扫描不到设备:确认
idf.py set-target的芯片与开发板一致,检查CONFIG_BT_ENABLED与CONFIG_BT_NIMBLE_ENABLED是否打开(本示例 sdkconfig.defaults 已预置); - 连接后收不到测量通知:确认手机端已订阅 Weight Measurement 特征的 Indicate(客户端需先写 CCCD);并检查
CONFIG_BLE_CONN_MGR_ROLE_PERIPHERAL=y是否生效; - 广播名与预期不符:在
Example Configuration → Advertisement name中核对配置值(注意当前仓库 Kconfig 的默认模板值为BLE_CTS,如需BLE_WSS请手动修改); - 服务未出现:确认
menuconfig中GATT Weight Scale Service(CONFIG_BLE_WSS)已启用,该开关控制 esp_wss.c 是否参与编译。
如需进一步深入,可直接阅读 ble_conn_mgr 与 ble_services 两个组件的文档,对比ble_services目录下其他标准服务(如 UDS、BCS、HRS 等)的实现,会发现它们共享完全相同的服务注册模式——掌握本示例后,即可快速移植出任意一个自定义 GATT 服务。
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考