蓝牙设备对接文档

波形回放

波形数据的实时回放与设备命令映射

高频广播系统

BroadcastStrategyManager 是波形回放的核心组件,负责将波形数据点转换为蓝牙广播指令。它实现了频率控制、智能去重、数据压缩等优化策略,确保设备响应既及时又稳定。

核心工作流程

波形数据点                BroadcastStrategyManager                 蓝牙设备
    │                              │                                │
    │  addDataPoint(point)         │                                │
    │ ───────────────────────────▶ │                                │
    │                              │                                │
    │                    shouldBroadcast(point)                     │
    │                    ┌─────────┴─────────┐                     │
    │                    │ 频率控制           │                     │
    │                    │ 档位变化检测       │                     │
    │                    │ 停止指令去重       │                     │
    │                    └─────────┬─────────┘                     │
    │                              │                                │
    │                    executeBroadcastDirectly(point)            │
    │                              │                                │
    │                    generateBroadcastCommand(point)            │
    │                    ┌─────────┴─────────┐                     │
    │                    │ intensity → 档位   │                     │
    │                    │ 查找长指令         │                     │
    │                    └─────────┬─────────┘                     │
    │                              │                                │
    │                    bluetoothService.broadcastCommand()        │
    │                              │ ─────────────────────────────▶ │
    │                              │        BLE Advertisement       │

模式预设与广播间隔

系统为不同的使用场景提供了预设的广播策略配置:

划屏模式 (swipe)

{
  maxHistoryPoints: 300,
  similarityThreshold: 0.95,
  compressionRatio: 0.2,
  broadcastInterval: 150,       // 150ms 间隔,快速响应手势
  enableSmoothing: true,
  enableCompression: true,
  useSimplifiedMode: true
}

适用于用户手指滑动控制场景。高相似度阈值 (0.95) 避免微小抖动导致的重复广播,150ms 的广播间隔保证了手势的实时反馈。

音乐模式 (music)

{
  maxHistoryPoints: 500,
  similarityThreshold: 0.85,
  compressionRatio: 0.4,
  broadcastInterval: 200,       // 200ms 间隔,跟随音频节拍
  enableSmoothing: true,
  enableCompression: true,
  useSimplifiedMode: true
}

适用于音频节奏同步场景。500 个历史数据点的缓存容量可以记录较长的音乐波形数据。相似度阈值较低 (0.85),允许更多的节拍变化通过。

视频模式 (video)

{
  maxHistoryPoints: 400,
  similarityThreshold: 0.90,
  compressionRatio: 0.3,
  broadcastInterval: 200,       // 200ms 间隔,跟随视频内容
  enableSmoothing: true,
  enableCompression: true,
  useSimplifiedMode: true
}

适用于视频内容同步场景。视频模式有专用的数据入口 addDataVideoPoint() 和独立的去重逻辑 shouldBroadcastVideo(),针对视频伪随机算法产生的小幅抖动进行过滤。

声控模式 (voice)

{
  maxHistoryPoints: 300,
  similarityThreshold: 0.90,
  compressionRatio: 0.3,
  broadcastInterval: 150,       // 150ms 间隔,快速响应声音变化
  enableSmoothing: true,
  enableCompression: true,
  useSimplifiedMode: true
}

适用于环境声音控制场景。高相似度阈值 (0.90) 过滤环境噪音引起的频繁变化,150ms 间隔确保声音变化的及时响应。

强度到指令的映射

广播设备的指令生成

generateBroadcastCommand() 方法将波形强度值转换为蓝牙广播指令:

// 1. 强度为 0:发送停止指令
if (dataPoint.intensity === 0) {
  const stopCommands = getStopCommand(modelName)
  for (let cmd of stopCommands) {
    await bluetoothService.broadcastCommand(cmd)
  }
  return
}

// 2. 强度 > 0:映射到长指令档位
// intensity 值 (1-3) 映射为档位索引 (0-2)
const intensityIndex = Math.max(0, Math.min(2, Math.floor(intensity) - 1))

// 根据设备品牌获取对应的长指令集
const modes = getClassicModes(modelName)
const command = modes[intensityIndex].command

// 通过蓝牙服务发送广播
await bluetoothService.broadcastCommand(command)

点对点设备的指令生成

对于点对点连接的设备,波形数据通过 function-command-generator 模块进行指令映射:

// 1. 根据选中的功能和强度,生成指令序号数组
const commandArray = generateFunctionSequenceArray(
  selectedFunctionIntensities, // Map<功能键, 强度值>
  deviceFunctions              // 设备支持的功能列表
)
// 示例: [2, 0, 1] 表示功能1强度2,功能2关闭,功能3强度1

// 2. 将指令序号数组转换为蓝牙命令
const commands = convertArrayToBluetoothCommands(commandArray, deviceFunctions)
// 查找每个功能对应强度的实际 Hex 命令

// 3. 私有协议设备:使用 PrivateProtocol 编码
const uint8Array = PrivateProtocol.makeMotorControlFromArray(commandArray)
const hexCommand = PrivateProtocol.toHexString(uint8Array)

去重与节流机制

通用模式的广播过滤 (shouldBroadcast)

通用模式(swipe、music、voice)使用以下过滤规则判断是否发送广播:

条件行为说明
intensity === 0 且上次也是 0,间隔 < 500ms跳过停止指令去重,500ms 窗口内不重复发送
intensity === 0 且不满足上述条件发送停止指令优先通过
首次广播(无历史记录)发送无条件通过
强度变化 >= 1.0发送档位发生变化,立即广播
强度变化 < 1.0跳过相同档位不重复发送(长指令会持续执行)

关键设计:由于系统使用长指令,设备收到指令后会持续执行直到收到新指令或停止指令。因此,相同档位无需重复发送,只需在档位变化时发送新指令。

视频模式的广播过滤 (shouldBroadcastVideo)

视频模式有更精细的过滤逻辑,针对视频伪随机算法产生的噪音进行优化:

条件行为说明
intensity === 0 且上次也是 0,间隔 < 50ms跳过更短的停止指令去重窗口
intensity === 0 且不满足上述条件发送停止指令优先通过
首次广播发送无条件通过
时间间隔 < 20ms跳过最小广播间隔限制
强度差 < 0.5 且间隔 < 40ms跳过微小变化过滤
强度差 < 1.0 且间隔 < 60ms跳过伪随机噪音过滤
其他情况发送正常广播

视频模式的核心区别:

  • 更短的停止指令去重窗口:50ms(通用模式为 500ms),因为视频可能快速切换开关状态
  • 最小广播间隔:20ms,比通用模式更密集
  • 渐进式噪音过滤:分两级过滤微小变化(< 0.5)和小幅抖动(< 1.0)

数据压缩

useSimplifiedModefalse 时,系统启用压缩缓冲模式。数据点进入压缩缓冲区,当缓冲区达到 5 个点时,使用加权平均算法压缩为 1 个点:

// 加权平均压缩:越新的数据点权重越高
points.forEach((point, index) => {
  const weight = (index + 1) / points.length  // 权重递增
  totalWeight += weight
  weightedIntensity += point.intensity * weight
})

const compressed = {
  intensity: Math.round(weightedIntensity / totalWeight),
  timestamp: latestTimestamp,
  metadata: { compressed: true, originalCount: points.length }
}

默认所有预设模式均启用简化模式(useSimplifiedMode: true),直接跳过压缩缓冲区进行广播,以获得最低延迟。

自动停止机制

广播执行后,系统会启动一个 2 秒的自动停止定时器:

广播指令发出

    ├─── 设备开始执行振动
    │    isDeviceActive = true

    │    ┌─── 如果 2 秒内收到新指令 ───┐
    │    │  清除旧定时器               │
    │    │  执行新指令                 │
    │    │  重置 2 秒定时器             │
    │    └─────────────────────────────┘

    │    ┌─── 如果 2 秒内无新指令 ─────┐
    │    │  isDeviceActive = false     │
    │    │  记录虚拟停止数据点          │
    │    │  同步 UI 状态               │
    │    └─────────────────────────────┘

停止数据点会自动记录到历史数据中,但不会触发新的广播:

const stopDataPoint: BroadcastDataPoint = {
  intensity: 0,
  timestamp: Date.now(),
  metadata: {
    autoStop: true,
    reason: 'device_2s_timeout'
  }
}

历史数据与波形显示

BroadcastStrategyManager 维护了完整的历史数据,支持波形可视化显示:

方法说明
getHistoryData()获取完整的历史数据数组
getCompressedHistoryData(targetPoints)按等距采样压缩历史数据,默认 200 个数据点
getSmoothHistoryData(windowSize)对历史数据应用滑动窗口平均平滑算法,默认窗口大小 5

历史数据上限由 maxHistoryPoints 配置控制,超出后自动丢弃最旧的数据。

完整使用示例

以下示例展示如何使用 BroadcastStrategyManager 实现音乐同步波形回放:

import {
  BroadcastStrategyManager,
  BroadcastPresets,
  type BroadcastDataPoint
} from './broadcast/broadcast-strategy'

// 1. 创建策略管理器(使用音乐模式预设)
const manager = new BroadcastStrategyManager(BroadcastPresets.music)

// 2. 检查蓝牙是否就绪
if (!manager.isBluetoothReady()) {
  const success = await manager.reinitializeBluetooth()
  if (!success) {
    console.error('蓝牙初始化失败')
    return
  }
}

// 3. 接收音频分析数据,添加波形数据点
function onAudioAnalysis(amplitude: number) {
  // amplitude: 0-100 的音频振幅值
  // 映射到设备强度: 0 = 停止, 1-3 = 设备档位
  let intensity = 0
  if (amplitude > 70) intensity = 3
  else if (amplitude > 40) intensity = 2
  else if (amplitude > 10) intensity = 1

  const dataPoint: BroadcastDataPoint = {
    intensity,
    timestamp: Date.now(),
    metadata: { source: 'music' }
  }

  // 策略管理器自动处理频率控制和指令生成
  manager.addDataPoint(dataPoint)
}

// 4. 视频同步场景(使用专用方法)
function onVideoFrame(amplitude: number) {
  const dataPoint: BroadcastDataPoint = {
    intensity: Math.min(3, Math.ceil(amplitude / 33)),
    timestamp: Date.now(),
    metadata: {
      source: 'video',
      amplitude,
      videoPosition: videoPlayer.currentTime
    }
  }

  // 视频专用入口,使用独立的去重逻辑
  manager.addDataVideoPoint(dataPoint)
}

// 5. 停止播放
function stopPlayback() {
  manager.addDataPoint({
    intensity: 0,
    timestamp: Date.now(),
    metadata: { source: 'music' }
  })
}

// 6. 清理资源
function cleanup() {
  manager.clear()
}

// 7. 获取性能统计
const stats = manager.getPerformanceStats()
console.log(`数据点: ${stats.totalPoints}, 队列: ${stats.queueLength}`)

动态切换策略配置

在运行过程中,可以动态更新策略配置以适应不同场景:

// 从音乐模式切换到滑动模式
manager.updateConfig(BroadcastPresets.swipe)

// 或者微调单个参数
manager.updateConfig({
  broadcastInterval: 120  // 进一步降低延迟
})

// 查看当前配置
const config = manager.getConfig()
console.log(`当前广播间隔: ${config.broadcastInterval}ms`)