185 lines
4.9 KiB
JavaScript
185 lines
4.9 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'
|
|
|
|
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
|
|
})
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
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()
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
async function cleanup() {
|
|
unbindDispatcher()
|
|
await ble.cleanup()
|
|
}
|
|
|
|
return {
|
|
ble,
|
|
auth,
|
|
wifi,
|
|
radar,
|
|
init,
|
|
scanAndPickDevice,
|
|
connectAndDiscover,
|
|
standardConnectFlow,
|
|
cleanup
|
|
}
|
|
}
|