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, profile.id), }); 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; } /** * 连接后执行协议前置(厂家确认流程): * 1) 发现服务和特征 * 2) 订阅 UUID_STATE(F301) / UUID_RADAR(F302) * * 厂家确认:无需读 MSG、无需鉴权,订阅 F301 后直接写 F401 配网即可。 */ async function connectAndDiscover(deviceId) { await ble.connect(deviceId); await ble.discoverServices(deviceId); const chars = await ble.discoverCharacteristics(); if (chars.state && chars.state.uuid) { // 打印 STATE 特征属性,区分 notify / indicate,定位通知不投递问题 console.debug('[BLE][SUB]', 'STATE props', JSON.stringify(chars.state.properties || {})); // STATE 是鉴权/WiFi 的唯一回执通道,订阅失败必须显式抛出, // 否则后续写 KEY 后设备零回执,表现为"等待 UUID_STATE 超时"难以定位。 try { await ble.notifyCharacteristic(chars.state.uuid, true); console.debug('[BLE][SUB]', 'notify STATE ok', chars.state.uuid); } catch (e) { throw new Error(`订阅 UUID_STATE(F301) 失败:${e?.message || e}`); } } else { throw new Error('未发现 UUID_STATE(F301) 特征值,无法接收设备回执'); } 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, }; }