Initial commit
This commit is contained in:
279
uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts
Normal file
279
uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts
Normal file
@@ -0,0 +1,279 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user