ESP-IDF项目中的BLE数据交换指南
概述
本文是ESP-IDF项目中蓝牙低功耗(BLE)入门系列的第四篇教程,将深入讲解BLE连接中的数据交换过程。我们将重点介绍GATT服务器的代码实现,基于NimBLE主机层栈的示例项目。
学习目标
- 理解特征数据和服务的数据结构细节
- 了解GATT中的不同数据访问操作
- 掌握GATT服务器示例的代码结构
GATT数据特征与服务
GATT服务是BLE连接中两个设备间数据交换的基础设施,最小数据单元是属性。在数据表示与交换部分,我们简要介绍了ATT层的属性和GATT层的特征数据、服务及规范。
属性结构
一个属性包含以下四个部分:
- 句柄(Handle):16位无符号整数,表示属性在属性表中的索引
- 类型(Type):使用UUID(通用唯一标识符)来区分类型
- 访问权限(Access Permission):指示是否需要加密/授权,是否可读或可写
- 值(Value):实际的用户数据或另一个属性的元数据
BLE中有两种UUID类型:
- SIG定义的16位UUID
- 制造商自定义的128位UUID
常见特征和服务UUID可在SIG的"Assigned Numbers"标准文档中找到,例如:
- 血压服务:0x1810
- 通用音频服务:0x1853
- 年龄特征:0x2A80
- 外观特征:0x2A01
特征数据
一个特征数据项通常由以下属性组成:
-
特征声明(Characteristic Declaration):
- 包含特征值的属性、句柄和UUID信息
- UUID为0x2803,只读
-
特征值(Characteristic Value):
- 用户数据
- UUID标识特征类型
-
特征描述符(Characteristic Descriptor):
- 特征数据的附加描述
- 可选属性
特征声明与特征值的关系
以心率测量为例,特征声明与特征值的关系如下表所示:
| 句柄 | UUID | 权限 | 值 | 属性类型 | |------|---------|----------|-----------------------------|-----------------------| | 0 | 0x2803 | 只读 | 属性=只读 | 特征声明 | | | | | 句柄=1 | | | | | | UUID=0x2A37 | | | 1 | 0x2A37 | 只读 | 标志位 | 特征值 | | | | | 测量值 | |
特征描述符
特征描述符提供关于特征数据的补充信息。最常见的是客户端特征配置描述符(CCCD)。当特征支持服务器发起的数据操作(通知或指示)时,必须使用CCCD来描述相关信息。
CCCD的UUID是0x2902,其属性值仅包含2位信息:
- 第一位表示是否启用通知
- 第二位表示是否启用指示
添加CCCD后,完整的心率测量特征数据如下:
| 句柄 | UUID | 权限 | 值 | 属性类型 | |------|---------|--------------|-----------------------------|-----------------------| | 0 | 0x2803 | 只读 | 属性=读/指示 | 特征声明 | | | | | 句柄=1 | | | | | | UUID=0x2A37 | | | 1 | 0x2A37 | 读/指示 | 标志位 | 特征值 | | | | | 测量值 | | | 2 | 0x2902 | 读/写 | 通知状态 | 特征描述符 | | | | | 指示状态 | |
服务结构
服务的数据结构大致可分为两部分:
- 服务声明属性
- 特征定义属性
服务声明属性的UUID为0x2800,是只读的,保存着标识服务类型的UUID。例如心率服务的UUID是0x180D,其服务声明属性可表示为:
| 句柄 | UUID | 权限 | 值 | 属性类型 | |------|---------|----------|----------|----------------| | 0 | 0x2800 | 只读 | 0x180D | 服务声明 |
属性表示例
以下是一个GATT服务器可能的属性表示例,包含两个服务:心率服务和自动化IO服务。前者包含一个心率测量特征,后者包含一个LED特征。
完整属性表如下:
| 句柄 | UUID | 权限 | 值 | 属性类型 | |------|----------------------------------------|--------------|-----------------------------|-----------------------| | 0 | 0x2800 | 只读 | UUID=0x180D | 服务声明 | | 1 | 0x2803 | 只读 | 属性=读/指示 | 特征声明 | | | | | 句柄=2 | | | | | | UUID=0x2A37 | | | 2 | 0x2A37 | 读/指示 | 标志位 | 特征值 | | | | | 测量值 | | | 3 | 0x2902 | 读/写 | 通知状态 | 特征描述符 | | | | | 指示状态 | | | 4 | 0x2800 | 只读 | UUID=0x1815 | 服务声明 | | 5 | 0x2803 | 只读 | 属性=只写 | 特征声明 | | | | | 句柄=6 | | | | | | 自定义UUID | | | 6 | 自定义UUID | 只写 | LED状态 | 特征值 |
当GATT客户端首次与GATT服务器建立通信时,它会从服务器的属性表中拉取元数据以发现可用的服务和特征,这个过程称为"服务发现"。
GATT数据操作
数据操作指的是访问GATT服务器上的特征数据,主要可分为两种类型:
- 客户端发起的操作
- 服务器发起的操作
客户端发起的操作
包括以下三种类型:
-
读取(Read):
- 从GATT服务器拉取特定特征的当前值
-
写入(Write):
- 标准写入操作需要GATT服务器在收到客户端的写入请求和数据后确认
-
无响应写入(Write without response):
- 另一种形式的写入操作,不需要服务器确认
服务器发起的操作
分为两种类型:
-
通知(Notify):
- GATT服务器主动将数据推送到客户端,不需要确认响应
-
指示(Indicate):
- 类似于通知,但需要客户端确认,这使得指示比通知慢
虽然通知和指示都是由服务器发起的,但这些操作的前提是客户端已启用通知或指示。因此,GATT中的数据交换过程本质上始于客户端对数据交换的请求。
实际应用建议
-
选择合适的UUID:
- 对于标准特征和服务,使用SIG定义的16位UUID
- 对于自定义功能,使用128位UUID确保唯一性
-
权限设置:
- 根据安全需求合理设置读写权限
- 敏感数据应设置加密要求
-
性能考虑:
- 频繁更新的数据使用通知(Notify)而非指示(Indicate)以提高效率
- 关键数据使用指示(Indicate)确保可靠性
-
服务设计:
- 将相关特征组织在同一服务中
- 保持属性表的逻辑清晰和结构合理
通过本文的介绍,开发者应该能够理解ESP-IDF项目中BLE数据交换的基本原理和实现方法,为开发基于BLE的应用打下坚实基础。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考