Initial commit of akmon project
This commit is contained in:
250
uni_modules/ak-sbsrv/utssdk/mp-weixin/device_manager.uts
Normal file
250
uni_modules/ak-sbsrv/utssdk/mp-weixin/device_manager.uts
Normal file
@@ -0,0 +1,250 @@
|
||||
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<string, BleDevice>();
|
||||
private connectionStates = new Map<string, BleConnectionState>();
|
||||
private connectionListeners: BleConnectionStateChangeCallback[] = [];
|
||||
private pendingConnects = new Map<string, PendingConnect>();
|
||||
private scanOptions: ScanDevicesOptions | null = null;
|
||||
private scanTimer: number | null = null;
|
||||
private adapterReady: boolean = false;
|
||||
private adapterPromise: Promise<void> | 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<void> {
|
||||
if (this.adapterReady) return Promise.resolve();
|
||||
if (this.adapterPromise != null) return this.adapterPromise!;
|
||||
this.adapterPromise = new Promise<void>((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<void> {
|
||||
return this.ensureAdapter().then(() => {
|
||||
return this.beginScan(options ?? {} as ScanDevicesOptions);
|
||||
});
|
||||
}
|
||||
|
||||
private beginScan(options: ScanDevicesOptions): Promise<void> {
|
||||
if (this.discoveryActive) {
|
||||
this.stopScanInternal();
|
||||
}
|
||||
this.scanOptions = options;
|
||||
const services = options.optionalServices ?? null;
|
||||
return new Promise<void>((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<void> {
|
||||
return this.ensureAdapter().then(() => {
|
||||
const timeout = options?.timeout ?? 15000;
|
||||
return new Promise<void>((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<void> {
|
||||
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<void> {
|
||||
const attempts = options?.maxAttempts ?? 3;
|
||||
const interval = options?.interval ?? 3000;
|
||||
let count = 0;
|
||||
const attempt = (): Promise<void> => {
|
||||
return this.connectDevice(deviceId, options).catch((err) => {
|
||||
count++;
|
||||
if (count >= attempts) throw err;
|
||||
return new Promise<void>((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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user