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) } 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 参数缺失')) } try { await promisifyUniApi('writeBLECharacteristicValue', { deviceId, serviceId, characteristicId, value: uint8ArrayToArrayBuffer(toUint8Array(value)) }) 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) => { eventBus.emit('characteristic:value', { ...result, value: toUint8Array(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 } }