import type { BleDevice, BleConnectOptionsExt, BleConnectionState, BleConnectionStateChangeCallback, ScanDevicesOptions } from '../interface.uts'; import ble from '@ohos.bluetooth.ble'; import type { BusinessError } from '@ohos.base'; type PendingConnect = { resolve: () => void; reject: (err?: any) => void; timer?: number; }; function now(): number { return Date.now(); } export class DeviceManager { private static instance: DeviceManager | null = null; private central: any | null = null; private devices = new Map(); private connectionStates = new Map(); private connectionListeners: BleConnectionStateChangeCallback[] = []; private pendingConnects = new Map(); private scanOptions: ScanDevicesOptions | null = null; private scanTimer: number | null = null; private gattMap = new Map(); private scanning: boolean = false; private eventsBound: boolean = false; private constructor() {} static getInstance(): DeviceManager { if (DeviceManager.instance == null) { DeviceManager.instance = new DeviceManager(); } return DeviceManager.instance!; } private ensureCentral(): any { if (this.central != null) return this.central; try { this.central = ble.createBluetoothCentralManager(); this.bindCentralEvents(); } catch (e) { console.warn('[AKBLE][Harmony] createBluetoothCentralManager failed', e); throw e; } return this.central!; } private bindCentralEvents() { if (this.eventsBound) return; this.eventsBound = true; const central = this.central; if (central == null) return; try { central.on('scanResult', (result: any) => { try { this.handleScanResult(result); } catch (e) { console.warn('[AKBLE][Harmony] scanResult handler error', e); } }); } catch (e) { console.warn('[AKBLE][Harmony] central.on scanResult failed', e); } try { central.on('bleDeviceFind', (result: any) => { try { this.handleScanResult(result); } catch (e) { console.warn('[AKBLE][Harmony] bleDeviceFind handler error', e); } }); } catch (e) { /* optional */ } try { central.on('BLEConnectionStateChange', (state: any) => { try { this.handleConnectionEvent(state); } catch (err) { console.warn('[AKBLE][Harmony] connection event error', err); } }); } catch (e) { console.warn('[AKBLE][Harmony] central.on connection change failed', e); } } private handleScanResult(result: any) { const list: any[] = result?.devices ?? result ?? []; if (!Array.isArray(list)) return; for (let i = 0; i < list.length; i++) { const item = list[i]; if (item == null) continue; const deviceId: string = item.deviceId ?? item.device?.deviceId ?? ''; if (!deviceId) continue; const name: string = item.name ?? item.deviceName ?? 'Unknown'; const rssi: number = item.rssi ?? item.RSSI ?? 0; let device = this.devices.get(deviceId); if (device == null) { device = { deviceId, name, rssi, lastSeen: now() }; this.devices.set(deviceId, device); } else { device.name = name; device.rssi = rssi; device.lastSeen = now(); } const cb = this.scanOptions?.onDeviceFound; if (cb != null) { try { cb(device); } catch (e) { console.warn('[AKBLE][Harmony] onDeviceFound error', e); } } } } private handleConnectionEvent(evt: any) { const deviceId: string = evt?.deviceId ?? evt?.device?.deviceId ?? ''; if (!deviceId) return; const connected = evt?.state === ble.BLEConnectionState.STATE_CONNECTED || evt?.connected === true; const state: BleConnectionState = connected ? 2 : 0; this.connectionStates.set(deviceId, state); const pending = this.pendingConnects.get(deviceId); if (pending != null) { this.pendingConnects.delete(deviceId); if (pending.timer != null) clearTimeout(pending.timer); if (connected) { try { pending.resolve(); } catch (e) { } } else { try { pending.reject(new Error('连接断开')); } catch (e) { } } } for (let i = 0; i < this.connectionListeners.length; i++) { const listener = this.connectionListeners[i]; try { listener(deviceId, state); } catch (e) { console.warn('[AKBLE][Harmony] listener error', e); } } } async startScan(options: ScanDevicesOptions): Promise { const central = this.ensureCentral(); this.scanOptions = options ?? {} as ScanDevicesOptions; if (this.scanning) { await this.stopScan(); } this.scanning = true; if (this.scanTimer != null) { clearTimeout(this.scanTimer); this.scanTimer = null; } return new Promise((resolve, reject) => { try { const filter = { interval: 0 } as any; const res = central.startScan?.(filter) ?? central.startScan?.(); if (this.scanOptions?.timeout != null && this.scanOptions.timeout > 0) { this.scanTimer = setTimeout(() => { this.stopScanInternal(); }, this.scanOptions.timeout); } if (res instanceof Promise) { res.then(() => resolve()).catch((err: BusinessError) => { this.scanning = false; reject(err); }); } else { resolve(); } } catch (e) { this.scanning = false; reject(e); } }); } async stopScan(): Promise { this.stopScanInternal(); return Promise.resolve(); } private stopScanInternal() { if (!this.scanning) return; this.scanning = false; try { this.central?.stopScan?.(); } catch (e) { console.warn('[AKBLE][Harmony] stopScan failed', e); } if (this.scanTimer != null) { clearTimeout(this.scanTimer); this.scanTimer = null; } const finished = this.scanOptions?.onScanFinished; this.scanOptions = null; if (finished != null) { try { finished(); } catch (e) { console.warn('[AKBLE][Harmony] onScanFinished error', e); } } } async connectDevice(deviceId: string, options?: BleConnectOptionsExt): Promise { this.ensureCentral(); const timeout = options?.timeout ?? 15000; if (this.connectionStates.get(deviceId) == 2) { return Promise.resolve(); } return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.pendingConnects.delete(deviceId); reject(new Error('连接超时')); }, timeout); this.pendingConnects.set(deviceId, { resolve, reject, timer }); try { let gatt = this.gattMap.get(deviceId); if (gatt == null) { gatt = ble.createGattClientDevice(deviceId); this.gattMap.set(deviceId, gatt); } const connectRes = gatt.connect?.(true); if (connectRes instanceof Promise) { connectRes.then(() => { clearTimeout(timer); this.pendingConnects.delete(deviceId); this.connectionStates.set(deviceId, 2); resolve(); }).catch((err: BusinessError) => { clearTimeout(timer); this.pendingConnects.delete(deviceId); reject(err); }); } else { // Some implementations return immediate state; rely on connection event to resolve. } } catch (e) { clearTimeout(timer); this.pendingConnects.delete(deviceId); reject(e); } }); } async disconnectDevice(deviceId: string): Promise { const gatt = this.gattMap.get(deviceId); if (gatt == null) return; try { const res = gatt.disconnect?.(); if (res instanceof Promise) { await res; } } catch (e) { console.warn('[AKBLE][Harmony] disconnect failed', e); } this.connectionStates.set(deviceId, 0); for (let i = 0; i < this.connectionListeners.length; i++) { const listener = this.connectionListeners[i]; try { listener(deviceId, 0); } catch (err) { console.warn('[AKBLE][Harmony] disconnect listener error', err); } } } reconnectDevice(deviceId: string, options?: BleConnectOptionsExt): Promise { const attempts = options?.maxAttempts ?? 3; const interval = options?.interval ?? 3000; let count = 0; const attempt = (): Promise => { return this.connectDevice(deviceId, options).catch((err) => { count++; if (count >= attempts) throw err; return new Promise((resolve) => { setTimeout(() => resolve(attempt()), interval); }); }); }; return attempt(); } getConnectedDevices(): BleDevice[] { const result: BleDevice[] = []; this.devices.forEach((device, id) => { if (this.connectionStates.get(id) == 2) { result.push(device); } }); return result; } onConnectionStateChange(listener: BleConnectionStateChangeCallback) { this.connectionListeners.push(listener); } getDevice(deviceId: string): BleDevice | null { return this.devices.get(deviceId) ?? null; } getGatt(deviceId: string): any | null { return this.gattMap.get(deviceId) ?? null; } }