@onekeyfe/hd-transport-react-native 1.2.0-alpha.4 → 1.2.0-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/BleTransport.d.ts +1 -3
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/bleStrategy.d.ts +0 -2
- package/dist/bleStrategy.d.ts.map +1 -1
- package/dist/constants.d.ts +0 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +21 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +364 -369
- package/dist/subscribeBleOn.d.ts.map +1 -1
- package/dist/transportLog.d.ts +2 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/jest.config.js +5 -0
- package/package.json +7 -6
- package/src/BleTransport.ts +2 -43
- package/src/__tests__/BleTransport.test.ts +41 -0
- package/src/__tests__/bleStrategy.test.ts +1 -6
- package/src/__tests__/constants.test.ts +20 -0
- package/src/__tests__/protocolV2Link.test.ts +520 -0
- package/src/bleStrategy.ts +0 -25
- package/src/constants.ts +7 -13
- package/src/index.ts +428 -388
- package/src/subscribeBleOn.ts +0 -2
- package/src/transportLog.ts +1 -0
- package/src/types.ts +1 -0
package/src/index.ts
CHANGED
|
@@ -12,26 +12,30 @@ import transport, {
|
|
|
12
12
|
LogBlockCommand,
|
|
13
13
|
type OneKeyDeviceInfoBase,
|
|
14
14
|
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
15
|
+
PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
15
16
|
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
16
17
|
type ProtocolType,
|
|
18
|
+
type ProtocolV2CallContext,
|
|
17
19
|
ProtocolV2FrameAssembler,
|
|
18
|
-
|
|
20
|
+
ProtocolV2LinkManager,
|
|
21
|
+
TRANSPORT_EVENT,
|
|
19
22
|
type TransportCallOptions,
|
|
20
23
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
24
|
+
writeProtocolV2BleFrame,
|
|
21
25
|
} from '@onekeyfe/hd-transport';
|
|
22
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
ERRORS,
|
|
28
|
+
HardwareErrorCode,
|
|
29
|
+
createDeferred,
|
|
30
|
+
isOnekeyBluetoothDevice,
|
|
31
|
+
} from '@onekeyfe/hd-shared';
|
|
23
32
|
|
|
24
33
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
25
|
-
import {
|
|
26
|
-
hasWritableCapability,
|
|
27
|
-
resolveBleWriteMode,
|
|
28
|
-
resolveProtocolV2PacketCapacity,
|
|
29
|
-
} from './bleStrategy';
|
|
34
|
+
import { hasWritableCapability, resolveProtocolV2PacketCapacity } from './bleStrategy';
|
|
30
35
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
31
36
|
import {
|
|
32
37
|
ANDROID_PACKET_LENGTH,
|
|
33
38
|
IOS_PACKET_LENGTH,
|
|
34
|
-
getBleUuidKey,
|
|
35
39
|
getBluetoothServiceUuids,
|
|
36
40
|
getInfosForServiceUuid,
|
|
37
41
|
isSameBleUuid,
|
|
@@ -40,6 +44,7 @@ import { isHeaderChunk } from './utils/validateNotify';
|
|
|
40
44
|
import BleTransport from './BleTransport';
|
|
41
45
|
import timer from './utils/timer';
|
|
42
46
|
import { bleLogger, setBleLogger } from './logger';
|
|
47
|
+
import { createTransportCallLog } from './transportLog';
|
|
43
48
|
|
|
44
49
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
45
50
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
@@ -55,13 +60,12 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
|
55
60
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
|
|
56
61
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
|
|
57
62
|
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
58
|
-
const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
|
|
59
63
|
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
60
64
|
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY =
|
|
61
65
|
Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
62
66
|
const ANDROID_GATT_CONGESTED_STATUS = 143;
|
|
63
67
|
|
|
64
|
-
type FirmwareUploadWriteRetryType = 'congested'
|
|
68
|
+
type FirmwareUploadWriteRetryType = 'congested';
|
|
65
69
|
type ResolvedBleCharacteristics = {
|
|
66
70
|
writeCharacteristic: Characteristic;
|
|
67
71
|
notifyCharacteristic: Characteristic;
|
|
@@ -72,7 +76,9 @@ const delay = (ms: number) =>
|
|
|
72
76
|
setTimeout(resolve, ms);
|
|
73
77
|
});
|
|
74
78
|
|
|
75
|
-
const getFirmwareUploadWriteRetryType = (
|
|
79
|
+
export const getFirmwareUploadWriteRetryType = (
|
|
80
|
+
error: unknown
|
|
81
|
+
): FirmwareUploadWriteRetryType | null => {
|
|
76
82
|
if (!error || typeof error !== 'object') return null;
|
|
77
83
|
const bleWriteError = error as {
|
|
78
84
|
androidErrorCode?: unknown;
|
|
@@ -83,13 +89,6 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
|
|
|
83
89
|
name?: unknown;
|
|
84
90
|
};
|
|
85
91
|
|
|
86
|
-
if (
|
|
87
|
-
bleWriteError.errorCode === BleErrorCode.DeviceDisconnected ||
|
|
88
|
-
bleWriteError.errorCode === BleErrorCode.CharacteristicNotFound
|
|
89
|
-
) {
|
|
90
|
-
return 'reconnectable';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
92
|
if (
|
|
94
93
|
bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
|
|
95
94
|
bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS
|
|
@@ -105,23 +104,14 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
|
|
|
105
104
|
|
|
106
105
|
const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
|
|
107
106
|
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
108
|
-
const BLE_RESPONSE_TIMEOUT_MS = 30_000;
|
|
109
107
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
110
108
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
111
|
-
const DEVICE_SCAN_TIMEOUT_MS =
|
|
109
|
+
const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
112
110
|
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
113
111
|
const ANDROID_NOTIFY_READY_DELAY_MS = 300;
|
|
114
|
-
const HIGH_VOLUME_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 6;
|
|
115
|
-
const HIGH_VOLUME_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 6 : 2;
|
|
116
|
-
const HIGH_VOLUME_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 20 : 8;
|
|
117
|
-
|
|
118
112
|
export type ProtocolV2BleTuning = {
|
|
119
113
|
iosPacketLength?: number;
|
|
120
114
|
androidPacketLength?: number;
|
|
121
|
-
highVolumeWriteBurstSize?: number;
|
|
122
|
-
highVolumeWritePauseMs?: number;
|
|
123
|
-
highVolumeWriteFlushDelayMs?: number;
|
|
124
|
-
highVolumeWriteWithResponse?: boolean;
|
|
125
115
|
};
|
|
126
116
|
|
|
127
117
|
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
@@ -129,10 +119,6 @@ type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
|
129
119
|
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
130
120
|
iosPacketLength: IOS_PACKET_LENGTH,
|
|
131
121
|
androidPacketLength: ANDROID_PACKET_LENGTH,
|
|
132
|
-
highVolumeWriteBurstSize: HIGH_VOLUME_WRITE_BURST_SIZE,
|
|
133
|
-
highVolumeWritePauseMs: HIGH_VOLUME_WRITE_PAUSE_MS,
|
|
134
|
-
highVolumeWriteFlushDelayMs: HIGH_VOLUME_WRITE_FLUSH_DELAY_MS,
|
|
135
|
-
highVolumeWriteWithResponse: false,
|
|
136
122
|
};
|
|
137
123
|
|
|
138
124
|
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
@@ -153,27 +139,13 @@ export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
|
|
|
153
139
|
tuning.androidPacketLength,
|
|
154
140
|
protocolV2BleTuning.androidPacketLength
|
|
155
141
|
),
|
|
156
|
-
highVolumeWriteBurstSize: normalizePositiveInteger(
|
|
157
|
-
tuning.highVolumeWriteBurstSize,
|
|
158
|
-
protocolV2BleTuning.highVolumeWriteBurstSize
|
|
159
|
-
),
|
|
160
|
-
highVolumeWritePauseMs: normalizePositiveInteger(
|
|
161
|
-
tuning.highVolumeWritePauseMs,
|
|
162
|
-
protocolV2BleTuning.highVolumeWritePauseMs
|
|
163
|
-
),
|
|
164
|
-
highVolumeWriteFlushDelayMs: normalizePositiveInteger(
|
|
165
|
-
tuning.highVolumeWriteFlushDelayMs,
|
|
166
|
-
protocolV2BleTuning.highVolumeWriteFlushDelayMs
|
|
167
|
-
),
|
|
168
|
-
highVolumeWriteWithResponse:
|
|
169
|
-
tuning.highVolumeWriteWithResponse ?? protocolV2BleTuning.highVolumeWriteWithResponse,
|
|
170
142
|
};
|
|
171
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
143
|
+
Log?.debug('[ReactNativeBleTransport] BLE tuning configured', protocolV2BleTuning);
|
|
172
144
|
}
|
|
173
145
|
|
|
174
146
|
export function resetProtocolV2BleTuning() {
|
|
175
147
|
protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
176
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
148
|
+
Log?.debug('[ReactNativeBleTransport] BLE tuning reset', protocolV2BleTuning);
|
|
177
149
|
}
|
|
178
150
|
|
|
179
151
|
export function getProtocolV2BleTuning() {
|
|
@@ -188,16 +160,6 @@ function getDeviceDisplayName(device?: Device | null) {
|
|
|
188
160
|
return device?.name || device?.localName || null;
|
|
189
161
|
}
|
|
190
162
|
|
|
191
|
-
function isGenericBleService(uuid?: string | null) {
|
|
192
|
-
return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function hasKnownOneKeyService(device?: Device | null) {
|
|
196
|
-
return (device?.serviceUUIDs ?? []).some(serviceUuid =>
|
|
197
|
-
getInfosForServiceUuid(serviceUuid, 'classic')
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
163
|
const ANDROID_REQUEST_MTU = 256;
|
|
202
164
|
|
|
203
165
|
const connectOptions: Record<string, unknown> = {
|
|
@@ -222,9 +184,10 @@ const requestAndroidMtu = async (device: Device) => {
|
|
|
222
184
|
|
|
223
185
|
try {
|
|
224
186
|
const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
|
|
225
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
187
|
+
Log?.debug('[ReactNativeBleTransport] MTU configured', {
|
|
188
|
+
deviceId: device.id,
|
|
226
189
|
requested: ANDROID_REQUEST_MTU,
|
|
227
|
-
|
|
190
|
+
actual: mtuDevice.mtu,
|
|
228
191
|
});
|
|
229
192
|
return mtuDevice;
|
|
230
193
|
} catch (error) {
|
|
@@ -272,6 +235,8 @@ export default class ReactNativeBleTransport {
|
|
|
272
235
|
|
|
273
236
|
_messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
274
237
|
|
|
238
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
239
|
+
|
|
275
240
|
name = 'ReactNativeBleTransport';
|
|
276
241
|
|
|
277
242
|
configured = false;
|
|
@@ -282,6 +247,8 @@ export default class ReactNativeBleTransport {
|
|
|
282
247
|
|
|
283
248
|
runPromise: Deferred<any> | null = null;
|
|
284
249
|
|
|
250
|
+
private runPromiseDeviceId: string | null = null;
|
|
251
|
+
|
|
285
252
|
emitter?: EventEmitter;
|
|
286
253
|
|
|
287
254
|
firmwareUploadWriteRecoveryIds = new Set<string>();
|
|
@@ -297,12 +264,31 @@ export default class ReactNativeBleTransport {
|
|
|
297
264
|
|
|
298
265
|
private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
299
266
|
|
|
300
|
-
private
|
|
301
|
-
|
|
302
|
-
|
|
267
|
+
private protocolV2Links = new ProtocolV2LinkManager<string>({
|
|
268
|
+
getSchemas: () => {
|
|
269
|
+
if (!this._messages || !this._messagesV2) {
|
|
270
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
protocolV1: this._messages,
|
|
274
|
+
protocolV2: this._messagesV2,
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
classifyError: () => 'link-fatal',
|
|
278
|
+
onLinkInvalidated: async (uuid, reason) => {
|
|
279
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
280
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
281
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
|
|
282
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
283
|
+
await this.releaseNative(uuid, true);
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
});
|
|
303
287
|
|
|
304
288
|
private monitorTokens: Map<string, number> = new Map();
|
|
305
289
|
|
|
290
|
+
private disconnectEventTokens: Map<string, number> = new Map();
|
|
291
|
+
|
|
306
292
|
private nextMonitorToken = 1;
|
|
307
293
|
|
|
308
294
|
constructor(options: TransportOptions) {
|
|
@@ -321,8 +307,19 @@ export default class ReactNativeBleTransport {
|
|
|
321
307
|
}
|
|
322
308
|
|
|
323
309
|
configureProtocolV2(signedData: any) {
|
|
310
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
311
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
324
316
|
this._messagesV2 = parseConfigure(signedData);
|
|
325
|
-
|
|
317
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
318
|
+
if (isReconfiguration) {
|
|
319
|
+
this.protocolV2Links
|
|
320
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
321
|
+
.catch(error => Log?.debug('Protocol V2 schema link cleanup failed:', error));
|
|
322
|
+
}
|
|
326
323
|
}
|
|
327
324
|
|
|
328
325
|
listen() {
|
|
@@ -352,29 +349,15 @@ export default class ReactNativeBleTransport {
|
|
|
352
349
|
}
|
|
353
350
|
}
|
|
354
351
|
|
|
355
|
-
let fallbackServiceUuid: string | undefined;
|
|
356
|
-
|
|
357
352
|
if (!infos) {
|
|
358
353
|
const services = await device.services();
|
|
359
354
|
Log?.debug(
|
|
360
355
|
'[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:',
|
|
361
356
|
services?.map(service => service.uuid)
|
|
362
357
|
);
|
|
363
|
-
|
|
364
|
-
const knownService = services.find(service =>
|
|
365
|
-
getInfosForServiceUuid(service.uuid, 'classic')
|
|
366
|
-
);
|
|
367
|
-
const fallbackService =
|
|
368
|
-
knownService ?? services.find(service => !isGenericBleService(service.uuid)) ?? services[0];
|
|
369
|
-
|
|
370
|
-
if (fallbackService) {
|
|
371
|
-
fallbackServiceUuid = fallbackService.uuid;
|
|
372
|
-
characteristics = await device.characteristicsForService(fallbackService.uuid);
|
|
373
|
-
Log?.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
|
|
374
|
-
}
|
|
375
358
|
}
|
|
376
359
|
|
|
377
|
-
if (!infos
|
|
360
|
+
if (!infos) {
|
|
378
361
|
try {
|
|
379
362
|
Log?.debug('cancel connection when service not found');
|
|
380
363
|
await device.cancelConnection();
|
|
@@ -384,9 +367,7 @@ export default class ReactNativeBleTransport {
|
|
|
384
367
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
385
368
|
}
|
|
386
369
|
|
|
387
|
-
const serviceUuid = infos
|
|
388
|
-
const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
|
|
389
|
-
const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
|
|
370
|
+
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
390
371
|
|
|
391
372
|
if (!serviceUuid) {
|
|
392
373
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
@@ -436,6 +417,7 @@ export default class ReactNativeBleTransport {
|
|
|
436
417
|
|
|
437
418
|
attachDisconnectSubscription(transport: BleTransport, device: Device, uuid: string) {
|
|
438
419
|
transport.disconnectSubscription?.remove();
|
|
420
|
+
const { monitorToken } = transport;
|
|
439
421
|
transport.disconnectSubscription = device.onDisconnected(() => {
|
|
440
422
|
if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
|
|
441
423
|
Log?.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
|
|
@@ -445,18 +427,17 @@ export default class ReactNativeBleTransport {
|
|
|
445
427
|
Log?.debug('device disconnect ignored for stale transport: ', device?.id);
|
|
446
428
|
return;
|
|
447
429
|
}
|
|
430
|
+
if (this.monitorTokens.get(uuid) !== monitorToken) {
|
|
431
|
+
Log?.debug('device disconnect ignored for stale generation: ', device?.id);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
448
434
|
|
|
449
435
|
try {
|
|
450
436
|
Log?.debug('device disconnect: ', device?.id);
|
|
451
|
-
this.
|
|
452
|
-
|
|
453
|
-
id: device?.id,
|
|
454
|
-
connectId: device?.id,
|
|
455
|
-
});
|
|
456
|
-
if (this.runPromise) {
|
|
437
|
+
this.emitDeviceDisconnect(uuid, device?.name, monitorToken);
|
|
438
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
457
439
|
const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
|
|
458
440
|
this.runPromise.reject(error);
|
|
459
|
-
this.rejectAllProtocolV2Frames(error);
|
|
460
441
|
}
|
|
461
442
|
} catch (e) {
|
|
462
443
|
Log?.debug('device disconnect error: ', e);
|
|
@@ -466,6 +447,22 @@ export default class ReactNativeBleTransport {
|
|
|
466
447
|
});
|
|
467
448
|
}
|
|
468
449
|
|
|
450
|
+
private emitDeviceDisconnect(uuid: string, name: string | null | undefined, token?: number) {
|
|
451
|
+
if (token === undefined || this.disconnectEventTokens.get(uuid) === token) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (this.monitorTokens.get(uuid) !== token) {
|
|
455
|
+
Log?.debug('device disconnect event ignored for stale generation: ', uuid);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
this.disconnectEventTokens.set(uuid, token);
|
|
459
|
+
this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
460
|
+
name,
|
|
461
|
+
id: uuid,
|
|
462
|
+
connectId: uuid,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
469
466
|
async reconnectFirmwareUploadTransport(uuid: string, transport: BleTransport) {
|
|
470
467
|
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
471
468
|
try {
|
|
@@ -553,14 +550,13 @@ export default class ReactNativeBleTransport {
|
|
|
553
550
|
}
|
|
554
551
|
|
|
555
552
|
blePlxManager.startDeviceScan(
|
|
556
|
-
|
|
553
|
+
getBluetoothServiceUuids(),
|
|
557
554
|
{
|
|
558
555
|
allowDuplicates: true,
|
|
559
556
|
scanMode: ScanMode.LowLatency,
|
|
560
557
|
},
|
|
561
558
|
(error, device) => {
|
|
562
559
|
if (error) {
|
|
563
|
-
Log?.debug('ble scan manager: ', blePlxManager);
|
|
564
560
|
Log?.debug('ble scan error: ', error);
|
|
565
561
|
if (
|
|
566
562
|
[BleErrorCode.BluetoothPoweredOff, BleErrorCode.BluetoothInUnknownState].includes(
|
|
@@ -584,33 +580,14 @@ export default class ReactNativeBleTransport {
|
|
|
584
580
|
}
|
|
585
581
|
|
|
586
582
|
const displayName = getDeviceDisplayName(device);
|
|
587
|
-
const isOneKey =
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (shouldTraceCandidate) {
|
|
595
|
-
Log?.debug('[ReactNativeBleTransport] scan candidate', {
|
|
596
|
-
name: device?.name,
|
|
597
|
-
localName: device?.localName,
|
|
598
|
-
id: device?.id,
|
|
599
|
-
serviceUUIDs: device?.serviceUUIDs,
|
|
600
|
-
accepted: isOneKey,
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
|
|
583
|
+
const isOneKey = isOnekeyBluetoothDevice({
|
|
584
|
+
id: device?.id,
|
|
585
|
+
name: device?.name,
|
|
586
|
+
localName: device?.localName,
|
|
587
|
+
serviceUuids: device?.serviceUUIDs,
|
|
588
|
+
});
|
|
604
589
|
if (isOneKey) {
|
|
605
|
-
Log?.debug('search device start ======================');
|
|
606
|
-
const { name, localName, id, serviceUUIDs } = device ?? {};
|
|
607
|
-
Log?.debug(
|
|
608
|
-
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${
|
|
609
|
-
id ?? ''
|
|
610
|
-
}\nserviceUUIDs: ${(serviceUUIDs ?? []).join(',')}`
|
|
611
|
-
);
|
|
612
590
|
addDevice(device as unknown as Device);
|
|
613
|
-
Log?.debug('search device end ======================\n');
|
|
614
591
|
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
615
592
|
Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
|
|
616
593
|
name: device?.name,
|
|
@@ -622,12 +599,27 @@ export default class ReactNativeBleTransport {
|
|
|
622
599
|
}
|
|
623
600
|
);
|
|
624
601
|
|
|
625
|
-
getConnectedDeviceIds(getBluetoothServiceUuids()).then(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
602
|
+
getConnectedDeviceIds(Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(
|
|
603
|
+
devices => {
|
|
604
|
+
for (const device of devices) {
|
|
605
|
+
const localName =
|
|
606
|
+
'localName' in device && typeof device.localName === 'string'
|
|
607
|
+
? device.localName
|
|
608
|
+
: null;
|
|
609
|
+
if (
|
|
610
|
+
isOnekeyBluetoothDevice({
|
|
611
|
+
id: device.id,
|
|
612
|
+
name: device.name,
|
|
613
|
+
localName,
|
|
614
|
+
serviceUuids: device.serviceUUIDs,
|
|
615
|
+
})
|
|
616
|
+
) {
|
|
617
|
+
Log?.debug('search connected peripheral: ', device.id);
|
|
618
|
+
addDevice(device as unknown as Device);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
629
621
|
}
|
|
630
|
-
|
|
622
|
+
);
|
|
631
623
|
|
|
632
624
|
const addDevice = (device: Device) => {
|
|
633
625
|
if (deviceList.every(d => d.id !== device.id)) {
|
|
@@ -641,6 +633,12 @@ export default class ReactNativeBleTransport {
|
|
|
641
633
|
name: displayName,
|
|
642
634
|
commType: 'ble',
|
|
643
635
|
} as IOneKeyDevice);
|
|
636
|
+
Log?.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
|
|
637
|
+
deviceId: device.id,
|
|
638
|
+
name: displayName,
|
|
639
|
+
serviceUUIDs: device.serviceUUIDs,
|
|
640
|
+
protocolHint,
|
|
641
|
+
});
|
|
644
642
|
}
|
|
645
643
|
};
|
|
646
644
|
|
|
@@ -651,6 +649,46 @@ export default class ReactNativeBleTransport {
|
|
|
651
649
|
});
|
|
652
650
|
}
|
|
653
651
|
|
|
652
|
+
private async installTransportForAcquire(
|
|
653
|
+
uuid: string,
|
|
654
|
+
device: Device,
|
|
655
|
+
characteristics?: ResolvedBleCharacteristics
|
|
656
|
+
) {
|
|
657
|
+
const { writeCharacteristic, notifyCharacteristic } =
|
|
658
|
+
characteristics ?? (await this.resolveCharacteristics(device));
|
|
659
|
+
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
660
|
+
if (Platform.OS === 'android') {
|
|
661
|
+
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
662
|
+
}
|
|
663
|
+
const monitorToken = this.nextMonitorToken;
|
|
664
|
+
this.nextMonitorToken += 1;
|
|
665
|
+
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
666
|
+
transport.monitorToken = monitorToken;
|
|
667
|
+
transport.notifyTransactionId = notifyTransactionId;
|
|
668
|
+
this.monitorTokens.set(uuid, monitorToken);
|
|
669
|
+
transport.notifySubscription = this._monitorCharacteristic(
|
|
670
|
+
transport.notifyCharacteristic,
|
|
671
|
+
uuid,
|
|
672
|
+
monitorToken,
|
|
673
|
+
notifyTransactionId
|
|
674
|
+
);
|
|
675
|
+
transportCache[uuid] = transport;
|
|
676
|
+
this.protocolV2Assemblers.set(
|
|
677
|
+
uuid,
|
|
678
|
+
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
if (Platform.OS === 'ios') {
|
|
682
|
+
await new Promise<void>(resolve => {
|
|
683
|
+
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
684
|
+
});
|
|
685
|
+
} else if (Platform.OS === 'android') {
|
|
686
|
+
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return transport;
|
|
690
|
+
}
|
|
691
|
+
|
|
654
692
|
async acquire(input: BleAcquireInput) {
|
|
655
693
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
656
694
|
|
|
@@ -684,9 +722,8 @@ export default class ReactNativeBleTransport {
|
|
|
684
722
|
if (forceCleanRunPromise && this.runPromise) {
|
|
685
723
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
686
724
|
this.runPromise.reject(error);
|
|
687
|
-
this.rejectAllProtocolV2Frames(error);
|
|
688
725
|
this.runPromise = null;
|
|
689
|
-
this.
|
|
726
|
+
this.runPromiseDeviceId = null;
|
|
690
727
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
691
728
|
}
|
|
692
729
|
|
|
@@ -775,12 +812,16 @@ export default class ReactNativeBleTransport {
|
|
|
775
812
|
}
|
|
776
813
|
|
|
777
814
|
device = await requestAndroidMtu(device);
|
|
778
|
-
const
|
|
815
|
+
const acquiredDevice = device;
|
|
816
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(
|
|
817
|
+
acquiredDevice
|
|
818
|
+
);
|
|
779
819
|
|
|
780
820
|
const protocolHint = expectedProtocol
|
|
781
821
|
? undefined
|
|
782
|
-
:
|
|
783
|
-
|
|
822
|
+
: input.protocolHint ??
|
|
823
|
+
this.deviceProtocolHints.get(uuid) ??
|
|
824
|
+
inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
|
|
784
825
|
|
|
785
826
|
// release transport before new transport instance
|
|
786
827
|
await this.release(uuid, true);
|
|
@@ -788,45 +829,30 @@ export default class ReactNativeBleTransport {
|
|
|
788
829
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
789
830
|
}
|
|
790
831
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
const monitorToken = this.nextMonitorToken;
|
|
796
|
-
this.nextMonitorToken += 1;
|
|
797
|
-
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
798
|
-
transport.monitorToken = monitorToken;
|
|
799
|
-
transport.notifyTransactionId = notifyTransactionId;
|
|
800
|
-
this.monitorTokens.set(uuid, monitorToken);
|
|
801
|
-
transport.notifySubscription = this._monitorCharacteristic(
|
|
802
|
-
transport.notifyCharacteristic,
|
|
803
|
-
uuid,
|
|
804
|
-
monitorToken,
|
|
805
|
-
notifyTransactionId
|
|
806
|
-
);
|
|
807
|
-
transportCache[uuid] = transport;
|
|
808
|
-
|
|
809
|
-
this.protocolV2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
|
|
810
|
-
|
|
811
|
-
if (Platform.OS === 'ios') {
|
|
812
|
-
await new Promise<void>(resolve => {
|
|
813
|
-
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
814
|
-
});
|
|
815
|
-
} else if (Platform.OS === 'android') {
|
|
816
|
-
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
820
|
-
|
|
821
|
-
this.emitter?.emit('device-connect', {
|
|
822
|
-
name: device.name,
|
|
823
|
-
id: device.id,
|
|
824
|
-
connectId: device.id,
|
|
832
|
+
await this.installTransportForAcquire(uuid, acquiredDevice, {
|
|
833
|
+
writeCharacteristic,
|
|
834
|
+
notifyCharacteristic,
|
|
825
835
|
});
|
|
826
836
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
837
|
+
try {
|
|
838
|
+
const protocolType = await this.detectProtocol(
|
|
839
|
+
uuid,
|
|
840
|
+
expectedProtocol,
|
|
841
|
+
protocolHint,
|
|
842
|
+
async () => {
|
|
843
|
+
await this.installTransportForAcquire(uuid, acquiredDevice);
|
|
844
|
+
}
|
|
845
|
+
);
|
|
846
|
+
const currentTransport = transportCache[uuid];
|
|
847
|
+
if (!currentTransport) {
|
|
848
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
849
|
+
}
|
|
850
|
+
this.attachDisconnectSubscription(currentTransport, acquiredDevice, uuid);
|
|
851
|
+
return { uuid, protocolType };
|
|
852
|
+
} catch (error) {
|
|
853
|
+
await this.release(uuid, true);
|
|
854
|
+
throw error;
|
|
855
|
+
}
|
|
830
856
|
}
|
|
831
857
|
|
|
832
858
|
_monitorCharacteristic(
|
|
@@ -853,7 +879,30 @@ export default class ReactNativeBleTransport {
|
|
|
853
879
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
854
880
|
return;
|
|
855
881
|
}
|
|
856
|
-
if (this.
|
|
882
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
883
|
+
let errorCode:
|
|
884
|
+
| typeof HardwareErrorCode.BleDeviceBondError
|
|
885
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
886
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
887
|
+
| typeof HardwareErrorCode.BleTimeoutError =
|
|
888
|
+
HardwareErrorCode.BleCharacteristicNotifyError;
|
|
889
|
+
if (error.reason?.includes('The connection has timed out unexpectedly')) {
|
|
890
|
+
errorCode = HardwareErrorCode.BleTimeoutError;
|
|
891
|
+
} else if (error.reason?.includes('Encryption is insufficient')) {
|
|
892
|
+
errorCode = HardwareErrorCode.BleDeviceBondError;
|
|
893
|
+
} else if (
|
|
894
|
+
error.reason?.includes('Cannot write client characteristic config descriptor') ||
|
|
895
|
+
error.reason?.includes('Cannot find client characteristic config descriptor') ||
|
|
896
|
+
error.reason?.includes('The handle is invalid') ||
|
|
897
|
+
error.reason?.includes('Writing is not permitted') ||
|
|
898
|
+
error.reason?.includes('notify change failed for device')
|
|
899
|
+
) {
|
|
900
|
+
errorCode = HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
|
|
901
|
+
}
|
|
902
|
+
this.rejectProtocolV2Frames(uuid, ERRORS.TypedError(errorCode));
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
857
906
|
let ERROR:
|
|
858
907
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
859
908
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
@@ -876,7 +925,6 @@ export default class ReactNativeBleTransport {
|
|
|
876
925
|
HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
877
926
|
);
|
|
878
927
|
this.runPromise.reject(notifyError);
|
|
879
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
880
928
|
Log?.debug(
|
|
881
929
|
`${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
|
|
882
930
|
);
|
|
@@ -884,7 +932,6 @@ export default class ReactNativeBleTransport {
|
|
|
884
932
|
}
|
|
885
933
|
const notifyError = ERRORS.TypedError(ERROR);
|
|
886
934
|
this.runPromise.reject(notifyError);
|
|
887
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
888
935
|
Log?.debug(': monitor notify error, and has unreleased Promise', Error);
|
|
889
936
|
}
|
|
890
937
|
|
|
@@ -908,7 +955,7 @@ export default class ReactNativeBleTransport {
|
|
|
908
955
|
return;
|
|
909
956
|
}
|
|
910
957
|
if (protocol === 'V2') {
|
|
911
|
-
this.handleProtocolV2Notification(uuid, new Uint8Array(data));
|
|
958
|
+
this.handleProtocolV2Notification(uuid, monitorToken, new Uint8Array(data));
|
|
912
959
|
return;
|
|
913
960
|
}
|
|
914
961
|
// console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
|
|
@@ -929,13 +976,18 @@ export default class ReactNativeBleTransport {
|
|
|
929
976
|
// );
|
|
930
977
|
bufferLength = 0;
|
|
931
978
|
buffer = [];
|
|
932
|
-
this.
|
|
979
|
+
if (this.runPromiseDeviceId === uuid) {
|
|
980
|
+
this.runPromise?.resolve(value.toString('hex'));
|
|
981
|
+
}
|
|
933
982
|
}
|
|
934
983
|
} catch (error) {
|
|
935
984
|
Log?.debug('monitor data error: ', error);
|
|
936
985
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
937
|
-
this.
|
|
938
|
-
|
|
986
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
987
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
988
|
+
} else if (this.runPromiseDeviceId === uuid) {
|
|
989
|
+
this.runPromise?.reject(notifyError);
|
|
990
|
+
}
|
|
939
991
|
}
|
|
940
992
|
}, notifyTransactionId);
|
|
941
993
|
|
|
@@ -943,13 +995,18 @@ export default class ReactNativeBleTransport {
|
|
|
943
995
|
}
|
|
944
996
|
|
|
945
997
|
async release(uuid: string, onclose = false) {
|
|
998
|
+
await this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
|
|
999
|
+
return this.releaseNative(uuid, onclose);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
private async releaseNative(uuid: string, onclose = false) {
|
|
946
1003
|
const transport = transportCache[uuid];
|
|
947
|
-
if (this.runPromise) {
|
|
1004
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
948
1005
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
949
1006
|
this.runPromise.reject(error);
|
|
950
1007
|
this.runPromise = null;
|
|
951
|
-
this.
|
|
952
|
-
this.
|
|
1008
|
+
this.runPromiseDeviceId = null;
|
|
1009
|
+
this.rejectProtocolV2Frames(uuid, error);
|
|
953
1010
|
} else {
|
|
954
1011
|
this.resetProtocolV2Frames(uuid);
|
|
955
1012
|
}
|
|
@@ -957,9 +1014,6 @@ export default class ReactNativeBleTransport {
|
|
|
957
1014
|
if (Platform.OS === 'android' && !onclose && transport) {
|
|
958
1015
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
959
1016
|
this.resetProtocolV2Frames(uuid);
|
|
960
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
961
|
-
this.activeProtocolV2Call = null;
|
|
962
|
-
}
|
|
963
1017
|
return Promise.resolve(true);
|
|
964
1018
|
}
|
|
965
1019
|
|
|
@@ -993,7 +1047,7 @@ export default class ReactNativeBleTransport {
|
|
|
993
1047
|
}
|
|
994
1048
|
|
|
995
1049
|
this.deviceProtocol.delete(uuid);
|
|
996
|
-
|
|
1050
|
+
// Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
|
|
997
1051
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
998
1052
|
this.protocolV2Assemblers.delete(uuid);
|
|
999
1053
|
this.resetProtocolV2Frames(uuid);
|
|
@@ -1025,13 +1079,6 @@ export default class ReactNativeBleTransport {
|
|
|
1025
1079
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1026
1080
|
}
|
|
1027
1081
|
|
|
1028
|
-
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
1029
|
-
|
|
1030
|
-
Log?.debug('transport-react-native call this.runPromise', this.runPromise);
|
|
1031
|
-
if (this.runPromise && !forceRun) {
|
|
1032
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
1082
|
const protocol = this.getProtocolType(uuid);
|
|
1036
1083
|
if (!protocol) {
|
|
1037
1084
|
throw ERRORS.TypedError(
|
|
@@ -1039,31 +1086,17 @@ export default class ReactNativeBleTransport {
|
|
|
1039
1086
|
`Device protocol has not been detected for ${uuid}`
|
|
1040
1087
|
);
|
|
1041
1088
|
}
|
|
1042
|
-
|
|
1043
|
-
if (name === 'ResourceUpdate' || name === 'ResourceAck') {
|
|
1044
|
-
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
|
|
1045
|
-
file_name: data?.file_name,
|
|
1046
|
-
hash: data?.hash,
|
|
1047
|
-
});
|
|
1048
|
-
} else if (LogBlockCommand.has(name)) {
|
|
1049
|
-
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' protocol: ', protocol);
|
|
1050
|
-
} else {
|
|
1051
|
-
Log?.debug(
|
|
1052
|
-
'transport-react-native',
|
|
1053
|
-
'call-',
|
|
1054
|
-
' name: ',
|
|
1055
|
-
name,
|
|
1056
|
-
' data: ',
|
|
1057
|
-
data,
|
|
1058
|
-
' protocol: ',
|
|
1059
|
-
protocol
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1089
|
+
Log?.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
1062
1090
|
|
|
1063
1091
|
if (protocol === 'V2') {
|
|
1064
1092
|
return this.callProtocolV2(uuid, name, data, options);
|
|
1065
1093
|
}
|
|
1066
1094
|
|
|
1095
|
+
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
1096
|
+
if (this.runPromise && !forceRun) {
|
|
1097
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1067
1100
|
return this.callProtocolV1(uuid, name, data, options);
|
|
1068
1101
|
}
|
|
1069
1102
|
|
|
@@ -1081,6 +1114,7 @@ export default class ReactNativeBleTransport {
|
|
|
1081
1114
|
const runPromise = createDeferred<string>();
|
|
1082
1115
|
runPromise.promise.catch(() => undefined);
|
|
1083
1116
|
this.runPromise = runPromise;
|
|
1117
|
+
this.runPromiseDeviceId = uuid;
|
|
1084
1118
|
const messages = this._messages;
|
|
1085
1119
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1086
1120
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -1157,14 +1191,13 @@ export default class ReactNativeBleTransport {
|
|
|
1157
1191
|
}
|
|
1158
1192
|
);
|
|
1159
1193
|
} else if (name === 'FirmwareUpload') {
|
|
1160
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
1194
|
+
Log?.debug('[ReactNativeBleTransport] Firmware upload transport configured', {
|
|
1161
1195
|
packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
|
|
1162
1196
|
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1163
1197
|
pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1164
1198
|
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1165
1199
|
maxRetries: FIRMWARE_UPLOAD_WRITE_MAX_RETRIES,
|
|
1166
1200
|
});
|
|
1167
|
-
|
|
1168
1201
|
await writeFirmwareUploadChunkedData(
|
|
1169
1202
|
buffers,
|
|
1170
1203
|
async data => {
|
|
@@ -1181,31 +1214,14 @@ export default class ReactNativeBleTransport {
|
|
|
1181
1214
|
if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
1182
1215
|
throw error;
|
|
1183
1216
|
}
|
|
1184
|
-
const
|
|
1185
|
-
const delayMs = shouldReconnect
|
|
1186
|
-
? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
|
|
1187
|
-
: resolveFirmwareUploadRetryDelay(attempt);
|
|
1217
|
+
const delayMs = resolveFirmwareUploadRetryDelay(attempt);
|
|
1188
1218
|
Log?.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
|
|
1189
1219
|
attempt: attempt + 1,
|
|
1190
1220
|
delayMs,
|
|
1191
|
-
reconnect: shouldReconnect,
|
|
1192
1221
|
error,
|
|
1193
1222
|
});
|
|
1194
|
-
if (shouldReconnect) {
|
|
1195
|
-
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
1196
|
-
}
|
|
1197
1223
|
await delay(delayMs);
|
|
1198
1224
|
attempt += 1;
|
|
1199
|
-
if (shouldReconnect) {
|
|
1200
|
-
try {
|
|
1201
|
-
await this.reconnectFirmwareUploadTransport(uuid, transport);
|
|
1202
|
-
} catch (e) {
|
|
1203
|
-
Log?.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
|
|
1204
|
-
if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
1205
|
-
throw e;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
1225
|
}
|
|
1210
1226
|
}
|
|
1211
1227
|
},
|
|
@@ -1218,7 +1234,6 @@ export default class ReactNativeBleTransport {
|
|
|
1218
1234
|
for (const o of buffers) {
|
|
1219
1235
|
const outData = o.toString('base64');
|
|
1220
1236
|
// Upload resources on low-end phones may OOM
|
|
1221
|
-
// this.Log.debug('send hex strting: ', o.toString('hex'));
|
|
1222
1237
|
try {
|
|
1223
1238
|
await transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1224
1239
|
} catch (e) {
|
|
@@ -1256,20 +1271,28 @@ export default class ReactNativeBleTransport {
|
|
|
1256
1271
|
throw new Error('Returning data is not string.');
|
|
1257
1272
|
}
|
|
1258
1273
|
|
|
1259
|
-
Log?.debug('receive data: ', response);
|
|
1260
1274
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
1261
1275
|
return check.call(jsonData);
|
|
1262
1276
|
} catch (e) {
|
|
1263
|
-
if (name === '
|
|
1264
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1
|
|
1277
|
+
if (name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
1278
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
|
|
1265
1279
|
} else {
|
|
1266
1280
|
Log?.error('call error: ', e);
|
|
1267
1281
|
}
|
|
1282
|
+
const isProbeTimeout =
|
|
1283
|
+
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1284
|
+
if (
|
|
1285
|
+
!isProbeTimeout &&
|
|
1286
|
+
(e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError
|
|
1287
|
+
) {
|
|
1288
|
+
await this.disconnect(uuid);
|
|
1289
|
+
}
|
|
1268
1290
|
throw e;
|
|
1269
1291
|
} finally {
|
|
1270
1292
|
if (timeout) clearTimeout(timeout);
|
|
1271
1293
|
if (this.runPromise === runPromise) {
|
|
1272
1294
|
this.runPromise = null;
|
|
1295
|
+
this.runPromiseDeviceId = null;
|
|
1273
1296
|
}
|
|
1274
1297
|
}
|
|
1275
1298
|
}
|
|
@@ -1279,8 +1302,9 @@ export default class ReactNativeBleTransport {
|
|
|
1279
1302
|
}
|
|
1280
1303
|
|
|
1281
1304
|
async disconnect(session: string) {
|
|
1282
|
-
|
|
1305
|
+
await this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
|
|
1283
1306
|
const transport = transportCache[session];
|
|
1307
|
+
const monitorToken = transport?.monitorToken ?? this.monitorTokens.get(session);
|
|
1284
1308
|
|
|
1285
1309
|
// Clean up disconnect subscription first to prevent onDisconnected callback
|
|
1286
1310
|
// from being triggered when we cancel the connection below
|
|
@@ -1341,20 +1365,16 @@ export default class ReactNativeBleTransport {
|
|
|
1341
1365
|
this.deviceProtocolHints.delete(session);
|
|
1342
1366
|
this.protocolV2Assemblers.delete(session);
|
|
1343
1367
|
this.resetProtocolV2Frames(session);
|
|
1344
|
-
if (this.activeProtocolV2Call?.uuid === session) {
|
|
1345
|
-
this.activeProtocolV2Call = null;
|
|
1346
|
-
}
|
|
1347
1368
|
|
|
1348
1369
|
// emit the disconnect event
|
|
1349
1370
|
try {
|
|
1350
|
-
this.
|
|
1351
|
-
name: transport?.device?.name,
|
|
1352
|
-
id: session,
|
|
1353
|
-
connectId: session,
|
|
1354
|
-
});
|
|
1371
|
+
this.emitDeviceDisconnect(session, transport?.device?.name, monitorToken);
|
|
1355
1372
|
} catch (e) {
|
|
1356
1373
|
Log?.error('resetSession: emit disconnect event error: ', e);
|
|
1357
1374
|
}
|
|
1375
|
+
if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
|
|
1376
|
+
this.monitorTokens.delete(session);
|
|
1377
|
+
}
|
|
1358
1378
|
// eslint-disable-next-line no-promise-executor-return
|
|
1359
1379
|
await new Promise<void>(resolve => setTimeout(() => resolve(), 100));
|
|
1360
1380
|
}
|
|
@@ -1365,6 +1385,7 @@ export default class ReactNativeBleTransport {
|
|
|
1365
1385
|
// this.runPromise.reject(new Error('Transport_CallCanceled'));
|
|
1366
1386
|
}
|
|
1367
1387
|
this.runPromise = null;
|
|
1388
|
+
this.runPromiseDeviceId = null;
|
|
1368
1389
|
}
|
|
1369
1390
|
|
|
1370
1391
|
private getCachedTransport(uuid: string) {
|
|
@@ -1385,7 +1406,7 @@ export default class ReactNativeBleTransport {
|
|
|
1385
1406
|
private createProtocolDetectionError() {
|
|
1386
1407
|
return ERRORS.TypedError(
|
|
1387
1408
|
HardwareErrorCode.BleTimeoutError,
|
|
1388
|
-
'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
1409
|
+
'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping'
|
|
1389
1410
|
);
|
|
1390
1411
|
}
|
|
1391
1412
|
|
|
@@ -1398,42 +1419,61 @@ export default class ReactNativeBleTransport {
|
|
|
1398
1419
|
private async detectProtocol(
|
|
1399
1420
|
uuid: string,
|
|
1400
1421
|
expectedProtocol?: ProtocolType,
|
|
1401
|
-
protocolHint?: ProtocolType
|
|
1422
|
+
protocolHint?: ProtocolType,
|
|
1423
|
+
rebuildTransport?: () => Promise<void>
|
|
1402
1424
|
): Promise<ProtocolType> {
|
|
1403
1425
|
if (expectedProtocol === 'V1') {
|
|
1404
1426
|
if (await this.probeProtocolV1(uuid)) {
|
|
1405
1427
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1406
|
-
Log?.debug(
|
|
1428
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1429
|
+
deviceId: uuid,
|
|
1430
|
+
protocol: 'V1',
|
|
1431
|
+
source: 'expected',
|
|
1432
|
+
});
|
|
1407
1433
|
return 'V1';
|
|
1408
1434
|
}
|
|
1409
1435
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1410
1436
|
}
|
|
1411
1437
|
|
|
1412
1438
|
if (expectedProtocol === 'V2') {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1439
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
1440
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1441
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1442
|
+
deviceId: uuid,
|
|
1443
|
+
protocol: 'V2',
|
|
1444
|
+
source: 'expected',
|
|
1445
|
+
});
|
|
1446
|
+
return 'V2';
|
|
1447
|
+
}
|
|
1448
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1418
1449
|
}
|
|
1419
1450
|
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1422
|
-
// 不能作为最终结论。
|
|
1451
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
1452
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
1423
1453
|
const probeOrder: ProtocolType[] =
|
|
1424
1454
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1425
1455
|
|
|
1426
1456
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1427
1457
|
const protocol = probeOrder[i];
|
|
1428
1458
|
if (i > 0) {
|
|
1429
|
-
//
|
|
1459
|
+
// Reset subscriptions and buffers after a failed probe before trying another protocol.
|
|
1430
1460
|
await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
1461
|
+
if (!transportCache[uuid]) {
|
|
1462
|
+
if (!rebuildTransport) {
|
|
1463
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
1464
|
+
}
|
|
1465
|
+
await rebuildTransport();
|
|
1466
|
+
}
|
|
1431
1467
|
}
|
|
1432
1468
|
const detected =
|
|
1433
1469
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
1434
1470
|
if (detected) {
|
|
1435
1471
|
this.deviceProtocol.set(uuid, protocol);
|
|
1436
|
-
Log?.debug(
|
|
1472
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1473
|
+
deviceId: uuid,
|
|
1474
|
+
protocol,
|
|
1475
|
+
source: 'probe',
|
|
1476
|
+
});
|
|
1437
1477
|
return protocol;
|
|
1438
1478
|
}
|
|
1439
1479
|
}
|
|
@@ -1444,11 +1484,12 @@ export default class ReactNativeBleTransport {
|
|
|
1444
1484
|
|
|
1445
1485
|
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
1446
1486
|
const transport = transportCache[uuid];
|
|
1487
|
+
await this.protocolV2Links.invalidateLink(
|
|
1488
|
+
uuid,
|
|
1489
|
+
`Reset notify state after Protocol ${protocol} probe`
|
|
1490
|
+
);
|
|
1447
1491
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1448
1492
|
this.resetProtocolV2Frames(uuid);
|
|
1449
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
1450
|
-
this.activeProtocolV2Call = null;
|
|
1451
|
-
}
|
|
1452
1493
|
if (this.runPromise) {
|
|
1453
1494
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1454
1495
|
this.runPromise.reject(error);
|
|
@@ -1500,11 +1541,13 @@ export default class ReactNativeBleTransport {
|
|
|
1500
1541
|
|
|
1501
1542
|
try {
|
|
1502
1543
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1503
|
-
|
|
1544
|
+
// GetFeatures identifies Protocol V1 without resetting an existing wallet
|
|
1545
|
+
// session before Core has a chance to restore a hidden wallet.
|
|
1546
|
+
await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1504
1547
|
return true;
|
|
1505
1548
|
} catch (error) {
|
|
1506
1549
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1507
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1
|
|
1550
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
1508
1551
|
return false;
|
|
1509
1552
|
}
|
|
1510
1553
|
}
|
|
@@ -1533,13 +1576,9 @@ export default class ReactNativeBleTransport {
|
|
|
1533
1576
|
return detected;
|
|
1534
1577
|
}
|
|
1535
1578
|
|
|
1536
|
-
private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
|
|
1579
|
+
private handleProtocolV2Notification(uuid: string, monitorToken: number, data: Uint8Array) {
|
|
1537
1580
|
try {
|
|
1538
|
-
if (
|
|
1539
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1540
|
-
this.resetProtocolV2Frames(uuid);
|
|
1541
|
-
return;
|
|
1542
|
-
}
|
|
1581
|
+
if (this.monitorTokens.get(uuid) !== monitorToken) return;
|
|
1543
1582
|
|
|
1544
1583
|
if (data.length === 0) return;
|
|
1545
1584
|
|
|
@@ -1552,8 +1591,15 @@ export default class ReactNativeBleTransport {
|
|
|
1552
1591
|
} catch (error) {
|
|
1553
1592
|
Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
|
|
1554
1593
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1555
|
-
this.
|
|
1556
|
-
this.
|
|
1594
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1595
|
+
this.protocolV2Links
|
|
1596
|
+
.invalidateLink(uuid, `Protocol V2 notification error: ${error}`)
|
|
1597
|
+
.catch(invalidateError =>
|
|
1598
|
+
Log?.debug(
|
|
1599
|
+
'[ReactNativeBleTransport] Protocol V2 notify cleanup failed:',
|
|
1600
|
+
invalidateError
|
|
1601
|
+
)
|
|
1602
|
+
);
|
|
1557
1603
|
}
|
|
1558
1604
|
}
|
|
1559
1605
|
|
|
@@ -1576,21 +1622,17 @@ export default class ReactNativeBleTransport {
|
|
|
1576
1622
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1577
1623
|
}
|
|
1578
1624
|
|
|
1579
|
-
private rejectAllProtocolV2Frames(error: Error) {
|
|
1580
|
-
this.protocolV2FrameQueues.clear();
|
|
1581
|
-
for (const framePromise of this.protocolV2FramePromises.values()) {
|
|
1582
|
-
framePromise.reject(error);
|
|
1583
|
-
}
|
|
1584
|
-
this.protocolV2FramePromises.clear();
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
1625
|
private resetProtocolV2Frames(uuid: string) {
|
|
1588
|
-
this.
|
|
1589
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1626
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1590
1627
|
}
|
|
1591
1628
|
|
|
1592
|
-
private
|
|
1593
|
-
|
|
1629
|
+
private rejectProtocolV2Frames(uuid: string, error: Error) {
|
|
1630
|
+
this.protocolV2FrameQueues.delete(uuid);
|
|
1631
|
+
const framePromise = this.protocolV2FramePromises.get(uuid);
|
|
1632
|
+
if (framePromise) {
|
|
1633
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1634
|
+
framePromise.reject(error);
|
|
1635
|
+
}
|
|
1594
1636
|
}
|
|
1595
1637
|
|
|
1596
1638
|
private async readProtocolV2Frame(uuid: string) {
|
|
@@ -1610,10 +1652,46 @@ export default class ReactNativeBleTransport {
|
|
|
1610
1652
|
}
|
|
1611
1653
|
}
|
|
1612
1654
|
|
|
1655
|
+
private async writeProtocolV2Packet(
|
|
1656
|
+
transport: BleTransport,
|
|
1657
|
+
base64: string,
|
|
1658
|
+
context: ProtocolV2CallContext,
|
|
1659
|
+
assertCurrentGeneration: () => void
|
|
1660
|
+
) {
|
|
1661
|
+
let attempt = 0;
|
|
1662
|
+
for (;;) {
|
|
1663
|
+
assertCurrentGeneration();
|
|
1664
|
+
if (context.signal.aborted) {
|
|
1665
|
+
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
1666
|
+
}
|
|
1667
|
+
try {
|
|
1668
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1669
|
+
assertCurrentGeneration();
|
|
1670
|
+
return;
|
|
1671
|
+
} catch (error) {
|
|
1672
|
+
if (
|
|
1673
|
+
getFirmwareUploadWriteRetryType(error) !== 'congested' ||
|
|
1674
|
+
attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES
|
|
1675
|
+
) {
|
|
1676
|
+
throw error;
|
|
1677
|
+
}
|
|
1678
|
+
const delayMs = resolveFirmwareUploadRetryDelay(attempt);
|
|
1679
|
+
attempt += 1;
|
|
1680
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 congested write retry:', {
|
|
1681
|
+
name: context.messageName,
|
|
1682
|
+
attempt,
|
|
1683
|
+
delayMs,
|
|
1684
|
+
});
|
|
1685
|
+
await delay(delayMs);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1613
1690
|
private async writeProtocolV2Frame(
|
|
1614
1691
|
transport: BleTransport,
|
|
1615
1692
|
frame: Uint8Array,
|
|
1616
|
-
|
|
1693
|
+
context: ProtocolV2CallContext,
|
|
1694
|
+
assertCurrentGeneration: () => void
|
|
1617
1695
|
) {
|
|
1618
1696
|
const tuning = getProtocolV2BleTuning();
|
|
1619
1697
|
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
@@ -1622,37 +1700,24 @@ export default class ReactNativeBleTransport {
|
|
|
1622
1700
|
androidPacketLength: tuning.androidPacketLength,
|
|
1623
1701
|
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1624
1702
|
});
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
if (
|
|
1645
|
-
shouldThrottle &&
|
|
1646
|
-
packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
|
|
1647
|
-
offset + packetCapacity < frame.length
|
|
1648
|
-
) {
|
|
1649
|
-
await delay(tuning.highVolumeWritePauseMs);
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
if (shouldThrottle) {
|
|
1654
|
-
await delay(tuning.highVolumeWriteFlushDelayMs);
|
|
1655
|
-
}
|
|
1703
|
+
await writeProtocolV2BleFrame({
|
|
1704
|
+
frame,
|
|
1705
|
+
packetCapacity,
|
|
1706
|
+
assertActive: assertCurrentGeneration,
|
|
1707
|
+
signal: context.signal,
|
|
1708
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1709
|
+
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1710
|
+
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1711
|
+
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1712
|
+
wait: delay,
|
|
1713
|
+
writePacket: packet =>
|
|
1714
|
+
this.writeProtocolV2Packet(
|
|
1715
|
+
transport,
|
|
1716
|
+
Buffer.from(packet).toString('base64'),
|
|
1717
|
+
context,
|
|
1718
|
+
assertCurrentGeneration
|
|
1719
|
+
),
|
|
1720
|
+
});
|
|
1656
1721
|
}
|
|
1657
1722
|
|
|
1658
1723
|
private async callProtocolV2(
|
|
@@ -1665,101 +1730,76 @@ export default class ReactNativeBleTransport {
|
|
|
1665
1730
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1666
1731
|
}
|
|
1667
1732
|
|
|
1668
|
-
const
|
|
1669
|
-
if (this.runPromise) {
|
|
1670
|
-
if (!forceRun) {
|
|
1671
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1672
|
-
}
|
|
1673
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1674
|
-
this.runPromise.reject(error);
|
|
1675
|
-
this.rejectAllProtocolV2Frames(error);
|
|
1676
|
-
this.runPromise = null;
|
|
1677
|
-
this.activeProtocolV2Call = null;
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
const transport = this.getCachedTransport(uuid);
|
|
1681
|
-
const runPromise = createDeferred<Uint8Array>();
|
|
1682
|
-
runPromise.promise.catch(() => undefined);
|
|
1683
|
-
this.runPromise = runPromise;
|
|
1684
|
-
const callToken = this.nextProtocolV2CallToken++;
|
|
1685
|
-
this.activeProtocolV2Call = { uuid, token: callToken };
|
|
1686
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1687
|
-
this.resetProtocolV2Frames(uuid);
|
|
1688
|
-
let completed = false;
|
|
1689
|
-
const callOptions = {
|
|
1690
|
-
...options,
|
|
1691
|
-
timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
|
|
1692
|
-
};
|
|
1733
|
+
const callOptions = options;
|
|
1693
1734
|
const highVolumeWrite = LogBlockCommand.has(name);
|
|
1694
1735
|
|
|
1695
1736
|
if (highVolumeWrite) {
|
|
1696
1737
|
const tuning = getProtocolV2BleTuning();
|
|
1697
|
-
Log?.debug(
|
|
1698
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
|
|
1738
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
1699
1739
|
name,
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
burstSize: tuning.highVolumeWriteBurstSize,
|
|
1704
|
-
pauseMs: tuning.highVolumeWritePauseMs,
|
|
1705
|
-
flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
|
|
1706
|
-
writeWithResponse: tuning.highVolumeWriteWithResponse,
|
|
1707
|
-
}
|
|
1708
|
-
);
|
|
1740
|
+
writeMode: 'withoutResponse',
|
|
1741
|
+
packetCapacity: Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1742
|
+
});
|
|
1709
1743
|
}
|
|
1710
1744
|
|
|
1711
1745
|
try {
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
await this.writeProtocolV2Frame(transport, frame, {
|
|
1720
|
-
highVolume: highVolumeWrite,
|
|
1721
|
-
});
|
|
1722
|
-
},
|
|
1723
|
-
readFrame: async () => {
|
|
1724
|
-
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1725
|
-
if (!(rxFrame instanceof Uint8Array)) {
|
|
1726
|
-
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1727
|
-
}
|
|
1728
|
-
return rxFrame;
|
|
1729
|
-
},
|
|
1730
|
-
logger: Log,
|
|
1731
|
-
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1732
|
-
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
1733
|
-
ERRORS.TypedError(
|
|
1734
|
-
HardwareErrorCode.BleTimeoutError,
|
|
1735
|
-
`BLE response timeout after ${timeout}ms for ${name}`
|
|
1736
|
-
),
|
|
1737
|
-
});
|
|
1738
|
-
|
|
1739
|
-
const result = await session.call(name, data, callOptions);
|
|
1740
|
-
completed = true;
|
|
1741
|
-
return result;
|
|
1746
|
+
return await this.protocolV2Links.call(
|
|
1747
|
+
uuid,
|
|
1748
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
1749
|
+
name,
|
|
1750
|
+
data,
|
|
1751
|
+
callOptions
|
|
1752
|
+
);
|
|
1742
1753
|
} catch (e) {
|
|
1743
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1744
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1745
|
-
this.resetProtocolV2Frames(uuid);
|
|
1746
|
-
}
|
|
1747
1754
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1748
1755
|
throw e;
|
|
1749
|
-
} finally {
|
|
1750
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1751
|
-
if (!completed) {
|
|
1752
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1753
|
-
}
|
|
1754
|
-
this.resetProtocolV2Frames(uuid);
|
|
1755
|
-
this.activeProtocolV2Call = null;
|
|
1756
|
-
}
|
|
1757
|
-
if (this.runPromise === runPromise) {
|
|
1758
|
-
this.runPromise = null;
|
|
1759
|
-
}
|
|
1760
1756
|
}
|
|
1761
1757
|
}
|
|
1762
1758
|
|
|
1759
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
1760
|
+
const generation = this.monitorTokens.get(uuid) ?? 0;
|
|
1761
|
+
const assertCurrentGeneration = () => {
|
|
1762
|
+
if (this.monitorTokens.get(uuid) !== generation) {
|
|
1763
|
+
throw new Error(`Protocol V2 monitor generation changed for ${uuid}`);
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
return {
|
|
1768
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1769
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1770
|
+
generation,
|
|
1771
|
+
prepareCall: () => {
|
|
1772
|
+
assertCurrentGeneration();
|
|
1773
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1774
|
+
this.resetProtocolV2Frames(uuid);
|
|
1775
|
+
},
|
|
1776
|
+
writeFrame: async (frame: Uint8Array, context: ProtocolV2CallContext) => {
|
|
1777
|
+
assertCurrentGeneration();
|
|
1778
|
+
const currentTransport = this.getCachedTransport(uuid);
|
|
1779
|
+
await this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
|
|
1780
|
+
},
|
|
1781
|
+
readFrame: async () => {
|
|
1782
|
+
assertCurrentGeneration();
|
|
1783
|
+
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1784
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1785
|
+
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1786
|
+
}
|
|
1787
|
+
return rxFrame;
|
|
1788
|
+
},
|
|
1789
|
+
reset: (reason: string) => {
|
|
1790
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1791
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
1792
|
+
},
|
|
1793
|
+
logger: Log,
|
|
1794
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1795
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
1796
|
+
ERRORS.TypedError(
|
|
1797
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1798
|
+
`BLE response timeout after ${timeout}ms for ${messageName}`
|
|
1799
|
+
),
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1763
1803
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
1764
1804
|
return this.deviceProtocol.get(path);
|
|
1765
1805
|
}
|