Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View 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;
}
}