import type { BleService, BleCharacteristic, BleDataReceivedCallback, BleCharacteristicProperties, WriteCharacteristicOptions, AutoDiscoverAllResult } from '../interface.uts'; import type { BleDevice } from '../interface.uts'; import { DeviceManager } from './device_manager.uts'; declare const wx: any; type PendingRead = { resolve: (data: ArrayBuffer) => void; reject: (err?: any) => void; timer?: number; }; function toUint8Array(buffer: ArrayBuffer): Uint8Array { return new Uint8Array(buffer ?? new ArrayBuffer(0)); } function toArrayBuffer(bytes: Uint8Array | ArrayBuffer): ArrayBuffer { return bytes instanceof Uint8Array ? bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) : (bytes ?? new ArrayBuffer(0)); } function makeProperties(item: any): BleCharacteristicProperties { const props = item?.properties ?? {}; const read = props.read === true; const write = props.write === true; const notify = props.notify === true; const indicate = props.indicate === true; const writeNoRsp = props.writeNoResponse === true || props.writeWithoutResponse === true; return { read, write, notify, indicate, writeWithoutResponse: writeNoRsp, canRead: read, canWrite: write || writeNoRsp, canNotify: notify || indicate }; } export class ServiceManager { private static instance: ServiceManager | null = null; private services = new Map(); private characteristics = new Map>(); private pendingReads = new Map(); private notifyCallbacks = new Map(); private listenersRegistered: boolean = false; private deviceManager = DeviceManager.getInstance(); private constructor() {} static getInstance(): ServiceManager { if (ServiceManager.instance == null) { ServiceManager.instance = new ServiceManager(); } return ServiceManager.instance!; } private ensureListeners() { if (this.listenersRegistered) return; this.listenersRegistered = true; wx.onBLECharacteristicValueChange((res: any) => { try { this.handleNotify(res); } catch (e) { } }); } private cacheKey(deviceId: string, serviceId: string): string { return `${deviceId}|${serviceId}`; } private notifyKey(deviceId: string, serviceId: string, characteristicId: string): string { return `${deviceId}|${serviceId}|${characteristicId}`; } async 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 cached; } return new Promise((resolve, reject) => { wx.getBLEDeviceServices({ deviceId, success: (res: any) => { const list: BleService[] = []; const services: any[] = res?.services ?? []; for (let i = 0; i < services.length; i++) { const svc = services[i]; if (svc == null) continue; list.push({ uuid: svc.uuid, isPrimary: svc.isPrimary === true }); } this.services.set(deviceId, list); if (callback != null) callback(list, null); resolve(list); }, fail: (err: any) => { const error = err ?? new Error('getBLEDeviceServices failed'); if (callback != null) callback(null, error); reject(error); } }); }); } async getCharacteristics(deviceId: string, serviceId: string, callback?: (list: BleCharacteristic[] | null, error?: Error) => void): Promise { const map = this.characteristics.get(deviceId); const cached = map != null ? map.get(serviceId) : null; if (cached != null && cached.length > 0) { if (callback != null) callback(cached, null); return cached; } return new Promise((resolve, reject) => { wx.getBLEDeviceCharacteristics({ deviceId, serviceId, success: (res: any) => { const list: BleCharacteristic[] = []; const chars: any[] = res?.characteristics ?? []; for (let i = 0; i < chars.length; i++) { const ch = chars[i]; if (ch == null) continue; list.push({ uuid: ch.uuid, service: { uuid: serviceId, isPrimary: true }, properties: makeProperties(ch) }); } let mapRef = this.characteristics.get(deviceId); if (mapRef == null) { mapRef = new Map(); this.characteristics.set(deviceId, mapRef); } mapRef.set(serviceId, list); if (callback != null) callback(list, null); resolve(list); }, fail: (err: any) => { const error = err ?? new Error('getBLEDeviceCharacteristics failed'); if (callback != null) callback(null, error); reject(error); } }); }); } async readCharacteristic(deviceId: string, serviceId: string, characteristicId: string): Promise { this.ensureListeners(); return new Promise((resolve, reject) => { const key = `${deviceId}|${serviceId}|${characteristicId}|read`; const timer = setTimeout(() => { this.pendingReads.delete(key); reject(new Error('读取超时')); }, 10000); this.pendingReads.set(key, { resolve, reject, timer }); wx.readBLECharacteristicValue({ deviceId, serviceId, characteristicId, fail: (err: any) => { this.pendingReads.delete(key); clearTimeout(timer); reject(err ?? new Error('readBLECharacteristicValue failed')); } }); }); } async writeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, value: Uint8Array | ArrayBuffer, options?: WriteCharacteristicOptions): Promise { const buffer = toArrayBuffer(value); return new Promise((resolve, reject) => { wx.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: buffer, fail: (err: any) => { reject(err ?? new Error('writeBLECharacteristicValue failed')); }, success: () => { resolve(true); } }); }); } async subscribeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, callback: BleDataReceivedCallback): Promise { this.ensureListeners(); const key = this.notifyKey(deviceId, serviceId, characteristicId); this.notifyCallbacks.set(key, callback); return new Promise((resolve, reject) => { wx.notifyBLECharacteristicValueChange({ deviceId, serviceId, characteristicId, state: true, success: () => resolve(), fail: (err: any) => { this.notifyCallbacks.delete(key); reject(err ?? new Error('notifyBLECharacteristicValueChange failed')); } }); }); } async unsubscribeCharacteristic(deviceId: string, serviceId: string, characteristicId: string): Promise { const key = this.notifyKey(deviceId, serviceId, characteristicId); this.notifyCallbacks.delete(key); return new Promise((resolve, reject) => { wx.notifyBLECharacteristicValueChange({ deviceId, serviceId, characteristicId, state: false, success: () => resolve(), fail: (err: any) => { reject(err ?? new Error('notifyBLECharacteristicValueChange disable failed')); } }); }); } async autoDiscoverAll(deviceId: string): Promise { const services = await this.getServices(deviceId); const allChars: 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++) { allChars.push(chars[j]); } } return { services, characteristics: allChars }; } 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 ch = chars[j]; if (ch.properties != null && (ch.properties.notify || ch.properties.indicate)) { try { await this.subscribeCharacteristic(deviceId, svc.uuid, ch.uuid, callback); } catch (e) { } } } } } private handleNotify(res: any) { const deviceId = res?.deviceId ?? ''; const serviceId = res?.serviceId ?? ''; const characteristicId = res?.characteristicId ?? ''; const key = `${deviceId}|${serviceId}|${characteristicId}|read`; const buffer: ArrayBuffer = res?.value ?? new ArrayBuffer(0); const pending = this.pendingReads.get(key); if (pending != null) { this.pendingReads.delete(key); if (pending.timer != null) clearTimeout(pending.timer); try { pending.resolve(buffer); } catch (e) { } } const notifyKey = this.notifyKey(deviceId, serviceId, characteristicId); const cb = this.notifyCallbacks.get(notifyKey); if (cb != null) { try { cb(toUint8Array(buffer)); } catch (e) { } } } }