Initial commit of akmon project
This commit is contained in:
311
uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts
Normal file
311
uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts
Normal file
@@ -0,0 +1,311 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user