@onekeyfe/hd-transport-react-native 1.1.27-alpha.35 → 1.1.27-alpha.4
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 +0 -2
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/constants.d.ts +0 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +5 -53
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +69 -672
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/BleManager.ts +2 -3
- package/src/BleTransport.ts +3 -8
- package/src/constants.ts +1 -24
- package/src/index.ts +55 -788
- package/src/types.ts +0 -3
- package/src/utils/validateNotify.ts +4 -4
- package/dist/logger.d.ts +0 -14
- package/dist/logger.d.ts.map +0 -1
- package/src/logger.ts +0 -19
package/src/index.ts
CHANGED
|
@@ -9,139 +9,35 @@ import {
|
|
|
9
9
|
} from 'react-native-ble-plx';
|
|
10
10
|
import ByteBuffer from 'bytebuffer';
|
|
11
11
|
import transport, {
|
|
12
|
+
COMMON_HEADER_SIZE,
|
|
12
13
|
LogBlockCommand,
|
|
13
14
|
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,
|
|
21
15
|
} from '@onekeyfe/hd-transport';
|
|
22
16
|
import { ERRORS, HardwareErrorCode, createDeferred, isOnekeyDevice } from '@onekeyfe/hd-shared';
|
|
17
|
+
import { LoggerNames, getLogger } from '@onekeyfe/hd-core';
|
|
23
18
|
|
|
24
19
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
25
20
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
26
21
|
import {
|
|
27
22
|
ANDROID_PACKET_LENGTH,
|
|
28
23
|
IOS_PACKET_LENGTH,
|
|
29
|
-
getBleUuidKey,
|
|
30
24
|
getBluetoothServiceUuids,
|
|
31
25
|
getInfosForServiceUuid,
|
|
32
|
-
isSameBleUuid,
|
|
33
26
|
} from './constants';
|
|
34
27
|
import { isHeaderChunk } from './utils/validateNotify';
|
|
35
28
|
import BleTransport from './BleTransport';
|
|
36
29
|
import timer from './utils/timer';
|
|
37
|
-
import { bleLogger, setBleLogger } from './logger';
|
|
38
30
|
|
|
39
31
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
40
32
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
41
33
|
import type EventEmitter from 'events';
|
|
42
34
|
import type { BleAcquireInput, TransportOptions } from './types';
|
|
43
35
|
|
|
44
|
-
const { check,
|
|
36
|
+
const { check, buildBuffers, receiveOne, parseConfigure } = transport;
|
|
45
37
|
|
|
46
|
-
const Log =
|
|
38
|
+
const Log = getLogger(LoggerNames.HdBleTransport);
|
|
47
39
|
|
|
48
40
|
const transportCache: Record<string, BleTransport> = {};
|
|
49
|
-
const BLE_RESPONSE_TIMEOUT_MS = 30_000;
|
|
50
|
-
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
51
|
-
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
52
|
-
const DEVICE_SCAN_TIMEOUT_MS = 8000;
|
|
53
|
-
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
54
|
-
const HIGH_VOLUME_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 6;
|
|
55
|
-
const HIGH_VOLUME_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 6 : 2;
|
|
56
|
-
const HIGH_VOLUME_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 20 : 8;
|
|
57
|
-
|
|
58
|
-
const delay = (ms: number) =>
|
|
59
|
-
new Promise<void>(resolve => {
|
|
60
|
-
setTimeout(resolve, ms);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
export type ProtocolV2BleTuning = {
|
|
64
|
-
iosPacketLength?: number;
|
|
65
|
-
androidPacketLength?: number;
|
|
66
|
-
highVolumeWriteBurstSize?: number;
|
|
67
|
-
highVolumeWritePauseMs?: number;
|
|
68
|
-
highVolumeWriteFlushDelayMs?: number;
|
|
69
|
-
highVolumeWriteWithResponse?: boolean;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
73
|
-
|
|
74
|
-
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
75
|
-
iosPacketLength: IOS_PACKET_LENGTH,
|
|
76
|
-
androidPacketLength: ANDROID_PACKET_LENGTH,
|
|
77
|
-
highVolumeWriteBurstSize: HIGH_VOLUME_WRITE_BURST_SIZE,
|
|
78
|
-
highVolumeWritePauseMs: HIGH_VOLUME_WRITE_PAUSE_MS,
|
|
79
|
-
highVolumeWriteFlushDelayMs: HIGH_VOLUME_WRITE_FLUSH_DELAY_MS,
|
|
80
|
-
highVolumeWriteWithResponse: false,
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
84
|
-
|
|
85
|
-
const normalizePositiveInteger = (value: unknown, fallback: number) => {
|
|
86
|
-
const normalized = Number(value);
|
|
87
|
-
if (!Number.isFinite(normalized) || normalized <= 0) return fallback;
|
|
88
|
-
return Math.floor(normalized);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
|
|
92
|
-
protocolV2BleTuning = {
|
|
93
|
-
iosPacketLength: normalizePositiveInteger(
|
|
94
|
-
tuning.iosPacketLength,
|
|
95
|
-
protocolV2BleTuning.iosPacketLength
|
|
96
|
-
),
|
|
97
|
-
androidPacketLength: normalizePositiveInteger(
|
|
98
|
-
tuning.androidPacketLength,
|
|
99
|
-
protocolV2BleTuning.androidPacketLength
|
|
100
|
-
),
|
|
101
|
-
highVolumeWriteBurstSize: normalizePositiveInteger(
|
|
102
|
-
tuning.highVolumeWriteBurstSize,
|
|
103
|
-
protocolV2BleTuning.highVolumeWriteBurstSize
|
|
104
|
-
),
|
|
105
|
-
highVolumeWritePauseMs: normalizePositiveInteger(
|
|
106
|
-
tuning.highVolumeWritePauseMs,
|
|
107
|
-
protocolV2BleTuning.highVolumeWritePauseMs
|
|
108
|
-
),
|
|
109
|
-
highVolumeWriteFlushDelayMs: normalizePositiveInteger(
|
|
110
|
-
tuning.highVolumeWriteFlushDelayMs,
|
|
111
|
-
protocolV2BleTuning.highVolumeWriteFlushDelayMs
|
|
112
|
-
),
|
|
113
|
-
highVolumeWriteWithResponse:
|
|
114
|
-
tuning.highVolumeWriteWithResponse ?? protocolV2BleTuning.highVolumeWriteWithResponse,
|
|
115
|
-
};
|
|
116
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning configured:', protocolV2BleTuning);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function resetProtocolV2BleTuning() {
|
|
120
|
-
protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
121
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning reset:', protocolV2BleTuning);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function getProtocolV2BleTuning() {
|
|
125
|
-
return { ...protocolV2BleTuning };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
129
|
-
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function getDeviceDisplayName(device?: Device | null) {
|
|
133
|
-
return device?.name || device?.localName || null;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function isGenericBleService(uuid?: string | null) {
|
|
137
|
-
return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function hasKnownOneKeyService(device?: Device | null) {
|
|
141
|
-
return (device?.serviceUUIDs ?? []).some(serviceUuid =>
|
|
142
|
-
getInfosForServiceUuid(serviceUuid, 'classic')
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
41
|
|
|
146
42
|
let connectOptions: Record<string, unknown> = {
|
|
147
43
|
requestMTU: 256,
|
|
@@ -153,8 +49,7 @@ export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
|
153
49
|
|
|
154
50
|
const tryToGetConfiguration = (device: Device) => {
|
|
155
51
|
if (!device || !device.serviceUUIDs) return null;
|
|
156
|
-
const serviceUUID = device.serviceUUIDs
|
|
157
|
-
if (!serviceUUID) return null;
|
|
52
|
+
const [serviceUUID] = device.serviceUUIDs;
|
|
158
53
|
const infos = getInfosForServiceUuid(serviceUUID, 'classic');
|
|
159
54
|
if (!infos) return null;
|
|
160
55
|
return infos;
|
|
@@ -197,45 +92,23 @@ export default class ReactNativeBleTransport {
|
|
|
197
92
|
|
|
198
93
|
_messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
199
94
|
|
|
200
|
-
_messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
201
|
-
|
|
202
95
|
name = 'ReactNativeBleTransport';
|
|
203
96
|
|
|
204
97
|
configured = false;
|
|
205
98
|
|
|
206
99
|
stopped = false;
|
|
207
100
|
|
|
208
|
-
scanTimeout =
|
|
101
|
+
scanTimeout = 3000;
|
|
209
102
|
|
|
210
103
|
runPromise: Deferred<any> | null = null;
|
|
211
104
|
|
|
212
105
|
emitter?: EventEmitter;
|
|
213
106
|
|
|
214
|
-
/** Per-device protocol type detected by active wire-level probe after connect. */
|
|
215
|
-
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
216
|
-
|
|
217
|
-
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
218
|
-
|
|
219
|
-
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
220
|
-
|
|
221
|
-
private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
|
|
222
|
-
|
|
223
|
-
private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
224
|
-
|
|
225
|
-
private activeProtocolV2Call: { uuid: string; token: number } | null = null;
|
|
226
|
-
|
|
227
|
-
private nextProtocolV2CallToken = 1;
|
|
228
|
-
|
|
229
|
-
private monitorTokens: Map<string, number> = new Map();
|
|
230
|
-
|
|
231
|
-
private nextMonitorToken = 1;
|
|
232
|
-
|
|
233
107
|
constructor(options: TransportOptions) {
|
|
234
|
-
this.scanTimeout = options.scanTimeout ??
|
|
108
|
+
this.scanTimeout = options.scanTimeout ?? 3000;
|
|
235
109
|
}
|
|
236
110
|
|
|
237
|
-
init(
|
|
238
|
-
setBleLogger(logger);
|
|
111
|
+
init(_logger: any, emitter: EventEmitter) {
|
|
239
112
|
this.emitter = emitter;
|
|
240
113
|
}
|
|
241
114
|
|
|
@@ -245,11 +118,6 @@ export default class ReactNativeBleTransport {
|
|
|
245
118
|
this._messages = messages;
|
|
246
119
|
}
|
|
247
120
|
|
|
248
|
-
configureProtocolV2(signedData: any) {
|
|
249
|
-
this._messagesV2 = parseConfigure(signedData);
|
|
250
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V2 schema configured');
|
|
251
|
-
}
|
|
252
|
-
|
|
253
121
|
listen() {
|
|
254
122
|
// empty
|
|
255
123
|
}
|
|
@@ -299,7 +167,6 @@ export default class ReactNativeBleTransport {
|
|
|
299
167
|
blePlxManager.startDeviceScan(
|
|
300
168
|
null,
|
|
301
169
|
{
|
|
302
|
-
allowDuplicates: true,
|
|
303
170
|
scanMode: ScanMode.LowLatency,
|
|
304
171
|
},
|
|
305
172
|
(error, device) => {
|
|
@@ -327,41 +194,14 @@ export default class ReactNativeBleTransport {
|
|
|
327
194
|
return;
|
|
328
195
|
}
|
|
329
196
|
|
|
330
|
-
|
|
331
|
-
const isOneKey =
|
|
332
|
-
isOnekeyDevice(device?.name ?? null, device?.id) ||
|
|
333
|
-
isOnekeyDevice(device?.localName ?? null, device?.id) ||
|
|
334
|
-
hasKnownOneKeyService(device);
|
|
335
|
-
const shouldTraceCandidate =
|
|
336
|
-
!!displayName && /onekey|bixinkey|pro\s*2|pro\b|touch|^k\d|^t\d/i.test(displayName);
|
|
337
|
-
|
|
338
|
-
if (shouldTraceCandidate) {
|
|
339
|
-
Log?.debug('[ReactNativeBleTransport] scan candidate', {
|
|
340
|
-
name: device?.name,
|
|
341
|
-
localName: device?.localName,
|
|
342
|
-
id: device?.id,
|
|
343
|
-
serviceUUIDs: device?.serviceUUIDs,
|
|
344
|
-
accepted: isOneKey,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (isOneKey) {
|
|
197
|
+
if (isOnekeyDevice(device?.name ?? null, device?.id)) {
|
|
349
198
|
Log?.debug('search device start ======================');
|
|
350
|
-
const { name, localName, id
|
|
199
|
+
const { name, localName, id } = device ?? {};
|
|
351
200
|
Log?.debug(
|
|
352
|
-
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${
|
|
353
|
-
id ?? ''
|
|
354
|
-
}\nserviceUUIDs: ${(serviceUUIDs ?? []).join(',')}`
|
|
201
|
+
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${id ?? ''}`
|
|
355
202
|
);
|
|
356
203
|
addDevice(device as unknown as Device);
|
|
357
204
|
Log?.debug('search device end ======================\n');
|
|
358
|
-
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
359
|
-
Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
|
|
360
|
-
name: device?.name,
|
|
361
|
-
localName: device?.localName,
|
|
362
|
-
id: device?.id,
|
|
363
|
-
serviceUUIDs: device?.serviceUUIDs,
|
|
364
|
-
});
|
|
365
205
|
}
|
|
366
206
|
}
|
|
367
207
|
);
|
|
@@ -375,16 +215,7 @@ export default class ReactNativeBleTransport {
|
|
|
375
215
|
|
|
376
216
|
const addDevice = (device: Device) => {
|
|
377
217
|
if (deviceList.every(d => d.id !== device.id)) {
|
|
378
|
-
|
|
379
|
-
const protocolHint = inferProtocolHintFromDeviceName(displayName);
|
|
380
|
-
if (protocolHint) {
|
|
381
|
-
this.deviceProtocolHints.set(device.id, protocolHint);
|
|
382
|
-
}
|
|
383
|
-
deviceList.push({
|
|
384
|
-
...device,
|
|
385
|
-
name: displayName,
|
|
386
|
-
commType: 'ble',
|
|
387
|
-
} as IOneKeyDevice);
|
|
218
|
+
deviceList.push({ ...device, commType: 'ble' } as IOneKeyDevice);
|
|
388
219
|
}
|
|
389
220
|
};
|
|
390
221
|
|
|
@@ -396,7 +227,7 @@ export default class ReactNativeBleTransport {
|
|
|
396
227
|
}
|
|
397
228
|
|
|
398
229
|
async acquire(input: BleAcquireInput) {
|
|
399
|
-
const { uuid, forceCleanRunPromise
|
|
230
|
+
const { uuid, forceCleanRunPromise } = input;
|
|
400
231
|
|
|
401
232
|
if (!uuid) {
|
|
402
233
|
throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
|
|
@@ -414,11 +245,7 @@ export default class ReactNativeBleTransport {
|
|
|
414
245
|
}
|
|
415
246
|
|
|
416
247
|
if (forceCleanRunPromise && this.runPromise) {
|
|
417
|
-
|
|
418
|
-
this.runPromise.reject(error);
|
|
419
|
-
this.rejectAllProtocolV2Frames(error);
|
|
420
|
-
this.runPromise = null;
|
|
421
|
-
this.activeProtocolV2Call = null;
|
|
248
|
+
this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
|
|
422
249
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
423
250
|
}
|
|
424
251
|
|
|
@@ -480,7 +307,7 @@ export default class ReactNativeBleTransport {
|
|
|
480
307
|
Log?.debug('not connected, try to connect to device: ', uuid);
|
|
481
308
|
|
|
482
309
|
try {
|
|
483
|
-
|
|
310
|
+
await device.connect(connectOptions);
|
|
484
311
|
} catch (e) {
|
|
485
312
|
Log?.debug('not connected, try to connect to device has error: ', e);
|
|
486
313
|
if (
|
|
@@ -490,7 +317,7 @@ export default class ReactNativeBleTransport {
|
|
|
490
317
|
connectOptions = {};
|
|
491
318
|
Log?.debug('second try to reconnect without params');
|
|
492
319
|
try {
|
|
493
|
-
|
|
320
|
+
await device.connect();
|
|
494
321
|
} catch (e) {
|
|
495
322
|
Log?.debug('last try to reconnect error: ', e);
|
|
496
323
|
// last try to reconnect device if this issue exists
|
|
@@ -498,7 +325,7 @@ export default class ReactNativeBleTransport {
|
|
|
498
325
|
if (e.errorCode === BleErrorCode.OperationCancelled) {
|
|
499
326
|
Log?.debug('last try to reconnect');
|
|
500
327
|
await device.cancelConnection();
|
|
501
|
-
|
|
328
|
+
await device.connect();
|
|
502
329
|
}
|
|
503
330
|
}
|
|
504
331
|
} else {
|
|
@@ -510,7 +337,6 @@ export default class ReactNativeBleTransport {
|
|
|
510
337
|
await device.discoverAllServicesAndCharacteristics();
|
|
511
338
|
let infos = tryToGetConfiguration(device);
|
|
512
339
|
let characteristics;
|
|
513
|
-
let fallbackServiceUuid: string | undefined;
|
|
514
340
|
|
|
515
341
|
if (!infos) {
|
|
516
342
|
for (const serviceUuid of getBluetoothServiceUuids()) {
|
|
@@ -525,45 +351,17 @@ export default class ReactNativeBleTransport {
|
|
|
525
351
|
}
|
|
526
352
|
|
|
527
353
|
if (!infos) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const knownService = services.find(service =>
|
|
535
|
-
getInfosForServiceUuid(service.uuid, 'classic')
|
|
536
|
-
);
|
|
537
|
-
const fallbackService =
|
|
538
|
-
knownService ?? services.find(service => !isGenericBleService(service.uuid)) ?? services[0];
|
|
539
|
-
|
|
540
|
-
if (fallbackService) {
|
|
541
|
-
fallbackServiceUuid = fallbackService.uuid;
|
|
542
|
-
characteristics = await device.characteristicsForService(fallbackService.uuid);
|
|
543
|
-
Log?.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
if (!infos) {
|
|
548
|
-
if (!fallbackServiceUuid) {
|
|
549
|
-
try {
|
|
550
|
-
Log?.debug('cancel connection when service not found');
|
|
551
|
-
await device.cancelConnection();
|
|
552
|
-
} catch (e) {
|
|
553
|
-
Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
|
|
554
|
-
}
|
|
555
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
354
|
+
try {
|
|
355
|
+
Log?.debug('cancel connection when service not found');
|
|
356
|
+
await device.cancelConnection();
|
|
357
|
+
} catch (e) {
|
|
358
|
+
Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
|
|
556
359
|
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const serviceUuid = infos?.serviceUuid ?? fallbackServiceUuid;
|
|
560
|
-
const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
|
|
561
|
-
const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
|
|
562
|
-
|
|
563
|
-
if (!serviceUuid) {
|
|
564
360
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
565
361
|
}
|
|
566
362
|
|
|
363
|
+
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
364
|
+
|
|
567
365
|
if (!characteristics) {
|
|
568
366
|
characteristics = await device.characteristicsForService(serviceUuid);
|
|
569
367
|
}
|
|
@@ -575,9 +373,9 @@ export default class ReactNativeBleTransport {
|
|
|
575
373
|
let writeCharacteristic;
|
|
576
374
|
let notifyCharacteristic;
|
|
577
375
|
for (const c of characteristics) {
|
|
578
|
-
if (
|
|
376
|
+
if (c.uuid === writeUuid) {
|
|
579
377
|
writeCharacteristic = c;
|
|
580
|
-
} else if (
|
|
378
|
+
} else if (c.uuid === notifyUuid) {
|
|
581
379
|
notifyCharacteristic = c;
|
|
582
380
|
}
|
|
583
381
|
}
|
|
@@ -600,42 +398,16 @@ export default class ReactNativeBleTransport {
|
|
|
600
398
|
);
|
|
601
399
|
}
|
|
602
400
|
|
|
603
|
-
const protocolHint = expectedProtocol
|
|
604
|
-
? undefined
|
|
605
|
-
: this.deviceProtocolHints.get(uuid) ??
|
|
606
|
-
inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
|
|
607
|
-
|
|
608
401
|
// release transport before new transport instance
|
|
609
402
|
await this.release(uuid);
|
|
610
|
-
if (protocolHint) {
|
|
611
|
-
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
612
|
-
}
|
|
613
403
|
|
|
614
404
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
615
|
-
const monitorToken = this.nextMonitorToken;
|
|
616
|
-
this.nextMonitorToken += 1;
|
|
617
|
-
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
618
|
-
transport.monitorToken = monitorToken;
|
|
619
|
-
transport.notifyTransactionId = notifyTransactionId;
|
|
620
|
-
this.monitorTokens.set(uuid, monitorToken);
|
|
621
405
|
transport.notifySubscription = this._monitorCharacteristic(
|
|
622
406
|
transport.notifyCharacteristic,
|
|
623
|
-
uuid
|
|
624
|
-
monitorToken,
|
|
625
|
-
notifyTransactionId
|
|
407
|
+
uuid
|
|
626
408
|
);
|
|
627
409
|
transportCache[uuid] = transport;
|
|
628
410
|
|
|
629
|
-
this.protocolV2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
|
|
630
|
-
|
|
631
|
-
if (Platform.OS === 'ios') {
|
|
632
|
-
await new Promise<void>(resolve => {
|
|
633
|
-
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
638
|
-
|
|
639
411
|
this.emitter?.emit('device-connect', {
|
|
640
412
|
name: device.name,
|
|
641
413
|
id: device.id,
|
|
@@ -643,11 +415,6 @@ export default class ReactNativeBleTransport {
|
|
|
643
415
|
});
|
|
644
416
|
|
|
645
417
|
transport.disconnectSubscription = device.onDisconnected(() => {
|
|
646
|
-
if (transportCache[uuid] !== transport) {
|
|
647
|
-
Log?.debug('device disconnect ignored for stale transport: ', device?.id);
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
418
|
try {
|
|
652
419
|
Log?.debug('device disconnect: ', device?.id);
|
|
653
420
|
this.emitter?.emit('device-disconnect', {
|
|
@@ -656,9 +423,7 @@ export default class ReactNativeBleTransport {
|
|
|
656
423
|
connectId: device?.id,
|
|
657
424
|
});
|
|
658
425
|
if (this.runPromise) {
|
|
659
|
-
|
|
660
|
-
this.runPromise.reject(error);
|
|
661
|
-
this.rejectAllProtocolV2Frames(error);
|
|
426
|
+
this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError));
|
|
662
427
|
}
|
|
663
428
|
} catch (e) {
|
|
664
429
|
Log?.debug('device disconnect error: ', e);
|
|
@@ -667,29 +432,19 @@ export default class ReactNativeBleTransport {
|
|
|
667
432
|
}
|
|
668
433
|
});
|
|
669
434
|
|
|
670
|
-
return { uuid
|
|
435
|
+
return { uuid };
|
|
671
436
|
}
|
|
672
437
|
|
|
673
|
-
_monitorCharacteristic(
|
|
674
|
-
characteristic: Characteristic,
|
|
675
|
-
uuid: string,
|
|
676
|
-
monitorToken: number,
|
|
677
|
-
notifyTransactionId: string
|
|
678
|
-
): Subscription {
|
|
438
|
+
_monitorCharacteristic(characteristic: Characteristic, uuid: string): Subscription {
|
|
679
439
|
let bufferLength = 0;
|
|
680
440
|
let buffer: any[] = [];
|
|
681
441
|
const subscription = characteristic.monitor((error, c) => {
|
|
682
|
-
const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
|
|
683
442
|
if (error) {
|
|
684
443
|
Log?.debug(
|
|
685
444
|
`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${
|
|
686
445
|
error as unknown as string
|
|
687
446
|
}`
|
|
688
447
|
);
|
|
689
|
-
if (!isCurrentMonitor) {
|
|
690
|
-
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
448
|
if (this.runPromise) {
|
|
694
449
|
let ERROR:
|
|
695
450
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
@@ -709,40 +464,27 @@ export default class ReactNativeBleTransport {
|
|
|
709
464
|
error.reason?.includes('Writing is not permitted') || // pro firmware 2.3.4 upgrade
|
|
710
465
|
error.reason?.includes('notify change failed for device')
|
|
711
466
|
) {
|
|
712
|
-
|
|
713
|
-
HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
467
|
+
this.runPromise.reject(
|
|
468
|
+
ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotifyChangeFailure)
|
|
714
469
|
);
|
|
715
|
-
this.runPromise.reject(notifyError);
|
|
716
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
717
470
|
Log?.debug(
|
|
718
471
|
`${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
|
|
719
472
|
);
|
|
720
473
|
return;
|
|
721
474
|
}
|
|
722
|
-
|
|
723
|
-
this.runPromise.reject(notifyError);
|
|
724
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
475
|
+
this.runPromise.reject(ERRORS.TypedError(ERROR));
|
|
725
476
|
Log?.debug(': monitor notify error, and has unreleased Promise', Error);
|
|
726
477
|
}
|
|
727
478
|
|
|
728
479
|
return;
|
|
729
480
|
}
|
|
730
481
|
|
|
731
|
-
if (!isCurrentMonitor) {
|
|
732
|
-
Log?.debug('monitor data ignored for stale transport: ', uuid, notifyTransactionId);
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
482
|
if (!c) {
|
|
737
483
|
throw ERRORS.TypedError(HardwareErrorCode.BleMonitorError);
|
|
738
484
|
}
|
|
739
485
|
|
|
740
486
|
try {
|
|
741
487
|
const data = Buffer.from(c.value as string, 'base64');
|
|
742
|
-
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
743
|
-
this.handleProtocolV2Notification(uuid, new Uint8Array(data));
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
488
|
// console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
|
|
747
489
|
if (isHeaderChunk(data)) {
|
|
748
490
|
bufferLength = data.readInt32BE(5);
|
|
@@ -751,7 +493,7 @@ export default class ReactNativeBleTransport {
|
|
|
751
493
|
buffer = buffer.concat([...data]);
|
|
752
494
|
}
|
|
753
495
|
|
|
754
|
-
if (buffer.length -
|
|
496
|
+
if (buffer.length - COMMON_HEADER_SIZE >= bufferLength) {
|
|
755
497
|
const value = Buffer.from(buffer);
|
|
756
498
|
// console.log(
|
|
757
499
|
// '[hd-transport-react-native] Received a complete packet of data, resolve Promise, this.runPromise: ',
|
|
@@ -765,32 +507,17 @@ export default class ReactNativeBleTransport {
|
|
|
765
507
|
}
|
|
766
508
|
} catch (error) {
|
|
767
509
|
Log?.debug('monitor data error: ', error);
|
|
768
|
-
|
|
769
|
-
this.runPromise?.reject(notifyError);
|
|
770
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
510
|
+
this.runPromise?.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
|
|
771
511
|
}
|
|
772
|
-
},
|
|
512
|
+
}, uuid);
|
|
773
513
|
|
|
774
514
|
return subscription;
|
|
775
515
|
}
|
|
776
516
|
|
|
777
517
|
async release(uuid: string) {
|
|
778
518
|
const transport = transportCache[uuid];
|
|
779
|
-
if (this.runPromise) {
|
|
780
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
781
|
-
this.runPromise.reject(error);
|
|
782
|
-
this.runPromise = null;
|
|
783
|
-
this.rejectAllProtocolV2Frames(error);
|
|
784
|
-
this.activeProtocolV2Call = null;
|
|
785
|
-
} else {
|
|
786
|
-
this.resetProtocolV2Frames(uuid);
|
|
787
|
-
}
|
|
788
519
|
|
|
789
520
|
if (transport) {
|
|
790
|
-
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
791
|
-
this.monitorTokens.delete(uuid);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
521
|
// Clean up disconnect subscription first to prevent callbacks on released transport
|
|
795
522
|
Log?.debug('release: removing disconnect subscription for device: ', uuid);
|
|
796
523
|
transport.disconnectSubscription?.remove();
|
|
@@ -804,27 +531,12 @@ export default class ReactNativeBleTransport {
|
|
|
804
531
|
transport.notifySubscription?.remove();
|
|
805
532
|
transport.notifySubscription = undefined;
|
|
806
533
|
|
|
807
|
-
if (transport.notifyTransactionId) {
|
|
808
|
-
try {
|
|
809
|
-
await this.blePlxManager?.cancelTransaction(transport.notifyTransactionId);
|
|
810
|
-
} catch (e) {
|
|
811
|
-
Log?.debug('release: cancel notify transaction error (ignored): ', e?.message || e);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
534
|
delete transportCache[uuid];
|
|
816
|
-
}
|
|
817
535
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
this.resetProtocolV2Frames(uuid);
|
|
823
|
-
|
|
824
|
-
try {
|
|
825
|
-
await this.blePlxManager?.cancelTransaction(uuid);
|
|
826
|
-
} catch (e) {
|
|
827
|
-
Log?.debug('release: cancel transaction error (ignored): ', e?.message || e);
|
|
536
|
+
// Temporary close the Android disconnect after each request
|
|
537
|
+
if (Platform.OS === 'android') {
|
|
538
|
+
// await this.blePlxManager?.cancelDeviceConnection(uuid);
|
|
539
|
+
}
|
|
828
540
|
}
|
|
829
541
|
|
|
830
542
|
return Promise.resolve(true);
|
|
@@ -834,12 +546,7 @@ export default class ReactNativeBleTransport {
|
|
|
834
546
|
await this.call(session, name, data);
|
|
835
547
|
}
|
|
836
548
|
|
|
837
|
-
async call(
|
|
838
|
-
uuid: string,
|
|
839
|
-
name: string,
|
|
840
|
-
data: Record<string, unknown>,
|
|
841
|
-
options?: TransportCallOptions
|
|
842
|
-
) {
|
|
549
|
+
async call(uuid: string, name: string, data: Record<string, unknown>) {
|
|
843
550
|
if (this.stopped) {
|
|
844
551
|
// eslint-disable-next-line prefer-promise-reject-errors
|
|
845
552
|
return Promise.reject(ERRORS.TypedError('Transport stopped.'));
|
|
@@ -855,7 +562,13 @@ export default class ReactNativeBleTransport {
|
|
|
855
562
|
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
856
563
|
}
|
|
857
564
|
|
|
858
|
-
const
|
|
565
|
+
const transport = transportCache[uuid];
|
|
566
|
+
if (!transport) {
|
|
567
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
this.runPromise = createDeferred();
|
|
571
|
+
const messages = this._messages;
|
|
859
572
|
// Upload resources on low-end phones may OOM
|
|
860
573
|
if (name === 'ResourceUpdate' || name === 'ResourceAck') {
|
|
861
574
|
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
|
|
@@ -863,44 +576,12 @@ export default class ReactNativeBleTransport {
|
|
|
863
576
|
hash: data?.hash,
|
|
864
577
|
});
|
|
865
578
|
} else if (LogBlockCommand.has(name)) {
|
|
866
|
-
Log?.debug('transport-react-native', 'call-', ' name: ', name
|
|
579
|
+
Log?.debug('transport-react-native', 'call-', ' name: ', name);
|
|
867
580
|
} else {
|
|
868
|
-
Log?.debug(
|
|
869
|
-
'transport-react-native',
|
|
870
|
-
'call-',
|
|
871
|
-
' name: ',
|
|
872
|
-
name,
|
|
873
|
-
' data: ',
|
|
874
|
-
data,
|
|
875
|
-
' protocol: ',
|
|
876
|
-
protocol
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
if (protocol === 'V2') {
|
|
881
|
-
return this.callProtocolV2(uuid, name, data, options);
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
return this.callProtocolV1(uuid, name, data, options);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
private async callProtocolV1(
|
|
888
|
-
uuid: string,
|
|
889
|
-
name: string,
|
|
890
|
-
data: Record<string, unknown>,
|
|
891
|
-
options?: TransportCallOptions
|
|
892
|
-
) {
|
|
893
|
-
if (!this._messages) {
|
|
894
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
581
|
+
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', data);
|
|
895
582
|
}
|
|
896
583
|
|
|
897
|
-
const
|
|
898
|
-
const runPromise = createDeferred<string>();
|
|
899
|
-
runPromise.promise.catch(() => undefined);
|
|
900
|
-
this.runPromise = runPromise;
|
|
901
|
-
const messages = this._messages;
|
|
902
|
-
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
903
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
584
|
+
const buffers = buildBuffers(messages, name, data);
|
|
904
585
|
|
|
905
586
|
async function writeChunkedData(
|
|
906
587
|
buffers: ByteBuffer[],
|
|
@@ -971,41 +652,20 @@ export default class ReactNativeBleTransport {
|
|
|
971
652
|
}
|
|
972
653
|
|
|
973
654
|
try {
|
|
974
|
-
const response = await
|
|
975
|
-
runPromise.promise,
|
|
976
|
-
new Promise<never>((_, reject) => {
|
|
977
|
-
if (options?.timeoutMs) {
|
|
978
|
-
timeout = setTimeout(() => {
|
|
979
|
-
const error = ERRORS.TypedError(
|
|
980
|
-
HardwareErrorCode.BleTimeoutError,
|
|
981
|
-
`BLE response timeout after ${options.timeoutMs}ms for ${name}`
|
|
982
|
-
);
|
|
983
|
-
runPromise.reject(error);
|
|
984
|
-
reject(error);
|
|
985
|
-
}, options.timeoutMs);
|
|
986
|
-
}
|
|
987
|
-
}),
|
|
988
|
-
]);
|
|
655
|
+
const response = await this.runPromise.promise;
|
|
989
656
|
|
|
990
657
|
if (typeof response !== 'string') {
|
|
991
658
|
throw new Error('Returning data is not string.');
|
|
992
659
|
}
|
|
993
660
|
|
|
994
661
|
Log?.debug('receive data: ', response);
|
|
995
|
-
const jsonData =
|
|
662
|
+
const jsonData = receiveOne(messages, response);
|
|
996
663
|
return check.call(jsonData);
|
|
997
664
|
} catch (e) {
|
|
998
|
-
|
|
999
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe call failed:', e);
|
|
1000
|
-
} else {
|
|
1001
|
-
Log?.error('call error: ', e);
|
|
1002
|
-
}
|
|
665
|
+
Log?.error('call error: ', e);
|
|
1003
666
|
throw e;
|
|
1004
667
|
} finally {
|
|
1005
|
-
|
|
1006
|
-
if (this.runPromise === runPromise) {
|
|
1007
|
-
this.runPromise = null;
|
|
1008
|
-
}
|
|
668
|
+
this.runPromise = null;
|
|
1009
669
|
}
|
|
1010
670
|
}
|
|
1011
671
|
|
|
@@ -1072,13 +732,6 @@ export default class ReactNativeBleTransport {
|
|
|
1072
732
|
if (transportCache[session]) {
|
|
1073
733
|
delete transportCache[session];
|
|
1074
734
|
}
|
|
1075
|
-
this.deviceProtocol.delete(session);
|
|
1076
|
-
this.deviceProtocolHints.delete(session);
|
|
1077
|
-
this.protocolV2Assemblers.delete(session);
|
|
1078
|
-
this.resetProtocolV2Frames(session);
|
|
1079
|
-
if (this.activeProtocolV2Call?.uuid === session) {
|
|
1080
|
-
this.activeProtocolV2Call = null;
|
|
1081
|
-
}
|
|
1082
735
|
|
|
1083
736
|
// emit the disconnect event
|
|
1084
737
|
try {
|
|
@@ -1101,390 +754,4 @@ export default class ReactNativeBleTransport {
|
|
|
1101
754
|
}
|
|
1102
755
|
this.runPromise = null;
|
|
1103
756
|
}
|
|
1104
|
-
|
|
1105
|
-
private getCachedTransport(uuid: string) {
|
|
1106
|
-
const transport = transportCache[uuid];
|
|
1107
|
-
if (!transport) {
|
|
1108
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
1109
|
-
}
|
|
1110
|
-
return transport;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
private createProtocolMismatchError(expected: ProtocolType) {
|
|
1114
|
-
return ERRORS.TypedError(
|
|
1115
|
-
HardwareErrorCode.RuntimeError,
|
|
1116
|
-
`Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
|
|
1117
|
-
);
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
private async detectProtocol(
|
|
1121
|
-
uuid: string,
|
|
1122
|
-
expectedProtocol?: ProtocolType,
|
|
1123
|
-
protocolHint?: ProtocolType
|
|
1124
|
-
): Promise<ProtocolType> {
|
|
1125
|
-
if (expectedProtocol === 'V1') {
|
|
1126
|
-
if (await this.probeProtocolV1(uuid)) {
|
|
1127
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
1128
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1 (expected)`);
|
|
1129
|
-
return 'V1';
|
|
1130
|
-
}
|
|
1131
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
if (expectedProtocol === 'V2') {
|
|
1135
|
-
if (await this.probeProtocolV2(uuid)) {
|
|
1136
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
1137
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1138
|
-
return 'V2';
|
|
1139
|
-
}
|
|
1140
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
if (protocolHint === 'V2' && (await this.probeProtocolV2(uuid))) {
|
|
1144
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
1145
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
|
|
1146
|
-
return 'V2';
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
if (this.deviceProtocol.get(uuid) === 'V2' && (await this.probeProtocolV2(uuid))) {
|
|
1150
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
1151
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
|
|
1152
|
-
return 'V2';
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
let protocol: ProtocolType = 'V1';
|
|
1156
|
-
const protocolV1Detected = await this.probeProtocolV1(uuid);
|
|
1157
|
-
if (!protocolV1Detected) {
|
|
1158
|
-
await this.resetProbeStateAfterProtocolProbe(uuid, 'V1');
|
|
1159
|
-
if (await this.probeProtocolV2(uuid)) {
|
|
1160
|
-
protocol = 'V2';
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
this.deviceProtocol.set(uuid, protocol);
|
|
1164
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
1165
|
-
return protocol;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
1169
|
-
const transport = transportCache[uuid];
|
|
1170
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1171
|
-
this.resetProtocolV2Frames(uuid);
|
|
1172
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
1173
|
-
this.activeProtocolV2Call = null;
|
|
1174
|
-
}
|
|
1175
|
-
if (this.runPromise) {
|
|
1176
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1177
|
-
this.runPromise.reject(error);
|
|
1178
|
-
this.runPromise = null;
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
if (!transport) return;
|
|
1182
|
-
|
|
1183
|
-
const previousNotifyTransactionId = transport.notifyTransactionId;
|
|
1184
|
-
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1185
|
-
this.monitorTokens.delete(uuid);
|
|
1186
|
-
}
|
|
1187
|
-
transport.notifySubscription?.remove();
|
|
1188
|
-
transport.notifySubscription = undefined;
|
|
1189
|
-
if (previousNotifyTransactionId) {
|
|
1190
|
-
try {
|
|
1191
|
-
await this.blePlxManager?.cancelTransaction(previousNotifyTransactionId);
|
|
1192
|
-
} catch (error) {
|
|
1193
|
-
Log?.debug(
|
|
1194
|
-
`[ReactNativeBleTransport] cancel notify after Protocol ${protocol} probe failed:`,
|
|
1195
|
-
error?.message || error
|
|
1196
|
-
);
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
const monitorToken = this.nextMonitorToken;
|
|
1201
|
-
this.nextMonitorToken += 1;
|
|
1202
|
-
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
1203
|
-
transport.monitorToken = monitorToken;
|
|
1204
|
-
transport.notifyTransactionId = notifyTransactionId;
|
|
1205
|
-
this.monitorTokens.set(uuid, monitorToken);
|
|
1206
|
-
transport.notifySubscription = this._monitorCharacteristic(
|
|
1207
|
-
transport.notifyCharacteristic,
|
|
1208
|
-
uuid,
|
|
1209
|
-
monitorToken,
|
|
1210
|
-
notifyTransactionId
|
|
1211
|
-
);
|
|
1212
|
-
if (Platform.OS === 'ios') {
|
|
1213
|
-
await new Promise<void>(resolve => {
|
|
1214
|
-
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
1215
|
-
});
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
private async probeProtocolV1(uuid: string) {
|
|
1220
|
-
if (!this._messages) {
|
|
1221
|
-
return false;
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
try {
|
|
1225
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
1226
|
-
await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1227
|
-
return true;
|
|
1228
|
-
} catch (error) {
|
|
1229
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
|
|
1230
|
-
return false;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
private async probeProtocolV2(uuid: string) {
|
|
1235
|
-
if (!this._messages || !this._messagesV2) {
|
|
1236
|
-
return false;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
1240
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1241
|
-
return probeProtocolV2Helper({
|
|
1242
|
-
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
1243
|
-
this.callProtocolV2(uuid, name, data, options),
|
|
1244
|
-
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
|
|
1245
|
-
logger: Log,
|
|
1246
|
-
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1247
|
-
onProbeFailed: () => {
|
|
1248
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1249
|
-
this.resetProtocolV2Frames(uuid);
|
|
1250
|
-
},
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
|
|
1255
|
-
try {
|
|
1256
|
-
if (!this.runPromise || this.activeProtocolV2Call?.uuid !== uuid) {
|
|
1257
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1258
|
-
this.resetProtocolV2Frames(uuid);
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
if (data.length === 0) return;
|
|
1263
|
-
|
|
1264
|
-
const assembler = this.protocolV2Assemblers.get(uuid);
|
|
1265
|
-
if (!assembler) return;
|
|
1266
|
-
|
|
1267
|
-
let frameData = assembler.push(data);
|
|
1268
|
-
while (frameData) {
|
|
1269
|
-
this.resolveProtocolV2Frame(uuid, frameData);
|
|
1270
|
-
frameData = assembler.push(new Uint8Array(0));
|
|
1271
|
-
}
|
|
1272
|
-
} catch (error) {
|
|
1273
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
|
|
1274
|
-
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1275
|
-
this.runPromise?.reject(notifyError);
|
|
1276
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
private getProtocolV2FrameQueue(uuid: string) {
|
|
1281
|
-
let queue = this.protocolV2FrameQueues.get(uuid);
|
|
1282
|
-
if (!queue) {
|
|
1283
|
-
queue = [];
|
|
1284
|
-
this.protocolV2FrameQueues.set(uuid, queue);
|
|
1285
|
-
}
|
|
1286
|
-
return queue;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
private resolveProtocolV2Frame(uuid: string, frame: Uint8Array) {
|
|
1290
|
-
const framePromise = this.protocolV2FramePromises.get(uuid);
|
|
1291
|
-
if (framePromise) {
|
|
1292
|
-
framePromise.resolve(frame);
|
|
1293
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1294
|
-
return;
|
|
1295
|
-
}
|
|
1296
|
-
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
private rejectAllProtocolV2Frames(error: Error) {
|
|
1300
|
-
this.protocolV2FrameQueues.clear();
|
|
1301
|
-
for (const framePromise of this.protocolV2FramePromises.values()) {
|
|
1302
|
-
framePromise.reject(error);
|
|
1303
|
-
}
|
|
1304
|
-
this.protocolV2FramePromises.clear();
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
private resetProtocolV2Frames(uuid: string) {
|
|
1308
|
-
this.protocolV2FrameQueues.delete(uuid);
|
|
1309
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
private isActiveProtocolV2Call(uuid: string, token: number) {
|
|
1313
|
-
return this.activeProtocolV2Call?.uuid === uuid && this.activeProtocolV2Call.token === token;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
private async readProtocolV2Frame(uuid: string) {
|
|
1317
|
-
const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
|
|
1318
|
-
if (queuedFrame) {
|
|
1319
|
-
return queuedFrame;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
const framePromise = createDeferred<Uint8Array>();
|
|
1323
|
-
this.protocolV2FramePromises.set(uuid, framePromise);
|
|
1324
|
-
try {
|
|
1325
|
-
return await framePromise.promise;
|
|
1326
|
-
} finally {
|
|
1327
|
-
if (this.protocolV2FramePromises.get(uuid) === framePromise) {
|
|
1328
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
private async writeProtocolV2Frame(
|
|
1334
|
-
transport: BleTransport,
|
|
1335
|
-
frame: Uint8Array,
|
|
1336
|
-
options?: { highVolume?: boolean; writeWithResponse?: boolean }
|
|
1337
|
-
) {
|
|
1338
|
-
const tuning = getProtocolV2BleTuning();
|
|
1339
|
-
const packetCapacity =
|
|
1340
|
-
Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength;
|
|
1341
|
-
const writeWithResponse =
|
|
1342
|
-
!!options?.writeWithResponse || (!!options?.highVolume && tuning.highVolumeWriteWithResponse);
|
|
1343
|
-
const shouldThrottle = !!options?.highVolume && !writeWithResponse;
|
|
1344
|
-
let packetsWritten = 0;
|
|
1345
|
-
|
|
1346
|
-
try {
|
|
1347
|
-
for (let offset = 0; offset < frame.length; offset += packetCapacity) {
|
|
1348
|
-
const chunk = frame.slice(offset, offset + packetCapacity);
|
|
1349
|
-
const base64 = Buffer.from(chunk).toString('base64');
|
|
1350
|
-
if (writeWithResponse) {
|
|
1351
|
-
await transport.writeCharacteristic.writeWithResponse(base64);
|
|
1352
|
-
} else {
|
|
1353
|
-
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1354
|
-
}
|
|
1355
|
-
packetsWritten += 1;
|
|
1356
|
-
|
|
1357
|
-
if (
|
|
1358
|
-
shouldThrottle &&
|
|
1359
|
-
packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
|
|
1360
|
-
offset + packetCapacity < frame.length
|
|
1361
|
-
) {
|
|
1362
|
-
await delay(tuning.highVolumeWritePauseMs);
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
if (shouldThrottle) {
|
|
1367
|
-
await delay(tuning.highVolumeWriteFlushDelayMs);
|
|
1368
|
-
}
|
|
1369
|
-
} catch (error) {
|
|
1370
|
-
if (options?.highVolume && !writeWithResponse && packetsWritten === 0) {
|
|
1371
|
-
Log?.debug(
|
|
1372
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume writeWithoutResponse failed before data was sent, fallback to writeWithResponse:',
|
|
1373
|
-
error
|
|
1374
|
-
);
|
|
1375
|
-
await this.writeProtocolV2Frame(transport, frame, {
|
|
1376
|
-
highVolume: true,
|
|
1377
|
-
writeWithResponse: true,
|
|
1378
|
-
});
|
|
1379
|
-
return;
|
|
1380
|
-
}
|
|
1381
|
-
throw error;
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
private async callProtocolV2(
|
|
1386
|
-
uuid: string,
|
|
1387
|
-
name: string,
|
|
1388
|
-
data: Record<string, unknown>,
|
|
1389
|
-
options?: TransportCallOptions
|
|
1390
|
-
) {
|
|
1391
|
-
if (!this._messages || !this._messagesV2) {
|
|
1392
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'GetProtoVersion';
|
|
1396
|
-
if (this.runPromise) {
|
|
1397
|
-
if (!forceRun) {
|
|
1398
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1399
|
-
}
|
|
1400
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1401
|
-
this.runPromise.reject(error);
|
|
1402
|
-
this.rejectAllProtocolV2Frames(error);
|
|
1403
|
-
this.runPromise = null;
|
|
1404
|
-
this.activeProtocolV2Call = null;
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
const transport = this.getCachedTransport(uuid);
|
|
1408
|
-
const runPromise = createDeferred<Uint8Array>();
|
|
1409
|
-
runPromise.promise.catch(() => undefined);
|
|
1410
|
-
this.runPromise = runPromise;
|
|
1411
|
-
const callToken = this.nextProtocolV2CallToken++;
|
|
1412
|
-
this.activeProtocolV2Call = { uuid, token: callToken };
|
|
1413
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1414
|
-
this.resetProtocolV2Frames(uuid);
|
|
1415
|
-
let completed = false;
|
|
1416
|
-
const callOptions = {
|
|
1417
|
-
...options,
|
|
1418
|
-
timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
|
|
1419
|
-
};
|
|
1420
|
-
const highVolumeWrite = LogBlockCommand.has(name);
|
|
1421
|
-
|
|
1422
|
-
if (highVolumeWrite) {
|
|
1423
|
-
const tuning = getProtocolV2BleTuning();
|
|
1424
|
-
Log?.debug(
|
|
1425
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
|
|
1426
|
-
name,
|
|
1427
|
-
{
|
|
1428
|
-
packetCapacity:
|
|
1429
|
-
Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1430
|
-
burstSize: tuning.highVolumeWriteBurstSize,
|
|
1431
|
-
pauseMs: tuning.highVolumeWritePauseMs,
|
|
1432
|
-
flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
|
|
1433
|
-
writeWithResponse: tuning.highVolumeWriteWithResponse,
|
|
1434
|
-
}
|
|
1435
|
-
);
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
try {
|
|
1439
|
-
const session = new ProtocolV2Session({
|
|
1440
|
-
schemas: {
|
|
1441
|
-
protocolV1: this._messages,
|
|
1442
|
-
protocolV2: this._messagesV2,
|
|
1443
|
-
},
|
|
1444
|
-
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1445
|
-
writeFrame: (frame: Uint8Array) =>
|
|
1446
|
-
this.writeProtocolV2Frame(transport, frame, { highVolume: highVolumeWrite }),
|
|
1447
|
-
readFrame: async () => {
|
|
1448
|
-
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1449
|
-
if (!(rxFrame instanceof Uint8Array)) {
|
|
1450
|
-
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1451
|
-
}
|
|
1452
|
-
return rxFrame;
|
|
1453
|
-
},
|
|
1454
|
-
logger: Log,
|
|
1455
|
-
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1456
|
-
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
1457
|
-
ERRORS.TypedError(
|
|
1458
|
-
HardwareErrorCode.BleTimeoutError,
|
|
1459
|
-
`BLE response timeout after ${timeout}ms for ${name}`
|
|
1460
|
-
),
|
|
1461
|
-
});
|
|
1462
|
-
|
|
1463
|
-
const result = await session.call(name, data, callOptions);
|
|
1464
|
-
completed = true;
|
|
1465
|
-
return result;
|
|
1466
|
-
} catch (e) {
|
|
1467
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1468
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1469
|
-
this.resetProtocolV2Frames(uuid);
|
|
1470
|
-
}
|
|
1471
|
-
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1472
|
-
throw e;
|
|
1473
|
-
} finally {
|
|
1474
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1475
|
-
if (!completed) {
|
|
1476
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1477
|
-
}
|
|
1478
|
-
this.resetProtocolV2Frames(uuid);
|
|
1479
|
-
this.activeProtocolV2Call = null;
|
|
1480
|
-
}
|
|
1481
|
-
if (this.runPromise === runPromise) {
|
|
1482
|
-
this.runPromise = null;
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
getProtocolType(path: string): ProtocolType {
|
|
1488
|
-
return this.deviceProtocol.get(path) ?? 'V1';
|
|
1489
|
-
}
|
|
1490
757
|
}
|