218 lines
6.4 KiB
JavaScript
218 lines
6.4 KiB
JavaScript
import {createBleCore} from './bleCore'
|
||
import {createAuthModule} from '../modules/auth'
|
||
import {createWifiModule} from '../modules/wifi'
|
||
import {createRadarModule} from '../modules/radar'
|
||
import {parseStateCode, parseDeviceInfo} from '../parsers'
|
||
import {DEVICE_PROFILE, guessProfileByRadarFrame} from '../protocols/profiles'
|
||
import {uuidEquals} from '../utils/uuid'
|
||
|
||
/**
|
||
* 创建特征值通知分发器。
|
||
* 根据特征UUID将通知分发到对应协议事件:state / msg / radar / raw。
|
||
* 雷达帧还能自动推断设备 profile(ED713/ED719)。
|
||
*/
|
||
function createCharacteristicDispatcher(ble) {
|
||
return (event) => {
|
||
const state = ble.getState()
|
||
const profile = ble.getProfile()
|
||
const current = state.characteristics
|
||
|
||
if (current.state && uuidEquals(event.characteristicId, current.state.uuid)) {
|
||
const stateCode = parseStateCode(event.value)
|
||
ble.setAuthState({
|
||
lastStateCode: stateCode,
|
||
keyMatched: stateCode === 7 || stateCode === 5 || state.auth.keyMatched
|
||
})
|
||
|
||
ble.emit('protocol:state', {
|
||
...event,
|
||
profileId: profile.id,
|
||
stateCode
|
||
})
|
||
return
|
||
}
|
||
|
||
if (current.msg && uuidEquals(event.characteristicId, current.msg.uuid)) {
|
||
ble.emit('protocol:msg', {
|
||
...event,
|
||
profileId: profile.id,
|
||
deviceInfo: parseDeviceInfo(event.value)
|
||
})
|
||
return
|
||
}
|
||
|
||
if (current.radar && uuidEquals(event.characteristicId, current.radar.uuid)) {
|
||
const guessed = guessProfileByRadarFrame(event.value)
|
||
if (state.profileId === DEVICE_PROFILE.UNKNOWN && guessed !== DEVICE_PROFILE.UNKNOWN) {
|
||
ble.setProfile(guessed)
|
||
}
|
||
|
||
ble.emit('protocol:radar', {
|
||
...event,
|
||
profileId: ble.getProfile().id
|
||
})
|
||
return
|
||
}
|
||
|
||
ble.emit('protocol:raw', {
|
||
...event,
|
||
profileId: profile.id
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 统一BLE控制器:整合核心能力 + 认证/WiFi/雷达三大业务模块。
|
||
* 页面层应通过此控制器编排完整业务流程。
|
||
* @param {object} options 透传给 createBleCore 的配置(重试策略/日志/profileId等)
|
||
*/
|
||
export function createUnifiedBleController(options = {}) {
|
||
const ble = createBleCore(options)
|
||
const auth = createAuthModule(ble)
|
||
const wifi = createWifiModule(ble)
|
||
const radar = createRadarModule(ble)
|
||
|
||
const characteristicDispatcher = createCharacteristicDispatcher(ble)
|
||
let offCharacteristic = null
|
||
|
||
/** 绑定特征值分发器到 ble 事件总线。 */
|
||
function bindDispatcher() {
|
||
if (offCharacteristic) {
|
||
return
|
||
}
|
||
|
||
offCharacteristic = ble.on('characteristic:value', characteristicDispatcher)
|
||
}
|
||
|
||
/** 解绑特征值分发器。 */
|
||
function unbindDispatcher() {
|
||
if (!offCharacteristic) {
|
||
return
|
||
}
|
||
|
||
offCharacteristic()
|
||
offCharacteristic = null
|
||
}
|
||
|
||
/** 初始化蓝牙适配器并注册所有监听(适配器状态/连接/特征值/分发器)。 */
|
||
async function init() {
|
||
await ble.openAdapter()
|
||
ble.watchAdapterState()
|
||
ble.watchConnectionChange()
|
||
ble.watchCharacteristicValue()
|
||
bindDispatcher()
|
||
}
|
||
|
||
/**
|
||
* 扫描并选取目标设备。
|
||
* 先按服务UUID过滤扫描,无结果时可退化为无过滤扫描。
|
||
* @param {object} params
|
||
* @param {string} params.serviceUuid 目标服务UUID,默认取 profile 配置
|
||
* @param {number} params.scanDuration 扫描时长(ms),默认5000
|
||
* @param {boolean} params.fallbackWithoutFilter 首次无结果时是否退化为无过滤扫描,默认true
|
||
* @returns {object|null} 匹配到的设备对象,未找到返回 null
|
||
*/
|
||
async function scanAndPickDevice({
|
||
serviceUuid,
|
||
scanDuration = 5000,
|
||
fallbackWithoutFilter = true
|
||
} = {}) {
|
||
const profile = ble.getProfile()
|
||
const targetService = serviceUuid || profile.uuids.service
|
||
|
||
ble.watchDeviceFound()
|
||
|
||
await ble.startDiscovery({services: [targetService]})
|
||
await new Promise((resolve) => setTimeout(resolve, scanDuration))
|
||
await ble.stopDiscovery()
|
||
|
||
let selected = ble.pickDeviceByService(targetService)
|
||
|
||
if (!selected && fallbackWithoutFilter) {
|
||
await ble.startDiscovery({services: []})
|
||
await new Promise((resolve) => setTimeout(resolve, scanDuration))
|
||
await ble.stopDiscovery()
|
||
selected = ble.pickDeviceByService(targetService) || ble.listDiscoveredDevices()[0] || null
|
||
}
|
||
|
||
return selected
|
||
}
|
||
|
||
/**
|
||
* 连接后严格执行协议前置:
|
||
* - 发现服务和特征
|
||
* - 订阅 UUID_STATE / UUID_RADAR
|
||
*/
|
||
async function connectAndDiscover(deviceId) {
|
||
await ble.connect(deviceId)
|
||
await ble.discoverServices(deviceId)
|
||
const chars = await ble.discoverCharacteristics()
|
||
|
||
if (chars.state && chars.state.uuid) {
|
||
try {
|
||
await ble.notifyCharacteristic(chars.state.uuid, true);
|
||
console.debug('[BLE][SUB]', 'notify STATE ok', chars.state.uuid)
|
||
} catch (e) {
|
||
console.debug('[BLE][SUB]', 'notify STATE fail', e?.message || e)
|
||
}
|
||
}
|
||
|
||
if (chars.radar && chars.radar.uuid) {
|
||
try {
|
||
await ble.notifyCharacteristic(chars.radar.uuid, true);
|
||
console.debug('[BLE][SUB]', 'notify RADAR ok', chars.radar.uuid)
|
||
} catch (e) {
|
||
console.debug('[BLE][SUB]', 'notify RADAR fail', e?.message || e)
|
||
}
|
||
}
|
||
|
||
return chars
|
||
}
|
||
|
||
/**
|
||
* 标准流程:连接 -> 订阅 -> 鉴权。
|
||
* 鉴权内部会按 bindStatus 分支处理绑定/匹配。
|
||
*/
|
||
async function standardConnectFlow({deviceId, key}) {
|
||
await init()
|
||
|
||
let targetDeviceId = deviceId
|
||
if (!targetDeviceId) {
|
||
const device = await scanAndPickDevice()
|
||
if (!device) {
|
||
throw new Error('未找到可连接设备')
|
||
}
|
||
|
||
targetDeviceId = device.deviceId
|
||
}
|
||
|
||
await connectAndDiscover(targetDeviceId)
|
||
const authResult = await auth.ensureAuthorized(key)
|
||
|
||
return {
|
||
deviceId: targetDeviceId,
|
||
authResult,
|
||
state: ble.getState(),
|
||
profile: ble.getProfile()
|
||
}
|
||
}
|
||
|
||
/** 释放所有资源:解绑分发器 + 清理核心BLE(停扫描/断连接/移监听/关适配器)。 */
|
||
async function cleanup() {
|
||
unbindDispatcher()
|
||
await ble.cleanup()
|
||
}
|
||
|
||
return {
|
||
ble,
|
||
auth,
|
||
wifi,
|
||
radar,
|
||
init,
|
||
scanAndPickDevice,
|
||
connectAndDiscover,
|
||
standardConnectFlow,
|
||
cleanup
|
||
}
|
||
}
|