@onekeyfe/hd-transport-react-native 1.2.0-alpha.3 → 1.2.0-alpha.31
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/bleStrategy.d.ts +0 -2
- package/dist/bleStrategy.d.ts.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +19 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +282 -254
- package/dist/subscribeBleOn.d.ts.map +1 -1
- package/dist/transportLog.d.ts +13 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/jest.config.js +5 -0
- package/package.json +7 -6
- package/src/__tests__/bleStrategy.test.ts +1 -6
- package/src/__tests__/protocolV2Link.test.ts +342 -0
- package/src/bleStrategy.ts +0 -25
- package/src/constants.ts +9 -1
- package/src/index.ts +294 -283
- package/src/subscribeBleOn.ts +0 -2
- package/src/transportLog.ts +18 -0
package/src/index.ts
CHANGED
|
@@ -12,21 +12,21 @@ 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
26
|
import { ERRORS, HardwareErrorCode, createDeferred, isOnekeyDevice } from '@onekeyfe/hd-shared';
|
|
23
27
|
|
|
24
28
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
25
|
-
import {
|
|
26
|
-
hasWritableCapability,
|
|
27
|
-
resolveBleWriteMode,
|
|
28
|
-
resolveProtocolV2PacketCapacity,
|
|
29
|
-
} from './bleStrategy';
|
|
29
|
+
import { hasWritableCapability, resolveProtocolV2PacketCapacity } from './bleStrategy';
|
|
30
30
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
31
31
|
import {
|
|
32
32
|
ANDROID_PACKET_LENGTH,
|
|
@@ -40,6 +40,7 @@ import { isHeaderChunk } from './utils/validateNotify';
|
|
|
40
40
|
import BleTransport from './BleTransport';
|
|
41
41
|
import timer from './utils/timer';
|
|
42
42
|
import { bleLogger, setBleLogger } from './logger';
|
|
43
|
+
import { createTransportCallLog } from './transportLog';
|
|
43
44
|
|
|
44
45
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
45
46
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
@@ -105,23 +106,14 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
|
|
|
105
106
|
|
|
106
107
|
const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
|
|
107
108
|
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
108
|
-
const BLE_RESPONSE_TIMEOUT_MS = 30_000;
|
|
109
109
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
110
110
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
111
|
-
const DEVICE_SCAN_TIMEOUT_MS =
|
|
111
|
+
const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
112
112
|
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
113
113
|
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
114
|
export type ProtocolV2BleTuning = {
|
|
119
115
|
iosPacketLength?: number;
|
|
120
116
|
androidPacketLength?: number;
|
|
121
|
-
highVolumeWriteBurstSize?: number;
|
|
122
|
-
highVolumeWritePauseMs?: number;
|
|
123
|
-
highVolumeWriteFlushDelayMs?: number;
|
|
124
|
-
highVolumeWriteWithResponse?: boolean;
|
|
125
117
|
};
|
|
126
118
|
|
|
127
119
|
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
@@ -129,10 +121,6 @@ type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
|
129
121
|
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
130
122
|
iosPacketLength: IOS_PACKET_LENGTH,
|
|
131
123
|
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
124
|
};
|
|
137
125
|
|
|
138
126
|
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
@@ -153,27 +141,13 @@ export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
|
|
|
153
141
|
tuning.androidPacketLength,
|
|
154
142
|
protocolV2BleTuning.androidPacketLength
|
|
155
143
|
),
|
|
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
144
|
};
|
|
171
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
145
|
+
Log?.debug('[ReactNativeBleTransport] BLE tuning configured', protocolV2BleTuning);
|
|
172
146
|
}
|
|
173
147
|
|
|
174
148
|
export function resetProtocolV2BleTuning() {
|
|
175
149
|
protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
176
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
150
|
+
Log?.debug('[ReactNativeBleTransport] BLE tuning reset', protocolV2BleTuning);
|
|
177
151
|
}
|
|
178
152
|
|
|
179
153
|
export function getProtocolV2BleTuning() {
|
|
@@ -222,9 +196,10 @@ const requestAndroidMtu = async (device: Device) => {
|
|
|
222
196
|
|
|
223
197
|
try {
|
|
224
198
|
const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
|
|
225
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
199
|
+
Log?.debug('[ReactNativeBleTransport] MTU configured', {
|
|
200
|
+
deviceId: device.id,
|
|
226
201
|
requested: ANDROID_REQUEST_MTU,
|
|
227
|
-
|
|
202
|
+
actual: mtuDevice.mtu,
|
|
228
203
|
});
|
|
229
204
|
return mtuDevice;
|
|
230
205
|
} catch (error) {
|
|
@@ -272,6 +247,8 @@ export default class ReactNativeBleTransport {
|
|
|
272
247
|
|
|
273
248
|
_messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
274
249
|
|
|
250
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
251
|
+
|
|
275
252
|
name = 'ReactNativeBleTransport';
|
|
276
253
|
|
|
277
254
|
configured = false;
|
|
@@ -297,12 +274,31 @@ export default class ReactNativeBleTransport {
|
|
|
297
274
|
|
|
298
275
|
private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
299
276
|
|
|
300
|
-
private
|
|
301
|
-
|
|
302
|
-
|
|
277
|
+
private protocolV2Links = new ProtocolV2LinkManager<string>({
|
|
278
|
+
getSchemas: () => {
|
|
279
|
+
if (!this._messages || !this._messagesV2) {
|
|
280
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
protocolV1: this._messages,
|
|
284
|
+
protocolV2: this._messagesV2,
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
classifyError: () => 'link-fatal',
|
|
288
|
+
onLinkInvalidated: async (uuid, reason) => {
|
|
289
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
290
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
291
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
|
|
292
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
293
|
+
await this.release(uuid, true);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
});
|
|
303
297
|
|
|
304
298
|
private monitorTokens: Map<string, number> = new Map();
|
|
305
299
|
|
|
300
|
+
private disconnectEventTokens: Map<string, number> = new Map();
|
|
301
|
+
|
|
306
302
|
private nextMonitorToken = 1;
|
|
307
303
|
|
|
308
304
|
constructor(options: TransportOptions) {
|
|
@@ -321,8 +317,19 @@ export default class ReactNativeBleTransport {
|
|
|
321
317
|
}
|
|
322
318
|
|
|
323
319
|
configureProtocolV2(signedData: any) {
|
|
320
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
321
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
324
326
|
this._messagesV2 = parseConfigure(signedData);
|
|
325
|
-
|
|
327
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
328
|
+
if (isReconfiguration) {
|
|
329
|
+
this.protocolV2Links
|
|
330
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
331
|
+
.catch(error => Log?.debug('Protocol V2 schema link cleanup failed:', error));
|
|
332
|
+
}
|
|
326
333
|
}
|
|
327
334
|
|
|
328
335
|
listen() {
|
|
@@ -436,6 +443,7 @@ export default class ReactNativeBleTransport {
|
|
|
436
443
|
|
|
437
444
|
attachDisconnectSubscription(transport: BleTransport, device: Device, uuid: string) {
|
|
438
445
|
transport.disconnectSubscription?.remove();
|
|
446
|
+
const { monitorToken } = transport;
|
|
439
447
|
transport.disconnectSubscription = device.onDisconnected(() => {
|
|
440
448
|
if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
|
|
441
449
|
Log?.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
|
|
@@ -445,14 +453,14 @@ export default class ReactNativeBleTransport {
|
|
|
445
453
|
Log?.debug('device disconnect ignored for stale transport: ', device?.id);
|
|
446
454
|
return;
|
|
447
455
|
}
|
|
456
|
+
if (this.monitorTokens.get(uuid) !== monitorToken) {
|
|
457
|
+
Log?.debug('device disconnect ignored for stale generation: ', device?.id);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
448
460
|
|
|
449
461
|
try {
|
|
450
462
|
Log?.debug('device disconnect: ', device?.id);
|
|
451
|
-
this.
|
|
452
|
-
name: device?.name,
|
|
453
|
-
id: device?.id,
|
|
454
|
-
connectId: device?.id,
|
|
455
|
-
});
|
|
463
|
+
this.emitDeviceDisconnect(uuid, device?.name, monitorToken);
|
|
456
464
|
if (this.runPromise) {
|
|
457
465
|
const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
|
|
458
466
|
this.runPromise.reject(error);
|
|
@@ -466,6 +474,22 @@ export default class ReactNativeBleTransport {
|
|
|
466
474
|
});
|
|
467
475
|
}
|
|
468
476
|
|
|
477
|
+
private emitDeviceDisconnect(uuid: string, name: string | null | undefined, token?: number) {
|
|
478
|
+
if (token === undefined || this.disconnectEventTokens.get(uuid) === token) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (this.monitorTokens.get(uuid) !== token) {
|
|
482
|
+
Log?.debug('device disconnect event ignored for stale generation: ', uuid);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
this.disconnectEventTokens.set(uuid, token);
|
|
486
|
+
this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
487
|
+
name,
|
|
488
|
+
id: uuid,
|
|
489
|
+
connectId: uuid,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
469
493
|
async reconnectFirmwareUploadTransport(uuid: string, transport: BleTransport) {
|
|
470
494
|
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
471
495
|
try {
|
|
@@ -553,14 +577,13 @@ export default class ReactNativeBleTransport {
|
|
|
553
577
|
}
|
|
554
578
|
|
|
555
579
|
blePlxManager.startDeviceScan(
|
|
556
|
-
|
|
580
|
+
getBluetoothServiceUuids(),
|
|
557
581
|
{
|
|
558
582
|
allowDuplicates: true,
|
|
559
583
|
scanMode: ScanMode.LowLatency,
|
|
560
584
|
},
|
|
561
585
|
(error, device) => {
|
|
562
586
|
if (error) {
|
|
563
|
-
Log?.debug('ble scan manager: ', blePlxManager);
|
|
564
587
|
Log?.debug('ble scan error: ', error);
|
|
565
588
|
if (
|
|
566
589
|
[BleErrorCode.BluetoothPoweredOff, BleErrorCode.BluetoothInUnknownState].includes(
|
|
@@ -588,29 +611,8 @@ export default class ReactNativeBleTransport {
|
|
|
588
611
|
isOnekeyDevice(device?.name ?? null, device?.id) ||
|
|
589
612
|
isOnekeyDevice(device?.localName ?? null, device?.id) ||
|
|
590
613
|
hasKnownOneKeyService(device);
|
|
591
|
-
const shouldTraceCandidate =
|
|
592
|
-
!!displayName && /onekey|bixinkey|pro\s*2|pro\b|touch|^k\d|^t\d/i.test(displayName);
|
|
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
|
-
|
|
604
614
|
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
615
|
addDevice(device as unknown as Device);
|
|
613
|
-
Log?.debug('search device end ======================\n');
|
|
614
616
|
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
615
617
|
Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
|
|
616
618
|
name: device?.name,
|
|
@@ -622,12 +624,19 @@ export default class ReactNativeBleTransport {
|
|
|
622
624
|
}
|
|
623
625
|
);
|
|
624
626
|
|
|
625
|
-
getConnectedDeviceIds(getBluetoothServiceUuids()).then(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
627
|
+
getConnectedDeviceIds(Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(
|
|
628
|
+
devices => {
|
|
629
|
+
for (const device of devices) {
|
|
630
|
+
const { serviceUUIDs } = device as { serviceUUIDs?: string[] };
|
|
631
|
+
const hasCachedServiceUuid = Boolean(serviceUUIDs?.length);
|
|
632
|
+
const keepDevice = Platform.OS === 'ios' || hasCachedServiceUuid;
|
|
633
|
+
if (keepDevice) {
|
|
634
|
+
Log?.debug('search connected peripheral: ', device.id);
|
|
635
|
+
addDevice(device as unknown as Device);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
629
638
|
}
|
|
630
|
-
|
|
639
|
+
);
|
|
631
640
|
|
|
632
641
|
const addDevice = (device: Device) => {
|
|
633
642
|
if (deviceList.every(d => d.id !== device.id)) {
|
|
@@ -641,6 +650,12 @@ export default class ReactNativeBleTransport {
|
|
|
641
650
|
name: displayName,
|
|
642
651
|
commType: 'ble',
|
|
643
652
|
} as IOneKeyDevice);
|
|
653
|
+
Log?.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
|
|
654
|
+
deviceId: device.id,
|
|
655
|
+
name: displayName,
|
|
656
|
+
serviceUUIDs: device.serviceUUIDs,
|
|
657
|
+
protocolHint,
|
|
658
|
+
});
|
|
644
659
|
}
|
|
645
660
|
};
|
|
646
661
|
|
|
@@ -686,7 +701,6 @@ export default class ReactNativeBleTransport {
|
|
|
686
701
|
this.runPromise.reject(error);
|
|
687
702
|
this.rejectAllProtocolV2Frames(error);
|
|
688
703
|
this.runPromise = null;
|
|
689
|
-
this.activeProtocolV2Call = null;
|
|
690
704
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
691
705
|
}
|
|
692
706
|
|
|
@@ -806,7 +820,10 @@ export default class ReactNativeBleTransport {
|
|
|
806
820
|
);
|
|
807
821
|
transportCache[uuid] = transport;
|
|
808
822
|
|
|
809
|
-
this.protocolV2Assemblers.set(
|
|
823
|
+
this.protocolV2Assemblers.set(
|
|
824
|
+
uuid,
|
|
825
|
+
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
826
|
+
);
|
|
810
827
|
|
|
811
828
|
if (Platform.OS === 'ios') {
|
|
812
829
|
await new Promise<void>(resolve => {
|
|
@@ -818,12 +835,6 @@ export default class ReactNativeBleTransport {
|
|
|
818
835
|
|
|
819
836
|
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
820
837
|
|
|
821
|
-
this.emitter?.emit('device-connect', {
|
|
822
|
-
name: device.name,
|
|
823
|
-
id: device.id,
|
|
824
|
-
connectId: device.id,
|
|
825
|
-
});
|
|
826
|
-
|
|
827
838
|
this.attachDisconnectSubscription(transport, device, uuid);
|
|
828
839
|
|
|
829
840
|
return { uuid, protocolType };
|
|
@@ -853,6 +864,29 @@ export default class ReactNativeBleTransport {
|
|
|
853
864
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
854
865
|
return;
|
|
855
866
|
}
|
|
867
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
868
|
+
let errorCode:
|
|
869
|
+
| typeof HardwareErrorCode.BleDeviceBondError
|
|
870
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
871
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
872
|
+
| typeof HardwareErrorCode.BleTimeoutError =
|
|
873
|
+
HardwareErrorCode.BleCharacteristicNotifyError;
|
|
874
|
+
if (error.reason?.includes('The connection has timed out unexpectedly')) {
|
|
875
|
+
errorCode = HardwareErrorCode.BleTimeoutError;
|
|
876
|
+
} else if (error.reason?.includes('Encryption is insufficient')) {
|
|
877
|
+
errorCode = HardwareErrorCode.BleDeviceBondError;
|
|
878
|
+
} else if (
|
|
879
|
+
error.reason?.includes('Cannot write client characteristic config descriptor') ||
|
|
880
|
+
error.reason?.includes('Cannot find client characteristic config descriptor') ||
|
|
881
|
+
error.reason?.includes('The handle is invalid') ||
|
|
882
|
+
error.reason?.includes('Writing is not permitted') ||
|
|
883
|
+
error.reason?.includes('notify change failed for device')
|
|
884
|
+
) {
|
|
885
|
+
errorCode = HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
|
|
886
|
+
}
|
|
887
|
+
this.rejectProtocolV2Frames(uuid, ERRORS.TypedError(errorCode));
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
856
890
|
if (this.runPromise) {
|
|
857
891
|
let ERROR:
|
|
858
892
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
@@ -908,7 +942,7 @@ export default class ReactNativeBleTransport {
|
|
|
908
942
|
return;
|
|
909
943
|
}
|
|
910
944
|
if (protocol === 'V2') {
|
|
911
|
-
this.handleProtocolV2Notification(uuid, new Uint8Array(data));
|
|
945
|
+
this.handleProtocolV2Notification(uuid, monitorToken, new Uint8Array(data));
|
|
912
946
|
return;
|
|
913
947
|
}
|
|
914
948
|
// console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
|
|
@@ -934,8 +968,11 @@ export default class ReactNativeBleTransport {
|
|
|
934
968
|
} catch (error) {
|
|
935
969
|
Log?.debug('monitor data error: ', error);
|
|
936
970
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
937
|
-
this.
|
|
938
|
-
|
|
971
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
972
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
973
|
+
} else {
|
|
974
|
+
this.runPromise?.reject(notifyError);
|
|
975
|
+
}
|
|
939
976
|
}
|
|
940
977
|
}, notifyTransactionId);
|
|
941
978
|
|
|
@@ -944,12 +981,12 @@ export default class ReactNativeBleTransport {
|
|
|
944
981
|
|
|
945
982
|
async release(uuid: string, onclose = false) {
|
|
946
983
|
const transport = transportCache[uuid];
|
|
984
|
+
await this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
|
|
947
985
|
if (this.runPromise) {
|
|
948
986
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
949
987
|
this.runPromise.reject(error);
|
|
950
988
|
this.runPromise = null;
|
|
951
989
|
this.rejectAllProtocolV2Frames(error);
|
|
952
|
-
this.activeProtocolV2Call = null;
|
|
953
990
|
} else {
|
|
954
991
|
this.resetProtocolV2Frames(uuid);
|
|
955
992
|
}
|
|
@@ -957,9 +994,6 @@ export default class ReactNativeBleTransport {
|
|
|
957
994
|
if (Platform.OS === 'android' && !onclose && transport) {
|
|
958
995
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
959
996
|
this.resetProtocolV2Frames(uuid);
|
|
960
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
961
|
-
this.activeProtocolV2Call = null;
|
|
962
|
-
}
|
|
963
997
|
return Promise.resolve(true);
|
|
964
998
|
}
|
|
965
999
|
|
|
@@ -993,7 +1027,7 @@ export default class ReactNativeBleTransport {
|
|
|
993
1027
|
}
|
|
994
1028
|
|
|
995
1029
|
this.deviceProtocol.delete(uuid);
|
|
996
|
-
|
|
1030
|
+
// Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
|
|
997
1031
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
998
1032
|
this.protocolV2Assemblers.delete(uuid);
|
|
999
1033
|
this.resetProtocolV2Frames(uuid);
|
|
@@ -1025,13 +1059,6 @@ export default class ReactNativeBleTransport {
|
|
|
1025
1059
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1026
1060
|
}
|
|
1027
1061
|
|
|
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
1062
|
const protocol = this.getProtocolType(uuid);
|
|
1036
1063
|
if (!protocol) {
|
|
1037
1064
|
throw ERRORS.TypedError(
|
|
@@ -1039,31 +1066,17 @@ export default class ReactNativeBleTransport {
|
|
|
1039
1066
|
`Device protocol has not been detected for ${uuid}`
|
|
1040
1067
|
);
|
|
1041
1068
|
}
|
|
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
|
-
}
|
|
1069
|
+
Log?.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
1062
1070
|
|
|
1063
1071
|
if (protocol === 'V2') {
|
|
1064
1072
|
return this.callProtocolV2(uuid, name, data, options);
|
|
1065
1073
|
}
|
|
1066
1074
|
|
|
1075
|
+
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
1076
|
+
if (this.runPromise && !forceRun) {
|
|
1077
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1067
1080
|
return this.callProtocolV1(uuid, name, data, options);
|
|
1068
1081
|
}
|
|
1069
1082
|
|
|
@@ -1157,14 +1170,13 @@ export default class ReactNativeBleTransport {
|
|
|
1157
1170
|
}
|
|
1158
1171
|
);
|
|
1159
1172
|
} else if (name === 'FirmwareUpload') {
|
|
1160
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
1173
|
+
Log?.debug('[ReactNativeBleTransport] Firmware upload transport configured', {
|
|
1161
1174
|
packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
|
|
1162
1175
|
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1163
1176
|
pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1164
1177
|
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1165
1178
|
maxRetries: FIRMWARE_UPLOAD_WRITE_MAX_RETRIES,
|
|
1166
1179
|
});
|
|
1167
|
-
|
|
1168
1180
|
await writeFirmwareUploadChunkedData(
|
|
1169
1181
|
buffers,
|
|
1170
1182
|
async data => {
|
|
@@ -1218,7 +1230,6 @@ export default class ReactNativeBleTransport {
|
|
|
1218
1230
|
for (const o of buffers) {
|
|
1219
1231
|
const outData = o.toString('base64');
|
|
1220
1232
|
// Upload resources on low-end phones may OOM
|
|
1221
|
-
// this.Log.debug('send hex strting: ', o.toString('hex'));
|
|
1222
1233
|
try {
|
|
1223
1234
|
await transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1224
1235
|
} catch (e) {
|
|
@@ -1256,7 +1267,6 @@ export default class ReactNativeBleTransport {
|
|
|
1256
1267
|
throw new Error('Returning data is not string.');
|
|
1257
1268
|
}
|
|
1258
1269
|
|
|
1259
|
-
Log?.debug('receive data: ', response);
|
|
1260
1270
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
1261
1271
|
return check.call(jsonData);
|
|
1262
1272
|
} catch (e) {
|
|
@@ -1279,8 +1289,9 @@ export default class ReactNativeBleTransport {
|
|
|
1279
1289
|
}
|
|
1280
1290
|
|
|
1281
1291
|
async disconnect(session: string) {
|
|
1282
|
-
|
|
1292
|
+
await this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
|
|
1283
1293
|
const transport = transportCache[session];
|
|
1294
|
+
const monitorToken = transport?.monitorToken ?? this.monitorTokens.get(session);
|
|
1284
1295
|
|
|
1285
1296
|
// Clean up disconnect subscription first to prevent onDisconnected callback
|
|
1286
1297
|
// from being triggered when we cancel the connection below
|
|
@@ -1341,20 +1352,16 @@ export default class ReactNativeBleTransport {
|
|
|
1341
1352
|
this.deviceProtocolHints.delete(session);
|
|
1342
1353
|
this.protocolV2Assemblers.delete(session);
|
|
1343
1354
|
this.resetProtocolV2Frames(session);
|
|
1344
|
-
if (this.activeProtocolV2Call?.uuid === session) {
|
|
1345
|
-
this.activeProtocolV2Call = null;
|
|
1346
|
-
}
|
|
1347
1355
|
|
|
1348
1356
|
// emit the disconnect event
|
|
1349
1357
|
try {
|
|
1350
|
-
this.
|
|
1351
|
-
name: transport?.device?.name,
|
|
1352
|
-
id: session,
|
|
1353
|
-
connectId: session,
|
|
1354
|
-
});
|
|
1358
|
+
this.emitDeviceDisconnect(session, transport?.device?.name, monitorToken);
|
|
1355
1359
|
} catch (e) {
|
|
1356
1360
|
Log?.error('resetSession: emit disconnect event error: ', e);
|
|
1357
1361
|
}
|
|
1362
|
+
if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
|
|
1363
|
+
this.monitorTokens.delete(session);
|
|
1364
|
+
}
|
|
1358
1365
|
// eslint-disable-next-line no-promise-executor-return
|
|
1359
1366
|
await new Promise<void>(resolve => setTimeout(() => resolve(), 100));
|
|
1360
1367
|
}
|
|
@@ -1403,37 +1410,49 @@ export default class ReactNativeBleTransport {
|
|
|
1403
1410
|
if (expectedProtocol === 'V1') {
|
|
1404
1411
|
if (await this.probeProtocolV1(uuid)) {
|
|
1405
1412
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1406
|
-
Log?.debug(
|
|
1413
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1414
|
+
deviceId: uuid,
|
|
1415
|
+
protocol: 'V1',
|
|
1416
|
+
source: 'expected',
|
|
1417
|
+
});
|
|
1407
1418
|
return 'V1';
|
|
1408
1419
|
}
|
|
1409
1420
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1410
1421
|
}
|
|
1411
1422
|
|
|
1412
1423
|
if (expectedProtocol === 'V2') {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1424
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
1425
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1426
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1427
|
+
deviceId: uuid,
|
|
1428
|
+
protocol: 'V2',
|
|
1429
|
+
source: 'expected',
|
|
1430
|
+
});
|
|
1431
|
+
return 'V2';
|
|
1432
|
+
}
|
|
1433
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1418
1434
|
}
|
|
1419
1435
|
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1422
|
-
// 不能作为最终结论。
|
|
1436
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
1437
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
1423
1438
|
const probeOrder: ProtocolType[] =
|
|
1424
1439
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1425
1440
|
|
|
1426
1441
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1427
1442
|
const protocol = probeOrder[i];
|
|
1428
1443
|
if (i > 0) {
|
|
1429
|
-
//
|
|
1444
|
+
// Reset subscriptions and buffers after a failed probe before trying another protocol.
|
|
1430
1445
|
await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
1431
1446
|
}
|
|
1432
1447
|
const detected =
|
|
1433
1448
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
1434
1449
|
if (detected) {
|
|
1435
1450
|
this.deviceProtocol.set(uuid, protocol);
|
|
1436
|
-
Log?.debug(
|
|
1451
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1452
|
+
deviceId: uuid,
|
|
1453
|
+
protocol,
|
|
1454
|
+
source: 'probe',
|
|
1455
|
+
});
|
|
1437
1456
|
return protocol;
|
|
1438
1457
|
}
|
|
1439
1458
|
}
|
|
@@ -1444,11 +1463,12 @@ export default class ReactNativeBleTransport {
|
|
|
1444
1463
|
|
|
1445
1464
|
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
1446
1465
|
const transport = transportCache[uuid];
|
|
1466
|
+
await this.protocolV2Links.invalidateLink(
|
|
1467
|
+
uuid,
|
|
1468
|
+
`Reset notify state after Protocol ${protocol} probe`
|
|
1469
|
+
);
|
|
1447
1470
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1448
1471
|
this.resetProtocolV2Frames(uuid);
|
|
1449
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
1450
|
-
this.activeProtocolV2Call = null;
|
|
1451
|
-
}
|
|
1452
1472
|
if (this.runPromise) {
|
|
1453
1473
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1454
1474
|
this.runPromise.reject(error);
|
|
@@ -1533,13 +1553,9 @@ export default class ReactNativeBleTransport {
|
|
|
1533
1553
|
return detected;
|
|
1534
1554
|
}
|
|
1535
1555
|
|
|
1536
|
-
private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
|
|
1556
|
+
private handleProtocolV2Notification(uuid: string, monitorToken: number, data: Uint8Array) {
|
|
1537
1557
|
try {
|
|
1538
|
-
if (
|
|
1539
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1540
|
-
this.resetProtocolV2Frames(uuid);
|
|
1541
|
-
return;
|
|
1542
|
-
}
|
|
1558
|
+
if (this.monitorTokens.get(uuid) !== monitorToken) return;
|
|
1543
1559
|
|
|
1544
1560
|
if (data.length === 0) return;
|
|
1545
1561
|
|
|
@@ -1552,8 +1568,15 @@ export default class ReactNativeBleTransport {
|
|
|
1552
1568
|
} catch (error) {
|
|
1553
1569
|
Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
|
|
1554
1570
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1555
|
-
this.
|
|
1556
|
-
this.
|
|
1571
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1572
|
+
this.protocolV2Links
|
|
1573
|
+
.invalidateLink(uuid, `Protocol V2 notification error: ${error}`)
|
|
1574
|
+
.catch(invalidateError =>
|
|
1575
|
+
Log?.debug(
|
|
1576
|
+
'[ReactNativeBleTransport] Protocol V2 notify cleanup failed:',
|
|
1577
|
+
invalidateError
|
|
1578
|
+
)
|
|
1579
|
+
);
|
|
1557
1580
|
}
|
|
1558
1581
|
}
|
|
1559
1582
|
|
|
@@ -1589,8 +1612,13 @@ export default class ReactNativeBleTransport {
|
|
|
1589
1612
|
this.protocolV2FramePromises.delete(uuid);
|
|
1590
1613
|
}
|
|
1591
1614
|
|
|
1592
|
-
private
|
|
1593
|
-
|
|
1615
|
+
private rejectProtocolV2Frames(uuid: string, error: Error) {
|
|
1616
|
+
this.protocolV2FrameQueues.delete(uuid);
|
|
1617
|
+
const framePromise = this.protocolV2FramePromises.get(uuid);
|
|
1618
|
+
if (framePromise) {
|
|
1619
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1620
|
+
framePromise.reject(error);
|
|
1621
|
+
}
|
|
1594
1622
|
}
|
|
1595
1623
|
|
|
1596
1624
|
private async readProtocolV2Frame(uuid: string) {
|
|
@@ -1610,10 +1638,46 @@ export default class ReactNativeBleTransport {
|
|
|
1610
1638
|
}
|
|
1611
1639
|
}
|
|
1612
1640
|
|
|
1641
|
+
private async writeProtocolV2Packet(
|
|
1642
|
+
transport: BleTransport,
|
|
1643
|
+
base64: string,
|
|
1644
|
+
context: ProtocolV2CallContext,
|
|
1645
|
+
assertCurrentGeneration: () => void
|
|
1646
|
+
) {
|
|
1647
|
+
let attempt = 0;
|
|
1648
|
+
for (;;) {
|
|
1649
|
+
assertCurrentGeneration();
|
|
1650
|
+
if (context.signal.aborted) {
|
|
1651
|
+
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
1652
|
+
}
|
|
1653
|
+
try {
|
|
1654
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1655
|
+
assertCurrentGeneration();
|
|
1656
|
+
return;
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
if (
|
|
1659
|
+
getFirmwareUploadWriteRetryType(error) !== 'congested' ||
|
|
1660
|
+
attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES
|
|
1661
|
+
) {
|
|
1662
|
+
throw error;
|
|
1663
|
+
}
|
|
1664
|
+
const delayMs = resolveFirmwareUploadRetryDelay(attempt);
|
|
1665
|
+
attempt += 1;
|
|
1666
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 congested write retry:', {
|
|
1667
|
+
name: context.messageName,
|
|
1668
|
+
attempt,
|
|
1669
|
+
delayMs,
|
|
1670
|
+
});
|
|
1671
|
+
await delay(delayMs);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1613
1676
|
private async writeProtocolV2Frame(
|
|
1614
1677
|
transport: BleTransport,
|
|
1615
1678
|
frame: Uint8Array,
|
|
1616
|
-
|
|
1679
|
+
context: ProtocolV2CallContext,
|
|
1680
|
+
assertCurrentGeneration: () => void
|
|
1617
1681
|
) {
|
|
1618
1682
|
const tuning = getProtocolV2BleTuning();
|
|
1619
1683
|
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
@@ -1622,52 +1686,24 @@ export default class ReactNativeBleTransport {
|
|
|
1622
1686
|
androidPacketLength: tuning.androidPacketLength,
|
|
1623
1687
|
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1624
1688
|
});
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
packetsWritten += 1;
|
|
1644
|
-
|
|
1645
|
-
if (
|
|
1646
|
-
shouldThrottle &&
|
|
1647
|
-
packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
|
|
1648
|
-
offset + packetCapacity < frame.length
|
|
1649
|
-
) {
|
|
1650
|
-
await delay(tuning.highVolumeWritePauseMs);
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
if (shouldThrottle) {
|
|
1655
|
-
await delay(tuning.highVolumeWriteFlushDelayMs);
|
|
1656
|
-
}
|
|
1657
|
-
} catch (error) {
|
|
1658
|
-
if (options?.highVolume && !writeWithResponse && packetsWritten === 0) {
|
|
1659
|
-
Log?.debug(
|
|
1660
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume writeWithoutResponse failed before data was sent, fallback to writeWithResponse:',
|
|
1661
|
-
error
|
|
1662
|
-
);
|
|
1663
|
-
await this.writeProtocolV2Frame(transport, frame, {
|
|
1664
|
-
highVolume: true,
|
|
1665
|
-
writeWithResponse: true,
|
|
1666
|
-
});
|
|
1667
|
-
return;
|
|
1668
|
-
}
|
|
1669
|
-
throw error;
|
|
1670
|
-
}
|
|
1689
|
+
await writeProtocolV2BleFrame({
|
|
1690
|
+
frame,
|
|
1691
|
+
packetCapacity,
|
|
1692
|
+
assertActive: assertCurrentGeneration,
|
|
1693
|
+
signal: context.signal,
|
|
1694
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1695
|
+
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1696
|
+
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1697
|
+
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1698
|
+
wait: delay,
|
|
1699
|
+
writePacket: packet =>
|
|
1700
|
+
this.writeProtocolV2Packet(
|
|
1701
|
+
transport,
|
|
1702
|
+
Buffer.from(packet).toString('base64'),
|
|
1703
|
+
context,
|
|
1704
|
+
assertCurrentGeneration
|
|
1705
|
+
),
|
|
1706
|
+
});
|
|
1671
1707
|
}
|
|
1672
1708
|
|
|
1673
1709
|
private async callProtocolV2(
|
|
@@ -1680,101 +1716,76 @@ export default class ReactNativeBleTransport {
|
|
|
1680
1716
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1681
1717
|
}
|
|
1682
1718
|
|
|
1683
|
-
const
|
|
1684
|
-
if (this.runPromise) {
|
|
1685
|
-
if (!forceRun) {
|
|
1686
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1687
|
-
}
|
|
1688
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1689
|
-
this.runPromise.reject(error);
|
|
1690
|
-
this.rejectAllProtocolV2Frames(error);
|
|
1691
|
-
this.runPromise = null;
|
|
1692
|
-
this.activeProtocolV2Call = null;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
const transport = this.getCachedTransport(uuid);
|
|
1696
|
-
const runPromise = createDeferred<Uint8Array>();
|
|
1697
|
-
runPromise.promise.catch(() => undefined);
|
|
1698
|
-
this.runPromise = runPromise;
|
|
1699
|
-
const callToken = this.nextProtocolV2CallToken++;
|
|
1700
|
-
this.activeProtocolV2Call = { uuid, token: callToken };
|
|
1701
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1702
|
-
this.resetProtocolV2Frames(uuid);
|
|
1703
|
-
let completed = false;
|
|
1704
|
-
const callOptions = {
|
|
1705
|
-
...options,
|
|
1706
|
-
timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
|
|
1707
|
-
};
|
|
1719
|
+
const callOptions = options;
|
|
1708
1720
|
const highVolumeWrite = LogBlockCommand.has(name);
|
|
1709
1721
|
|
|
1710
1722
|
if (highVolumeWrite) {
|
|
1711
1723
|
const tuning = getProtocolV2BleTuning();
|
|
1712
|
-
Log?.debug(
|
|
1713
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
|
|
1724
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
1714
1725
|
name,
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
burstSize: tuning.highVolumeWriteBurstSize,
|
|
1719
|
-
pauseMs: tuning.highVolumeWritePauseMs,
|
|
1720
|
-
flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
|
|
1721
|
-
writeWithResponse: tuning.highVolumeWriteWithResponse,
|
|
1722
|
-
}
|
|
1723
|
-
);
|
|
1726
|
+
writeMode: 'withoutResponse',
|
|
1727
|
+
packetCapacity: Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1728
|
+
});
|
|
1724
1729
|
}
|
|
1725
1730
|
|
|
1726
1731
|
try {
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
await this.writeProtocolV2Frame(transport, frame, {
|
|
1735
|
-
highVolume: highVolumeWrite,
|
|
1736
|
-
});
|
|
1737
|
-
},
|
|
1738
|
-
readFrame: async () => {
|
|
1739
|
-
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1740
|
-
if (!(rxFrame instanceof Uint8Array)) {
|
|
1741
|
-
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1742
|
-
}
|
|
1743
|
-
return rxFrame;
|
|
1744
|
-
},
|
|
1745
|
-
logger: Log,
|
|
1746
|
-
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1747
|
-
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
1748
|
-
ERRORS.TypedError(
|
|
1749
|
-
HardwareErrorCode.BleTimeoutError,
|
|
1750
|
-
`BLE response timeout after ${timeout}ms for ${name}`
|
|
1751
|
-
),
|
|
1752
|
-
});
|
|
1753
|
-
|
|
1754
|
-
const result = await session.call(name, data, callOptions);
|
|
1755
|
-
completed = true;
|
|
1756
|
-
return result;
|
|
1732
|
+
return await this.protocolV2Links.call(
|
|
1733
|
+
uuid,
|
|
1734
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
1735
|
+
name,
|
|
1736
|
+
data,
|
|
1737
|
+
callOptions
|
|
1738
|
+
);
|
|
1757
1739
|
} catch (e) {
|
|
1758
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1759
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1760
|
-
this.resetProtocolV2Frames(uuid);
|
|
1761
|
-
}
|
|
1762
1740
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1763
1741
|
throw e;
|
|
1764
|
-
} finally {
|
|
1765
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1766
|
-
if (!completed) {
|
|
1767
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1768
|
-
}
|
|
1769
|
-
this.resetProtocolV2Frames(uuid);
|
|
1770
|
-
this.activeProtocolV2Call = null;
|
|
1771
|
-
}
|
|
1772
|
-
if (this.runPromise === runPromise) {
|
|
1773
|
-
this.runPromise = null;
|
|
1774
|
-
}
|
|
1775
1742
|
}
|
|
1776
1743
|
}
|
|
1777
1744
|
|
|
1745
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
1746
|
+
const generation = this.monitorTokens.get(uuid) ?? 0;
|
|
1747
|
+
const assertCurrentGeneration = () => {
|
|
1748
|
+
if (this.monitorTokens.get(uuid) !== generation) {
|
|
1749
|
+
throw new Error(`Protocol V2 monitor generation changed for ${uuid}`);
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
return {
|
|
1754
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1755
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1756
|
+
generation,
|
|
1757
|
+
prepareCall: () => {
|
|
1758
|
+
assertCurrentGeneration();
|
|
1759
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1760
|
+
this.resetProtocolV2Frames(uuid);
|
|
1761
|
+
},
|
|
1762
|
+
writeFrame: async (frame: Uint8Array, context: ProtocolV2CallContext) => {
|
|
1763
|
+
assertCurrentGeneration();
|
|
1764
|
+
const currentTransport = this.getCachedTransport(uuid);
|
|
1765
|
+
await this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
|
|
1766
|
+
},
|
|
1767
|
+
readFrame: async () => {
|
|
1768
|
+
assertCurrentGeneration();
|
|
1769
|
+
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1770
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1771
|
+
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1772
|
+
}
|
|
1773
|
+
return rxFrame;
|
|
1774
|
+
},
|
|
1775
|
+
reset: (reason: string) => {
|
|
1776
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1777
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
1778
|
+
},
|
|
1779
|
+
logger: Log,
|
|
1780
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1781
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
1782
|
+
ERRORS.TypedError(
|
|
1783
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1784
|
+
`BLE response timeout after ${timeout}ms for ${messageName}`
|
|
1785
|
+
),
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1778
1789
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
1779
1790
|
return this.deviceProtocol.get(path);
|
|
1780
1791
|
}
|