3、GATT与Mesh的桥梁:Proxy协议、PB-GATT承载层、GATT承载层的报文格式

好,咱们进入第三章。这一章可以说是整个课程里最「拧巴」的部分——为什么?因为蓝牙Mesh本质上是个基于广播的洪泛网络,而GATT是面向连接的、一对一的。这两个东西怎么凑到一起的?

说白了,就是靠一个叫Proxy的协议在中间当翻译官。我当年第一次看Spec的时候,也觉得这设计挺绕的。但等你真正调试过几次配网失败的案例,就会明白这个桥梁有多重要。

3.1 为什么需要Proxy?

先想一个问题:你的手机能直接收发蓝牙Mesh的广播包吗?

答案是:不能。绝大多数手机的操作系统(iOS、Android)都不开放对蓝牙广播包的自定义扫描和发送接口,尤其是Mesh那种长广播包(最多31字节,根本装不下Mesh网络PDU)。

那手机怎么控制Mesh设备?

嗯,这里就需要Proxy协议了。手机通过GATT连接到一个Mesh设备(这个设备叫Proxy节点),然后把这个GATT连接当作一个「管道」,把Mesh的协议数据单元(PDU)塞进GATT的Notification和Write里。Proxy节点收到后,再把它转成广播包发到Mesh网络里。

核心一句话:Proxy协议 = GATT通道 + Mesh网络PDU的封装与转发。

3.2 Proxy协议的角色与流程

Proxy协议定义了两种角色:

  • Proxy Client(代理客户端)——通常是手机、平板、PC,它通过GATT连接发送和接收Mesh PDU。
  • Proxy Server(代理服务端)——通常是Mesh节点(比如智能灯、网关),它维护GATT服务,同时参与Mesh网络。

整个流程是这样的:

  1. 手机扫描到Proxy Server广播的「Mesh Proxy Service」UUID(0x1828)。
  2. 手机发起GATT连接。
  3. 手机发现Service和Characteristics:
    • Mesh Proxy Data In(UUID: 0x2ADD):手机往这个Characteristic写数据(发给Mesh网络)。
    • Mesh Proxy Data Out(UUID: 0x2ADE):手机订阅这个Characteristic的Notification(接收来自Mesh网络的数据)。
  4. 双方通过这两个管道,开始交换Mesh Network PDU。

我个人的习惯:在调试Proxy连接时,先确认手机是否成功订阅了Data Out的Notification。很多问题都出在这一步——手机没订阅,自然收不到Mesh网络的任何消息。

3.3 PB-GATT承载层:配网阶段的桥梁

Proxy协议解决的是「已经入网的设备怎么通过GATT通信」。那还没入网的设备呢?

这就轮到PB-GATT(Provisioning Bearer over GATT)登场了。

PB-GATT是配网阶段的承载层。当一个新设备(未配网设备)想加入Mesh网络时,它可以通过GATT连接与Provisioner(配网器)通信,完成配网流程。

PB-GATT使用的Service UUID是0x1827(Mesh Provisioning Service),包含两个Characteristic:

Characteristic UUID 方向 说明
Mesh Provisioning Data In 0x2ADB Provisioner → 设备 配网器写入配网数据
Mesh Provisioning Data Out 0x2ADC 设备 → Provisioner 设备通过Notification发送配网数据

配网完成后,设备会切换到Proxy Service(0x1828),进入正常的数据通信阶段。

我曾经踩过一个坑:PB-GATT的配网数据包是分片传输的,每个包最大20字节(GATT MTU默认23字节,减去3字节头)。如果配网器没有正确处理分片重组,设备就会卡在配网阶段。后来我强制把MTU协商到247字节,才彻底解决这个问题。

3.4 GATT承载层的报文格式

不管是Proxy还是PB-GATT,底层传输的都是GATT报文。但Mesh协议在GATT报文里又封装了一层自己的格式。

咱们直接看Proxy协议的报文格式:

+--------+--------+--------+--------+--------+--------+--------+
| Octet 0 | Octet 1 | Octet 2 ... N                          |
+--------+--------+--------+--------+--------+--------+--------+
| SAR    | Type   | Mesh PDU (Network PDU / Provisioning PDU) |
+--------+--------+--------+--------+--------+--------+--------+

第一个字节的高两位是SAR(Segmentation and Reassembly)字段,用来处理分片:

  • 0b00:Complete Message(完整消息,未分片)
  • 0b01:First Segment(分片的第一段)
  • 0b10:Continuation Segment(中间段)
  • 0b11:Last Segment(最后一段)

第一个字节的低6位是Type字段:

Type值 含义
0x00 Network PDU(网络层数据)
0x01 Mesh Beacon(信标)
0x02 Proxy Configuration(代理配置)
0x03 Provisioning PDU(配网数据)

从第二个字节开始,就是真正的Mesh PDU内容了。

调试小技巧:抓包时如果看到SAR字段不是0b00,说明这个GATT包是分片的一部分。你需要把所有分片收齐才能重组出完整的Mesh PDU。我习惯在代码里加一个分片超时机制——超过5秒没收齐,直接丢弃,防止内存泄漏。

3.5 整体关系图

下面这张图把Proxy协议、PB-GATT、GATT承载层的关系画清楚了。我建议你多看几遍,理解数据是怎么从手机一路走到Mesh网络里的。

GATT与Mesh桥梁关系图 手机(Proxy Client) Proxy Server Mesh网络节点 GATT连接 Proxy / PB-GATT 广播/好友 GATT承载层报文格式 | SAR (2bit) | Type (6bit) | Mesh PDU (可变长度) | SAR: 00=完整 01=首段 10=中间段 11=末段 Type: 0x00=Network PDU 0x01=Beacon 0x02=Proxy Config 0x03=Provisioning 配网阶段 vs 数据阶段 配网阶段:PB-GATT (Service 0x1827) → Provisioning PDU 数据阶段:Proxy (Service 0x1828) → Network PDU 配网完成后,设备自动切换到Proxy Service

3.6 实际开发中的注意事项

讲完理论,说几个我在项目中遇到的真实问题:

  • MTU协商不能省:默认23字节的MTU太窄了,分片多了容易丢包。我建议在GATT连接建立后,立即发起MTU Request,争取到247字节以上。
  • Notification的CCCD要确认:很多手机端SDK会自动订阅,但有些不会。你必须在代码里显式写入0x0001到Client Characteristic Configuration Descriptor。
  • Proxy过滤:Proxy Server可以设置过滤规则,只转发特定地址或特定类型的Mesh PDU。这个功能在节点多的网络里特别有用,能大幅减少GATT通道的流量。

我曾经遇到过一个诡异的问题:手机端收不到Mesh网络的应答消息。查了两天,最后发现是Proxy Server的Notification发送频率太快,手机端的BLE芯片处理不过来,导致丢包。解决办法是在Server端加一个发送队列,每发一个Notification等一个Connection Event的间隔。