// H5平台 Web Bluetooth 设备扫描实现 import { DeviceManager } from './device-manager.uts'; import { ServiceManager } from './service-manager.uts'; import type { BleDevice, BleOptions, BleConnectOptionsExt, BleDataReceivedCallback, BleConnectionStateChangeCallback } from '../interface.uts' const DEFAULT_OPTIONAL_SERVICES = [ '00001800-0000-1000-8000-00805f9b34fb', // GAP '0000180a-0000-1000-8000-00805f9b34fb', // Device Information '0000180f-0000-1000-8000-00805f9b34fb', // Battery '00001812-0000-1000-8000-00805f9b34fb', // Human Interface Device '0000fe59-0000-1000-8000-00805f9b34fb', // Nordic DFU / vendor specific '6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART primary service '6e400010-b5a3-f393-e0a9-e50e24dcca9e', // Custom health service (observed on Android) '6e400020-b5a3-f393-e0a9-e50e24dcca9e' // Additional vendor service ]; export const BLE_SERVICE_PREFIXES = [ '6e4000', '0000180f', '00001812', '0000fe59' ]; function normalizeServiceUuid(uuid: string): string { if (!uuid) return uuid; let u = uuid.trim().toLowerCase(); if (u.startsWith('0x')) { u = u.slice(2); } if (/^[0-9a-f]{4}$/.test(u)) { return `0000${u}-0000-1000-8000-00805f9b34fb`; } return u; } function mergeOptionalServices(userServices?: string[]): string[] { const set = new Set(); DEFAULT_OPTIONAL_SERVICES.forEach((svc) => set.add(normalizeServiceUuid(svc))); if (userServices != null) { for (let i = 0; i < userServices.length; i++) { const normalized = normalizeServiceUuid(userServices[i]); if (normalized != null && normalized !== '') { set.add(normalized); } } } return Array.from(set); } // 实例化各个管理器 const deviceManager = new DeviceManager(); const serviceManager = new ServiceManager(); // 导出简化接口 export const scanDevices = async (options?: { optionalServices?: string[] }) => { const mergedOptions = options != null ? { ...options } : {}; mergedOptions.optionalServices = mergeOptionalServices(options?.optionalServices ?? []); return deviceManager.startScan(mergedOptions); }; export const connectDevice = async (deviceId: string, options?: BleConnectOptionsExt) => deviceManager.connectDevice(deviceId, options); export const disconnectDevice = async (deviceId: string) => deviceManager.disconnectDevice(deviceId); export const getConnectedDevices = () => deviceManager.getConnectedDevices(); export const getKnownDevices = () => Object.keys((deviceManager as any).devices || {}) export const discoverServices = async (deviceId: string) => { // 获取 server 实例 const server = deviceManager.getServer(deviceId) if (!server) throw new Error(`设备未连接: ${deviceId}`) return serviceManager.discoverServices(deviceId, server); }; export const getCharacteristics = async (deviceId: string, serviceId: string) => serviceManager.getCharacteristics(deviceId, serviceId); export const writeCharacteristic = async (deviceId: string, serviceId: string, characteristicId: string, data: string | ArrayBuffer) => serviceManager.writeCharacteristic(deviceId, serviceId, characteristicId, data); export const subscribeCharacteristic = async (deviceId: string, serviceId: string, characteristicId: string, callback) => { console.log('[bluetooth_manager] subscribeCharacteristic called:', deviceId, serviceId, characteristicId) return serviceManager.subscribeCharacteristic(deviceId, serviceId, characteristicId, callback); } export const unsubscribeCharacteristic = async (deviceId: string, serviceId: string, characteristicId: string) => serviceManager.unsubscribeCharacteristic(deviceId, serviceId, characteristicId); export const readCharacteristic = async (deviceId: string, serviceId: string, characteristicId: string) => serviceManager.readCharacteristic(deviceId, serviceId, characteristicId); export const sendCommand = async (deviceId: string, serviceId: string, writeCharId: string, notifyCharId: string, command: string, params: any = null, timeout: number = 5000) => dataProcessor.sendAndReceive(deviceId, serviceId, writeCharId, notifyCharId, command, params, timeout); // Event adapter helpers: translate DeviceManager callbacks into payload objects export const onDeviceFound = (listener) => deviceManager.onDeviceFound((device) => { try { listener({ device }); } catch (e) { /* ignore listener errors */ } }); export const onScanFinished = (listener) => deviceManager.onScanFinished(() => { try { listener({}); } catch (e) {} }); export const onConnectionStateChange = (listener) => deviceManager.onConnectionStateChange((deviceId, state) => { try { listener({ device: { deviceId }, state }); } catch (e) {} }); /** * 自动连接并初始化蓝牙设备,获取可用serviceId、writeCharId、notifyCharId * @param deviceId 设备ID * @returns {Promise<{serviceId: string, writeCharId: string, notifyCharId: string}>} */ export const autoConnect = async (deviceId: string): Promise<{serviceId: string, writeCharId: string, notifyCharId: string}> => { // 1. 连接设备 await connectDevice(deviceId); // 2. 服务发现 const services = await discoverServices(deviceId); if (!services || services.length === 0) throw new Error('未发现服务'); // 3. 获取私有serviceId(优先bae前缀或通过dataProcessor模板) let serviceId = ''; for (const s of services) { if (s.uuid && BLE_SERVICE_PREFIXES.some(prefix => s.uuid.startsWith(prefix))) { serviceId = s.uuid; break; } } if (!serviceId) { // 可扩展:通过dataProcessor获取模板serviceId serviceId = services[0].uuid; } // 4. 获取特征值 const characteristics = await getCharacteristics(deviceId, serviceId); if (!characteristics || characteristics.length === 0) throw new Error('未发现特征值'); // 5. 找到write和notify特征 let writeCharId = ''; let notifyCharId = ''; for (const c of characteristics) { if (!writeCharId && (c.properties.write || c.properties.writeWithoutResponse)) writeCharId = c.uuid; if (!notifyCharId && (c.properties.notify || c.properties.indicate)) notifyCharId = c.uuid; } if (!writeCharId || !notifyCharId) throw new Error('未找到可用的写/通知特征'); // 6. 注册notification await subscribeCharacteristic(deviceId, serviceId, notifyCharId, (data) => { // 可在此处分发/处理notification // console.log('Notification:', data); }); // 7. 返回结果 return { serviceId, writeCharId, notifyCharId }; };