Initial commit
This commit is contained in:
276
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts
vendored
Normal file
276
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts
vendored
Normal file
@@ -0,0 +1,276 @@
|
||||
import type { BleDevice, BleConnectionState, BleEvent, BleEventCallback, BleEventPayload, BleScanResult, BleConnectOptionsExt, AutoBleInterfaces, BleDataPayload, SendDataPayload, BleOptions, MultiProtocolDevice, ScanHandler, BleProtocolType, ScanDevicesOptions } from '../interface.uts';
|
||||
import { ProtocolHandler } from '../protocol_handler.uts';
|
||||
import { BluetoothService } from '../interface.uts';
|
||||
import { DeviceManager } from './device_manager.uts';
|
||||
// Shape used when callers register plain objects as handlers. Using a named
|
||||
// type keeps member access explicit so the code generator emits valid Kotlin
|
||||
// member references instead of trying to access properties on Any.
|
||||
type RawProtocolHandler = {
|
||||
protocol?: BleProtocolType;
|
||||
scanDevices?: (options?: ScanDevicesOptions) => Promise<void>;
|
||||
connect?: (device: BleDevice, options?: BleConnectOptionsExt) => Promise<void>;
|
||||
disconnect?: (device: BleDevice) => Promise<void>;
|
||||
sendData?: (device: BleDevice, payload?: SendDataPayload, options?: BleOptions) => Promise<void>;
|
||||
autoConnect?: (device: BleDevice, options?: BleConnectOptionsExt) => Promise<AutoBleInterfaces>;
|
||||
};
|
||||
// 设备上下文
|
||||
class DeviceContext {
|
||||
device: BleDevice;
|
||||
protocol: BleProtocolType;
|
||||
state: BleConnectionState;
|
||||
handler: ProtocolHandler;
|
||||
constructor(device: BleDevice, protocol: BleProtocolType, handler: ProtocolHandler) {
|
||||
this.device = device;
|
||||
this.protocol = protocol;
|
||||
this.state = 0; // DISCONNECTED
|
||||
this.handler = handler;
|
||||
}
|
||||
}
|
||||
const deviceMap = new Map<string, DeviceContext>(); // key: deviceId|protocol
|
||||
// Single active protocol handler (no multi-protocol registration)
|
||||
let activeProtocol: BleProtocolType = 'standard';
|
||||
let activeHandler: ProtocolHandler | null = null;
|
||||
// 事件监听注册表
|
||||
const eventListeners = new Map<BleEvent, Set<BleEventCallback>>();
|
||||
function emit(event: BleEvent, payload: BleEventPayload) {
|
||||
if (event === 'connectionStateChanged') {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts:57', '[AKBLE][LOG] bluetooth_manager.uts emit connectionStateChanged', payload);
|
||||
}
|
||||
const listeners = eventListeners.get(event);
|
||||
if (listeners != null) {
|
||||
listeners.forEach(cb => {
|
||||
try {
|
||||
cb(payload);
|
||||
}
|
||||
catch (e: any) { }
|
||||
});
|
||||
}
|
||||
}
|
||||
class ProtocolHandlerWrapper extends ProtocolHandler {
|
||||
private _raw: RawProtocolHandler | null;
|
||||
constructor(raw?: RawProtocolHandler) {
|
||||
// pass a lightweight BluetoothService instance to satisfy generators
|
||||
super(new BluetoothService());
|
||||
this._raw = (raw != null) ? raw : null;
|
||||
}
|
||||
override async scanDevices(options?: ScanDevicesOptions): Promise<void> {
|
||||
const rawTyped = this._raw;
|
||||
if (rawTyped != null && typeof rawTyped.scanDevices === 'function') {
|
||||
await rawTyped.scanDevices!(options);
|
||||
}
|
||||
return;
|
||||
}
|
||||
override async connect(device: BleDevice, options?: BleConnectOptionsExt): Promise<void> {
|
||||
const rawTyped = this._raw;
|
||||
if (rawTyped != null && typeof rawTyped.connect === 'function') {
|
||||
await rawTyped.connect!(device, options);
|
||||
}
|
||||
return;
|
||||
}
|
||||
override async disconnect(device: BleDevice): Promise<void> {
|
||||
const rawTyped = this._raw;
|
||||
if (rawTyped != null && typeof rawTyped.disconnect === 'function') {
|
||||
await rawTyped.disconnect!(device);
|
||||
}
|
||||
return;
|
||||
}
|
||||
override async sendData(device: BleDevice, payload?: SendDataPayload, options?: BleOptions): Promise<void> {
|
||||
const rawTyped = this._raw;
|
||||
if (rawTyped != null && typeof rawTyped.sendData === 'function') {
|
||||
await rawTyped.sendData!(device, payload, options);
|
||||
}
|
||||
return;
|
||||
}
|
||||
override async autoConnect(device: BleDevice, options?: BleConnectOptionsExt): Promise<AutoBleInterfaces> {
|
||||
const rawTyped = this._raw;
|
||||
if (rawTyped != null && typeof rawTyped.autoConnect === 'function') {
|
||||
return await rawTyped.autoConnect!(device, options);
|
||||
}
|
||||
return { serviceId: '', writeCharId: '', notifyCharId: '' } as AutoBleInterfaces;
|
||||
}
|
||||
}
|
||||
// Strong runtime detector for plain object handlers (no Type Predicate)
|
||||
// Note: the UTS bundler doesn't support TypeScript type predicates (x is T),
|
||||
// and it doesn't accept the 'unknown' type. This returns a boolean and
|
||||
// callers must cast the value to RawProtocolHandler after the function
|
||||
// returns true.
|
||||
function isRawProtocolHandler(x: any): boolean {
|
||||
if (x == null || typeof x !== 'object')
|
||||
return false;
|
||||
const r = x as Record<string, unknown>;
|
||||
if (typeof r['scanDevices'] === 'function')
|
||||
return true;
|
||||
if (typeof r['connect'] === 'function')
|
||||
return true;
|
||||
if (typeof r['disconnect'] === 'function')
|
||||
return true;
|
||||
if (typeof r['sendData'] === 'function')
|
||||
return true;
|
||||
if (typeof r['autoConnect'] === 'function')
|
||||
return true;
|
||||
if (typeof r['protocol'] === 'string')
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
export const registerProtocolHandler = (handler: any) => {
|
||||
if (handler == null)
|
||||
return;
|
||||
// Determine protocol value defensively. Default to 'standard' when unknown.
|
||||
let proto: BleProtocolType = 'standard';
|
||||
if (handler instanceof ProtocolHandler) {
|
||||
try {
|
||||
proto = (handler as ProtocolHandler).protocol as BleProtocolType;
|
||||
}
|
||||
catch (e: any) { }
|
||||
activeHandler = handler as ProtocolHandler;
|
||||
}
|
||||
else if (isRawProtocolHandler(handler)) {
|
||||
try {
|
||||
proto = (handler as RawProtocolHandler).protocol as BleProtocolType;
|
||||
}
|
||||
catch (e: any) { }
|
||||
activeHandler = new ProtocolHandlerWrapper(handler as RawProtocolHandler);
|
||||
(activeHandler as ProtocolHandler).protocol = proto;
|
||||
}
|
||||
else {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts:139', '[AKBLE] registerProtocolHandler: unsupported handler type, ignoring', handler);
|
||||
return;
|
||||
}
|
||||
activeProtocol = proto;
|
||||
};
|
||||
export const scanDevices = async (options?: ScanDevicesOptions): Promise<void> => {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts:147', '[AKBLE] start scan', options);
|
||||
// Determine which protocols to run: either user-specified or all registered
|
||||
// Single active handler flow
|
||||
if (activeHandler == null) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts:151', '[AKBLE] no active scan handler registered');
|
||||
return;
|
||||
}
|
||||
const handler = activeHandler as ProtocolHandler;
|
||||
const scanOptions: ScanDevicesOptions = {
|
||||
onDeviceFound: (device: BleDevice) => emit('deviceFound', { event: 'deviceFound', device } as BleEventPayload),
|
||||
onScanFinished: () => emit('scanFinished', { event: 'scanFinished' } as BleEventPayload)
|
||||
};
|
||||
try {
|
||||
await handler.scanDevices(scanOptions);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts:162', '[AKBLE] scanDevices handler error', e);
|
||||
}
|
||||
};
|
||||
export const connectDevice = async (deviceId: string, protocol: BleProtocolType, options?: BleConnectOptionsExt): Promise<void> => {
|
||||
const handler = activeHandler;
|
||||
if (handler == null)
|
||||
throw new Error('No protocol handler');
|
||||
const device: BleDevice = { deviceId, name: '', rssi: 0 }; // 可扩展
|
||||
await handler.connect(device, options);
|
||||
const ctx = new DeviceContext(device, protocol, handler);
|
||||
ctx.state = 2; // CONNECTED
|
||||
deviceMap.set(getDeviceKey(deviceId, protocol), ctx);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts:175', deviceMap);
|
||||
emit('connectionStateChanged', { event: 'connectionStateChanged', device, protocol, state: 2 } as BleEventPayload);
|
||||
};
|
||||
export const disconnectDevice = async (deviceId: string, protocol: BleProtocolType): Promise<void> => {
|
||||
const ctx = deviceMap.get(getDeviceKey(deviceId, protocol));
|
||||
if (ctx == null || ctx.handler == null)
|
||||
return;
|
||||
await ctx.handler.disconnect(ctx.device);
|
||||
ctx.state = 0;
|
||||
emit('connectionStateChanged', { event: 'connectionStateChanged', device: ctx.device, protocol, state: 0 } as BleEventPayload);
|
||||
deviceMap.delete(getDeviceKey(deviceId, protocol));
|
||||
};
|
||||
export const sendData = async (payload: SendDataPayload, options?: BleOptions): Promise<void> => {
|
||||
const ctx = deviceMap.get(getDeviceKey(payload.deviceId, payload.protocol));
|
||||
if (ctx == null)
|
||||
throw new Error('Device not connected');
|
||||
// copy to local non-null variable so generator can smart-cast across awaits
|
||||
const deviceCtx = ctx as DeviceContext;
|
||||
if (deviceCtx.handler == null)
|
||||
throw new Error('sendData not supported for this protocol');
|
||||
await deviceCtx.handler.sendData(deviceCtx.device, payload, options);
|
||||
emit('dataSent', { event: 'dataSent', device: deviceCtx.device, protocol: payload.protocol, data: payload.data } as BleEventPayload);
|
||||
};
|
||||
export const getConnectedDevices = (): MultiProtocolDevice[] => {
|
||||
const result: MultiProtocolDevice[] = [];
|
||||
deviceMap.forEach((ctx: DeviceContext) => {
|
||||
const dev: MultiProtocolDevice = {
|
||||
deviceId: ctx.device.deviceId,
|
||||
name: ctx.device.name,
|
||||
rssi: ctx.device.rssi,
|
||||
protocol: ctx.protocol
|
||||
};
|
||||
result.push(dev);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
export const getConnectionState = (deviceId: string, protocol: BleProtocolType): BleConnectionState => {
|
||||
const ctx = deviceMap.get(getDeviceKey(deviceId, protocol));
|
||||
if (ctx == null)
|
||||
return 0;
|
||||
return ctx.state;
|
||||
};
|
||||
export const on = (event: BleEvent, callback: BleEventCallback) => {
|
||||
if (!eventListeners.has(event))
|
||||
eventListeners.set(event, new Set());
|
||||
eventListeners.get(event)!.add(callback);
|
||||
};
|
||||
export const off = (event: BleEvent, callback?: BleEventCallback) => {
|
||||
if (callback == null) {
|
||||
eventListeners.delete(event);
|
||||
}
|
||||
else {
|
||||
eventListeners.get(event)?.delete(callback as BleEventCallback);
|
||||
}
|
||||
};
|
||||
function getDeviceKey(deviceId: string, protocol: BleProtocolType): string {
|
||||
return `${deviceId}|${protocol}`;
|
||||
}
|
||||
export const autoConnect = async (deviceId: string, protocol: BleProtocolType, options?: BleConnectOptionsExt): Promise<AutoBleInterfaces> => {
|
||||
const handler = activeHandler;
|
||||
if (handler == null)
|
||||
throw new Error('autoConnect not supported for this protocol');
|
||||
const device: BleDevice = { deviceId, name: '', rssi: 0 };
|
||||
// safe call - handler.autoConnect exists on ProtocolHandler
|
||||
return await handler.autoConnect(device, options) as AutoBleInterfaces;
|
||||
};
|
||||
// Ensure there is at least one handler registered so callers can scan/connect
|
||||
// without needing to import a registry module. This creates a minimal default
|
||||
// ProtocolHandler backed by a BluetoothService instance.
|
||||
try {
|
||||
if (activeHandler == null) {
|
||||
// Create a DeviceManager-backed raw handler that delegates to native code
|
||||
const _dm = DeviceManager.getInstance();
|
||||
const _raw: RawProtocolHandler = {
|
||||
protocol: 'standard',
|
||||
scanDevices: (options?: ScanDevicesOptions): Promise<void> => {
|
||||
try {
|
||||
const scanOptions = options != null ? options : {} as ScanDevicesOptions;
|
||||
_dm.startScan(scanOptions);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts:256', '[AKBLE] DeviceManager.startScan failed', e);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
connect: (device, options?: BleConnectOptionsExt): Promise<void> => {
|
||||
return _dm.connectDevice(device.deviceId, options);
|
||||
},
|
||||
disconnect: (device): Promise<void> => {
|
||||
return _dm.disconnectDevice(device.deviceId);
|
||||
},
|
||||
autoConnect: (device, options?: any): Promise<AutoBleInterfaces> => {
|
||||
// DeviceManager does not provide an autoConnect helper; return default
|
||||
const result: AutoBleInterfaces = { serviceId: '', writeCharId: '', notifyCharId: '' };
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
};
|
||||
const _wrapper = new ProtocolHandlerWrapper(_raw);
|
||||
activeHandler = _wrapper;
|
||||
activeProtocol = _raw.protocol as BleProtocolType;
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts:275', '[AKBLE] default protocol handler (BluetoothService-backed) registered', activeProtocol);
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/bluetooth_manager.uts:278', '[AKBLE] failed to register default protocol handler', e);
|
||||
}
|
||||
//# sourceMappingURL=bluetooth_manager.uts.map
|
||||
File diff suppressed because one or more lines are too long
301
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts
vendored
Normal file
301
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts
vendored
Normal file
@@ -0,0 +1,301 @@
|
||||
import type { BleDevice, BleOptions, BleConnectionState, BleConnectionStateChangeCallback } from '../interface.uts';
|
||||
import type { BleConnectOptionsExt } from '../interface.uts';
|
||||
import type { ScanDevicesOptions } from '../interface.uts';
|
||||
import Context from "android.content.Context";
|
||||
import BluetoothAdapter from "android.bluetooth.BluetoothAdapter";
|
||||
import BluetoothManager from "android.bluetooth.BluetoothManager";
|
||||
import BluetoothDevice from "android.bluetooth.BluetoothDevice";
|
||||
import BluetoothGatt from "android.bluetooth.BluetoothGatt";
|
||||
import BluetoothGattCallback from "android.bluetooth.BluetoothGattCallback";
|
||||
import ScanCallback from "android.bluetooth.le.ScanCallback";
|
||||
import ScanResult from "android.bluetooth.le.ScanResult";
|
||||
import ScanSettings from "android.bluetooth.le.ScanSettings";
|
||||
import Handler from "android.os.Handler";
|
||||
import Looper from "android.os.Looper";
|
||||
import ContextCompat from "androidx.core.content.ContextCompat";
|
||||
import PackageManager from "android.content.pm.PackageManager";
|
||||
// 定义 PendingConnect 类型和实现类
|
||||
interface PendingConnect {
|
||||
resolve: () => void;
|
||||
reject: (err?: any) => void; // Changed to make err optional
|
||||
timer?: number;
|
||||
}
|
||||
class PendingConnectImpl implements PendingConnect {
|
||||
override resolve: () => void;
|
||||
override reject: (err?: any) => void; // Changed to make err optional
|
||||
override timer?: number;
|
||||
constructor(resolve: () => void, reject: (err?: any) => void, timer?: number) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this.timer = timer;
|
||||
}
|
||||
}
|
||||
// 引入全局回调管理
|
||||
import { gattCallback } from './service_manager.uts';
|
||||
const pendingConnects = new Map<string, PendingConnect>();
|
||||
const STATE_DISCONNECTED = 0;
|
||||
const STATE_CONNECTING = 1;
|
||||
const STATE_CONNECTED = 2;
|
||||
const STATE_DISCONNECTING = 3;
|
||||
export class DeviceManager {
|
||||
private static instance: DeviceManager | null = null;
|
||||
private devices = new Map<string, BleDevice>();
|
||||
private connectionStates = new Map<string, BleConnectionState>();
|
||||
private connectionStateChangeListeners: BleConnectionStateChangeCallback[] = [];
|
||||
private gattMap = new Map<string, BluetoothGatt | null>();
|
||||
private scanCallback: ScanCallback | null = null;
|
||||
private isScanning: boolean = false;
|
||||
private constructor() { }
|
||||
static getInstance(): DeviceManager {
|
||||
if (DeviceManager.instance == null) {
|
||||
DeviceManager.instance = new DeviceManager();
|
||||
}
|
||||
return DeviceManager.instance!;
|
||||
}
|
||||
startScan(options: ScanDevicesOptions): void {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:60', 'ak startscan now');
|
||||
const adapter = this.getBluetoothAdapter();
|
||||
if (adapter == null) {
|
||||
throw new Error('未找到蓝牙适配器');
|
||||
}
|
||||
if (!adapter.isEnabled) {
|
||||
// 尝试请求用户开启蓝牙
|
||||
try {
|
||||
adapter.enable(); // 直接调用,无需可选链和括号
|
||||
}
|
||||
catch (e: any) {
|
||||
// 某些设备可能不支持 enable
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (!adapter.isEnabled) {
|
||||
throw new Error('蓝牙未开启');
|
||||
}
|
||||
}, 1500);
|
||||
throw new Error('正在开启蓝牙,请重试');
|
||||
}
|
||||
const foundDevices = this.devices; // 直接用全局 devices
|
||||
class MyScanCallback extends ScanCallback {
|
||||
private foundDevices: Map<string, BleDevice>;
|
||||
private onDeviceFound: (device: BleDevice) => void;
|
||||
constructor(foundDevices: Map<string, BleDevice>, onDeviceFound: (device: BleDevice) => void) {
|
||||
super();
|
||||
this.foundDevices = foundDevices;
|
||||
this.onDeviceFound = onDeviceFound;
|
||||
}
|
||||
override onScanResult(callbackType: Int, result: ScanResult): void {
|
||||
const device = result.getDevice();
|
||||
if (device != null) {
|
||||
const deviceId = device.getAddress();
|
||||
let bleDevice = foundDevices.get(deviceId);
|
||||
if (bleDevice == null) {
|
||||
bleDevice = {
|
||||
deviceId,
|
||||
name: device.getName() ?? 'Unknown',
|
||||
rssi: result.getRssi(),
|
||||
lastSeen: Date.now()
|
||||
} as BleDevice;
|
||||
foundDevices.set(deviceId, bleDevice);
|
||||
this.onDeviceFound(bleDevice);
|
||||
}
|
||||
else {
|
||||
// 更新属性(已确保 bleDevice 非空)
|
||||
bleDevice.rssi = result.getRssi();
|
||||
bleDevice.name = device.getName() ?? bleDevice.name;
|
||||
bleDevice.lastSeen = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
override onScanFailed(errorCode: Int): void {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:114', 'ak scan fail');
|
||||
}
|
||||
}
|
||||
this.scanCallback = new MyScanCallback(foundDevices, options.onDeviceFound ?? ((_device) => { }));
|
||||
const scanner = adapter.getBluetoothLeScanner();
|
||||
if (scanner == null) {
|
||||
throw new Error('无法获取扫描器');
|
||||
}
|
||||
const scanSettings = new ScanSettings.Builder()
|
||||
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||||
.build();
|
||||
scanner.startScan(null, scanSettings, this.scanCallback);
|
||||
this.isScanning = true;
|
||||
// 默认10秒后停止扫描
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() => {
|
||||
if (this.isScanning && this.scanCallback != null) {
|
||||
scanner.stopScan(this.scanCallback);
|
||||
this.isScanning = false;
|
||||
// this.devices = foundDevices;
|
||||
if (options.onScanFinished != null)
|
||||
options.onScanFinished?.invoke();
|
||||
}
|
||||
}, 40000);
|
||||
}
|
||||
async connectDevice(deviceId: string, options?: BleConnectOptionsExt): Promise<void> {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:139', '[AKBLE] connectDevice called, deviceId:', deviceId, 'options:', options, 'connectionStates:');
|
||||
const adapter = this.getBluetoothAdapter();
|
||||
if (adapter == null) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:142', '[AKBLE] connectDevice failed: 蓝牙适配器不可用');
|
||||
throw new Error('蓝牙适配器不可用');
|
||||
}
|
||||
const device = adapter.getRemoteDevice(deviceId);
|
||||
if (device == null) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:147', '[AKBLE] connectDevice failed: 未找到设备', deviceId);
|
||||
throw new Error('未找到设备');
|
||||
}
|
||||
this.connectionStates.set(deviceId, STATE_CONNECTING);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:151', '[AKBLE] connectDevice set STATE_CONNECTING, deviceId:', deviceId, 'connectionStates:');
|
||||
this.emitConnectionStateChange(deviceId, STATE_CONNECTING);
|
||||
const activity = UTSAndroid.getUniActivity();
|
||||
const timeout = options?.timeout ?? 15000;
|
||||
const key = `${deviceId}|connect`;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:158', '[AKBLE] connectDevice 超时:', deviceId);
|
||||
pendingConnects.delete(key);
|
||||
this.connectionStates.set(deviceId, STATE_DISCONNECTED);
|
||||
this.gattMap.set(deviceId, null);
|
||||
this.emitConnectionStateChange(deviceId, STATE_DISCONNECTED);
|
||||
reject(new Error('连接超时'));
|
||||
}, timeout);
|
||||
// 创建一个适配器函数来匹配类型签名
|
||||
const resolveAdapter = () => {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:168', '[AKBLE] connectDevice resolveAdapter:', deviceId);
|
||||
resolve(void 0);
|
||||
};
|
||||
const rejectAdapter = (err?: any) => {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:172', '[AKBLE] connectDevice rejectAdapter:', deviceId, err);
|
||||
reject(err);
|
||||
};
|
||||
pendingConnects.set(key, new PendingConnectImpl(resolveAdapter, rejectAdapter, timer));
|
||||
try {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:178', '[AKBLE] connectGatt 调用前:', deviceId);
|
||||
const gatt = device.connectGatt(activity, false, gattCallback);
|
||||
this.gattMap.set(deviceId, gatt);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:181', '[AKBLE] connectGatt 调用后:', deviceId, gatt);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:183', '[AKBLE] connectGatt 异常:', deviceId, e);
|
||||
clearTimeout(timer);
|
||||
pendingConnects.delete(key);
|
||||
this.connectionStates.set(deviceId, STATE_DISCONNECTED);
|
||||
this.gattMap.set(deviceId, null);
|
||||
this.emitConnectionStateChange(deviceId, STATE_DISCONNECTED);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 统一分发连接回调(应在 gattCallback.onConnectionStateChange 内调用)
|
||||
static handleConnectionStateChange(deviceId: string, newState: number, error?: any) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:196', '[AKBLE] handleConnectionStateChange:', deviceId, 'newState:', newState, 'error:', error, 'pendingConnects:');
|
||||
const key = `${deviceId}|connect`;
|
||||
const cb = pendingConnects.get(key);
|
||||
if (cb != null) {
|
||||
// 修复 timer 的空安全问题,使用临时变量
|
||||
const timerValue = cb.timer;
|
||||
if (timerValue != null) {
|
||||
clearTimeout(timerValue);
|
||||
}
|
||||
// 修复 error 处理
|
||||
if (newState === STATE_CONNECTED) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:208', '[AKBLE] handleConnectionStateChange: 连接成功', deviceId);
|
||||
cb.resolve();
|
||||
}
|
||||
else {
|
||||
// 正确处理可空值
|
||||
const errorToUse = error != null ? error : new Error('连接断开');
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:213', '[AKBLE] handleConnectionStateChange: 连接失败', deviceId, errorToUse);
|
||||
cb.reject(errorToUse);
|
||||
}
|
||||
pendingConnects.delete(key);
|
||||
}
|
||||
else {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:218', '[AKBLE] handleConnectionStateChange: 未找到 pendingConnects', deviceId, newState);
|
||||
}
|
||||
}
|
||||
async disconnectDevice(deviceId: string, isActive: boolean = true): Promise<void> {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:223', '[AKBLE] disconnectDevice called, deviceId:', deviceId, 'isActive:', isActive);
|
||||
let gatt = this.gattMap.get(deviceId);
|
||||
if (gatt != null) {
|
||||
gatt.disconnect();
|
||||
gatt.close();
|
||||
// gatt=null;
|
||||
this.gattMap.set(deviceId, null);
|
||||
this.connectionStates.set(deviceId, STATE_DISCONNECTED);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:231', '[AKBLE] disconnectDevice set STATE_DISCONNECTED, deviceId:', deviceId, 'connectionStates:');
|
||||
this.emitConnectionStateChange(deviceId, STATE_DISCONNECTED);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:235', '[AKBLE] disconnectDevice: gatt is null, deviceId:', deviceId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
async reconnectDevice(deviceId: string, options?: BleConnectOptionsExt): Promise<void> {
|
||||
let attempts = 0;
|
||||
const maxAttempts = options?.maxAttempts ?? 3;
|
||||
const interval = options?.interval ?? 3000;
|
||||
while (attempts < maxAttempts) {
|
||||
try {
|
||||
await this.disconnectDevice(deviceId, false);
|
||||
await this.connectDevice(deviceId, options);
|
||||
return;
|
||||
}
|
||||
catch (e: any) {
|
||||
attempts++;
|
||||
if (attempts >= maxAttempts)
|
||||
throw new Error('重连失败');
|
||||
// 修复 setTimeout 问题,使用旧式 Promise + setTimeout 解决
|
||||
await new Promise<void>((resolve, _reject) => {
|
||||
setTimeout(() => {
|
||||
resolve(void 0);
|
||||
}, interval);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
getConnectedDevices(): BleDevice[] {
|
||||
// 创建一个空数组来存储结果
|
||||
const result: BleDevice[] = [];
|
||||
// 遍历 devices Map 并检查连接状态
|
||||
this.devices.forEach((device, deviceId) => {
|
||||
if (this.connectionStates.get(deviceId) === STATE_CONNECTED) {
|
||||
result.push(device);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
onConnectionStateChange(listener: BleConnectionStateChangeCallback) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:277', '[AKBLE][LOG] onConnectionStateChange 注册, 当前监听数:', this.connectionStateChangeListeners.length + 1, listener);
|
||||
this.connectionStateChangeListeners.push(listener);
|
||||
}
|
||||
protected emitConnectionStateChange(deviceId: string, state: BleConnectionState) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:282', '[AKBLE][LOG] emitConnectionStateChange', deviceId, state, 'listeners:', this.connectionStateChangeListeners.length, 'connectionStates:', this.connectionStates);
|
||||
for (const listener of this.connectionStateChangeListeners) {
|
||||
try {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:285', '[AKBLE][LOG] emitConnectionStateChange 调用 listener', listener);
|
||||
listener(deviceId, state);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:288', '[AKBLE][LOG] emitConnectionStateChange listener error', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
getGattInstance(deviceId: string): BluetoothGatt | null {
|
||||
return this.gattMap.get(deviceId) ?? null;
|
||||
}
|
||||
private getBluetoothAdapter(): BluetoothAdapter | null {
|
||||
const context = UTSAndroid.getAppContext();
|
||||
if (context == null)
|
||||
return null;
|
||||
const manager = context?.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager;
|
||||
return manager.getAdapter();
|
||||
}
|
||||
/**
|
||||
* 获取指定ID的设备(如果存在)
|
||||
*/
|
||||
public getDevice(deviceId: string): BleDevice | null {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/device_manager.uts:308', deviceId, this.devices);
|
||||
return this.devices.get(deviceId) ?? null;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=device_manager.uts.map
|
||||
File diff suppressed because one or more lines are too long
808
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts
vendored
Normal file
808
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts
vendored
Normal file
@@ -0,0 +1,808 @@
|
||||
import { BleService } from '../interface.uts';
|
||||
import type { WriteCharacteristicOptions, DfuOptions, ControlParserResult } from '../interface.uts';
|
||||
import { DeviceManager } from './device_manager.uts';
|
||||
import { ServiceManager } from './service_manager.uts';
|
||||
import BluetoothGatt from 'android.bluetooth.BluetoothGatt';
|
||||
import BluetoothGattCharacteristic from 'android.bluetooth.BluetoothGattCharacteristic';
|
||||
import BluetoothGattDescriptor from 'android.bluetooth.BluetoothGattDescriptor';
|
||||
import UUID from 'java.util.UUID';
|
||||
// 通用 Nordic DFU UUIDs (常见设备可能使用这些;如厂商自定义请替换)
|
||||
const DFU_SERVICE_UUID = '0000fe59-0000-1000-8000-00805f9b34fb';
|
||||
const DFU_CONTROL_POINT_UUID = '8ec90001-f315-4f60-9fb8-838830daea50';
|
||||
const DFU_PACKET_UUID = '8ec90002-f315-4f60-9fb8-838830daea50';
|
||||
type DfuSession = {
|
||||
resolve: () => void;
|
||||
reject: (err?: any) => void;
|
||||
onProgress?: (p: number) => void;
|
||||
onLog?: (s: string) => void;
|
||||
controlParser?: (data: Uint8Array) => ControlParserResult | null;
|
||||
// Nordic 专用字段
|
||||
bytesSent?: number;
|
||||
totalBytes?: number;
|
||||
useNordic?: boolean;
|
||||
// PRN (packet receipt notification) support
|
||||
prn?: number;
|
||||
packetsSincePrn?: number;
|
||||
prnResolve?: () => void;
|
||||
prnReject?: (err?: any) => void;
|
||||
};
|
||||
export class DfuManager {
|
||||
// 会话表,用于把 control-point 通知路由到当前 DFU 流程
|
||||
private sessions: Map<string, DfuSession> = new Map();
|
||||
// 简化:只实现最基本的 GATT-based DFU 上传逻辑,需按设备协议调整 control point 的命令/解析
|
||||
// Emit a DFU lifecycle event for a session. name should follow Nordic listener names
|
||||
private _emitDfuEvent(deviceId: string, name: string, payload?: any) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:42', '[DFU][Event]', name, deviceId, payload ?? '');
|
||||
const s = this.sessions.get(deviceId);
|
||||
if (s == null)
|
||||
return;
|
||||
if (typeof s.onLog == 'function') {
|
||||
try {
|
||||
const logFn = s.onLog as (msg: string) => void;
|
||||
logFn(`[${name}] ${payload != null ? JSON.stringify(payload) : ''}`);
|
||||
}
|
||||
catch (e: any) { }
|
||||
}
|
||||
if (name == 'onProgress' && typeof s.onProgress == 'function' && typeof payload == 'number') {
|
||||
try {
|
||||
s.onProgress!(payload as number);
|
||||
}
|
||||
catch (e: any) { }
|
||||
}
|
||||
}
|
||||
async startDfu(deviceId: string, firmwareBytes: Uint8Array, options?: DfuOptions): Promise<void> {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:57', 'startDfu 0');
|
||||
const deviceManager = DeviceManager.getInstance();
|
||||
const serviceManager = ServiceManager.getInstance();
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:60', 'startDfu 1');
|
||||
const gatt: BluetoothGatt | null = deviceManager.getGattInstance(deviceId);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:62', 'startDfu 2');
|
||||
if (gatt == null)
|
||||
throw new Error('Device not connected');
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:64', '[DFU] startDfu start deviceId=', deviceId, 'firmwareBytes=', firmwareBytes != null ? firmwareBytes.length : 0, 'options=', options);
|
||||
try {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:66', '[DFU] requesting high connection priority for', deviceId);
|
||||
gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:69', '[DFU] requestConnectionPriority failed', e);
|
||||
}
|
||||
// 发现服务并特征
|
||||
// ensure services discovered before accessing GATT; serviceManager exposes Promise-based API
|
||||
await serviceManager.getServices(deviceId, null);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:75', '[DFU] services ensured for', deviceId);
|
||||
const dfuService = gatt.getService(UUID.fromString(DFU_SERVICE_UUID));
|
||||
if (dfuService == null)
|
||||
throw new Error('DFU service not found');
|
||||
const controlChar = dfuService.getCharacteristic(UUID.fromString(DFU_CONTROL_POINT_UUID));
|
||||
const packetChar = dfuService.getCharacteristic(UUID.fromString(DFU_PACKET_UUID));
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:80', '[DFU] dfuService=', dfuService != null ? dfuService.getUuid().toString() : null, 'controlChar=', controlChar != null ? controlChar.getUuid().toString() : null, 'packetChar=', packetChar != null ? packetChar.getUuid().toString() : null);
|
||||
if (controlChar == null || packetChar == null)
|
||||
throw new Error('DFU characteristics missing');
|
||||
const packetProps = packetChar.getProperties();
|
||||
const supportsWriteWithResponse = (packetProps & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0;
|
||||
const supportsWriteNoResponse = (packetProps & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0;
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:85', '[DFU] packet characteristic props mask=', packetProps, 'supportsWithResponse=', supportsWriteWithResponse, 'supportsNoResponse=', supportsWriteNoResponse);
|
||||
// Allow caller to request a desired MTU via options for higher throughput
|
||||
const desiredMtu = (options != null && typeof options.mtu == 'number') ? options.mtu! : 247;
|
||||
try {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:90', '[DFU] requesting MTU=', desiredMtu, 'for', deviceId);
|
||||
await this._requestMtu(gatt, desiredMtu, 8000);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:92', '[DFU] requestMtu completed for', deviceId);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:94', '[DFU] requestMtu failed or timed out, continue with default.', e);
|
||||
}
|
||||
const mtu = desiredMtu; // 假定成功或使用期望值
|
||||
const chunkSize = Math.max(20, mtu - 3);
|
||||
// small helper to convert a byte (possibly signed) to a two-digit hex string
|
||||
const byteToHex = (b: number): string => {
|
||||
const v = (b < 0) ? (b + 256) : b;
|
||||
let s = v.toString(16);
|
||||
if (s.length < 2)
|
||||
s = '0' + s;
|
||||
return s;
|
||||
};
|
||||
// Parameterize PRN window and timeout via options early so they are available
|
||||
// for session logging. Defaults: prn = 12 packets, prnTimeoutMs = 10000 ms
|
||||
let prnWindow = 0;
|
||||
if (options != null && typeof options.prn == 'number') {
|
||||
prnWindow = Math.max(0, Math.floor(options.prn!));
|
||||
}
|
||||
const prnTimeoutMs = (options != null && typeof options.prnTimeoutMs == 'number') ? Math.max(1000, Math.floor(options.prnTimeoutMs!)) : 8000;
|
||||
const disablePrnOnTimeout = !(options != null && options.disablePrnOnTimeout == false);
|
||||
// 订阅 control point 通知并将通知路由到会话处理器
|
||||
const controlHandler = (data: Uint8Array) => {
|
||||
// 交给会话处理器解析并触发事件
|
||||
try {
|
||||
const hexParts: string[] = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const v = data[i] as number;
|
||||
hexParts.push(byteToHex(v));
|
||||
}
|
||||
const hex = hexParts.join(' ');
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:126', '[DFU] control notification callback invoked for', deviceId, 'raw=', Array.from(data), 'hex=', hex);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:128', '[DFU] control notification callback invoked for', deviceId, 'raw=', Array.from(data));
|
||||
}
|
||||
this._handleControlNotification(deviceId, data);
|
||||
};
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:132', '[DFU] subscribing control point for', deviceId);
|
||||
await serviceManager.subscribeCharacteristic(deviceId, DFU_SERVICE_UUID, DFU_CONTROL_POINT_UUID, controlHandler);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:134', '[DFU] subscribeCharacteristic returned for', deviceId);
|
||||
// 保存会话回调(用于 waitForControlEvent); 支持 Nordic 模式追踪已发送字节
|
||||
this.sessions.set(deviceId, {
|
||||
resolve: () => { },
|
||||
reject: (err?: any) => { __f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:139', err); },
|
||||
onProgress: null,
|
||||
onLog: null,
|
||||
controlParser: (data: Uint8Array): ControlParserResult | null => this._defaultControlParser(data),
|
||||
bytesSent: 0,
|
||||
totalBytes: firmwareBytes.length,
|
||||
useNordic: options != null && options.useNordic == true,
|
||||
prn: null,
|
||||
packetsSincePrn: 0,
|
||||
prnResolve: null,
|
||||
prnReject: null
|
||||
} as DfuSession);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:151', '[DFU] session created for', deviceId, 'totalBytes=', firmwareBytes.length);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:152', '[DFU] DFU session details:', { deviceId: deviceId, totalBytes: firmwareBytes.length, chunkSize: chunkSize, prnWindow: prnWindow, prnTimeoutMs: prnTimeoutMs });
|
||||
// wire options callbacks into the session (if provided)
|
||||
const sessRef = this.sessions.get(deviceId);
|
||||
if (sessRef != null) {
|
||||
sessRef.onProgress = (options != null && typeof options.onProgress == 'function') ? options.onProgress! : null;
|
||||
sessRef.onLog = (options != null && typeof options.onLog == 'function') ? options.onLog! : null;
|
||||
}
|
||||
// emit initial lifecycle events (Nordic-like)
|
||||
this._emitDfuEvent(deviceId, 'onDeviceConnecting', null);
|
||||
// 写入固件数据(非常保守的实现:逐包写入并等待短延迟)
|
||||
// --- PRN setup (optional, Nordic-style flow) ---
|
||||
// Parameterize PRN window and timeout via options: options.prn, options.prnTimeoutMs
|
||||
// Defaults were set earlier; build PRN payload using arithmetic to avoid
|
||||
// bitwise operators which don't map cleanly to generated Kotlin.
|
||||
if (prnWindow > 0) {
|
||||
try {
|
||||
// send Set PRN to device (format: [OP_CODE_SET_PRN, prn LSB, prn MSB])
|
||||
// WARNING: Ensure your device uses the same opcode/format; change if needed.
|
||||
const prnLsb = prnWindow % 256;
|
||||
const prnMsb = Math.floor(prnWindow / 256) % 256;
|
||||
const prnPayload = new Uint8Array([0x02, prnLsb, prnMsb]);
|
||||
await serviceManager.writeCharacteristic(deviceId, DFU_SERVICE_UUID, DFU_CONTROL_POINT_UUID, prnPayload, null);
|
||||
const sess0 = this.sessions.get(deviceId);
|
||||
if (sess0 != null) {
|
||||
sess0.useNordic = true;
|
||||
sess0.prn = prnWindow;
|
||||
sess0.packetsSincePrn = 0;
|
||||
sess0.controlParser = (data: Uint8Array): ControlParserResult | null => this._nordicControlParser(data);
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:184', '[DFU] Set PRN sent (prn=', prnWindow, ') for', deviceId);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:186', '[DFU] Set PRN failed (continuing without PRN):', e);
|
||||
const sessFallback = this.sessions.get(deviceId);
|
||||
if (sessFallback != null)
|
||||
sessFallback.prn = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:191', '[DFU] PRN disabled (prnWindow=', prnWindow, ') for', deviceId);
|
||||
}
|
||||
// 写入固件数据(逐包写入并根据 options.waitForResponse 选择是否等待响应)
|
||||
let offset = 0;
|
||||
const total = firmwareBytes.length;
|
||||
this._emitDfuEvent(deviceId, 'onDfuProcessStarted', null);
|
||||
this._emitDfuEvent(deviceId, 'onUploadingStarted', null);
|
||||
// Track outstanding write operations when using fire-and-forget mode so we can
|
||||
// log and throttle if the Android stack becomes overwhelmed.
|
||||
let outstandingWrites = 0;
|
||||
// read tuning parameters from options in a safe, generator-friendly way
|
||||
let configuredMaxOutstanding = 2;
|
||||
let writeSleepMs = 0;
|
||||
let writeRetryDelay = 100;
|
||||
let writeMaxAttempts = 12;
|
||||
let writeGiveupTimeout = 60000;
|
||||
let drainOutstandingTimeout = 3000;
|
||||
let failureBackoffMs = 0;
|
||||
// throughput measurement
|
||||
let throughputWindowBytes = 0;
|
||||
let lastThroughputTime = Date.now();
|
||||
function _logThroughputIfNeeded(force?: boolean) {
|
||||
try {
|
||||
const now = Date.now();
|
||||
const elapsed = now - lastThroughputTime;
|
||||
if (force == true || elapsed >= 1000) {
|
||||
const bytes = throughputWindowBytes;
|
||||
const bps = Math.floor((bytes * 1000) / Math.max(1, elapsed));
|
||||
// reset window
|
||||
throughputWindowBytes = 0;
|
||||
lastThroughputTime = now;
|
||||
const human = `${bps} B/s`;
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:225', '[DFU] throughput:', human, 'elapsedMs=', elapsed);
|
||||
const s = this.sessions.get(deviceId);
|
||||
if (s != null && typeof s.onLog == 'function') {
|
||||
try {
|
||||
s.onLog?.invoke('[DFU] throughput: ' + human);
|
||||
}
|
||||
catch (e: any) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: any) { }
|
||||
}
|
||||
function _safeErr(e?: any): any {
|
||||
try {
|
||||
if (e == null)
|
||||
return '';
|
||||
if (typeof e == 'string')
|
||||
return e as string;
|
||||
try {
|
||||
return JSON.stringify(e);
|
||||
}
|
||||
catch (e2: any) { }
|
||||
try {
|
||||
return (e as any).toString();
|
||||
}
|
||||
catch (e3: any) { }
|
||||
return '';
|
||||
}
|
||||
catch (e4: any) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (options != null) {
|
||||
try {
|
||||
if (options.maxOutstanding != null) {
|
||||
const parsed = Math.floor(options.maxOutstanding as number);
|
||||
if (!isNaN(parsed) && parsed > 0)
|
||||
configuredMaxOutstanding = parsed;
|
||||
}
|
||||
}
|
||||
catch (e: any) { }
|
||||
try {
|
||||
if (options.writeSleepMs != null) {
|
||||
const parsedWs = Math.floor(options.writeSleepMs as number);
|
||||
if (!isNaN(parsedWs) && parsedWs >= 0)
|
||||
writeSleepMs = parsedWs;
|
||||
}
|
||||
}
|
||||
catch (e: any) { }
|
||||
try {
|
||||
if (options.writeRetryDelayMs != null) {
|
||||
const parsedRetry = Math.floor(options.writeRetryDelayMs as number);
|
||||
if (!isNaN(parsedRetry) && parsedRetry >= 0)
|
||||
writeRetryDelay = parsedRetry;
|
||||
}
|
||||
}
|
||||
catch (e: any) { }
|
||||
try {
|
||||
if (options.writeMaxAttempts != null) {
|
||||
const parsedAttempts = Math.floor(options.writeMaxAttempts as number);
|
||||
if (!isNaN(parsedAttempts) && parsedAttempts > 0)
|
||||
writeMaxAttempts = parsedAttempts;
|
||||
}
|
||||
}
|
||||
catch (e: any) { }
|
||||
try {
|
||||
if (options.writeGiveupTimeoutMs != null) {
|
||||
const parsedGiveupTimeout = Math.floor(options.writeGiveupTimeoutMs as number);
|
||||
if (!isNaN(parsedGiveupTimeout) && parsedGiveupTimeout > 0)
|
||||
writeGiveupTimeout = parsedGiveupTimeout;
|
||||
}
|
||||
}
|
||||
catch (e: any) { }
|
||||
try {
|
||||
if (options.drainOutstandingTimeoutMs != null) {
|
||||
const parsedDrain = Math.floor(options.drainOutstandingTimeoutMs as number);
|
||||
if (!isNaN(parsedDrain) && parsedDrain >= 0)
|
||||
drainOutstandingTimeout = parsedDrain;
|
||||
}
|
||||
}
|
||||
catch (e: any) { }
|
||||
}
|
||||
if (configuredMaxOutstanding < 1)
|
||||
configuredMaxOutstanding = 1;
|
||||
if (writeSleepMs < 0)
|
||||
writeSleepMs = 0;
|
||||
}
|
||||
catch (e: any) { }
|
||||
if (supportsWriteWithResponse == false && supportsWriteNoResponse == true) {
|
||||
if (configuredMaxOutstanding > 1)
|
||||
configuredMaxOutstanding = 1;
|
||||
if (writeSleepMs < 15)
|
||||
writeSleepMs = 15;
|
||||
if (writeRetryDelay < 150)
|
||||
writeRetryDelay = 150;
|
||||
if (writeMaxAttempts < 40)
|
||||
writeMaxAttempts = 40;
|
||||
if (writeGiveupTimeout < 120000)
|
||||
writeGiveupTimeout = 120000;
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:291', '[DFU] packet char only supports WRITE_NO_RESPONSE; serializing writes with conservative pacing');
|
||||
}
|
||||
const maxOutstandingCeiling = configuredMaxOutstanding;
|
||||
let adaptiveMaxOutstanding = configuredMaxOutstanding;
|
||||
const minOutstandingWindow = 1;
|
||||
while (offset < total) {
|
||||
const end = Math.min(offset + chunkSize, total);
|
||||
const slice = firmwareBytes.subarray(offset, end);
|
||||
// Decide whether to wait for response per-chunk. Honor characteristic support.
|
||||
// Generator-friendly: avoid 'undefined' and use explicit boolean check.
|
||||
let finalWaitForResponse = true;
|
||||
if (options != null) {
|
||||
try {
|
||||
const maybe = options.waitForResponse;
|
||||
if (maybe == true && supportsWriteWithResponse == false && supportsWriteNoResponse == true) {
|
||||
// caller requested response but characteristic cannot provide it; keep true to ensure we await the write promise.
|
||||
finalWaitForResponse = true;
|
||||
}
|
||||
else if (maybe == false) {
|
||||
finalWaitForResponse = false;
|
||||
}
|
||||
else if (maybe == true) {
|
||||
finalWaitForResponse = true;
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
finalWaitForResponse = true;
|
||||
}
|
||||
}
|
||||
const writeOpts: WriteCharacteristicOptions = {
|
||||
waitForResponse: finalWaitForResponse,
|
||||
retryDelayMs: writeRetryDelay,
|
||||
maxAttempts: writeMaxAttempts,
|
||||
giveupTimeoutMs: writeGiveupTimeout,
|
||||
forceWriteTypeNoResponse: finalWaitForResponse == false
|
||||
};
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:324', '[DFU] writing packet chunk offset=', offset, 'len=', slice.length, 'waitForResponse=', finalWaitForResponse, 'outstanding=', outstandingWrites);
|
||||
// Fire-and-forget path: do not await the write if waitForResponse == false.
|
||||
if (finalWaitForResponse == false) {
|
||||
if (failureBackoffMs > 0) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:329', '[DFU] applying failure backoff', failureBackoffMs, 'ms before next write for', deviceId);
|
||||
await this._sleep(failureBackoffMs);
|
||||
failureBackoffMs = Math.floor(failureBackoffMs / 2);
|
||||
}
|
||||
while (outstandingWrites >= adaptiveMaxOutstanding) {
|
||||
await this._sleep(Math.max(1, writeSleepMs));
|
||||
}
|
||||
// increment outstanding counter and kick the write without awaiting.
|
||||
outstandingWrites = outstandingWrites + 1;
|
||||
// fire-and-forget: start the write but don't await its Promise
|
||||
const writeOffset = offset;
|
||||
serviceManager.writeCharacteristic(deviceId, DFU_SERVICE_UUID, DFU_PACKET_UUID, slice, writeOpts).then((res) => {
|
||||
outstandingWrites = Math.max(0, outstandingWrites - 1);
|
||||
if (res == true) {
|
||||
if (adaptiveMaxOutstanding < maxOutstandingCeiling) {
|
||||
adaptiveMaxOutstanding = Math.min(maxOutstandingCeiling, adaptiveMaxOutstanding + 1);
|
||||
}
|
||||
if (failureBackoffMs > 0)
|
||||
failureBackoffMs = Math.floor(failureBackoffMs / 2);
|
||||
}
|
||||
// log occasional completions
|
||||
if ((outstandingWrites & 0x1f) == 0) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:350', '[DFU] write completion callback, outstandingWrites=', outstandingWrites, 'adaptiveWindow=', adaptiveMaxOutstanding, 'device=', deviceId);
|
||||
}
|
||||
// detect write failure signaled by service manager
|
||||
if (res !== true) {
|
||||
adaptiveMaxOutstanding = Math.max(minOutstandingWindow, Math.floor(adaptiveMaxOutstanding / 2));
|
||||
failureBackoffMs = Math.min(200, Math.max(failureBackoffMs, Math.max(5, writeRetryDelay)));
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:356', '[DFU] writeCharacteristic returned false for device=', deviceId, 'offset=', writeOffset, 'adaptiveWindow now=', adaptiveMaxOutstanding);
|
||||
try {
|
||||
this._emitDfuEvent(deviceId, 'onError', { phase: 'write', offset: writeOffset, reason: 'write returned false' });
|
||||
}
|
||||
catch (e: any) { }
|
||||
}
|
||||
}).catch((e) => {
|
||||
outstandingWrites = Math.max(0, outstandingWrites - 1);
|
||||
adaptiveMaxOutstanding = Math.max(minOutstandingWindow, Math.floor(adaptiveMaxOutstanding / 2));
|
||||
failureBackoffMs = Math.min(200, Math.max(failureBackoffMs, Math.max(5, writeRetryDelay)));
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:363', '[DFU] fire-and-forget write failed for device=', deviceId, e, 'adaptiveWindow now=', adaptiveMaxOutstanding);
|
||||
try {
|
||||
const errMsg = '[DFU] fire-and-forget write failed for device=';
|
||||
this._emitDfuEvent(deviceId, 'onError', { phase: 'write', offset: writeOffset, reason: errMsg });
|
||||
}
|
||||
catch (e2: any) { }
|
||||
});
|
||||
// account bytes for throughput
|
||||
throughputWindowBytes += slice.length;
|
||||
_logThroughputIfNeeded(false);
|
||||
}
|
||||
else {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:373', '[DFU] awaiting write for chunk offset=', offset);
|
||||
try {
|
||||
const writeResult = await serviceManager.writeCharacteristic(deviceId, DFU_SERVICE_UUID, DFU_PACKET_UUID, slice, writeOpts);
|
||||
if (writeResult !== true) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:377', '[DFU] writeCharacteristic(await) returned false at offset=', offset, 'device=', deviceId);
|
||||
try {
|
||||
this._emitDfuEvent(deviceId, 'onError', { phase: 'write', offset: offset, reason: 'write returned false' });
|
||||
}
|
||||
catch (e: any) { }
|
||||
// abort DFU by throwing
|
||||
throw new Error('write failed');
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:383', '[DFU] awaiting write failed at offset=', offset, 'device=', deviceId, e);
|
||||
try {
|
||||
const errMsg = '[DFU] awaiting write failed ';
|
||||
this._emitDfuEvent(deviceId, 'onError', { phase: 'write', offset: offset, reason: errMsg });
|
||||
}
|
||||
catch (e2: any) { }
|
||||
throw e;
|
||||
}
|
||||
// account bytes for throughput
|
||||
throughputWindowBytes += slice.length;
|
||||
_logThroughputIfNeeded(false);
|
||||
}
|
||||
// update PRN counters and wait when window reached
|
||||
const sessAfter = this.sessions.get(deviceId);
|
||||
if (sessAfter != null && sessAfter.useNordic == true && typeof sessAfter.prn == 'number' && (sessAfter.prn ?? 0) > 0) {
|
||||
sessAfter.packetsSincePrn = (sessAfter.packetsSincePrn ?? 0) + 1;
|
||||
if ((sessAfter.packetsSincePrn ?? 0) >= (sessAfter.prn ?? 0) && (sessAfter.prn ?? 0) > 0) {
|
||||
// wait for PRN (device notification) before continuing
|
||||
try {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:401', '[DFU] reached PRN window, waiting for PRN for', deviceId, 'packetsSincePrn=', sessAfter.packetsSincePrn, 'prn=', sessAfter.prn);
|
||||
await this._waitForPrn(deviceId, prnTimeoutMs);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:403', '[DFU] PRN received, resuming transfer for', deviceId);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:405', '[DFU] PRN wait failed/timed out, continuing anyway for', deviceId, e);
|
||||
if (disablePrnOnTimeout) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:407', '[DFU] disabling PRN waits after timeout for', deviceId);
|
||||
sessAfter.prn = 0;
|
||||
}
|
||||
}
|
||||
// reset counter
|
||||
sessAfter.packetsSincePrn = 0;
|
||||
}
|
||||
}
|
||||
offset = end;
|
||||
// 如果启用 nordic 模式,统计已发送字节
|
||||
const sess = this.sessions.get(deviceId);
|
||||
if (sess != null && typeof sess.bytesSent == 'number') {
|
||||
sess.bytesSent = (sess.bytesSent ?? 0) + slice.length;
|
||||
}
|
||||
// 简单节流与日志,避免过快。默认睡眠非常短以提高吞吐量; 可在设备上调节
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:422', '[DFU] wrote chunk for', deviceId, 'offset=', offset, '/', total, 'chunkSize=', slice.length, 'bytesSent=', sess != null ? sess.bytesSent : null, 'outstanding=', outstandingWrites);
|
||||
// emit upload progress event (percent) if available
|
||||
if (sess != null && typeof sess.bytesSent == 'number' && typeof sess.totalBytes == 'number') {
|
||||
const p = Math.floor((sess.bytesSent! / sess.totalBytes!) * 100);
|
||||
this._emitDfuEvent(deviceId, 'onProgress', p);
|
||||
}
|
||||
// yield to event loop and avoid starving the Android BLE stack
|
||||
await this._sleep(Math.max(0, writeSleepMs));
|
||||
}
|
||||
// wait for outstanding writes to drain before continuing with control commands
|
||||
if (outstandingWrites > 0) {
|
||||
const drainStart = Date.now();
|
||||
while (outstandingWrites > 0 && (Date.now() - drainStart) < drainOutstandingTimeout) {
|
||||
await this._sleep(Math.max(0, writeSleepMs));
|
||||
}
|
||||
if (outstandingWrites > 0) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:438', '[DFU] outstandingWrites remain after drain timeout, continuing with', outstandingWrites);
|
||||
}
|
||||
else {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:440', '[DFU] outstandingWrites drained before control phase');
|
||||
}
|
||||
}
|
||||
this._emitDfuEvent(deviceId, 'onUploadingCompleted', null);
|
||||
// force final throughput log before activate/validate
|
||||
_logThroughputIfNeeded(true);
|
||||
// 发送 activate/validate 命令到 control point(需根据设备协议实现)
|
||||
// 下面为占位:请替换为实际的 opcode
|
||||
// 在写入前先启动控制结果等待,防止设备快速响应导致丢失通知
|
||||
const controlTimeout = 20000;
|
||||
const controlResultPromise = this._waitForControlResult(deviceId, controlTimeout);
|
||||
try {
|
||||
// control writes: pass undefined options explicitly to satisfy the generator/typechecker
|
||||
const activatePayload = new Uint8Array([0x04]);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:456', '[DFU] sending activate/validate payload=', Array.from(activatePayload));
|
||||
await serviceManager.writeCharacteristic(deviceId, DFU_SERVICE_UUID, DFU_CONTROL_POINT_UUID, activatePayload, null);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:458', '[DFU] activate/validate write returned for', deviceId);
|
||||
}
|
||||
catch (e: any) {
|
||||
// 写入失败时取消控制等待,避免悬挂定时器
|
||||
try {
|
||||
const sessOnWriteFail = this.sessions.get(deviceId);
|
||||
if (sessOnWriteFail != null && typeof sessOnWriteFail.reject == 'function') {
|
||||
sessOnWriteFail.reject(e);
|
||||
}
|
||||
}
|
||||
catch (rejectErr: any) { }
|
||||
await controlResultPromise.catch(() => { });
|
||||
try {
|
||||
await serviceManager.unsubscribeCharacteristic(deviceId, DFU_SERVICE_UUID, DFU_CONTROL_POINT_UUID);
|
||||
}
|
||||
catch (e2: any) { }
|
||||
this.sessions.delete(deviceId);
|
||||
throw e;
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:472', '[DFU] sent control activate/validate command to control point for', deviceId);
|
||||
this._emitDfuEvent(deviceId, 'onValidating', null);
|
||||
// 等待 control-point 返回最终结果(成功或失败),超时可配置
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:476', '[DFU] waiting for control result (timeout=', controlTimeout, ') for', deviceId);
|
||||
try {
|
||||
await controlResultPromise;
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:479', '[DFU] control result resolved for', deviceId);
|
||||
}
|
||||
catch (err: any) {
|
||||
// 清理订阅后抛出
|
||||
try {
|
||||
await serviceManager.unsubscribeCharacteristic(deviceId, DFU_SERVICE_UUID, DFU_CONTROL_POINT_UUID);
|
||||
}
|
||||
catch (e: any) { }
|
||||
this.sessions.delete(deviceId);
|
||||
throw err;
|
||||
}
|
||||
// 取消订阅
|
||||
try {
|
||||
await serviceManager.unsubscribeCharacteristic(deviceId, DFU_SERVICE_UUID, DFU_CONTROL_POINT_UUID);
|
||||
}
|
||||
catch (e: any) { }
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:491', '[DFU] unsubscribed control point for', deviceId);
|
||||
// 清理会话
|
||||
this.sessions.delete(deviceId);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:495', '[DFU] session cleared for', deviceId);
|
||||
return;
|
||||
}
|
||||
async _requestMtu(gatt: BluetoothGatt, mtu: number, timeoutMs: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// 在当前项目,BluetoothGattCallback.onMtuChanged 未被封装;简单发起请求并等待短超时
|
||||
try {
|
||||
const ok = gatt.requestMtu(Math.floor(mtu) as Int);
|
||||
if (!ok) {
|
||||
return reject(new Error('requestMtu failed'));
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
return reject(e);
|
||||
}
|
||||
// 无 callback 监听时退回,等待一小段时间以便成功
|
||||
setTimeout(() => resolve(void 0), Math.min(2000, timeoutMs));
|
||||
});
|
||||
}
|
||||
_sleep(ms: number): Promise<void> {
|
||||
return new Promise<void>((r, _reject) => { setTimeout(() => { r(void 0); }, ms); });
|
||||
}
|
||||
_waitForPrn(deviceId: string, timeoutMs: number): Promise<void> {
|
||||
const session = this.sessions.get(deviceId);
|
||||
if (session == null)
|
||||
return Promise.reject(new Error('no dfu session'));
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
// timeout waiting for PRN
|
||||
// clear pending handlers
|
||||
session.prnResolve = null;
|
||||
session.prnReject = null;
|
||||
reject(new Error('PRN timeout'));
|
||||
}, timeoutMs);
|
||||
const prnResolve = () => {
|
||||
clearTimeout(timer);
|
||||
resolve(void 0);
|
||||
};
|
||||
const prnReject = (err?: any) => {
|
||||
clearTimeout(timer);
|
||||
reject(err);
|
||||
};
|
||||
session.prnResolve = prnResolve;
|
||||
session.prnReject = prnReject;
|
||||
});
|
||||
}
|
||||
// 默认 control point 解析器(非常通用的尝试解析:如果设备发送 progress byte 或成功码)
|
||||
_defaultControlParser(data: Uint8Array): ControlParserResult | null {
|
||||
// 假设协议:第一个字节为 opcode, 第二字节可为状态或进度
|
||||
if (data == null || data.length === 0)
|
||||
return null;
|
||||
const op = data[0];
|
||||
// Nordic-style response: [0x10, requestOp, resultCode]
|
||||
if (op === 0x10 && data.length >= 3) {
|
||||
const requestOp = data[1];
|
||||
const resultCode = data[2];
|
||||
if (resultCode === 0x01) {
|
||||
return { type: 'success' } as ControlParserResult;
|
||||
}
|
||||
return { type: 'error', error: { requestOp: requestOp, resultCode: resultCode, raw: Array.from(data) } } as ControlParserResult;
|
||||
}
|
||||
// Nordic PRN notification: [0x11, LSB, MSB] -> return as progress (bytes received)
|
||||
if (op === 0x11 && data.length >= 3) {
|
||||
const lsb = data[1];
|
||||
const msb = data[2];
|
||||
const received = (msb << 8) | lsb;
|
||||
return { type: 'progress', progress: received } as ControlParserResult;
|
||||
}
|
||||
// vendor-specific opcode example: 0x60 may mean 'response/progress' for some firmwares
|
||||
if (op === 0x60) {
|
||||
if (data.length >= 3) {
|
||||
const requestOp = data[1];
|
||||
const status = data[2];
|
||||
// Known success/status codes observed in field devices
|
||||
if (status === 0x00 || status === 0x01 || status === 0x0A) {
|
||||
return { type: 'success' } as ControlParserResult;
|
||||
}
|
||||
return { type: 'error', error: { requestOp: requestOp, resultCode: status, raw: Array.from(data) } } as ControlParserResult;
|
||||
}
|
||||
if (data.length >= 2) {
|
||||
return { type: 'progress', progress: data[1] } as ControlParserResult;
|
||||
}
|
||||
}
|
||||
// 通用进度回退:若第二字节位于 0-100 之间,当作百分比
|
||||
if (data.length >= 2) {
|
||||
const maybeProgress = data[1];
|
||||
if (maybeProgress >= 0 && maybeProgress <= 100) {
|
||||
return { type: 'progress', progress: maybeProgress } as ControlParserResult;
|
||||
}
|
||||
}
|
||||
// 若找到明显的 success opcode (示例 0x01) 或 error 0xFF
|
||||
if (op === 0x01)
|
||||
return { type: 'success' } as ControlParserResult;
|
||||
if (op === 0xFF)
|
||||
return { type: 'error', error: data } as ControlParserResult;
|
||||
return { type: 'info' } as ControlParserResult;
|
||||
}
|
||||
// Nordic DFU control-parser(支持 Response and Packet Receipt Notification)
|
||||
_nordicControlParser(data: Uint8Array): ControlParserResult | null {
|
||||
// Nordic opcodes (简化):
|
||||
// - 0x10 : Response (opcode, requestOp, resultCode)
|
||||
// - 0x11 : Packet Receipt Notification (opcode, value LSB, value MSB)
|
||||
if (data == null || data.length == 0)
|
||||
return null;
|
||||
const op = data[0];
|
||||
if (op == 0x11 && data.length >= 3) {
|
||||
// packet receipt notif: bytes received (little endian)
|
||||
const lsb = data[1];
|
||||
const msb = data[2];
|
||||
const received = (msb << 8) | lsb;
|
||||
// Return received bytes as progress value; parser does not resolve device-specific session here.
|
||||
return { type: 'progress', progress: received } as ControlParserResult;
|
||||
}
|
||||
// Nordic vendor-specific progress/response opcode (example 0x60)
|
||||
if (op == 0x60) {
|
||||
if (data.length >= 3) {
|
||||
const requestOp = data[1];
|
||||
const status = data[2];
|
||||
if (status == 0x00 || status == 0x01 || status == 0x0A) {
|
||||
return { type: 'success' } as ControlParserResult;
|
||||
}
|
||||
return { type: 'error', error: { requestOp, resultCode: status, raw: Array.from(data) } } as ControlParserResult;
|
||||
}
|
||||
if (data.length >= 2) {
|
||||
return { type: 'progress', progress: data[1] } as ControlParserResult;
|
||||
}
|
||||
}
|
||||
// Response: check result code for success (0x01 may indicate success in some stacks)
|
||||
if (op == 0x10 && data.length >= 3) {
|
||||
const requestOp = data[1];
|
||||
const resultCode = data[2];
|
||||
// Nordic resultCode 0x01 = SUCCESS typically
|
||||
if (resultCode == 0x01)
|
||||
return { type: 'success' } as ControlParserResult;
|
||||
else
|
||||
return { type: 'error', error: { requestOp, resultCode } } as ControlParserResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
_handleControlNotification(deviceId: string, data: Uint8Array) {
|
||||
const session = this.sessions.get(deviceId);
|
||||
if (session == null) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:636', '[DFU] control notification received but no session for', deviceId, 'data=', Array.from(data));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// human readable opcode mapping
|
||||
let opcodeName = 'unknown';
|
||||
switch (data[0]) {
|
||||
case 0x10:
|
||||
opcodeName = 'Response';
|
||||
break;
|
||||
case 0x11:
|
||||
opcodeName = 'PRN';
|
||||
break;
|
||||
case 0x60:
|
||||
opcodeName = 'VendorProgress';
|
||||
break;
|
||||
case 0x01:
|
||||
opcodeName = 'SuccessOpcode';
|
||||
break;
|
||||
case 0xFF:
|
||||
opcodeName = 'ErrorOpcode';
|
||||
break;
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:649', '[DFU] _handleControlNotification deviceId=', deviceId, 'opcode=0x' + data[0].toString(16), 'name=', opcodeName, 'raw=', Array.from(data));
|
||||
const parsed = session.controlParser != null ? session.controlParser!(data) : null;
|
||||
if (session.onLog != null)
|
||||
session.onLog!('DFU control notify: ' + Array.from(data).join(','));
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:652', '[DFU] parsed control result=', parsed);
|
||||
if (parsed == null)
|
||||
return;
|
||||
if (parsed.type == 'progress' && parsed.progress != null) {
|
||||
// 如果在 nordic 模式 parsed.progress 可能是已接收字节数,则转换为百分比
|
||||
if (session.useNordic == true && session.totalBytes != null && session.totalBytes! > 0) {
|
||||
const percent = Math.floor((parsed.progress! / session.totalBytes!) * 100);
|
||||
session.onProgress?.(percent);
|
||||
// If we have written all bytes locally, log that event
|
||||
if (session.bytesSent != null && session.totalBytes != null && session.bytesSent! >= session.totalBytes!) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:661', '[DFU] all bytes written locally for', deviceId, 'bytesSent=', session.bytesSent, 'total=', session.totalBytes);
|
||||
// emit uploading completed once
|
||||
this._emitDfuEvent(deviceId, 'onUploadingCompleted', null);
|
||||
}
|
||||
// If a PRN wait is pending, resolve it (PRN indicates device received packets)
|
||||
if (typeof session.prnResolve == 'function') {
|
||||
try {
|
||||
session.prnResolve!();
|
||||
}
|
||||
catch (e: any) { }
|
||||
session.prnResolve = null;
|
||||
session.prnReject = null;
|
||||
session.packetsSincePrn = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const progress = parsed.progress!;
|
||||
if (progress != null) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:675', '[DFU] progress for', deviceId, 'progress=', progress);
|
||||
session.onProgress?.(progress);
|
||||
// also resolve PRN if was waiting (in case device reports numeric progress)
|
||||
if (typeof session.prnResolve == 'function') {
|
||||
try {
|
||||
session.prnResolve!();
|
||||
}
|
||||
catch (e: any) { }
|
||||
session.prnResolve = null;
|
||||
session.prnReject = null;
|
||||
session.packetsSincePrn = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (parsed.type == 'success') {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:687', '[DFU] parsed success for', deviceId, 'resolving session');
|
||||
session.resolve();
|
||||
// Log final device-acknowledged success
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:690', '[DFU] device reported DFU success for', deviceId);
|
||||
this._emitDfuEvent(deviceId, 'onDfuCompleted', null);
|
||||
}
|
||||
else if (parsed.type == 'error') {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:693', '[DFU] parsed error for', deviceId, parsed.error);
|
||||
session.reject(parsed.error ?? new Error('DFU device error'));
|
||||
this._emitDfuEvent(deviceId, 'onError', parsed.error ?? {});
|
||||
}
|
||||
else {
|
||||
// info - just log
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
session.onLog?.('control parse error: ' + e);
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:701', '[DFU] control parse exception for', deviceId, e);
|
||||
}
|
||||
}
|
||||
_waitForControlResult(deviceId: string, timeoutMs: number): Promise<void> {
|
||||
const session = this.sessions.get(deviceId);
|
||||
if (session == null)
|
||||
return Promise.reject(new Error('no dfu session'));
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// wrap resolve/reject to clear timer
|
||||
const timer = setTimeout(() => {
|
||||
// 超时
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:712', '[DFU] _waitForControlResult timeout for', deviceId);
|
||||
reject(new Error('DFU control timeout'));
|
||||
}, timeoutMs);
|
||||
const origResolve = () => {
|
||||
clearTimeout(timer);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:717', '[DFU] _waitForControlResult resolved for', deviceId);
|
||||
resolve(void 0);
|
||||
};
|
||||
const origReject = (err?: any) => {
|
||||
clearTimeout(timer);
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts:722', '[DFU] _waitForControlResult rejected for', deviceId, 'err=', err);
|
||||
reject(err);
|
||||
};
|
||||
// replace session handlers temporarily (guard nullable)
|
||||
if (session != null) {
|
||||
session.resolve = origResolve;
|
||||
session.reject = origReject;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export const dfuManager = new DfuManager();
|
||||
//# sourceMappingURL=dfu_manager.uts.map
|
||||
File diff suppressed because one or more lines are too long
113
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/index.uts
vendored
Normal file
113
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/index.uts
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
import * as BluetoothManager from './bluetooth_manager.uts';
|
||||
import { ServiceManager } from './service_manager.uts';
|
||||
import type { ScanDevicesOptions, BleConnectOptionsExt, MultiProtocolDevice, BleEvent, BleEventCallback, BleService, BleCharacteristic, WriteCharacteristicOptions, AutoBleInterfaces, BleDataReceivedCallback } from '../interface.uts';
|
||||
import { DeviceManager } from './device_manager.uts';
|
||||
const serviceManager = ServiceManager.getInstance();
|
||||
export class BluetoothService {
|
||||
scanDevices(options?: ScanDevicesOptions): Promise<void> { return BluetoothManager.scanDevices(options); }
|
||||
connectDevice(deviceId: string, protocol: string, options?: BleConnectOptionsExt): Promise<void> { return BluetoothManager.connectDevice(deviceId, protocol, options); }
|
||||
disconnectDevice(deviceId: string, protocol: string): Promise<void> { return BluetoothManager.disconnectDevice(deviceId, protocol); }
|
||||
getConnectedDevices(): MultiProtocolDevice[] { return BluetoothManager.getConnectedDevices(); }
|
||||
on(event: BleEvent, callback: BleEventCallback) { return BluetoothManager.on(event, callback); }
|
||||
off(event: BleEvent, callback?: BleEventCallback) { return BluetoothManager.off(event, callback); }
|
||||
getServices(deviceId: string): Promise<BleService[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
serviceManager.getServices(deviceId, (list, err) => {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:18', 'getServices:', list, err); // 新增日志
|
||||
if (err != null)
|
||||
reject(err);
|
||||
else
|
||||
resolve((list as BleService[]) ?? []);
|
||||
});
|
||||
});
|
||||
}
|
||||
getCharacteristics(deviceId: string, serviceId: string): Promise<BleCharacteristic[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:26', deviceId, serviceId);
|
||||
serviceManager.getCharacteristics(deviceId, serviceId, (list, err) => {
|
||||
if (err != null)
|
||||
reject(err);
|
||||
else
|
||||
resolve((list as BleCharacteristic[]) ?? []);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 自动发现服务和特征,返回可用的写入和通知特征ID
|
||||
* @param deviceId 设备ID
|
||||
* @returns {Promise<AutoBleInterfaces>}
|
||||
*/
|
||||
async getAutoBleInterfaces(deviceId: string): Promise<AutoBleInterfaces> {
|
||||
// 1. 获取服务列表
|
||||
const services = await this.getServices(deviceId);
|
||||
if (services == null || services.length == 0)
|
||||
throw new Error('未发现服务');
|
||||
// 2. 选择目标服务(优先bae前缀,可根据需要调整)
|
||||
let serviceId = '';
|
||||
for (let i = 0; i < services.length; i++) {
|
||||
const s = services[i];
|
||||
const uuidCandidate: string | null = (s.uuid != null ? s.uuid : null);
|
||||
const uuid: string = uuidCandidate != null ? uuidCandidate : '';
|
||||
// prefer regex test to avoid nullable receiver calls in generated Kotlin
|
||||
if (/^bae/i.test(uuid)) {
|
||||
serviceId = uuid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:55', serviceId);
|
||||
if (serviceId == null || serviceId == '')
|
||||
serviceId = services[0].uuid;
|
||||
// 3. 获取特征列表
|
||||
const characteristics = await this.getCharacteristics(deviceId, serviceId);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:60', characteristics);
|
||||
if (characteristics == null || characteristics.length == 0)
|
||||
throw new Error('未发现特征值');
|
||||
// 4. 筛选write和notify特征
|
||||
let writeCharId = '';
|
||||
let notifyCharId = '';
|
||||
for (let i = 0; i < characteristics.length; i++) {
|
||||
const c = characteristics[i];
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:69', c);
|
||||
if ((writeCharId == null || writeCharId == '') && c.properties != null && (c.properties.write || c.properties.writeWithoutResponse == true))
|
||||
writeCharId = c.uuid;
|
||||
if ((notifyCharId == null || notifyCharId == '') && c.properties != null && (c.properties.notify || c.properties.indicate))
|
||||
notifyCharId = c.uuid;
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:73', serviceId, writeCharId, notifyCharId);
|
||||
if ((writeCharId == null || writeCharId == '') || (notifyCharId == null || notifyCharId == ''))
|
||||
throw new Error('未找到合适的写入或通知特征');
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:75', serviceId, writeCharId, notifyCharId);
|
||||
// // 发现服务和特征后
|
||||
const deviceManager = DeviceManager.getInstance();
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:78', deviceManager);
|
||||
const device = deviceManager.getDevice(deviceId);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:80', deviceId, device);
|
||||
device!.serviceId = serviceId;
|
||||
device!.writeCharId = writeCharId;
|
||||
device!.notifyCharId = notifyCharId;
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/index.uts:84', device);
|
||||
return { serviceId, writeCharId, notifyCharId } as AutoBleInterfaces;
|
||||
}
|
||||
async subscribeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, onData: BleDataReceivedCallback): Promise<void> {
|
||||
return serviceManager.subscribeCharacteristic(deviceId, serviceId, characteristicId, onData);
|
||||
}
|
||||
async readCharacteristic(deviceId: string, serviceId: string, characteristicId: string): Promise<ArrayBuffer> {
|
||||
return serviceManager.readCharacteristic(deviceId, serviceId, characteristicId);
|
||||
}
|
||||
async writeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, data: Uint8Array, options?: WriteCharacteristicOptions): Promise<boolean> {
|
||||
return serviceManager.writeCharacteristic(deviceId, serviceId, characteristicId, data, options);
|
||||
}
|
||||
async unsubscribeCharacteristic(deviceId: string, serviceId: string, characteristicId: string): Promise<void> {
|
||||
return serviceManager.unsubscribeCharacteristic(deviceId, serviceId, characteristicId);
|
||||
}
|
||||
async autoDiscoverAll(deviceId: string): Promise<any> {
|
||||
return serviceManager.autoDiscoverAll(deviceId);
|
||||
}
|
||||
async subscribeAllNotifications(deviceId: string, onData: BleDataReceivedCallback): Promise<void> {
|
||||
return serviceManager.subscribeAllNotifications(deviceId, onData);
|
||||
}
|
||||
}
|
||||
export const bluetoothService = new BluetoothService();
|
||||
// Ensure protocol handlers are registered when this module is imported.
|
||||
// import './protocol_registry.uts';
|
||||
//# sourceMappingURL=index.uts.map
|
||||
1
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/index.uts.map
vendored
Normal file
1
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/index.uts.map
vendored
Normal file
File diff suppressed because one or more lines are too long
711
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts
vendored
Normal file
711
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts
vendored
Normal file
@@ -0,0 +1,711 @@
|
||||
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|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:108', 'ak onServicesDiscovered');
|
||||
const deviceId = gatt.getDevice().getAddress();
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:111', `服务发现成功: ${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 {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:139', `服务发现失败: ${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) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:154', `设备已连接: ${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:157', `设备已断开: ${deviceId}`);
|
||||
serviceDiscovered.delete(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:163', '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:170', '[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;
|
||||
}
|
||||
// 保存接收日志
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:179', `
|
||||
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:188', '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:195', '[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: any) {
|
||||
try {
|
||||
pending.reject(e);
|
||||
}
|
||||
catch (e2: any) {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:218', e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int): void {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:224', '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:230', '[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:244', 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[]> {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:267', '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:273', '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:277', 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)) {
|
||||
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:347', 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:362', '[ServiceManager] characteristic uuid=', charUuid);
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:363', '[ServiceManager] failed to read char uuid', e);
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:364', 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:385', 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:391', '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:400', '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:406', '[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:409', '[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:414', '[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:419', '[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:454', '[writeCharacteristic] timeout');
|
||||
resolve(false);
|
||||
}, initialTimeout);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:457', '[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:459', '[writeCharacteristic] resolveAdapter called');
|
||||
resolve(true);
|
||||
};
|
||||
const rejectAdapter = (err?: any) => {
|
||||
__f__('error', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:463', '[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();
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:475', '[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: any) { }
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:485', '[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:488', '[writeCharacteristic] using WRITE_TYPE_DEFAULT');
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:491', '[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:501', '[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:505', '[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:518', '[writeCharacteristic] attempt', att, 'calling gatt.writeCharacteristic');
|
||||
const r = gattInstance.writeCharacteristic(char);
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:520', '[writeCharacteristic] attempt', att, 'result=', r);
|
||||
if (r == true) {
|
||||
if (usesNoResponse) {
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:523', '[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:533', '[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:541', '[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:551', '[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:557', '[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:560', '[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:568', '[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:578', '[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:608', 'subscribeCharacteristic: CCCD written for notify', writedescript);
|
||||
}
|
||||
else {
|
||||
__f__('warn', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:610', 'subscribeCharacteristic: CCCD descriptor not found!');
|
||||
}
|
||||
__f__('log', 'at uni_modules/ak-sbsrv/utssdk/app-android/service_manager.uts:612', '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:658', `订阅特征 ${char.uuid} 失败:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=service_manager.uts.map
|
||||
File diff suppressed because one or more lines are too long
409
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/interface.uts
vendored
Normal file
409
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/interface.uts
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
// 蓝牙相关接口和类型定义
|
||||
// 基础设备信息类型
|
||||
export type BleDeviceInfo = {
|
||||
deviceId: string;
|
||||
name: string;
|
||||
RSSI?: number;
|
||||
connected?: boolean;
|
||||
// 新增
|
||||
serviceId?: string;
|
||||
writeCharId?: string;
|
||||
notifyCharId?: string;
|
||||
};
|
||||
export type AutoDiscoverAllResult = {
|
||||
services: BleService[];
|
||||
characteristics: BleCharacteristic[];
|
||||
};
|
||||
// 服务信息类型
|
||||
export type BleServiceInfo = {
|
||||
uuid: string;
|
||||
isPrimary: boolean;
|
||||
};
|
||||
// 特征值属性类型
|
||||
export type BleCharacteristicProperties = {
|
||||
read: boolean;
|
||||
write: boolean;
|
||||
notify: boolean;
|
||||
indicate: boolean;
|
||||
writeWithoutResponse?: boolean;
|
||||
canRead?: boolean;
|
||||
canWrite?: boolean;
|
||||
canNotify?: boolean;
|
||||
};
|
||||
// 特征值信息类型
|
||||
export type BleCharacteristicInfo = {
|
||||
uuid: string;
|
||||
serviceId: string;
|
||||
properties: BleCharacteristicProperties;
|
||||
};
|
||||
// 错误状态码
|
||||
export enum BleErrorCode {
|
||||
UNKNOWN_ERROR = 0,
|
||||
BLUETOOTH_UNAVAILABLE = 1,
|
||||
PERMISSION_DENIED = 2,
|
||||
DEVICE_NOT_CONNECTED = 3,
|
||||
SERVICE_NOT_FOUND = 4,
|
||||
CHARACTERISTIC_NOT_FOUND = 5,
|
||||
OPERATION_TIMEOUT = 6
|
||||
}
|
||||
// 命令类型
|
||||
export enum CommandType {
|
||||
BATTERY = 1,
|
||||
DEVICE_INFO = 2,
|
||||
CUSTOM = 99,
|
||||
TestBatteryLevel = 0x01
|
||||
}
|
||||
// 错误接口
|
||||
export type BleError = {
|
||||
errCode: number;
|
||||
errMsg: string;
|
||||
errSubject?: string;
|
||||
};
|
||||
// 连接选项
|
||||
export type BleConnectOptions = {
|
||||
deviceId: string;
|
||||
timeout?: number;
|
||||
success?: (result: any) => void;
|
||||
fail?: (error: BleError) => void;
|
||||
complete?: (result: any) => void;
|
||||
};
|
||||
// 断开连接选项
|
||||
export type BleDisconnectOptions = {
|
||||
deviceId: string;
|
||||
success?: (result: any) => void;
|
||||
fail?: (error: BleError) => void;
|
||||
complete?: (result: any) => void;
|
||||
};
|
||||
// 获取特征值选项
|
||||
export type BleCharacteristicOptions = {
|
||||
deviceId: string;
|
||||
serviceId: string;
|
||||
characteristicId: string;
|
||||
success?: (result: any) => void;
|
||||
fail?: (error: BleError) => void;
|
||||
complete?: (result: any) => void;
|
||||
};
|
||||
// 写入特征值选项
|
||||
export type BleWriteOptions = {
|
||||
deviceId: string;
|
||||
serviceId: string;
|
||||
characteristicId: string;
|
||||
value: Uint8Array;
|
||||
writeType?: number;
|
||||
success?: (result: any) => void;
|
||||
fail?: (error: BleError) => void;
|
||||
complete?: (result: any) => void;
|
||||
};
|
||||
// Options for writeCharacteristic helper
|
||||
export type WriteCharacteristicOptions = {
|
||||
waitForResponse?: boolean;
|
||||
maxAttempts?: number;
|
||||
retryDelayMs?: number;
|
||||
giveupTimeoutMs?: number;
|
||||
forceWriteTypeNoResponse?: boolean;
|
||||
};
|
||||
// 通知特征值回调函数
|
||||
export type BleNotifyCallback = (data: Uint8Array) => void;
|
||||
// 通知特征值选项
|
||||
export type BleNotifyOptions = {
|
||||
deviceId: string;
|
||||
serviceId: string;
|
||||
characteristicId: string;
|
||||
state?: boolean; // true: 启用通知,false: 禁用通知
|
||||
onCharacteristicValueChange: BleNotifyCallback;
|
||||
success?: (result: any) => void;
|
||||
fail?: (error: BleError) => void;
|
||||
complete?: (result: any) => void;
|
||||
};
|
||||
// 获取服务选项
|
||||
export type BleDeviceServicesOptions = {
|
||||
deviceId: string;
|
||||
success?: (result: BleServicesResult) => void;
|
||||
fail?: (error: BleError) => void;
|
||||
complete?: (result: any) => void;
|
||||
};
|
||||
// 获取特征值选项
|
||||
export type BleDeviceCharacteristicsOptions = {
|
||||
deviceId: string;
|
||||
serviceId: string;
|
||||
success?: (result: BleCharacteristicsResult) => void;
|
||||
fail?: (error: BleError) => void;
|
||||
complete?: (result: any) => void;
|
||||
};
|
||||
// 蓝牙扫描选项
|
||||
export type BluetoothScanOptions = {
|
||||
services?: string[];
|
||||
timeout?: number;
|
||||
onDeviceFound?: (device: BleDeviceInfo) => void;
|
||||
success?: (result: BleScanResult) => void;
|
||||
fail?: (error: BleError) => void;
|
||||
complete?: (result: any) => void;
|
||||
};
|
||||
// 扫描结果
|
||||
// 服务结果
|
||||
export type BleServicesResult = {
|
||||
services: BleServiceInfo[];
|
||||
errMsg?: string;
|
||||
};
|
||||
// 特征值结果
|
||||
export type BleCharacteristicsResult = {
|
||||
characteristics: BleCharacteristicInfo[];
|
||||
errMsg?: string;
|
||||
};
|
||||
// 定义连接状态枚举
|
||||
export enum BLE_CONNECTION_STATE {
|
||||
DISCONNECTED = 0,
|
||||
CONNECTING = 1,
|
||||
CONNECTED = 2,
|
||||
DISCONNECTING = 3
|
||||
}
|
||||
// 电池状态类型定义
|
||||
export type BatteryStatus = {
|
||||
batteryLevel: number; // 电量百分比
|
||||
isCharging: boolean; // 充电状态
|
||||
};
|
||||
// 蓝牙服务接口类型定义 - 转换为type类型
|
||||
export type BleService = {
|
||||
uuid: string;
|
||||
isPrimary: boolean;
|
||||
};
|
||||
// 蓝牙特征值接口定义 - 转换为type类型
|
||||
export type BleCharacteristic = {
|
||||
uuid: string;
|
||||
service: BleService;
|
||||
properties: BleCharacteristicProperties;
|
||||
};
|
||||
// PendingPromise接口定义
|
||||
export interface PendingCallback {
|
||||
resolve: (data: any) => void;
|
||||
reject: (err?: any) => void;
|
||||
timer?: number;
|
||||
}
|
||||
// 蓝牙相关接口和类型定义
|
||||
export type BleDevice = {
|
||||
deviceId: string;
|
||||
name: string;
|
||||
rssi?: number;
|
||||
lastSeen?: number; // 新增
|
||||
// 新增
|
||||
serviceId?: string;
|
||||
writeCharId?: string;
|
||||
notifyCharId?: string;
|
||||
};
|
||||
// BLE常规选项
|
||||
export type BleOptions = {
|
||||
timeout?: number;
|
||||
success?: (result: any) => void;
|
||||
fail?: (error: any) => void;
|
||||
complete?: () => void;
|
||||
};
|
||||
export type BleConnectionState = number; // 0: DISCONNECTED, 1: CONNECTING, 2: CONNECTED, 3: DISCONNECTING
|
||||
export type BleConnectOptionsExt = {
|
||||
timeout?: number;
|
||||
services?: string[];
|
||||
requireResponse?: boolean;
|
||||
autoReconnect?: boolean;
|
||||
maxAttempts?: number;
|
||||
interval?: number;
|
||||
};
|
||||
// 回调函数类型
|
||||
export type BleDeviceFoundCallback = (device: BleDevice) => void;
|
||||
export type BleConnectionStateChangeCallback = (deviceId: string, state: BleConnectionState) => void;
|
||||
export type BleDataPayload = {
|
||||
deviceId: string;
|
||||
serviceId?: string;
|
||||
characteristicId?: string;
|
||||
data: string | ArrayBuffer;
|
||||
format?: number; // 0: JSON, 1: XML, 2: RAW
|
||||
};
|
||||
export type BleDataSentCallback = (payload: BleDataPayload, success: boolean, error?: BleError) => void;
|
||||
export type BleErrorCallback = (error: BleError) => void;
|
||||
// 健康数据类型定义
|
||||
export enum HealthDataType {
|
||||
HEART_RATE = 1,
|
||||
BLOOD_OXYGEN = 2,
|
||||
TEMPERATURE = 3,
|
||||
STEP_COUNT = 4,
|
||||
SLEEP_DATA = 5,
|
||||
HEALTH_DATA = 6
|
||||
}
|
||||
// Platform-specific services should be imported from per-platform entrypoints
|
||||
// (e.g. './app-android/index.uts' or './web/index.uts').
|
||||
// Avoid re-exporting platform modules at the SDK root to prevent bundlers
|
||||
// Platform-specific services should be imported from per-platform entrypoints
|
||||
// (e.g. './app-android/index.uts' or './web/index.uts').
|
||||
// Avoid re-exporting platform modules at the SDK root to prevent bundlers
|
||||
// from pulling android.* symbols into web bundles.
|
||||
// If a typed ambient reference is required, declare the shape here instead of importing implementation.
|
||||
// Example lightweight typed placeholder (do not import platform code here):
|
||||
// export type BluetoothService = any; // platform-specific implementation exported from platform index files
|
||||
// ====== 新增多协议、统一事件、协议适配、状态管理支持 ======
|
||||
export type BleProtocolType = 'standard' | 'custom' | 'health' | 'ibeacon' | 'mesh';
|
||||
export type BleEvent = 'deviceFound' | 'scanFinished' | 'connectionStateChanged' | 'dataReceived' | 'dataSent' | 'error' | 'servicesDiscovered' | 'connected' // 新增
|
||||
| 'disconnected'; // 新增
|
||||
// 事件回调参数
|
||||
export type BleEventPayload = {
|
||||
event: BleEvent;
|
||||
device?: BleDevice;
|
||||
protocol?: BleProtocolType;
|
||||
state?: BleConnectionState;
|
||||
data?: ArrayBuffer | string | object;
|
||||
format?: string;
|
||||
error?: BleError;
|
||||
extra?: any;
|
||||
};
|
||||
// 事件回调函数
|
||||
export type BleEventCallback = (payload: BleEventPayload) => void;
|
||||
// 多协议设备信息(去除交叉类型,直接展开字段)
|
||||
export type MultiProtocolDevice = {
|
||||
deviceId: string;
|
||||
name: string;
|
||||
rssi?: number;
|
||||
protocol: BleProtocolType;
|
||||
};
|
||||
export type ScanDevicesOptions = {
|
||||
protocols?: BleProtocolType[];
|
||||
optionalServices?: string[];
|
||||
timeout?: number;
|
||||
onDeviceFound?: (device: BleDevice) => void;
|
||||
onScanFinished?: () => void;
|
||||
};
|
||||
// Named payload type used by sendData
|
||||
export type SendDataPayload = {
|
||||
deviceId: string;
|
||||
serviceId?: string;
|
||||
characteristicId?: string;
|
||||
data: string | ArrayBuffer;
|
||||
format?: number;
|
||||
protocol: BleProtocolType;
|
||||
};
|
||||
// 协议处理器接口(为 protocol-handler 适配器预留)
|
||||
export type ScanHandler = {
|
||||
protocol: BleProtocolType;
|
||||
scanDevices?: (options: ScanDevicesOptions) => Promise<void>;
|
||||
connect: (device: BleDevice, options?: BleConnectOptionsExt) => Promise<void>;
|
||||
disconnect: (device: BleDevice) => Promise<void>;
|
||||
// Optional: send arbitrary data via the protocol's write characteristic
|
||||
sendData?: (device: BleDevice, payload: SendDataPayload, options?: BleOptions) => Promise<void>;
|
||||
// Optional: try to connect and discover service/characteristic ids for this device
|
||||
autoConnect?: (device: BleDevice, options?: BleConnectOptionsExt) => Promise<AutoBleInterfaces>;
|
||||
};
|
||||
// 自动发现服务和特征返回类型
|
||||
export type AutoBleInterfaces = {
|
||||
serviceId: string;
|
||||
writeCharId: string;
|
||||
notifyCharId: string;
|
||||
};
|
||||
export type ResponseCallbackEntry = {
|
||||
cb: (data: Uint8Array) => boolean | void;
|
||||
multi: boolean;
|
||||
};
|
||||
// Result returned by a DFU control parser. Use a plain string `type` to keep
|
||||
// the generated Kotlin simple and avoid inline union types which the generator
|
||||
// does not handle well.
|
||||
export type ControlParserResult = {
|
||||
type: string; // e.g. 'progress', 'success', 'error', 'info'
|
||||
progress?: number;
|
||||
error?: any;
|
||||
};
|
||||
// DFU types
|
||||
export type DfuOptions = {
|
||||
mtu?: number;
|
||||
useNordic?: boolean;
|
||||
// If true, the DFU upload will await a write response per-packet. Set false to use
|
||||
// WRITE_NO_RESPONSE (fire-and-forget) for higher throughput. Default: false.
|
||||
waitForResponse?: boolean;
|
||||
// Maximum number of outstanding NO_RESPONSE writes to allow before throttling.
|
||||
// This implements a simple sliding window. Default: 32.
|
||||
maxOutstanding?: number;
|
||||
// Per-chunk sleep (ms) to yield to event loop / Android BLE stack. Default: 2.
|
||||
writeSleepMs?: number;
|
||||
// Retry delay (ms) used by the Android write helper when gatt.writeCharacteristic
|
||||
// returns false. Smaller values can improve throughput on congested stacks.
|
||||
writeRetryDelayMs?: number;
|
||||
// Maximum number of immediate write attempts before falling back to the give-up timeout.
|
||||
writeMaxAttempts?: number;
|
||||
// Timeout (ms) to wait for a late onCharacteristicWrite callback after all retries fail.
|
||||
writeGiveupTimeoutMs?: number;
|
||||
// Packet Receipt Notification (PRN) window size in packets. If set, DFU
|
||||
// manager will send a Set PRN command to the device and wait for PRN
|
||||
// notifications after this many packets. Default: 12.
|
||||
prn?: number;
|
||||
// Timeout (ms) to wait for a PRN notification once the window is reached.
|
||||
// Default: 10000 (10s).
|
||||
prnTimeoutMs?: number;
|
||||
// When true, disable PRN waits automatically after the first timeout to prevent
|
||||
// repeated long stalls on devices that do not send PRNs. Default: true.
|
||||
disablePrnOnTimeout?: boolean;
|
||||
// Time (ms) to wait for outstanding fire-and-forget writes to drain before issuing
|
||||
// the activate/validate control command. Default: 3000.
|
||||
drainOutstandingTimeoutMs?: number;
|
||||
controlTimeout?: number;
|
||||
onProgress?: (percent: number) => void;
|
||||
onLog?: (message: string) => void;
|
||||
controlParser?: (data: Uint8Array) => ControlParserResult | null;
|
||||
};
|
||||
export type DfuManagerType = {
|
||||
startDfu: (deviceId: string, firmwareBytes: Uint8Array, options?: DfuOptions) => Promise<void>;
|
||||
};
|
||||
// Lightweight runtime / UTS shims and missing types
|
||||
// These are conservative placeholders to satisfy typings used across platform files.
|
||||
// UTSJSONObject: bundler environments used by the build may not support
|
||||
// TypeScript-style index signatures in this .uts context. Use a conservative
|
||||
// 'any' alias so generated code doesn't rely on unsupported syntax while
|
||||
// preserving a usable type at the source level.
|
||||
export type UTSJSONObject = any;
|
||||
// ByteArray / Int are used in the Android platform code to interop with Java APIs.
|
||||
// Define minimal aliases so source can compile. Runtime uses Uint8Array and number.
|
||||
export type ByteArray = any; // runtime will use Java byte[] via UTS bridge; keep as any here
|
||||
// Callback types used by service_manager and index wrappers
|
||||
export type BleDataReceivedCallback = (data: Uint8Array) => void;
|
||||
export type BleScanResult = {
|
||||
deviceId: string;
|
||||
name?: string;
|
||||
rssi?: number;
|
||||
advertising?: any;
|
||||
};
|
||||
// Minimal UI / framework placeholders (some files reference these in types only)
|
||||
export type ComponentPublicInstance = any;
|
||||
export type UniElement = any;
|
||||
export type UniPage = any;
|
||||
// Platform service placeholder (actual implementation exported from platform index files)
|
||||
// Provide a lightweight, strongly-shaped class skeleton so source-level code
|
||||
// (and the code generator) can rely on concrete method names and signatures.
|
||||
// Implementations are platform-specific and exported from per-platform index
|
||||
// files (e.g. './app-android/index.uts' or './web/index.uts'). This class is
|
||||
// intentionally thin — it declares method signatures used across pages and
|
||||
// platform shims so the generator emits resolvable Kotlin symbols.
|
||||
export class BluetoothService {
|
||||
// Event emitter style
|
||||
on(event: BleEvent | string, callback: BleEventCallback): void { }
|
||||
off(event: BleEvent | string, callback?: BleEventCallback): void { }
|
||||
// Scanning / discovery
|
||||
scanDevices(options?: ScanDevicesOptions): Promise<void> { return Promise.resolve(); }
|
||||
// Connection management
|
||||
connectDevice(deviceId: string, protocol?: string, options?: BleConnectOptionsExt): Promise<void> { return Promise.resolve(); }
|
||||
disconnectDevice(deviceId: string, protocol?: string): Promise<void> { return Promise.resolve(); }
|
||||
getConnectedDevices(): MultiProtocolDevice[] { return []; }
|
||||
// Services / characteristics
|
||||
getServices(deviceId: string): Promise<BleService[]> { return Promise.resolve([]); }
|
||||
getCharacteristics(deviceId: string, serviceId: string): Promise<BleCharacteristic[]> { return Promise.resolve([]); }
|
||||
// Read / write / notify
|
||||
readCharacteristic(deviceId: string, serviceId: string, characteristicId: string): Promise<ArrayBuffer> { return Promise.resolve(new ArrayBuffer(0)); }
|
||||
writeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, value: Uint8Array | ArrayBuffer, options?: WriteCharacteristicOptions): Promise<boolean> { return Promise.resolve(true); }
|
||||
subscribeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, callback: BleNotifyCallback): Promise<void> { return Promise.resolve(); }
|
||||
unsubscribeCharacteristic(deviceId: string, serviceId: string, characteristicId: string): Promise<void> { return Promise.resolve(); }
|
||||
// Convenience helpers
|
||||
getAutoBleInterfaces(deviceId: string): Promise<AutoBleInterfaces> {
|
||||
const res: AutoBleInterfaces = { serviceId: '', writeCharId: '', notifyCharId: '' };
|
||||
return Promise.resolve(res);
|
||||
}
|
||||
}
|
||||
// Runtime protocol handler base class. Exporting a concrete class ensures the
|
||||
// generator emits a resolvable runtime type that platform handlers can extend.
|
||||
// Source-level code can still use the ScanHandler type for typing.
|
||||
// Runtime ProtocolHandler is implemented in `protocol_handler.uts`.
|
||||
// Keep the public typing in this file minimal to avoid duplicate runtime
|
||||
// declarations. Consumers that need the runtime class should import it from
|
||||
// './protocol_handler.uts'.
|
||||
//# sourceMappingURL=interface.uts.map
|
||||
1
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/interface.uts.map
vendored
Normal file
1
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/interface.uts.map
vendored
Normal file
File diff suppressed because one or more lines are too long
127
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/protocol_handler.uts
vendored
Normal file
127
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/protocol_handler.uts
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// Minimal ProtocolHandler runtime class used by pages and components.
|
||||
// This class adapts the platform `BluetoothService` to a small protocol API
|
||||
// expected by pages: setConnectionParameters, initialize, testBatteryLevel,
|
||||
// testVersionInfo. Implemented conservatively to avoid heavy dependencies.
|
||||
import type { BluetoothService, AutoBleInterfaces, AutoDiscoverAllResult, BleService, BleCharacteristic, BleProtocolType, BleDevice, ScanDevicesOptions, BleConnectOptionsExt, SendDataPayload, BleOptions } from './interface.uts';
|
||||
export class ProtocolHandler {
|
||||
// bluetoothService may be omitted for lightweight wrappers; allow null
|
||||
bluetoothService: BluetoothService | null = null;
|
||||
protocol: BleProtocolType = 'standard';
|
||||
deviceId: string | null = null;
|
||||
serviceId: string | null = null;
|
||||
writeCharId: string | null = null;
|
||||
notifyCharId: string | null = null;
|
||||
initialized: boolean = false;
|
||||
// Accept an optional BluetoothService so wrapper subclasses can call
|
||||
// `super()` without forcing a runtime instance.
|
||||
constructor(bluetoothService?: BluetoothService) {
|
||||
if (bluetoothService != null)
|
||||
this.bluetoothService = bluetoothService;
|
||||
}
|
||||
setConnectionParameters(deviceId: string, serviceId: string, writeCharId: string, notifyCharId: string) {
|
||||
this.deviceId = deviceId;
|
||||
this.serviceId = serviceId;
|
||||
this.writeCharId = writeCharId;
|
||||
this.notifyCharId = notifyCharId;
|
||||
}
|
||||
// initialize: optional setup, returns a Promise that resolves when ready
|
||||
async initialize(): Promise<void> {
|
||||
// Simple async initializer — keep implementation minimal and generator-friendly.
|
||||
try {
|
||||
// If bluetoothService exposes any protocol-specific setup, call it here.
|
||||
this.initialized = true;
|
||||
return;
|
||||
}
|
||||
catch (e: any) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
// Protocol lifecycle / operations — default no-ops so generated code has
|
||||
// concrete member references and platform-specific handlers can override.
|
||||
async scanDevices(options?: ScanDevicesOptions): Promise<void> { return; }
|
||||
async connect(device: BleDevice, options?: BleConnectOptionsExt): Promise<void> { return; }
|
||||
async disconnect(device: BleDevice): Promise<void> { return; }
|
||||
async sendData(device: BleDevice, payload?: SendDataPayload, options?: BleOptions): Promise<void> { return; }
|
||||
async autoConnect(device: BleDevice, options?: BleConnectOptionsExt): Promise<AutoBleInterfaces> { return { serviceId: '', writeCharId: '', notifyCharId: '' } as AutoBleInterfaces; }
|
||||
// Example: testBatteryLevel will attempt to read the battery characteristic
|
||||
// if write/notify-based protocol is not available. Returns number percentage.
|
||||
async testBatteryLevel(): Promise<number> {
|
||||
if (this.deviceId == null)
|
||||
throw new Error('deviceId not set');
|
||||
// copy to local so Kotlin generator can smart-cast the value across awaits
|
||||
const deviceId = this.deviceId!;
|
||||
// try reading standard Battery characteristic (180F -> 2A19)
|
||||
if (this.bluetoothService == null)
|
||||
throw new Error('bluetoothService not set');
|
||||
const services = await this.bluetoothService!.getServices(deviceId);
|
||||
let found: BleService | null = null;
|
||||
for (let i = 0; i < services.length; i++) {
|
||||
const s = services[i];
|
||||
const uuidCandidate: string | null = (s != null && s.uuid != null ? s.uuid : null);
|
||||
const uuid = uuidCandidate != null ? ('' + uuidCandidate).toLowerCase() : '';
|
||||
if (uuid.indexOf('180f') !== -1) {
|
||||
found = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == null) {
|
||||
// fallback: if writeCharId exists and notify available use protocol (not implemented)
|
||||
return 0;
|
||||
}
|
||||
const foundUuid = found!.uuid;
|
||||
const charsRaw = await this.bluetoothService!.getCharacteristics(deviceId, foundUuid);
|
||||
const chars: BleCharacteristic[] = charsRaw;
|
||||
const batChar = chars.find((c: BleCharacteristic): boolean => ((c.properties != null && c.properties.read) || (c.uuid != null && ('' + c.uuid).toLowerCase().includes('2a19'))));
|
||||
if (batChar == null)
|
||||
return 0;
|
||||
const buf = await this.bluetoothService!.readCharacteristic(deviceId, foundUuid, batChar.uuid);
|
||||
const arr = new Uint8Array(buf);
|
||||
if (arr.length > 0) {
|
||||
return arr[0];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// testVersionInfo: try to read Device Information characteristics or return empty
|
||||
async testVersionInfo(hw: boolean): Promise<string> {
|
||||
// copy to local so Kotlin generator can smart-cast the value across awaits
|
||||
const deviceId = this.deviceId;
|
||||
if (deviceId == null)
|
||||
return '';
|
||||
// Device Information service 180A, characteristics: 2A26 (SW), 2A27 (HW) sometimes
|
||||
if (this.bluetoothService == null)
|
||||
return '';
|
||||
const _services = await this.bluetoothService!.getServices(deviceId);
|
||||
const services2: BleService[] = _services;
|
||||
let found2: BleService | null = null;
|
||||
for (let i = 0; i < services2.length; i++) {
|
||||
const s = services2[i];
|
||||
const uuidCandidate: string | null = (s != null && s.uuid != null ? s.uuid : null);
|
||||
const uuid = uuidCandidate != null ? ('' + uuidCandidate).toLowerCase() : '';
|
||||
if (uuid.indexOf('180a') !== -1) {
|
||||
found2 = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found2 == null)
|
||||
return '';
|
||||
const _found2 = found2;
|
||||
const found2Uuid = _found2!.uuid;
|
||||
const chars = await this.bluetoothService!.getCharacteristics(deviceId, found2Uuid);
|
||||
const target = chars.find((c): boolean => {
|
||||
const id = ('' + c.uuid).toLowerCase();
|
||||
if (hw)
|
||||
return id.includes('2a27') || id.includes('hardware');
|
||||
return id.includes('2a26') || id.includes('software');
|
||||
});
|
||||
if (target == null)
|
||||
return '';
|
||||
const buf = await this.bluetoothService!.readCharacteristic(deviceId, found2Uuid, target.uuid);
|
||||
try {
|
||||
return new TextDecoder().decode(new Uint8Array(buf));
|
||||
}
|
||||
catch (e: any) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=protocol_handler.uts.map
|
||||
1
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/protocol_handler.uts.map
vendored
Normal file
1
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/protocol_handler.uts.map
vendored
Normal file
File diff suppressed because one or more lines are too long
33
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/unierror.uts
vendored
Normal file
33
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/unierror.uts
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Minimal error definitions used across the BLE module.
|
||||
// Keep this file small and avoid runtime dependencies; it's mainly for typing and
|
||||
// simple runtime error construction used by native platform code.
|
||||
export enum AkBluetoothErrorCode {
|
||||
UnknownError = 0,
|
||||
DeviceNotFound = 1,
|
||||
ServiceNotFound = 2,
|
||||
CharacteristicNotFound = 3,
|
||||
ConnectionTimeout = 4,
|
||||
Unspecified = 99
|
||||
}
|
||||
export class AkBleErrorImpl extends Error {
|
||||
public code: AkBluetoothErrorCode;
|
||||
public detail: any | null;
|
||||
constructor(code: AkBluetoothErrorCode, message?: string, detail: any | null = null) {
|
||||
super(message ?? AkBleErrorImpl.defaultMessage(code));
|
||||
this.name = 'AkBleError';
|
||||
this.code = code;
|
||||
this.detail = detail;
|
||||
}
|
||||
static defaultMessage(code: AkBluetoothErrorCode): string {
|
||||
switch (code) {
|
||||
case AkBluetoothErrorCode.DeviceNotFound: return 'Device not found';
|
||||
case AkBluetoothErrorCode.ServiceNotFound: return 'Service not found';
|
||||
case AkBluetoothErrorCode.CharacteristicNotFound: return 'Characteristic not found';
|
||||
case AkBluetoothErrorCode.ConnectionTimeout: return 'Connection timed out';
|
||||
case AkBluetoothErrorCode.UnknownError:
|
||||
default: return 'Unknown Bluetooth error';
|
||||
}
|
||||
}
|
||||
}
|
||||
export default AkBleErrorImpl;
|
||||
//# sourceMappingURL=unierror.uts.map
|
||||
1
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/unierror.uts.map
vendored
Normal file
1
unpackage/dist/dev/.uvue/app-android/uni_modules/ak-sbsrv/utssdk/unierror.uts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"unierror.uts","sourceRoot":"","sources":["uni_modules/ak-sbsrv/utssdk/unierror.uts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,kFAAkF;AAClF,kEAAkE;AAElE,MAAM,MAAM,oBAAoB;IAC/B,YAAY,GAAG,CAAC;IAChB,cAAc,GAAG,CAAC;IAClB,eAAe,GAAG,CAAC;IACnB,sBAAsB,GAAG,CAAC;IAC1B,iBAAiB,GAAG,CAAC;IACrB,WAAW,GAAG,EAAE;CAChB;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC;IAClC,MAAM,CAAC,MAAM,EAAE,GAAG,GAAC,IAAI,CAAC;IACxB,YAAY,IAAI,EAAE,oBAAoB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAC,IAAI,GAAG,IAAI;QAChF,KAAK,CAAC,OAAO,IAAI,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IACD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,oBAAoB;QAC/C,QAAQ,IAAI,EAAE;YACb,KAAK,oBAAoB,CAAC,cAAc,CAAC,CAAC,OAAO,kBAAkB,CAAC;YACpE,KAAK,oBAAoB,CAAC,eAAe,CAAC,CAAC,OAAO,mBAAmB,CAAC;YACtE,KAAK,oBAAoB,CAAC,sBAAsB,CAAC,CAAC,OAAO,0BAA0B,CAAC;YACpF,KAAK,oBAAoB,CAAC,iBAAiB,CAAC,CAAC,OAAO,sBAAsB,CAAC;YAC3E,KAAK,oBAAoB,CAAC,YAAY,CAAC;YAAC,OAAO,CAAC,CAAC,OAAO,yBAAyB,CAAC;SAClF;IACF,CAAC;CACD;AAED,eAAe,cAAc,CAAC","sourcesContent":["// Minimal error definitions used across the BLE module.\r\n// Keep this file small and avoid runtime dependencies; it's mainly for typing and\r\n// simple runtime error construction used by native platform code.\r\n\r\nexport enum AkBluetoothErrorCode {\r\n\tUnknownError = 0,\r\n\tDeviceNotFound = 1,\r\n\tServiceNotFound = 2,\r\n\tCharacteristicNotFound = 3,\r\n\tConnectionTimeout = 4,\r\n\tUnspecified = 99\r\n}\r\n\r\nexport class AkBleErrorImpl extends Error {\r\n\tpublic code: AkBluetoothErrorCode;\r\n\tpublic detail: any|null;\n\tconstructor(code: AkBluetoothErrorCode, message?: string, detail: any|null = null) {\r\n\t\tsuper(message ?? AkBleErrorImpl.defaultMessage(code));\r\n\t\tthis.name = 'AkBleError';\r\n\t\tthis.code = code;\r\n\t\tthis.detail = detail;\r\n\t}\r\n\tstatic defaultMessage(code: AkBluetoothErrorCode) {\r\n\t\tswitch (code) {\r\n\t\t\tcase AkBluetoothErrorCode.DeviceNotFound: return 'Device not found';\r\n\t\t\tcase AkBluetoothErrorCode.ServiceNotFound: return 'Service not found';\r\n\t\t\tcase AkBluetoothErrorCode.CharacteristicNotFound: return 'Characteristic not found';\r\n\t\t\tcase AkBluetoothErrorCode.ConnectionTimeout: return 'Connection timed out';\r\n\t\t\tcase AkBluetoothErrorCode.UnknownError: default: return 'Unknown Bluetooth error';\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport default AkBleErrorImpl;\r\n"]}
|
||||
62
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/app-android/index.uts
vendored
Normal file
62
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/app-android/index.uts
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import Context from "android.content.Context";
|
||||
import BatteryManager from "android.os.BatteryManager";
|
||||
import { GetBatteryInfo, GetBatteryInfoOptions, GetBatteryInfoSuccess, GetBatteryInfoResult, GetBatteryInfoSync } from '../interface.uts';
|
||||
import IntentFilter from 'android.content.IntentFilter';
|
||||
import Intent from 'android.content.Intent';
|
||||
import { GetBatteryInfoFailImpl } from '../unierror';
|
||||
/**
|
||||
* 异步获取电量
|
||||
*/
|
||||
export const getBatteryInfo: GetBatteryInfo = function (options: GetBatteryInfoOptions) {
|
||||
const context = UTSAndroid.getAppContext();
|
||||
if (context != null) {
|
||||
const manager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager;
|
||||
const level = manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
|
||||
let ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
let batteryStatus = context.registerReceiver(null, ifilter);
|
||||
let status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
|
||||
let isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
|
||||
const res: GetBatteryInfoSuccess = {
|
||||
errMsg: 'getBatteryInfo:ok',
|
||||
level,
|
||||
isCharging: isCharging
|
||||
};
|
||||
options.success?.(res);
|
||||
options.complete?.(res);
|
||||
}
|
||||
else {
|
||||
let res = new GetBatteryInfoFailImpl(1001);
|
||||
options.fail?.(res);
|
||||
options.complete?.(res);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 同步获取电量
|
||||
*/
|
||||
export const getBatteryInfoSync: GetBatteryInfoSync = function (): GetBatteryInfoResult {
|
||||
const context = UTSAndroid.getAppContext();
|
||||
if (context != null) {
|
||||
const manager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager;
|
||||
const level = manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
|
||||
let ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
let batteryStatus = context.registerReceiver(null, ifilter);
|
||||
let status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
|
||||
let isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
|
||||
const res: GetBatteryInfoResult = {
|
||||
level: level,
|
||||
isCharging: isCharging
|
||||
};
|
||||
return res;
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* 无有效上下文
|
||||
*/
|
||||
const res: GetBatteryInfoResult = {
|
||||
level: -1,
|
||||
isCharging: false
|
||||
};
|
||||
return res;
|
||||
}
|
||||
};
|
||||
//# sourceMappingURL=index.uts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.uts","sourceRoot":"","sources":["uni_modules/uni-getbatteryinfo/utssdk/app-android/index.uts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,yBAAyB,CAAC;AAC9C,OAAO,cAAc,MAAM,2BAA2B,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACzI,OAAO,YAAY,MAAM,8BAA8B,CAAC;AACxD,OAAO,MAAM,MAAM,wBAAwB,CAAC;AAE5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAErD;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,EAAG,cAAc,GAAG,UAAU,OAAO,EAAG,qBAAqB;IACtF,MAAM,OAAO,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC;IAC3C,IAAI,OAAO,IAAI,IAAI,EAAE;QACnB,MAAM,OAAO,GAAG,OAAO,CAAC,gBAAgB,CACtC,OAAO,CAAC,eAAe,CACxB,IAAI,cAAc,CAAC;QACpB,MAAM,KAAK,GAAG,OAAO,CAAC,cAAc,CAClC,cAAc,CAAC,yBAAyB,CACzC,CAAC;QAEF,IAAI,OAAO,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;QAC9D,IAAI,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5D,IAAI,MAAM,GAAG,aAAa,EAAE,WAAW,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC;QACzE,IAAI,UAAU,GAAG,MAAM,IAAI,cAAc,CAAC,uBAAuB,IAAI,MAAM,IAAI,cAAc,CAAC,mBAAmB,CAAC;QAElH,MAAM,GAAG,EAAG,qBAAqB,GAAG;YAClC,MAAM,EAAE,mBAAmB;YAC3B,KAAK;YACL,UAAU,EAAE,UAAU;SACvB,CAAA;QACD,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAA;QACtB,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAA;KACxB;SAAM;QACL,IAAI,GAAG,GAAG,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAA;QACnB,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAA;KACxB;AACH,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,EAAG,kBAAkB,GAAG,aAAc,oBAAoB;IACvF,MAAM,OAAO,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC;IAC3C,IAAI,OAAO,IAAI,IAAI,EAAE;QACnB,MAAM,OAAO,GAAG,OAAO,CAAC,gBAAgB,CACtC,OAAO,CAAC,eAAe,CACxB,IAAI,cAAc,CAAC;QACpB,MAAM,KAAK,GAAG,OAAO,CAAC,cAAc,CAClC,cAAc,CAAC,yBAAyB,CACzC,CAAC;QAEF,IAAI,OAAO,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;QAC9D,IAAI,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5D,IAAI,MAAM,GAAG,aAAa,EAAE,WAAW,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC;QACzE,IAAI,UAAU,GAAG,MAAM,IAAI,cAAc,CAAC,uBAAuB,IAAI,MAAM,IAAI,cAAc,CAAC,mBAAmB,CAAC;QAElH,MAAM,GAAG,EAAG,oBAAoB,GAAG;YACjC,KAAK,EAAE,KAAK;YACZ,UAAU,EAAE,UAAU;SACvB,CAAC;QACF,OAAO,GAAG,CAAC;KACZ;SACI;QACH;;WAEG;QACH,MAAM,GAAG,EAAG,oBAAoB,GAAG;YACjC,KAAK,EAAE,CAAC,CAAC;YACT,UAAU,EAAE,KAAK;SAClB,CAAC;QACF,OAAO,GAAG,CAAC;KACZ;AACH,CAAC,CAAA","sourcesContent":["import Context from \"android.content.Context\";\r\nimport BatteryManager from \"android.os.BatteryManager\";\r\n\r\nimport { GetBatteryInfo, GetBatteryInfoOptions, GetBatteryInfoSuccess, GetBatteryInfoResult, GetBatteryInfoSync } from '../interface.uts'\r\nimport IntentFilter from 'android.content.IntentFilter';\r\nimport Intent from 'android.content.Intent';\r\n\r\nimport { GetBatteryInfoFailImpl } from '../unierror';\r\n\r\n/**\r\n * 异步获取电量\r\n */\r\nexport const getBatteryInfo : GetBatteryInfo = function (options : GetBatteryInfoOptions) {\r\n const context = UTSAndroid.getAppContext();\r\n if (context != null) {\r\n const manager = context.getSystemService(\r\n Context.BATTERY_SERVICE\r\n ) as BatteryManager;\r\n const level = manager.getIntProperty(\r\n BatteryManager.BATTERY_PROPERTY_CAPACITY\r\n );\r\n\r\n let ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);\r\n let batteryStatus = context.registerReceiver(null, ifilter);\r\n let status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1);\r\n let isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;\r\n\r\n const res : GetBatteryInfoSuccess = {\r\n errMsg: 'getBatteryInfo:ok',\r\n level,\r\n isCharging: isCharging\r\n }\r\n options.success?.(res)\r\n options.complete?.(res)\r\n } else {\r\n let res = new GetBatteryInfoFailImpl(1001);\r\n options.fail?.(res)\r\n options.complete?.(res)\r\n }\r\n}\r\n\r\n/**\r\n * 同步获取电量\r\n */\r\nexport const getBatteryInfoSync : GetBatteryInfoSync = function () : GetBatteryInfoResult {\r\n const context = UTSAndroid.getAppContext();\r\n if (context != null) {\r\n const manager = context.getSystemService(\r\n Context.BATTERY_SERVICE\r\n ) as BatteryManager;\r\n const level = manager.getIntProperty(\r\n BatteryManager.BATTERY_PROPERTY_CAPACITY\r\n );\r\n\r\n let ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);\r\n let batteryStatus = context.registerReceiver(null, ifilter);\r\n let status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1);\r\n let isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;\r\n\r\n const res : GetBatteryInfoResult = {\r\n level: level,\r\n isCharging: isCharging\r\n };\r\n return res;\r\n }\r\n else {\r\n /**\r\n * 无有效上下文\r\n */\r\n const res : GetBatteryInfoResult = {\r\n level: -1,\r\n isCharging: false\r\n };\r\n return res;\r\n }\r\n}\r\n"]}
|
||||
131
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/interface.uts
vendored
Normal file
131
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/interface.uts
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
export type GetBatteryInfoSuccess = {
|
||||
errMsg: string;
|
||||
/**
|
||||
* 设备电量,范围1 - 100
|
||||
*/
|
||||
level: number;
|
||||
/**
|
||||
* 是否正在充电中
|
||||
*/
|
||||
isCharging: boolean;
|
||||
};
|
||||
export type GetBatteryInfoOptions = {
|
||||
/**
|
||||
* 接口调用结束的回调函数(调用成功、失败都会执行)
|
||||
*/
|
||||
success?: (res: GetBatteryInfoSuccess) => void;
|
||||
/**
|
||||
* 接口调用失败的回调函数
|
||||
*/
|
||||
fail?: (res: UniError) => void;
|
||||
/**
|
||||
* 接口调用成功的回调
|
||||
*/
|
||||
complete?: (res: any) => void;
|
||||
};
|
||||
export type GetBatteryInfoResult = {
|
||||
/**
|
||||
* 设备电量,范围1 - 100
|
||||
*/
|
||||
level: number;
|
||||
/**
|
||||
* 是否正在充电中
|
||||
*/
|
||||
isCharging: boolean;
|
||||
};
|
||||
/**
|
||||
* 错误码
|
||||
* - 1001 getAppContext is null
|
||||
*/
|
||||
export type GetBatteryInfoErrorCode = 1001;
|
||||
/**
|
||||
* GetBatteryInfo 的错误回调参数
|
||||
*/
|
||||
export interface GetBatteryInfoFail extends IUniError {
|
||||
errCode: GetBatteryInfoErrorCode;
|
||||
}
|
||||
;
|
||||
/**
|
||||
* 获取电量信息
|
||||
* @param {GetBatteryInfoOptions} options
|
||||
*
|
||||
*
|
||||
* @tutorial https://uniapp.dcloud.net.cn/api/system/batteryInfo.html
|
||||
* @platforms APP-IOS = ^9.0,APP-ANDROID = ^22
|
||||
* @since 3.6.11
|
||||
*
|
||||
* @assert () => success({errCode: 0, errSubject: "uni-getBatteryInfo", errMsg: "getBatteryInfo:ok", level: 60, isCharging: false })
|
||||
* @assert () => fail({errCode: 1001, errSubject: "uni-getBatteryInfo", errMsg: "getBatteryInfo:fail getAppContext is null" })
|
||||
*/
|
||||
export type GetBatteryInfo = (options: GetBatteryInfoOptions) => void;
|
||||
export type GetBatteryInfoSync = () => GetBatteryInfoResult;
|
||||
interface Uni {
|
||||
/**
|
||||
* 获取电池电量信息
|
||||
* @description 获取电池电量信息
|
||||
* @param {GetBatteryInfoOptions} options
|
||||
* @example
|
||||
* ```typescript
|
||||
* uni.getBatteryInfo({
|
||||
* success(res) {
|
||||
* __f__('log','at uni_modules/uni-getbatteryinfo/utssdk/interface.uts:78',res);
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @remark
|
||||
* - 该接口需要同步调用
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "osVer": "4.4.4",
|
||||
* "uniVer": "3.6.11",
|
||||
* "unixVer": "3.9.0"
|
||||
* },
|
||||
* "ios": {
|
||||
* "osVer": "12.0",
|
||||
* "uniVer": "3.6.11",
|
||||
* "unixVer": "4.11"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "3.6.11",
|
||||
* "unixVer": "4.0"
|
||||
* }
|
||||
* }
|
||||
* @uniVueVersion 2,3 //支持的vue版本
|
||||
*
|
||||
*/
|
||||
getBatteryInfo(options: GetBatteryInfoOptions): void;
|
||||
/**
|
||||
* 同步获取电池电量信息
|
||||
* @description 获取电池电量信息
|
||||
* @example
|
||||
* ```typescript
|
||||
* uni.getBatteryInfo()
|
||||
* ```
|
||||
* @remark
|
||||
* - 该接口需要同步调用
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "osVer": "4.4.4",
|
||||
* "uniVer": "3.6.11",
|
||||
* "unixVer": "3.9.0"
|
||||
* },
|
||||
* "ios": {
|
||||
* "osVer": "12.0",
|
||||
* "uniVer": "3.6.11",
|
||||
* "unixVer": "4.11"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "3.6.11",
|
||||
* "unixVer": "4.0"
|
||||
* }
|
||||
* }
|
||||
* @uniVueVersion 2,3 //支持的vue版本
|
||||
*
|
||||
*/
|
||||
getBatteryInfoSync(): GetBatteryInfoResult;
|
||||
}
|
||||
//# sourceMappingURL=interface.uts.map
|
||||
1
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/interface.uts.map
vendored
Normal file
1
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/interface.uts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"interface.uts","sourceRoot":"","sources":["uni_modules/uni-getbatteryinfo/utssdk/interface.uts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAG,MAAM,CAAC;IAChB;;MAEE;IACF,KAAK,EAAG,MAAM,CAAC;IACf;;MAEE;IACF,UAAU,EAAG,OAAO,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IACnC;;UAEG;IACH,OAAQ,CAAC,EAAE,CAAC,GAAG,EAAG,qBAAqB,KAAK,IAAI,CAAA;IAChD;;UAEG;IACH,IAAK,CAAC,EAAE,CAAC,GAAG,EAAG,QAAQ,KAAK,IAAI,CAAA;IAChC;;UAEG;IACH,QAAS,CAAC,EAAE,CAAC,GAAG,EAAG,GAAG,KAAK,IAAI,CAAA;CAC/B,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IAClC;;MAEE;IACF,KAAK,EAAG,MAAM,CAAC;IACf;;MAEE;IACF,UAAU,EAAG,OAAO,CAAA;CACpB,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAE;AAC5C;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACnD,OAAO,EAAG,uBAAuB,CAAA;CAClC;AAAA,CAAC;AAEF;;;;;;;;;;;EAWE;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAG,qBAAqB,KAAK,IAAI,CAAA;AAGtE,MAAM,MAAM,kBAAkB,GAAG,MAAM,oBAAoB,CAAA;AAE3D,UAAU,GAAG;IAEZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,cAAc,CAAE,OAAO,EAAG,qBAAqB,GAAI,IAAI,CAAC;IACxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,kBAAkB,IAAG,oBAAoB,CAAA;CAEzC","sourcesContent":["export type GetBatteryInfoSuccess = {\n\terrMsg : string,\n\t/**\n\t* 设备电量,范围1 - 100\n\t*/\n\tlevel : number,\n\t/**\n\t* 是否正在充电中\n\t*/\n\tisCharging : boolean\n}\n\nexport type GetBatteryInfoOptions = {\n\t/**\n\t\t* 接口调用结束的回调函数(调用成功、失败都会执行)\n\t\t*/\n\tsuccess ?: (res : GetBatteryInfoSuccess) => void\n\t/**\n\t\t* 接口调用失败的回调函数\n\t\t*/\n\tfail ?: (res : UniError) => void\n\t/**\n\t\t* 接口调用成功的回调\n\t\t*/\n\tcomplete ?: (res : any) => void\n}\n\nexport type GetBatteryInfoResult = {\n\t/**\n\t* 设备电量,范围1 - 100\n\t*/\n\tlevel : number,\n\t/**\n\t* 是否正在充电中\n\t*/\n\tisCharging : boolean\n}\n\n/**\n * 错误码\n * - 1001 getAppContext is null\n */\nexport type GetBatteryInfoErrorCode = 1001 ;\n/**\n * GetBatteryInfo 的错误回调参数\n */\nexport interface GetBatteryInfoFail extends IUniError {\n errCode : GetBatteryInfoErrorCode\n};\n\n/**\n* 获取电量信息\n* @param {GetBatteryInfoOptions} options\n*\n*\n* @tutorial https://uniapp.dcloud.net.cn/api/system/batteryInfo.html\n* @platforms APP-IOS = ^9.0,APP-ANDROID = ^22\n* @since 3.6.11\n*\n* @assert () => success({errCode: 0, errSubject: \"uni-getBatteryInfo\", errMsg: \"getBatteryInfo:ok\", level: 60, isCharging: false })\n* @assert () => fail({errCode: 1001, errSubject: \"uni-getBatteryInfo\", errMsg: \"getBatteryInfo:fail getAppContext is null\" })\n*/\nexport type GetBatteryInfo = (options : GetBatteryInfoOptions) => void\n\n\nexport type GetBatteryInfoSync = () => GetBatteryInfoResult\n\ninterface Uni {\n\n\t/**\n\t * 获取电池电量信息\n\t * @description 获取电池电量信息\n\t * @param {GetBatteryInfoOptions} options\n\t * @example\n\t * ```typescript\n\t * uni.getBatteryInfo({\n\t *\t\tsuccess(res) {\n\t *\t\t\t__f__('log','at uni_modules/uni-getbatteryinfo/utssdk/interface.uts:78',res);\n\t *\t\t}\n\t * })\n\t * ```\n\t * @remark\n\t * - 该接口需要同步调用\n\t * @uniPlatform {\n\t * \"app\": {\n\t * \"android\": {\n\t * \"osVer\": \"4.4.4\",\n\t * \"uniVer\": \"3.6.11\",\n\t * \"unixVer\": \"3.9.0\"\n\t * },\n\t * \"ios\": {\n\t * \"osVer\": \"12.0\",\n\t * \"uniVer\": \"3.6.11\",\n\t * \"unixVer\": \"4.11\"\n\t * }\n\t * },\n\t * \"web\": {\n\t * \"uniVer\": \"3.6.11\",\n\t * \"unixVer\": \"4.0\"\n\t * }\n\t * }\n\t * @uniVueVersion 2,3 //支持的vue版本\n\t *\n\t */\n\tgetBatteryInfo (options : GetBatteryInfoOptions) : void,\n\t/**\n\t * 同步获取电池电量信息\n\t * @description 获取电池电量信息\n\t * @example\n\t * ```typescript\n\t * uni.getBatteryInfo()\n\t * ```\n\t * @remark\n\t * - 该接口需要同步调用\n\t * @uniPlatform {\n\t * \"app\": {\n\t * \"android\": {\n\t * \"osVer\": \"4.4.4\",\n\t * \"uniVer\": \"3.6.11\",\n\t * \"unixVer\": \"3.9.0\"\n\t * },\n\t * \"ios\": {\n\t * \"osVer\": \"12.0\",\n\t * \"uniVer\": \"3.6.11\",\n\t * \"unixVer\": \"4.11\"\n\t * }\n\t * },\n\t * \"web\": {\n\t * \"uniVer\": \"3.6.11\",\n\t * \"unixVer\": \"4.0\"\n\t * }\n\t * }\n\t * @uniVueVersion 2,3 //支持的vue版本\n\t *\n\t */\n\tgetBatteryInfoSync():GetBatteryInfoResult\n\n}\n"]}
|
||||
30
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/unierror.uts
vendored
Normal file
30
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/unierror.uts
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import { GetBatteryInfoErrorCode, GetBatteryInfoFail } from "./interface.uts";
|
||||
/**
|
||||
* 错误主题
|
||||
*/
|
||||
export const UniErrorSubject = 'uni-getBatteryInfo';
|
||||
/**
|
||||
* 错误信息
|
||||
* @UniError
|
||||
*/
|
||||
export const UniErrors: Map<GetBatteryInfoErrorCode, string> = new Map([
|
||||
/**
|
||||
* 错误码及对应的错误信息
|
||||
*/
|
||||
[1001, 'getBatteryInfo:fail getAppContext is null'],
|
||||
]);
|
||||
/**
|
||||
* 错误对象实现
|
||||
*/
|
||||
export class GetBatteryInfoFailImpl extends UniError implements GetBatteryInfoFail {
|
||||
/**
|
||||
* 错误对象构造函数
|
||||
*/
|
||||
constructor(errCode: GetBatteryInfoErrorCode) {
|
||||
super();
|
||||
this.errSubject = UniErrorSubject;
|
||||
this.errCode = errCode;
|
||||
this.errMsg = UniErrors[errCode] ?? "";
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=unierror.uts.map
|
||||
1
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/unierror.uts.map
vendored
Normal file
1
unpackage/dist/dev/.uvue/app-android/uni_modules/uni-getbatteryinfo/utssdk/unierror.uts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"unierror.uts","sourceRoot":"","sources":["uni_modules/uni-getbatteryinfo/utssdk/unierror.uts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC7E;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAGpD;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,EAAG,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC;IACtE;;OAEG;IACH,CAAC,IAAI,EAAE,2CAA2C,CAAC;CACpD,CAAC,CAAC;AAGH;;GAEG;AACH,MAAM,OAAO,sBAAuB,SAAQ,QAAS,YAAW,kBAAkB;IAEhF;;OAEG;IACH,YAAY,OAAO,EAAG,uBAAuB;QAC3C,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,UAAU,GAAG,eAAe,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IACzC,CAAC;CACF","sourcesContent":["import { GetBatteryInfoErrorCode, GetBatteryInfoFail } from \"./interface.uts\"\r\n/**\r\n * 错误主题\r\n */\r\nexport const UniErrorSubject = 'uni-getBatteryInfo';\r\n\r\n\r\n/**\r\n * 错误信息\r\n * @UniError\r\n */\r\nexport const UniErrors : Map<GetBatteryInfoErrorCode, string> = new Map([\r\n /**\r\n * 错误码及对应的错误信息\r\n */\r\n [1001, 'getBatteryInfo:fail getAppContext is null'],\r\n]);\r\n\r\n\r\n/**\r\n * 错误对象实现\r\n */\r\nexport class GetBatteryInfoFailImpl extends UniError implements GetBatteryInfoFail {\r\n\r\n /**\r\n * 错误对象构造函数\r\n */\r\n constructor(errCode : GetBatteryInfoErrorCode) {\r\n super();\r\n this.errSubject = UniErrorSubject;\r\n this.errCode = errCode;\r\n this.errMsg = UniErrors[errCode] ?? \"\";\r\n }\r\n}"]}
|
||||
Reference in New Issue
Block a user