279 lines
11 KiB
Plaintext
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);
|
|
} |