Initial commit

This commit is contained in:
2026-03-16 10:37:46 +08:00
commit c052a67816
508 changed files with 22987 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
// 设备管理相关:扫描、连接、断开、重连
import { BleDevice, BLE_CONNECTION_STATE } from '../interface.uts';
import type { BleConnectOptionsExt } from '../interface.uts';
export class DeviceManager {
private devices = {};
private servers = {};
private connectionStates = {};
private allowedServices = {};
private reconnectAttempts: number = 0;
private maxReconnectAttempts: number = 5;
private reconnectDelay: number = 2000;
private reconnectTimeoutId: number = 0;
private autoReconnect: boolean = false;
private connectionStateChangeListeners: Function[] = [];
private deviceFoundListeners: ((device: BleDevice) => void)[] = [];
private scanFinishedListeners: (() => void)[] = [];
onDeviceFound(listener: (device: BleDevice) => void) {
this.deviceFoundListeners.push(listener);
}
onScanFinished(listener: () => void) {
this.scanFinishedListeners.push(listener);
}
private emitDeviceFound(device: BleDevice) {
for (const listener of this.deviceFoundListeners) {
try { listener(device); } catch (e) {}
}
}
private emitScanFinished() {
for (const listener of this.scanFinishedListeners) {
try { listener(); } catch (e) {}
}
}
async startScan(options?: { optionalServices?: string[] } ): Promise<void> {
if (!navigator.bluetooth) throw new Error('Web Bluetooth API not supported');
try {
const scanOptions: any = { acceptAllDevices: true };
// allow callers to request optionalServices (required by Web Bluetooth to access custom services)
if (options && Array.isArray(options.optionalServices) && options.optionalServices.length > 0) {
scanOptions.optionalServices = options.optionalServices;
}
// Log the exact options passed to requestDevice for debugging optionalServices propagation
try {
console.log('[DeviceManager] requestDevice options:', JSON.stringify(scanOptions));
} catch (e) {
console.log('[DeviceManager] requestDevice options (raw):', scanOptions);
}
const device = await navigator.bluetooth.requestDevice(scanOptions);
try {
console.log('[DeviceManager] requestDevice result:', device);
} catch (e) {
console.log('[DeviceManager] requestDevice result (raw):', device);
}
if (device) {
console.log(device)
// 格式化 deviceId 为 MAC 地址格式
const formatDeviceId = (id: string): string => {
// 如果是12位16进制字符串如 'AABBCCDDEEFF'),转为 'AA:BB:CC:DD:EE:FF'
if (/^[0-9A-Fa-f]{12}$/.test(id)) {
return id.match(/.{1,2}/g)!.join(":").toUpperCase();
}
// 如果是base64无法直接转MAC保留原样
// 你可以根据实际情况扩展此处
return id;
};
const isConnected = !!this.servers[device.id];
const formattedId = formatDeviceId(device.id);
const bleDevice = { deviceId: formattedId, name: device.name, connected: isConnected };
this.devices[formattedId] = device;
this.emitDeviceFound(bleDevice);
}
this.emitScanFinished();
} catch (e) {
this.emitScanFinished();
throw e;
}
}
onConnectionStateChange(listener: (deviceId: string, state: string) => void) {
this.connectionStateChangeListeners.push(listener);
}
private emitConnectionStateChange(deviceId: string, state: string) {
for (const listener of this.connectionStateChangeListeners) {
try {
listener(deviceId, state);
} catch (e) {
// 忽略单个回调异常
}
}
}
async connectDevice(deviceId: string, options?: BleConnectOptionsExt): Promise<boolean> {
this.autoReconnect = options?.autoReconnect ?? false;
try {
const device = this.devices[deviceId];
if (!device) throw new Error('设备未找到');
const server = await device.gatt.connect();
this.servers[deviceId] = server;
this.connectionStates[deviceId] = BLE_CONNECTION_STATE.CONNECTED;
this.reconnectAttempts = 0;
this.emitConnectionStateChange(deviceId, 'connected');
// 监听物理断开
if (device.gatt) {
device.gatt.onconnectionstatechanged = null;
device.gatt.onconnectionstatechanged = () => {
if (!device.gatt.connected) {
this.emitConnectionStateChange(deviceId, 'disconnected');
}
};
}
return true;
} catch (error) {
if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
return this.scheduleReconnect(deviceId);
}
throw error;
}
}
async disconnectDevice(deviceId: string): Promise<void> {
const device = this.devices[deviceId];
if (!device) throw new Error('设备未找到');
try {
if (device.gatt && device.gatt.connected) {
device.gatt.disconnect();
}
delete this.servers[deviceId];
this.connectionStates[deviceId] = BLE_CONNECTION_STATE.DISCONNECTED;
this.emitConnectionStateChange(deviceId, 'disconnected');
} catch (e) {
throw e;
}
}
getConnectedDevices(): BleDevice[] {
const connectedDevices: BleDevice[] = [];
for (const deviceId in this.servers) {
const device = this.devices[deviceId];
if (device) {
connectedDevices.push({ deviceId: device.id, name: device.name || '未知设备', connected: true });
}
}
return connectedDevices;
}
handleDisconnect(deviceId: string) {
this.connectionStates[deviceId] = BLE_CONNECTION_STATE.DISCONNECTED;
this.emitConnectionStateChange(deviceId, 'disconnected');
if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
this.scheduleReconnect(deviceId);
}
}
private scheduleReconnect(deviceId: string): Promise<boolean> {
this.reconnectAttempts++;
return new Promise((resolve, reject) => {
this.reconnectTimeoutId = setTimeout(() => {
this.connectDevice(deviceId, { autoReconnect: true })
.then(resolve)
.catch(reject);
}, this.reconnectDelay);
});
}
cancelReconnect() {
if (this.reconnectTimeoutId) {
clearTimeout(this.reconnectTimeoutId);
this.reconnectTimeoutId = 0;
}
this.autoReconnect = false;
this.reconnectAttempts = 0;
}
setMaxReconnectAttempts(attempts: number) {
this.maxReconnectAttempts = attempts;
}
setReconnectDelay(delay: number) {
this.reconnectDelay = delay;
}
isDeviceConnected(deviceId: string): boolean {
return !!this.servers[deviceId];
}
}