4. Mesh Proxy Service详解:UUID、特征值与节点身份识别
好,咱们进入第四章。这一章我打算把 Mesh Proxy Service 彻底讲透。说白了,这个服务就是蓝牙 Mesh 和标准 BLE 之间的那座桥。没有它,你的手机 App 根本没法跟 Mesh 网络里的节点说话。
我记得刚接触 Mesh 时,第一反应就是:这不就是个 GATT 服务吗?有什么难的?结果真上手调的时候,发现坑还真不少。尤其是 Proxy Data In/Out 这两个特征值,读写时机搞错了,节点直接掉线。嗯,咱们今天就把这些细节掰开揉碎了讲。
4.1 Mesh Proxy Service 的 UUID
先看最基础的东西——UUID。Mesh Proxy Service 的官方 UUID 是:
Service UUID: 0x1828
这个 16-bit UUID 在 Bluetooth SIG 里是专门分配给 Mesh Proxy Service 的。你想想看,整个 BLE 协议栈里服务那么多,0x1828 就是 Mesh 的专属门牌号。
我个人的习惯是,在代码里不要硬编码这个值,而是用宏定义或者常量。为什么呢?因为项目后期如果要升级协议栈版本,UUID 定义的位置统一了,改起来方便。我曾经在一个项目里看到同事把 UUID 散落在七八个文件里,那叫一个酸爽。
关键点:Mesh Proxy Service 的 UUID 是 0x1828,所有支持 Proxy 功能的节点都必须声明这个服务。
4.2 Proxy Data In 特征值
这个特征值负责接收数据。说白了,它就是 Mesh 网络的入站通道。手机 App 通过这个特征值把 Mesh 协议数据单元(PDU)发给节点。
| 属性 | 值 |
|---|---|
| UUID | 0x2ADD |
| 属性 | Write Without Response(优先)、Write |
| 安全要求 | 需要加密连接(LE Secure Connections) |
| 最大长度 | MTU - 3 字节(默认 20 字节,可扩展) |
这里有个容易踩的坑:Write Without Response 是推荐方式。为什么?因为 Mesh 网络本身就有重传机制,不需要 GATT 层再确认一次。如果你用 Write Request,每次写都要等回应,延迟会明显增加。
我曾经犯过的错:在早期一个项目中,我用了 Write Request 来写 Proxy Data In。结果节点数量一多,手机端频繁等待响应,整个配网流程慢得像蜗牛。后来改成 Write Without Response,速度直接翻了三倍。
4.3 Proxy Data Out 特征值
有进就有出。Proxy Data Out 是节点向手机发送 Mesh 数据的通道。它的配置和 Data In 正好互补:
| 属性 | 值 |
|---|---|
| UUID | 0x2ADE |
| 属性 | Notify(必须)、Read(可选) |
| CCCD 要求 | 必须使能通知 |
| 数据格式 | Network PDU 或 Mesh Message |
注意看,Data Out 用的是 Notify,不是 Indicate。这意味着节点主动推送数据,手机端不需要确认。你想想看,Mesh 网络里一个消息可能被中继多次,如果每跳都确认,那延迟就爆炸了。
我建议你在实现 Data Out 时,一定要处理好 CCCD(Client Characteristic Configuration Descriptor)。手机端必须先写 CCCD 使能通知,节点才能发数据。这个顺序搞反了,数据就丢了。
小技巧:在调试阶段,可以在 Data Out 的回调里加个打印,每次收到通知请求时输出当前 CCCD 值。这样能快速定位是手机没使能通知,还是节点没发出来。
4.4 节点身份识别
这部分我觉得是 Proxy Service 里最容易被忽视的。节点身份识别,说白了就是让手机知道:你是谁?你属于哪个网络?
Mesh 规范里定义了两种身份识别方式:
- Network ID:基于网络密钥(NetKey)生成的 8 字节标识。手机端如果已经知道 NetKey,就能通过 Network ID 确认节点属于同一个网络。
- Node Identity:基于节点密钥(Node Key)生成的 8 字节标识。这个更精确,能直接识别出具体是哪个节点。
我画了一张图,帮你理清这两种识别方式的流程:
实际项目中,我建议你这样处理:
- 先尝试 Network ID:如果手机已经配过网,大概率知道 NetKey。直接匹配 Network ID 最快。
- 匹配失败再用 Node Identity:如果 Network ID 对不上,说明节点可能属于另一个网络,或者手机还没拿到 NetKey。这时候用 Node Identity 做精确匹配。
- 都匹配不上:节点进入未配网状态,等待 Provisioning。
核心要点:节点身份识别不是可选项,是 Proxy 服务的必选功能。没有它,手机端无法判断该跟谁建立 Proxy 连接。
4.5 实际代码片段
最后,我贴一段实际项目中用过的初始化代码。这是基于 Zephyr 协议栈的,但思路通用:
// Mesh Proxy Service 初始化
static struct bt_gatt_attr proxy_attrs[] = {
BT_GATT_PRIMARY_SERVICE(BT_UUID_MESH_PROXY),
// Proxy Data In - 只写
BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROXY_DATA_IN,
BT_GATT_CHRC_WRITE_WITHOUT_RESP |
BT_GATT_CHRC_WRITE,
BT_GATT_PERM_WRITE_ENCRYPT,
NULL, proxy_data_in_write, NULL),
// Proxy Data Out - 通知
BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROXY_DATA_OUT,
BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_NONE,
NULL, NULL, NULL),
BT_GATT_CCC(proxy_data_out_ccc_cb,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};
// 注册服务
bt_gatt_register(proxy_attrs, ARRAY_SIZE(proxy_attrs));
注意看 Data Out 的 CCCB 回调。我习惯在里面加个标志位,记录手机是否使能了通知。这样在发送数据时可以先检查,避免无效发送。
避坑指南:我曾经在 Data In 的写回调里直接处理 Mesh 消息,结果导致协议栈死锁。后来改成用消息队列,把收到的数据先丢到队列里,再由主循环处理。这样既安全又高效。
好了,这一章的内容就到这里。Mesh Proxy Service 看似简单,但 Data In/Out 的读写时机、节点身份识别的匹配逻辑,都是实际开发中容易出问题的地方。希望今天的分享能帮你少走弯路。