蓝牙设备对接文档

广播连接方式

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()

关键属性

属性类型说明
isInitializedboolean服务是否已初始化
advertiserInitializedboolean广播器是否已初始化
activeAdvertisingIdnumber | null当前活跃的广播实例 ID
isBroadcastingboolean互斥锁标志,防止并发广播
instanceIdCounternumber固定实例 ID(值为 1)
lastBroadcastTimenumber上次广播时间戳,用于防抖

核心方法

initialize()

初始化蓝牙广播服务。在原生环境中加载 capacitor-ble-advertiser 插件并初始化广播服务。

const success = await bluetoothService.initialize()
if (!success) {
  console.error('广播服务初始化失败')
  return
}

初始化流程:

  1. 检查是否在 Capacitor 原生环境中(Capacitor.isNativePlatform()
  2. 动态导入 capacitor-ble-advertiser 插件
  3. 调用 bleAdvertiser.initialize() 初始化广播服务
  4. 记录初始化状态

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 ID255 (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?.()
  }
}

停止指令优先处理

停止指令具有最高优先级,即使当前正在广播也会立即处理:

  1. 取消当前自动停止定时器
  2. 清空操作队列(丢弃所有待处理的指令)
  3. 立即停止当前广播
  4. 等待 100ms 确保广播完全停止
  5. 重置互斥锁状态
  6. 发送停止指令

完整连接示例

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')  // 停止