GATT 协议基础:角色、结构与 UUID
好,咱们正式开始聊 GATT。说实话,很多做蓝牙 Mesh 的工程师,一上来就扎进 Mesh 模型和配置流程里,结果遇到手机连不上节点、数据读不出来这种问题,回头才发现是 GATT 层没搞透。我个人习惯是,不管做 Mesh 还是做传统 BLE,先把 GATT 这张网理清楚,后面写代码才不慌。
这一节咱们就掰开揉碎,把 GATT 的角色、Service/Characteristic/Descriptor 三层结构,还有 UUID 这套命名规则讲明白。你想想看,GATT 说白了就是蓝牙设备之间“怎么组织数据、怎么读写数据”的一套规矩。
2.1 GATT 角色:谁说话,谁听话?
GATT 定义了两种角色:GATT Client 和 GATT Server。这个概念特别重要,我见过不少新手把“主从”和“Client/Server”搞混。
- GATT Server:持有数据的一方。它把自己的数据组织成 Service 和 Characteristic,等着别人来读、来写、来订阅通知。
- GATT Client:主动发起读写操作的一方。它去发现 Server 上有哪些 Service,然后读写对应的 Characteristic。
举个例子,一个蓝牙温湿度传感器,它就是 GATT Server,把温度和湿度数据放在 Characteristic 里。手机 App 是 GATT Client,去读这些数据。嗯,这里要注意:GATT 角色和链路层的“主从”没有必然绑定关系。一个设备可以是 Central(主)同时又是 GATT Server,也可以是 Peripheral(从)同时又是 GATT Client。我在项目中就遇到过这种场景——一个网关设备作为 Central 连接多个传感器,但网关本身也作为 GATT Server 对外提供聚合数据。
关键点:在蓝牙 Mesh 的 Proxy 协议中,Proxy 节点同时扮演 GATT Server(提供 Proxy Service)和 GATT Client(处理来自手机的命令)。这个双重身份是理解 Mesh 与手机交互的基础。
2.2 三层结构:Service → Characteristic → Descriptor
GATT 的数据组织方式,就像一本书的目录结构。我习惯把它想象成一个文件系统:
- Service(服务):相当于一个文件夹,把相关的数据功能归类。比如“电池服务”、“设备信息服务”。
- Characteristic(特征):相当于文件夹里的一个文件,是实际数据的载体。每个 Characteristic 包含一个 Value 和若干 Descriptor。
- Descriptor(描述符):相当于文件的属性或说明。它描述 Characteristic 的配置、范围、用户描述等信息。
咱们用一张图来直观感受一下这个层级关系:
这张图把三层关系画得很清楚了。Service 在最上层,下面挂 Characteristic,再下面是 Descriptor。每个层级都有自己的 UUID 来标识。
2.3 Characteristic 的读写属性
Characteristic 不光有值,还有一组属性(Properties),告诉 Client 这个特征能干什么。我刚开始做 BLE 时,就吃过这个亏——定义了一个特征,但忘了配通知属性,结果手机端死活收不到数据。
常见的属性有这些:
| 属性 | 值(位掩码) | 说明 |
|---|---|---|
| Broadcast | 0x01 | 支持广播包中携带该特征值 |
| Read | 0x02 | Client 可以读取该特征值 |
| Write Without Response | 0x04 | Client 可以写入,但 Server 不回复确认 |
| Write | 0x08 | Client 可以写入,Server 回复确认 |
| Notify | 0x10 | Server 主动推送数据,Client 不确认 |
| Indicate | 0x20 | Server 主动推送数据,Client 必须确认 |
我的经验:Notify 和 Indicate 的区别在于可靠性。Notify 效率高但可能丢包,Indicate 可靠但慢。在 Mesh Proxy 协议中,Notify 用得更多,因为 Mesh 网络本身有重传机制,不需要在 GATT 层再搞一次确认。
2.4 UUID:给每个元素一个身份证
UUID(Universally Unique Identifier)就是蓝牙世界里每个 Service、Characteristic、Descriptor 的唯一编号。蓝牙 SIG 定义了一套标准 UUID,同时也允许开发者自定义。
UUID 分两种长度:
- 16-bit UUID:用于 SIG 标准定义的服务和特征。比如电池服务是 0x180F,电池电量特征是 0x2A19。这些是“官方认证”的。
- 128-bit UUID:用于自定义服务和特征。格式是 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx。比如 Mesh Proxy Service 的 UUID 就是 00001828-0000-1000-8000-00805F9B34FB。
你可能会问,为什么 128-bit UUID 里也有 0x1828 这种 16-bit 的影子?其实 128-bit UUID 的 Base UUID 是固定的:0000xxxx-0000-1000-8000-00805F9B34FB,把 16-bit UUID 填到 xxxx 位置就行。说白了,16-bit UUID 就是 128-bit UUID 的“缩写”。
下面这张表列出了 Mesh 开发中常见的 UUID:
| 名称 | UUID(16-bit) | 用途 |
|---|---|---|
| Mesh Proxy Service | 0x1828 | 提供 Mesh 网络与 GATT Client 之间的数据通道 |
| Mesh Proxy Data In | 0x2ADD | Client 向 Mesh 网络发送数据 |
| Mesh Proxy Data Out | 0x2ADE | Mesh 网络向 Client 发送数据 |
| Battery Service | 0x180F | 电池电量信息 |
| Device Information Service | 0x180A | 设备型号、序列号等 |
避坑指南:我曾经在自定义 Service 时,随手写了一个 16-bit UUID,结果和 SIG 标准冲突了,导致手机端解析出错。后来我养成了习惯——自定义服务一律用 128-bit UUID,并且用在线 UUID 生成器生成,确保全球唯一。
2.5 实战视角:GATT 在 Mesh Proxy 中的角色
咱们这门课的核心是 GATT 在 Mesh 中的应用,所以得提前打个底。Mesh Proxy 协议本质上就是通过 GATT 的 Notify 和 Write 操作,在 GATT 通道上传输 Mesh 网络的数据包。
具体来说:
- Proxy 节点作为 GATT Server,提供 Mesh Proxy Service(0x1828)。
- 这个 Service 包含两个 Characteristic:Proxy Data In(0x2ADD)和 Proxy Data Out(0x2ADE)。
- 手机 App 作为 GATT Client,往 Proxy Data In 里写数据,就相当于向 Mesh 网络发消息。
- Proxy 节点通过 Proxy Data Out 的 Notify,把 Mesh 网络的消息推送给手机。
你看,整个流程就是 GATT 三层结构的经典应用。Service 定义功能范围,Characteristic 承载数据流,Descriptor 配置传输方式(比如使能 Notify)。