ESP IoT Solution 设备信息服务(BLE DIS)开发指南:GATT 服务构建与特征值读写实战
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
设备信息服务(Device Information Service,DIS)是蓝牙低功耗(BLE)协议栈中用于对外公开设备制造商、供应商与产品版本信息的标准 GATT 服务。本文基于 esp-iot-solution 仓库中的 BLE 标准服务实现(docs/en/bluetooth/ble_dis.rst)与配套示例工程,讲解如何基于esp_ble_conn_mgr连接管理框架快速注册 DIS 服务、配置各特征值与 PnP ID,并通过esp_ble_dis_*API 在连接事件中读取设备信息,帮助读者掌握在 ESP32 系列芯片上集成标准 BLE 服务与连接管理的完整实战路径。
一、DIS 服务是什么
DIS(Device Information Service)是 Bluetooth SIG 定义的标准 GATT 服务(Service UUID 为 0x180A),其核心职责是公开关于设备的制造商和/或供应商信息。在 esp-iot-solution 的文档中,这一服务被描述为 "exposes manufacturer and/or vendor information about a device",即向 BLE 对端(如手机 App、网关)暴露设备型号、序列号、固件/硬件/软件版本、厂商名、System ID 以及 PnP ID 等元数据。
该服务的典型应用场景包括:
- 设备身份识别:客户端扫描到广播后,通过读取 DIS 特征值即可获知设备的型号与厂商,用于在 UI 中展示设备类型;
- 固件版本校验:OTA 升级前读取 Firmware Revision,判断是否需要推送新版本;
- 产测与溯源:通过 Serial Number 唯一标识每一台设备实例,配合 System ID 与 PnP ID 完成设备追踪。
在 esp-iot-solution 中,DIS 属于 components/bluetooth/ble_services/dis 下的标准 BLE 服务组件,与 ANS、BAS、HRS 等服务并列,统一基于 components/bluetooth/ble_conn_mgr 连接管理框架实现,而非直接操作 NimBLE/Bluedroid 底层 API。
二、仓库结构与示例总览
DIS 相关资源在仓库中分布在两个位置:
| 位置 | 内容 | 作用 |
|---|---|---|
| components/bluetooth/ble_services/dis/include/esp_dis.h | 服务 UUID、特征值 UUID、数据结构与全部 API 声明 | 组件头文件,供应用层调用 |
| components/bluetooth/ble_services/dis/src/esp_dis.c | 服务注册、特征值回调与读写实现 | 组件源码 |
| components/bluetooth/ble_services/dis/Kconfig.in | 组件配置菜单(Kconfig) | 定义各特征值的开关与默认值 |
| examples/bluetooth/ble_services/ble_dis/main/app_main.c | 示例主程序 | 演示初始化、配置与事件回调 |
| examples/bluetooth/ble_services/ble_dis/README.md | 示例说明 | 编译、烧录与运行指引 |
示例工程 examples/bluetooth/ble_services/ble_dis 创建一个 GATT Server 并开始广播,等待 GATT Client 连接;它基于 BLE 连接管理(esp_ble_conn_mgr)构建,官方支持 ESP32、ESP32-C3、ESP32-C2、ESP32-S3、ESP32-H2 等芯片目标。文档(ble_dis.rst)中给出的核心入口为:
:example:`bluetooth/ble_services/ble_dis`.即直接引用该示例工程作为实战起点。
三、源码级剖析:DIS 组件如何工作
3.1 服务与特征值 UUID 定义
在 esp_dis.h 中定义了服务与特征值的 16 位 UUID:
| 宏 | UUID | 含义 |
|---|---|---|
BLE_DIS_UUID16 | 0x180A | DIS 服务 |
BLE_DIS_CHR_UUID16_SYSTEM_ID | 0x2A23 | System ID |
BLE_DIS_CHR_UUID16_MODEL_NUMBER | 0x2A24 | Model Number(型号) |
BLE_DIS_CHR_UUID16_SERIAL_NUMBER | 0x2A25 | Serial Number(序列号) |
BLE_DIS_CHR_UUID16_FIRMWARE_REVISION | 0x2A26 | Firmware Revision(固件版本) |
BLE_DIS_CHR_UUID16_HARDWARE_REVISION | 0x2A27 | Hardware Revision(硬件版本) |
BLE_DIS_CHR_UUID16_SOFTWARE_REVISION | 0x2A28 | Software Revision(软件版本) |
BLE_DIS_CHR_UUID16_MANUFACTURER_NAME | 0x2A29 | Manufacturer Name(厂商名) |
BLE_DIS_CHR_UUID16_REG_CERT | 0x2A2A | Regulatory Certification Data(法规认证数据) |
BLE_DIS_CHR_UUID16_PNP_ID | 0x2A50 | PnP ID |
其中 0x2A2A 仅在头文件中声明了 UUID 宏,当前实现并未为它注册特征值回调;其余特征值均可在 Kconfig 中按需启用。
3.2 数据结构
组件用两个核心结构体承载设备信息(见 esp_dis.h):
esp_ble_dis_pnp_t(PnP ID,__attribute__((packed))紧凑排列):
typedef struct esp_ble_dis_pnp { uint8_t src; /*!< The vendor ID source */ uint16_t vid; /*!< The product vendor from the namespace in the vendor ID source*/ uint16_t pid; /*!< Manufacturer managed identifier for this product*/ uint16_t ver; /*!< Manufacturer managed version for this product*/ } __attribute__((packed)) esp_ble_dis_pnp_t;esp_ble_dis_data_t(保存全部特征值字符串与 PnP ID):
typedef struct esp_ble_dis_data { char model_number[CONFIG_BLE_DIS_STR_MAX]; char serial_number[CONFIG_BLE_DIS_STR_MAX]; char firmware_revision[CONFIG_BLE_DIS_STR_MAX]; char hardware_revision[CONFIG_BLE_DIS_STR_MAX]; char software_revision[CONFIG_BLE_DIS_STR_MAX]; char manufacturer_name[CONFIG_BLE_DIS_STR_MAX]; char system_id[CONFIG_BLE_DIS_STR_MAX]; esp_ble_dis_pnp_t pnp_id; } esp_ble_dis_data_t;字符串存储长度由CONFIG_BLE_DIS_STR_MAX决定(默认 32,范围 2~64),超出长度的写入会被截断(见源码中MIN(CONFIG_BLE_DIS_STR_MAX, strlen(value))的处理)。
3.3 特征值回调与只读语义
DIS 的特征值均为只读(BLE_CONN_GATT_CHR_READ)。在 esp_dis.c 中通过nu_lookup_table将每个特征值与回调函数绑定:
static const esp_ble_conn_character_t nu_lookup_table[] = { #ifdef CONFIG_BLE_DIS_SYSTEM_ID {"system_id", BLE_CONN_UUID_TYPE_16, BLE_CONN_GATT_CHR_READ, { BLE_DIS_CHR_UUID16_SYSTEM_ID }, esp_dis_system_id_cb}, #endif #ifdef CONFIG_BLE_DIS_MODEL {"model_number", BLE_CONN_UUID_TYPE_16, BLE_CONN_GATT_CHR_READ, { BLE_DIS_CHR_UUID16_MODEL_NUMBER }, esp_dis_model_number_cb}, #endif /* ... */ #ifdef CONFIG_BLE_DIS_PNP {"pnp_id", BLE_CONN_UUID_TYPE_16, BLE_CONN_GATT_CHR_READ, { BLE_DIS_CHR_UUID16_PNP_ID }, esp_dis_pnp_id_chr_cb}, #endif }; static const esp_ble_conn_svc_t svc = { .type = BLE_CONN_UUID_TYPE_16, .uuid = { .uuid16 = BLE_DIS_UUID16, }, .nu_lookup_count = sizeof(nu_lookup_table) / sizeof(nu_lookup_table[0]), .nu_lookup = (esp_ble_conn_character_t *)nu_lookup_table };每个字符串回调(如esp_dis_model_number_cb)遵循统一模式:对读请求用strndup复制对应字符串到输出缓冲区,并设置*att_status = ESP_IOT_ATT_SUCCESS;当资源不足时返回ESP_IOT_ATT_INSUF_RESOURCE,参数非法时返回ESP_IOT_ATT_INTERNAL_ERROR。PnP ID 回调则直接calloc一块sizeof(esp_ble_dis_pnp_t)内存并memcpy结构体内容,保证以紧凑的二进制格式(5 字节)上报。
3.4 服务的注册与注销
服务注册通过连接管理框架完成:
esp_err_t esp_ble_dis_init(void) { return esp_ble_conn_add_svc(&svc); } esp_err_t esp_ble_dis_deinit(void) { return esp_ble_conn_remove_svc(&svc); }也就是说,DIS 组件本身不直接接触 GATT 栈 API,而是把"服务表"交给esp_ble_conn_mgr统一注册,从源码结构看,esp_ble_conn_mgr负责在 Host 启动后批量添加服务并处理连接/MTU 更新等事件。
四、Kconfig 配置详解
DIS 的全部可配置项位于 Kconfig.in,可通过idf.py menuconfig在BLE Standard Services菜单下进入GATT Device Information service配置。各配置项说明如下:
4.1 基础配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
BLE_DIS | bool | - | DIS 服务总开关(menuconfig) |
BLE_DIS_STR_MAX | int | 32 | 字符串存储长度,范围 2~64 |
BLE_DIS_MODEL | string | ESP | 设备型号 |
BLE_DIS_MANUF | string | Manufacturer | 厂商名 |
BLE_DIS_SYSTEM_ID | string | System | System ID(注意:此处为字符串类型,按文本上报) |
4.2 PnP ID 相关配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
BLE_DIS_PNP | bool | y | 是否启用 PnP ID 特征值 |
BLE_DIS_PNP_VID_SRC | int | 1 | Vendor ID Source:1 = Bluetooth SIG 分配,2 = USB IF 分配(范围 1~2) |
BLE_DIS_PNP_VID | hex | 0x0 | Vendor ID,与 VID Source 配合标识厂商(范围 0x0~0xFFFF) |
BLE_DIS_PNP_PID | hex | 0x0 | Product ID,厂商自管的产品标识(范围 0x0~0xFFFF) |
BLE_DIS_PNP_VER | hex | 0x1 | Product Version,BCD 编码的版本号(范围 0x0~0xFFFF) |
其中BLE_DIS_PNP_VER采用 Binary-Coded Decimal 编码:值为0xJJMN表示版本JJ.M.N。例如版本 2.1.3 编码为0x0213,版本 2.0.0 编码为0x0200。规范建议:向上兼容的变更递增 minor 版本号,不兼容变更递增 major 版本号,bug 修复递增 sub-minor 版本号。
4.3 版本类特征值开关
| 配置项 | 类型 | 说明 |
|---|---|---|
BLE_DIS_SERIAL_NUMBER | bool | 启用 Serial Number 特征值 |
BLE_DIS_SERIAL_NUMBER_STR | string | Serial Number 内容(依赖前者) |
BLE_DIS_FW_REV | bool | 启用 Firmware Revision 特征值 |
BLE_DIS_FW_REV_STR | string | Firmware Revision 内容 |
BLE_DIS_HW_REV | bool | 启用 Hardware Revision 特征值 |
BLE_DIS_HW_REV_STR | string | Hardware Revision 内容 |
BLE_DIS_SW_REV | bool | 启用 Software Revision 特征值 |
BLE_DIS_SW_REV_STR | string | Software Revision 内容 |
在示例工程中,sdkconfig.defaults 通过以下配置同时打开 NimBLE 协议栈、连接管理外设角色与 DIS 各特征值:
CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y CONFIG_BLE_CONN_MGR_ROLE_PERIPHERAL=y CONFIG_BLE_DIS=y CONFIG_BLE_DIS_SERIAL_NUMBER=y CONFIG_BLE_DIS_FW_REV=y CONFIG_BLE_DIS_HW_REV=y CONFIG_BLE_DIS_SW_REV=y CONFIG_BLE_DIS_PNP=y注意BLE_DIS_MODEL、BLE_DIS_MANUF、BLE_DIS_SYSTEM_ID等字符串项在 Kconfig 中直接给出默认值即视为启用;而版本类特征值需要同时打开 bool 开关并填写字符串内容。示例同时提供了 sdkconfig.ci.nimble 用于 CI 环境的 NimBLE 配置。
五、示例工程实战
5.1 环境准备与硬件要求
示例官方支持 ESP32、ESP32-C3、ESP32-C2、ESP32-S3、ESP32-H2 芯片。硬件上需要一块对应的开发板、一根 USB 线(供电与烧录)。开始前先设置目标芯片:
idf.py set-target <chip_name>例如idf.py set-target esp32c3。
5.2 工程配置
运行idf.py menuconfig打开配置界面:
- 在
Example Configuration菜单中,通过Advertisement name修改广播设备名(默认BLE_DIS);Subsequent advertisement data可设置后续广播数据(默认SUB_ADV);Settings Device Information开关用于决定是否在运行期覆写设备信息(默认开启); - 在
BLE Standard Services菜单中,通过GATT Device Information service选择需要启用的可选特征值(默认关闭,示例通过 sdkconfig.defaults 显式开启)。
5.3 编译、烧录与监控
idf.py -p PORT flash monitorPORT替换为实际串口(如/dev/ttyUSB0)。退出串口监控按Ctrl-]。
5.4 初始化流程解析
app_main.c 的启动流程分四步:
- 初始化 NVS:调用
nvs_flash_init(),若返回ESP_ERR_NVS_NO_FREE_PAGES或ESP_ERR_NVS_NEW_VERSION_FOUND则先nvs_flash_erase()再重试; - 创建并注册事件循环:
esp_event_loop_create_default()后,用esp_event_handler_register(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, app_ble_conn_event_handler, NULL)订阅连接管理事件; - 初始化连接管理:
esp_ble_conn_init(&config),配置项包括广播名device_name与后续广播数据broadcast_data; - 初始化 DIS 并启动:
app_ble_dis_init()调用esp_ble_dis_init()注册服务,随后esp_ble_conn_start()开始广播;启动失败则依次esp_ble_conn_stop()、esp_ble_conn_deinit()并注销事件回调。
5.5 在运行期覆写设备信息
当CONFIG_EXAMPLE_BLE_DIS_INFO开启时,app_main.c 演示了如何在运行期用代码覆盖 Kconfig 默认值:
static void app_ble_dis_init(void) { esp_ble_dis_init(); #ifdef CONFIG_EXAMPLE_BLE_DIS_INFO esp_ble_dis_pnp_t pnp_id = { .src = CONFIG_BLE_DIS_PNP_VID_SRC, .vid = CONFIG_BLE_DIS_PNP_VID, .pid = CONFIG_BLE_DIS_PNP_PID, .ver = CONFIG_BLE_DIS_PNP_VER }; static char mac_str[19] = {0}; uint8_t mac[6]; ESP_ERROR_CHECK(esp_read_mac((uint8_t *)mac, ESP_MAC_BT)); sprintf(mac_str, MACSTR, MAC2STR(mac)); esp_ble_dis_set_model_number(CONFIG_BLE_DIS_MODEL); esp_ble_dis_set_serial_number(mac_str); esp_ble_dis_set_firmware_revision(esp_get_idf_version()); esp_ble_dis_set_hardware_revision(esp_get_idf_version()); esp_ble_dis_set_software_revision(esp_get_idf_version()); esp_ble_dis_set_manufacturer_name(CONFIG_BLE_DIS_MANUF); esp_ble_dis_set_system_id(CONFIG_BLE_DIS_SYSTEM_ID); esp_ble_dis_set_pnp_id(&pnp_id); #endif }这段代码展示了两个实用技巧:
- 用蓝牙 MAC 地址作为序列号:通过
esp_read_mac(mac, ESP_MAC_BT)读取蓝牙 MAC,格式化为xx:xx:xx:xx:xx:xx字符串后作为 Serial Number,实现每台设备唯一; - 版本号动态获取:
esp_get_idf_version()直接取当前 IDF 版本作为固件/硬件/软件版本,避免硬编码。
5.6 连接事件中读取设备信息
app_main.c 的事件回调在收到ESP_BLE_CONN_EVENT_CONNECTED时,通过 Get 系列 API 读取全部设备信息并打印:
case ESP_BLE_CONN_EVENT_CONNECTED: ESP_LOGI(TAG, "ESP_BLE_CONN_EVENT_CONNECTED"); esp_ble_dis_get_pnp_id(&pnp_id); esp_ble_dis_get_model_number(&model_number); esp_ble_dis_get_serial_number(&serial_number); esp_ble_dis_get_firmware_revision(&firmware_revision); esp_ble_dis_get_hardware_revision(&hardware_revision); esp_ble_dis_get_software_revision(&software_revision); esp_ble_dis_get_manufacturer_name(&manufacturer); esp_ble_dis_get_system_id(&system_id); /* ... ESP_LOGI 打印各字段,PnP ID 打印 src/vid/pid/ver */ break;字符串类 Get 接口返回const char **指针(指向组件内部静态存储,勿释放),PnP ID 通过esp_ble_dis_get_pnp_id(&pnp_id)以结构体方式拷贝返回。
六、API 速查表
以下 API 全部声明于 esp_dis.h,返回esp_err_t(成功为ESP_OK,参数非法为ESP_ERR_INVALID_ARG):
| API | 功能 |
|---|---|
esp_ble_dis_init() | 注册 DIS 服务到连接管理框架(失败返回ESP_FAIL) |
esp_ble_dis_deinit() | 从连接管理框架注销服务(失败返回esp_ble_conn_remove_svc()的错误码) |
esp_ble_dis_get_model_number(const char **value) | 获取型号 |
esp_ble_dis_set_model_number(const char *value) | 设置型号 |
esp_ble_dis_get_serial_number(const char **value) | 获取序列号 |
esp_ble_dis_set_serial_number(const char *value) | 设置序列号 |
esp_ble_dis_get_firmware_revision(const char **value) | 获取固件版本 |
esp_ble_dis_set_firmware_revision(const char *value) | 设置固件版本 |
esp_ble_dis_get_hardware_revision(const char **value) | 获取硬件版本 |
esp_ble_dis_set_hardware_revision(const char *value) | 设置硬件版本 |
esp_ble_dis_get_software_revision(const char **value) | 获取软件版本 |
esp_ble_dis_set_software_revision(const char *value) | 设置软件版本 |
esp_ble_dis_get_manufacturer_name(const char **value) | 获取厂商名 |
esp_ble_dis_set_manufacturer_name(const char *value) | 设置厂商名 |
esp_ble_dis_get_system_id(const char **value) | 获取 System ID |
esp_ble_dis_set_system_id(const char *value) | 设置 System ID |
esp_ble_dis_get_pnp_id(esp_ble_dis_pnp_t *pnp_id) | 获取 PnP ID(结构体拷贝) |
esp_ble_dis_set_pnp_id(esp_ble_dis_pnp_t *pnp_id) | 设置 PnP ID |
Set 系列接口内部通过memset清零后按MIN(CONFIG_BLE_DIS_STR_MAX, strlen(value))截断拷贝,因此传入超长字符串会被安全截断。
七、运行效果与验证
示例 README(README.md)给出了连接并读取特征值后的典型串口输出。启动阶段可见 NimBLE 广播启动日志与连接管理日志:
I (200) blecm_nimble: BLE Host Task Started I (200) blecm_nimble: getting characteristic(0x2a00) I (200) blecm_nimble: getting characteristic(0x2a01) I (200) blecm_nimble: getting characteristic(0x2a05) I (220) NimBLE: GAP procedure initiated: advertise;对端连接并读取特征值后,app_main 打印设备信息:
I (27280) app_main: ESP_BLE_CONN_EVENT_CONNECTED I (27280) app_main: Model name ESP I (27280) app_main: Serial Number 84:f7:03:08:21:1a I (27280) app_main: Firmware revision v4.3.5-dirty I (27280) app_main: Hardware revision v4.3.5-dirty I (27280) app_main: Software revision v4.3.5-dirty I (27280) app_main: Manufacturer name Manufacturer I (27280) app_main: System ID System I (27280) app_main: PnP ID Vendor ID Source 0x1, Vendor ID 0x00, Product ID 0x00, Product Version 0x01同时 blecm_nimble 会打印客户端对各个特征值 UUID 的读请求记录(如0x2a23System ID、0x2a24Model Number、0x2a25Serial Number、0x2a26Firmware Revision、0x2a27Hardware Revision、0x2a28Software Revision、0x2a29Manufacturer Name、0x2a50PnP ID),可直接用任意 BLE 扫描/调试 App(如 nRF Connect)验证。
八、常见问题与注意事项
- 服务未出现在对端设备中:检查
CONFIG_BLE_DIS是否已使能(示例中通过 sdkconfig.defaults 显式开启),并确认esp_ble_dis_init()在esp_ble_conn_start()之前被调用; - 特征值读取为空:确认对应特征值的 Kconfig bool 开关(如
CONFIG_BLE_DIS_SERIAL_NUMBER)已打开,否则特征值不会被注册到nu_lookup_table; - 字符串被截断:所有字符串特征值长度受
CONFIG_BLE_DIS_STR_MAX限制(默认 32),如需更长内容请调整该配置(范围 2~64); - 版本号建议动态化:示例中通过
esp_get_idf_version()填充版本特征值,避免固件升级后版本信息失实; - DIS 为只读服务:所有特征值均为
BLE_CONN_GATT_CHR_READ权限,客户端无法写入修改。
九、进一步探索
- 该组件属于 components/bluetooth/ble_services 标准服务家族,可参照其结构了解 ANS、BAS、CTS、HRS 等其他服务的实现方式;
- 连接管理框架 components/bluetooth/ble_conn_mgr 是理解服务注册与事件分发机制的关键;
- 更多 BLE 服务示例见 examples/bluetooth/ble_services,更多 BLE 主题文档见 docs/en/bluetooth。
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考