@onekeyfe/hd-transport-react-native 1.2.0-alpha.68 → 1.2.0-alpha.69
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 -1
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/bleStrategy.d.ts +6 -0
- package/dist/bleStrategy.d.ts.map +1 -1
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +9 -80
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +210 -341
- package/jest.config.js +0 -5
- package/package.json +5 -5
- package/src/BleTransport.ts +1 -1
- package/src/__tests__/bleStrategy.test.ts +77 -7
- package/src/__tests__/protocolV2Link.test.ts +121 -28
- package/src/bleStrategy.ts +23 -10
- package/src/constants.ts +2 -1
- package/src/index.ts +229 -486
- package/src/__tests__/connectTimeout.test.ts +0 -281
- package/src/__tests__/protocolReprobe.test.ts +0 -132
- package/src/__tests__/protocolV1SchemaFixture.ts +0 -39
- package/src/__tests__/staleCallTimeout.test.ts +0 -210
- package/src/__tests__/writePacketTimeout.test.ts +0 -305
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
BleError,
|
|
6
6
|
BleErrorCode,
|
|
7
7
|
BleManager as BlePlxManager,
|
|
8
|
+
ConnectionPriority,
|
|
8
9
|
ScanMode,
|
|
9
10
|
} from 'react-native-ble-plx';
|
|
10
11
|
import ByteBuffer from 'bytebuffer';
|
|
@@ -32,11 +33,17 @@ import {
|
|
|
32
33
|
} from '@onekeyfe/hd-shared';
|
|
33
34
|
|
|
34
35
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
35
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
hasWritableCapability,
|
|
38
|
+
resolveProtocolV2PacketCapacity,
|
|
39
|
+
shouldWriteProtocolV2WithResponse,
|
|
40
|
+
} from './bleStrategy';
|
|
36
41
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
37
42
|
import {
|
|
38
43
|
ANDROID_PACKET_LENGTH,
|
|
44
|
+
ANDROID_PROTOCOL_V2_PACKET_LENGTH,
|
|
39
45
|
IOS_PACKET_LENGTH,
|
|
46
|
+
IOS_PROTOCOL_V2_PACKET_LENGTH,
|
|
40
47
|
getBluetoothServiceUuids,
|
|
41
48
|
getInfosForServiceUuid,
|
|
42
49
|
isSameBleUuid,
|
|
@@ -61,7 +68,6 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
|
61
68
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
|
|
62
69
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
|
|
63
70
|
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
64
|
-
const IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS = 5;
|
|
65
71
|
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
66
72
|
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY =
|
|
67
73
|
Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
@@ -135,22 +141,6 @@ const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, max
|
|
|
135
141
|
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
136
142
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
137
143
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
138
|
-
/**
|
|
139
|
-
* Per-packet write budget. iOS only resolves writeWithoutResponse once CoreBluetooth
|
|
140
|
-
* reports the peripheral ready again; a peripheral wedged by its own firmware reboot
|
|
141
|
-
* stops reporting ready while staying connected, so the write promise never settles.
|
|
142
|
-
* Response timeouts cannot cover that — they are armed after the writes complete —
|
|
143
|
-
* and an unbounded write leaves the whole transport unusable until the process dies.
|
|
144
|
-
* A healthy packet completes in milliseconds, so this only fires on a dead link.
|
|
145
|
-
*/
|
|
146
|
-
export const BLE_WRITE_PACKET_TIMEOUT_MS = 10_000;
|
|
147
|
-
const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
|
|
148
|
-
const isWedgedWriteError = (error: unknown): boolean =>
|
|
149
|
-
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleWriteCharacteristicError &&
|
|
150
|
-
typeof (error as { message?: unknown })?.message === 'string' &&
|
|
151
|
-
(error as { message: string }).message.startsWith(WEDGED_WRITE_MESSAGE);
|
|
152
|
-
/** Consecutive wedged writes on one device before the BLE manager itself is recreated. */
|
|
153
|
-
export const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
154
144
|
const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
155
145
|
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
156
146
|
const ANDROID_NOTIFY_READY_DELAY_MS = 300;
|
|
@@ -162,8 +152,8 @@ export type ProtocolV2BleTuning = {
|
|
|
162
152
|
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
163
153
|
|
|
164
154
|
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
165
|
-
iosPacketLength:
|
|
166
|
-
androidPacketLength:
|
|
155
|
+
iosPacketLength: IOS_PROTOCOL_V2_PACKET_LENGTH,
|
|
156
|
+
androidPacketLength: ANDROID_PROTOCOL_V2_PACKET_LENGTH,
|
|
167
157
|
};
|
|
168
158
|
|
|
169
159
|
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
@@ -205,54 +195,21 @@ function getDeviceDisplayName(device?: Device | null) {
|
|
|
205
195
|
return device?.name || device?.localName || null;
|
|
206
196
|
}
|
|
207
197
|
|
|
208
|
-
const
|
|
198
|
+
const IOS_REQUEST_MTU = 247;
|
|
199
|
+
const ANDROID_REQUEST_MTU = 517;
|
|
200
|
+
const BLE_MTU_REFRESH_THRESHOLD = 247;
|
|
201
|
+
const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
|
|
202
|
+
const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
|
|
209
203
|
|
|
210
|
-
const
|
|
204
|
+
const getRequestedBleMtu = () =>
|
|
205
|
+
Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
|
|
211
206
|
|
|
212
207
|
const connectOptions: Record<string, unknown> = {
|
|
213
|
-
requestMTU:
|
|
214
|
-
timeout:
|
|
208
|
+
requestMTU: getRequestedBleMtu(),
|
|
209
|
+
timeout: 3000,
|
|
215
210
|
refreshGatt: 'OnConnected',
|
|
216
211
|
};
|
|
217
212
|
|
|
218
|
-
/** Fallback connect options: drops requestMTU (the thing being worked around) but keeps the native budget. */
|
|
219
|
-
const fallbackConnectOptions: Record<string, unknown> = {
|
|
220
|
-
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* JS backstop for connect. The native adapter applies its own 3s budget, but it
|
|
225
|
-
* schedules that timeout on its serial queue, so a busy queue (e.g. right after a
|
|
226
|
-
* firmware install tears the link down) can leave the promise unsettled — observed
|
|
227
|
-
* blocking a reconnect for 61s until the app-level timeout. Healthy connects finish
|
|
228
|
-
* inside the native budget, so this only fires when the native timeout did not.
|
|
229
|
-
*/
|
|
230
|
-
export const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
|
|
231
|
-
/**
|
|
232
|
-
* Service discovery and characteristic resolution run after connect() succeeds, but
|
|
233
|
-
* CoreBluetooth schedules them on the same serial queue. If that queue is wedged by a
|
|
234
|
-
* device reboot, these calls can remain pending forever unless they have their own
|
|
235
|
-
* budget.
|
|
236
|
-
*/
|
|
237
|
-
export const BLE_GATT_SETUP_TIMEOUT_MS = 10_000;
|
|
238
|
-
/**
|
|
239
|
-
* How many times a known device may fail its own protocol before we probe the others
|
|
240
|
-
* again. Reconnect polling during a device reboot repeats this every few seconds, and
|
|
241
|
-
* probing Protocol V2 costs a 10s Ping timeout, so paying it on every attempt for a
|
|
242
|
-
* device we just spoke V1 to dominates the wait. A firmware update can legitimately
|
|
243
|
-
* change a device's protocol, so the shortcut has to expire rather than stick.
|
|
244
|
-
*/
|
|
245
|
-
export const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
|
|
246
|
-
/** BLE setup timeouts since the last successful setup before the manager is recreated. */
|
|
247
|
-
export const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
248
|
-
const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
|
|
249
|
-
const isConnectTimeoutError = (error: unknown): boolean =>
|
|
250
|
-
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleConnectedError &&
|
|
251
|
-
typeof (error as { message?: unknown })?.message === 'string' &&
|
|
252
|
-
(error as { message: string }).message.startsWith(CONNECT_TIMEOUT_MESSAGE);
|
|
253
|
-
const isNativeOperationTimeoutError = (error: unknown): boolean =>
|
|
254
|
-
(error as { errorCode?: unknown })?.errorCode === BleErrorCode.OperationTimedOut;
|
|
255
|
-
|
|
256
213
|
export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
257
214
|
|
|
258
215
|
const tryToGetConfiguration = (device: Device) => {
|
|
@@ -264,23 +221,32 @@ const tryToGetConfiguration = (device: Device) => {
|
|
|
264
221
|
return infos;
|
|
265
222
|
};
|
|
266
223
|
|
|
267
|
-
const
|
|
268
|
-
|
|
224
|
+
const requestNegotiatedMtu = async (
|
|
225
|
+
device: Device,
|
|
226
|
+
stage: 'connected' | 'servicesAndNotifyReady',
|
|
227
|
+
attempt: number
|
|
228
|
+
) => {
|
|
229
|
+
if (Platform.OS !== 'ios' && Platform.OS !== 'android') return device;
|
|
269
230
|
|
|
270
231
|
try {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
requested: ANDROID_REQUEST_MTU,
|
|
275
|
-
actual: mtuDevice.mtu,
|
|
276
|
-
});
|
|
232
|
+
// iOS ignores the requested value but react-native-ble-plx returns a fresh
|
|
233
|
+
// Device snapshot whose MTU is derived from CoreBluetooth's maximum write length.
|
|
234
|
+
const mtuDevice = await device.requestMTU(getRequestedBleMtu());
|
|
277
235
|
return mtuDevice;
|
|
278
236
|
} catch (error) {
|
|
279
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
237
|
+
Log?.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
|
|
238
|
+
platform: Platform.OS,
|
|
239
|
+
stage,
|
|
240
|
+
attempt,
|
|
241
|
+
actual: device.mtu,
|
|
242
|
+
error: error instanceof Error ? error.message : String(error),
|
|
243
|
+
});
|
|
280
244
|
return device;
|
|
281
245
|
}
|
|
282
246
|
};
|
|
283
247
|
|
|
248
|
+
const resolveNegotiatedMtu = (device: Device) => requestNegotiatedMtu(device, 'connected', 0);
|
|
249
|
+
|
|
284
250
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
285
251
|
|
|
286
252
|
function remapError(error: IOBleErrorRemap) {
|
|
@@ -341,28 +307,8 @@ export default class ReactNativeBleTransport {
|
|
|
341
307
|
/** Per-device protocol type detected by active wire-level probe after connect. */
|
|
342
308
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
343
309
|
|
|
344
|
-
/**
|
|
345
|
-
* Protocol a probe is currently trying, before the device has confirmed it. Calls
|
|
346
|
-
* must route with it, but acquire() must not treat it as a detected protocol: a
|
|
347
|
-
* probe that never answers would otherwise leave the reuse fast path handing out a
|
|
348
|
-
* transport that was never validated.
|
|
349
|
-
*/
|
|
350
|
-
private probingProtocols: Map<string, ProtocolType> = new Map();
|
|
351
|
-
|
|
352
|
-
/** Consecutive write timeouts per device; reset by any write that completes. */
|
|
353
|
-
private writeTimeoutCounts: Map<string, number> = new Map();
|
|
354
|
-
|
|
355
|
-
/** BLE setup timeouts per device since the last complete characteristic resolution. */
|
|
356
|
-
private connectionSetupTimeoutCounts: Map<string, number> = new Map();
|
|
357
|
-
|
|
358
310
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
359
311
|
|
|
360
|
-
/** Protocol this device actually answered on, kept across reconnects of one session. */
|
|
361
|
-
private sessionProtocols: Map<string, ProtocolType> = new Map();
|
|
362
|
-
|
|
363
|
-
/** Consecutive detections that failed while trusting sessionProtocols. */
|
|
364
|
-
private protocolReprobeFailures: Map<string, number> = new Map();
|
|
365
|
-
|
|
366
312
|
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
367
313
|
|
|
368
314
|
private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
|
|
@@ -394,6 +340,12 @@ export default class ReactNativeBleTransport {
|
|
|
394
340
|
|
|
395
341
|
private disconnectEventTokens: Map<string, number> = new Map();
|
|
396
342
|
|
|
343
|
+
private protocolV2HighVolumeLogSignatures: Map<string, Set<string>> = new Map();
|
|
344
|
+
|
|
345
|
+
private androidHighPriorityDevices: Set<string> = new Set();
|
|
346
|
+
|
|
347
|
+
private androidPriorityResetTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
|
348
|
+
|
|
397
349
|
private nextMonitorToken = 1;
|
|
398
350
|
|
|
399
351
|
constructor(options: TransportOptions) {
|
|
@@ -580,21 +532,22 @@ export default class ReactNativeBleTransport {
|
|
|
580
532
|
const isConnected = await device.isConnected().catch(() => false);
|
|
581
533
|
if (!isConnected) {
|
|
582
534
|
try {
|
|
583
|
-
device = await
|
|
535
|
+
device = await device.connect(connectOptions);
|
|
584
536
|
} catch (e) {
|
|
585
537
|
if (
|
|
586
538
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
587
539
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
588
540
|
) {
|
|
589
|
-
device = await
|
|
541
|
+
device = await device.connect();
|
|
590
542
|
} else if (e.errorCode !== BleErrorCode.DeviceAlreadyConnected) {
|
|
591
543
|
throw e;
|
|
592
544
|
}
|
|
593
545
|
}
|
|
594
546
|
}
|
|
595
547
|
|
|
596
|
-
const { writeCharacteristic, notifyCharacteristic } =
|
|
597
|
-
|
|
548
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(
|
|
549
|
+
device
|
|
550
|
+
);
|
|
598
551
|
|
|
599
552
|
transport.device = device;
|
|
600
553
|
transport.writeCharacteristic = writeCharacteristic;
|
|
@@ -773,11 +726,9 @@ export default class ReactNativeBleTransport {
|
|
|
773
726
|
characteristics?: ResolvedBleCharacteristics
|
|
774
727
|
) {
|
|
775
728
|
const { writeCharacteristic, notifyCharacteristic } =
|
|
776
|
-
characteristics ?? (await this.
|
|
729
|
+
characteristics ?? (await this.resolveCharacteristics(device));
|
|
777
730
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
778
|
-
|
|
779
|
-
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
780
|
-
}
|
|
731
|
+
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
|
|
781
732
|
const monitorToken = this.nextMonitorToken;
|
|
782
733
|
this.nextMonitorToken += 1;
|
|
783
734
|
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
@@ -791,6 +742,7 @@ export default class ReactNativeBleTransport {
|
|
|
791
742
|
notifyTransactionId
|
|
792
743
|
);
|
|
793
744
|
transportCache[uuid] = transport;
|
|
745
|
+
this.protocolV2HighVolumeLogSignatures.set(uuid, new Set());
|
|
794
746
|
this.protocolV2Assemblers.set(
|
|
795
747
|
uuid,
|
|
796
748
|
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
@@ -804,6 +756,40 @@ export default class ReactNativeBleTransport {
|
|
|
804
756
|
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
805
757
|
}
|
|
806
758
|
|
|
759
|
+
const initialMtu = transport.mtuSize;
|
|
760
|
+
let refreshAttempts = 0;
|
|
761
|
+
if (
|
|
762
|
+
(Platform.OS === 'ios' || Platform.OS === 'android') &&
|
|
763
|
+
(typeof transport.mtuSize !== 'number' || transport.mtuSize < BLE_MTU_REFRESH_THRESHOLD)
|
|
764
|
+
) {
|
|
765
|
+
refreshAttempts += 1;
|
|
766
|
+
let refreshedDevice = await requestNegotiatedMtu(
|
|
767
|
+
transport.device,
|
|
768
|
+
'servicesAndNotifyReady',
|
|
769
|
+
1
|
|
770
|
+
);
|
|
771
|
+
transport.device = refreshedDevice;
|
|
772
|
+
transport.mtuSize =
|
|
773
|
+
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
774
|
+
|
|
775
|
+
if (typeof transport.mtuSize !== 'number' || transport.mtuSize < BLE_MTU_REFRESH_THRESHOLD) {
|
|
776
|
+
await delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
|
|
777
|
+
refreshAttempts += 1;
|
|
778
|
+
refreshedDevice = await requestNegotiatedMtu(transport.device, 'servicesAndNotifyReady', 2);
|
|
779
|
+
transport.device = refreshedDevice;
|
|
780
|
+
transport.mtuSize =
|
|
781
|
+
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
Log?.debug('[ReactNativeBleTransport] BLE MTU ready', {
|
|
786
|
+
platform: Platform.OS,
|
|
787
|
+
requested: getRequestedBleMtu(),
|
|
788
|
+
initial: initialMtu,
|
|
789
|
+
actual: transport.mtuSize,
|
|
790
|
+
refreshAttempts,
|
|
791
|
+
});
|
|
792
|
+
|
|
807
793
|
return transport;
|
|
808
794
|
}
|
|
809
795
|
|
|
@@ -877,22 +863,15 @@ export default class ReactNativeBleTransport {
|
|
|
877
863
|
if (!device) {
|
|
878
864
|
Log?.debug('try to connect to device: ', uuid);
|
|
879
865
|
try {
|
|
880
|
-
device = await
|
|
881
|
-
blePlxManager.connectToDevice(uuid, connectOptions)
|
|
882
|
-
);
|
|
866
|
+
device = await blePlxManager.connectToDevice(uuid, connectOptions);
|
|
883
867
|
} catch (e) {
|
|
884
868
|
Log?.debug('try to connect to device has error: ', e);
|
|
885
|
-
if (isConnectTimeoutError(e)) {
|
|
886
|
-
throw e;
|
|
887
|
-
}
|
|
888
869
|
if (
|
|
889
870
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
890
871
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
891
872
|
) {
|
|
892
873
|
Log?.debug('first try to reconnect without params');
|
|
893
|
-
device = await
|
|
894
|
-
blePlxManager.connectToDevice(uuid, fallbackConnectOptions)
|
|
895
|
-
);
|
|
874
|
+
device = await blePlxManager.connectToDevice(uuid);
|
|
896
875
|
} else if (e.errorCode === BleErrorCode.DeviceAlreadyConnected) {
|
|
897
876
|
Log?.debug('device already connected');
|
|
898
877
|
throw ERRORS.TypedError(HardwareErrorCode.BleAlreadyConnected);
|
|
@@ -908,36 +887,26 @@ export default class ReactNativeBleTransport {
|
|
|
908
887
|
|
|
909
888
|
if (!(await device.isConnected())) {
|
|
910
889
|
Log?.debug('not connected, try to connect to device: ', uuid);
|
|
911
|
-
const disconnectedDevice = device;
|
|
912
890
|
|
|
913
891
|
try {
|
|
914
|
-
device = await
|
|
915
|
-
disconnectedDevice.connect(connectOptions)
|
|
916
|
-
);
|
|
892
|
+
device = await device.connect(connectOptions);
|
|
917
893
|
} catch (e) {
|
|
918
894
|
Log?.debug('not connected, try to connect to device has error: ', e);
|
|
919
|
-
if (isConnectTimeoutError(e)) {
|
|
920
|
-
throw e;
|
|
921
|
-
}
|
|
922
895
|
if (
|
|
923
896
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
924
897
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
925
898
|
) {
|
|
926
899
|
Log?.debug('second try to reconnect without params');
|
|
927
900
|
try {
|
|
928
|
-
device = await
|
|
929
|
-
disconnectedDevice.connect(fallbackConnectOptions)
|
|
930
|
-
);
|
|
901
|
+
device = await device.connect();
|
|
931
902
|
} catch (e) {
|
|
932
903
|
Log?.debug('last try to reconnect error: ', e);
|
|
933
904
|
// last try to reconnect device if this issue exists
|
|
934
905
|
// https://github.com/dotintent/react-native-ble-plx/issues/426
|
|
935
906
|
if (e.errorCode === BleErrorCode.OperationCancelled) {
|
|
936
907
|
Log?.debug('last try to reconnect');
|
|
937
|
-
await
|
|
938
|
-
device = await
|
|
939
|
-
disconnectedDevice.connect(fallbackConnectOptions)
|
|
940
|
-
);
|
|
908
|
+
await device.cancelConnection();
|
|
909
|
+
device = await device.connect();
|
|
941
910
|
}
|
|
942
911
|
}
|
|
943
912
|
} else {
|
|
@@ -946,10 +915,11 @@ export default class ReactNativeBleTransport {
|
|
|
946
915
|
}
|
|
947
916
|
}
|
|
948
917
|
|
|
949
|
-
device = await
|
|
918
|
+
device = await resolveNegotiatedMtu(device);
|
|
950
919
|
const acquiredDevice = device;
|
|
951
|
-
const { writeCharacteristic, notifyCharacteristic } =
|
|
952
|
-
|
|
920
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(
|
|
921
|
+
acquiredDevice
|
|
922
|
+
);
|
|
953
923
|
|
|
954
924
|
const protocolHint = expectedProtocol
|
|
955
925
|
? undefined
|
|
@@ -981,7 +951,7 @@ export default class ReactNativeBleTransport {
|
|
|
981
951
|
if (!currentTransport) {
|
|
982
952
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
983
953
|
}
|
|
984
|
-
this.attachDisconnectSubscription(currentTransport,
|
|
954
|
+
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
985
955
|
return { uuid, protocolType };
|
|
986
956
|
} catch (error) {
|
|
987
957
|
await this.release(uuid, true);
|
|
@@ -1013,7 +983,7 @@ export default class ReactNativeBleTransport {
|
|
|
1013
983
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
1014
984
|
return;
|
|
1015
985
|
}
|
|
1016
|
-
if (this.
|
|
986
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
1017
987
|
let errorCode:
|
|
1018
988
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
1019
989
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
@@ -1083,7 +1053,7 @@ export default class ReactNativeBleTransport {
|
|
|
1083
1053
|
|
|
1084
1054
|
try {
|
|
1085
1055
|
const data = Buffer.from(c.value as string, 'base64');
|
|
1086
|
-
const protocol = this.
|
|
1056
|
+
const protocol = this.deviceProtocol.get(uuid);
|
|
1087
1057
|
if (!protocol) {
|
|
1088
1058
|
Log?.debug('monitor data ignored before protocol detection: ', uuid);
|
|
1089
1059
|
return;
|
|
@@ -1117,7 +1087,7 @@ export default class ReactNativeBleTransport {
|
|
|
1117
1087
|
} catch (error) {
|
|
1118
1088
|
Log?.debug('monitor data error: ', error);
|
|
1119
1089
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1120
|
-
if (this.
|
|
1090
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
1121
1091
|
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1122
1092
|
} else if (this.runPromiseDeviceId === uuid) {
|
|
1123
1093
|
this.runPromise?.reject(notifyError);
|
|
@@ -1151,6 +1121,8 @@ export default class ReactNativeBleTransport {
|
|
|
1151
1121
|
return Promise.resolve(true);
|
|
1152
1122
|
}
|
|
1153
1123
|
|
|
1124
|
+
await this.restoreAndroidConnectionPriority(uuid, transport);
|
|
1125
|
+
|
|
1154
1126
|
if (transport) {
|
|
1155
1127
|
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1156
1128
|
this.monitorTokens.delete(uuid);
|
|
@@ -1180,8 +1152,9 @@ export default class ReactNativeBleTransport {
|
|
|
1180
1152
|
delete transportCache[uuid];
|
|
1181
1153
|
}
|
|
1182
1154
|
|
|
1155
|
+
this.protocolV2HighVolumeLogSignatures.delete(uuid);
|
|
1156
|
+
|
|
1183
1157
|
this.deviceProtocol.delete(uuid);
|
|
1184
|
-
this.probingProtocols.delete(uuid);
|
|
1185
1158
|
// Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
|
|
1186
1159
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1187
1160
|
this.protocolV2Assemblers.delete(uuid);
|
|
@@ -1248,25 +1221,8 @@ export default class ReactNativeBleTransport {
|
|
|
1248
1221
|
const transport = this.getCachedTransport(uuid);
|
|
1249
1222
|
const runPromise = createDeferred<string>();
|
|
1250
1223
|
runPromise.promise.catch(() => undefined);
|
|
1251
|
-
const supersededRunPromise = this.runPromise;
|
|
1252
|
-
if (supersededRunPromise) {
|
|
1253
|
-
// Only forceRun calls (Initialize/Cancel) reach here with a pending call. Settle
|
|
1254
|
-
// the superseded deferred now so its response race resolves and its finally block
|
|
1255
|
-
// clears its timeout timer; an orphaned timer would otherwise fire much later and
|
|
1256
|
-
// tear down the shared connection while another call is using it.
|
|
1257
|
-
supersededRunPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
|
|
1258
|
-
}
|
|
1259
1224
|
this.runPromise = runPromise;
|
|
1260
1225
|
this.runPromiseDeviceId = uuid;
|
|
1261
|
-
// A superseded call's late write failure must not clear the successor's ownership;
|
|
1262
|
-
// only the call that still owns the slot may release it.
|
|
1263
|
-
const releaseOwnershipIfCurrent = () => {
|
|
1264
|
-
if (this.runPromise === runPromise) {
|
|
1265
|
-
this.runPromise = null;
|
|
1266
|
-
this.runPromiseDeviceId = null;
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
1269
|
-
const isCurrentOwner = () => this.runPromise === runPromise;
|
|
1270
1226
|
const messages = this._messages;
|
|
1271
1227
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1272
1228
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -1292,9 +1248,6 @@ export default class ReactNativeBleTransport {
|
|
|
1292
1248
|
chunk = ByteBuffer.allocate(packetCapacity);
|
|
1293
1249
|
} catch (e) {
|
|
1294
1250
|
onError(e);
|
|
1295
|
-
if (isWedgedWriteError(e)) {
|
|
1296
|
-
throw e;
|
|
1297
|
-
}
|
|
1298
1251
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1299
1252
|
}
|
|
1300
1253
|
}
|
|
@@ -1326,9 +1279,6 @@ export default class ReactNativeBleTransport {
|
|
|
1326
1279
|
}
|
|
1327
1280
|
} catch (e) {
|
|
1328
1281
|
onError(e);
|
|
1329
|
-
if (isWedgedWriteError(e)) {
|
|
1330
|
-
throw e;
|
|
1331
|
-
}
|
|
1332
1282
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1333
1283
|
}
|
|
1334
1284
|
}
|
|
@@ -1342,15 +1292,9 @@ export default class ReactNativeBleTransport {
|
|
|
1342
1292
|
if (name === 'EmmcFileWrite') {
|
|
1343
1293
|
await writeChunkedData(
|
|
1344
1294
|
buffers,
|
|
1345
|
-
data =>
|
|
1346
|
-
this.writeBlePacket(
|
|
1347
|
-
uuid,
|
|
1348
|
-
data,
|
|
1349
|
-
payload => transport.writeWithRetry(payload),
|
|
1350
|
-
isCurrentOwner
|
|
1351
|
-
),
|
|
1295
|
+
data => transport.writeWithRetry(data),
|
|
1352
1296
|
e => {
|
|
1353
|
-
|
|
1297
|
+
this.runPromise = null;
|
|
1354
1298
|
Log?.error('writeCharacteristic write error: ', e);
|
|
1355
1299
|
}
|
|
1356
1300
|
);
|
|
@@ -1371,12 +1315,7 @@ export default class ReactNativeBleTransport {
|
|
|
1371
1315
|
// eslint-disable-next-line no-constant-condition
|
|
1372
1316
|
while (true) {
|
|
1373
1317
|
try {
|
|
1374
|
-
await
|
|
1375
|
-
uuid,
|
|
1376
|
-
data,
|
|
1377
|
-
payload => transport.writeWithRetry(payload),
|
|
1378
|
-
isCurrentOwner
|
|
1379
|
-
);
|
|
1318
|
+
await transport.writeWithRetry(data);
|
|
1380
1319
|
return;
|
|
1381
1320
|
} catch (error) {
|
|
1382
1321
|
const retryType = getFirmwareUploadWriteRetryType(error);
|
|
@@ -1395,7 +1334,7 @@ export default class ReactNativeBleTransport {
|
|
|
1395
1334
|
}
|
|
1396
1335
|
},
|
|
1397
1336
|
e => {
|
|
1398
|
-
|
|
1337
|
+
this.runPromise = null;
|
|
1399
1338
|
Log?.error('writeCharacteristic write error: ', e);
|
|
1400
1339
|
}
|
|
1401
1340
|
);
|
|
@@ -1406,21 +1345,14 @@ export default class ReactNativeBleTransport {
|
|
|
1406
1345
|
try {
|
|
1407
1346
|
const shouldUseWriteWithResponse =
|
|
1408
1347
|
Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
? transport.writeCharacteristic.writeWithResponse(payload)
|
|
1415
|
-
: transport.writeCharacteristic.writeWithoutResponse(payload),
|
|
1416
|
-
isCurrentOwner
|
|
1417
|
-
);
|
|
1348
|
+
if (shouldUseWriteWithResponse) {
|
|
1349
|
+
await transport.writeCharacteristic.writeWithResponse(outData);
|
|
1350
|
+
} else {
|
|
1351
|
+
await transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1352
|
+
}
|
|
1418
1353
|
} catch (e) {
|
|
1419
1354
|
Log?.debug('writeCharacteristic write error: ', e);
|
|
1420
|
-
|
|
1421
|
-
if (isWedgedWriteError(e)) {
|
|
1422
|
-
throw e;
|
|
1423
|
-
}
|
|
1355
|
+
this.runPromise = null;
|
|
1424
1356
|
if (e.errorCode === BleErrorCode.DeviceDisconnected) {
|
|
1425
1357
|
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded);
|
|
1426
1358
|
} else if (e.errorCode === BleErrorCode.OperationStartFailed) {
|
|
@@ -1463,13 +1395,8 @@ export default class ReactNativeBleTransport {
|
|
|
1463
1395
|
}
|
|
1464
1396
|
const isProbeTimeout =
|
|
1465
1397
|
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1466
|
-
// A call that has been superseded (forceRun) or cleaned up no longer owns the
|
|
1467
|
-
// transport; its late timeout must not tear down the connection the current
|
|
1468
|
-
// call is actively using.
|
|
1469
|
-
const isStaleCall = this.runPromise !== runPromise;
|
|
1470
1398
|
if (
|
|
1471
1399
|
!isProbeTimeout &&
|
|
1472
|
-
!isStaleCall &&
|
|
1473
1400
|
(e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError
|
|
1474
1401
|
) {
|
|
1475
1402
|
await this.disconnect(uuid);
|
|
@@ -1549,10 +1476,7 @@ export default class ReactNativeBleTransport {
|
|
|
1549
1476
|
delete transportCache[session];
|
|
1550
1477
|
}
|
|
1551
1478
|
this.deviceProtocol.delete(session);
|
|
1552
|
-
this.probingProtocols.delete(session);
|
|
1553
1479
|
this.deviceProtocolHints.delete(session);
|
|
1554
|
-
this.sessionProtocols.delete(session);
|
|
1555
|
-
this.protocolReprobeFailures.delete(session);
|
|
1556
1480
|
this.protocolV2Assemblers.delete(session);
|
|
1557
1481
|
this.resetProtocolV2Frames(session);
|
|
1558
1482
|
|
|
@@ -1578,113 +1502,6 @@ export default class ReactNativeBleTransport {
|
|
|
1578
1502
|
this.runPromiseDeviceId = null;
|
|
1579
1503
|
}
|
|
1580
1504
|
|
|
1581
|
-
/** Run a native connect under the JS backstop budget. */
|
|
1582
|
-
private async connectWithTimeout<T>(uuid: string, connect: () => Promise<T>): Promise<T> {
|
|
1583
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1584
|
-
let timedOut = false;
|
|
1585
|
-
const pending = connect();
|
|
1586
|
-
// The abandoned attempt keeps running; swallow its late outcome so it cannot
|
|
1587
|
-
// surface as an unhandled rejection after we have already given up on it.
|
|
1588
|
-
pending.catch(() => undefined);
|
|
1589
|
-
try {
|
|
1590
|
-
const result = await Promise.race([
|
|
1591
|
-
pending,
|
|
1592
|
-
new Promise<never>((_, reject) => {
|
|
1593
|
-
timer = setTimeout(() => {
|
|
1594
|
-
timedOut = true;
|
|
1595
|
-
reject(
|
|
1596
|
-
ERRORS.TypedError(
|
|
1597
|
-
HardwareErrorCode.BleConnectedError,
|
|
1598
|
-
`BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`
|
|
1599
|
-
)
|
|
1600
|
-
);
|
|
1601
|
-
}, BLE_CONNECT_TIMEOUT_MS);
|
|
1602
|
-
}),
|
|
1603
|
-
]);
|
|
1604
|
-
return result;
|
|
1605
|
-
} catch (error) {
|
|
1606
|
-
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1607
|
-
this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
|
|
1608
|
-
}
|
|
1609
|
-
throw error;
|
|
1610
|
-
} finally {
|
|
1611
|
-
if (timer) clearTimeout(timer);
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
/** Resolve the complete GATT shape under a budget so acquire() always settles. */
|
|
1616
|
-
private async resolveCharacteristicsWithTimeout(
|
|
1617
|
-
uuid: string,
|
|
1618
|
-
device: Device
|
|
1619
|
-
): Promise<ResolvedBleCharacteristics> {
|
|
1620
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1621
|
-
let timedOut = false;
|
|
1622
|
-
const pending = this.resolveCharacteristics(device);
|
|
1623
|
-
pending.catch(() => undefined);
|
|
1624
|
-
try {
|
|
1625
|
-
const result = await Promise.race([
|
|
1626
|
-
pending,
|
|
1627
|
-
new Promise<never>((_, reject) => {
|
|
1628
|
-
timer = setTimeout(() => {
|
|
1629
|
-
timedOut = true;
|
|
1630
|
-
reject(
|
|
1631
|
-
ERRORS.TypedError(
|
|
1632
|
-
HardwareErrorCode.BleConnectedError,
|
|
1633
|
-
`BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`
|
|
1634
|
-
)
|
|
1635
|
-
);
|
|
1636
|
-
}, BLE_GATT_SETUP_TIMEOUT_MS);
|
|
1637
|
-
}),
|
|
1638
|
-
]);
|
|
1639
|
-
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1640
|
-
return result;
|
|
1641
|
-
} catch (error) {
|
|
1642
|
-
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1643
|
-
this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
|
|
1644
|
-
}
|
|
1645
|
-
throw error;
|
|
1646
|
-
} finally {
|
|
1647
|
-
if (timer) clearTimeout(timer);
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
/**
|
|
1652
|
-
* Give up on a BLE setup operation the native layer did not settle. The abandoned
|
|
1653
|
-
* operation still owns native connection/GATT state that can poison the next attempt,
|
|
1654
|
-
* so it is cleared here without awaiting the same queue that stopped responding.
|
|
1655
|
-
*/
|
|
1656
|
-
private abandonStalledConnection(
|
|
1657
|
-
uuid: string,
|
|
1658
|
-
stage: 'connect-backstop' | 'connect-native' | 'gatt-backstop' | 'gatt-native'
|
|
1659
|
-
) {
|
|
1660
|
-
const timeouts = (this.connectionSetupTimeoutCounts.get(uuid) ?? 0) + 1;
|
|
1661
|
-
this.connectionSetupTimeoutCounts.set(uuid, timeouts);
|
|
1662
|
-
Log?.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
|
|
1663
|
-
stage,
|
|
1664
|
-
setupTimeoutsSinceSuccess: timeouts,
|
|
1665
|
-
});
|
|
1666
|
-
|
|
1667
|
-
this.blePlxManager?.cancelDeviceConnection(uuid).catch(() => {
|
|
1668
|
-
// Rejects with "Operation was cancelled" while merely connecting — expected.
|
|
1669
|
-
});
|
|
1670
|
-
const stalled = transportCache[uuid];
|
|
1671
|
-
if (stalled) {
|
|
1672
|
-
delete transportCache[uuid];
|
|
1673
|
-
}
|
|
1674
|
-
this.deviceProtocol.delete(uuid);
|
|
1675
|
-
this.probingProtocols.delete(uuid);
|
|
1676
|
-
this.protocolV2Assemblers.delete(uuid);
|
|
1677
|
-
this.resetProtocolV2Frames(uuid);
|
|
1678
|
-
|
|
1679
|
-
if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1680
|
-
// BleManager.destroy() force-rejects every promise the native queue abandoned —
|
|
1681
|
-
// the only JS-reachable way to settle them — and drops all cached peripherals.
|
|
1682
|
-
Log?.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
|
|
1683
|
-
this.resetPlxManager();
|
|
1684
|
-
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
1505
|
private getCachedTransport(uuid: string) {
|
|
1689
1506
|
const transport = transportCache[uuid];
|
|
1690
1507
|
if (!transport) {
|
|
@@ -1693,107 +1510,6 @@ export default class ReactNativeBleTransport {
|
|
|
1693
1510
|
return transport;
|
|
1694
1511
|
}
|
|
1695
1512
|
|
|
1696
|
-
/**
|
|
1697
|
-
* Write one packet under a bounded budget. A write that never settles means the
|
|
1698
|
-
* peripheral is wedged even though the GATT link still reports connected, so the
|
|
1699
|
-
* link is torn down: releasing JS state alone would leave the poisoned peripheral
|
|
1700
|
-
* cached and every later call would hang on it again.
|
|
1701
|
-
*/
|
|
1702
|
-
private async writeBlePacket(
|
|
1703
|
-
uuid: string,
|
|
1704
|
-
data: string,
|
|
1705
|
-
write: (payload: string) => Promise<unknown>,
|
|
1706
|
-
isCurrentOwner?: () => boolean
|
|
1707
|
-
) {
|
|
1708
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1709
|
-
let timedOut = false;
|
|
1710
|
-
try {
|
|
1711
|
-
await Promise.race([
|
|
1712
|
-
write(data),
|
|
1713
|
-
new Promise<never>((_, reject) => {
|
|
1714
|
-
timer = setTimeout(() => {
|
|
1715
|
-
timedOut = true;
|
|
1716
|
-
reject(
|
|
1717
|
-
ERRORS.TypedError(
|
|
1718
|
-
HardwareErrorCode.BleWriteCharacteristicError,
|
|
1719
|
-
`BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`
|
|
1720
|
-
)
|
|
1721
|
-
);
|
|
1722
|
-
}, BLE_WRITE_PACKET_TIMEOUT_MS);
|
|
1723
|
-
}),
|
|
1724
|
-
]);
|
|
1725
|
-
this.writeTimeoutCounts.delete(uuid);
|
|
1726
|
-
} catch (error) {
|
|
1727
|
-
if (timedOut) {
|
|
1728
|
-
// A superseded call's late write must not tear down the link the current
|
|
1729
|
-
// call is using; only the owner of the transport may declare it dead.
|
|
1730
|
-
if (isCurrentOwner && !isCurrentOwner()) {
|
|
1731
|
-
Log?.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
|
|
1732
|
-
} else {
|
|
1733
|
-
this.tearDownWedgedLink(uuid);
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
throw error;
|
|
1737
|
-
} finally {
|
|
1738
|
-
if (timer) clearTimeout(timer);
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
/**
|
|
1743
|
-
* Drop a link whose writes stopped completing. The JS state is purged synchronously
|
|
1744
|
-
* so the next acquire() cannot reuse the dead transport, while the native teardown is
|
|
1745
|
-
* intentionally NOT awaited: it talks to the very layer that just stopped settling
|
|
1746
|
-
* promises, so awaiting it could hang exactly like the write it is recovering from.
|
|
1747
|
-
*/
|
|
1748
|
-
private tearDownWedgedLink(uuid: string) {
|
|
1749
|
-
const timeouts = (this.writeTimeoutCounts.get(uuid) ?? 0) + 1;
|
|
1750
|
-
this.writeTimeoutCounts.set(uuid, timeouts);
|
|
1751
|
-
Log?.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
|
|
1752
|
-
consecutiveWriteTimeouts: timeouts,
|
|
1753
|
-
});
|
|
1754
|
-
|
|
1755
|
-
const wedged = transportCache[uuid];
|
|
1756
|
-
this.disconnect(uuid).catch(error => {
|
|
1757
|
-
Log?.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
|
|
1758
|
-
});
|
|
1759
|
-
if (wedged && transportCache[uuid] === wedged) {
|
|
1760
|
-
delete transportCache[uuid];
|
|
1761
|
-
}
|
|
1762
|
-
this.deviceProtocol.delete(uuid);
|
|
1763
|
-
this.probingProtocols.delete(uuid);
|
|
1764
|
-
this.protocolV2Assemblers.delete(uuid);
|
|
1765
|
-
this.resetProtocolV2Frames(uuid);
|
|
1766
|
-
|
|
1767
|
-
if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1768
|
-
// Reconnecting reuses the same native peripheral object. When it stays wedged
|
|
1769
|
-
// across attempts the poison lives in the BLE manager itself, and only a fresh
|
|
1770
|
-
// manager drops every cached peripheral — the JS equivalent of restarting the app.
|
|
1771
|
-
Log?.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
|
|
1772
|
-
this.resetPlxManager();
|
|
1773
|
-
this.writeTimeoutCounts.delete(uuid);
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
private resetPlxManager() {
|
|
1778
|
-
const manager = this.blePlxManager;
|
|
1779
|
-
this.blePlxManager = undefined;
|
|
1780
|
-
// Every cached transport belongs to the destroyed manager's peripherals.
|
|
1781
|
-
Object.keys(transportCache).forEach(key => {
|
|
1782
|
-
delete transportCache[key];
|
|
1783
|
-
});
|
|
1784
|
-
this.deviceProtocol.clear();
|
|
1785
|
-
this.probingProtocols.clear();
|
|
1786
|
-
this.sessionProtocols.clear();
|
|
1787
|
-
this.protocolReprobeFailures.clear();
|
|
1788
|
-
this.monitorTokens.clear();
|
|
1789
|
-
this.protocolV2Assemblers.clear();
|
|
1790
|
-
try {
|
|
1791
|
-
manager?.destroy();
|
|
1792
|
-
} catch (error) {
|
|
1793
|
-
Log?.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
1513
|
private createProtocolMismatchError(expected: ProtocolType) {
|
|
1798
1514
|
return ERRORS.TypedError(
|
|
1799
1515
|
HardwareErrorCode.RuntimeError,
|
|
@@ -1809,19 +1525,11 @@ export default class ReactNativeBleTransport {
|
|
|
1809
1525
|
}
|
|
1810
1526
|
|
|
1811
1527
|
private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
|
|
1812
|
-
if (this.probingProtocols.get(uuid) === protocol) {
|
|
1813
|
-
this.probingProtocols.delete(uuid);
|
|
1814
|
-
}
|
|
1815
1528
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
1816
1529
|
this.deviceProtocol.delete(uuid);
|
|
1817
1530
|
}
|
|
1818
1531
|
}
|
|
1819
1532
|
|
|
1820
|
-
/** Protocol to route a call with: confirmed if known, otherwise the one being probed. */
|
|
1821
|
-
private getActiveProtocol(uuid: string): ProtocolType | undefined {
|
|
1822
|
-
return this.deviceProtocol.get(uuid) ?? this.probingProtocols.get(uuid);
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
1533
|
private async detectProtocol(
|
|
1826
1534
|
uuid: string,
|
|
1827
1535
|
expectedProtocol?: ProtocolType,
|
|
@@ -1841,7 +1549,6 @@ export default class ReactNativeBleTransport {
|
|
|
1841
1549
|
if (expectedProtocol === 'V1') {
|
|
1842
1550
|
if (await this.probeProtocolV1(uuid)) {
|
|
1843
1551
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1844
|
-
this.sessionProtocols.set(uuid, 'V1');
|
|
1845
1552
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1846
1553
|
deviceId: uuid,
|
|
1847
1554
|
protocol: 'V1',
|
|
@@ -1855,7 +1562,6 @@ export default class ReactNativeBleTransport {
|
|
|
1855
1562
|
if (expectedProtocol === 'V2') {
|
|
1856
1563
|
if (await this.probeProtocolV2(uuid)) {
|
|
1857
1564
|
this.deviceProtocol.set(uuid, 'V2');
|
|
1858
|
-
this.sessionProtocols.set(uuid, 'V2');
|
|
1859
1565
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1860
1566
|
deviceId: uuid,
|
|
1861
1567
|
protocol: 'V2',
|
|
@@ -1868,18 +1574,8 @@ export default class ReactNativeBleTransport {
|
|
|
1868
1574
|
|
|
1869
1575
|
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
1870
1576
|
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
1871
|
-
const
|
|
1872
|
-
const reprobeFailures = this.protocolReprobeFailures.get(uuid) ?? 0;
|
|
1873
|
-
const fullProbeOrder: ProtocolType[] =
|
|
1577
|
+
const probeOrder: ProtocolType[] =
|
|
1874
1578
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1875
|
-
// A device that already answered on a protocol in this session keeps answering on
|
|
1876
|
-
// it; while it is rebooting nothing answers at all, so probing the other protocol
|
|
1877
|
-
// only adds its timeout to every poll.
|
|
1878
|
-
const trustSessionProtocol =
|
|
1879
|
-
sessionProtocol !== undefined &&
|
|
1880
|
-
!protocolHint &&
|
|
1881
|
-
reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
|
|
1882
|
-
const probeOrder: ProtocolType[] = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
|
|
1883
1579
|
|
|
1884
1580
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1885
1581
|
const protocol = probeOrder[i];
|
|
@@ -1897,8 +1593,6 @@ export default class ReactNativeBleTransport {
|
|
|
1897
1593
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
1898
1594
|
if (detected) {
|
|
1899
1595
|
this.deviceProtocol.set(uuid, protocol);
|
|
1900
|
-
this.sessionProtocols.set(uuid, protocol);
|
|
1901
|
-
this.protocolReprobeFailures.delete(uuid);
|
|
1902
1596
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1903
1597
|
deviceId: uuid,
|
|
1904
1598
|
protocol,
|
|
@@ -1908,16 +1602,7 @@ export default class ReactNativeBleTransport {
|
|
|
1908
1602
|
}
|
|
1909
1603
|
}
|
|
1910
1604
|
|
|
1911
|
-
if (trustSessionProtocol) {
|
|
1912
|
-
// Still silent on its own protocol: count it, and let the streak expire the
|
|
1913
|
-
// shortcut so a device that genuinely switched protocols is found again.
|
|
1914
|
-
this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
|
|
1915
|
-
} else {
|
|
1916
|
-
this.protocolReprobeFailures.delete(uuid);
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
1605
|
this.deviceProtocol.delete(uuid);
|
|
1920
|
-
this.probingProtocols.delete(uuid);
|
|
1921
1606
|
throw this.createProtocolDetectionError();
|
|
1922
1607
|
}
|
|
1923
1608
|
|
|
@@ -1979,20 +1664,14 @@ export default class ReactNativeBleTransport {
|
|
|
1979
1664
|
}
|
|
1980
1665
|
|
|
1981
1666
|
try {
|
|
1982
|
-
this.
|
|
1667
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
1983
1668
|
// GetFeatures identifies Protocol V1 without resetting an existing wallet
|
|
1984
1669
|
// session before Core has a chance to restore a hidden wallet.
|
|
1985
1670
|
await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1986
|
-
this.probingProtocols.delete(uuid);
|
|
1987
1671
|
return true;
|
|
1988
1672
|
} catch (error) {
|
|
1989
1673
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1990
1674
|
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
1991
|
-
// A wedged write already dropped the link, so probing another protocol on it
|
|
1992
|
-
// would only fail against a torn-down transport: surface the real cause.
|
|
1993
|
-
if (isWedgedWriteError(error)) {
|
|
1994
|
-
throw error;
|
|
1995
|
-
}
|
|
1996
1675
|
return false;
|
|
1997
1676
|
}
|
|
1998
1677
|
}
|
|
@@ -2002,7 +1681,7 @@ export default class ReactNativeBleTransport {
|
|
|
2002
1681
|
return false;
|
|
2003
1682
|
}
|
|
2004
1683
|
|
|
2005
|
-
this.
|
|
1684
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
2006
1685
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
2007
1686
|
const detected = await probeProtocolV2Helper({
|
|
2008
1687
|
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
@@ -2017,8 +1696,6 @@ export default class ReactNativeBleTransport {
|
|
|
2017
1696
|
});
|
|
2018
1697
|
if (!detected) {
|
|
2019
1698
|
this.clearProbeProtocol(uuid, 'V2');
|
|
2020
|
-
} else {
|
|
2021
|
-
this.probingProtocols.delete(uuid);
|
|
2022
1699
|
}
|
|
2023
1700
|
return detected;
|
|
2024
1701
|
}
|
|
@@ -2100,15 +1777,17 @@ export default class ReactNativeBleTransport {
|
|
|
2100
1777
|
}
|
|
2101
1778
|
|
|
2102
1779
|
private async writeProtocolV2Packet(
|
|
2103
|
-
uuid: string,
|
|
2104
1780
|
transport: BleTransport,
|
|
2105
1781
|
base64: string,
|
|
2106
1782
|
context: ProtocolV2CallContext,
|
|
2107
1783
|
assertCurrentGeneration: () => void
|
|
2108
1784
|
) {
|
|
2109
|
-
const shouldUseWriteWithResponse =
|
|
2110
|
-
|
|
2111
|
-
|
|
1785
|
+
const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
|
|
1786
|
+
platform: Platform.OS,
|
|
1787
|
+
highVolume: context.highVolume,
|
|
1788
|
+
requestedWithResponse: context.writeWithResponse,
|
|
1789
|
+
characteristic: transport.writeCharacteristic,
|
|
1790
|
+
});
|
|
2112
1791
|
let attempt = 0;
|
|
2113
1792
|
for (;;) {
|
|
2114
1793
|
assertCurrentGeneration();
|
|
@@ -2116,24 +1795,11 @@ export default class ReactNativeBleTransport {
|
|
|
2116
1795
|
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
2117
1796
|
}
|
|
2118
1797
|
try {
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
? transport.writeCharacteristic.writeWithResponse(payload)
|
|
2125
|
-
: transport.writeCharacteristic.writeWithoutResponse(payload),
|
|
2126
|
-
// Same rule as Protocol V1: a write from a superseded generation must not
|
|
2127
|
-
// tear down the link that the current generation is using.
|
|
2128
|
-
() => {
|
|
2129
|
-
try {
|
|
2130
|
-
assertCurrentGeneration();
|
|
2131
|
-
return !context.signal.aborted;
|
|
2132
|
-
} catch {
|
|
2133
|
-
return false;
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
|
-
);
|
|
1798
|
+
if (shouldUseWriteWithResponse) {
|
|
1799
|
+
await transport.writeCharacteristic.writeWithResponse(base64);
|
|
1800
|
+
} else {
|
|
1801
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1802
|
+
}
|
|
2137
1803
|
assertCurrentGeneration();
|
|
2138
1804
|
return;
|
|
2139
1805
|
} catch (error) {
|
|
@@ -2156,7 +1822,6 @@ export default class ReactNativeBleTransport {
|
|
|
2156
1822
|
}
|
|
2157
1823
|
|
|
2158
1824
|
private async writeProtocolV2Frame(
|
|
2159
|
-
uuid: string,
|
|
2160
1825
|
transport: BleTransport,
|
|
2161
1826
|
frame: Uint8Array,
|
|
2162
1827
|
context: ProtocolV2CallContext,
|
|
@@ -2167,28 +1832,17 @@ export default class ReactNativeBleTransport {
|
|
|
2167
1832
|
platform: Platform.OS,
|
|
2168
1833
|
iosPacketLength: tuning.iosPacketLength,
|
|
2169
1834
|
androidPacketLength: tuning.androidPacketLength,
|
|
2170
|
-
mtu:
|
|
1835
|
+
mtu: transport.mtuSize,
|
|
2171
1836
|
});
|
|
2172
|
-
// Match Desktop BLE pacing so Pro2 firmware can finish the previous response
|
|
2173
|
-
// before the next single-packet control command is written.
|
|
2174
|
-
const initialDelayMs =
|
|
2175
|
-
Platform.OS === 'ios' && !context.highVolume && frame.length <= packetCapacity
|
|
2176
|
-
? IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS
|
|
2177
|
-
: 0;
|
|
2178
1837
|
await writeProtocolV2BleFrame({
|
|
2179
1838
|
frame,
|
|
2180
1839
|
packetCapacity,
|
|
2181
1840
|
assertActive: assertCurrentGeneration,
|
|
2182
1841
|
signal: context.signal,
|
|
2183
1842
|
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
2184
|
-
initialDelayMs,
|
|
2185
|
-
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
2186
|
-
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
2187
|
-
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
2188
1843
|
wait: delay,
|
|
2189
1844
|
writePacket: packet =>
|
|
2190
1845
|
this.writeProtocolV2Packet(
|
|
2191
|
-
uuid,
|
|
2192
1846
|
transport,
|
|
2193
1847
|
Buffer.from(packet).toString('base64'),
|
|
2194
1848
|
context,
|
|
@@ -2212,11 +1866,39 @@ export default class ReactNativeBleTransport {
|
|
|
2212
1866
|
|
|
2213
1867
|
if (highVolumeWrite) {
|
|
2214
1868
|
const tuning = getProtocolV2BleTuning();
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
1869
|
+
const currentTransport = this.getCachedTransport(uuid);
|
|
1870
|
+
const writeWithResponse = shouldWriteProtocolV2WithResponse({
|
|
1871
|
+
platform: Platform.OS,
|
|
1872
|
+
highVolume: true,
|
|
1873
|
+
requestedWithResponse: options?.writeWithResponse,
|
|
1874
|
+
characteristic: currentTransport.writeCharacteristic,
|
|
1875
|
+
});
|
|
1876
|
+
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
1877
|
+
platform: Platform.OS,
|
|
1878
|
+
iosPacketLength: tuning.iosPacketLength,
|
|
1879
|
+
androidPacketLength: tuning.androidPacketLength,
|
|
1880
|
+
mtu: currentTransport.mtuSize,
|
|
2219
1881
|
});
|
|
1882
|
+
const writeMode = writeWithResponse ? 'withResponse' : 'withoutResponse';
|
|
1883
|
+
const logSignature = `${name}:${writeMode}:${String(
|
|
1884
|
+
currentTransport.mtuSize
|
|
1885
|
+
)}:${packetCapacity}`;
|
|
1886
|
+
const loggedSignatures =
|
|
1887
|
+
this.protocolV2HighVolumeLogSignatures.get(uuid) ?? new Set<string>();
|
|
1888
|
+
if (!loggedSignatures.has(logSignature)) {
|
|
1889
|
+
loggedSignatures.add(logSignature);
|
|
1890
|
+
this.protocolV2HighVolumeLogSignatures.set(uuid, loggedSignatures);
|
|
1891
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
1892
|
+
name,
|
|
1893
|
+
writeMode,
|
|
1894
|
+
reportedMtu: currentTransport.mtuSize,
|
|
1895
|
+
packetCapacity,
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
if (highVolumeWrite) {
|
|
1901
|
+
await this.enableAndroidHighConnectionPriority(uuid);
|
|
2220
1902
|
}
|
|
2221
1903
|
|
|
2222
1904
|
try {
|
|
@@ -2230,6 +1912,73 @@ export default class ReactNativeBleTransport {
|
|
|
2230
1912
|
} catch (e) {
|
|
2231
1913
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
2232
1914
|
throw e;
|
|
1915
|
+
} finally {
|
|
1916
|
+
if (highVolumeWrite) {
|
|
1917
|
+
this.scheduleAndroidBalancedConnectionPriority(uuid);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
private clearAndroidPriorityResetTimer(uuid: string) {
|
|
1923
|
+
const timerId = this.androidPriorityResetTimers.get(uuid);
|
|
1924
|
+
if (timerId !== undefined) {
|
|
1925
|
+
clearTimeout(timerId);
|
|
1926
|
+
this.androidPriorityResetTimers.delete(uuid);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
private async enableAndroidHighConnectionPriority(uuid: string) {
|
|
1931
|
+
if (Platform.OS !== 'android') return;
|
|
1932
|
+
|
|
1933
|
+
this.clearAndroidPriorityResetTimer(uuid);
|
|
1934
|
+
if (this.androidHighPriorityDevices.has(uuid)) return;
|
|
1935
|
+
|
|
1936
|
+
const transport = transportCache[uuid];
|
|
1937
|
+
if (!transport) return;
|
|
1938
|
+
|
|
1939
|
+
try {
|
|
1940
|
+
transport.device = await transport.device.requestConnectionPriority(ConnectionPriority.High);
|
|
1941
|
+
this.androidHighPriorityDevices.add(uuid);
|
|
1942
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
|
|
1943
|
+
priority: 'high',
|
|
1944
|
+
});
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE high priority request failed', {
|
|
1947
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
private scheduleAndroidBalancedConnectionPriority(uuid: string) {
|
|
1953
|
+
if (Platform.OS !== 'android' || !this.androidHighPriorityDevices.has(uuid)) return;
|
|
1954
|
+
|
|
1955
|
+
this.clearAndroidPriorityResetTimer(uuid);
|
|
1956
|
+
const timerId = setTimeout(() => {
|
|
1957
|
+
this.androidPriorityResetTimers.delete(uuid);
|
|
1958
|
+
this.restoreAndroidConnectionPriority(uuid, transportCache[uuid]).catch(error =>
|
|
1959
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE priority restore failed', error)
|
|
1960
|
+
);
|
|
1961
|
+
}, ANDROID_HIGH_PRIORITY_IDLE_MS);
|
|
1962
|
+
this.androidPriorityResetTimers.set(uuid, timerId);
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
private async restoreAndroidConnectionPriority(uuid: string, transport?: BleTransport) {
|
|
1966
|
+
this.clearAndroidPriorityResetTimer(uuid);
|
|
1967
|
+
if (Platform.OS !== 'android' || !this.androidHighPriorityDevices.delete(uuid) || !transport) {
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
try {
|
|
1972
|
+
transport.device = await transport.device.requestConnectionPriority(
|
|
1973
|
+
ConnectionPriority.Balanced
|
|
1974
|
+
);
|
|
1975
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
|
|
1976
|
+
priority: 'balanced',
|
|
1977
|
+
});
|
|
1978
|
+
} catch (error) {
|
|
1979
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE balanced priority request failed', {
|
|
1980
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1981
|
+
});
|
|
2233
1982
|
}
|
|
2234
1983
|
}
|
|
2235
1984
|
|
|
@@ -2253,13 +2002,7 @@ export default class ReactNativeBleTransport {
|
|
|
2253
2002
|
writeFrame: async (frame: Uint8Array, context: ProtocolV2CallContext) => {
|
|
2254
2003
|
assertCurrentGeneration();
|
|
2255
2004
|
const currentTransport = this.getCachedTransport(uuid);
|
|
2256
|
-
await this.writeProtocolV2Frame(
|
|
2257
|
-
uuid,
|
|
2258
|
-
currentTransport,
|
|
2259
|
-
frame,
|
|
2260
|
-
context,
|
|
2261
|
-
assertCurrentGeneration
|
|
2262
|
-
);
|
|
2005
|
+
await this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
|
|
2263
2006
|
},
|
|
2264
2007
|
readFrame: async () => {
|
|
2265
2008
|
assertCurrentGeneration();
|
|
@@ -2284,6 +2027,6 @@ export default class ReactNativeBleTransport {
|
|
|
2284
2027
|
}
|
|
2285
2028
|
|
|
2286
2029
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
2287
|
-
return this.
|
|
2030
|
+
return this.deviceProtocol.get(path);
|
|
2288
2031
|
}
|
|
2289
2032
|
}
|