import type { BleDevice, BleConnectOptionsExt, BleConnectionState, BleConnectionStateChangeCallback, ScanDevicesOptions } from '../interface.uts'; declare const wx: any; 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 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 adapterReady: boolean = false; private adapterPromise: Promise | null = null; private discoveryActive: boolean = false; private deviceFoundRegistered: boolean = false; private connectionEventRegistered: boolean = false; private constructor() {} static getInstance(): DeviceManager { if (DeviceManager.instance == null) { DeviceManager.instance = new DeviceManager(); } return DeviceManager.instance!; } private ensureAdapter(): Promise { if (this.adapterReady) return Promise.resolve(); if (this.adapterPromise != null) return this.adapterPromise!; this.adapterPromise = new Promise((resolve, reject) => { wx.openBluetoothAdapter({ success: () => { this.adapterReady = true; this.adapterPromise = null; this.ensureEventHandlers(); resolve(); }, fail: (err: any) => { this.adapterPromise = null; reject(err ?? new Error('openBluetoothAdapter failed')); } }); }); return this.adapterPromise!; } private ensureEventHandlers() { if (!this.deviceFoundRegistered) { this.deviceFoundRegistered = true; wx.onBluetoothDeviceFound((res: any) => { try { this.handleDeviceFound(res); } catch (e) { } }); } if (!this.connectionEventRegistered) { this.connectionEventRegistered = true; wx.onBLEConnectionStateChange((res: any) => { try { this.handleConnectionState(res); } catch (e) { } }); } } startScan(options: ScanDevicesOptions): Promise { return this.ensureAdapter().then(() => { return this.beginScan(options ?? {} as ScanDevicesOptions); }); } private beginScan(options: ScanDevicesOptions): Promise { if (this.discoveryActive) { this.stopScanInternal(); } this.scanOptions = options; const services = options.optionalServices ?? null; return new Promise((resolve, reject) => { wx.startBluetoothDevicesDiscovery({ services: services ?? undefined, allowDuplicatesKey: false, success: () => { this.discoveryActive = true; if (options.timeout != null && options.timeout > 0) { this.scanTimer = setTimeout(() => { this.stopScanInternal(); }, options.timeout); } resolve(); }, fail: (err: any) => { this.discoveryActive = false; reject(err ?? new Error('startBluetoothDevicesDiscovery failed')); } }); }); } stopScan(): void { this.stopScanInternal(); } private stopScanInternal() { if (!this.discoveryActive) return; this.discoveryActive = false; try { wx.stopBluetoothDevicesDiscovery({}); } catch (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) { } } } private handleDeviceFound(res: any) { const list: any[] = res?.devices ?? []; for (let i = 0; i < list.length; i++) { const item = list[i]; if (item == null) continue; const deviceId = item.deviceId ?? item.deviceId ?? ''; if (!deviceId) continue; const name = item.name ?? item.localName ?? 'Unknown'; const rssi = 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) { } } } } private handleConnectionState(res: any) { const deviceId = res?.deviceId ?? ''; if (!deviceId) return; const connected = res?.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) { } } } connectDevice(deviceId: string, options?: BleConnectOptionsExt): Promise { return this.ensureAdapter().then(() => { const timeout = options?.timeout ?? 15000; return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.pendingConnects.delete(deviceId); reject(new Error('连接超时')); }, timeout); this.pendingConnects.set(deviceId, { resolve, reject, timer }); wx.createBLEConnection({ deviceId, timeout, success: () => { this.pendingConnects.delete(deviceId); clearTimeout(timer); this.connectionStates.set(deviceId, 2); resolve(); }, fail: (err: any) => { this.pendingConnects.delete(deviceId); clearTimeout(timer); reject(err ?? new Error('createBLEConnection failed')); } }); }); }); } disconnectDevice(deviceId: string): Promise { return new Promise((resolve, reject) => { wx.closeBLEConnection({ deviceId, success: () => { this.connectionStates.set(deviceId, 0); resolve(); }, fail: (err: any) => { reject(err ?? new Error('closeBLEConnection failed')); } }); }); } 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; } }