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 } }