广播连接方式
BLE Advertising 广播连接的初始化与数据发送
BLE Advertising 原理
广播设备通过监听 BLE Advertisement 数据包接收指令,无需建立 GATT 连接。App 充当广播发送方(Advertiser),将控制指令编码到广播数据包的 Manufacturer Specific Data 字段中,设备持续扫描并解析广播数据。
这种通信方式的核心优势在于:
- 无需搜索和配对设备
- 连接速度极快(初始化即可用)
- 资源占用低
- 多个设备可同时接收同一广播
初始化流程
检查 Capacitor 原生环境
│
▼
动态导入 BleAdvertiser 插件
│
▼
调用 bleAdvertiser.initialize()
│
▼
检查蓝牙是否启用 (isBluetoothEnabled)
│
▼
保存设备信息到 Store
│
▼
准备就绪,可发送广播指令BluetoothService 单例
BluetoothService 是广播通信的核心类,采用单例模式确保全局唯一实例:
import { BluetoothService } from '@/utils/bluetooth/BluetoothService'
// 获取单例实例
const bluetoothService = BluetoothService.getInstance()关键属性
| 属性 | 类型 | 说明 |
|---|---|---|
isInitialized | boolean | 服务是否已初始化 |
advertiserInitialized | boolean | 广播器是否已初始化 |
activeAdvertisingId | number | null | 当前活跃的广播实例 ID |
isBroadcasting | boolean | 互斥锁标志,防止并发广播 |
instanceIdCounter | number | 固定实例 ID(值为 1) |
lastBroadcastTime | number | 上次广播时间戳,用于防抖 |
核心方法
initialize()
初始化蓝牙广播服务。在原生环境中加载 capacitor-ble-advertiser 插件并初始化广播服务。
const success = await bluetoothService.initialize()
if (!success) {
console.error('广播服务初始化失败')
return
}初始化流程:
- 检查是否在 Capacitor 原生环境中(
Capacitor.isNativePlatform()) - 动态导入
capacitor-ble-advertiser插件 - 调用
bleAdvertiser.initialize()初始化广播服务 - 记录初始化状态
checkBluetooth()
检查蓝牙是否已启用。如果服务未初始化,会自动尝试初始化。
const enabled = await bluetoothService.checkBluetooth()
if (!enabled) {
console.error('蓝牙未启用,请开启蓝牙')
return
}broadcastCommand(command)
发送广播指令的核心方法,内置互斥锁和防抖机制。
// 发送模式 1 指令
const result = await bluetoothService.broadcastCommand('6db643ce97fe427ce49c6c')该方法内部执行流程:
接收 Hex 命令字符串
│
▼
检查是否为停止指令(优先处理)
│
├── 是停止指令 → 取消定时器 → 清空队列 → 立即停止当前广播
│
└── 非停止指令 → 继续
│
▼
互斥锁检查
│
├── 正在广播 → 加入操作队列等待
│
└── 未广播 → 获取锁,执行 _broadcastCommand()
│
▼
防抖检查(100ms 内重复指令被跳过,停止指令除外)
│
▼
检查蓝牙状态
│
▼
停止之前的活跃广播
│
▼
调用 broadcastAdvertisement() 发送广播
│
▼
设置 800ms 自动停止定时器
│
▼
释放互斥锁,处理队列中下一个操作broadcastAdvertisement(command)
底层广播发送方法。将 Hex 字符串转为字节数组,通过 BLE Advertiser 发送。
// 内部实现关键参数
await bleAdvertiser.startAdvertising({
manufacturerId: 255, // 固定制造商 ID
data: Array.from(bytes), // 命令字节数组
instanceId: 1 // 固定实例 ID
})停止广播
// 停止当前广播
await bluetoothService.stopBroadcasting()
// 停止所有广播实例(强制清理)
await bluetoothService.stopAllAdvertising()状态查询
// 是否有活跃的广播
bluetoothService.isBroadcastActive()
// 广播服务是否已初始化
bluetoothService.isBroadcastInitialized()关键参数
| 参数 | 值 | 说明 |
|---|---|---|
| Manufacturer ID | 255 (0x00FF) | Android 平台广播载荷的制造商标识 |
| 广播时长 | 800ms | 每次广播自动停止的时间 |
| 防抖间隔 | 100ms | 非停止指令的最小广播间隔 |
| 停止指令等待 | 50ms | 停止指令的优化等待时间 |
| 普通指令等待 | 100ms | 普通指令广播前的等待时间 |
| 实例 ID | 固定值 1 | 避免创建过多广播实例 |
Hex 字符串转字节数组
所有广播指令以 Hex 字符串形式存储,发送前需要转换为字节数组:
public static hexStringToBytes(hexString: string): Uint8Array {
// 移除空格并转为小写
const cleanHex = hexString.replace(/\s/g, '').toLowerCase()
// 确保长度为偶数
const normalizedHex = cleanHex.length % 2 === 0 ? cleanHex : `0${cleanHex}`
// 创建字节数组
const bytes = new Uint8Array(normalizedHex.length / 2)
for (let i = 0; i < normalizedHex.length; i += 2) {
bytes[i / 2] = Number.parseInt(normalizedHex.substring(i, i + 2), 16)
}
return bytes
}实例管理
系统始终使用固定的 instanceId = 1 来避免创建过多广播实例。当遇到 TOO_MANY_ADVERTISERS 错误(错误码 2)时,执行双重清理策略:
1. 调用 stopAllAdvertising() 停止所有广播 (Phase 1)
2. 等待 500ms 让系统释放广播器资源
3. 再次调用 stopAllAdvertising() 确保彻底清理 (Phase 2)
4. 等待 200ms
5. 使用相同 instanceId 重试广播
6. 如果仍然失败,建议用户重启蓝牙互斥锁机制
同一时刻只允许一个广播操作。通过 isBroadcasting 标志实现互斥:
// 如果正在广播,新指令进入队列等待
if (this.isBroadcasting) {
return new Promise(resolve => {
this.operationQueue.push(async () => {
const result = await this._broadcastCommand(command)
resolve(result)
})
})
}当前广播完成后(在 finally 块中),自动从队列取出下一条指令执行:
finally {
this.isBroadcasting = false
if (this.operationQueue.length > 0) {
const nextOperation = this.operationQueue.shift()
nextOperation?.()
}
}停止指令优先处理
停止指令具有最高优先级,即使当前正在广播也会立即处理:
- 取消当前自动停止定时器
- 清空操作队列(丢弃所有待处理的指令)
- 立即停止当前广播
- 等待 100ms 确保广播完全停止
- 重置互斥锁状态
- 发送停止指令
完整连接示例
import { bluetoothService } from '@/utils/bluetooth/BluetoothService'
import { useDeviceStore } from '@/stores/device-store'
/**
* 广播设备连接
*/
async function connectBroadcast(device: Device): Promise<boolean> {
try {
// 1. 初始化广播服务
const initSuccess = await bluetoothService.initialize()
if (!initSuccess) {
console.error('广播服务初始化失败')
return false
}
// 2. 检查蓝牙是否启用
const bluetoothEnabled = await bluetoothService.checkBluetooth()
if (!bluetoothEnabled) {
console.error('蓝牙未启用')
return false
}
// 3. 保存设备状态到全局 Store
const { connectDevice } = useDeviceStore.getState()
connectDevice(device, 'home')
return true
} catch (error) {
console.error('广播连接失败:', error)
return false
}
}
/**
* 发送广播命令
*/
async function sendBroadcastCommand(command: string): Promise<boolean> {
return await bluetoothService.broadcastCommand(command)
}
// 使用示例
await connectBroadcast(myDevice)
await sendBroadcastCommand('6db643ce97fe427ce49c6c') // 模式 1
await sendBroadcastCommand('6db643ce97fe427ce5157d') // 停止