Files
akbleserver/uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts
2026-03-16 10:37:46 +08:00

279 lines
11 KiB
Plaintext

import type {
BleDevice,
BleConnectionState,
BleEvent,
BleEventCallback,
BleEventPayload,
BleScanResult,
BleConnectOptionsExt,
AutoBleInterfaces,
BleDataPayload,
SendDataPayload,
BleOptions,
MultiProtocolDevice,
ScanHandler,
BleProtocolType,
ScanDevicesOptions
} from '../interface.uts';
import { ProtocolHandler } from '../protocol_handler.uts';
import { BluetoothService } from '../interface.uts';
import { DeviceManager } from './device_manager.uts';
// Shape used when callers register plain objects as handlers. Using a named
// type keeps member access explicit so the code generator emits valid Kotlin
// member references instead of trying to access properties on Any.
type RawProtocolHandler = {
protocol?: BleProtocolType;
scanDevices?: (options?: ScanDevicesOptions) => Promise<void>;
connect?: (device: BleDevice, options?: BleConnectOptionsExt) => Promise<void>;
disconnect?: (device: BleDevice) => Promise<void>;
sendData?: (device: BleDevice, payload?: SendDataPayload, options?: BleOptions) => Promise<void>;
autoConnect?: (device: BleDevice, options?: BleConnectOptionsExt) => Promise<AutoBleInterfaces>;
}
// 设备上下文
class DeviceContext {
device : BleDevice;
protocol : BleProtocolType;
state : BleConnectionState;
handler : ProtocolHandler;
constructor(device : BleDevice, protocol : BleProtocolType, handler : ProtocolHandler) {
this.device = device;
this.protocol = protocol;
this.state = 0; // DISCONNECTED
this.handler = handler;
}
}
const deviceMap = new Map<string, DeviceContext>(); // key: deviceId|protocol
// Single active protocol handler (no multi-protocol registration)
let activeProtocol: BleProtocolType = 'standard';
let activeHandler: ProtocolHandler | null = null;
// 事件监听注册表
const eventListeners = new Map<BleEvent, Set<BleEventCallback>>();
function emit(event : BleEvent, payload : BleEventPayload) {
if (event === 'connectionStateChanged') {
console.log('[AKBLE][LOG] bluetooth_manager.uts emit connectionStateChanged', payload)
}
const listeners = eventListeners.get(event);
if (listeners != null) {
listeners.forEach(cb => {
try { cb(payload); } catch (e) { }
});
}
}
class ProtocolHandlerWrapper extends ProtocolHandler {
private _raw: RawProtocolHandler | null;
constructor(raw?: RawProtocolHandler) {
// pass a lightweight BluetoothService instance to satisfy generators
super(new BluetoothService());
this._raw = (raw != null) ? raw : null;
}
override async scanDevices(options?: ScanDevicesOptions): Promise<void> {
const rawTyped = this._raw;
if (rawTyped != null && typeof rawTyped.scanDevices === 'function') {
await rawTyped.scanDevices(options);
}
return;
}
override async connect(device: BleDevice, options?: BleConnectOptionsExt): Promise<void> {
const rawTyped = this._raw;
if (rawTyped != null && typeof rawTyped.connect === 'function') {
await rawTyped.connect(device, options);
}
return;
}
override async disconnect(device: BleDevice): Promise<void> {
const rawTyped = this._raw;
if (rawTyped != null && typeof rawTyped.disconnect === 'function') {
await rawTyped.disconnect(device);
}
return;
}
override async sendData(device: BleDevice, payload?: SendDataPayload, options?: BleOptions): Promise<void> {
const rawTyped = this._raw;
if (rawTyped != null && typeof rawTyped.sendData === 'function') {
await rawTyped.sendData(device, payload, options);
}
return;
}
override async autoConnect(device: BleDevice, options?: BleConnectOptionsExt): Promise<AutoBleInterfaces> {
const rawTyped = this._raw;
if (rawTyped != null && typeof rawTyped.autoConnect === 'function') {
return await rawTyped.autoConnect(device, options);
}
return { serviceId: '', writeCharId: '', notifyCharId: '' };
}
}
// Strong runtime detector for plain object handlers (no Type Predicate)
// Note: the UTS bundler doesn't support TypeScript type predicates (x is T),
// and it doesn't accept the 'unknown' type. This returns a boolean and
// callers must cast the value to RawProtocolHandler after the function
// returns true.
function isRawProtocolHandler(x: any): boolean {
if (x == null || typeof x !== 'object') return false;
const r = x as Record<string, unknown>;
if (typeof r['scanDevices'] === 'function') return true;
if (typeof r['connect'] === 'function') return true;
if (typeof r['disconnect'] === 'function') return true;
if (typeof r['sendData'] === 'function') return true;
if (typeof r['autoConnect'] === 'function') return true;
if (typeof r['protocol'] === 'string') return true;
return false;
}
export const registerProtocolHandler = (handler : any) => {
if (handler == null) return;
// Determine protocol value defensively. Default to 'standard' when unknown.
let proto: BleProtocolType = 'standard';
if (handler instanceof ProtocolHandler) {
try { proto = (handler as ProtocolHandler).protocol as BleProtocolType; } catch (e) { }
activeHandler = handler as ProtocolHandler;
} else if (isRawProtocolHandler(handler)) {
try { proto = (handler as RawProtocolHandler).protocol as BleProtocolType; } catch (e) { }
activeHandler = new ProtocolHandlerWrapper(handler as RawProtocolHandler);
(activeHandler as ProtocolHandler).protocol = proto;
} else {
console.warn('[AKBLE] registerProtocolHandler: unsupported handler type, ignoring', handler);
return;
}
activeProtocol = proto;
}
export const scanDevices = async (options ?: ScanDevicesOptions) : Promise<void> => {
console.log('[AKBLE] start scan', options)
// Determine which protocols to run: either user-specified or all registered
// Single active handler flow
if (activeHandler == null) {
console.log('[AKBLE] no active scan handler registered')
return
}
const handler = activeHandler as ProtocolHandler;
const scanOptions : ScanDevicesOptions = {
onDeviceFound: (device : BleDevice) => emit('deviceFound', { event: 'deviceFound', device }),
onScanFinished: () => emit('scanFinished', { event: 'scanFinished' })
}
try {
await handler.scanDevices(scanOptions)
} catch (e) {
console.warn('[AKBLE] scanDevices handler error', e)
}
}
export const connectDevice = async (deviceId : string, protocol : BleProtocolType, options ?: BleConnectOptionsExt) : Promise<void> => {
const handler = activeHandler;
if (handler == null) throw new Error('No protocol handler');
const device : BleDevice = { deviceId, name: '', rssi: 0 }; // 可扩展
await handler.connect(device, options);
const ctx = new DeviceContext(device, protocol, handler);
ctx.state = 2; // CONNECTED
deviceMap.set(getDeviceKey(deviceId, protocol), ctx);
console.log(deviceMap)
emit('connectionStateChanged', { event: 'connectionStateChanged', device, protocol, state: 2 });
}
export const disconnectDevice = async (deviceId : string, protocol : BleProtocolType) : Promise<void> => {
const ctx = deviceMap.get(getDeviceKey(deviceId, protocol));
if (ctx == null || ctx.handler == null) return;
await ctx.handler.disconnect(ctx.device);
ctx.state = 0;
emit('connectionStateChanged', { event: 'connectionStateChanged', device: ctx.device, protocol, state: 0 });
deviceMap.delete(getDeviceKey(deviceId, protocol));
}
export const sendData = async (payload : SendDataPayload, options ?: BleOptions) : Promise<void> => {
const ctx = deviceMap.get(getDeviceKey(payload.deviceId, payload.protocol));
if (ctx == null) throw new Error('Device not connected');
// copy to local non-null variable so generator can smart-cast across awaits
const deviceCtx = ctx as DeviceContext;
if (deviceCtx.handler == null) throw new Error('sendData not supported for this protocol');
await deviceCtx.handler.sendData(deviceCtx.device, payload, options);
emit('dataSent', { event: 'dataSent', device: deviceCtx.device, protocol: payload.protocol, data: payload.data });
}
export const getConnectedDevices = () : MultiProtocolDevice[] => {
const result : MultiProtocolDevice[] = [];
deviceMap.forEach((ctx : DeviceContext) => {
const dev : MultiProtocolDevice = {
deviceId: ctx.device.deviceId,
name: ctx.device.name,
rssi: ctx.device.rssi,
protocol: ctx.protocol
};
result.push(dev);
});
return result;
}
export const getConnectionState = (deviceId : string, protocol : BleProtocolType) : BleConnectionState => {
const ctx = deviceMap.get(getDeviceKey(deviceId, protocol));
if (ctx == null) return 0;
return ctx.state;
}
export const on = (event : BleEvent, callback : BleEventCallback) => {
if (!eventListeners.has(event)) eventListeners.set(event, new Set());
eventListeners.get(event)!.add(callback);
}
export const off = (event : BleEvent, callback ?: BleEventCallback) => {
if (callback == null) {
eventListeners.delete(event);
} else {
eventListeners.get(event)?.delete(callback as BleEventCallback);
}
}
function getDeviceKey(deviceId : string, protocol : BleProtocolType) : string {
return `${deviceId}|${protocol}`;
}
export const autoConnect = async (deviceId : string, protocol : BleProtocolType, options ?: BleConnectOptionsExt) : Promise<AutoBleInterfaces> => {
const handler = activeHandler;
if (handler == null) throw new Error('autoConnect not supported for this protocol');
const device : BleDevice = { deviceId, name: '', rssi: 0 };
// safe call - handler.autoConnect exists on ProtocolHandler
return await handler.autoConnect(device, options) as AutoBleInterfaces;
}
// Ensure there is at least one handler registered so callers can scan/connect
// without needing to import a registry module. This creates a minimal default
// ProtocolHandler backed by a BluetoothService instance.
try {
if (activeHandler == null) {
// Create a DeviceManager-backed raw handler that delegates to native code
const _dm = DeviceManager.getInstance();
const _raw: RawProtocolHandler = {
protocol: 'standard',
scanDevices: (options?: ScanDevicesOptions) => {
try {
const scanOptions = options != null ? options : {} as ScanDevicesOptions;
_dm.startScan(scanOptions);
} catch (e) {
console.warn('[AKBLE] DeviceManager.startScan failed', e);
}
return Promise.resolve();
},
connect: (device, options?: BleConnectOptionsExt) => {
return _dm.connectDevice(device.deviceId, options);
},
disconnect: (device) => {
return _dm.disconnectDevice(device.deviceId);
},
autoConnect: (device, options?: any) => {
// DeviceManager does not provide an autoConnect helper; return default
const result: AutoBleInterfaces = { serviceId: '', writeCharId: '', notifyCharId: '' };
return Promise.resolve(result);
}
};
const _wrapper = new ProtocolHandlerWrapper(_raw);
activeHandler = _wrapper;
activeProtocol = _raw.protocol as BleProtocolType;
console.log('[AKBLE] default protocol handler (BluetoothService-backed) registered', activeProtocol);
}
} catch (e) {
console.warn('[AKBLE] failed to register default protocol handler', e);
}