SEC_Warehouse/docs/unified-ble-flow.md

199 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 统一 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`
2. 搜索阶段
- 优先过滤扫描:`startBluetoothDevicesDiscovery({ services:[0x00F4] })`
- 超时无结果则无过滤兜底扫描
- 选择目标设备 `deviceId`
3. 连接与发现阶段
- `createBLEConnection(deviceId)`
- `getBLEDeviceServices`,定位服务 `0x00F4`
- `getBLEDeviceCharacteristics`,识别 `F401/F402/F403/F501/F301/F302`
- 订阅通知:`notify(F301)=true`、`notify(F302)=true`
4. 标准业务阶段
- 读取设备信息:`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`
5. 收尾阶段
- 停止扫描 / 断开连接 / 关闭适配器
- 清理监听器与状态
## 2. 目录结构
```text
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. 核心流程代码示例
```js
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-10 恢复过滤扫描优先 + 搜索排障日志
#### src/ble/discovery.js
- 回退 2026-09-09 的"放弃过滤扫描改直接全扫"方案:真机验证该机型下无过滤扫描反而搜不出设备(修改前过滤扫描正常),存在机型兼容差异;恢复 `DISCOVERY_FILTER_SERVICE_UUIDS` 过滤扫描优先 + `fallbackToUnfilteredDiscoveryIfNeeded` 4s 兜底切全扫的原始策略,兜底切换日志(`[BLE][SCAN] filtered scan found nothing, fallback to unfiltered`)可在 vConsole 确认切换是否发生
- `upsertDevice` 返回布尔(是否新命中),事件通道(`device:found`)与轮询通道(`getBluetoothDevices`)均输出排障日志:`[BLE][SCAN] native devices=N newHits=M`(原生采集总量 vs JS 匹配命中量)、`[BLE][FOUND] event devices=N newHits=M`。真机排障按三级定位:
- `native devices=0` → 原生层没采到任何设备(权限/适配器/机型问题)
- `native devices>0, newHits=0` → 采到了但 JS 匹配规则全没命中(设备广播内容与关键词不符)
- 无任何 `[BLE][SCAN]` 日志 → 扫描流程未走到(初始化/会话状态问题)
- 启动扫描处输出 `[BLE][SCAN] filtered discovery started` / 过滤启动失败转全扫日志,均带 sessionId
#### src/constants/bluetooth.js
- 恢复 `DISCOVERY_FILTER_SERVICE_UUIDS` 导出(三个 128 位完整 UUID),供过滤扫描使用
### 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` 补清节流定时器
- 放弃 services 过滤扫描,`startSearch` 直接无过滤全频段扫描:微信 `startBluetoothDevicesDiscovery` 的 services 参数只按 128 位完整 UUID 匹配,目标设备广播的是 16 位短 UUID(0x00F4 等),部分机型/基础库版本下过滤扫描收不到这些设备的广播(表现为一直搜不出,或等 4s 兜底切全扫后才出现)。事件通道(`onBluetoothDeviceFound`)+ 轮询通道(`getBluetoothDevices`)双路取数,过滤交给 JS 层 `matchTargetDevice`(服务 UUID/名称关键词/广播包关键词三路匹配)。删除 `fallbackToUnfilteredDiscoveryIfNeeded`/`FILTER_SCAN_TIMEOUT_MS`/`DISCOVERY_FILTER_SERVICE_UUIDS`
#### 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 条目覆盖)