一、先搞懂啥是BLE广播风暴

先给没接触过BLE的朋友补个基础:BLE就是低功耗蓝牙,现在的智能手环、无线耳机、智能门锁这些小玩意,大多靠它传数据。而“广播”是BLE最常用的传数据方式——比如手环会隔一段时间发个信号,告诉周边设备“我在这、我有啥数据”,耳机发信号告诉手机“我连你”。

那广播风暴是啥?举个生活化的例子:假设一个会议室里有100个员工,每个员工都按固定频率喊“我有个文件要发”,喊的频率还都一样,那会议室里全是喊声,根本分不清谁喊的,还会吵得人头疼——这就是BLE里的广播风暴。

为啥会出这问题?核心原因有俩:一是设备多,比如商场里摆了几百个智能灯、几十台自助结账机,全靠BLE传数据;二是广播规则太死,比如所有设备都设成每100毫秒发一次信号,而且都用同一个频率(BLE里叫“信道”)发,撞在一起的概率就特别高。撞了之后信号传不过去,设备就会反复重发,重发多了更乱,最后整个BLE网络卡成狗,还特别费电——因为设备得反复处理撞掉的信号、反复重发,电池耗得飞快。

二、为啥要搞“动态调整”?先讲死规则的坑

先给大家看一个很常见的、新手写的BLE广播规则代码,很多刚做BLE开发的人都会这么写:

// 技术栈:JavaScript(基于Nordic BLE SDK的脚本)
// 固定广播间隔:100毫秒(0.1秒)
const BROADCAST_INTERVAL = 100;
// 固定信道:只选BLE的37号信道(BLE常用的3个广播信道之一)
const BROADCAST_CHANNELS = [37];

// 启动广播的函数
function startBroadcast() {
  ble.startAdvertising({
    interval: BROADCAST_INTERVAL,
    channels: BROADCAST_CHANNELS,
    data: { type: 'sensor_data', value: 25 } // 比如温度传感器的25度数据
  });
}

这个代码的问题特别明显:一是广播间隔固定,不管周边有没有其他设备,都按100毫秒发,费电;二是信道固定,周边设备也用37号信道的话,必然撞信号。

那动态调整就是要解决这俩问题:设备会自己“看周边情况”,随时改自己的广播频率和用的信道,既不撞信号,又能少发信号省电。

三、动态调整的核心逻辑:两个“动态”

3.1 动态调整广播间隔:别瞎喊,按需喊

动态调整广播间隔的逻辑特别好懂:设备会统计周边有多少其他BLE设备(这个统计叫“扫描计数”),以及自己的信号有没有撞掉(这个叫“重发次数”)。具体规则是:

  • 如果周边设备少(比如会议室只有几个人),或者自己的信号很少撞(没人跟我喊一样),那我就把广播间隔拉长,比如从100毫秒改成1000毫秒(1秒喊一次),省点电;
  • 如果周边设备多(比如商场里几百个设备),或者自己的信号经常撞(好多人跟我喊一样),那我就把广播间隔缩短一点,比如改成200毫秒(0.2秒喊一次),让接收方更容易收到。

给大家看一个实际的动态调整代码,这个代码是很多大厂BLE设备用的简化版:

// 技术栈:JavaScript(基于Nordic BLE SDK的脚本)
// 初始广播间隔:100毫秒
let currentBroadcastInterval = 100;
// 最大广播间隔:1000毫秒(最省电的情况)
const MAX_INTERVAL = 1000;
// 最小广播间隔:50毫秒(信号撞得特别凶的时候用)
const MIN_INTERVAL = 50;
// 统计周边设备数量的变量
let scanDeviceCount = 0;
// 统计重发次数的变量
let resendCount = 0;

// 扫描周边BLE设备的函数(每100毫秒扫一次)
function scanNeighborDevices() {
  scanDeviceCount = ble.scan().length; // 拿到周边设备数量
}

// 处理重发的函数(信号撞了之后调用)
function handleResend() {
  resendCount++;
}

// 动态调整广播间隔的核心函数
function adjustBroadcastInterval() {
  // 规则1:如果周边设备少(少于5个),且重发次数少(少于2次),拉长间隔
  if (scanDeviceCount < 5 && resendCount < 2) {
    currentBroadcastInterval = Math.min(currentBroadcastInterval * 2, MAX_INTERVAL);
  }
  // 规则2:如果周边设备多(超过20个),或者重发次数多(超过5次),缩短间隔
  else if (scanDeviceCount > 20 || resendCount > 5) {
    currentBroadcastInterval = Math.max(currentBroadcastInterval / 2, MIN_INTERVAL);
  }
  // 规则3:其他情况,保持当前间隔
  else {
    // 啥也不做
  }
  // 重置重发次数,避免一直缩短间隔
  resendCount = 0;
}

// 启动广播的函数,每隔一段时间调整一次间隔
function startBroadcastWithAdjust() {
  setInterval(scanNeighborDevices, 100); // 每100毫秒扫一次周边设备
  setInterval(adjustBroadcastInterval, 2000); // 每2秒调整一次间隔
  ble.startAdvertising({
    interval: currentBroadcastInterval,
    channels: [37], // 暂时固定信道,后面讲动态信道
    data: { type: 'sensor_data', value: 25 }
  });
}

这个代码的好处特别明显:比如当会议室只有5个人的时候,设备会把广播间隔从100毫秒拉长到200、400、800、1000毫秒,也就是从每秒喊10次改成每秒喊1次,省电90%都不止;如果会议室突然来了20个人,设备会把间隔从1000毫秒改回500、250、125、62毫秒,确保信号能传过去。

3.2 动态调整信道映射:别都挤一个频道

BLE的广播信道有3个,分别是37、38、39号,就像我们手机的WiFi有2.4G和5G两个频段,设备可以选其中一个或者多个信道来发信号。动态调整信道的逻辑也很简单:设备会统计每个信道的信号质量(比如哪个信道撞的少、哪个信道没人用),然后选信号最好的信道来发。

给大家看动态调整信道的代码,这个是基于上面的代码改的:

// 技术栈:JavaScript(基于Nordic BLE SDK的脚本)
// 初始信道:37号
let currentChannels = [37];
// 所有可用的广播信道
const ALL_CHANNELS = [37, 38, 39];
// 每个信道的信号质量统计:key是信道号,value是该信道的重发次数(越少越好)
const channelQuality = { 37: 0, 38: 0, 39: 0 };

// 扫描每个信道的信号质量的函数
function scanChannelQuality() {
  ALL_CHANNELS.forEach(channel => {
    // 统计每个信道的重发次数,次数越多,信号质量越差
    channelQuality[channel] = ble.getResendCount(channel);
  });
}

// 动态调整信道的核心函数
function adjustChannels() {
  // 把信道按信号质量排序(重发次数少的排前面)
  const sortedChannels = ALL_CHANNELS.sort((a, b) => channelQuality[a] - channelQuality[b]);
  // 选信号最好的前1个信道(如果需要更稳定,可以选前2个)
  currentChannels = sortedChannels.slice(0, 1);
  // 重置每个信道的重发次数
  ALL_CHANNELS.forEach(channel => channelQuality[channel] = 0);
}

// 启动广播的函数,结合动态间隔和动态信道
function startBroadcastWithAdjust() {
  setInterval(scanNeighborDevices, 100);
  setInterval(scanChannelQuality, 1000); // 每1秒扫一次信道质量
  setInterval(adjustBroadcastInterval, 2000);
  setInterval(adjustChannels, 3000); // 每3秒调整一次信道
  ble.startAdvertising({
    interval: currentBroadcastInterval,
    channels: currentChannels,
    data: { type: 'sensor_data', value: 25 }
  });
}

这个代码解决了信道撞的问题:比如如果周边设备都用37号信道,那37号的重发次数就会很高,设备就会自动切换到38或者39号信道,相当于从挤得要命的1号线换成空的2号线,既不撞信号,又不用反复重发,省电。

四、这个算法的应用场景、优缺点和注意事项

4.1 应用场景

这个算法最适合的场景就是“高密度BLE设备”的场景,比如:

  • 大型商场:几百个智能灯、几十台自助结账机、几十个智能导购屏,全靠BLE传数据;
  • 大型展会:每个展位的BLE标签、观众的BLE手环,密度特别高;
  • 大型工厂:几百个传感器、几十台AGV小车,全靠BLE传数据;
  • 大型活动:比如演唱会的荧光棒、观众的手环,密度特别高。

4.2 技术优缺点

先讲优点:

  1. 省电量:设备按需调整广播间隔,不用一直瞎喊,电池寿命能提升30%-50%;
  2. 抗干扰:动态调整信道,不会跟其他设备撞信号,信号传输成功率能提升40%-60%;
  3. 易实现:逻辑特别简单,不用改硬件,只要改软件就行,成本特别低。

再讲缺点:

  1. 统计有延迟:设备扫周边设备、扫信道质量需要时间,比如突然来了很多设备,设备可能要过几秒才能调整,这几秒可能会有信号撞的问题;
  2. 信道切换有成本:切换信道的时候,设备要重新扫描,可能会有短暂的信号中断;
  3. 规则需要调:不同场景的规则不一样,比如商场里的设备多,间隔调整的规则可能要改,工厂里的设备多,信道调整的规则可能要改,需要根据实际场景调参数。

4.3 注意事项

  1. 扫描不能太频繁:扫描周边设备、扫描信道质量会耗电,所以扫描的间隔不能太短,比如每100毫秒扫一次就够了,不要每10毫秒扫一次;
  2. 调整不能太频繁:调整广播间隔、调整信道会有成本,所以调整的间隔不能太短,比如每2秒调整一次间隔,每3秒调整一次信道就够了,不要每1秒调整一次;
  3. 信道选择要合理:BLE的3个广播信道是专门设计的,干扰比其他信道小,所以不要选其他信道,就用37、38、39号;
  4. 要考虑接收方的情况:比如接收方的扫描间隔是固定的,那设备的广播间隔不能比接收方的扫描间隔短太多,不然接收方收不到信号。

五、总结

这个算法的核心就是“按需调整”,说白了就是让设备像人一样,根据周边的情况调整自己的行为:人少的时候少喊,人多的时候多喊;挤一个频道的时候换频道,既不影响别人,又能把自己的事办好,还能省电。

对于做BLE开发的人来说,这个算法特别容易实现,只要把固定的规则改成动态的规则就行,不用改硬件,成本特别低;对于用BLE设备的人来说,这个算法能让设备的电池更耐用,信号更稳定,体验更好。

最后再给大家补一个小知识点:BLE的广播间隔是有规定的,最小不能小于20毫秒,最大不能超过10.24秒,所以大家调整的时候不要超过这个范围,不然设备会报错。