SEC_Warehouse/docs/unified-ble-flow.md

9.5 KiB
Raw Blame History

统一 BLE 连接与控制流程(ED713 / ED719)

平台注入层(2026-09 迁移新增)

BLE 运行时调用统一收敛:createBleCore({ platform }) 接受平台对象(默认 Taro,见 src/ble/adapter/index.ts),单元测试可注入 mock;createBluetoothDiscovery(src/ble/discovery.js)原样保留全部能力(扫描过滤/去重/重启/Android 定位权限/连接/鉴权/WiFi/雷达),仅 3 处 uni.* 换为 Taro.*。React 页面通过 useBle()(src/ble/hooks/useBle.ts)消费——工厂回调转 state,方法原样透出。

1. 标准连接流程图(文字版)

  1. 初始化阶段
  • openBluetoothAdapter
  • 注册监听:onBluetoothAdapterStateChange、onBLEConnectionStateChange、onBLECharacteristicValueChange
  1. 搜索阶段
  • 优先过滤扫描:startBluetoothDevicesDiscovery({ services:[0x00F4] })
  • 超时无结果则无过滤兜底扫描
  • 选择目标设备 deviceId
  1. 连接与发现阶段
  • createBLEConnection(deviceId)
  • getBLEDeviceServices,定位服务 0x00F4
  • getBLEDeviceCharacteristics,识别 F401/F402/F403/F501/F301/F302
  • 订阅通知:notify(F301)=true、notify(F302)=true
  1. 标准业务阶段
  • 读取设备信息:read(F501)
  • 鉴权:write(F402, A7分包key),监听 F301 状态码
  • 当前项目约定:已知密钥 = 设备 MAC 去分隔符后 + 0000,例如 10:52:1C:81:64:F4 → 10521C8164F40000
  • 鉴权成功后进行功能操作:
    • WiFi 配网:write(F401, A7分包ssid|pass)
    • 控制命令:write(F403, [cmd])
    • 雷达数据:cmd=0xA1 开始、cmd=0xA2 停止,解析 F302
  1. 收尾阶段
  • 停止扫描 / 断开连接 / 关闭适配器
  • 清理监听器与状态

2. 目录结构

src/ble/
  core/
    bleCore.js          # 蓝牙核心能力(连接/发现/读写/订阅/重连)
    controller.js       # 标准流程编排(scan->connect->discover->auth)
    errors.js           # 统一异常
    eventBus.js         # 回调分发
    packet.js           # A7 分包工具
    retry.js            # 重试工具
    state.js            # 状态模型
  modules/
    auth.js             # 鉴权流程(读MSG + 写KEY)
    wifi.js             # WiFi配置流程
    radar.js            # 雷达控制流程
  parsers/
    commonParser.js     # MSG/STATE/雷达通用解析
    index.js            # 解析聚合出口
  protocols/
    profiles.js         # ED713/ED719/UNKNOWN profile 差异配置
  utils/
    bytes.js            # 字节/高低位工具
    hex.js              # hex/utf8 转换
    uuid.js             # UUID 标准化与比较
  index.js              # 统一出口

3. 公共方法列表

  • 蓝牙核心
    • createBleCore(options)
    • openAdapter/getAdapterState/closeAdapter
    • startDiscovery/stopDiscovery
    • connect/disconnect
    • discoverServices/discoverCharacteristics
    • readCharacteristic/writeCharacteristic/notifyCharacteristic
  • 流程编排
    • createUnifiedBleController(options)
    • standardConnectFlow({ deviceId, key })
    • scanAndPickDevice/connectAndDiscover/cleanup
  • 业务模块
    • createAuthModule(ble).ensureAuthorized(key)
    • createAuthModule(ble).ensureAuthorizedByMac({ suffix: '0000' })
    • createWifiModule(ble).configureWifi(ssid, password)
    • createRadarModule(ble).startRadarStream()/stopRadarStream()/writeFallParams()
  • 工具
    • buildA7Packets(payload, packetConfig)
    • bytesToHex/hexToBytes/utf8ToBytes/bytesToUtf8
    • splitToLowHigh/joinLowHigh/readUint16LE/readUint32LE
    • normalizeUuid/toFullUuid/uuidEquals

4. 核心流程代码示例

import {
  createUnifiedBleController,
  DEVICE_PROFILE
} from '@/ble'

const bleController = createUnifiedBleController({
  profileId: DEVICE_PROFILE.UNKNOWN,
  reconnect: { enabled: true, retries: 2, delay: 1000 }
})

async function runStandardFlow({ key, ssid, password }) {
  try {
    // 1) 连接 + 服务发现 + 自动订阅F301/F302 + 鉴权
    const session = await bleController.standardConnectFlow({ key })

    // 2) 读取设备信息(如需要可再次读取)
    const info = await bleController.auth.readDeviceInfo()

    // 3) WiFi 配置
    const wifiResult = await bleController.wifi.configureWifi(ssid, password)

    // 4) 雷达控制
    await bleController.radar.startRadarStream()

    return {
      session,
      info,
      wifiResult
    }
  } finally {
    // 页面退出或流程结束时清理
    await bleController.cleanup()
  }
}

5. ED713/ED719 兼容策略

  • 相同 UUID 与鉴权主流程统一抽象。
  • 差异通过 profiles.js 配置:
    • radar.notifySignature: ED713=0x7c,ED719=0x67
    • cmd.supportsNarrowMode: ED713=true
    • cmd.supportsFallParam67: ED719=true
  • 未知设备先用 UNKNOWN,收到 F302 数据后按签名字节自动识别 profile。

6. 修改记录

2026-09-09 Taro 迁移后交互卡顿修复

src/ble/discovery.js

  • stopSearch: setSearching(false) 提前到 stopBluetoothDevicesDiscovery 之前同步执行——部分机型原生停扫回调慢,等回调才翻转状态会让按钮长时间停在"搜索中"并串行阻塞连接等后续操作;searchSessionId 已递增、acceptingDeviceEvents 已关闭,先翻转状态安全
  • connectDevice: 由无条件 stopSearch(false) 改为仅在 isSearching 时停搜索,避免每次连接都白调一次原生停扫接口
  • 设备列表 UI 刷新加 300ms 节流(emitDeviceListThrottled):allowDuplicatesKey=true 下目标设备广播包每秒到达数次且 RSSI 逐包变化,签名判重只拦"完全相同"的包;不节流时每包都触发 onDeviceListChange → React setState → 全列表重渲染,搜到设备后渲染线程被打满、点击排队卡死(表现为"搜到设备就无法停搜索")。数据仍实时合入 deviceMap,仅通知 UI 频率受限;resetDeviceMap/stopSearch 绕过节流立即冲刷,cleanupDiscovery 补清节流定时器

src/pages/device/ble-config.tsx

  • 补 useDidHide 停搜索:迁移版原只有 useEffect 卸载清理,navigateTo 进配网页/切后台时组件不卸载,扫描持续空转(对齐旧版 onHide/onUnload 双触发)
  • prepareWifiConfig 包 60s Promise.race 超时兜底:createBLEConnection 部分机型失败后不回调、discoverServices/notify 无原生超时,不设兜底会导致 loading 永不消失、按钮永久禁用
  • 连接失败分支补 disconnectDevice() 清 OS 层残留连接(对齐旧版 safeDisconnect),避免"已连接(10010)"卡死后续重试;成功分支补回"设备已连接,请填写WiFi信息"提示

2026-04-30 对照协议文档第二轮修复

commonParser.js

  • parseDeviceInfo(payload, profileId): 重写为按 ED713/ED719 分路解析
    • ED713:MAC[0:6] + bindStatus[6] + 其它[7:10] + productId[10:12] + baseBoard[12] + coreBoard[13] + 其它[14:16] + deviceType[16] + wifiStatus[17] + cellularStatus[18]
    • ED719:布局与 ED713 一致(bindStatus/productId 偏移相同,文档仅省略标注),复用 parseEd713DeviceInfo
  • parseEd719Radar: 修正 shieldNum 从 bytes[27] → bytes[24](按文档结构体偏移累加 header[4]+event+people+num+nodata+height+mode+beeper+left+right+front+back+sensitive+nodata1+stateDelay=24)
  • 新增 wallOption、peopleHaveSwitch、peopleActivitySwitch 字段解析

radar.js

  • setNarrowMode(enabled): 修正命令格式,从单字节 0x7C/0x7D 改为两字节 [0x7C, flag],flag 0=关 1=开(符合协议文档 CMD=0x7C + 1byte参数)

auth.js

  • readDeviceInfo(options): 新增 msgTimeout 参数与 4 秒超时保护,防止 UUID_MSG 无回包时页面挂死
  • readDeviceInfo(): 调用 parseDeviceInfo 时传入 ble.getProfile().id
  • ensureAuthorized/ensureAuthorizedByMac: 透传 options 到 readDeviceInfo
  • writeKeyAndExpectStates: rejectOnUnexpected 从 true 改为 false,避免设备中间态(-1/0)直接 reject 阻断鉴权
  • ensureAuthorizedWithDeviceInfo: 移除 bindStatus === null 分支(ED719 实际布局与 ED713 一致,bindStatus 可正常读取)

controller.js

  • 特征分发器 createCharacteristicDispatcher 中 parseDeviceInfo 调用传入 profile.id

bleCore.js

  • closeAdapter(): 新增重置 adapterStateHandler/connectionStateHandler/characteristicValueHandler/deviceFoundHandler 为 null,确保 openAdapter 后 init() 可重新注册所有原生监听

pages/device/config.vue

  • openWifiPanel: prepareWifiConfig 超时从 800000 改为 8000,新增 msgTimeout: 4000
  • wifiPanelVisible = true 后加 await this.$nextTick() 确保 Vue DOM 更新后再调原生 toast

hooks/useBluetoothDiscovery.js

  • 新增 SCAN_DURATION_MS = 30000,蓝牙搜索 30 秒后自动停止
  • startSearch: 搜索开始前同步设置 isSearching = true(避免异步导致无法暂停);先 stopDiscoverySilently() 再 resetDeviceMap()(新搜索刷新数据);启动 30 秒自动停止定时器
  • startSearch: stopDiscoverySilently() 后执行完整适配器重置(unwatch* 注销所有原生监听 → closeAdapter 关闭适配器 → initialized = false),由 ensureInitialized 重新打开并注册全部监听,确保每次搜索都从干净适配器状态开始,修复二次搜索找不到设备的问题
  • stopSearch: 移除 resetDeviceMap() 调用,暂停搜索时保留已发现设备数据;清除自动停止定时器

2026-04-29 对照协议文档修复

(初次修复,记录同上,已被 2026-04-30 条目覆盖)