import type { BleService, BleCharacteristic, BleDataReceivedCallback, BleCharacteristicProperties, WriteCharacteristicOptions, AutoDiscoverAllResult } from '../interface.uts'; import { CBPeripheral, CBService, CBCharacteristic, CBCharacteristicWriteType } from 'CoreBluetooth'; import { Data, NSError } from 'Foundation'; import { DeviceManager } from './device_manager.uts'; function toUint8Array(value: Uint8Array | ArrayBuffer): Uint8Array { if (value instanceof Uint8Array) return value; return new Uint8Array(value); } function dataToUint8Array(data: Data | null): Uint8Array { if (data == null) return new Uint8Array(0); const base64 = data.base64EncodedString(options = 0); if (base64 == null) return new Uint8Array(0); const raw = atob(base64); const out = new Uint8Array(raw.length); for (let i = 0; i < raw.length; i++) { out[i] = raw.charCodeAt(i) & 0xff; } return out; } function uint8ArrayToData(bytes: Uint8Array): Data { if (bytes.length == 0) { return new Data(); } let binary = ''; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } const base64 = btoa(binary); const data = new Data(base64Encoded = base64); return data != null ? data! : new Data(); } function iterateNSArray(collection: any, handler: (item: T | null) => void) { if (collection == null) return; if (Array.isArray(collection)) { for (let i = 0; i < collection.length; i++) { handler(collection[i] as T); } return; } try { const count = collection.count as number; if (typeof count === 'number') { for (let i = 0; i < count; i++) { const item = collection.objectAtIndex(i); handler(item as T); } return; } } catch (e) { } try { const len = collection.length as number; if (typeof len === 'number') { for (let i = 0; i < len; i++) { handler(collection[i] as T); } } } catch (e2) { } } type PendingCallback = { resolve: (data: any) => void; reject: (err?: any) => void; timer?: number; }; function makeCharProperties(flags: number): BleCharacteristicProperties { const read = (flags & 0x02) != 0; const write = (flags & 0x08) != 0; const notify = (flags & 0x10) != 0; const indicate = (flags & 0x20) != 0; const writeNoRsp = (flags & 0x04) != 0; return { read, write, notify, indicate, writeWithoutResponse: writeNoRsp, canRead: read, canWrite: write || writeNoRsp, canNotify: notify || indicate }; } function getCharPropertiesValue(characteristic: CBCharacteristic): number { try { const anyProps = characteristic.properties as any; if (anyProps != null && anyProps.rawValue != null) { return Number(anyProps.rawValue); } } catch (e) { } try { return Number((characteristic as any).properties); } catch (e2) { } return 0; } export class ServiceManager { private static instance: ServiceManager | null = null; private services = new Map(); private characteristics = new Map>(); private serviceWaiters = new Map void)[]>(); private characteristicWaiters = new Map void)[]>(); private pendingReads = new Map(); private pendingWrites = new Map(); private notifyCallbacks = new Map(); private deviceManager = DeviceManager.getInstance(); private constructor() {} static getInstance(): ServiceManager { if (ServiceManager.instance == null) { ServiceManager.instance = new ServiceManager(); } return ServiceManager.instance!; } resetDiscoveryState(deviceId: string) { this.services.delete(deviceId); this.characteristics.forEach((_value, key) => { if (key.startsWith(deviceId + '|')) { this.characteristics.delete(key); } }); } handleServicesDiscovered(peripheral: CBPeripheral, error: NSError | null) { const deviceId = peripheral.identifier.UUIDString; if (error != null) { const err = new Error('服务发现失败: ' + error.localizedDescription); this.resolveServiceWaiters(deviceId, null, err); return; } const list: BleService[] = []; const native = peripheral.services; iterateNSArray(native, (svc) => { if (svc == null) return; const uuid = svc.UUID.UUIDString; list.push({ uuid, isPrimary: svc.isPrimary }); }); this.services.set(deviceId, list); this.resolveServiceWaiters(deviceId, list, null); } handleCharacteristicsDiscovered(peripheral: CBPeripheral, service: CBService, error: NSError | null) { const deviceId = peripheral.identifier.UUIDString; const serviceId = service.UUID.UUIDString; const key = this.characteristicKey(deviceId, serviceId); if (error != null) { const err = new Error('特征发现失败: ' + error.localizedDescription); this.resolveCharacteristicWaiters(key, null, err); return; } const list: BleCharacteristic[] = []; const chars = service.characteristics; iterateNSArray(chars, (ch) => { if (ch == null) return; const propsValue = getCharPropertiesValue(ch); const props = makeCharProperties(propsValue); list.push({ uuid: ch.UUID.UUIDString, service: { uuid: serviceId, isPrimary: service.isPrimary }, properties: props }); }); let map = this.characteristics.get(deviceId); if (map == null) { map = new Map(); this.characteristics.set(deviceId, map); } map.set(serviceId, list); this.resolveCharacteristicWaiters(key, list, null); } handleCharacteristicValueUpdated(peripheral: CBPeripheral, characteristic: CBCharacteristic, error: NSError | null) { const deviceId = peripheral.identifier.UUIDString; const serviceId = characteristic.service.UUID.UUIDString; const charId = characteristic.UUID.UUIDString; const notifyKey = this.notifyKey(deviceId, serviceId, charId); const readKey = this.operationKey(deviceId, serviceId, charId, 'read'); if (error != null) { const pending = this.pendingReads.get(readKey); if (pending != null) { this.pendingReads.delete(readKey); try { pending.reject(error); } catch (e) { } } return; } const bytes = dataToUint8Array(characteristic.value); const pending = this.pendingReads.get(readKey); if (pending != null) { this.pendingReads.delete(readKey); if (pending.timer != null) clearTimeout(pending.timer); try { pending.resolve(bytes.buffer as ArrayBuffer); } catch (e) { } } const cb = this.notifyCallbacks.get(notifyKey); if (cb != null) { try { cb(bytes); } catch (e) { } } } handleCharacteristicWrite(peripheral: CBPeripheral, characteristic: CBCharacteristic, error: NSError | null) { const deviceId = peripheral.identifier.UUIDString; const serviceId = characteristic.service.UUID.UUIDString; const charId = characteristic.UUID.UUIDString; const writeKey = this.operationKey(deviceId, serviceId, charId, 'write'); const pending = this.pendingWrites.get(writeKey); if (pending == null) return; this.pendingWrites.delete(writeKey); if (pending.timer != null) clearTimeout(pending.timer); if (error != null) { try { pending.reject(error); } catch (e) { } } else { try { pending.resolve(true); } catch (e) { } } } handleNotificationState(peripheral: CBPeripheral, characteristic: CBCharacteristic, error: NSError | null) { if (error != null) { console.warn('[AKBLE][iOS] notify state change error', error.localizedDescription); } } getServices(deviceId: string, callback?: (services: BleService[] | null, error?: Error) => void): Promise { const cached = this.services.get(deviceId); if (cached != null && cached.length > 0) { if (callback != null) callback(cached, null); return Promise.resolve(cached); } return new Promise((resolve, reject) => { this.enqueueServiceWaiter(deviceId, (list, err) => { if (err != null || list == null) { if (callback != null) callback(null, err ?? new Error('服务获取失败')); reject(err ?? new Error('服务获取失败')); } else { if (callback != null) callback(list, null); resolve(list); } }); const peripheral = this.deviceManager.getPeripheral(deviceId); if (peripheral == null) { const err = new Error('设备未连接'); this.resolveServiceWaiters(deviceId, null, err); return; } try { peripheral.discoverServices(serviceUUIDs = null); } catch (e) { const err = e instanceof Error ? e : new Error('服务发现失败'); this.resolveServiceWaiters(deviceId, null, err); } }); } getCharacteristics(deviceId: string, serviceId: string, callback?: (list: BleCharacteristic[] | null, error?: Error) => void): Promise { const cached = this.characteristics.get(deviceId)?.get(serviceId) ?? null; if (cached != null && cached.length > 0) { if (callback != null) callback(cached, null); return Promise.resolve(cached); } return new Promise((resolve, reject) => { const key = this.characteristicKey(deviceId, serviceId); this.enqueueCharacteristicWaiter(key, (list, err) => { if (err != null || list == null) { if (callback != null) callback(null, err ?? new Error('特征获取失败')); reject(err ?? new Error('特征获取失败')); } else { if (callback != null) callback(list, null); resolve(list); } }); const peripheral = this.deviceManager.getPeripheral(deviceId); if (peripheral == null) { const err = new Error('设备未连接'); this.resolveCharacteristicWaiters(key, null, err); return; } const service = this.findNativeService(peripheral, serviceId); if (service == null) { const err = new Error('未找到服务'); this.resolveCharacteristicWaiters(key, null, err); return; } try { peripheral.discoverCharacteristics(characteristicUUIDs = null, forService = service); } catch (e) { const err = e instanceof Error ? e : new Error('特征发现失败'); this.resolveCharacteristicWaiters(key, null, err); } }); } async readCharacteristic(deviceId: string, serviceId: string, characteristicId: string): Promise { const peripheral = this.deviceManager.getPeripheral(deviceId); if (peripheral == null) throw new Error('设备未连接'); const characteristic = this.findNativeCharacteristic(peripheral, serviceId, characteristicId); if (characteristic == null) throw new Error('未找到特征值'); return new Promise((resolve, reject) => { const key = this.operationKey(deviceId, serviceId, characteristicId, 'read'); const timer = setTimeout(() => { this.pendingReads.delete(key); reject(new Error('读取超时')); }, 10000); this.pendingReads.set(key, { resolve, reject, timer }); try { peripheral.readValueForCharacteristic(characteristic); } catch (e) { clearTimeout(timer); this.pendingReads.delete(key); reject(e); } }); } async writeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, value: Uint8Array | ArrayBuffer, options?: WriteCharacteristicOptions): Promise { const peripheral = this.deviceManager.getPeripheral(deviceId); if (peripheral == null) throw new Error('设备未连接'); const characteristic = this.findNativeCharacteristic(peripheral, serviceId, characteristicId); if (characteristic == null) throw new Error('未找到特征值'); const payload = uint8ArrayToData(toUint8Array(value)); const waitForResponse = options?.waitForResponse ?? true; const key = this.operationKey(deviceId, serviceId, characteristicId, 'write'); if (!waitForResponse) { try { peripheral.writeValue(payload, forCharacteristic = characteristic, type = CBCharacteristicWriteType.withoutResponse); return true; } catch (e) { throw e; } } return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.pendingWrites.delete(key); reject(new Error('写入超时')); }, options?.giveupTimeoutMs ?? 10000); this.pendingWrites.set(key, { resolve, reject, timer }); try { peripheral.writeValue(payload, forCharacteristic = characteristic, type = CBCharacteristicWriteType.withResponse); } catch (e) { clearTimeout(timer); this.pendingWrites.delete(key); reject(e); } }); } async subscribeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, callback: BleDataReceivedCallback): Promise { const peripheral = this.deviceManager.getPeripheral(deviceId); if (peripheral == null) throw new Error('设备未连接'); const characteristic = this.findNativeCharacteristic(peripheral, serviceId, characteristicId); if (characteristic == null) throw new Error('未找到特征值'); const key = this.notifyKey(deviceId, serviceId, characteristicId); this.notifyCallbacks.set(key, callback); try { peripheral.setNotifyValue(true, forCharacteristic = characteristic); } catch (e) { this.notifyCallbacks.delete(key); throw e; } } async unsubscribeCharacteristic(deviceId: string, serviceId: string, characteristicId: string): Promise { const peripheral = this.deviceManager.getPeripheral(deviceId); if (peripheral == null) return; const characteristic = this.findNativeCharacteristic(peripheral, serviceId, characteristicId); if (characteristic == null) return; const key = this.notifyKey(deviceId, serviceId, characteristicId); this.notifyCallbacks.delete(key); try { peripheral.setNotifyValue(false, forCharacteristic = characteristic); } catch (e) { console.warn('[AKBLE][iOS] unsubscribe failed', e); } } async autoDiscoverAll(deviceId: string): Promise { const services = await this.getServices(deviceId); const allCharacteristics: BleCharacteristic[] = []; for (let i = 0; i < services.length; i++) { const svc = services[i]; const chars = await this.getCharacteristics(deviceId, svc.uuid); for (let j = 0; j < chars.length; j++) { allCharacteristics.push(chars[j]); } } return { services, characteristics: allCharacteristics }; } async subscribeAllNotifications(deviceId: string, callback: BleDataReceivedCallback): Promise { const services = await this.getServices(deviceId); for (let i = 0; i < services.length; i++) { const svc = services[i]; const chars = await this.getCharacteristics(deviceId, svc.uuid); for (let j = 0; j < chars.length; j++) { const charDef = chars[j]; if (charDef.properties != null && (charDef.properties.notify || charDef.properties.indicate)) { try { await this.subscribeCharacteristic(deviceId, svc.uuid, charDef.uuid, callback); } catch (e) { console.warn('[AKBLE][iOS] subscribeAllNotifications failed', svc.uuid, charDef.uuid, e); } } } } } private enqueueServiceWaiter(deviceId: string, waiter: (list: BleService[] | null, error?: Error) => void) { let queue = this.serviceWaiters.get(deviceId); if (queue == null) { queue = []; this.serviceWaiters.set(deviceId, queue); } queue.push(waiter); } private enqueueCharacteristicWaiter(key: string, waiter: (list: BleCharacteristic[] | null, error?: Error) => void) { let queue = this.characteristicWaiters.get(key); if (queue == null) { queue = []; this.characteristicWaiters.set(key, queue); } queue.push(waiter); } private resolveServiceWaiters(deviceId: string, list: BleService[] | null, error: Error | null) { const queue = this.serviceWaiters.get(deviceId); if (queue == null) return; this.serviceWaiters.delete(deviceId); for (let i = 0; i < queue.length; i++) { const waiter = queue[i]; try { waiter(list, error ?? undefined); } catch (e) { } } } private resolveCharacteristicWaiters(key: string, list: BleCharacteristic[] | null, error: Error | null) { const queue = this.characteristicWaiters.get(key); if (queue == null) return; this.characteristicWaiters.delete(key); for (let i = 0; i < queue.length; i++) { const waiter = queue[i]; try { waiter(list, error ?? undefined); } catch (e) { } } } private findNativeService(peripheral: CBPeripheral, serviceId: string): CBService | null { const services = peripheral.services; let found: CBService | null = null; iterateNSArray(services, (svc) => { if (found != null) return; if (svc != null && svc.UUID.UUIDString == serviceId) found = svc; }); if (found != null) return found; return null; } private findNativeCharacteristic(peripheral: CBPeripheral, serviceId: string, characteristicId: string): CBCharacteristic | null { const service = this.findNativeService(peripheral, serviceId); if (service == null) return null; const chars = service.characteristics; let found: CBCharacteristic | null = null; iterateNSArray(chars, (ch) => { if (found != null) return; if (ch != null && ch.UUID.UUIDString == characteristicId) found = ch; }); if (found != null) return found; return null; } private notifyKey(deviceId: string, serviceId: string, charId: string): string { return `${deviceId}|${serviceId}|${charId}|notify`; } private operationKey(deviceId: string, serviceId: string, charId: string, op: string): string { return `${deviceId}|${serviceId}|${charId}|${op}`; } private characteristicKey(deviceId: string, serviceId: string): string { return `${deviceId}|${serviceId}`; } }