@onekeyfe/hd-transport-react-native 1.2.0-alpha.2 → 1.2.0-alpha.21
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/index.d.ts +15 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +191 -228
- package/dist/subscribeBleOn.d.ts.map +1 -1
- package/dist/transportLog.d.ts +13 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/package.json +6 -6
- package/src/__tests__/bleStrategy.test.ts +1 -6
- package/src/__tests__/protocolV2Link.test.ts +243 -0
- package/src/bleStrategy.ts +0 -25
- package/src/index.ts +192 -261
- package/src/subscribeBleOn.ts +0 -2
- package/src/transportLog.ts +18 -0
package/src/index.ts
CHANGED
|
@@ -12,21 +12,18 @@ 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,
|
|
17
18
|
ProtocolV2FrameAssembler,
|
|
18
|
-
|
|
19
|
+
ProtocolV2LinkManager,
|
|
19
20
|
type TransportCallOptions,
|
|
20
21
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
21
22
|
} from '@onekeyfe/hd-transport';
|
|
22
23
|
import { ERRORS, HardwareErrorCode, createDeferred, isOnekeyDevice } from '@onekeyfe/hd-shared';
|
|
23
24
|
|
|
24
25
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
25
|
-
import {
|
|
26
|
-
hasWritableCapability,
|
|
27
|
-
resolveBleWriteMode,
|
|
28
|
-
resolveProtocolV2PacketCapacity,
|
|
29
|
-
} from './bleStrategy';
|
|
26
|
+
import { hasWritableCapability, resolveProtocolV2PacketCapacity } from './bleStrategy';
|
|
30
27
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
31
28
|
import {
|
|
32
29
|
ANDROID_PACKET_LENGTH,
|
|
@@ -40,6 +37,7 @@ import { isHeaderChunk } from './utils/validateNotify';
|
|
|
40
37
|
import BleTransport from './BleTransport';
|
|
41
38
|
import timer from './utils/timer';
|
|
42
39
|
import { bleLogger, setBleLogger } from './logger';
|
|
40
|
+
import { createTransportCallLog } from './transportLog';
|
|
43
41
|
|
|
44
42
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
45
43
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
@@ -111,17 +109,9 @@ const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
|
111
109
|
const DEVICE_SCAN_TIMEOUT_MS = 8000;
|
|
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() {
|
|
@@ -222,9 +194,10 @@ const requestAndroidMtu = async (device: Device) => {
|
|
|
222
194
|
|
|
223
195
|
try {
|
|
224
196
|
const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
|
|
225
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
197
|
+
Log?.debug('[ReactNativeBleTransport] MTU configured', {
|
|
198
|
+
deviceId: device.id,
|
|
226
199
|
requested: ANDROID_REQUEST_MTU,
|
|
227
|
-
|
|
200
|
+
actual: mtuDevice.mtu,
|
|
228
201
|
});
|
|
229
202
|
return mtuDevice;
|
|
230
203
|
} catch (error) {
|
|
@@ -297,9 +270,26 @@ export default class ReactNativeBleTransport {
|
|
|
297
270
|
|
|
298
271
|
private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
299
272
|
|
|
300
|
-
private
|
|
301
|
-
|
|
302
|
-
|
|
273
|
+
private protocolV2Links = new ProtocolV2LinkManager<string>({
|
|
274
|
+
getSchemas: () => {
|
|
275
|
+
if (!this._messages || !this._messagesV2) {
|
|
276
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
protocolV1: this._messages,
|
|
280
|
+
protocolV2: this._messagesV2,
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
classifyError: () => 'link-fatal',
|
|
284
|
+
onLinkInvalidated: async (uuid, reason) => {
|
|
285
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
286
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
287
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
|
|
288
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
289
|
+
await this.release(uuid, true);
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
});
|
|
303
293
|
|
|
304
294
|
private monitorTokens: Map<string, number> = new Map();
|
|
305
295
|
|
|
@@ -322,7 +312,9 @@ export default class ReactNativeBleTransport {
|
|
|
322
312
|
|
|
323
313
|
configureProtocolV2(signedData: any) {
|
|
324
314
|
this._messagesV2 = parseConfigure(signedData);
|
|
325
|
-
|
|
315
|
+
this.protocolV2Links
|
|
316
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
317
|
+
.catch(error => Log?.debug('Protocol V2 schema link cleanup failed:', error));
|
|
326
318
|
}
|
|
327
319
|
|
|
328
320
|
listen() {
|
|
@@ -553,14 +545,13 @@ export default class ReactNativeBleTransport {
|
|
|
553
545
|
}
|
|
554
546
|
|
|
555
547
|
blePlxManager.startDeviceScan(
|
|
556
|
-
|
|
548
|
+
getBluetoothServiceUuids(),
|
|
557
549
|
{
|
|
558
550
|
allowDuplicates: true,
|
|
559
551
|
scanMode: ScanMode.LowLatency,
|
|
560
552
|
},
|
|
561
553
|
(error, device) => {
|
|
562
554
|
if (error) {
|
|
563
|
-
Log?.debug('ble scan manager: ', blePlxManager);
|
|
564
555
|
Log?.debug('ble scan error: ', error);
|
|
565
556
|
if (
|
|
566
557
|
[BleErrorCode.BluetoothPoweredOff, BleErrorCode.BluetoothInUnknownState].includes(
|
|
@@ -588,29 +579,8 @@ export default class ReactNativeBleTransport {
|
|
|
588
579
|
isOnekeyDevice(device?.name ?? null, device?.id) ||
|
|
589
580
|
isOnekeyDevice(device?.localName ?? null, device?.id) ||
|
|
590
581
|
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
582
|
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
583
|
addDevice(device as unknown as Device);
|
|
613
|
-
Log?.debug('search device end ======================\n');
|
|
614
584
|
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
615
585
|
Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
|
|
616
586
|
name: device?.name,
|
|
@@ -622,12 +592,19 @@ export default class ReactNativeBleTransport {
|
|
|
622
592
|
}
|
|
623
593
|
);
|
|
624
594
|
|
|
625
|
-
getConnectedDeviceIds(getBluetoothServiceUuids()).then(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
595
|
+
getConnectedDeviceIds(Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(
|
|
596
|
+
devices => {
|
|
597
|
+
for (const device of devices) {
|
|
598
|
+
const { serviceUUIDs } = device as { serviceUUIDs?: string[] };
|
|
599
|
+
const hasCachedServiceUuid = Boolean(serviceUUIDs?.length);
|
|
600
|
+
const keepDevice = Platform.OS === 'ios' || hasCachedServiceUuid;
|
|
601
|
+
if (keepDevice) {
|
|
602
|
+
Log?.debug('search connected peripheral: ', device.id);
|
|
603
|
+
addDevice(device as unknown as Device);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
629
606
|
}
|
|
630
|
-
|
|
607
|
+
);
|
|
631
608
|
|
|
632
609
|
const addDevice = (device: Device) => {
|
|
633
610
|
if (deviceList.every(d => d.id !== device.id)) {
|
|
@@ -641,6 +618,12 @@ export default class ReactNativeBleTransport {
|
|
|
641
618
|
name: displayName,
|
|
642
619
|
commType: 'ble',
|
|
643
620
|
} as IOneKeyDevice);
|
|
621
|
+
Log?.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
|
|
622
|
+
deviceId: device.id,
|
|
623
|
+
name: displayName,
|
|
624
|
+
serviceUUIDs: device.serviceUUIDs,
|
|
625
|
+
protocolHint,
|
|
626
|
+
});
|
|
644
627
|
}
|
|
645
628
|
};
|
|
646
629
|
|
|
@@ -686,7 +669,6 @@ export default class ReactNativeBleTransport {
|
|
|
686
669
|
this.runPromise.reject(error);
|
|
687
670
|
this.rejectAllProtocolV2Frames(error);
|
|
688
671
|
this.runPromise = null;
|
|
689
|
-
this.activeProtocolV2Call = null;
|
|
690
672
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
691
673
|
}
|
|
692
674
|
|
|
@@ -853,6 +835,29 @@ export default class ReactNativeBleTransport {
|
|
|
853
835
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
854
836
|
return;
|
|
855
837
|
}
|
|
838
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
839
|
+
let errorCode:
|
|
840
|
+
| typeof HardwareErrorCode.BleDeviceBondError
|
|
841
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
842
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
843
|
+
| typeof HardwareErrorCode.BleTimeoutError =
|
|
844
|
+
HardwareErrorCode.BleCharacteristicNotifyError;
|
|
845
|
+
if (error.reason?.includes('The connection has timed out unexpectedly')) {
|
|
846
|
+
errorCode = HardwareErrorCode.BleTimeoutError;
|
|
847
|
+
} else if (error.reason?.includes('Encryption is insufficient')) {
|
|
848
|
+
errorCode = HardwareErrorCode.BleDeviceBondError;
|
|
849
|
+
} else if (
|
|
850
|
+
error.reason?.includes('Cannot write client characteristic config descriptor') ||
|
|
851
|
+
error.reason?.includes('Cannot find client characteristic config descriptor') ||
|
|
852
|
+
error.reason?.includes('The handle is invalid') ||
|
|
853
|
+
error.reason?.includes('Writing is not permitted') ||
|
|
854
|
+
error.reason?.includes('notify change failed for device')
|
|
855
|
+
) {
|
|
856
|
+
errorCode = HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
|
|
857
|
+
}
|
|
858
|
+
this.rejectProtocolV2Frames(uuid, ERRORS.TypedError(errorCode));
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
856
861
|
if (this.runPromise) {
|
|
857
862
|
let ERROR:
|
|
858
863
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
@@ -908,7 +913,7 @@ export default class ReactNativeBleTransport {
|
|
|
908
913
|
return;
|
|
909
914
|
}
|
|
910
915
|
if (protocol === 'V2') {
|
|
911
|
-
this.handleProtocolV2Notification(uuid, new Uint8Array(data));
|
|
916
|
+
this.handleProtocolV2Notification(uuid, monitorToken, new Uint8Array(data));
|
|
912
917
|
return;
|
|
913
918
|
}
|
|
914
919
|
// console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
|
|
@@ -934,8 +939,11 @@ export default class ReactNativeBleTransport {
|
|
|
934
939
|
} catch (error) {
|
|
935
940
|
Log?.debug('monitor data error: ', error);
|
|
936
941
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
937
|
-
this.
|
|
938
|
-
|
|
942
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
943
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
944
|
+
} else {
|
|
945
|
+
this.runPromise?.reject(notifyError);
|
|
946
|
+
}
|
|
939
947
|
}
|
|
940
948
|
}, notifyTransactionId);
|
|
941
949
|
|
|
@@ -944,12 +952,12 @@ export default class ReactNativeBleTransport {
|
|
|
944
952
|
|
|
945
953
|
async release(uuid: string, onclose = false) {
|
|
946
954
|
const transport = transportCache[uuid];
|
|
955
|
+
await this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
|
|
947
956
|
if (this.runPromise) {
|
|
948
957
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
949
958
|
this.runPromise.reject(error);
|
|
950
959
|
this.runPromise = null;
|
|
951
960
|
this.rejectAllProtocolV2Frames(error);
|
|
952
|
-
this.activeProtocolV2Call = null;
|
|
953
961
|
} else {
|
|
954
962
|
this.resetProtocolV2Frames(uuid);
|
|
955
963
|
}
|
|
@@ -957,9 +965,6 @@ export default class ReactNativeBleTransport {
|
|
|
957
965
|
if (Platform.OS === 'android' && !onclose && transport) {
|
|
958
966
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
959
967
|
this.resetProtocolV2Frames(uuid);
|
|
960
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
961
|
-
this.activeProtocolV2Call = null;
|
|
962
|
-
}
|
|
963
968
|
return Promise.resolve(true);
|
|
964
969
|
}
|
|
965
970
|
|
|
@@ -993,7 +998,7 @@ export default class ReactNativeBleTransport {
|
|
|
993
998
|
}
|
|
994
999
|
|
|
995
1000
|
this.deviceProtocol.delete(uuid);
|
|
996
|
-
|
|
1001
|
+
// Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
|
|
997
1002
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
998
1003
|
this.protocolV2Assemblers.delete(uuid);
|
|
999
1004
|
this.resetProtocolV2Frames(uuid);
|
|
@@ -1025,13 +1030,6 @@ export default class ReactNativeBleTransport {
|
|
|
1025
1030
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1026
1031
|
}
|
|
1027
1032
|
|
|
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
1033
|
const protocol = this.getProtocolType(uuid);
|
|
1036
1034
|
if (!protocol) {
|
|
1037
1035
|
throw ERRORS.TypedError(
|
|
@@ -1039,31 +1037,17 @@ export default class ReactNativeBleTransport {
|
|
|
1039
1037
|
`Device protocol has not been detected for ${uuid}`
|
|
1040
1038
|
);
|
|
1041
1039
|
}
|
|
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
|
-
}
|
|
1040
|
+
Log?.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
1062
1041
|
|
|
1063
1042
|
if (protocol === 'V2') {
|
|
1064
1043
|
return this.callProtocolV2(uuid, name, data, options);
|
|
1065
1044
|
}
|
|
1066
1045
|
|
|
1046
|
+
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
1047
|
+
if (this.runPromise && !forceRun) {
|
|
1048
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1067
1051
|
return this.callProtocolV1(uuid, name, data, options);
|
|
1068
1052
|
}
|
|
1069
1053
|
|
|
@@ -1157,14 +1141,13 @@ export default class ReactNativeBleTransport {
|
|
|
1157
1141
|
}
|
|
1158
1142
|
);
|
|
1159
1143
|
} else if (name === 'FirmwareUpload') {
|
|
1160
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
1144
|
+
Log?.debug('[ReactNativeBleTransport] Firmware upload transport configured', {
|
|
1161
1145
|
packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
|
|
1162
1146
|
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1163
1147
|
pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1164
1148
|
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1165
1149
|
maxRetries: FIRMWARE_UPLOAD_WRITE_MAX_RETRIES,
|
|
1166
1150
|
});
|
|
1167
|
-
|
|
1168
1151
|
await writeFirmwareUploadChunkedData(
|
|
1169
1152
|
buffers,
|
|
1170
1153
|
async data => {
|
|
@@ -1218,7 +1201,6 @@ export default class ReactNativeBleTransport {
|
|
|
1218
1201
|
for (const o of buffers) {
|
|
1219
1202
|
const outData = o.toString('base64');
|
|
1220
1203
|
// Upload resources on low-end phones may OOM
|
|
1221
|
-
// this.Log.debug('send hex strting: ', o.toString('hex'));
|
|
1222
1204
|
try {
|
|
1223
1205
|
await transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1224
1206
|
} catch (e) {
|
|
@@ -1256,7 +1238,6 @@ export default class ReactNativeBleTransport {
|
|
|
1256
1238
|
throw new Error('Returning data is not string.');
|
|
1257
1239
|
}
|
|
1258
1240
|
|
|
1259
|
-
Log?.debug('receive data: ', response);
|
|
1260
1241
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
1261
1242
|
return check.call(jsonData);
|
|
1262
1243
|
} catch (e) {
|
|
@@ -1279,7 +1260,7 @@ export default class ReactNativeBleTransport {
|
|
|
1279
1260
|
}
|
|
1280
1261
|
|
|
1281
1262
|
async disconnect(session: string) {
|
|
1282
|
-
|
|
1263
|
+
await this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
|
|
1283
1264
|
const transport = transportCache[session];
|
|
1284
1265
|
|
|
1285
1266
|
// Clean up disconnect subscription first to prevent onDisconnected callback
|
|
@@ -1341,9 +1322,6 @@ export default class ReactNativeBleTransport {
|
|
|
1341
1322
|
this.deviceProtocolHints.delete(session);
|
|
1342
1323
|
this.protocolV2Assemblers.delete(session);
|
|
1343
1324
|
this.resetProtocolV2Frames(session);
|
|
1344
|
-
if (this.activeProtocolV2Call?.uuid === session) {
|
|
1345
|
-
this.activeProtocolV2Call = null;
|
|
1346
|
-
}
|
|
1347
1325
|
|
|
1348
1326
|
// emit the disconnect event
|
|
1349
1327
|
try {
|
|
@@ -1403,37 +1381,48 @@ export default class ReactNativeBleTransport {
|
|
|
1403
1381
|
if (expectedProtocol === 'V1') {
|
|
1404
1382
|
if (await this.probeProtocolV1(uuid)) {
|
|
1405
1383
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1406
|
-
Log?.debug(
|
|
1384
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1385
|
+
deviceId: uuid,
|
|
1386
|
+
protocol: 'V1',
|
|
1387
|
+
source: 'expected',
|
|
1388
|
+
});
|
|
1407
1389
|
return 'V1';
|
|
1408
1390
|
}
|
|
1409
1391
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1410
1392
|
}
|
|
1411
1393
|
|
|
1412
1394
|
if (expectedProtocol === 'V2') {
|
|
1413
|
-
//
|
|
1414
|
-
//
|
|
1395
|
+
// Skip probing when the caller explicitly confirms V2, such as reconnect after a
|
|
1396
|
+
// firmware reboot where expectedProtocol carries the previously probed result.
|
|
1415
1397
|
this.deviceProtocol.set(uuid, 'V2');
|
|
1416
|
-
Log?.debug(
|
|
1398
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1399
|
+
deviceId: uuid,
|
|
1400
|
+
protocol: 'V2',
|
|
1401
|
+
source: 'expected',
|
|
1402
|
+
});
|
|
1417
1403
|
return 'V2';
|
|
1418
1404
|
}
|
|
1419
1405
|
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1422
|
-
// 不能作为最终结论。
|
|
1406
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
1407
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
1423
1408
|
const probeOrder: ProtocolType[] =
|
|
1424
1409
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1425
1410
|
|
|
1426
1411
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1427
1412
|
const protocol = probeOrder[i];
|
|
1428
1413
|
if (i > 0) {
|
|
1429
|
-
//
|
|
1414
|
+
// Reset subscriptions and buffers after a failed probe before trying another protocol.
|
|
1430
1415
|
await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
1431
1416
|
}
|
|
1432
1417
|
const detected =
|
|
1433
1418
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
1434
1419
|
if (detected) {
|
|
1435
1420
|
this.deviceProtocol.set(uuid, protocol);
|
|
1436
|
-
Log?.debug(
|
|
1421
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1422
|
+
deviceId: uuid,
|
|
1423
|
+
protocol,
|
|
1424
|
+
source: 'probe',
|
|
1425
|
+
});
|
|
1437
1426
|
return protocol;
|
|
1438
1427
|
}
|
|
1439
1428
|
}
|
|
@@ -1444,11 +1433,12 @@ export default class ReactNativeBleTransport {
|
|
|
1444
1433
|
|
|
1445
1434
|
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
1446
1435
|
const transport = transportCache[uuid];
|
|
1436
|
+
await this.protocolV2Links.invalidateLink(
|
|
1437
|
+
uuid,
|
|
1438
|
+
`Reset notify state after Protocol ${protocol} probe`
|
|
1439
|
+
);
|
|
1447
1440
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1448
1441
|
this.resetProtocolV2Frames(uuid);
|
|
1449
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
1450
|
-
this.activeProtocolV2Call = null;
|
|
1451
|
-
}
|
|
1452
1442
|
if (this.runPromise) {
|
|
1453
1443
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1454
1444
|
this.runPromise.reject(error);
|
|
@@ -1533,13 +1523,9 @@ export default class ReactNativeBleTransport {
|
|
|
1533
1523
|
return detected;
|
|
1534
1524
|
}
|
|
1535
1525
|
|
|
1536
|
-
private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
|
|
1526
|
+
private handleProtocolV2Notification(uuid: string, monitorToken: number, data: Uint8Array) {
|
|
1537
1527
|
try {
|
|
1538
|
-
if (
|
|
1539
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1540
|
-
this.resetProtocolV2Frames(uuid);
|
|
1541
|
-
return;
|
|
1542
|
-
}
|
|
1528
|
+
if (this.monitorTokens.get(uuid) !== monitorToken) return;
|
|
1543
1529
|
|
|
1544
1530
|
if (data.length === 0) return;
|
|
1545
1531
|
|
|
@@ -1552,8 +1538,15 @@ export default class ReactNativeBleTransport {
|
|
|
1552
1538
|
} catch (error) {
|
|
1553
1539
|
Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
|
|
1554
1540
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1555
|
-
this.
|
|
1556
|
-
this.
|
|
1541
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1542
|
+
this.protocolV2Links
|
|
1543
|
+
.invalidateLink(uuid, `Protocol V2 notification error: ${error}`)
|
|
1544
|
+
.catch(invalidateError =>
|
|
1545
|
+
Log?.debug(
|
|
1546
|
+
'[ReactNativeBleTransport] Protocol V2 notify cleanup failed:',
|
|
1547
|
+
invalidateError
|
|
1548
|
+
)
|
|
1549
|
+
);
|
|
1557
1550
|
}
|
|
1558
1551
|
}
|
|
1559
1552
|
|
|
@@ -1589,8 +1582,13 @@ export default class ReactNativeBleTransport {
|
|
|
1589
1582
|
this.protocolV2FramePromises.delete(uuid);
|
|
1590
1583
|
}
|
|
1591
1584
|
|
|
1592
|
-
private
|
|
1593
|
-
|
|
1585
|
+
private rejectProtocolV2Frames(uuid: string, error: Error) {
|
|
1586
|
+
this.protocolV2FrameQueues.delete(uuid);
|
|
1587
|
+
const framePromise = this.protocolV2FramePromises.get(uuid);
|
|
1588
|
+
if (framePromise) {
|
|
1589
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1590
|
+
framePromise.reject(error);
|
|
1591
|
+
}
|
|
1594
1592
|
}
|
|
1595
1593
|
|
|
1596
1594
|
private async readProtocolV2Frame(uuid: string) {
|
|
@@ -1610,11 +1608,7 @@ export default class ReactNativeBleTransport {
|
|
|
1610
1608
|
}
|
|
1611
1609
|
}
|
|
1612
1610
|
|
|
1613
|
-
private async writeProtocolV2Frame(
|
|
1614
|
-
transport: BleTransport,
|
|
1615
|
-
frame: Uint8Array,
|
|
1616
|
-
options?: { highVolume?: boolean; writeWithResponse?: boolean }
|
|
1617
|
-
) {
|
|
1611
|
+
private async writeProtocolV2Frame(transport: BleTransport, frame: Uint8Array) {
|
|
1618
1612
|
const tuning = getProtocolV2BleTuning();
|
|
1619
1613
|
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
1620
1614
|
platform: Platform.OS,
|
|
@@ -1622,51 +1616,10 @@ export default class ReactNativeBleTransport {
|
|
|
1622
1616
|
androidPacketLength: tuning.androidPacketLength,
|
|
1623
1617
|
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1624
1618
|
});
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
transport.writeCharacteristic
|
|
1629
|
-
writeWithResponse ? 'withResponse' : 'withoutResponse'
|
|
1630
|
-
);
|
|
1631
|
-
const shouldThrottle = !!options?.highVolume && writeMode === 'withoutResponse';
|
|
1632
|
-
let packetsWritten = 0;
|
|
1633
|
-
|
|
1634
|
-
try {
|
|
1635
|
-
for (let offset = 0; offset < frame.length; offset += packetCapacity) {
|
|
1636
|
-
const chunk = frame.slice(offset, offset + packetCapacity);
|
|
1637
|
-
const base64 = Buffer.from(chunk).toString('base64');
|
|
1638
|
-
if (writeMode === 'withResponse') {
|
|
1639
|
-
await transport.writeCharacteristic.writeWithResponse(base64);
|
|
1640
|
-
} else {
|
|
1641
|
-
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
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;
|
|
1619
|
+
for (let offset = 0; offset < frame.length; offset += packetCapacity) {
|
|
1620
|
+
const chunk = frame.slice(offset, offset + packetCapacity);
|
|
1621
|
+
const base64 = Buffer.from(chunk).toString('base64');
|
|
1622
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1670
1623
|
}
|
|
1671
1624
|
}
|
|
1672
1625
|
|
|
@@ -1680,27 +1633,6 @@ export default class ReactNativeBleTransport {
|
|
|
1680
1633
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1681
1634
|
}
|
|
1682
1635
|
|
|
1683
|
-
const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'Ping';
|
|
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
1636
|
const callOptions = {
|
|
1705
1637
|
...options,
|
|
1706
1638
|
timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
|
|
@@ -1709,72 +1641,71 @@ export default class ReactNativeBleTransport {
|
|
|
1709
1641
|
|
|
1710
1642
|
if (highVolumeWrite) {
|
|
1711
1643
|
const tuning = getProtocolV2BleTuning();
|
|
1712
|
-
Log?.debug(
|
|
1713
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
|
|
1644
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
1714
1645
|
name,
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
burstSize: tuning.highVolumeWriteBurstSize,
|
|
1719
|
-
pauseMs: tuning.highVolumeWritePauseMs,
|
|
1720
|
-
flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
|
|
1721
|
-
writeWithResponse: tuning.highVolumeWriteWithResponse,
|
|
1722
|
-
}
|
|
1723
|
-
);
|
|
1646
|
+
writeMode: 'withoutResponse',
|
|
1647
|
+
packetCapacity: Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1648
|
+
});
|
|
1724
1649
|
}
|
|
1725
1650
|
|
|
1726
1651
|
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;
|
|
1652
|
+
return await this.protocolV2Links.call(
|
|
1653
|
+
uuid,
|
|
1654
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
1655
|
+
name,
|
|
1656
|
+
data,
|
|
1657
|
+
callOptions
|
|
1658
|
+
);
|
|
1757
1659
|
} catch (e) {
|
|
1758
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1759
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1760
|
-
this.resetProtocolV2Frames(uuid);
|
|
1761
|
-
}
|
|
1762
1660
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1763
1661
|
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
1662
|
}
|
|
1776
1663
|
}
|
|
1777
1664
|
|
|
1665
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
1666
|
+
const generation = this.monitorTokens.get(uuid) ?? 0;
|
|
1667
|
+
const assertCurrentGeneration = () => {
|
|
1668
|
+
if (this.monitorTokens.get(uuid) !== generation) {
|
|
1669
|
+
throw new Error(`Protocol V2 monitor generation changed for ${uuid}`);
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
return {
|
|
1674
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1675
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1676
|
+
generation,
|
|
1677
|
+
prepareCall: () => {
|
|
1678
|
+
assertCurrentGeneration();
|
|
1679
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1680
|
+
this.resetProtocolV2Frames(uuid);
|
|
1681
|
+
},
|
|
1682
|
+
writeFrame: async (frame: Uint8Array) => {
|
|
1683
|
+
assertCurrentGeneration();
|
|
1684
|
+
const currentTransport = this.getCachedTransport(uuid);
|
|
1685
|
+
await this.writeProtocolV2Frame(currentTransport, frame);
|
|
1686
|
+
},
|
|
1687
|
+
readFrame: async () => {
|
|
1688
|
+
assertCurrentGeneration();
|
|
1689
|
+
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1690
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1691
|
+
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1692
|
+
}
|
|
1693
|
+
return rxFrame;
|
|
1694
|
+
},
|
|
1695
|
+
reset: (reason: string) => {
|
|
1696
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1697
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
1698
|
+
},
|
|
1699
|
+
logger: Log,
|
|
1700
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1701
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
1702
|
+
ERRORS.TypedError(
|
|
1703
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1704
|
+
`BLE response timeout after ${timeout}ms for ${messageName}`
|
|
1705
|
+
),
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1778
1709
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
1779
1710
|
return this.deviceProtocol.get(path);
|
|
1780
1711
|
}
|