@onekeyfe/hd-transport-react-native 1.1.27-alpha.4 → 1.1.27-alpha.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BleManager.d.ts.map +1 -1
- package/dist/BleTransport.d.ts +2 -0
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/bleStrategy.d.ts +15 -0
- package/dist/bleStrategy.d.ts.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +56 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +828 -124
- package/dist/logger.d.ts +14 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/BleManager.ts +14 -14
- package/src/BleTransport.ts +9 -5
- package/src/__tests__/bleStrategy.test.ts +47 -0
- package/src/bleStrategy.ts +62 -0
- package/src/constants.ts +25 -1
- package/src/index.ts +899 -69
- package/src/logger.ts +19 -0
- package/src/types.ts +3 -0
- package/src/utils/validateNotify.ts +4 -4
package/src/index.ts
CHANGED
|
@@ -9,38 +9,150 @@ import {
|
|
|
9
9
|
} from 'react-native-ble-plx';
|
|
10
10
|
import ByteBuffer from 'bytebuffer';
|
|
11
11
|
import transport, {
|
|
12
|
-
COMMON_HEADER_SIZE,
|
|
13
12
|
LogBlockCommand,
|
|
14
13
|
type OneKeyDeviceInfoBase,
|
|
14
|
+
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
15
|
+
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
16
|
+
type ProtocolType,
|
|
17
|
+
ProtocolV2FrameAssembler,
|
|
18
|
+
ProtocolV2Session,
|
|
19
|
+
type TransportCallOptions,
|
|
20
|
+
probeProtocolV2 as probeProtocolV2Helper,
|
|
15
21
|
} from '@onekeyfe/hd-transport';
|
|
16
22
|
import { ERRORS, HardwareErrorCode, createDeferred, isOnekeyDevice } from '@onekeyfe/hd-shared';
|
|
17
|
-
import { LoggerNames, getLogger } from '@onekeyfe/hd-core';
|
|
18
23
|
|
|
19
24
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
25
|
+
import {
|
|
26
|
+
hasWritableCapability,
|
|
27
|
+
resolveBleWriteMode,
|
|
28
|
+
resolveProtocolV2PacketCapacity,
|
|
29
|
+
} from './bleStrategy';
|
|
20
30
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
21
31
|
import {
|
|
22
32
|
ANDROID_PACKET_LENGTH,
|
|
23
33
|
IOS_PACKET_LENGTH,
|
|
34
|
+
getBleUuidKey,
|
|
24
35
|
getBluetoothServiceUuids,
|
|
25
36
|
getInfosForServiceUuid,
|
|
37
|
+
isSameBleUuid,
|
|
26
38
|
} from './constants';
|
|
27
39
|
import { isHeaderChunk } from './utils/validateNotify';
|
|
28
40
|
import BleTransport from './BleTransport';
|
|
29
41
|
import timer from './utils/timer';
|
|
42
|
+
import { bleLogger, setBleLogger } from './logger';
|
|
30
43
|
|
|
31
44
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
32
45
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
33
46
|
import type EventEmitter from 'events';
|
|
34
47
|
import type { BleAcquireInput, TransportOptions } from './types';
|
|
35
48
|
|
|
36
|
-
const { check,
|
|
49
|
+
const { check, ProtocolV1, parseConfigure } = transport;
|
|
37
50
|
|
|
38
|
-
const Log =
|
|
51
|
+
const Log = bleLogger;
|
|
39
52
|
|
|
40
53
|
const transportCache: Record<string, BleTransport> = {};
|
|
54
|
+
const BLE_RESPONSE_TIMEOUT_MS = 30_000;
|
|
55
|
+
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
56
|
+
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
57
|
+
const DEVICE_SCAN_TIMEOUT_MS = 8000;
|
|
58
|
+
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
59
|
+
const ANDROID_NOTIFY_READY_DELAY_MS = 300;
|
|
60
|
+
const HIGH_VOLUME_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 6;
|
|
61
|
+
const HIGH_VOLUME_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 6 : 2;
|
|
62
|
+
const HIGH_VOLUME_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 20 : 8;
|
|
63
|
+
|
|
64
|
+
const delay = (ms: number) =>
|
|
65
|
+
new Promise<void>(resolve => {
|
|
66
|
+
setTimeout(resolve, ms);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export type ProtocolV2BleTuning = {
|
|
70
|
+
iosPacketLength?: number;
|
|
71
|
+
androidPacketLength?: number;
|
|
72
|
+
highVolumeWriteBurstSize?: number;
|
|
73
|
+
highVolumeWritePauseMs?: number;
|
|
74
|
+
highVolumeWriteFlushDelayMs?: number;
|
|
75
|
+
highVolumeWriteWithResponse?: boolean;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
79
|
+
|
|
80
|
+
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
81
|
+
iosPacketLength: IOS_PACKET_LENGTH,
|
|
82
|
+
androidPacketLength: ANDROID_PACKET_LENGTH,
|
|
83
|
+
highVolumeWriteBurstSize: HIGH_VOLUME_WRITE_BURST_SIZE,
|
|
84
|
+
highVolumeWritePauseMs: HIGH_VOLUME_WRITE_PAUSE_MS,
|
|
85
|
+
highVolumeWriteFlushDelayMs: HIGH_VOLUME_WRITE_FLUSH_DELAY_MS,
|
|
86
|
+
highVolumeWriteWithResponse: false,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
90
|
+
|
|
91
|
+
const normalizePositiveInteger = (value: unknown, fallback: number) => {
|
|
92
|
+
const normalized = Number(value);
|
|
93
|
+
if (!Number.isFinite(normalized) || normalized <= 0) return fallback;
|
|
94
|
+
return Math.floor(normalized);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
|
|
98
|
+
protocolV2BleTuning = {
|
|
99
|
+
iosPacketLength: normalizePositiveInteger(
|
|
100
|
+
tuning.iosPacketLength,
|
|
101
|
+
protocolV2BleTuning.iosPacketLength
|
|
102
|
+
),
|
|
103
|
+
androidPacketLength: normalizePositiveInteger(
|
|
104
|
+
tuning.androidPacketLength,
|
|
105
|
+
protocolV2BleTuning.androidPacketLength
|
|
106
|
+
),
|
|
107
|
+
highVolumeWriteBurstSize: normalizePositiveInteger(
|
|
108
|
+
tuning.highVolumeWriteBurstSize,
|
|
109
|
+
protocolV2BleTuning.highVolumeWriteBurstSize
|
|
110
|
+
),
|
|
111
|
+
highVolumeWritePauseMs: normalizePositiveInteger(
|
|
112
|
+
tuning.highVolumeWritePauseMs,
|
|
113
|
+
protocolV2BleTuning.highVolumeWritePauseMs
|
|
114
|
+
),
|
|
115
|
+
highVolumeWriteFlushDelayMs: normalizePositiveInteger(
|
|
116
|
+
tuning.highVolumeWriteFlushDelayMs,
|
|
117
|
+
protocolV2BleTuning.highVolumeWriteFlushDelayMs
|
|
118
|
+
),
|
|
119
|
+
highVolumeWriteWithResponse:
|
|
120
|
+
tuning.highVolumeWriteWithResponse ?? protocolV2BleTuning.highVolumeWriteWithResponse,
|
|
121
|
+
};
|
|
122
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning configured:', protocolV2BleTuning);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function resetProtocolV2BleTuning() {
|
|
126
|
+
protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
127
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning reset:', protocolV2BleTuning);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getProtocolV2BleTuning() {
|
|
131
|
+
return { ...protocolV2BleTuning };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
135
|
+
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getDeviceDisplayName(device?: Device | null) {
|
|
139
|
+
return device?.name || device?.localName || null;
|
|
140
|
+
}
|
|
41
141
|
|
|
42
|
-
|
|
43
|
-
|
|
142
|
+
function isGenericBleService(uuid?: string | null) {
|
|
143
|
+
return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function hasKnownOneKeyService(device?: Device | null) {
|
|
147
|
+
return (device?.serviceUUIDs ?? []).some(serviceUuid =>
|
|
148
|
+
getInfosForServiceUuid(serviceUuid, 'classic')
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const ANDROID_REQUEST_MTU = 256;
|
|
153
|
+
|
|
154
|
+
const connectOptions: Record<string, unknown> = {
|
|
155
|
+
requestMTU: ANDROID_REQUEST_MTU,
|
|
44
156
|
timeout: 3000,
|
|
45
157
|
refreshGatt: 'OnConnected',
|
|
46
158
|
};
|
|
@@ -49,12 +161,29 @@ export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
|
49
161
|
|
|
50
162
|
const tryToGetConfiguration = (device: Device) => {
|
|
51
163
|
if (!device || !device.serviceUUIDs) return null;
|
|
52
|
-
const
|
|
164
|
+
const serviceUUID = device.serviceUUIDs.find(uuid => getInfosForServiceUuid(uuid, 'classic'));
|
|
165
|
+
if (!serviceUUID) return null;
|
|
53
166
|
const infos = getInfosForServiceUuid(serviceUUID, 'classic');
|
|
54
167
|
if (!infos) return null;
|
|
55
168
|
return infos;
|
|
56
169
|
};
|
|
57
170
|
|
|
171
|
+
const requestAndroidMtu = async (device: Device) => {
|
|
172
|
+
if (Platform.OS !== 'android') return device;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
|
|
176
|
+
Log?.debug('[ReactNativeBleTransport] Android MTU requested:', {
|
|
177
|
+
requested: ANDROID_REQUEST_MTU,
|
|
178
|
+
mtu: mtuDevice.mtu,
|
|
179
|
+
});
|
|
180
|
+
return mtuDevice;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
Log?.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
|
|
183
|
+
return device;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
58
187
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
59
188
|
|
|
60
189
|
function remapError(error: IOBleErrorRemap) {
|
|
@@ -92,23 +221,45 @@ export default class ReactNativeBleTransport {
|
|
|
92
221
|
|
|
93
222
|
_messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
94
223
|
|
|
224
|
+
_messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
225
|
+
|
|
95
226
|
name = 'ReactNativeBleTransport';
|
|
96
227
|
|
|
97
228
|
configured = false;
|
|
98
229
|
|
|
99
230
|
stopped = false;
|
|
100
231
|
|
|
101
|
-
scanTimeout =
|
|
232
|
+
scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
|
|
102
233
|
|
|
103
234
|
runPromise: Deferred<any> | null = null;
|
|
104
235
|
|
|
105
236
|
emitter?: EventEmitter;
|
|
106
237
|
|
|
238
|
+
/** Per-device protocol type detected by active wire-level probe after connect. */
|
|
239
|
+
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
240
|
+
|
|
241
|
+
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
242
|
+
|
|
243
|
+
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
244
|
+
|
|
245
|
+
private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
|
|
246
|
+
|
|
247
|
+
private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
248
|
+
|
|
249
|
+
private activeProtocolV2Call: { uuid: string; token: number } | null = null;
|
|
250
|
+
|
|
251
|
+
private nextProtocolV2CallToken = 1;
|
|
252
|
+
|
|
253
|
+
private monitorTokens: Map<string, number> = new Map();
|
|
254
|
+
|
|
255
|
+
private nextMonitorToken = 1;
|
|
256
|
+
|
|
107
257
|
constructor(options: TransportOptions) {
|
|
108
|
-
this.scanTimeout = options.scanTimeout ??
|
|
258
|
+
this.scanTimeout = options.scanTimeout ?? DEVICE_SCAN_TIMEOUT_MS;
|
|
109
259
|
}
|
|
110
260
|
|
|
111
|
-
init(
|
|
261
|
+
init(logger: any, emitter: EventEmitter) {
|
|
262
|
+
setBleLogger(logger);
|
|
112
263
|
this.emitter = emitter;
|
|
113
264
|
}
|
|
114
265
|
|
|
@@ -118,6 +269,11 @@ export default class ReactNativeBleTransport {
|
|
|
118
269
|
this._messages = messages;
|
|
119
270
|
}
|
|
120
271
|
|
|
272
|
+
configureProtocolV2(signedData: any) {
|
|
273
|
+
this._messagesV2 = parseConfigure(signedData);
|
|
274
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 schema configured');
|
|
275
|
+
}
|
|
276
|
+
|
|
121
277
|
listen() {
|
|
122
278
|
// empty
|
|
123
279
|
}
|
|
@@ -167,6 +323,7 @@ export default class ReactNativeBleTransport {
|
|
|
167
323
|
blePlxManager.startDeviceScan(
|
|
168
324
|
null,
|
|
169
325
|
{
|
|
326
|
+
allowDuplicates: true,
|
|
170
327
|
scanMode: ScanMode.LowLatency,
|
|
171
328
|
},
|
|
172
329
|
(error, device) => {
|
|
@@ -194,14 +351,41 @@ export default class ReactNativeBleTransport {
|
|
|
194
351
|
return;
|
|
195
352
|
}
|
|
196
353
|
|
|
197
|
-
|
|
354
|
+
const displayName = getDeviceDisplayName(device);
|
|
355
|
+
const isOneKey =
|
|
356
|
+
isOnekeyDevice(device?.name ?? null, device?.id) ||
|
|
357
|
+
isOnekeyDevice(device?.localName ?? null, device?.id) ||
|
|
358
|
+
hasKnownOneKeyService(device);
|
|
359
|
+
const shouldTraceCandidate =
|
|
360
|
+
!!displayName && /onekey|bixinkey|pro\s*2|pro\b|touch|^k\d|^t\d/i.test(displayName);
|
|
361
|
+
|
|
362
|
+
if (shouldTraceCandidate) {
|
|
363
|
+
Log?.debug('[ReactNativeBleTransport] scan candidate', {
|
|
364
|
+
name: device?.name,
|
|
365
|
+
localName: device?.localName,
|
|
366
|
+
id: device?.id,
|
|
367
|
+
serviceUUIDs: device?.serviceUUIDs,
|
|
368
|
+
accepted: isOneKey,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (isOneKey) {
|
|
198
373
|
Log?.debug('search device start ======================');
|
|
199
|
-
const { name, localName, id } = device ?? {};
|
|
374
|
+
const { name, localName, id, serviceUUIDs } = device ?? {};
|
|
200
375
|
Log?.debug(
|
|
201
|
-
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${
|
|
376
|
+
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${
|
|
377
|
+
id ?? ''
|
|
378
|
+
}\nserviceUUIDs: ${(serviceUUIDs ?? []).join(',')}`
|
|
202
379
|
);
|
|
203
380
|
addDevice(device as unknown as Device);
|
|
204
381
|
Log?.debug('search device end ======================\n');
|
|
382
|
+
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
383
|
+
Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
|
|
384
|
+
name: device?.name,
|
|
385
|
+
localName: device?.localName,
|
|
386
|
+
id: device?.id,
|
|
387
|
+
serviceUUIDs: device?.serviceUUIDs,
|
|
388
|
+
});
|
|
205
389
|
}
|
|
206
390
|
}
|
|
207
391
|
);
|
|
@@ -215,7 +399,16 @@ export default class ReactNativeBleTransport {
|
|
|
215
399
|
|
|
216
400
|
const addDevice = (device: Device) => {
|
|
217
401
|
if (deviceList.every(d => d.id !== device.id)) {
|
|
218
|
-
|
|
402
|
+
const displayName = getDeviceDisplayName(device) ?? 'Unknown BLE Device';
|
|
403
|
+
const protocolHint = inferProtocolHintFromDeviceName(displayName);
|
|
404
|
+
if (protocolHint) {
|
|
405
|
+
this.deviceProtocolHints.set(device.id, protocolHint);
|
|
406
|
+
}
|
|
407
|
+
deviceList.push({
|
|
408
|
+
...device,
|
|
409
|
+
name: displayName,
|
|
410
|
+
commType: 'ble',
|
|
411
|
+
} as IOneKeyDevice);
|
|
219
412
|
}
|
|
220
413
|
};
|
|
221
414
|
|
|
@@ -227,25 +420,45 @@ export default class ReactNativeBleTransport {
|
|
|
227
420
|
}
|
|
228
421
|
|
|
229
422
|
async acquire(input: BleAcquireInput) {
|
|
230
|
-
const { uuid, forceCleanRunPromise } = input;
|
|
423
|
+
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
231
424
|
|
|
232
425
|
if (!uuid) {
|
|
233
426
|
throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
|
|
234
427
|
}
|
|
235
428
|
|
|
236
|
-
|
|
429
|
+
const cachedTransport = transportCache[uuid];
|
|
430
|
+
if (cachedTransport) {
|
|
431
|
+
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
432
|
+
const isCachedDeviceConnected = await cachedTransport.device.isConnected().catch(() => false);
|
|
433
|
+
if (
|
|
434
|
+
isCachedDeviceConnected &&
|
|
435
|
+
cachedProtocol &&
|
|
436
|
+
(!expectedProtocol || cachedProtocol === expectedProtocol)
|
|
437
|
+
) {
|
|
438
|
+
Log?.debug(
|
|
439
|
+
'[ReactNativeBleTransport] reuse cached BLE transport:',
|
|
440
|
+
uuid,
|
|
441
|
+
cachedProtocol
|
|
442
|
+
);
|
|
443
|
+
return { uuid, protocolType: cachedProtocol };
|
|
444
|
+
}
|
|
237
445
|
|
|
238
|
-
if (transportCache[uuid]) {
|
|
239
446
|
/**
|
|
240
|
-
* If the transport is not
|
|
241
|
-
* it
|
|
447
|
+
* If the transport is not reusable due to a protocol mismatch or stale
|
|
448
|
+
* connection, clean it up before creating a new transport instance.
|
|
242
449
|
*/
|
|
243
|
-
Log?.debug('transport not
|
|
244
|
-
await this.release(uuid);
|
|
450
|
+
Log?.debug('transport not reusable, will release: ', uuid);
|
|
451
|
+
await this.release(uuid, true);
|
|
245
452
|
}
|
|
246
453
|
|
|
454
|
+
let device: Device | null = null;
|
|
455
|
+
|
|
247
456
|
if (forceCleanRunPromise && this.runPromise) {
|
|
248
|
-
|
|
457
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
458
|
+
this.runPromise.reject(error);
|
|
459
|
+
this.rejectAllProtocolV2Frames(error);
|
|
460
|
+
this.runPromise = null;
|
|
461
|
+
this.activeProtocolV2Call = null;
|
|
249
462
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
250
463
|
}
|
|
251
464
|
|
|
@@ -257,11 +470,12 @@ export default class ReactNativeBleTransport {
|
|
|
257
470
|
throw error;
|
|
258
471
|
}
|
|
259
472
|
|
|
260
|
-
// check device is bonded
|
|
261
473
|
if (Platform.OS === 'android') {
|
|
262
474
|
const bondState = await pairDevice(uuid);
|
|
263
475
|
if (bondState.bonding) {
|
|
264
476
|
await onDeviceBondState(uuid);
|
|
477
|
+
} else if (!bondState.bonded) {
|
|
478
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
|
|
265
479
|
}
|
|
266
480
|
}
|
|
267
481
|
|
|
@@ -287,7 +501,6 @@ export default class ReactNativeBleTransport {
|
|
|
287
501
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
288
502
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
289
503
|
) {
|
|
290
|
-
connectOptions = {};
|
|
291
504
|
Log?.debug('first try to reconnect without params');
|
|
292
505
|
device = await blePlxManager.connectToDevice(uuid);
|
|
293
506
|
} else if (e.errorCode === BleErrorCode.DeviceAlreadyConnected) {
|
|
@@ -307,17 +520,16 @@ export default class ReactNativeBleTransport {
|
|
|
307
520
|
Log?.debug('not connected, try to connect to device: ', uuid);
|
|
308
521
|
|
|
309
522
|
try {
|
|
310
|
-
await device.connect(connectOptions);
|
|
523
|
+
device = await device.connect(connectOptions);
|
|
311
524
|
} catch (e) {
|
|
312
525
|
Log?.debug('not connected, try to connect to device has error: ', e);
|
|
313
526
|
if (
|
|
314
527
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
315
528
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
316
529
|
) {
|
|
317
|
-
connectOptions = {};
|
|
318
530
|
Log?.debug('second try to reconnect without params');
|
|
319
531
|
try {
|
|
320
|
-
await device.connect();
|
|
532
|
+
device = await device.connect();
|
|
321
533
|
} catch (e) {
|
|
322
534
|
Log?.debug('last try to reconnect error: ', e);
|
|
323
535
|
// last try to reconnect device if this issue exists
|
|
@@ -325,7 +537,7 @@ export default class ReactNativeBleTransport {
|
|
|
325
537
|
if (e.errorCode === BleErrorCode.OperationCancelled) {
|
|
326
538
|
Log?.debug('last try to reconnect');
|
|
327
539
|
await device.cancelConnection();
|
|
328
|
-
await device.connect();
|
|
540
|
+
device = await device.connect();
|
|
329
541
|
}
|
|
330
542
|
}
|
|
331
543
|
} else {
|
|
@@ -334,9 +546,12 @@ export default class ReactNativeBleTransport {
|
|
|
334
546
|
}
|
|
335
547
|
}
|
|
336
548
|
|
|
549
|
+
device = await requestAndroidMtu(device);
|
|
550
|
+
|
|
337
551
|
await device.discoverAllServicesAndCharacteristics();
|
|
338
552
|
let infos = tryToGetConfiguration(device);
|
|
339
553
|
let characteristics;
|
|
554
|
+
let fallbackServiceUuid: string | undefined;
|
|
340
555
|
|
|
341
556
|
if (!infos) {
|
|
342
557
|
for (const serviceUuid of getBluetoothServiceUuids()) {
|
|
@@ -351,16 +566,44 @@ export default class ReactNativeBleTransport {
|
|
|
351
566
|
}
|
|
352
567
|
|
|
353
568
|
if (!infos) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
569
|
+
const services = await device.services();
|
|
570
|
+
Log?.debug(
|
|
571
|
+
'[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:',
|
|
572
|
+
services?.map(service => service.uuid)
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const knownService = services.find(service =>
|
|
576
|
+
getInfosForServiceUuid(service.uuid, 'classic')
|
|
577
|
+
);
|
|
578
|
+
const fallbackService =
|
|
579
|
+
knownService ?? services.find(service => !isGenericBleService(service.uuid)) ?? services[0];
|
|
580
|
+
|
|
581
|
+
if (fallbackService) {
|
|
582
|
+
fallbackServiceUuid = fallbackService.uuid;
|
|
583
|
+
characteristics = await device.characteristicsForService(fallbackService.uuid);
|
|
584
|
+
Log?.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (!infos) {
|
|
589
|
+
if (!fallbackServiceUuid) {
|
|
590
|
+
try {
|
|
591
|
+
Log?.debug('cancel connection when service not found');
|
|
592
|
+
await device.cancelConnection();
|
|
593
|
+
} catch (e) {
|
|
594
|
+
Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
|
|
595
|
+
}
|
|
596
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
359
597
|
}
|
|
360
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
361
598
|
}
|
|
362
599
|
|
|
363
|
-
const
|
|
600
|
+
const serviceUuid = infos?.serviceUuid ?? fallbackServiceUuid;
|
|
601
|
+
const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
|
|
602
|
+
const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
|
|
603
|
+
|
|
604
|
+
if (!serviceUuid) {
|
|
605
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
606
|
+
}
|
|
364
607
|
|
|
365
608
|
if (!characteristics) {
|
|
366
609
|
characteristics = await device.characteristicsForService(serviceUuid);
|
|
@@ -373,9 +616,9 @@ export default class ReactNativeBleTransport {
|
|
|
373
616
|
let writeCharacteristic;
|
|
374
617
|
let notifyCharacteristic;
|
|
375
618
|
for (const c of characteristics) {
|
|
376
|
-
if (c.uuid
|
|
619
|
+
if (isSameBleUuid(c.uuid, writeUuid)) {
|
|
377
620
|
writeCharacteristic = c;
|
|
378
|
-
} else if (c.uuid
|
|
621
|
+
} else if (isSameBleUuid(c.uuid, notifyUuid)) {
|
|
379
622
|
notifyCharacteristic = c;
|
|
380
623
|
}
|
|
381
624
|
}
|
|
@@ -388,7 +631,7 @@ export default class ReactNativeBleTransport {
|
|
|
388
631
|
throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
|
|
389
632
|
}
|
|
390
633
|
|
|
391
|
-
if (!writeCharacteristic
|
|
634
|
+
if (!hasWritableCapability(writeCharacteristic)) {
|
|
392
635
|
throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
|
|
393
636
|
}
|
|
394
637
|
|
|
@@ -398,16 +641,47 @@ export default class ReactNativeBleTransport {
|
|
|
398
641
|
);
|
|
399
642
|
}
|
|
400
643
|
|
|
644
|
+
const protocolHint = expectedProtocol
|
|
645
|
+
? undefined
|
|
646
|
+
: this.deviceProtocolHints.get(uuid) ??
|
|
647
|
+
inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
|
|
648
|
+
|
|
401
649
|
// release transport before new transport instance
|
|
402
|
-
await this.release(uuid);
|
|
650
|
+
await this.release(uuid, true);
|
|
651
|
+
if (protocolHint) {
|
|
652
|
+
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
653
|
+
}
|
|
403
654
|
|
|
404
655
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
656
|
+
if (Platform.OS === 'android') {
|
|
657
|
+
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
658
|
+
}
|
|
659
|
+
const monitorToken = this.nextMonitorToken;
|
|
660
|
+
this.nextMonitorToken += 1;
|
|
661
|
+
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
662
|
+
transport.monitorToken = monitorToken;
|
|
663
|
+
transport.notifyTransactionId = notifyTransactionId;
|
|
664
|
+
this.monitorTokens.set(uuid, monitorToken);
|
|
405
665
|
transport.notifySubscription = this._monitorCharacteristic(
|
|
406
666
|
transport.notifyCharacteristic,
|
|
407
|
-
uuid
|
|
667
|
+
uuid,
|
|
668
|
+
monitorToken,
|
|
669
|
+
notifyTransactionId
|
|
408
670
|
);
|
|
409
671
|
transportCache[uuid] = transport;
|
|
410
672
|
|
|
673
|
+
this.protocolV2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
|
|
674
|
+
|
|
675
|
+
if (Platform.OS === 'ios') {
|
|
676
|
+
await new Promise<void>(resolve => {
|
|
677
|
+
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
678
|
+
});
|
|
679
|
+
} else if (Platform.OS === 'android') {
|
|
680
|
+
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
684
|
+
|
|
411
685
|
this.emitter?.emit('device-connect', {
|
|
412
686
|
name: device.name,
|
|
413
687
|
id: device.id,
|
|
@@ -415,6 +689,11 @@ export default class ReactNativeBleTransport {
|
|
|
415
689
|
});
|
|
416
690
|
|
|
417
691
|
transport.disconnectSubscription = device.onDisconnected(() => {
|
|
692
|
+
if (transportCache[uuid] !== transport) {
|
|
693
|
+
Log?.debug('device disconnect ignored for stale transport: ', device?.id);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
418
697
|
try {
|
|
419
698
|
Log?.debug('device disconnect: ', device?.id);
|
|
420
699
|
this.emitter?.emit('device-disconnect', {
|
|
@@ -423,28 +702,40 @@ export default class ReactNativeBleTransport {
|
|
|
423
702
|
connectId: device?.id,
|
|
424
703
|
});
|
|
425
704
|
if (this.runPromise) {
|
|
426
|
-
|
|
705
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
|
|
706
|
+
this.runPromise.reject(error);
|
|
707
|
+
this.rejectAllProtocolV2Frames(error);
|
|
427
708
|
}
|
|
428
709
|
} catch (e) {
|
|
429
710
|
Log?.debug('device disconnect error: ', e);
|
|
430
711
|
} finally {
|
|
431
|
-
this.release(uuid);
|
|
712
|
+
this.release(uuid, true);
|
|
432
713
|
}
|
|
433
714
|
});
|
|
434
715
|
|
|
435
|
-
return { uuid };
|
|
716
|
+
return { uuid, protocolType };
|
|
436
717
|
}
|
|
437
718
|
|
|
438
|
-
_monitorCharacteristic(
|
|
719
|
+
_monitorCharacteristic(
|
|
720
|
+
characteristic: Characteristic,
|
|
721
|
+
uuid: string,
|
|
722
|
+
monitorToken: number,
|
|
723
|
+
notifyTransactionId: string
|
|
724
|
+
): Subscription {
|
|
439
725
|
let bufferLength = 0;
|
|
440
726
|
let buffer: any[] = [];
|
|
441
727
|
const subscription = characteristic.monitor((error, c) => {
|
|
728
|
+
const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
|
|
442
729
|
if (error) {
|
|
443
730
|
Log?.debug(
|
|
444
731
|
`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${
|
|
445
732
|
error as unknown as string
|
|
446
733
|
}`
|
|
447
734
|
);
|
|
735
|
+
if (!isCurrentMonitor) {
|
|
736
|
+
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
448
739
|
if (this.runPromise) {
|
|
449
740
|
let ERROR:
|
|
450
741
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
@@ -464,27 +755,45 @@ export default class ReactNativeBleTransport {
|
|
|
464
755
|
error.reason?.includes('Writing is not permitted') || // pro firmware 2.3.4 upgrade
|
|
465
756
|
error.reason?.includes('notify change failed for device')
|
|
466
757
|
) {
|
|
467
|
-
|
|
468
|
-
|
|
758
|
+
const notifyError = ERRORS.TypedError(
|
|
759
|
+
HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
469
760
|
);
|
|
761
|
+
this.runPromise.reject(notifyError);
|
|
762
|
+
this.rejectAllProtocolV2Frames(notifyError);
|
|
470
763
|
Log?.debug(
|
|
471
764
|
`${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
|
|
472
765
|
);
|
|
473
766
|
return;
|
|
474
767
|
}
|
|
475
|
-
|
|
768
|
+
const notifyError = ERRORS.TypedError(ERROR);
|
|
769
|
+
this.runPromise.reject(notifyError);
|
|
770
|
+
this.rejectAllProtocolV2Frames(notifyError);
|
|
476
771
|
Log?.debug(': monitor notify error, and has unreleased Promise', Error);
|
|
477
772
|
}
|
|
478
773
|
|
|
479
774
|
return;
|
|
480
775
|
}
|
|
481
776
|
|
|
777
|
+
if (!isCurrentMonitor) {
|
|
778
|
+
Log?.debug('monitor data ignored for stale transport: ', uuid, notifyTransactionId);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
482
782
|
if (!c) {
|
|
483
783
|
throw ERRORS.TypedError(HardwareErrorCode.BleMonitorError);
|
|
484
784
|
}
|
|
485
785
|
|
|
486
786
|
try {
|
|
487
787
|
const data = Buffer.from(c.value as string, 'base64');
|
|
788
|
+
const protocol = this.deviceProtocol.get(uuid);
|
|
789
|
+
if (!protocol) {
|
|
790
|
+
Log?.debug('monitor data ignored before protocol detection: ', uuid);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (protocol === 'V2') {
|
|
794
|
+
this.handleProtocolV2Notification(uuid, new Uint8Array(data));
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
488
797
|
// console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
|
|
489
798
|
if (isHeaderChunk(data)) {
|
|
490
799
|
bufferLength = data.readInt32BE(5);
|
|
@@ -493,7 +802,7 @@ export default class ReactNativeBleTransport {
|
|
|
493
802
|
buffer = buffer.concat([...data]);
|
|
494
803
|
}
|
|
495
804
|
|
|
496
|
-
if (buffer.length -
|
|
805
|
+
if (buffer.length - PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferLength) {
|
|
497
806
|
const value = Buffer.from(buffer);
|
|
498
807
|
// console.log(
|
|
499
808
|
// '[hd-transport-react-native] Received a complete packet of data, resolve Promise, this.runPromise: ',
|
|
@@ -507,17 +816,41 @@ export default class ReactNativeBleTransport {
|
|
|
507
816
|
}
|
|
508
817
|
} catch (error) {
|
|
509
818
|
Log?.debug('monitor data error: ', error);
|
|
510
|
-
|
|
819
|
+
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
820
|
+
this.runPromise?.reject(notifyError);
|
|
821
|
+
this.rejectAllProtocolV2Frames(notifyError);
|
|
511
822
|
}
|
|
512
|
-
},
|
|
823
|
+
}, notifyTransactionId);
|
|
513
824
|
|
|
514
825
|
return subscription;
|
|
515
826
|
}
|
|
516
827
|
|
|
517
|
-
async release(uuid: string) {
|
|
828
|
+
async release(uuid: string, onclose = false) {
|
|
518
829
|
const transport = transportCache[uuid];
|
|
830
|
+
if (this.runPromise) {
|
|
831
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
832
|
+
this.runPromise.reject(error);
|
|
833
|
+
this.runPromise = null;
|
|
834
|
+
this.rejectAllProtocolV2Frames(error);
|
|
835
|
+
this.activeProtocolV2Call = null;
|
|
836
|
+
} else {
|
|
837
|
+
this.resetProtocolV2Frames(uuid);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (Platform.OS === 'android' && !onclose && transport) {
|
|
841
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
842
|
+
this.resetProtocolV2Frames(uuid);
|
|
843
|
+
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
844
|
+
this.activeProtocolV2Call = null;
|
|
845
|
+
}
|
|
846
|
+
return Promise.resolve(true);
|
|
847
|
+
}
|
|
519
848
|
|
|
520
849
|
if (transport) {
|
|
850
|
+
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
851
|
+
this.monitorTokens.delete(uuid);
|
|
852
|
+
}
|
|
853
|
+
|
|
521
854
|
// Clean up disconnect subscription first to prevent callbacks on released transport
|
|
522
855
|
Log?.debug('release: removing disconnect subscription for device: ', uuid);
|
|
523
856
|
transport.disconnectSubscription?.remove();
|
|
@@ -531,12 +864,27 @@ export default class ReactNativeBleTransport {
|
|
|
531
864
|
transport.notifySubscription?.remove();
|
|
532
865
|
transport.notifySubscription = undefined;
|
|
533
866
|
|
|
867
|
+
if (transport.notifyTransactionId) {
|
|
868
|
+
try {
|
|
869
|
+
await this.blePlxManager?.cancelTransaction(transport.notifyTransactionId);
|
|
870
|
+
} catch (e) {
|
|
871
|
+
Log?.debug('release: cancel notify transaction error (ignored): ', e?.message || e);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
534
875
|
delete transportCache[uuid];
|
|
876
|
+
}
|
|
535
877
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
878
|
+
this.deviceProtocol.delete(uuid);
|
|
879
|
+
this.deviceProtocolHints.delete(uuid);
|
|
880
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
881
|
+
this.protocolV2Assemblers.delete(uuid);
|
|
882
|
+
this.resetProtocolV2Frames(uuid);
|
|
883
|
+
|
|
884
|
+
try {
|
|
885
|
+
await this.blePlxManager?.cancelTransaction(uuid);
|
|
886
|
+
} catch (e) {
|
|
887
|
+
Log?.debug('release: cancel transaction error (ignored): ', e?.message || e);
|
|
540
888
|
}
|
|
541
889
|
|
|
542
890
|
return Promise.resolve(true);
|
|
@@ -546,7 +894,12 @@ export default class ReactNativeBleTransport {
|
|
|
546
894
|
await this.call(session, name, data);
|
|
547
895
|
}
|
|
548
896
|
|
|
549
|
-
async call(
|
|
897
|
+
async call(
|
|
898
|
+
uuid: string,
|
|
899
|
+
name: string,
|
|
900
|
+
data: Record<string, unknown>,
|
|
901
|
+
options?: TransportCallOptions
|
|
902
|
+
) {
|
|
550
903
|
if (this.stopped) {
|
|
551
904
|
// eslint-disable-next-line prefer-promise-reject-errors
|
|
552
905
|
return Promise.reject(ERRORS.TypedError('Transport stopped.'));
|
|
@@ -562,13 +915,13 @@ export default class ReactNativeBleTransport {
|
|
|
562
915
|
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
563
916
|
}
|
|
564
917
|
|
|
565
|
-
const
|
|
566
|
-
if (!
|
|
567
|
-
throw ERRORS.TypedError(
|
|
918
|
+
const protocol = this.getProtocolType(uuid);
|
|
919
|
+
if (!protocol) {
|
|
920
|
+
throw ERRORS.TypedError(
|
|
921
|
+
HardwareErrorCode.RuntimeError,
|
|
922
|
+
`Device protocol has not been detected for ${uuid}`
|
|
923
|
+
);
|
|
568
924
|
}
|
|
569
|
-
|
|
570
|
-
this.runPromise = createDeferred();
|
|
571
|
-
const messages = this._messages;
|
|
572
925
|
// Upload resources on low-end phones may OOM
|
|
573
926
|
if (name === 'ResourceUpdate' || name === 'ResourceAck') {
|
|
574
927
|
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
|
|
@@ -576,12 +929,44 @@ export default class ReactNativeBleTransport {
|
|
|
576
929
|
hash: data?.hash,
|
|
577
930
|
});
|
|
578
931
|
} else if (LogBlockCommand.has(name)) {
|
|
579
|
-
Log?.debug('transport-react-native', 'call-', ' name: ', name);
|
|
932
|
+
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' protocol: ', protocol);
|
|
580
933
|
} else {
|
|
581
|
-
Log?.debug(
|
|
934
|
+
Log?.debug(
|
|
935
|
+
'transport-react-native',
|
|
936
|
+
'call-',
|
|
937
|
+
' name: ',
|
|
938
|
+
name,
|
|
939
|
+
' data: ',
|
|
940
|
+
data,
|
|
941
|
+
' protocol: ',
|
|
942
|
+
protocol
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (protocol === 'V2') {
|
|
947
|
+
return this.callProtocolV2(uuid, name, data, options);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return this.callProtocolV1(uuid, name, data, options);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
private async callProtocolV1(
|
|
954
|
+
uuid: string,
|
|
955
|
+
name: string,
|
|
956
|
+
data: Record<string, unknown>,
|
|
957
|
+
options?: TransportCallOptions
|
|
958
|
+
) {
|
|
959
|
+
if (!this._messages) {
|
|
960
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
582
961
|
}
|
|
583
962
|
|
|
584
|
-
const
|
|
963
|
+
const transport = this.getCachedTransport(uuid);
|
|
964
|
+
const runPromise = createDeferred<string>();
|
|
965
|
+
runPromise.promise.catch(() => undefined);
|
|
966
|
+
this.runPromise = runPromise;
|
|
967
|
+
const messages = this._messages;
|
|
968
|
+
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
969
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
585
970
|
|
|
586
971
|
async function writeChunkedData(
|
|
587
972
|
buffers: ByteBuffer[],
|
|
@@ -652,20 +1037,41 @@ export default class ReactNativeBleTransport {
|
|
|
652
1037
|
}
|
|
653
1038
|
|
|
654
1039
|
try {
|
|
655
|
-
const response = await
|
|
1040
|
+
const response = await Promise.race([
|
|
1041
|
+
runPromise.promise,
|
|
1042
|
+
new Promise<never>((_, reject) => {
|
|
1043
|
+
if (options?.timeoutMs) {
|
|
1044
|
+
timeout = setTimeout(() => {
|
|
1045
|
+
const error = ERRORS.TypedError(
|
|
1046
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1047
|
+
`BLE response timeout after ${options.timeoutMs}ms for ${name}`
|
|
1048
|
+
);
|
|
1049
|
+
runPromise.reject(error);
|
|
1050
|
+
reject(error);
|
|
1051
|
+
}, options.timeoutMs);
|
|
1052
|
+
}
|
|
1053
|
+
}),
|
|
1054
|
+
]);
|
|
656
1055
|
|
|
657
1056
|
if (typeof response !== 'string') {
|
|
658
1057
|
throw new Error('Returning data is not string.');
|
|
659
1058
|
}
|
|
660
1059
|
|
|
661
1060
|
Log?.debug('receive data: ', response);
|
|
662
|
-
const jsonData =
|
|
1061
|
+
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
663
1062
|
return check.call(jsonData);
|
|
664
1063
|
} catch (e) {
|
|
665
|
-
|
|
1064
|
+
if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
1065
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe call failed:', e);
|
|
1066
|
+
} else {
|
|
1067
|
+
Log?.error('call error: ', e);
|
|
1068
|
+
}
|
|
666
1069
|
throw e;
|
|
667
1070
|
} finally {
|
|
668
|
-
|
|
1071
|
+
if (timeout) clearTimeout(timeout);
|
|
1072
|
+
if (this.runPromise === runPromise) {
|
|
1073
|
+
this.runPromise = null;
|
|
1074
|
+
}
|
|
669
1075
|
}
|
|
670
1076
|
}
|
|
671
1077
|
|
|
@@ -732,6 +1138,13 @@ export default class ReactNativeBleTransport {
|
|
|
732
1138
|
if (transportCache[session]) {
|
|
733
1139
|
delete transportCache[session];
|
|
734
1140
|
}
|
|
1141
|
+
this.deviceProtocol.delete(session);
|
|
1142
|
+
this.deviceProtocolHints.delete(session);
|
|
1143
|
+
this.protocolV2Assemblers.delete(session);
|
|
1144
|
+
this.resetProtocolV2Frames(session);
|
|
1145
|
+
if (this.activeProtocolV2Call?.uuid === session) {
|
|
1146
|
+
this.activeProtocolV2Call = null;
|
|
1147
|
+
}
|
|
735
1148
|
|
|
736
1149
|
// emit the disconnect event
|
|
737
1150
|
try {
|
|
@@ -754,4 +1167,421 @@ export default class ReactNativeBleTransport {
|
|
|
754
1167
|
}
|
|
755
1168
|
this.runPromise = null;
|
|
756
1169
|
}
|
|
1170
|
+
|
|
1171
|
+
private getCachedTransport(uuid: string) {
|
|
1172
|
+
const transport = transportCache[uuid];
|
|
1173
|
+
if (!transport) {
|
|
1174
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
1175
|
+
}
|
|
1176
|
+
return transport;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
private createProtocolMismatchError(expected: ProtocolType) {
|
|
1180
|
+
return ERRORS.TypedError(
|
|
1181
|
+
HardwareErrorCode.RuntimeError,
|
|
1182
|
+
`Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
private createProtocolDetectionError() {
|
|
1187
|
+
return ERRORS.TypedError(
|
|
1188
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1189
|
+
'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
|
|
1194
|
+
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
1195
|
+
this.deviceProtocol.delete(uuid);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
private async detectProtocol(
|
|
1200
|
+
uuid: string,
|
|
1201
|
+
expectedProtocol?: ProtocolType,
|
|
1202
|
+
protocolHint?: ProtocolType
|
|
1203
|
+
): Promise<ProtocolType> {
|
|
1204
|
+
if (expectedProtocol === 'V1') {
|
|
1205
|
+
if (await this.probeProtocolV1(uuid)) {
|
|
1206
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
1207
|
+
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1 (expected)`);
|
|
1208
|
+
return 'V1';
|
|
1209
|
+
}
|
|
1210
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if (expectedProtocol === 'V2') {
|
|
1214
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1215
|
+
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1216
|
+
return 'V2';
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (protocolHint === 'V2') {
|
|
1220
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1221
|
+
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
|
|
1222
|
+
return 'V2';
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
1226
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1227
|
+
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
|
|
1228
|
+
return 'V2';
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const protocolV1Detected = await this.probeProtocolV1(uuid);
|
|
1232
|
+
if (protocolV1Detected) {
|
|
1233
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
1234
|
+
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
1235
|
+
return 'V1';
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
await this.resetProbeStateAfterProtocolProbe(uuid, 'V1');
|
|
1239
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
1240
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1241
|
+
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2`);
|
|
1242
|
+
return 'V2';
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
this.deviceProtocol.delete(uuid);
|
|
1246
|
+
throw this.createProtocolDetectionError();
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
1250
|
+
const transport = transportCache[uuid];
|
|
1251
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1252
|
+
this.resetProtocolV2Frames(uuid);
|
|
1253
|
+
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
1254
|
+
this.activeProtocolV2Call = null;
|
|
1255
|
+
}
|
|
1256
|
+
if (this.runPromise) {
|
|
1257
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1258
|
+
this.runPromise.reject(error);
|
|
1259
|
+
this.runPromise = null;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
if (!transport) return;
|
|
1263
|
+
|
|
1264
|
+
const previousNotifyTransactionId = transport.notifyTransactionId;
|
|
1265
|
+
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1266
|
+
this.monitorTokens.delete(uuid);
|
|
1267
|
+
}
|
|
1268
|
+
transport.notifySubscription?.remove();
|
|
1269
|
+
transport.notifySubscription = undefined;
|
|
1270
|
+
if (previousNotifyTransactionId) {
|
|
1271
|
+
try {
|
|
1272
|
+
await this.blePlxManager?.cancelTransaction(previousNotifyTransactionId);
|
|
1273
|
+
} catch (error) {
|
|
1274
|
+
Log?.debug(
|
|
1275
|
+
`[ReactNativeBleTransport] cancel notify after Protocol ${protocol} probe failed:`,
|
|
1276
|
+
error?.message || error
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
const monitorToken = this.nextMonitorToken;
|
|
1282
|
+
this.nextMonitorToken += 1;
|
|
1283
|
+
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
1284
|
+
transport.monitorToken = monitorToken;
|
|
1285
|
+
transport.notifyTransactionId = notifyTransactionId;
|
|
1286
|
+
this.monitorTokens.set(uuid, monitorToken);
|
|
1287
|
+
transport.notifySubscription = this._monitorCharacteristic(
|
|
1288
|
+
transport.notifyCharacteristic,
|
|
1289
|
+
uuid,
|
|
1290
|
+
monitorToken,
|
|
1291
|
+
notifyTransactionId
|
|
1292
|
+
);
|
|
1293
|
+
if (Platform.OS === 'ios') {
|
|
1294
|
+
await new Promise<void>(resolve => {
|
|
1295
|
+
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
private async probeProtocolV1(uuid: string) {
|
|
1301
|
+
if (!this._messages) {
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
try {
|
|
1306
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
1307
|
+
await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1308
|
+
return true;
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
this.clearProbeProtocol(uuid, 'V1');
|
|
1311
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
private async probeProtocolV2(uuid: string) {
|
|
1317
|
+
if (!this._messages || !this._messagesV2) {
|
|
1318
|
+
return false;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1322
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1323
|
+
const detected = await probeProtocolV2Helper({
|
|
1324
|
+
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
1325
|
+
this.callProtocolV2(uuid, name, data, options),
|
|
1326
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
|
|
1327
|
+
logger: Log,
|
|
1328
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1329
|
+
onProbeFailed: () => {
|
|
1330
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1331
|
+
this.resetProtocolV2Frames(uuid);
|
|
1332
|
+
},
|
|
1333
|
+
});
|
|
1334
|
+
if (!detected) {
|
|
1335
|
+
this.clearProbeProtocol(uuid, 'V2');
|
|
1336
|
+
}
|
|
1337
|
+
return detected;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
|
|
1341
|
+
try {
|
|
1342
|
+
if (!this.runPromise || this.activeProtocolV2Call?.uuid !== uuid) {
|
|
1343
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1344
|
+
this.resetProtocolV2Frames(uuid);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (data.length === 0) return;
|
|
1349
|
+
|
|
1350
|
+
const assembler = this.protocolV2Assemblers.get(uuid);
|
|
1351
|
+
if (!assembler) return;
|
|
1352
|
+
|
|
1353
|
+
let frameData = assembler.push(data);
|
|
1354
|
+
while (frameData) {
|
|
1355
|
+
this.resolveProtocolV2Frame(uuid, frameData);
|
|
1356
|
+
frameData = assembler.push(new Uint8Array(0));
|
|
1357
|
+
}
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
|
|
1360
|
+
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1361
|
+
this.runPromise?.reject(notifyError);
|
|
1362
|
+
this.rejectAllProtocolV2Frames(notifyError);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
private getProtocolV2FrameQueue(uuid: string) {
|
|
1367
|
+
let queue = this.protocolV2FrameQueues.get(uuid);
|
|
1368
|
+
if (!queue) {
|
|
1369
|
+
queue = [];
|
|
1370
|
+
this.protocolV2FrameQueues.set(uuid, queue);
|
|
1371
|
+
}
|
|
1372
|
+
return queue;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
private resolveProtocolV2Frame(uuid: string, frame: Uint8Array) {
|
|
1376
|
+
const framePromise = this.protocolV2FramePromises.get(uuid);
|
|
1377
|
+
if (framePromise) {
|
|
1378
|
+
framePromise.resolve(frame);
|
|
1379
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
private rejectAllProtocolV2Frames(error: Error) {
|
|
1386
|
+
this.protocolV2FrameQueues.clear();
|
|
1387
|
+
for (const framePromise of this.protocolV2FramePromises.values()) {
|
|
1388
|
+
framePromise.reject(error);
|
|
1389
|
+
}
|
|
1390
|
+
this.protocolV2FramePromises.clear();
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
private resetProtocolV2Frames(uuid: string) {
|
|
1394
|
+
this.protocolV2FrameQueues.delete(uuid);
|
|
1395
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
private isActiveProtocolV2Call(uuid: string, token: number) {
|
|
1399
|
+
return this.activeProtocolV2Call?.uuid === uuid && this.activeProtocolV2Call.token === token;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
private async readProtocolV2Frame(uuid: string) {
|
|
1403
|
+
const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
|
|
1404
|
+
if (queuedFrame) {
|
|
1405
|
+
return queuedFrame;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
const framePromise = createDeferred<Uint8Array>();
|
|
1409
|
+
this.protocolV2FramePromises.set(uuid, framePromise);
|
|
1410
|
+
try {
|
|
1411
|
+
return await framePromise.promise;
|
|
1412
|
+
} finally {
|
|
1413
|
+
if (this.protocolV2FramePromises.get(uuid) === framePromise) {
|
|
1414
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
private async writeProtocolV2Frame(
|
|
1420
|
+
transport: BleTransport,
|
|
1421
|
+
frame: Uint8Array,
|
|
1422
|
+
options?: { highVolume?: boolean; writeWithResponse?: boolean }
|
|
1423
|
+
) {
|
|
1424
|
+
const tuning = getProtocolV2BleTuning();
|
|
1425
|
+
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
1426
|
+
platform: Platform.OS,
|
|
1427
|
+
iosPacketLength: tuning.iosPacketLength,
|
|
1428
|
+
androidPacketLength: tuning.androidPacketLength,
|
|
1429
|
+
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1430
|
+
});
|
|
1431
|
+
const writeWithResponse =
|
|
1432
|
+
!!options?.writeWithResponse || (!!options?.highVolume && tuning.highVolumeWriteWithResponse);
|
|
1433
|
+
const writeMode = resolveBleWriteMode(
|
|
1434
|
+
transport.writeCharacteristic,
|
|
1435
|
+
writeWithResponse ? 'withResponse' : 'withoutResponse'
|
|
1436
|
+
);
|
|
1437
|
+
const shouldThrottle = !!options?.highVolume && writeMode === 'withoutResponse';
|
|
1438
|
+
let packetsWritten = 0;
|
|
1439
|
+
|
|
1440
|
+
try {
|
|
1441
|
+
for (let offset = 0; offset < frame.length; offset += packetCapacity) {
|
|
1442
|
+
const chunk = frame.slice(offset, offset + packetCapacity);
|
|
1443
|
+
const base64 = Buffer.from(chunk).toString('base64');
|
|
1444
|
+
if (writeMode === 'withResponse') {
|
|
1445
|
+
await transport.writeCharacteristic.writeWithResponse(base64);
|
|
1446
|
+
} else {
|
|
1447
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1448
|
+
}
|
|
1449
|
+
packetsWritten += 1;
|
|
1450
|
+
|
|
1451
|
+
if (
|
|
1452
|
+
shouldThrottle &&
|
|
1453
|
+
packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
|
|
1454
|
+
offset + packetCapacity < frame.length
|
|
1455
|
+
) {
|
|
1456
|
+
await delay(tuning.highVolumeWritePauseMs);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
if (shouldThrottle) {
|
|
1461
|
+
await delay(tuning.highVolumeWriteFlushDelayMs);
|
|
1462
|
+
}
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
if (options?.highVolume && !writeWithResponse && packetsWritten === 0) {
|
|
1465
|
+
Log?.debug(
|
|
1466
|
+
'[ReactNativeBleTransport] Protocol V2 high-volume writeWithoutResponse failed before data was sent, fallback to writeWithResponse:',
|
|
1467
|
+
error
|
|
1468
|
+
);
|
|
1469
|
+
await this.writeProtocolV2Frame(transport, frame, {
|
|
1470
|
+
highVolume: true,
|
|
1471
|
+
writeWithResponse: true,
|
|
1472
|
+
});
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
throw error;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
private async callProtocolV2(
|
|
1480
|
+
uuid: string,
|
|
1481
|
+
name: string,
|
|
1482
|
+
data: Record<string, unknown>,
|
|
1483
|
+
options?: TransportCallOptions
|
|
1484
|
+
) {
|
|
1485
|
+
if (!this._messages || !this._messagesV2) {
|
|
1486
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'GetProtoVersion';
|
|
1490
|
+
if (this.runPromise) {
|
|
1491
|
+
if (!forceRun) {
|
|
1492
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1493
|
+
}
|
|
1494
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1495
|
+
this.runPromise.reject(error);
|
|
1496
|
+
this.rejectAllProtocolV2Frames(error);
|
|
1497
|
+
this.runPromise = null;
|
|
1498
|
+
this.activeProtocolV2Call = null;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const transport = this.getCachedTransport(uuid);
|
|
1502
|
+
const runPromise = createDeferred<Uint8Array>();
|
|
1503
|
+
runPromise.promise.catch(() => undefined);
|
|
1504
|
+
this.runPromise = runPromise;
|
|
1505
|
+
const callToken = this.nextProtocolV2CallToken++;
|
|
1506
|
+
this.activeProtocolV2Call = { uuid, token: callToken };
|
|
1507
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1508
|
+
this.resetProtocolV2Frames(uuid);
|
|
1509
|
+
let completed = false;
|
|
1510
|
+
const callOptions = {
|
|
1511
|
+
...options,
|
|
1512
|
+
timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
|
|
1513
|
+
};
|
|
1514
|
+
const highVolumeWrite = LogBlockCommand.has(name);
|
|
1515
|
+
|
|
1516
|
+
if (highVolumeWrite) {
|
|
1517
|
+
const tuning = getProtocolV2BleTuning();
|
|
1518
|
+
Log?.debug(
|
|
1519
|
+
'[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
|
|
1520
|
+
name,
|
|
1521
|
+
{
|
|
1522
|
+
packetCapacity:
|
|
1523
|
+
Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1524
|
+
burstSize: tuning.highVolumeWriteBurstSize,
|
|
1525
|
+
pauseMs: tuning.highVolumeWritePauseMs,
|
|
1526
|
+
flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
|
|
1527
|
+
writeWithResponse: tuning.highVolumeWriteWithResponse,
|
|
1528
|
+
}
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
try {
|
|
1533
|
+
const session = new ProtocolV2Session({
|
|
1534
|
+
schemas: {
|
|
1535
|
+
protocolV1: this._messages,
|
|
1536
|
+
protocolV2: this._messagesV2,
|
|
1537
|
+
},
|
|
1538
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1539
|
+
writeFrame: async (frame: Uint8Array) => {
|
|
1540
|
+
await this.writeProtocolV2Frame(transport, frame, {
|
|
1541
|
+
highVolume: highVolumeWrite,
|
|
1542
|
+
});
|
|
1543
|
+
},
|
|
1544
|
+
readFrame: async () => {
|
|
1545
|
+
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1546
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1547
|
+
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1548
|
+
}
|
|
1549
|
+
return rxFrame;
|
|
1550
|
+
},
|
|
1551
|
+
logger: Log,
|
|
1552
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1553
|
+
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
1554
|
+
ERRORS.TypedError(
|
|
1555
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1556
|
+
`BLE response timeout after ${timeout}ms for ${name}`
|
|
1557
|
+
),
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
const result = await session.call(name, data, callOptions);
|
|
1561
|
+
completed = true;
|
|
1562
|
+
return result;
|
|
1563
|
+
} catch (e) {
|
|
1564
|
+
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1565
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1566
|
+
this.resetProtocolV2Frames(uuid);
|
|
1567
|
+
}
|
|
1568
|
+
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1569
|
+
throw e;
|
|
1570
|
+
} finally {
|
|
1571
|
+
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1572
|
+
if (!completed) {
|
|
1573
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1574
|
+
}
|
|
1575
|
+
this.resetProtocolV2Frames(uuid);
|
|
1576
|
+
this.activeProtocolV2Call = null;
|
|
1577
|
+
}
|
|
1578
|
+
if (this.runPromise === runPromise) {
|
|
1579
|
+
this.runPromise = null;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
getProtocolType(path: string): ProtocolType | undefined {
|
|
1585
|
+
return this.deviceProtocol.get(path);
|
|
1586
|
+
}
|
|
757
1587
|
}
|