最佳实践
广播设备的频率控制、节流策略与常见问题
广播策略管理器
BroadcastStrategyManager 是高频广播场景的核心管理器,负责频率控制、强度变化检测、停止指令去重和数据压缩。它封装了 BluetoothService,为上层业务(滑动、音乐、视频、语音等)提供统一的广播入口。
import { BroadcastStrategyManager, BroadcastPresets } from '@/broadcast/broadcast-strategy'
// 创建滑动模式的策略管理器
const manager = new BroadcastStrategyManager(BroadcastPresets.swipe)
// 添加数据点,管理器自动决定是否广播
manager.addDataPoint({ intensity: 5, timestamp: Date.now() })预设配置
系统提供五种预设配置,针对不同业务场景优化:
| 预设模式 | 广播间隔 | 相似度阈值 | 压缩比 | 平滑 | 说明 |
|---|---|---|---|---|---|
swipe (滑动) | 150ms | 0.95 | 0.2 | 开启 | 实时响应手势操作 |
music (音乐) | 200ms | 0.85 | 0.4 | 开启 | 跟随音频节拍变化 |
video (视频) | 200ms | 0.9 | 0.3 | 开启 | 跟随视频内容,过滤伪随机噪音 |
voice (语音) | 150ms | 0.9 | 0.3 | 开启 | 实时响应环境声音 |
performance (高性能) | 150ms | 0.8 | 0.6 | 关闭 | 最大压缩,最低延迟 |
配置参数详解
interface BroadcastStrategyConfig {
maxHistoryPoints: number // 最大历史数据点数(默认 500)
similarityThreshold: number // 相似度阈值 0-1(越高越严格)
compressionRatio: number // 压缩比例 0-1
broadcastInterval: number // 广播间隔 (ms)
enableSmoothing: boolean // 是否启用平滑算法
enableCompression: boolean // 是否启用数据压缩
useSimplifiedMode: boolean // 是否使用简化模式(减少缓冲延迟)
}所有预设默认启用 useSimplifiedMode(简化模式),跳过压缩缓冲区直接广播,以获得最低延迟。
频率控制策略
档位变化过滤
核心过滤规则:只在档位变化 >= 1.0 时发送新指令。由于长指令会持续执行直到收到停止指令,相同档位无需重复发送。
private shouldBroadcast(dataPoint: BroadcastDataPoint): boolean {
// 首次广播,无条件通过
if (!this.lastBroadcastData) return true
const intensityDiff = Math.abs(
dataPoint.intensity - this.lastBroadcastData.intensity
)
// 档位变化 >= 1.0 时发送
if (intensityDiff >= 1.0) return true
// 相同档位不重复发送(长指令会持续执行)
return false
}停止指令去重
停止指令(intensity === 0)在 500ms 内去重,避免重复发送:
if (dataPoint.intensity === 0) {
if (this.lastBroadcastData?.intensity === 0) {
const timeDiff = now - this.lastBroadcastData.timestamp
if (timeDiff < 500) {
return false // 500ms 内跳过重复停止指令
}
}
return true // 允许发送停止指令
}BluetoothService 层防抖
BluetoothService 内部还有 100ms 防抖机制,非停止指令在 100ms 内的重复请求会被跳过:
// 停止指令绕过防抖,立即发送
if (!isStopCommand && now - this.lastBroadcastTime < 100) {
return false // 广播频率过高,跳过
}视频模式特殊处理
视频模式有额外的去重和噪音过滤规则,针对视频内容的伪随机特性进行优化:
private shouldBroadcastVideo(dataPoint: BroadcastDataPoint): boolean {
// 停止指令:50ms 内去重(比普通模式更短)
if (dataPoint.intensity === 0) {
if (lastIntensity === 0 && timeDiff < 50) return false
return true
}
// 首次广播无条件通过
if (!this.lastBroadcastData) return true
// 基本频率控制:20ms 最小间隔
if (timeDiff < 20) return false
// 微小变化过滤:强度差 < 0.5 且间隔 < 40ms
if (intensityDiff < 0.5 && timeDiff < 40) return false
// 伪随机噪音过滤:强度差 < 1.0 且间隔 < 60ms
if (intensityDiff < 1.0 && timeDiff < 60) return false
return true
}视频模式 vs 普通模式对比
| 过滤规则 | 普通模式 | 视频模式 |
|---|---|---|
| 最小广播间隔 | 100ms (BluetoothService 防抖) | 20ms |
| 档位变化阈值 | >= 1.0 | 渐进式过滤 |
| 停止指令去重窗口 | 500ms | 50ms |
| 微小变化过滤 | 无 | < 0.5 强度差 + < 40ms 间隔 |
| 伪随机噪音过滤 | 无 | < 1.0 强度差 + < 60ms 间隔 |
| 使用指令类型 | 长指令 (getClassicModes) | 长指令 (getClassicModes) |
自动停止行为
广播自动停止(App 侧)
每次广播在 800ms 后自动停止,释放 BLE 广播资源:
发送广播
│
▼
启动 800ms 定时器
│
├── 800ms 内发送新指令 → 取消旧定时器 → 发送新广播 → 重新启动 800ms 定时器
│
└── 800ms 到期 → 自动调用 stopBroadcasting() → 释放广播资源设备自动停止(设备侧)
- 长指令 (Classic Modes): 设备持续执行,必须收到停止指令才停止
- 短指令 (Short Broadcast Modes): 设备执行约 2 秒后自动停止,无需发送停止指令
BroadcastStrategyManager 内部维护设备状态同步,设置 2 秒定时器模拟设备自动停止:
// 设置 2 秒后的自动停止回调
this.deviceAutoStopTimer = setTimeout(() => {
this.isDeviceActive = false
this.handleDeviceAutoStop() // 同步 UI 状态
}, 2000)MonstroRush 广播设备示例
MonstroRush(型号 MIT-010,connectionType: 1)是一个典型的广播设备,支持组合模式、抽插模式和吮吸模式。
设备特点
connectionType: 1— 广播连接,无需蓝牙搜索配对isPrivate: 0— 使用标准 Hex 广播命令- 所有命令均为 22 位 Hex 字符串
- 支持多功能(组合、抽插、吮吸)独立控制
完整控制示例
import { bluetoothService } from '@/utils/bluetooth/BluetoothService'
import { useDeviceStore } from '@/stores/device-store'
class MonstroRushController {
private readonly STOP_COMMAND = "6db643ce97fe427ce5157d"
private readonly COMBINATION_COMMANDS = [
"6db643ce97fe427cf5946d", // 基础档
"6db643ce97fe427cf41d7c", // 档位 1
"6db643ce97fe427cf7864e", // 档位 2
"6db643ce97fe427cf60f5f", // 档位 3
"6db643ce97fe427cf1b02b", // 档位 4
"6db643ce97fe427cf0393a", // 档位 5
"6db643ce97fe427cf3a208", // 档位 6
"6db643ce97fe427cf22b19", // 档位 7
"6db643ce97fe427cfddce1", // 档位 8
"6db643ce97fe427cfc55f0" // 档位 9
]
private readonly THRUST_COMMANDS = [
"6db643ce97fe427cd5964c", // 基础档
"6db643ce97fe427cd41f5d", // 模式 1
"6db643ce97fe427cd7846f", // 模式 2
"6db643ce97fe427cd60d7e", // 模式 3
"6db643ce97fe427cd1b20a", // 模式 4
"6db643ce97fe427cd03b1b", // 模式 5
"6db643ce97fe427cd3a029", // 模式 6
"6db643ce97fe427cd22938", // 模式 7
"6db643ce97fe427cdddec0", // 模式 8
"6db643ce97fe427cdc57d1" // 模式 9
]
private readonly SUCTION_COMMANDS = [
"6db643ce97fe427ca5113f", // 基础档
"6db643ce97fe427ca4982e", // 模式 1
"6db643ce97fe427ca7031c", // 模式 2
"6db643ce97fe427ca68a0d", // 模式 3
"6db643ce97fe427ca13579", // 模式 4
"6db643ce97fe427ca0bc68", // 模式 5
"6db643ce97fe427ca3275a", // 模式 6
"6db643ce97fe427ca2ae4b", // 模式 7
"6db643ce97fe427cad59b3", // 模式 8
"6db643ce97fe427cacd0a2" // 模式 9
]
/** 设置组合模式档位 (0-9) */
async setCombinationLevel(level: number): Promise<boolean> {
return await bluetoothService.broadcastCommand(
this.COMBINATION_COMMANDS[level]
)
}
/** 设置抽插模式 (0-9) */
async setThrustMode(mode: number): Promise<boolean> {
return await bluetoothService.broadcastCommand(
this.THRUST_COMMANDS[mode]
)
}
/** 设置吮吸模式 (0-9) */
async setSuctionMode(mode: number): Promise<boolean> {
return await bluetoothService.broadcastCommand(
this.SUCTION_COMMANDS[mode]
)
}
/** 停止所有功能 */
async stopAll(): Promise<boolean> {
return await bluetoothService.broadcastCommand(this.STOP_COMMAND)
}
}
// 使用示例
const controller = new MonstroRushController()
await controller.setCombinationLevel(3) // 组合模式档位 3
await controller.setThrustMode(5) // 抽插模式 5
await controller.setSuctionMode(1) // 吮吸模式 1
await controller.stopAll() // 停止所有常见问题
1. TOO_MANY_ADVERTISERS 错误
症状: 广播失败,返回错误码 2。
原因: Android 系统限制同时活跃的广播实例数。未正确停止的广播实例会占用系统资源。
解决方案:
- 确保始终使用固定的
instanceId = 1 - 系统自动执行双重清理(stopAll → 等待 500ms → 再次 stopAll → 等待 200ms → 重试)
- 如果自动恢复失败,建议用户关闭并重新打开蓝牙
2. 广播指令丢失或设备无响应
症状: 发送了广播命令但设备没有反应。
可能原因及解决:
- 广播频率过高 — 100ms 防抖机制跳过了指令。降低发送频率或使用
BroadcastStrategyManager - 蓝牙未启用 — 检查
checkBluetooth()返回值 - 设备距离过远 — BLE 广播的有效范围通常为 10-30 米
- 前一个广播未停止 — 互斥锁排队延迟了新指令
3. 停止指令延迟
症状: 发送停止指令后设备响应慢。
原因: 停止指令可能被排在操作队列中等待。
系统优化: broadcastCommand() 对停止指令有特殊优先处理 — 立即取消定时器、清空队列、停止当前广播后再发送停止指令。
4. iOS 设备广播不生效
症状: Android 设备正常,iOS 设备无法控制。
原因: iOS 使用 Service UUID 编码方式,需要确保设备端同时支持 Manufacturer Data(Android)和 Service UUID 列表(iOS)两种解析方式。
检查项:
- 确认设备固件支持 iOS 编码格式
- 确认 UUID 编码算法正确
5. 长指令设备不停止
症状: 发送了短指令但设备持续运行不停止。
原因: 可能错误使用了长指令(getClassicModes())而非短指令(getShortBroadcastModes())。长指令需要显式发送停止指令。
解决: 检查使用的指令类型,或在适当时机发送停止指令 getStopCommand(modelName)。
6. 视频模式设备频繁启停
症状: 视频模式下设备频繁振动和停止,体验不佳。
原因: 视频伪随机算法产生的小幅抖动触发了频繁广播。
解决: 使用 BroadcastPresets.video 预设配置,已内置微变化过滤和噪音过滤规则。
资源管理
清理策略
在以下时机执行资源清理:
- App 进入后台
- 切换设备
- 遇到广播错误
- 用户手动断开
// 完整清理流程
await bluetoothService.stopAllAdvertising()
// BroadcastStrategyManager 清理
manager.clear()实例 ID 管理
始终使用固定 instanceId = 1,避免创建过多实例导致系统资源耗尽。BluetoothService 内部通过 instanceIdCounter = 1 确保每次广播使用相同的实例 ID。