312 lines
12 KiB
Plaintext
312 lines
12 KiB
Plaintext
import type { BleDevice, BleOptions, BleConnectionState, BleConnectionStateChangeCallback } from '../interface.uts'
|
||
import type { BleConnectOptionsExt } from '../interface.uts'
|
||
import type { ScanDevicesOptions } from '../interface.uts';
|
||
import Context from "android.content.Context";
|
||
import BluetoothAdapter from "android.bluetooth.BluetoothAdapter";
|
||
import BluetoothManager from "android.bluetooth.BluetoothManager";
|
||
import BluetoothDevice from "android.bluetooth.BluetoothDevice";
|
||
import BluetoothGatt from "android.bluetooth.BluetoothGatt";
|
||
import BluetoothGattCallback from "android.bluetooth.BluetoothGattCallback";
|
||
import ScanCallback from "android.bluetooth.le.ScanCallback";
|
||
import ScanResult from "android.bluetooth.le.ScanResult";
|
||
import ScanSettings from "android.bluetooth.le.ScanSettings";
|
||
import Handler from "android.os.Handler";
|
||
import Looper from "android.os.Looper";
|
||
import ContextCompat from "androidx.core.content.ContextCompat";
|
||
import PackageManager from "android.content.pm.PackageManager";
|
||
// 定义 PendingConnect 类型和实现类
|
||
interface PendingConnect {
|
||
resolve: () => void;
|
||
reject: (err?: any) => void; // Changed to make err optional
|
||
timer?: number;
|
||
}
|
||
|
||
class PendingConnectImpl implements PendingConnect {
|
||
resolve: () => void;
|
||
reject: (err?: any) => void; // Changed to make err optional
|
||
timer?: number;
|
||
|
||
constructor(resolve: () => void, reject: (err?: any) => void, timer?: number) {
|
||
this.resolve = resolve;
|
||
this.reject = reject;
|
||
this.timer = timer;
|
||
}
|
||
}
|
||
// 引入全局回调管理
|
||
import { gattCallback } from './service_manager.uts'
|
||
const pendingConnects = new Map<string, PendingConnect>();
|
||
|
||
const STATE_DISCONNECTED = 0;
|
||
const STATE_CONNECTING = 1;
|
||
const STATE_CONNECTED = 2;
|
||
const STATE_DISCONNECTING = 3;
|
||
|
||
export class DeviceManager {
|
||
private static instance: DeviceManager | null = null;
|
||
private devices = new Map<string, BleDevice>();
|
||
private connectionStates = new Map<string, BleConnectionState>();
|
||
private connectionStateChangeListeners: BleConnectionStateChangeCallback[] = []
|
||
private gattMap = new Map<string, BluetoothGatt | null>();
|
||
private scanCallback: ScanCallback | null = null
|
||
private isScanning: boolean = false
|
||
private constructor() {}
|
||
static getInstance(): DeviceManager {
|
||
if (DeviceManager.instance == null) {
|
||
DeviceManager.instance = new DeviceManager();
|
||
}
|
||
return DeviceManager.instance!;
|
||
}
|
||
startScan(options: ScanDevicesOptions): void {
|
||
console.log('ak startscan now')
|
||
const adapter = this.getBluetoothAdapter();
|
||
if (adapter == null) {
|
||
throw new Error('未找到蓝牙适配器');
|
||
}
|
||
if (!adapter.isEnabled) {
|
||
// 尝试请求用户开启蓝牙
|
||
try {
|
||
adapter.enable(); // 直接调用,无需可选链和括号
|
||
} catch (e) {
|
||
// 某些设备可能不支持 enable
|
||
}
|
||
setTimeout(() => {
|
||
if (!adapter.isEnabled) {
|
||
throw new Error('蓝牙未开启');
|
||
}
|
||
}, 1500);
|
||
throw new Error('正在开启蓝牙,请重试');
|
||
}
|
||
const foundDevices = this.devices; // 直接用全局 devices
|
||
|
||
class MyScanCallback extends ScanCallback {
|
||
private foundDevices: Map<string, BleDevice>;
|
||
private onDeviceFound: (device: BleDevice) => void;
|
||
constructor(foundDevices: Map<string, BleDevice>, onDeviceFound: (device: BleDevice) => void) {
|
||
super();
|
||
this.foundDevices = foundDevices;
|
||
this.onDeviceFound = onDeviceFound;
|
||
}
|
||
override onScanResult(callbackType: Int, result: ScanResult): void {
|
||
const device = result.getDevice();
|
||
if (device != null) {
|
||
const deviceId = device.getAddress();
|
||
let bleDevice = foundDevices.get(deviceId);
|
||
if (bleDevice == null) {
|
||
bleDevice = {
|
||
deviceId,
|
||
name: device.getName() ?? 'Unknown',
|
||
rssi: result.getRssi(),
|
||
lastSeen: Date.now()
|
||
};
|
||
foundDevices.set(deviceId, bleDevice);
|
||
this.onDeviceFound(bleDevice);
|
||
} else {
|
||
// 更新属性(已确保 bleDevice 非空)
|
||
bleDevice.rssi = result.getRssi();
|
||
bleDevice.name = device.getName() ?? bleDevice.name;
|
||
bleDevice.lastSeen = Date.now();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
override onScanFailed(errorCode: Int): void {
|
||
console.log('ak scan fail')
|
||
}
|
||
}
|
||
this.scanCallback = new MyScanCallback(foundDevices, options.onDeviceFound ?? (() => {}));
|
||
const scanner = adapter.getBluetoothLeScanner();
|
||
if (scanner == null) {
|
||
throw new Error('无法获取扫描器');
|
||
}
|
||
const scanSettings = new ScanSettings.Builder()
|
||
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||
.build();
|
||
scanner.startScan(null, scanSettings, this.scanCallback);
|
||
this.isScanning = true;
|
||
// 默认10秒后停止扫描
|
||
new Handler(Looper.getMainLooper()).postDelayed(() => {
|
||
if (this.isScanning && this.scanCallback != null) {
|
||
scanner.stopScan(this.scanCallback);
|
||
this.isScanning = false;
|
||
// this.devices = foundDevices;
|
||
if (options.onScanFinished != null) options.onScanFinished?.invoke();
|
||
}
|
||
}, 40000);
|
||
}
|
||
|
||
async connectDevice(deviceId: string, options?: BleConnectOptionsExt): Promise<void> {
|
||
console.log('[AKBLE] connectDevice called, deviceId:', deviceId, 'options:', options, 'connectionStates:')
|
||
const adapter = this.getBluetoothAdapter();
|
||
if (adapter == null) {
|
||
console.error('[AKBLE] connectDevice failed: 蓝牙适配器不可用')
|
||
throw new Error('蓝牙适配器不可用');
|
||
}
|
||
const device = adapter.getRemoteDevice(deviceId);
|
||
if (device == null) {
|
||
console.error('[AKBLE] connectDevice failed: 未找到设备', deviceId)
|
||
throw new Error('未找到设备');
|
||
}
|
||
this.connectionStates.set(deviceId, STATE_CONNECTING);
|
||
console.log('[AKBLE] connectDevice set STATE_CONNECTING, deviceId:', deviceId, 'connectionStates:')
|
||
this.emitConnectionStateChange(deviceId, STATE_CONNECTING);
|
||
const activity = UTSAndroid.getUniActivity();
|
||
const timeout = options?.timeout ?? 15000;
|
||
const key = `${deviceId}|connect`;
|
||
return new Promise<void>((resolve, reject) => {
|
||
const timer = setTimeout(() => {
|
||
console.error('[AKBLE] connectDevice 超时:', deviceId)
|
||
pendingConnects.delete(key);
|
||
this.connectionStates.set(deviceId, STATE_DISCONNECTED);
|
||
this.gattMap.set(deviceId, null);
|
||
this.emitConnectionStateChange(deviceId, STATE_DISCONNECTED);
|
||
reject(new Error('连接超时'));
|
||
}, timeout);
|
||
|
||
// 创建一个适配器函数来匹配类型签名
|
||
const resolveAdapter = () => {
|
||
console.log('[AKBLE] connectDevice resolveAdapter:', deviceId)
|
||
resolve();
|
||
};
|
||
const rejectAdapter = (err?: any) => {
|
||
console.error('[AKBLE] connectDevice rejectAdapter:', deviceId, err)
|
||
reject(err);
|
||
};
|
||
|
||
pendingConnects.set(key, new PendingConnectImpl(resolveAdapter, rejectAdapter, timer));
|
||
try {
|
||
console.log('[AKBLE] connectGatt 调用前:', deviceId)
|
||
const gatt = device.connectGatt(activity, false, gattCallback);
|
||
this.gattMap.set(deviceId, gatt);
|
||
console.log('[AKBLE] connectGatt 调用后:', deviceId, gatt)
|
||
} catch (e) {
|
||
console.error('[AKBLE] connectGatt 异常:', deviceId, e)
|
||
clearTimeout(timer);
|
||
pendingConnects.delete(key);
|
||
this.connectionStates.set(deviceId, STATE_DISCONNECTED);
|
||
this.gattMap.set(deviceId, null);
|
||
this.emitConnectionStateChange(deviceId, STATE_DISCONNECTED);
|
||
reject(e);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 统一分发连接回调(应在 gattCallback.onConnectionStateChange 内调用)
|
||
static handleConnectionStateChange(deviceId: string, newState: number, error?: any) {
|
||
console.log('[AKBLE] handleConnectionStateChange:', deviceId, 'newState:', newState, 'error:', error, 'pendingConnects:')
|
||
const key = `${deviceId}|connect`;
|
||
const cb = pendingConnects.get(key);
|
||
if (cb != null) {
|
||
// 修复 timer 的空安全问题,使用临时变量
|
||
const timerValue = cb.timer;
|
||
if (timerValue != null) {
|
||
clearTimeout(timerValue);
|
||
}
|
||
|
||
// 修复 error 处理
|
||
if (newState === STATE_CONNECTED) {
|
||
console.log('[AKBLE] handleConnectionStateChange: 连接成功', deviceId)
|
||
cb.resolve();
|
||
} else {
|
||
// 正确处理可空值
|
||
const errorToUse = error != null ? error : new Error('连接断开');
|
||
console.error('[AKBLE] handleConnectionStateChange: 连接失败', deviceId, errorToUse)
|
||
cb.reject(errorToUse);
|
||
}
|
||
pendingConnects.delete(key);
|
||
} else {
|
||
console.warn('[AKBLE] handleConnectionStateChange: 未找到 pendingConnects', deviceId, newState)
|
||
}
|
||
}
|
||
|
||
async disconnectDevice(deviceId: string, isActive: boolean = true): Promise<void> {
|
||
console.log('[AKBLE] disconnectDevice called, deviceId:', deviceId, 'isActive:', isActive)
|
||
let gatt = this.gattMap.get(deviceId);
|
||
if (gatt != null) {
|
||
gatt.disconnect();
|
||
gatt.close();
|
||
// gatt=null;
|
||
this.gattMap.set(deviceId, null);
|
||
this.connectionStates.set(deviceId, STATE_DISCONNECTED);
|
||
console.log('[AKBLE] disconnectDevice set STATE_DISCONNECTED, deviceId:', deviceId, 'connectionStates:')
|
||
this.emitConnectionStateChange(deviceId, STATE_DISCONNECTED);
|
||
return;
|
||
} else {
|
||
console.log('[AKBLE] disconnectDevice: gatt is null, deviceId:', deviceId)
|
||
return;
|
||
}
|
||
}
|
||
|
||
async reconnectDevice(deviceId: string, options?: BleConnectOptionsExt): Promise<void> {
|
||
let attempts = 0;
|
||
const maxAttempts = options?.maxAttempts ?? 3;
|
||
const interval = options?.interval ?? 3000;
|
||
while (attempts < maxAttempts) {
|
||
try {
|
||
await this.disconnectDevice(deviceId, false);
|
||
await this.connectDevice(deviceId, options);
|
||
return;
|
||
} catch (e) {
|
||
attempts++;
|
||
if (attempts >= maxAttempts) throw new Error('重连失败');
|
||
// 修复 setTimeout 问题,使用旧式 Promise + setTimeout 解决
|
||
await new Promise<void>((resolve) => {
|
||
setTimeout(() => {
|
||
resolve();
|
||
}, interval);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
getConnectedDevices(): BleDevice[] {
|
||
// 创建一个空数组来存储结果
|
||
const result: BleDevice[] = [];
|
||
|
||
// 遍历 devices Map 并检查连接状态
|
||
this.devices.forEach((device, deviceId) => {
|
||
if (this.connectionStates.get(deviceId) === STATE_CONNECTED) {
|
||
result.push(device);
|
||
}
|
||
});
|
||
|
||
return result;
|
||
}
|
||
|
||
onConnectionStateChange(listener: BleConnectionStateChangeCallback) {
|
||
console.log('[AKBLE][LOG] onConnectionStateChange 注册, 当前监听数:', this.connectionStateChangeListeners.length + 1, listener)
|
||
this.connectionStateChangeListeners.push(listener)
|
||
}
|
||
|
||
protected emitConnectionStateChange(deviceId: string, state: BleConnectionState) {
|
||
console.log('[AKBLE][LOG] emitConnectionStateChange', deviceId, state, 'listeners:', this.connectionStateChangeListeners.length, 'connectionStates:', this.connectionStates)
|
||
for (const listener of this.connectionStateChangeListeners) {
|
||
try {
|
||
console.log('[AKBLE][LOG] emitConnectionStateChange 调用 listener', listener)
|
||
listener(deviceId, state)
|
||
} catch (e) {
|
||
console.error('[AKBLE][LOG] emitConnectionStateChange listener error', e)
|
||
}
|
||
}
|
||
}
|
||
|
||
getGattInstance(deviceId: string): BluetoothGatt | null {
|
||
return this.gattMap.get(deviceId) ?? null;
|
||
}
|
||
|
||
private getBluetoothAdapter(): BluetoothAdapter | null {
|
||
const context = UTSAndroid.getAppContext();
|
||
if (context == null) return null;
|
||
const manager = context?.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager;
|
||
return manager.getAdapter();
|
||
}
|
||
|
||
/**
|
||
* 获取指定ID的设备(如果存在)
|
||
*/
|
||
public getDevice(deviceId: string): BleDevice | null {
|
||
console.log(deviceId,this.devices)
|
||
return this.devices.get(deviceId) ?? null;
|
||
}
|
||
}
|