import type { BleService, BleCharacteristic, BleDataReceivedCallback, BleCharacteristicProperties, WriteCharacteristicOptions, ByteArray } from '../interface.uts' import BluetoothGatt from "android.bluetooth.BluetoothGatt"; import BluetoothGattService from "android.bluetooth.BluetoothGattService"; import BluetoothGattCharacteristic from "android.bluetooth.BluetoothGattCharacteristic"; import BluetoothGattDescriptor from "android.bluetooth.BluetoothGattDescriptor"; import BluetoothGattCallback from "android.bluetooth.BluetoothGattCallback"; import UUID from "java.util.UUID"; import { DeviceManager } from './device_manager.uts' import { AkBleErrorImpl, AkBluetoothErrorCode } from '../unierror.uts' import { AutoDiscoverAllResult } from '../interface.uts' // 补全UUID格式,将短格式转换为标准格式 function getFullUuid(shortUuid : string) : string { return `0000${shortUuid}-0000-1000-8000-00805f9b34fb` } const deviceWriteQueues = new Map>(); function enqueueDeviceWrite(deviceId : string, work : () => Promise) : Promise { const previous = deviceWriteQueues.get(deviceId) ?? Promise.resolve(); const next = (async () : Promise => { try { await previous; } catch (e) { /* ignore previous rejection to keep queue alive */ } return await work(); })(); const queued = next.then(() => { }, () => { }); deviceWriteQueues.set(deviceId, queued); return next.finally(() => { if (deviceWriteQueues.get(deviceId) == queued) { deviceWriteQueues.delete(deviceId); } }); } function createCharProperties(props : number) : BleCharacteristicProperties { const result : BleCharacteristicProperties = { read: false, write: false, notify: false, indicate: false, canRead: false, canWrite: false, canNotify: false, writeWithoutResponse: false }; result.read = (props & BluetoothGattCharacteristic.PROPERTY_READ) !== 0; result.write = (props & BluetoothGattCharacteristic.PROPERTY_WRITE) !== 0; result.notify = (props & BluetoothGattCharacteristic.PROPERTY_NOTIFY) !== 0; result.indicate = (props & BluetoothGattCharacteristic.PROPERTY_INDICATE) !== 0; result.writeWithoutResponse = (props & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) !== 0; result.canRead = result.read; const writeWithoutResponse = result.writeWithoutResponse; result.canWrite = (result.write != null && result.write) || (writeWithoutResponse != null && writeWithoutResponse); result.canNotify = result.notify; return result; } // 定义 PendingCallback 类型和实现类 interface PendingCallback { resolve : (data : any) => void; reject : (err ?: any) => void; timer ?: number; // Changed from any to number } class PendingCallbackImpl implements PendingCallback { resolve : (data : any) => void; reject : (err ?: any) => void; timer ?: number; // Changed from any to number constructor(resolve : (data : any) => void, reject : (err ?: any) => void, timer ?: number) { this.resolve = resolve; this.reject = reject; this.timer = timer; } } // 全局回调管理(必须在类外部声明) let pendingCallbacks : Map; let notifyCallbacks : Map; // 在全局范围内初始化 pendingCallbacks = new Map(); notifyCallbacks = new Map(); // 服务发现等待队列:deviceId -> 回调数组 const serviceDiscoveryWaiters = new Map void)[]>(); // 服务发现状态:deviceId -> 是否已发现 const serviceDiscovered = new Map(); // 特征发现等待队列:deviceId|serviceId -> 回调数组 const characteristicDiscoveryWaiters = new Map void)[]>(); class GattCallback extends BluetoothGattCallback { constructor() { super(); } override onServicesDiscovered(gatt : BluetoothGatt, status : Int) : void { console.log('ak onServicesDiscovered') const deviceId = gatt.getDevice().getAddress(); if (status == BluetoothGatt.GATT_SUCCESS) { console.log(`服务发现成功: ${deviceId}`); serviceDiscovered.set(deviceId, true); // 统一回调所有等待 getServices 的调用 const waiters = serviceDiscoveryWaiters.get(deviceId); if (waiters != null && waiters.length > 0) { const services = gatt.getServices(); const result : BleService[] = []; if (services != null) { const servicesList = services; const size = servicesList.size; for (let i = 0; i < size; i++) { const service = servicesList.get(i as Int); if (service != null) { const bleService : BleService = { uuid: service.getUuid().toString(), isPrimary: service.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY }; result.push(bleService); } } } for (let i = 0; i < waiters.length; i++) { const cb = waiters[i]; if (cb != null) { cb(result, null); } } serviceDiscoveryWaiters.delete(deviceId); } } else { console.log(`服务发现失败: ${deviceId}, status: ${status}`); // 失败时也要通知等待队列 const waiters = serviceDiscoveryWaiters.get(deviceId); if (waiters != null && waiters.length > 0) { for (let i = 0; i < waiters.length; i++) { const cb = waiters[i]; if (cb != null) { cb(null, new Error('服务发现失败')); } } serviceDiscoveryWaiters.delete(deviceId); } } } override onConnectionStateChange(gatt : BluetoothGatt, status : Int, newState : Int) : void { const deviceId = gatt.getDevice().getAddress(); if (newState == BluetoothGatt.STATE_CONNECTED) { console.log(`设备已连接: ${deviceId}`); DeviceManager.handleConnectionStateChange(deviceId, 2, null); // 2 = STATE_CONNECTED } else if (newState == BluetoothGatt.STATE_DISCONNECTED) { console.log(`设备已断开: ${deviceId}`); serviceDiscovered.delete(deviceId); DeviceManager.handleConnectionStateChange(deviceId, 0, null); // 0 = STATE_DISCONNECTED } } override onCharacteristicChanged(gatt : BluetoothGatt, characteristic : BluetoothGattCharacteristic) : void { console.log('ak onCharacteristicChanged') const deviceId = gatt.getDevice().getAddress(); const serviceId = characteristic.getService().getUuid().toString(); const charId = characteristic.getUuid().toString(); const key = `${deviceId}|${serviceId}|${charId}|notify`; const callback = notifyCallbacks.get(key); const value = characteristic.getValue(); console.log('[onCharacteristicChanged]', key, value); if (callback != null && value != null) { const valueLength = value.size; const arr = new Uint8Array(valueLength); for (let i = 0 as Int; i < valueLength; i++) { const v = value[i as Int]; arr[i] = v != null ? v : 0; } // 保存接收日志 console.log(` INSERT INTO ble_data_log (device_id, service_id, char_id, direction, data, timestamp) VALUES ('${deviceId}', '${serviceId}', '${charId}', 'recv', '${Array.from(arr).join(',')}', ${Date.now()}) `) callback(arr); } } override onCharacteristicRead(gatt : BluetoothGatt, characteristic : BluetoothGattCharacteristic, status : Int) : void { console.log('ak onCharacteristicRead', status) const deviceId = gatt.getDevice().getAddress(); const serviceId = characteristic.getService().getUuid().toString(); const charId = characteristic.getUuid().toString(); const key = `${deviceId}|${serviceId}|${charId}|read`; const pending = pendingCallbacks.get(key); const value = characteristic.getValue(); console.log('[onCharacteristicRead]', key, 'status=', status, 'value=', value); if (pending != null) { try { const timer = pending.timer; if (timer != null) { clearTimeout(timer); pending.timer = null; } pendingCallbacks.delete(key); if (status == BluetoothGatt.GATT_SUCCESS && value != null) { const valueLength = value.size const arr = new Uint8Array(valueLength); for (let i = 0 as Int; i < valueLength; i++) { const v = value[i as Int]; arr[i] = v != null ? v : 0; } // resolve with ArrayBuffer pending.resolve(arr.buffer as ArrayBuffer); } else { pending.reject(new Error('Characteristic read failed')); } } catch (e) { try { pending.reject(e); } catch (e2) { console.error(e2); } } } } override onCharacteristicWrite(gatt : BluetoothGatt, characteristic : BluetoothGattCharacteristic, status : Int) : void { console.log('ak onCharacteristicWrite', status) const deviceId = gatt.getDevice().getAddress(); const serviceId = characteristic.getService().getUuid().toString(); const charId = characteristic.getUuid().toString(); const key = `${deviceId}|${serviceId}|${charId}|write`; const pending = pendingCallbacks.get(key); console.log('[onCharacteristicWrite]', key, 'status=', status); if (pending != null) { try { const timer = pending.timer; if (timer != null) { clearTimeout(timer); } pendingCallbacks.delete(key); if (status == BluetoothGatt.GATT_SUCCESS) { pending.resolve('ok'); } else { pending.reject(new Error('Characteristic write failed')); } } catch (e) { try { pending.reject(e); } catch (e2) { console.error(e2); } } } } } // 导出单例实例供外部使用 export const gattCallback = new GattCallback(); export class ServiceManager { private static instance : ServiceManager | null = null; private services = new Map(); private characteristics = new Map>(); private deviceManager = DeviceManager.getInstance(); private constructor() { } static getInstance() : ServiceManager { if (ServiceManager.instance == null) { ServiceManager.instance = new ServiceManager(); } return ServiceManager.instance!; } getServices(deviceId : string, callback ?: (services : BleService[] | null, error ?: Error) => void) : any | Promise { console.log('ak start getservice', deviceId); const gatt = this.deviceManager.getGattInstance(deviceId); if (gatt == null) { if (callback != null) { callback(null, new AkBleErrorImpl(AkBluetoothErrorCode.DeviceNotFound, "Device not found", "")); } return Promise.reject(new AkBleErrorImpl(AkBluetoothErrorCode.DeviceNotFound, "Device not found", "")); } console.log('ak serviceDiscovered', gatt) // 如果服务已发现,直接返回 if (serviceDiscovered.get(deviceId) == true) { const services = gatt.getServices(); console.log(services) const result : BleService[] = []; if (services != null) { const servicesList = services; const size = servicesList.size; if (size > 0) { for (let i = 0 as Int; i < size; i++) { const service = servicesList != null ? servicesList.get(i) : servicesList[i]; if (service != null) { const bleService : BleService = { uuid: service.getUuid().toString(), isPrimary: service.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY }; result.push(bleService); if (bleService.uuid == getFullUuid('0001')) { const device = this.deviceManager.getDevice(deviceId); if (device != null) { device.serviceId = bleService.uuid; this.getCharacteristics(deviceId, device.serviceId, (chars, err) => { if (err == null && chars != null) { const writeChar = chars.find(c => c.uuid == getFullUuid('0010')); const notifyChar = chars.find(c => c.uuid == getFullUuid('0011')); if (writeChar != null) device.writeCharId = writeChar.uuid; if (notifyChar != null) device.notifyCharId = notifyChar.uuid; } }); } } } } } } if (callback != null) { callback(result, null); } return Promise.resolve(result); } // 未发现则发起服务发现并加入等待队列 if (!serviceDiscoveryWaiters.has(deviceId)) { serviceDiscoveryWaiters.set(deviceId, []); gatt.discoverServices(); } return new Promise((resolve, reject) => { const cb = (services : BleService[] | null, error ?: Error) => { if (error != null) reject(error); else resolve(services ?? []); if (callback != null) callback(services, error); }; const arr = serviceDiscoveryWaiters.get(deviceId); if (arr != null) arr.push(cb); }); } getCharacteristics(deviceId : string, serviceId : string, callback : (characteristics : BleCharacteristic[] | null, error ?: Error) => void) : void { const gatt = this.deviceManager.getGattInstance(deviceId); if (gatt == null) return callback(null, new AkBleErrorImpl(AkBluetoothErrorCode.DeviceNotFound, "Device not found", "")); // 如果服务还没发现,等待服务发现后再查特征 if (serviceDiscovered.get(deviceId) !== true) { // 先注册到服务发现等待队列 this.getServices(deviceId, (services, err) => { if (err != null) { callback(null, err); } else { this.getCharacteristics(deviceId, serviceId, callback); } }); return; } // 服务已发现,正常获取特征 const service = gatt.getService(UUID.fromString(serviceId)); if (service == null) return callback(null, new AkBleErrorImpl(AkBluetoothErrorCode.ServiceNotFound, "Service not found", "")); const chars = service.getCharacteristics(); console.log(chars) const result : BleCharacteristic[] = []; if (chars != null) { const characteristicsList = chars; const size = characteristicsList.size; const bleService : BleService = { uuid: serviceId, isPrimary: service.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY }; for (let i = 0 as Int; i < size; i++) { const char = characteristicsList != null ? characteristicsList.get(i as Int) : characteristicsList[i]; if (char != null) { const props = char.getProperties(); try { const charUuid = char.getUuid() != null ? char.getUuid().toString() : ''; console.log('[ServiceManager] characteristic uuid=', charUuid); } catch (e) { console.warn('[ServiceManager] failed to read char uuid', e); } console.log(props); const bleCharacteristic : BleCharacteristic = { uuid: char.getUuid().toString(), service: bleService, properties: createCharProperties(props) }; result.push(bleCharacteristic); } } } callback(result, null); } public async readCharacteristic(deviceId : string, serviceId : string, characteristicId : string) : Promise { const gatt = this.deviceManager.getGattInstance(deviceId); if (gatt == null) throw new AkBleErrorImpl(AkBluetoothErrorCode.DeviceNotFound, "Device not found", ""); const service = gatt.getService(UUID.fromString(serviceId)); if (service == null) throw new AkBleErrorImpl(AkBluetoothErrorCode.ServiceNotFound, "Service not found", ""); const char = service.getCharacteristic(UUID.fromString(characteristicId)); if (char == null) throw new AkBleErrorImpl(AkBluetoothErrorCode.CharacteristicNotFound, "Characteristic not found", ""); const key = `${deviceId}|${serviceId}|${characteristicId}|read`; console.log(key) return new Promise((resolve, reject) => { const timer = setTimeout(() => { pendingCallbacks.delete(key); reject(new AkBleErrorImpl(AkBluetoothErrorCode.ConnectionTimeout, "Connection timeout", "")); }, 5000); const resolveAdapter = (data : any) => { console.log('read resolve:', data); resolve(data as ArrayBuffer); }; const rejectAdapter = (err ?: any) => { reject(new AkBleErrorImpl(AkBluetoothErrorCode.UnknownError, "Unknown error occurred", "")); }; pendingCallbacks.set(key, new PendingCallbackImpl(resolveAdapter, rejectAdapter, timer)); if (gatt.readCharacteristic(char) == false) { clearTimeout(timer); pendingCallbacks.delete(key); reject(new AkBleErrorImpl(AkBluetoothErrorCode.UnknownError, "Unknown error occurred", "")); } else { console.log('read should be succeed', key) } }); } public async writeCharacteristic(deviceId : string, serviceId : string, characteristicId : string, data : Uint8Array, options ?: WriteCharacteristicOptions) : Promise { console.log('[writeCharacteristic] deviceId:', deviceId, 'serviceId:', serviceId, 'characteristicId:', characteristicId, 'data:', data); const gatt = this.deviceManager.getGattInstance(deviceId); if (gatt == null) { console.error('[writeCharacteristic] gatt is null'); throw new AkBleErrorImpl(AkBluetoothErrorCode.DeviceNotFound, "Device not found", ""); } const service = gatt.getService(UUID.fromString(serviceId)); if (service == null) { console.error('[writeCharacteristic] service is null'); throw new AkBleErrorImpl(AkBluetoothErrorCode.ServiceNotFound, "Service not found", ""); } const char = service.getCharacteristic(UUID.fromString(characteristicId)); if (char == null) { console.error('[writeCharacteristic] characteristic is null'); throw new AkBleErrorImpl(AkBluetoothErrorCode.CharacteristicNotFound, "Characteristic not found", ""); } const key = `${deviceId}|${serviceId}|${characteristicId}|write`; const wantsNoResponse = options != null && options.waitForResponse == false; let retryMaxAttempts = 20; let retryDelay = 100; let giveupTimeout = 20000; if (options != null) { try { if (options.maxAttempts != null) { const parsedAttempts = Math.floor(options.maxAttempts as number); if (!isNaN(parsedAttempts) && parsedAttempts > 0) retryMaxAttempts = parsedAttempts; } } catch (e) { } try { if (options.retryDelayMs != null) { const parsedDelay = Math.floor(options.retryDelayMs as number); if (!isNaN(parsedDelay) && parsedDelay >= 0) retryDelay = parsedDelay; } } catch (e) { } try { if (options.giveupTimeoutMs != null) { const parsedGiveup = Math.floor(options.giveupTimeoutMs as number); if (!isNaN(parsedGiveup) && parsedGiveup > 0) giveupTimeout = parsedGiveup; } } catch (e) { } } const gattInstance = gatt; const executeWrite = () : Promise => { return new Promise((resolve) => { const initialTimeout = Math.max(giveupTimeout + 5000, 10000); let timer = setTimeout(() => { pendingCallbacks.delete(key); console.error('[writeCharacteristic] timeout'); resolve(false); }, initialTimeout); console.log('[writeCharacteristic] initial timeout set to', initialTimeout, 'ms for', key); const resolveAdapter = (data : any) => { console.log('[writeCharacteristic] resolveAdapter called'); resolve(true); }; const rejectAdapter = (err ?: any) => { console.error('[writeCharacteristic] rejectAdapter called', err); resolve(false); }; pendingCallbacks.set(key, new PendingCallbackImpl(resolveAdapter, rejectAdapter, timer)); const byteArray = new ByteArray(data.length as Int); for (let i = 0 as Int; i < data.length; i++) { byteArray[i] = data[i].toByte(); } const forceWriteTypeNoResponse = options != null && options.forceWriteTypeNoResponse == true; let usesNoResponse = forceWriteTypeNoResponse || wantsNoResponse; try { const props = char.getProperties(); console.log('[writeCharacteristic] characteristic properties mask=', props); if (usesNoResponse == false) { const supportsWriteWithResponse = (props & BluetoothGattCharacteristic.PROPERTY_WRITE) !== 0; const supportsWriteNoResponse = (props & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) !== 0; if (supportsWriteWithResponse == false && supportsWriteNoResponse == true) { usesNoResponse = true; } } if (usesNoResponse) { try { char.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); } catch (e) { } console.log('[writeCharacteristic] using WRITE_TYPE_NO_RESPONSE'); } else { try { char.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); } catch (e) { } console.log('[writeCharacteristic] using WRITE_TYPE_DEFAULT'); } } catch (e) { console.warn('[writeCharacteristic] failed to inspect/set write type', e); } const maxAttempts = retryMaxAttempts; function attemptWrite(att : Int) : void { try { let setOk = true; try { const setRes = char.setValue(byteArray); if (typeof setRes == 'boolean' && setRes == false) { setOk = false; console.warn('[writeCharacteristic] setValue returned false for', key, 'attempt', att); } } catch (e) { setOk = false; console.warn('[writeCharacteristic] setValue threw for', key, 'attempt', att, e); } if (setOk == false) { if (att >= maxAttempts) { try { clearTimeout(timer); } catch (e) { } pendingCallbacks.delete(key); resolve(false); return; } setTimeout(() => { attemptWrite((att + 1) as Int); }, retryDelay); return; } try { console.log('[writeCharacteristic] attempt', att, 'calling gatt.writeCharacteristic'); const r = gattInstance.writeCharacteristic(char); console.log('[writeCharacteristic] attempt', att, 'result=', r); if (r == true) { if (usesNoResponse) { console.log('[writeCharacteristic] WRITE_TYPE_NO_RESPONSE success for', key); try { clearTimeout(timer); } catch (e) { } pendingCallbacks.delete(key); resolve(true); return; } try { clearTimeout(timer); } catch (e) { } const extra = 20000; timer = setTimeout(() => { pendingCallbacks.delete(key); console.error('[writeCharacteristic] timeout after write initiated'); resolve(false); }, extra); const pendingEntry = pendingCallbacks.get(key); if (pendingEntry != null) pendingEntry.timer = timer; return; } } catch (e) { console.error('[writeCharacteristic] attempt', att, 'exception when calling writeCharacteristic', e); } if (att < maxAttempts) { const nextAtt = (att + 1) as Int; setTimeout(() => { attemptWrite(nextAtt); }, retryDelay); return; } if (usesNoResponse) { try { clearTimeout(timer); } catch (e) { } pendingCallbacks.delete(key); console.warn('[writeCharacteristic] all attempts failed with WRITE_NO_RESPONSE for', key); resolve(false); return; } try { clearTimeout(timer); } catch (e) { } const giveupTimeoutLocal = giveupTimeout; console.warn('[writeCharacteristic] all attempts failed; waiting for late callback up to', giveupTimeoutLocal, 'ms for', key); const giveupTimer = setTimeout(() => { pendingCallbacks.delete(key); console.error('[writeCharacteristic] giveup timeout expired for', key); resolve(false); }, giveupTimeoutLocal); const pendingEntryAfter = pendingCallbacks.get(key); if (pendingEntryAfter != null) pendingEntryAfter.timer = giveupTimer; } catch (e) { clearTimeout(timer); pendingCallbacks.delete(key); console.error('[writeCharacteristic] Exception in attemptWrite', e); resolve(false); } } try { attemptWrite(1 as Int); } catch (e) { clearTimeout(timer); pendingCallbacks.delete(key); console.error('[writeCharacteristic] Exception before attempting write', e); resolve(false); } }); }; return enqueueDeviceWrite(deviceId, executeWrite); } public async subscribeCharacteristic(deviceId : string, serviceId : string, characteristicId : string, onData : BleDataReceivedCallback) : Promise { const gatt = this.deviceManager.getGattInstance(deviceId); if (gatt == null) throw new AkBleErrorImpl(AkBluetoothErrorCode.DeviceNotFound, "Device not found", ""); const service = gatt.getService(UUID.fromString(serviceId)); if (service == null) throw new AkBleErrorImpl(AkBluetoothErrorCode.ServiceNotFound, "Service not found", ""); const char = service.getCharacteristic(UUID.fromString(characteristicId)); if (char == null) throw new AkBleErrorImpl(AkBluetoothErrorCode.CharacteristicNotFound, "Characteristic not found", ""); const key = `${deviceId}|${serviceId}|${characteristicId}|notify`; notifyCallbacks.set(key, onData); if (gatt.setCharacteristicNotification(char, true) == false) { notifyCallbacks.delete(key); throw new AkBleErrorImpl(AkBluetoothErrorCode.UnknownError, "Failed to unsubscribe characteristic", ""); } else { // 写入 CCCD 描述符,启用 notify const descriptor = char.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); if (descriptor != null) { // 设置描述符值 const value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; descriptor.setValue(value); const writedescript = gatt.writeDescriptor(descriptor); console.log('subscribeCharacteristic: CCCD written for notify', writedescript); } else { console.warn('subscribeCharacteristic: CCCD descriptor not found!'); } console.log('subscribeCharacteristic ok!!'); } } public async unsubscribeCharacteristic(deviceId : string, serviceId : string, characteristicId : string) : Promise { const gatt = this.deviceManager.getGattInstance(deviceId); if (gatt == null) throw new AkBleErrorImpl(AkBluetoothErrorCode.DeviceNotFound, "Device not found", ""); const service = gatt.getService(UUID.fromString(serviceId)); if (service == null) throw new AkBleErrorImpl(AkBluetoothErrorCode.ServiceNotFound, "Service not found", ""); const char = service.getCharacteristic(UUID.fromString(characteristicId)); if (char == null) throw new AkBleErrorImpl(AkBluetoothErrorCode.CharacteristicNotFound, "Characteristic not found", ""); const key = `${deviceId}|${serviceId}|${characteristicId}|notify`; notifyCallbacks.delete(key); if (gatt.setCharacteristicNotification(char, false) == false) { throw new AkBleErrorImpl(AkBluetoothErrorCode.UnknownError, "Failed to unsubscribe characteristic", ""); } } // 自动发现所有服务和特征 public async autoDiscoverAll(deviceId : string) : Promise { const services = await this.getServices(deviceId, null) as BleService[]; const allCharacteristics : BleCharacteristic[] = []; for (const service of services) { await new Promise((resolve, reject) => { this.getCharacteristics(deviceId, service.uuid, (chars, err) => { if (err != null) reject(err); else { if (chars != null) allCharacteristics.push(...chars); resolve(); } }); }); } return { services, characteristics: allCharacteristics }; } // 自动订阅所有支持 notify/indicate 的特征 public async subscribeAllNotifications(deviceId : string, onData : BleDataReceivedCallback) : Promise { const { services, characteristics } = await this.autoDiscoverAll(deviceId); for (const char of characteristics) { if (char.properties.notify || char.properties.indicate) { try { await this.subscribeCharacteristic(deviceId, char.service.uuid, char.uuid, onData); } catch (e) { // 可以选择忽略单个特征订阅失败 console.warn(`订阅特征 ${char.uuid} 失败:`, e); } } } } }