import { createEventBus } from './eventBus'; import { createBleState, BLE_STAGE } from './state'; import { createBleError, getBleErrorText, normalizeBleError } from './errors'; import { uint8ArrayToArrayBuffer, toUint8Array } from '../utils/bytes'; import { toFullUuid, uuidEquals, uuidInList } from '../utils/uuid'; import { runWithRetry } from './retry'; import { getBleProfile, DEVICE_PROFILE } from '../protocols/profiles'; // 把 uni 的 success/fail 回调风格统一成 Promise,便于串联 async 流程。 function promisifyUniApi(apiName, params = {}) { return new Promise((resolve, reject) => { uni[apiName]({ ...params, success: (res) => resolve(res), fail: (err) => reject(err), }); }); } /** * BLE 核心能力封装: * 负责适配器生命周期、扫描、连接、服务/特征发现、读写与通知监听。 */ export function createBleCore(options = {}) { const { reconnect = { enabled: true, retries: 2, delay: 1200, }, logger = console, profileId = DEVICE_PROFILE.UNKNOWN, } = options; const eventBus = createEventBus(); const state = createBleState(); let currentProfile = getBleProfile(profileId); let deviceFoundHandler = null; let adapterStateHandler = null; let connectionStateHandler = null; let characteristicValueHandler = null; // 返回状态快照,避免调用方直接改写内部状态对象。 function snapshotState() { return { ...state, auth: { ...state.auth }, deviceMap: { ...state.deviceMap }, characteristics: { ...state.characteristics }, }; } // 统一 patch 状态并广播 state:change,保证 UI 状态来源单一。 function patchState(partialState) { Object.assign(state, partialState); eventBus.emit('state:change', snapshotState()); } function setStage(stage) { patchState({ stage }); } function setProfile(nextProfileId) { currentProfile = getBleProfile(nextProfileId); patchState({ profileId: currentProfile.id }); } function getProfile() { return currentProfile; } function setAuthState(partialAuth = {}) { patchState({ auth: { ...state.auth, ...partialAuth, }, }); } function emit(eventName, payload) { eventBus.emit(eventName, payload); } function emitError(error) { const normalized = normalizeBleError(error); patchState({ lastError: normalized }); eventBus.emit('error', normalized); return normalized; } /** 打开蓝牙适配器,成功后标记可用并进入 ADAPTER_OPENED 阶段。 */ async function openAdapter() { try { await promisifyUniApi('openBluetoothAdapter'); patchState({ available: true }); setStage(BLE_STAGE.ADAPTER_OPENED); return true; } catch (error) { throw emitError(error); } } /** 关闭蓝牙适配器并重置全部运行时状态。 */ async function closeAdapter() { try { await promisifyUniApi('closeBluetoothAdapter'); } catch (error) { logger.warn('[BLE] close adapter failed', error); } // 重置原生 handler 引用,以便 openAdapter 后 init() 可重新注册 adapterStateHandler = null; connectionStateHandler = null; characteristicValueHandler = null; deviceFoundHandler = null; patchState({ stage: BLE_STAGE.IDLE, available: false, discovering: false, connected: false, deviceId: '', serviceId: '', characteristics: {}, }); } /** 读取当前适配器状态(可用/搜索中),同步到内部状态。 */ async function getAdapterState() { try { const result = await promisifyUniApi('getBluetoothAdapterState'); patchState({ available: Boolean(result.available), discovering: Boolean(result.discovering), }); return result; } catch (error) { throw emitError(error); } } /** 注册适配器状态变更监听,蓝牙关闭时自动切换到 DISCONNECTED。 */ function watchAdapterState() { if (adapterStateHandler) { return; } adapterStateHandler = (result) => { patchState({ available: Boolean(result.available), discovering: Boolean(result.discovering), }); eventBus.emit('adapter:state', result); if (!result.available) { setStage(BLE_STAGE.DISCONNECTED); } }; uni.onBluetoothAdapterStateChange(adapterStateHandler); } /** 移除适配器状态变更监听。 */ function unwatchAdapterState() { if (!adapterStateHandler) { return; } if (uni.offBluetoothAdapterStateChange) { uni.offBluetoothAdapterStateChange(adapterStateHandler); } adapterStateHandler = null; } /** * 启动设备扫描。 * services 传入时会做 UUID 标准化,用于只扫描目标服务设备。 */ async function startDiscovery(params = {}) { const { services = [], allowDuplicatesKey = true, interval = 0, powerLevel = 'high', } = params; try { if (!state.available) { await openAdapter(); } const options = { allowDuplicatesKey, interval, }; if (services.length) { options.services = services.map((uuid) => toFullUuid(uuid) || uuid); } if (powerLevel) { options.powerLevel = powerLevel; } await promisifyUniApi('startBluetoothDevicesDiscovery', options); patchState({ discovering: true, deviceMap: {} }); setStage(BLE_STAGE.DISCOVERING); return true; } catch (error) { throw emitError(error); } } /** 停止设备扫描。 */ async function stopDiscovery() { try { await promisifyUniApi('stopBluetoothDevicesDiscovery'); } catch (error) { logger.warn('[BLE] stop discovery failed', error); } patchState({ discovering: false }); } /** 注册设备发现监听,按 deviceId 去重合并后广播 device:found。 */ function watchDeviceFound() { if (deviceFoundHandler) { return; } deviceFoundHandler = (result) => { const list = Array.isArray(result.devices) ? result.devices : [result]; const nextMap = { ...state.deviceMap }; // 基于 deviceId 做去重与增量更新,避免列表无限叠加。 list.forEach((device) => { if (!device || !device.deviceId) { return; } const previous = nextMap[device.deviceId] || {}; nextMap[device.deviceId] = { ...previous, ...device, showName: device.name || device.localName || previous.showName || '未命名设备', }; }); patchState({ deviceMap: nextMap }); eventBus.emit('device:found', Object.values(nextMap)); }; uni.onBluetoothDeviceFound(deviceFoundHandler); } /** 移除设备发现监听。 */ function unwatchDeviceFound() { if (!deviceFoundHandler) { return; } if (uni.offBluetoothDeviceFound) { uni.offBluetoothDeviceFound(deviceFoundHandler); } deviceFoundHandler = null; } /** * 建立BLE连接。 * @param {string} deviceId 设备ID * @param {object} options * @param {number} options.timeout 连接超时(ms),默认15000 * @param {boolean} options.autoReconnect 是否自动重试,默认跟随初始化配置 */ async function connect(deviceId, options = {}) { if (!deviceId) { throw emitError(createBleError(10013, 'deviceId 不能为空')); } const { timeout = 15000, autoReconnect = reconnect.enabled } = options; const runConnect = async () => { setStage(BLE_STAGE.CONNECTING); await promisifyUniApi('createBLEConnection', { deviceId, timeout }); patchState({ connected: true, deviceId }); setStage(BLE_STAGE.CONNECTED); return true; }; try { if (!autoReconnect) { return await runConnect(); } return await runWithRetry(runConnect, { retries: reconnect.retries, delay: reconnect.delay, shouldRetry: (error) => { const normalized = normalizeBleError(error); return [10003, 10006, 10012].includes(normalized.code); }, }); } catch (error) { throw emitError(error); } } /** 断开BLE连接并重置连接相关状态。 */ async function disconnect(deviceId = state.deviceId) { if (!deviceId) { return; } try { await promisifyUniApi('closeBLEConnection', { deviceId }); } catch (error) { logger.warn('[BLE] close connection failed', error); } patchState({ connected: false, deviceId: '', serviceId: '', characteristics: {}, }); setStage(BLE_STAGE.DISCONNECTED); } /** 注册连接状态变更监听,断开时自动切换到 DISCONNECTED。 */ function watchConnectionChange() { if (connectionStateHandler) { return; } connectionStateHandler = (result) => { if (state.deviceId && result.deviceId !== state.deviceId) { return; } patchState({ connected: Boolean(result.connected) }); eventBus.emit('connection:change', result); if (!result.connected) { setStage(BLE_STAGE.DISCONNECTED); } }; uni.onBLEConnectionStateChange(connectionStateHandler); } /** 移除连接状态变更监听。 */ function unwatchConnectionChange() { if (!connectionStateHandler) { return; } if (uni.offBLEConnectionStateChange) { uni.offBLEConnectionStateChange(connectionStateHandler); } connectionStateHandler = null; } /** 获取候选服务UUID列表:profile主服务 + 兼容固件的 00F4/F400/FFF4。 */ function getServiceCandidates() { const profileService = currentProfile?.uuids?.service; const candidates = [ profileService, toFullUuid('00F4'), toFullUuid('F400'), toFullUuid('FFF4'), ]; return candidates.filter(Boolean); } /** * 发现并锁定目标服务。 * 文档主服务为 0x00F4,同时兼容部分固件 0xF400 / 0xFFF4。 */ async function discoverServices(deviceId = state.deviceId) { if (!deviceId) { throw emitError(createBleError(10013, 'discoverServices 缺少 deviceId')); } try { const result = await promisifyUniApi('getBLEDeviceServices', { deviceId, }); const services = result.services || []; const candidates = getServiceCandidates(); const targetService = services.find((service) => candidates.some((uuid) => uuidEquals(service.uuid, uuid)), ); if (!targetService) { throw createBleError(10004, '设备未暴露目标服务(00F4/F400/FFF4)'); } patchState({ serviceId: targetService.uuid }); setStage(BLE_STAGE.SERVICE_DISCOVERED); return { services, targetService, }; } catch (error) { throw emitError(error); } } /** * 发现特征值并按业务用途缓存(wifi/key/cmd/msg/state/radar)。 * @param {object} options 可选覆盖 deviceId/serviceId * @returns {object} characteristicMap 各业务特征 + all 全量列表 */ async function discoverCharacteristics(options = {}) { const deviceId = options.deviceId || state.deviceId; const serviceId = options.serviceId || state.serviceId; if (!deviceId || !serviceId) { throw emitError( createBleError( 10013, 'discoverCharacteristics 缺少 deviceId/serviceId', ), ); } try { const result = await promisifyUniApi('getBLEDeviceCharacteristics', { deviceId, serviceId, }); const list = result.characteristics || []; const profile = getProfile(); // 缓存业务侧常用特征,减少后续每次操作时的查找成本。 const characteristicMap = { wifi: list.find((item) => uuidEquals(item.uuid, profile.uuids.wifi)) || null, key: list.find((item) => uuidEquals(item.uuid, profile.uuids.key)) || null, cmd: list.find((item) => uuidEquals(item.uuid, profile.uuids.cmd)) || null, msg: list.find((item) => uuidEquals(item.uuid, profile.uuids.msg)) || null, state: list.find((item) => uuidEquals(item.uuid, profile.uuids.state)) || null, radar: list.find((item) => uuidEquals(item.uuid, profile.uuids.radar)) || null, all: list, }; patchState({ characteristics: characteristicMap }); return characteristicMap; } catch (error) { throw emitError(error); } } /** 读取指定特征值(触发 onBLECharacteristicValueChange 回调)。 */ async function readCharacteristic(characteristicId, options = {}) { const deviceId = options.deviceId || state.deviceId; const serviceId = options.serviceId || state.serviceId; if (!deviceId || !serviceId || !characteristicId) { throw emitError(createBleError(10013, 'readCharacteristic 参数缺失')); } try { await promisifyUniApi('readBLECharacteristicValue', { deviceId, serviceId, characteristicId, }); return true; } catch (error) { throw emitError(error); } } /** 向指定特征值写入数据,自动将输入转为 ArrayBuffer。 */ async function writeCharacteristic(characteristicId, value, options = {}) { const deviceId = options.deviceId || state.deviceId; const serviceId = options.serviceId || state.serviceId; if (!deviceId || !serviceId || !characteristicId) { throw emitError(createBleError(10013, 'writeCharacteristic 参数缺失')); } // 取特征 properties,用于诊断写入类型是否匹配,并按需指定 writeType const chars = state.characteristics || {}; const matched = Object.values(chars).find( (item) => item && uuidEquals(item.uuid, characteristicId), ); const props = matched?.properties || {}; const supportsWrite = Boolean(props.write); const supportsWriteNoResp = Boolean(props.writeWithoutResponse); // 默认优先有响应写入(可靠);仅当不支持有响应时退化为无响应写入 const writeType = options.writeType || (supportsWrite ? 'write' : supportsWriteNoResp ? 'writeWithoutResponse' : undefined); try { console.debug('[BLE][WRITE]', characteristicId, { writeType, supportsWrite, supportsWriteNoResp, valueLen: value?.length }); const params = { deviceId, serviceId, characteristicId, value: uint8ArrayToArrayBuffer(toUint8Array(value)), }; if (writeType) { params.writeType = writeType; } await promisifyUniApi('writeBLECharacteristicValue', params); return true; } catch (error) { throw emitError(error); } } /** 订阅/取消订阅指定特征值的通知。 */ async function notifyCharacteristic( characteristicId, stateFlag = true, options = {}, ) { const deviceId = options.deviceId || state.deviceId; const serviceId = options.serviceId || state.serviceId; if (!deviceId || !serviceId || !characteristicId) { throw emitError(createBleError(10013, 'notifyCharacteristic 参数缺失')); } try { await promisifyUniApi('notifyBLECharacteristicValueChange', { deviceId, serviceId, characteristicId, state: Boolean(stateFlag), }); return true; } catch (error) { throw emitError(error); } } function watchCharacteristicValue() { if (characteristicValueHandler) { return; } // 统一转为 Uint8Array,简化上层解析逻辑。 characteristicValueHandler = (result) => { const value = toUint8Array(result.value); // 原始 trace:定位 notify 是否投递时,确认每个特征值变更的来源与内容 try { const hex = Array.from(value) .map((b) => b.toString(16).padStart(2, '0')) .join(' '); console.debug('[BLE][RAW]', result.characteristicId, 'len=', value.length, 'hex=', hex); } catch (e) { } eventBus.emit('characteristic:value', { ...result, value, }); }; uni.onBLECharacteristicValueChange(characteristicValueHandler); } /** 移除特征值变更监听。 */ function unwatchCharacteristicValue() { if (!characteristicValueHandler) { return; } if (uni.offBLECharacteristicValueChange) { uni.offBLECharacteristicValueChange(characteristicValueHandler); } characteristicValueHandler = null; } /** 返回已发现设备列表。 */ function listDiscoveredDevices() { return Object.values(state.deviceMap); } /** 从已发现设备中,按广播服务UUID匹配并返回第一个设备。 */ function pickDeviceByService(serviceUuid = currentProfile.uuids.service) { const list = listDiscoveredDevices(); return ( list.find((device) => uuidInList(serviceUuid, device.advertisServiceUUIDs || []), ) || null ); } async function cleanup() { // 清理顺序:先停扫描与连接,再移除监听,最后关闭适配器。 await stopDiscovery(); await disconnect(); unwatchDeviceFound(); unwatchAdapterState(); unwatchConnectionChange(); unwatchCharacteristicValue(); await closeAdapter(); eventBus.clear(); } return { openAdapter, closeAdapter, getAdapterState, watchAdapterState, unwatchAdapterState, startDiscovery, stopDiscovery, watchDeviceFound, unwatchDeviceFound, connect, disconnect, watchConnectionChange, unwatchConnectionChange, discoverServices, discoverCharacteristics, readCharacteristic, writeCharacteristic, notifyCharacteristic, watchCharacteristicValue, unwatchCharacteristicValue, pickDeviceByService, listDiscoveredDevices, getState: snapshotState, setStage, setProfile, getProfile, setAuthState, emit, on: eventBus.on, once: eventBus.once, cleanup, getBleErrorText, }; }