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网络。
整个流程是这样的:
- 手机扫描到Proxy Server广播的「Mesh Proxy Service」UUID(0x1828)。
- 手机发起GATT连接。
- 手机发现Service和Characteristics:
- Mesh Proxy Data In(UUID: 0x2ADD):手机往这个Characteristic写数据(发给Mesh网络)。
- Mesh Proxy Data Out(UUID: 0x2ADE):手机订阅这个Characteristic的Notification(接收来自Mesh网络的数据)。
- 双方通过这两个管道,开始交换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网络里的。
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的间隔。