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 的配置、范围、用户描述等信息。

咱们用一张图来直观感受一下这个层级关系:

GATT 三层结构示意图 GATT 三层结构:Service → Characteristic → Descriptor Service(服务) 例如:0x180F 电池服务 Characteristic(特征) 例如:0x2A19 电池电量特征 Descriptor 0x2902 客户端特征配置 (使能通知/指示) Descriptor 0x2901 用户描述 (文本说明) Descriptor 0x2904 特征表示格式 (数据类型/单位) 说明: • 一个 Service 可以包含多个 Characteristic • 一个 Characteristic 可以包含多个 Descriptor • Characteristic Value 是实际数据,Descriptor 是元数据

这张图把三层关系画得很清楚了。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)。