import type { BleDevice, BleConnectionState, BleEvent, BleEventCallback, BleEventPayload, BleConnectOptionsExt, AutoBleInterfaces, SendDataPayload, BleOptions, MultiProtocolDevice, BleProtocolType, ScanDevicesOptions } from '../interface.uts'; import { ProtocolHandler } from '../protocol_handler.uts'; import { BluetoothService } from '../interface.uts'; import { DeviceManager } from './device_manager.uts'; 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; this.handler = handler; } } const deviceMap = new Map(); let activeProtocol: BleProtocolType = 'standard'; let activeHandler: ProtocolHandler | null = null; const eventListeners = new Map>(); let defaultBluetoothService: BluetoothService | null = null; let connectionHooked = false; function emit(event: BleEvent, payload: BleEventPayload) { 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: '' }; } } 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; } function ensureConnectionHook() { if (connectionHooked) return; connectionHooked = true; const dm = DeviceManager.getInstance(); dm.onConnectionStateChange((deviceId, state) => { let handled = false; deviceMap.forEach((ctx) => { if (ctx.device.deviceId == deviceId) { ctx.state = state; emit('connectionStateChanged', { event: 'connectionStateChanged', device: ctx.device, protocol: ctx.protocol, state }); handled = true; } }); if (!handled) { emit('connectionStateChanged', { event: 'connectionStateChanged', device: { deviceId, name: '', rssi: 0 }, protocol: activeProtocol, state }); } }); } export const registerProtocolHandler = (handler: any) => { if (handler == null) return; 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][Harmony] registerProtocolHandler unsupported handler', handler); return; } activeProtocol = proto; ensureConnectionHook(); } export const scanDevices = async (options?: ScanDevicesOptions): Promise => { ensureDefaultProtocolHandler(); if (activeHandler == null) { console.log('[AKBLE][Harmony] no active scan handler'); return; } const handler = activeHandler as ProtocolHandler; const original = options ?? null; const scanOptions: ScanDevicesOptions = {} as ScanDevicesOptions; if (original != null) { if (original.protocols != null) scanOptions.protocols = original.protocols; if (original.optionalServices != null) scanOptions.optionalServices = original.optionalServices; if (original.timeout != null) scanOptions.timeout = original.timeout; } const userFound = original?.onDeviceFound ?? null; scanOptions.onDeviceFound = (device: BleDevice) => { emit('deviceFound', { event: 'deviceFound', device }); if (userFound != null) { try { userFound(device); } catch (err) { } } }; const userFinished = original?.onScanFinished ?? null; scanOptions.onScanFinished = () => { emit('scanFinished', { event: 'scanFinished' }); if (userFinished != null) { try { userFinished(); } catch (err) { } } }; try { await handler.scanDevices(scanOptions); } catch (e) { console.warn('[AKBLE][Harmony] scan 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; deviceMap.set(getDeviceKey(deviceId, protocol), ctx); 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'); if (ctx.handler == null) throw new Error('sendData not supported for this protocol'); await ctx.handler.sendData(ctx.device, payload, options); emit('dataSent', { event: 'dataSent', device: ctx.device, protocol: payload.protocol, data: payload.data }); } export const getConnectedDevices = (): MultiProtocolDevice[] => { const result: MultiProtocolDevice[] = []; deviceMap.forEach((ctx: DeviceContext) => { result.push({ deviceId: ctx.device.deviceId, name: ctx.device.name, rssi: ctx.device.rssi, protocol: ctx.protocol }); }); 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 }; 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) => dm.startScan(options ?? {} as ScanDevicesOptions), connect: (device, options?: BleConnectOptionsExt) => dm.connectDevice(device.deviceId, options), disconnect: (device) => dm.disconnectDevice(device.deviceId), autoConnect: () => Promise.resolve({ serviceId: '', writeCharId: '', notifyCharId: '' }) }; const wrapper = new ProtocolHandlerWrapper(raw, service); activeHandler = wrapper; activeProtocol = raw.protocol as BleProtocolType; ensureConnectionHook(); console.log('[AKBLE][Harmony] default protocol handler registered', activeProtocol); } catch (e) { console.warn('[AKBLE][Harmony] register default protocol handler failed', e); } } export const setDefaultBluetoothService = (service: BluetoothService) => { defaultBluetoothService = service; ensureDefaultProtocolHandler(); };