3. 心跳机制与健康监测:配置心跳发布与订阅、心跳消息格式、基于心跳的节点存活检测算法

设备掉线了,你什么时候才知道?

很多做蓝牙Mesh的朋友,一开始都觉得配网成功就万事大吉了。结果呢?设备在角落里默默离线,用户那边灯控没反应了,后台还显示“在线”。这种问题我遇到过太多次了。

心跳机制,说白了就是让节点定期喊一嗓子:“我还活着!”

这一章,我们就来把心跳这件事聊透。从消息格式到发布订阅配置,再到我自己项目里用过的存活检测算法,全给你掰开揉碎了讲。

3.1 心跳消息长什么样?

蓝牙Mesh的心跳消息,不是你想发什么就发什么。它有一套固定的格式,咱们先把这个基础打牢。

字段 长度(字节) 说明
源地址 2 发送心跳的节点单播地址
目的地址 2 订阅了心跳的组播地址或单播地址
心跳消息 1 包含心跳计数和TTL信息

这个1字节的心跳消息,里面其实塞了两个东西:

  • 心跳计数(4位):每次心跳发布时递增,从0到15循环。用来判断有没有漏掉心跳。
  • 心跳TTL(4位):消息转发时经过的中继跳数。可以用来估算节点距离。

嗯,就这么简单。但简单归简单,配置起来有不少坑。

核心要点:心跳消息本身不携带业务数据,它就是一个“我还活着”的信号。别想着往里面塞传感器数据,那是另外的模型干的活。

3.2 心跳发布与订阅配置

配置心跳,你得操作两个东西:心跳发布和心跳订阅。

我个人习惯把发布端叫做“被监测节点”,订阅端叫做“监测中心”。

3.2.1 配置心跳发布

发布端要设置三个参数:

  1. 发布地址:心跳发到哪个组播地址或单播地址。
  2. 发布周期:多久发一次。单位是秒,一般建议10秒到60秒。
  3. 心跳消息初始TTL:默认用网络TTL就行,但如果你想控制传播范围,可以单独设。

配置命令长这样(以Config模型为例):

// 配置心跳发布
Config_Heartbeat_Publication_Set(
    dst: 0xC000,        // 组播地址,所有监测节点都订阅这个地址
    period: 10,         // 每10秒发一次
    ttl: 7,             // 初始TTL
    features: 0x00      // 不携带功能特性
);

这里有个细节:features字段可以携带节点当前的中继、代理、朋友等功能状态。但我建议初期先设为0,等稳定了再考虑加。

3.2.2 配置心跳订阅

订阅端要设置的就少一些:

  1. 订阅地址:监听哪个地址上的心跳消息。
  2. 心跳计数预期:用来判断是否连续丢包。
// 配置心跳订阅
Config_Heartbeat_Subscription_Set(
    src: 0x0001,        // 订阅来自节点0x0001的心跳
    period: 10,         // 预期每10秒收到一次
    count: 3            // 连续丢失3次判定为离线
);

我的经验:订阅端的count参数别设太小。我曾经设成2,结果网络稍微波动一下,设备就被误判离线了。后来改成3,误报率降了80%。

3.3 基于心跳的节点存活检测算法

配置好了,数据也收到了。接下来就是核心问题:怎么判断一个节点还活着?

我见过不少方案,有的简单粗暴,有的精细复杂。这里我分享三种,从简单到复杂,你自己选。

3.3.1 超时判定法(最简单)

这个法子,说白了就是:

  • 记录上次收到心跳的时间。
  • 如果超过N秒没收到,就判定离线。

伪代码:

uint32_t last_heartbeat_time = 0;
uint32_t timeout_threshold = 30; // 30秒超时

void on_heartbeat_received() {
    last_heartbeat_time = get_system_time();
}

bool is_node_alive() {
    uint32_t now = get_system_time();
    if (now - last_heartbeat_time > timeout_threshold) {
        return false; // 离线
    }
    return true; // 在线
}

优点:实现简单,CPU开销小。
缺点:容易误判。网络延迟高一点,或者心跳刚好卡在边界上,就误报了。

注意:超时阈值至少要设成心跳周期的3倍。比如心跳周期10秒,阈值就设30秒。别问我怎么知道的,问就是吃过亏。

3.3.2 心跳计数连续性检测法

这个方法比超时判定靠谱一些。它利用心跳消息里的4位计数。

原理:

  • 每次收到心跳,检查计数是否连续递增。
  • 如果计数跳变了(比如从5直接跳到8),说明中间丢了2个心跳。
  • 连续丢包超过阈值,判定离线。
uint8_t expected_count = 0;
uint8_t lost_count = 0;
uint8_t max_lost = 3; // 最多允许连续丢3个

void on_heartbeat_received(uint8_t count) {
    if (count != expected_count) {
        // 计数不连续,说明丢了包
        lost_count += (count - expected_count) & 0x0F;
    } else {
        // 正常收到,重置丢包计数
        lost_count = 0;
    }
    expected_count = (count + 1) & 0x0F;
}

bool is_node_alive() {
    return lost_count < max_lost;
}

这个方法好在哪里?它能感知到“丢包率”。就算心跳还在发,但如果丢包率突然升高,也能提前预警。

我在一个智能照明项目里用过这个方案。当时有个节点位置比较偏,信号时好时坏。用超时判定法,经常误报。换成计数检测法后,准确率提升了很多。

3.3.3 综合健康评分法(推荐)

这个方法是我在实际项目中打磨出来的。它不只看心跳,还结合了其他指标。

评分维度:

指标 权重 说明
心跳连续性 40% 基于计数检测的丢包率
心跳延迟 30% 从TTL估算的跳数,跳数越多延迟越高
响应成功率 30% 主动发送消息后,节点回复的成功率

评分公式:

uint8_t calculate_health_score() {
    uint8_t score = 0;
    
    // 1. 心跳连续性评分(0-40分)
    if (lost_count == 0) score += 40;
    else if (lost_count == 1) score += 30;
    else if (lost_count == 2) score += 15;
    else score += 0;
    
    // 2. 心跳延迟评分(0-30分)
    if (ttl <= 3) score += 30;
    else if (ttl <= 6) score += 20;
    else score += 10;
    
    // 3. 响应成功率评分(0-30分)
    uint8_t response_rate = (success_responses * 100) / total_requests;
    if (response_rate >= 90) score += 30;
    else if (response_rate >= 70) score += 20;
    else score += 10;
    
    return score;
}

bool is_node_healthy() {
    return calculate_health_score() >= 60; // 60分及格
}

这个方案的好处是:它不会因为一次网络抖动就判离线。而是综合多个维度,给出一个健康度评分。低于60分才触发告警。

实战建议:综合评分法的阈值可以动态调整。比如白天网络负载高,可以把阈值降到50分;晚上负载低,提到70分。我在一个商场项目里就是这么干的,误报率降到了1%以下。

3.4 心跳机制的整体流程

说了这么多,咱们用一张图把整个流程串起来。

心跳机制整体流程图 节点A(发布端) 节点B(中继) 节点C(订阅端) 心跳消息 转发 配置心跳发布 配置心跳订阅 存活检测算法 定期发布心跳 接收并解析心跳 输出在线/离线状态 核心逻辑 发布端按周期发送心跳 → 中继转发 → 订阅端接收 → 存活检测算法综合判断 → 输出健康状态

3.5 避坑指南

最后,分享几个我在项目里踩过的坑。你遇到了,直接绕过去。

坑1:心跳周期设得太短

我曾经把心跳周期设成2秒,想着实时性高一点。结果网络拥塞了,心跳消息互相挤占带宽,业务消息反而发不出去。后来改成10秒,问题解决。

建议:心跳周期不要短于5秒。一般10-30秒比较合理。

坑2:所有节点都往同一个组播地址发心跳

100个节点,每10秒发一次,组播地址上每秒要处理10条心跳。如果节点更多,处理不过来就会丢包。

建议:按区域或功能分组,每个组用不同的组播地址。比如“一楼灯光”一个组,“二楼灯光”一个组。

坑3:忽略心跳消息的TTL字段

TTL不仅能用来控制传播范围,还能反映节点间的网络质量。如果某个节点的TTL突然变大,说明它可能绕路了,网络拓扑发生了变化。这个信息可以用来做故障预判。