Initial commit of akmon project
This commit is contained in:
815
uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts
Normal file
815
uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts
Normal file
@@ -0,0 +1,815 @@
|
||||
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: any) { /* 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 {
|
||||
override resolve: (data: any) => void;
|
||||
override reject: (err?: any) => void;
|
||||
override 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 -> 尝试次数
|
||||
const serviceDiscoveryAttempts = new Map<string, number>();
|
||||
const SERVICE_DISCOVERY_MAX_RETRIES = 3;
|
||||
const SERVICE_DISCOVERY_RETRY_DELAY_MS = 600;
|
||||
// 特征发现等待队列: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 {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:112', 'ak onServicesDiscovered',gatt);
|
||||
const deviceId = gatt.getDevice().getAddress();
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
const attempt = serviceDiscoveryAttempts.get(deviceId) ?? 0;
|
||||
const services = gatt.getServices();
|
||||
const result: BleService[] = [];
|
||||
let size = 0;
|
||||
if (services != null) {
|
||||
const servicesList = services;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:133', '[ServiceManager] onServicesDiscovered size=', size, 'attempt=', attempt, 'device=', deviceId);
|
||||
if (result.length == 0 && attempt < SERVICE_DISCOVERY_MAX_RETRIES) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:135', '[ServiceManager] services empty after discovery, retrying', deviceId, 'nextAttempt=', attempt + 1);
|
||||
serviceDiscoveryAttempts.set(deviceId, attempt + 1);
|
||||
serviceDiscovered.delete(deviceId);
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const dm = DeviceManager.getInstance();
|
||||
const currentGatt = dm.getGattInstance(deviceId);
|
||||
const target = currentGatt != null ? currentGatt : gatt;
|
||||
if (target != null) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:144', '[ServiceManager] retry discoverServices for', deviceId);
|
||||
target.discoverServices();
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:148', '[ServiceManager] retry discoverServices failed', e);
|
||||
}
|
||||
}, SERVICE_DISCOVERY_RETRY_DELAY_MS);
|
||||
return;
|
||||
}
|
||||
let finalResult: BleService[] | null = result;
|
||||
if (result.length == 0) {
|
||||
const cached = ServiceManager.getInstance().getCachedServices(deviceId);
|
||||
if (cached != null && cached.length > 0) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:154', '[ServiceManager] discovery returned empty, using cached services for', deviceId, 'len=', cached.length);
|
||||
finalResult = cached;
|
||||
} else {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:154', '[ServiceManager] discovery returned empty after retries for', deviceId);
|
||||
serviceDiscoveryAttempts.delete(deviceId);
|
||||
const waitersFail = serviceDiscoveryWaiters.get(deviceId);
|
||||
if (waitersFail != null && waitersFail.length > 0) {
|
||||
for (let i = 0; i < waitersFail.length; i++) {
|
||||
const cb = waitersFail[i];
|
||||
if (cb != null) {
|
||||
cb(null, new Error('服务发现返回空列表'));
|
||||
}
|
||||
}
|
||||
serviceDiscoveryWaiters.delete(deviceId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
serviceDiscoveryAttempts.delete(deviceId);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:154', `服务发现成功: ${deviceId}, services=${finalResult != null ? finalResult.length : 0}`);
|
||||
serviceDiscovered.set(deviceId, true);
|
||||
ServiceManager.getInstance().handleServicesDiscovered(deviceId, finalResult ?? []);
|
||||
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(finalResult ?? [], null);
|
||||
}
|
||||
}
|
||||
serviceDiscoveryWaiters.delete(deviceId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:166', `服务发现失败: ${deviceId}, status: ${status}`);
|
||||
serviceDiscoveryAttempts.delete(deviceId);
|
||||
// 失败时也要通知等待队列
|
||||
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) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:182', `设备已连接: ${deviceId}`);
|
||||
ServiceManager.getInstance().resetDiscoveryState(deviceId);
|
||||
DeviceManager.handleConnectionStateChange(deviceId, 2, null); // 2 = STATE_CONNECTED
|
||||
}
|
||||
else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:186', `设备已断开: ${deviceId}`);
|
||||
serviceDiscovered.delete(deviceId);
|
||||
serviceDiscoveryAttempts.delete(deviceId);
|
||||
ServiceManager.getInstance().handleDisconnected(deviceId);
|
||||
DeviceManager.handleConnectionStateChange(deviceId, 0, null); // 0 = STATE_DISCONNECTED
|
||||
}
|
||||
}
|
||||
override onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic): void {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:194', '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();
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:201', '[onCharacteristicChanged]', key, value);
|
||||
// Check for PING packets (0xAA 0x04 0x00 ...)
|
||||
if (value != null && value.size >= 4) {
|
||||
const arr = new Uint8Array(value.size);
|
||||
for (let i = 0 as Int; i < value.size; i++) {
|
||||
const v = value[i as Int];
|
||||
arr[i] = v != null ? v : 0;
|
||||
}
|
||||
if (arr[0] === 0xAA && arr[2] === 0x00) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:201', '[BLE] PING packet detected:', Array.from(arr));
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 保存接收日志
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:210', `
|
||||
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 {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:219', '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();
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:226', '[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;
|
||||
}
|
||||
// debug: log raw bytes and decoded string (helpful on Android native path)
|
||||
try {
|
||||
const hex = Array.from(arr).map((b): string => b.toString(16).padStart(2, '0')).join(' ');
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:246', '[ServiceManager] onCharacteristicRead raw hex:', hex, 'len=', arr.length, 'key=', key);
|
||||
try {
|
||||
const decoded = new TextDecoder().decode(arr);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:249', '[ServiceManager] onCharacteristicRead decoded string:', decoded);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:250', '[ServiceManager] decode error', e);
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:251', '[ServiceManager] failed to log read buffer', e);
|
||||
}
|
||||
// resolve with ArrayBuffer
|
||||
pending.resolve(arr.buffer as ArrayBuffer);
|
||||
}
|
||||
else {
|
||||
pending.reject(new Error('Characteristic read failed'));
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
try {
|
||||
pending.reject(e);
|
||||
}
|
||||
catch (e2: any) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:259', e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int): void {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:265', '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);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:271', '[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: any) {
|
||||
try {
|
||||
pending.reject(e);
|
||||
}
|
||||
catch (e2: any) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:285', 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!;
|
||||
}
|
||||
public resetDiscoveryState(deviceId: string, clearServiceCache: boolean = false): void {
|
||||
serviceDiscoveryAttempts.delete(deviceId);
|
||||
serviceDiscovered.delete(deviceId);
|
||||
serviceDiscoveryWaiters.delete(deviceId);
|
||||
if (clearServiceCache == true) {
|
||||
this.services.delete(deviceId);
|
||||
}
|
||||
}
|
||||
public handleServicesDiscovered(deviceId: string, services: BleService[]): void {
|
||||
this.services.set(deviceId, services);
|
||||
}
|
||||
public getCachedServices(deviceId: string): BleService[] | null {
|
||||
const cached = this.services.get(deviceId);
|
||||
return cached != null ? cached : null;
|
||||
}
|
||||
public handleDisconnected(deviceId: string): void {
|
||||
this.resetDiscoveryState(deviceId);
|
||||
const keysToRemove: string[] = [];
|
||||
this.characteristics.forEach((_value, key) => {
|
||||
if (key != null && key.indexOf(deviceId + '|') == 0) {
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < keysToRemove.length; i++) {
|
||||
this.characteristics.delete(keysToRemove[i]);
|
||||
}
|
||||
}
|
||||
getServices(deviceId: string, callback?: (services: BleService[] | null, error?: Error) => void): any | Promise<BleService[]> {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts', '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", ""));
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts', 'ak serviceDiscovered', gatt);
|
||||
// 如果服务已发现,直接返回
|
||||
if (serviceDiscovered.get(deviceId) == true) {
|
||||
const services = gatt.getServices();
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:333', 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): boolean => c.uuid == getFullUuid('0010'));
|
||||
const notifyChar = chars.find((c): boolean => 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)) {
|
||||
console.log('ak should start serviceDiscoveryWaiters')
|
||||
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();
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:403', 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() : '';
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:418', '[ServiceManager] characteristic uuid=', charUuid);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:419', '[ServiceManager] failed to read char uuid', e);
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:420', 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`;
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:441', 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) => { __f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:447', '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 {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:456', 'read should be succeed', key);
|
||||
}
|
||||
});
|
||||
}
|
||||
public async writeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, data: Uint8Array, options?: WriteCharacteristicOptions): Promise<boolean> {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:462', '[writeCharacteristic] deviceId:', deviceId, 'serviceId:', serviceId, 'characteristicId:', characteristicId, 'data:', data);
|
||||
const gatt = this.deviceManager.getGattInstance(deviceId);
|
||||
if (gatt == null) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:465', '[writeCharacteristic] gatt is null');
|
||||
throw new AkBleErrorImpl(AkBluetoothErrorCode.DeviceNotFound, "Device not found", "");
|
||||
}
|
||||
const service = gatt.getService(UUID.fromString(serviceId));
|
||||
if (service == null) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:470', '[writeCharacteristic] service is null');
|
||||
throw new AkBleErrorImpl(AkBluetoothErrorCode.ServiceNotFound, "Service not found", "");
|
||||
}
|
||||
const char = service.getCharacteristic(UUID.fromString(characteristicId));
|
||||
if (char == null) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:475', '[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: any) { }
|
||||
try {
|
||||
if (options.retryDelayMs != null) {
|
||||
const parsedDelay = Math.floor(options.retryDelayMs as number);
|
||||
if (!isNaN(parsedDelay) && parsedDelay >= 0)
|
||||
retryDelay = parsedDelay;
|
||||
}
|
||||
}
|
||||
catch (e: any) { }
|
||||
try {
|
||||
if (options.giveupTimeoutMs != null) {
|
||||
const parsedGiveup = Math.floor(options.giveupTimeoutMs as number);
|
||||
if (!isNaN(parsedGiveup) && parsedGiveup > 0)
|
||||
giveupTimeout = parsedGiveup;
|
||||
}
|
||||
}
|
||||
catch (e: any) { }
|
||||
}
|
||||
const gattInstance = gatt;
|
||||
const executeWrite = (): Promise<boolean> => {
|
||||
return new Promise<boolean>((resolve, _reject) => {
|
||||
const initialTimeout = Math.max(giveupTimeout + 5000, 10000);
|
||||
let timer = setTimeout(() => {
|
||||
pendingCallbacks.delete(key);
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:510', '[writeCharacteristic] timeout');
|
||||
resolve(false);
|
||||
}, initialTimeout);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:513', '[writeCharacteristic] initial timeout set to', initialTimeout, 'ms for', key);
|
||||
const resolveAdapter = (data: any) => {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:515', '[writeCharacteristic] resolveAdapter called');
|
||||
resolve(true);
|
||||
};
|
||||
const rejectAdapter = (err?: any) => {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:519', '[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 {
|
||||
if (usesNoResponse == false) {
|
||||
const props = char.getProperties();
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:532', '[writeCharacteristic] characteristic properties mask=', props);
|
||||
usesNoResponse = (props & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) !== 0;
|
||||
}
|
||||
if (usesNoResponse) {
|
||||
try {
|
||||
char.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
|
||||
}
|
||||
catch (e: any) { }
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:537', '[writeCharacteristic] using WRITE_TYPE_NO_RESPONSE');
|
||||
}
|
||||
else {
|
||||
try {
|
||||
char.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
|
||||
}
|
||||
catch (e: any) { }
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:540', '[writeCharacteristic] using WRITE_TYPE_DEFAULT');
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:543', '[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;
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:553', '[writeCharacteristic] setValue returned false for', key, 'attempt', att);
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
setOk = false;
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:557', '[writeCharacteristic] setValue threw for', key, 'attempt', att, e);
|
||||
}
|
||||
if (setOk == false) {
|
||||
if (att >= maxAttempts) {
|
||||
try {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
catch (e: any) { }
|
||||
pendingCallbacks.delete(key);
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => { attemptWrite((att + 1) as Int); }, retryDelay);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:570', '[writeCharacteristic] attempt', att, 'calling gatt.writeCharacteristic');
|
||||
const r = gattInstance.writeCharacteristic(char);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:572', '[writeCharacteristic] attempt', att, 'result=', r);
|
||||
if (r == true) {
|
||||
if (usesNoResponse) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:575', '[writeCharacteristic] WRITE_TYPE_NO_RESPONSE success for', key);
|
||||
try {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
catch (e: any) { }
|
||||
pendingCallbacks.delete(key);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
catch (e: any) { }
|
||||
const extra = 20000;
|
||||
timer = setTimeout(() => {
|
||||
pendingCallbacks.delete(key);
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:585', '[writeCharacteristic] timeout after write initiated');
|
||||
resolve(false);
|
||||
}, extra);
|
||||
const pendingEntry = pendingCallbacks.get(key);
|
||||
if (pendingEntry != null)
|
||||
pendingEntry.timer = timer;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:593', '[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: any) { }
|
||||
pendingCallbacks.delete(key);
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:603', '[writeCharacteristic] all attempts failed with WRITE_NO_RESPONSE for', key);
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
catch (e: any) { }
|
||||
const giveupTimeoutLocal = giveupTimeout;
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:609', '[writeCharacteristic] all attempts failed; waiting for late callback up to', giveupTimeoutLocal, 'ms for', key);
|
||||
const giveupTimer = setTimeout(() => {
|
||||
pendingCallbacks.delete(key);
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:612', '[writeCharacteristic] giveup timeout expired for', key);
|
||||
resolve(false);
|
||||
}, giveupTimeoutLocal);
|
||||
const pendingEntryAfter = pendingCallbacks.get(key);
|
||||
if (pendingEntryAfter != null)
|
||||
pendingEntryAfter.timer = giveupTimer;
|
||||
}
|
||||
catch (e: any) {
|
||||
clearTimeout(timer);
|
||||
pendingCallbacks.delete(key);
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:620', '[writeCharacteristic] Exception in attemptWrite', e);
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
try {
|
||||
attemptWrite(1 as Int);
|
||||
}
|
||||
catch (e: any) {
|
||||
clearTimeout(timer);
|
||||
pendingCallbacks.delete(key);
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:630', '[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);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:660', 'subscribeCharacteristic: CCCD written for notify', writedescript);
|
||||
}
|
||||
else {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:662', 'subscribeCharacteristic: CCCD descriptor not found!');
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:664', '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(void 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return { services, characteristics: allCharacteristics } as AutoDiscoverAllResult;
|
||||
}
|
||||
// 自动订阅所有支持 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: any) {
|
||||
// 可以选择忽略单个特征订阅失败
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:710', `订阅特征 ${char.uuid} 失败:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=service_manager.uts.map
|
||||
Reference in New Issue
Block a user