3. 蓝牙Mesh协议栈与照明模型解析

消息发布与订阅,是蓝牙Mesh的灵魂。说白了,它解决了一个核心问题:一个开关按下,怎么让特定的一批灯亮起来,而不是全屋的灯都跟着瞎起哄。

我刚开始接触Mesh时,总觉得这不就是广播嘛。后来踩了坑才明白,发布/订阅模型加上地址类型,才是Mesh网络真正灵活的地方。

3.1 发布/订阅模型:谁说话,谁听话

在蓝牙Mesh里,节点之间不直接点对点通信。它们通过一种“话题”机制来交互。

  • 发布者(Publisher):只管往某个“话题”上发消息。比如一个墙壁开关,它按下后发布一条“开灯”指令。
  • 订阅者(Subscriber):只关心自己订阅的“话题”。灯泡订阅了“客厅灯”话题,它只处理这个话题下的消息。

这种解耦非常实用。开关不需要知道灯泡的地址,灯泡也不需要知道开关是谁。它们只认“话题”。

核心优势:新增一个灯泡,只需要配置它订阅“客厅灯”话题,开关代码一行都不用改。这在智能照明项目中太常见了。

我在做第一个Mesh项目时,客户要求一个开关控制三组灯。如果点对点通信,我得写死三个地址。用了发布/订阅,开关只发布一个消息,三组灯各自订阅就行。代码量直接减半。

3.2 地址类型:单播、组播、虚拟地址

地址就是“话题”的编号。蓝牙Mesh定义了三种地址,各有各的用处。

地址类型 范围 用途 我常用的场景
单播地址 0x0001 ~ 0x7FFF 点对点通信,配置设备 给灯泡配网、升级固件
组播地址 0xC000 ~ 0xFFFF 一对多群组通信 “客厅灯”、“卧室灯”分组控制
虚拟地址 基于128位UUID 灵活的场景绑定 “回家模式”、“观影模式”

3.2.1 单播地址:每个人的身份证

每个节点在入网时,会被分配一个唯一的单播地址。这就像每个人的身份证号。配置工具(比如手机App)通过单播地址,单独给某个灯泡设置参数。

嗯,这里要注意:单播地址是有限的,整个网络最多支持32767个节点。家庭照明绰绰有余,但大型商业项目就要规划好。

3.2.2 组播地址:群聊的群号

组播地址就是群组ID。一个灯泡可以加入多个群组。比如一个吸顶灯,既属于“客厅灯”群组,又属于“全屋灯”群组。

我曾经遇到一个坑:客户说“为什么我关客厅灯,走廊灯也灭了?”查了半天,发现走廊灯误订阅了客厅灯的组播地址。所以组播地址的分配一定要做好文档记录。

3.2.3 虚拟地址:场景的魔法标签

虚拟地址基于128位UUID,数量几乎无限。它不占用组播地址池,适合做场景绑定。

举个例子:你希望一个“回家模式”开关,同时控制门厅灯、客厅灯、窗帘。你可以创建一个虚拟地址,让这些设备都订阅它。开关发布消息到这个虚拟地址,所有设备响应。

我的习惯:组播地址用于固定的物理分组(如楼层、房间),虚拟地址用于逻辑场景(如“离家”、“睡眠”)。这样管理起来清晰很多。

3.3 消息缓存与重传机制:Mesh网络的“快递员”

Mesh网络是无线环境,丢包是常态。蓝牙Mesh设计了缓存与重传机制,确保消息能可靠送达。

3.3.1 消息缓存:避免“消息风暴”

每个节点收到一条消息后,会先检查自己的缓存。如果这条消息的序列号已经在缓存里,说明已经转发过了,直接丢弃。这防止了消息在网络里无限循环。

缓存大小有限,默认会保存最近收到的几十条消息。我建议在资源允许的情况下,适当增大缓存,尤其是在中继节点上。

3.3.2 重传机制:三次不行就四次

发布者发送消息后,不会只发一次。它会按照配置的重传次数和间隔,重复发送。

举个例子:一个开关发送“开灯”指令,默认可能重传2次,间隔50毫秒。这样即使第一次丢包,第二次或第三次也能到达。

参数 默认值 说明
重传次数 2 消息发送后额外重传的次数
重传间隔 50ms 每次重传之间的时间间隔
TTL(生存时间) 127 消息最多能跳转的次数

避坑指南:我曾经在一个项目中,为了追求“响应快”,把重传次数设为0。结果在信号干扰强的环境里,灯泡经常不响应。后来改回默认值2,问题解决。重传不是浪费,是可靠性保障。

3.4 消息发布与订阅的完整流程

下面这张图展示了从开关按下到灯泡亮起的完整路径。

消息发布与订阅流程 消息发布与订阅流程 墙壁开关 发布者 发布消息 组播地址 0xC001 转发 中继节点 检查缓存 未缓存,继续转发 灯泡 A 订阅 0xC001 灯泡 B 订阅 0xC001 灯泡 C 未订阅 丢弃 流程说明: 1. 开关发布消息到组播地址 0xC001 2. 中继节点收到消息,检查缓存,发现未处理过,继续转发 3. 灯泡A和灯泡B订阅了0xC001,收到消息后执行开灯动作 4. 灯泡C未订阅,丢弃消息

3.5 配置示例:用代码说话

下面是一个配置发布/订阅的伪代码示例。实际项目中,你会通过Mesh Model API来操作。

// 配置开关为发布者
void configure_switch() {
    // 设置发布地址为组播地址 0xC001
    config_publish_address(0xC001);
    // 设置重传次数为2,间隔50ms
    config_retransmit(2, 50);
    // 设置TTL为10,限制跳转范围
    config_ttl(10);
}

// 配置灯泡为订阅者
void configure_light() {
    // 订阅组播地址 0xC001
    config_subscribe_address(0xC001);
    // 订阅虚拟地址(场景)
    config_subscribe_virtual_uuid("回家模式UUID");
}

我的建议:TTL不要设太大。家庭环境设10就够,大型商场设30。TTL越大,网络负载越高。我曾经见过一个项目TTL设127,结果消息在Mesh网络里转了几十圈才被缓存丢弃,白白浪费带宽。

3.6 避坑总结

  • 地址冲突:组播地址范围有限,多个项目共用时容易冲突。我习惯用0xC000-0xCFFF做固定分组,0xD000-0xDFFF做临时分组。
  • 缓存溢出:中继节点缓存太小,会导致重复消息转发。建议至少缓存64条消息。
  • 重传过多:重传次数不是越多越好。3次以上收益递减,反而增加网络拥塞。