Initial commit

This commit is contained in:
2026-03-16 10:37:46 +08:00
commit c052a67816
508 changed files with 22987 additions and 0 deletions

View File

@@ -0,0 +1,663 @@
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<string, Promise<void>>();
function enqueueDeviceWrite<T>(deviceId : string, work : () => Promise<T>) : Promise<T> {
const previous = deviceWriteQueues.get(deviceId) ?? Promise.resolve();
const next = (async () : Promise<T> => {
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<string, PendingCallback>;
let notifyCallbacks : Map<string, BleDataReceivedCallback>;
// 在全局范围内初始化
pendingCallbacks = new Map<string, PendingCallback>();
notifyCallbacks = new Map<string, BleDataReceivedCallback>();
// 服务发现等待队列deviceId -> 回调数组
const serviceDiscoveryWaiters = new Map<string, ((services : BleService[] | null, error ?: Error) => void)[]>();
// 服务发现状态deviceId -> 是否已发现
const serviceDiscovered = new Map<string, boolean>();
// 特征发现等待队列deviceId|serviceId -> 回调数组
const characteristicDiscoveryWaiters = new Map<string, ((characteristics : BleCharacteristic[] | null, error ?: Error) => 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<string, BleService[]>();
private characteristics = new Map<string, Map<string, BleCharacteristic[]>>();
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<BleService[]> {
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<BleService[]>((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<ArrayBuffer> {
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<ArrayBuffer>((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<boolean> {
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<boolean> => {
return new Promise<boolean>((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<void> {
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<void> {
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<AutoDiscoverAllResult> {
const services = await this.getServices(deviceId, null) as BleService[];
const allCharacteristics : BleCharacteristic[] = [];
for (const service of services) {
await new Promise<void>((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<void> {
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);
}
}
}
}
}