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; connect?: (device: BleDevice, options?: BleConnectOptionsExt) => Promise; disconnect?: (device: BleDevice) => Promise; sendData?: (device: BleDevice, payload?: SendDataPayload, options?: BleOptions) => Promise; autoConnect?: (device: BleDevice, options?: BleConnectOptionsExt) => Promise; } // 设备上下文 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(); // key: deviceId|protocol // Single active protocol handler (no multi-protocol registration) let activeProtocol: BleProtocolType = 'standard'; let activeHandler: ProtocolHandler | null = null; // 事件监听注册表 const eventListeners = new Map>(); let defaultBluetoothService: BluetoothService | null = null; 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, bluetoothService?: BluetoothService) { super(bluetoothService); this._raw = raw ?? null; } override async scanDevices(options?: ScanDevicesOptions): Promise { const rawTyped = this._raw; if (rawTyped != null && typeof rawTyped.scanDevices == 'function') { await rawTyped.scanDevices(options); } return; } override async connect(device: BleDevice, options?: BleConnectOptionsExt): Promise { const rawTyped = this._raw; if (rawTyped != null && typeof rawTyped.connect == 'function') { await rawTyped.connect(device, options); } return; } override async disconnect(device: BleDevice): Promise { 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 { 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 { 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; 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, defaultBluetoothService); (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 => { ensureDefaultProtocolHandler(); 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 => { 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 => { 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 => { 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 => { 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; } function ensureDefaultProtocolHandler(): void { if (activeHandler != null) return; const service = defaultBluetoothService; if (service == null) return; try { 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) => { const result: AutoBleInterfaces = { serviceId: '', writeCharId: '', notifyCharId: '' }; return Promise.resolve(result); } }; const _wrapper = new ProtocolHandlerWrapper(_raw, service); 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); } } export const setDefaultBluetoothService = (service: BluetoothService) => { defaultBluetoothService = service; ensureDefaultProtocolHandler(); };