@onekeyfe/hd-transport-react-native 1.2.0-alpha.70 → 1.2.0-alpha.71
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 +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +170 -43
- 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 +132 -11
- package/src/bleStrategy.ts +23 -10
- package/src/constants.ts +2 -1
- package/src/index.ts +191 -38
package/dist/BleTransport.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BleTransport.d.ts","sourceRoot":"","sources":["../src/BleTransport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEjF,MAAM,CAAC,OAAO,OAAO,YAAY;IAC/B,EAAE,EAAE,MAAM,CAAC;IAEX,IAAI,SAA6B;IAEjC,MAAM,EAAE,MAAM,CAAC;IAEf,OAAO,
|
|
1
|
+
{"version":3,"file":"BleTransport.d.ts","sourceRoot":"","sources":["../src/BleTransport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEjF,MAAM,CAAC,OAAO,OAAO,YAAY;IAC/B,EAAE,EAAE,MAAM,CAAC;IAEX,IAAI,SAA6B;IAEjC,MAAM,EAAE,MAAM,CAAC;IAEf,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAE5B,mBAAmB,EAAE,cAAc,CAAC;IAEpC,oBAAoB,EAAE,cAAc,CAAC;IAErC,kBAAkB,CAAC,EAAE,YAAY,CAAC;IAElC,sBAAsB,CAAC,EAAE,YAAY,CAAC;IAEtC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,YAAY,CAAC,EAAE,MAAM,CAAC;gBAGpB,MAAM,EAAE,MAAM,EACd,mBAAmB,EAAE,cAAc,EACnC,oBAAoB,EAAE,cAAc;IAQhC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAOlD"}
|
package/dist/bleStrategy.d.ts
CHANGED
|
@@ -10,4 +10,10 @@ export declare function resolveProtocolV2PacketCapacity({ platform, iosPacketLen
|
|
|
10
10
|
androidPacketLength?: number;
|
|
11
11
|
mtu?: number | null;
|
|
12
12
|
}): number;
|
|
13
|
+
export declare function shouldWriteProtocolV2WithResponse({ platform, highVolume, requestedWithResponse, characteristic, }: {
|
|
14
|
+
platform: BlePlatform;
|
|
15
|
+
highVolume: boolean;
|
|
16
|
+
requestedWithResponse?: boolean;
|
|
17
|
+
characteristic: BleWriteCapability;
|
|
18
|
+
}): boolean;
|
|
13
19
|
//# sourceMappingURL=bleStrategy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bleStrategy.d.ts","sourceRoot":"","sources":["../src/bleStrategy.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;AAErD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sBAAsB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACxC,yBAAyB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5C,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,kBAAkB,WAEvE;AAED,wBAAgB,+BAA+B,CAAC,EAC9C,QAAQ,EACR,
|
|
1
|
+
{"version":3,"file":"bleStrategy.d.ts","sourceRoot":"","sources":["../src/bleStrategy.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;AAErD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sBAAsB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACxC,yBAAyB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5C,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,kBAAkB,WAEvE;AAED,wBAAgB,+BAA+B,CAAC,EAC9C,QAAQ,EACR,eAA+C,EAC/C,mBAAuD,EACvD,GAAG,GACJ,EAAE;IACD,QAAQ,EAAE,WAAW,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,UAQA;AAED,wBAAgB,iCAAiC,CAAC,EAChD,QAAQ,EACR,UAAU,EACV,qBAAqB,EACrB,cAAc,GACf,EAAE;IACD,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,cAAc,EAAE,kBAAkB,CAAC;CACpC,WAIA"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const IOS_PACKET_LENGTH = 128;
|
|
2
2
|
export declare const ANDROID_PACKET_LENGTH = 192;
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const IOS_PROTOCOL_V2_PACKET_LENGTH = 244;
|
|
4
|
+
export declare const ANDROID_PROTOCOL_V2_PACKET_LENGTH = 514;
|
|
4
5
|
export declare const getBluetoothServiceUuids: () => string[];
|
|
5
6
|
export declare const getInfosForServiceUuid: (serviceUuid: string, deviceType: 'classic') => {
|
|
6
7
|
serviceUuid: string;
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,6BAA6B,MAAM,CAAC;AACjD,eAAO,MAAM,iCAAiC,MAAM,CAAC;AA8BrD,eAAO,MAAM,wBAAwB,gBAA0B,CAAC;AAChE,eAAO,MAAM,sBAAsB,gBAAiB,MAAM,cAAc,SAAS;iBA1BhE,MAAM;;;QA2CtB,CAAC;AAEF,eAAO,MAAM,gBAAgB,UAAW,MAAM,GAAG,IAAI,WACP,CAAC;AAE/C,eAAO,MAAM,aAAa,UAAW,MAAM,GAAG,IAAI,UAAU,MAAM,GAAG,IAAI,YAGxE,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ declare class BleTransport {
|
|
|
8
8
|
id: string;
|
|
9
9
|
name: string;
|
|
10
10
|
device: Device;
|
|
11
|
-
mtuSize: number;
|
|
11
|
+
mtuSize: number | undefined;
|
|
12
12
|
writeCharacteristic: Characteristic;
|
|
13
13
|
notifyCharacteristic: Characteristic;
|
|
14
14
|
notifySubscription?: Subscription;
|
|
@@ -119,6 +119,9 @@ declare class ReactNativeBleTransport {
|
|
|
119
119
|
private protocolV2Links;
|
|
120
120
|
private monitorTokens;
|
|
121
121
|
private disconnectEventTokens;
|
|
122
|
+
private protocolV2HighVolumeLogSignatures;
|
|
123
|
+
private androidHighPriorityDevices;
|
|
124
|
+
private androidPriorityResetTimers;
|
|
122
125
|
private nextMonitorToken;
|
|
123
126
|
constructor(options: TransportOptions);
|
|
124
127
|
init(logger: any, emitter: EventEmitter): void;
|
|
@@ -194,6 +197,10 @@ declare class ReactNativeBleTransport {
|
|
|
194
197
|
private writeProtocolV2Packet;
|
|
195
198
|
private writeProtocolV2Frame;
|
|
196
199
|
private callProtocolV2;
|
|
200
|
+
private clearAndroidPriorityResetTimer;
|
|
201
|
+
private enableAndroidHighConnectionPriority;
|
|
202
|
+
private scheduleAndroidBalancedConnectionPriority;
|
|
203
|
+
private restoreAndroidConnectionPriority;
|
|
197
204
|
private createProtocolV2Adapter;
|
|
198
205
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
199
206
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAIL,UAAU,IAAI,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -93,7 +93,8 @@ const onDeviceBondState = (bleMacAddress) => new Promise((resolve, reject) => {
|
|
|
93
93
|
|
|
94
94
|
const IOS_PACKET_LENGTH = 128;
|
|
95
95
|
const ANDROID_PACKET_LENGTH = 192;
|
|
96
|
-
const
|
|
96
|
+
const IOS_PROTOCOL_V2_PACKET_LENGTH = 244;
|
|
97
|
+
const ANDROID_PROTOCOL_V2_PACKET_LENGTH = 514;
|
|
97
98
|
const ClassicServiceUUID = '00000001-0000-1000-8000-00805f9b34fb';
|
|
98
99
|
const OneKeyServices = {
|
|
99
100
|
classic: {
|
|
@@ -134,15 +135,20 @@ const isSameBleUuid = (left, right) => {
|
|
|
134
135
|
function hasWritableCapability(characteristic) {
|
|
135
136
|
return !!(characteristic.isWritableWithResponse || characteristic.isWritableWithoutResponse);
|
|
136
137
|
}
|
|
137
|
-
function resolveProtocolV2PacketCapacity({ platform, iosPacketLength =
|
|
138
|
-
if (
|
|
139
|
-
|
|
138
|
+
function resolveProtocolV2PacketCapacity({ platform, iosPacketLength = IOS_PROTOCOL_V2_PACKET_LENGTH, androidPacketLength = ANDROID_PROTOCOL_V2_PACKET_LENGTH, mtu, }) {
|
|
139
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= 3) {
|
|
140
|
+
throw new Error(`Protocol V2 BLE requires a negotiated MTU, received: ${String(mtu)}`);
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
const payloadLength = Math.floor(mtu) - 3;
|
|
143
|
+
const configuredPacketLength = platform === 'ios' ? iosPacketLength : androidPacketLength;
|
|
144
|
+
return Math.min(configuredPacketLength, payloadLength);
|
|
145
|
+
}
|
|
146
|
+
function shouldWriteProtocolV2WithResponse({ platform, highVolume, requestedWithResponse, characteristic, }) {
|
|
147
|
+
if (!characteristic.isWritableWithResponse)
|
|
148
|
+
return false;
|
|
149
|
+
if (!characteristic.isWritableWithoutResponse)
|
|
150
|
+
return true;
|
|
151
|
+
return requestedWithResponse === true || (platform === 'ios' && !highVolume);
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
const timer = process.env.NODE_ENV === 'development'
|
|
@@ -202,7 +208,6 @@ const isHeaderChunk = (chunk) => {
|
|
|
202
208
|
class BleTransport {
|
|
203
209
|
constructor(device, writeCharacteristic, notifyCharacteristic) {
|
|
204
210
|
this.name = 'ReactNativeBleTransport';
|
|
205
|
-
this.mtuSize = 23;
|
|
206
211
|
this.id = device.id;
|
|
207
212
|
this.device = device;
|
|
208
213
|
this.writeCharacteristic = writeCharacteristic;
|
|
@@ -226,7 +231,6 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 :
|
|
|
226
231
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
|
|
227
232
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
|
|
228
233
|
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
229
|
-
const IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS = 5;
|
|
230
234
|
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
231
235
|
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
232
236
|
const ANDROID_GATT_CONGESTED_STATUS = 143;
|
|
@@ -285,8 +289,8 @@ const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
|
285
289
|
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
286
290
|
const ANDROID_NOTIFY_READY_DELAY_MS = 300;
|
|
287
291
|
const DEFAULT_PROTOCOL_V2_BLE_TUNING = {
|
|
288
|
-
iosPacketLength:
|
|
289
|
-
androidPacketLength:
|
|
292
|
+
iosPacketLength: IOS_PROTOCOL_V2_PACKET_LENGTH,
|
|
293
|
+
androidPacketLength: ANDROID_PROTOCOL_V2_PACKET_LENGTH,
|
|
290
294
|
};
|
|
291
295
|
let protocolV2BleTuning = Object.assign({}, DEFAULT_PROTOCOL_V2_BLE_TUNING);
|
|
292
296
|
const normalizePositiveInteger = (value, fallback) => {
|
|
@@ -315,10 +319,15 @@ function inferProtocolHintFromDeviceName(name) {
|
|
|
315
319
|
function getDeviceDisplayName(device) {
|
|
316
320
|
return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
|
|
317
321
|
}
|
|
318
|
-
const
|
|
322
|
+
const IOS_REQUEST_MTU = 247;
|
|
323
|
+
const ANDROID_REQUEST_MTU = 517;
|
|
324
|
+
const BLE_MTU_REFRESH_THRESHOLD = 247;
|
|
325
|
+
const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
|
|
326
|
+
const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
|
|
327
|
+
const getRequestedBleMtu = () => reactNative.Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
|
|
319
328
|
const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
|
|
320
329
|
const connectOptions = {
|
|
321
|
-
requestMTU:
|
|
330
|
+
requestMTU: getRequestedBleMtu(),
|
|
322
331
|
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
323
332
|
refreshGatt: 'OnConnected',
|
|
324
333
|
};
|
|
@@ -345,23 +354,25 @@ const tryToGetConfiguration = (device) => {
|
|
|
345
354
|
return null;
|
|
346
355
|
return infos;
|
|
347
356
|
};
|
|
348
|
-
const
|
|
349
|
-
if (reactNative.Platform.OS !== 'android')
|
|
357
|
+
const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void 0, void 0, function* () {
|
|
358
|
+
if (reactNative.Platform.OS !== 'ios' && reactNative.Platform.OS !== 'android')
|
|
350
359
|
return device;
|
|
351
360
|
try {
|
|
352
|
-
const mtuDevice = yield device.requestMTU(
|
|
353
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU configured', {
|
|
354
|
-
deviceId: device.id,
|
|
355
|
-
requested: ANDROID_REQUEST_MTU,
|
|
356
|
-
actual: mtuDevice.mtu,
|
|
357
|
-
});
|
|
361
|
+
const mtuDevice = yield device.requestMTU(getRequestedBleMtu());
|
|
358
362
|
return mtuDevice;
|
|
359
363
|
}
|
|
360
364
|
catch (error) {
|
|
361
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport]
|
|
365
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
|
|
366
|
+
platform: reactNative.Platform.OS,
|
|
367
|
+
stage,
|
|
368
|
+
attempt,
|
|
369
|
+
actual: device.mtu,
|
|
370
|
+
error: error instanceof Error ? error.message : String(error),
|
|
371
|
+
});
|
|
362
372
|
return device;
|
|
363
373
|
}
|
|
364
374
|
});
|
|
375
|
+
const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
|
|
365
376
|
function remapError(error) {
|
|
366
377
|
var _a;
|
|
367
378
|
if (error instanceof reactNativeBlePlx.BleError) {
|
|
@@ -423,6 +434,9 @@ class ReactNativeBleTransport {
|
|
|
423
434
|
});
|
|
424
435
|
this.monitorTokens = new Map();
|
|
425
436
|
this.disconnectEventTokens = new Map();
|
|
437
|
+
this.protocolV2HighVolumeLogSignatures = new Map();
|
|
438
|
+
this.androidHighPriorityDevices = new Set();
|
|
439
|
+
this.androidPriorityResetTimers = new Map();
|
|
426
440
|
this.nextMonitorToken = 1;
|
|
427
441
|
this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
|
|
428
442
|
}
|
|
@@ -739,9 +753,7 @@ class ReactNativeBleTransport {
|
|
|
739
753
|
return __awaiter(this, void 0, void 0, function* () {
|
|
740
754
|
const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
|
|
741
755
|
const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
742
|
-
|
|
743
|
-
transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport$1.mtuSize;
|
|
744
|
-
}
|
|
756
|
+
transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
|
|
745
757
|
const monitorToken = this.nextMonitorToken;
|
|
746
758
|
this.nextMonitorToken += 1;
|
|
747
759
|
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
@@ -750,6 +762,7 @@ class ReactNativeBleTransport {
|
|
|
750
762
|
this.monitorTokens.set(uuid, monitorToken);
|
|
751
763
|
transport$1.notifySubscription = this._monitorCharacteristic(transport$1.notifyCharacteristic, uuid, monitorToken, notifyTransactionId);
|
|
752
764
|
transportCache[uuid] = transport$1;
|
|
765
|
+
this.protocolV2HighVolumeLogSignatures.set(uuid, new Set());
|
|
753
766
|
this.protocolV2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
754
767
|
if (reactNative.Platform.OS === 'ios') {
|
|
755
768
|
yield new Promise(resolve => {
|
|
@@ -759,6 +772,31 @@ class ReactNativeBleTransport {
|
|
|
759
772
|
else if (reactNative.Platform.OS === 'android') {
|
|
760
773
|
yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
761
774
|
}
|
|
775
|
+
const initialMtu = transport$1.mtuSize;
|
|
776
|
+
let refreshAttempts = 0;
|
|
777
|
+
if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
|
|
778
|
+
(typeof transport$1.mtuSize !== 'number' || transport$1.mtuSize < BLE_MTU_REFRESH_THRESHOLD)) {
|
|
779
|
+
refreshAttempts += 1;
|
|
780
|
+
let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
|
|
781
|
+
transport$1.device = refreshedDevice;
|
|
782
|
+
transport$1.mtuSize =
|
|
783
|
+
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
|
|
784
|
+
if (typeof transport$1.mtuSize !== 'number' || transport$1.mtuSize < BLE_MTU_REFRESH_THRESHOLD) {
|
|
785
|
+
yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
|
|
786
|
+
refreshAttempts += 1;
|
|
787
|
+
refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
|
|
788
|
+
transport$1.device = refreshedDevice;
|
|
789
|
+
transport$1.mtuSize =
|
|
790
|
+
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
|
|
794
|
+
platform: reactNative.Platform.OS,
|
|
795
|
+
requested: getRequestedBleMtu(),
|
|
796
|
+
initial: initialMtu,
|
|
797
|
+
actual: transport$1.mtuSize,
|
|
798
|
+
refreshAttempts,
|
|
799
|
+
});
|
|
762
800
|
return transport$1;
|
|
763
801
|
});
|
|
764
802
|
}
|
|
@@ -875,7 +913,7 @@ class ReactNativeBleTransport {
|
|
|
875
913
|
}
|
|
876
914
|
}
|
|
877
915
|
}
|
|
878
|
-
device = yield
|
|
916
|
+
device = yield resolveNegotiatedMtu(device);
|
|
879
917
|
const acquiredDevice = device;
|
|
880
918
|
const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
|
|
881
919
|
const protocolHint = expectedProtocol
|
|
@@ -897,7 +935,7 @@ class ReactNativeBleTransport {
|
|
|
897
935
|
if (!currentTransport) {
|
|
898
936
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
|
|
899
937
|
}
|
|
900
|
-
this.attachDisconnectSubscription(currentTransport,
|
|
938
|
+
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
901
939
|
return { uuid, protocolType };
|
|
902
940
|
}
|
|
903
941
|
catch (error) {
|
|
@@ -1036,6 +1074,7 @@ class ReactNativeBleTransport {
|
|
|
1036
1074
|
this.resetProtocolV2Frames(uuid);
|
|
1037
1075
|
return Promise.resolve(true);
|
|
1038
1076
|
}
|
|
1077
|
+
yield this.restoreAndroidConnectionPriority(uuid, transport);
|
|
1039
1078
|
if (transport) {
|
|
1040
1079
|
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1041
1080
|
this.monitorTokens.delete(uuid);
|
|
@@ -1056,6 +1095,7 @@ class ReactNativeBleTransport {
|
|
|
1056
1095
|
}
|
|
1057
1096
|
delete transportCache[uuid];
|
|
1058
1097
|
}
|
|
1098
|
+
this.protocolV2HighVolumeLogSignatures.delete(uuid);
|
|
1059
1099
|
this.deviceProtocol.delete(uuid);
|
|
1060
1100
|
this.probingProtocols.delete(uuid);
|
|
1061
1101
|
(_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
|
|
@@ -1800,8 +1840,12 @@ class ReactNativeBleTransport {
|
|
|
1800
1840
|
}
|
|
1801
1841
|
writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
|
|
1802
1842
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1803
|
-
const shouldUseWriteWithResponse =
|
|
1804
|
-
|
|
1843
|
+
const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
|
|
1844
|
+
platform: reactNative.Platform.OS,
|
|
1845
|
+
highVolume: context.highVolume,
|
|
1846
|
+
requestedWithResponse: context.writeWithResponse,
|
|
1847
|
+
characteristic: transport.writeCharacteristic,
|
|
1848
|
+
});
|
|
1805
1849
|
let attempt = 0;
|
|
1806
1850
|
for (;;) {
|
|
1807
1851
|
assertCurrentGeneration();
|
|
@@ -1847,27 +1891,21 @@ class ReactNativeBleTransport {
|
|
|
1847
1891
|
platform: reactNative.Platform.OS,
|
|
1848
1892
|
iosPacketLength: tuning.iosPacketLength,
|
|
1849
1893
|
androidPacketLength: tuning.androidPacketLength,
|
|
1850
|
-
mtu:
|
|
1894
|
+
mtu: transport$1.mtuSize,
|
|
1851
1895
|
});
|
|
1852
|
-
const initialDelayMs = reactNative.Platform.OS === 'ios' && !context.highVolume && frame.length <= packetCapacity
|
|
1853
|
-
? IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS
|
|
1854
|
-
: 0;
|
|
1855
1896
|
yield transport.writeProtocolV2BleFrame({
|
|
1856
1897
|
frame,
|
|
1857
1898
|
packetCapacity,
|
|
1858
1899
|
assertActive: assertCurrentGeneration,
|
|
1859
1900
|
signal: context.signal,
|
|
1860
1901
|
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1861
|
-
initialDelayMs,
|
|
1862
|
-
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1863
|
-
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1864
|
-
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1865
1902
|
wait: delay,
|
|
1866
1903
|
writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
|
|
1867
1904
|
});
|
|
1868
1905
|
});
|
|
1869
1906
|
}
|
|
1870
1907
|
callProtocolV2(uuid, name, data, options) {
|
|
1908
|
+
var _a;
|
|
1871
1909
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1872
1910
|
if (!this._messages || !this._messagesV2) {
|
|
1873
1911
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
@@ -1876,11 +1914,35 @@ class ReactNativeBleTransport {
|
|
|
1876
1914
|
const highVolumeWrite = transport.LogBlockCommand.has(name);
|
|
1877
1915
|
if (highVolumeWrite) {
|
|
1878
1916
|
const tuning = getProtocolV2BleTuning();
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1917
|
+
const currentTransport = this.getCachedTransport(uuid);
|
|
1918
|
+
const writeWithResponse = shouldWriteProtocolV2WithResponse({
|
|
1919
|
+
platform: reactNative.Platform.OS,
|
|
1920
|
+
highVolume: true,
|
|
1921
|
+
requestedWithResponse: options === null || options === void 0 ? void 0 : options.writeWithResponse,
|
|
1922
|
+
characteristic: currentTransport.writeCharacteristic,
|
|
1883
1923
|
});
|
|
1924
|
+
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
1925
|
+
platform: reactNative.Platform.OS,
|
|
1926
|
+
iosPacketLength: tuning.iosPacketLength,
|
|
1927
|
+
androidPacketLength: tuning.androidPacketLength,
|
|
1928
|
+
mtu: currentTransport.mtuSize,
|
|
1929
|
+
});
|
|
1930
|
+
const writeMode = writeWithResponse ? 'withResponse' : 'withoutResponse';
|
|
1931
|
+
const logSignature = `${name}:${writeMode}:${String(currentTransport.mtuSize)}:${packetCapacity}`;
|
|
1932
|
+
const loggedSignatures = (_a = this.protocolV2HighVolumeLogSignatures.get(uuid)) !== null && _a !== void 0 ? _a : new Set();
|
|
1933
|
+
if (!loggedSignatures.has(logSignature)) {
|
|
1934
|
+
loggedSignatures.add(logSignature);
|
|
1935
|
+
this.protocolV2HighVolumeLogSignatures.set(uuid, loggedSignatures);
|
|
1936
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
1937
|
+
name,
|
|
1938
|
+
writeMode,
|
|
1939
|
+
reportedMtu: currentTransport.mtuSize,
|
|
1940
|
+
packetCapacity,
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
if (highVolumeWrite) {
|
|
1945
|
+
yield this.enableAndroidHighConnectionPriority(uuid);
|
|
1884
1946
|
}
|
|
1885
1947
|
try {
|
|
1886
1948
|
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
|
|
@@ -1889,6 +1951,71 @@ class ReactNativeBleTransport {
|
|
|
1889
1951
|
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1890
1952
|
throw e;
|
|
1891
1953
|
}
|
|
1954
|
+
finally {
|
|
1955
|
+
if (highVolumeWrite) {
|
|
1956
|
+
this.scheduleAndroidBalancedConnectionPriority(uuid);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
clearAndroidPriorityResetTimer(uuid) {
|
|
1962
|
+
const timerId = this.androidPriorityResetTimers.get(uuid);
|
|
1963
|
+
if (timerId !== undefined) {
|
|
1964
|
+
clearTimeout(timerId);
|
|
1965
|
+
this.androidPriorityResetTimers.delete(uuid);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
enableAndroidHighConnectionPriority(uuid) {
|
|
1969
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1970
|
+
if (reactNative.Platform.OS !== 'android')
|
|
1971
|
+
return;
|
|
1972
|
+
this.clearAndroidPriorityResetTimer(uuid);
|
|
1973
|
+
if (this.androidHighPriorityDevices.has(uuid))
|
|
1974
|
+
return;
|
|
1975
|
+
const transport = transportCache[uuid];
|
|
1976
|
+
if (!transport)
|
|
1977
|
+
return;
|
|
1978
|
+
try {
|
|
1979
|
+
transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.High);
|
|
1980
|
+
this.androidHighPriorityDevices.add(uuid);
|
|
1981
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
|
|
1982
|
+
priority: 'high',
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
catch (error) {
|
|
1986
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE high priority request failed', {
|
|
1987
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
scheduleAndroidBalancedConnectionPriority(uuid) {
|
|
1993
|
+
if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.has(uuid))
|
|
1994
|
+
return;
|
|
1995
|
+
this.clearAndroidPriorityResetTimer(uuid);
|
|
1996
|
+
const timerId = setTimeout(() => {
|
|
1997
|
+
this.androidPriorityResetTimers.delete(uuid);
|
|
1998
|
+
this.restoreAndroidConnectionPriority(uuid, transportCache[uuid]).catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE priority restore failed', error));
|
|
1999
|
+
}, ANDROID_HIGH_PRIORITY_IDLE_MS);
|
|
2000
|
+
this.androidPriorityResetTimers.set(uuid, timerId);
|
|
2001
|
+
}
|
|
2002
|
+
restoreAndroidConnectionPriority(uuid, transport) {
|
|
2003
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2004
|
+
this.clearAndroidPriorityResetTimer(uuid);
|
|
2005
|
+
if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.delete(uuid) || !transport) {
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
try {
|
|
2009
|
+
transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.Balanced);
|
|
2010
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
|
|
2011
|
+
priority: 'balanced',
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
catch (error) {
|
|
2015
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE balanced priority request failed', {
|
|
2016
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
1892
2019
|
});
|
|
1893
2020
|
}
|
|
1894
2021
|
createProtocolV2Adapter(uuid) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-react-native",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.71",
|
|
4
4
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"lint:fix": "eslint . --fix"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@onekeyfe/hd-core": "1.2.0-alpha.
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
23
|
+
"@onekeyfe/hd-core": "1.2.0-alpha.71",
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.71",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.71",
|
|
26
26
|
"@onekeyfe/react-native-ble-utils": "^0.1.6",
|
|
27
27
|
"react-native-ble-plx": "3.5.1"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "c58f39f2e5c54a98faa8844cd8b6b0453a17d80c"
|
|
30
30
|
}
|
package/src/BleTransport.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
hasWritableCapability,
|
|
3
|
+
resolveProtocolV2PacketCapacity,
|
|
4
|
+
shouldWriteProtocolV2WithResponse,
|
|
5
|
+
} from '../bleStrategy';
|
|
2
6
|
|
|
3
7
|
describe('React Native BLE strategy', () => {
|
|
4
8
|
test('accepts writeWithoutResponse-only characteristics', () => {
|
|
@@ -10,27 +14,35 @@ describe('React Native BLE strategy', () => {
|
|
|
10
14
|
expect(hasWritableCapability(characteristic)).toBe(true);
|
|
11
15
|
});
|
|
12
16
|
|
|
13
|
-
test('
|
|
14
|
-
expect(
|
|
17
|
+
test('rejects Protocol V2 packet sizing when MTU is unavailable', () => {
|
|
18
|
+
expect(() =>
|
|
15
19
|
resolveProtocolV2PacketCapacity({
|
|
16
20
|
platform: 'android',
|
|
17
|
-
androidPacketLength: 192,
|
|
18
21
|
mtu: null,
|
|
19
22
|
})
|
|
20
|
-
).
|
|
23
|
+
).toThrow('Protocol V2 BLE requires a negotiated MTU');
|
|
21
24
|
});
|
|
22
25
|
|
|
23
26
|
test('caps Android packet length by negotiated MTU payload', () => {
|
|
24
27
|
expect(
|
|
25
28
|
resolveProtocolV2PacketCapacity({
|
|
26
29
|
platform: 'android',
|
|
27
|
-
androidPacketLength:
|
|
30
|
+
androidPacketLength: 244,
|
|
28
31
|
mtu: 100,
|
|
29
32
|
})
|
|
30
33
|
).toBe(97);
|
|
31
34
|
});
|
|
32
35
|
|
|
33
|
-
test('
|
|
36
|
+
test('uses the Android 517 MTU payload when fully negotiated', () => {
|
|
37
|
+
expect(
|
|
38
|
+
resolveProtocolV2PacketCapacity({
|
|
39
|
+
platform: 'android',
|
|
40
|
+
mtu: 517,
|
|
41
|
+
})
|
|
42
|
+
).toBe(514);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('caps iOS packet length by the system-negotiated write payload', () => {
|
|
34
46
|
expect(
|
|
35
47
|
resolveProtocolV2PacketCapacity({
|
|
36
48
|
platform: 'ios',
|
|
@@ -39,4 +51,62 @@ describe('React Native BLE strategy', () => {
|
|
|
39
51
|
})
|
|
40
52
|
).toBe(244);
|
|
41
53
|
});
|
|
54
|
+
|
|
55
|
+
test('uses the reported iOS MTU without a compatibility fallback', () => {
|
|
56
|
+
expect(
|
|
57
|
+
resolveProtocolV2PacketCapacity({
|
|
58
|
+
platform: 'ios',
|
|
59
|
+
iosPacketLength: 128,
|
|
60
|
+
mtu: 23,
|
|
61
|
+
})
|
|
62
|
+
).toBe(20);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('uses a smaller system-negotiated iOS write payload', () => {
|
|
66
|
+
expect(
|
|
67
|
+
resolveProtocolV2PacketCapacity({
|
|
68
|
+
platform: 'ios',
|
|
69
|
+
iosPacketLength: 244,
|
|
70
|
+
mtu: 185,
|
|
71
|
+
})
|
|
72
|
+
).toBe(182);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('uses withoutResponse for a high-volume write unless explicitly overridden', () => {
|
|
76
|
+
const characteristic = {
|
|
77
|
+
isWritableWithResponse: true,
|
|
78
|
+
isWritableWithoutResponse: true,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
expect(
|
|
82
|
+
shouldWriteProtocolV2WithResponse({
|
|
83
|
+
platform: 'ios',
|
|
84
|
+
highVolume: true,
|
|
85
|
+
requestedWithResponse: false,
|
|
86
|
+
characteristic,
|
|
87
|
+
})
|
|
88
|
+
).toBe(false);
|
|
89
|
+
expect(
|
|
90
|
+
shouldWriteProtocolV2WithResponse({
|
|
91
|
+
platform: 'ios',
|
|
92
|
+
highVolume: true,
|
|
93
|
+
requestedWithResponse: true,
|
|
94
|
+
characteristic,
|
|
95
|
+
})
|
|
96
|
+
).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('falls back to withResponse when withoutResponse is unavailable', () => {
|
|
100
|
+
expect(
|
|
101
|
+
shouldWriteProtocolV2WithResponse({
|
|
102
|
+
platform: 'ios',
|
|
103
|
+
highVolume: true,
|
|
104
|
+
requestedWithResponse: false,
|
|
105
|
+
characteristic: {
|
|
106
|
+
isWritableWithResponse: true,
|
|
107
|
+
isWritableWithoutResponse: false,
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
).toBe(true);
|
|
111
|
+
});
|
|
42
112
|
});
|
|
@@ -7,6 +7,7 @@ import transportPackage, {
|
|
|
7
7
|
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
8
8
|
|
|
9
9
|
import ReactNativeBleTransport, {
|
|
10
|
+
BLE_WRITE_PACKET_TIMEOUT_MS,
|
|
10
11
|
configureProtocolV2BleTuning,
|
|
11
12
|
getFirmwareUploadWriteRetryType,
|
|
12
13
|
resetProtocolV2BleTuning,
|
|
@@ -32,6 +33,7 @@ jest.mock('react-native-ble-plx', () => ({
|
|
|
32
33
|
CharacteristicNotFound: 404,
|
|
33
34
|
},
|
|
34
35
|
BleManager: jest.fn(),
|
|
36
|
+
ConnectionPriority: { Balanced: 0, High: 1, LowPower: 2 },
|
|
35
37
|
ScanMode: { LowLatency: 2 },
|
|
36
38
|
}));
|
|
37
39
|
|
|
@@ -157,6 +159,7 @@ const createHarness = ({
|
|
|
157
159
|
id: uuid,
|
|
158
160
|
name: deviceName,
|
|
159
161
|
localName: deviceName,
|
|
162
|
+
mtu: 247,
|
|
160
163
|
serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
|
|
161
164
|
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
162
165
|
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
@@ -164,7 +167,9 @@ const createHarness = ({
|
|
|
164
167
|
disconnectCallback = callback;
|
|
165
168
|
return { remove: jest.fn() };
|
|
166
169
|
}),
|
|
167
|
-
};
|
|
170
|
+
} as any;
|
|
171
|
+
device.requestMTU = jest.fn(() => Promise.resolve(device));
|
|
172
|
+
device.requestConnectionPriority = jest.fn(() => Promise.resolve(device));
|
|
168
173
|
const bleManager = {
|
|
169
174
|
devices: jest.fn(() => Promise.resolve([device])),
|
|
170
175
|
connectedDevices: jest.fn(() => Promise.resolve([])),
|
|
@@ -172,18 +177,21 @@ const createHarness = ({
|
|
|
172
177
|
};
|
|
173
178
|
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
174
179
|
const emitter = new EventEmitter();
|
|
180
|
+
const logger = { debug: jest.fn(), error: jest.fn() };
|
|
175
181
|
transport.blePlxManager = bleManager;
|
|
176
182
|
transport.resolveCharacteristics = jest.fn(() =>
|
|
177
183
|
Promise.resolve({ writeCharacteristic, notifyCharacteristic })
|
|
178
184
|
);
|
|
179
|
-
transport.init(
|
|
185
|
+
transport.init(logger, emitter);
|
|
180
186
|
transport.configure(protocolV1Schema);
|
|
181
187
|
transport.configureProtocolV2(protocolV2Schema);
|
|
182
188
|
|
|
183
189
|
return {
|
|
184
190
|
transport,
|
|
185
191
|
emitter,
|
|
192
|
+
logger,
|
|
186
193
|
uuid,
|
|
194
|
+
device,
|
|
187
195
|
sentSeqs,
|
|
188
196
|
writeCharacteristic,
|
|
189
197
|
setShouldRespond(value: boolean) {
|
|
@@ -247,6 +255,7 @@ const createV1Harness = ({
|
|
|
247
255
|
id: uuid,
|
|
248
256
|
name: 'OneKey Classic',
|
|
249
257
|
localName: 'OneKey Classic',
|
|
258
|
+
mtu: 247,
|
|
250
259
|
serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
|
|
251
260
|
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
252
261
|
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
@@ -255,7 +264,8 @@ const createV1Harness = ({
|
|
|
255
264
|
disconnectSubscriptionRemovers.push(remove);
|
|
256
265
|
return { remove };
|
|
257
266
|
}),
|
|
258
|
-
};
|
|
267
|
+
} as any;
|
|
268
|
+
device.requestMTU = jest.fn(() => Promise.resolve(device));
|
|
259
269
|
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
260
270
|
const bleManager = {
|
|
261
271
|
devices: jest.fn(() => Promise.resolve([device])),
|
|
@@ -392,7 +402,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
392
402
|
});
|
|
393
403
|
|
|
394
404
|
test('actively probes Protocol V2 on iOS when only a name-derived hint is available', async () => {
|
|
395
|
-
const { transport, uuid, sentSeqs, writeCharacteristic } = createHarness({
|
|
405
|
+
const { transport, uuid, device, sentSeqs, writeCharacteristic } = createHarness({
|
|
396
406
|
deviceName: 'Pro2 6E9E',
|
|
397
407
|
});
|
|
398
408
|
|
|
@@ -400,6 +410,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
400
410
|
uuid,
|
|
401
411
|
protocolType: 'V2',
|
|
402
412
|
});
|
|
413
|
+
expect(device.requestMTU).toHaveBeenCalledWith(247);
|
|
403
414
|
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
|
|
404
415
|
|
|
405
416
|
await expect(
|
|
@@ -429,6 +440,57 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
429
440
|
await transport.release(uuid, true);
|
|
430
441
|
});
|
|
431
442
|
|
|
443
|
+
test('continues with the current MTU when the connected snapshot refresh fails', async () => {
|
|
444
|
+
const { transport, uuid, device } = createHarness();
|
|
445
|
+
const mtuError = new Error('MTU refresh failed');
|
|
446
|
+
device.requestMTU.mockRejectedValueOnce(mtuError);
|
|
447
|
+
|
|
448
|
+
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
449
|
+
uuid,
|
|
450
|
+
protocolType: 'V2',
|
|
451
|
+
});
|
|
452
|
+
expect(device.requestMTU).toHaveBeenCalledTimes(1);
|
|
453
|
+
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(247);
|
|
454
|
+
await transport.release(uuid, true);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('refreshes a transient bootloader MTU after notifications are ready', async () => {
|
|
458
|
+
const { transport, uuid, device } = createHarness();
|
|
459
|
+
device.mtu = 23;
|
|
460
|
+
device.requestMTU
|
|
461
|
+
.mockResolvedValueOnce(device)
|
|
462
|
+
.mockResolvedValueOnce(device)
|
|
463
|
+
.mockImplementationOnce(() => {
|
|
464
|
+
device.mtu = 247;
|
|
465
|
+
return Promise.resolve(device);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
469
|
+
uuid,
|
|
470
|
+
protocolType: 'V2',
|
|
471
|
+
});
|
|
472
|
+
expect(device.requestMTU).toHaveBeenCalledTimes(3);
|
|
473
|
+
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(247);
|
|
474
|
+
await transport.release(uuid, true);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test('continues with a low bootloader MTU when the bounded retry fails', async () => {
|
|
478
|
+
const { transport, uuid, device } = createHarness();
|
|
479
|
+
device.mtu = 23;
|
|
480
|
+
device.requestMTU
|
|
481
|
+
.mockResolvedValueOnce(device)
|
|
482
|
+
.mockResolvedValueOnce(device)
|
|
483
|
+
.mockRejectedValueOnce(new Error('bootloader MTU retry failed'));
|
|
484
|
+
|
|
485
|
+
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
486
|
+
uuid,
|
|
487
|
+
protocolType: 'V2',
|
|
488
|
+
});
|
|
489
|
+
expect(device.requestMTU).toHaveBeenCalledTimes(3);
|
|
490
|
+
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(23);
|
|
491
|
+
await transport.release(uuid, true);
|
|
492
|
+
});
|
|
493
|
+
|
|
432
494
|
test('reconnects before falling back to Protocol V1 after a fatal V2 probe failure', async () => {
|
|
433
495
|
setPlatformOS('android');
|
|
434
496
|
const { transport, uuid, device, notifySubscriptionRemovers, disconnectSubscriptionRemovers } =
|
|
@@ -564,16 +626,40 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
564
626
|
});
|
|
565
627
|
|
|
566
628
|
test('keeps iOS Protocol V2 high-volume calls on withoutResponse', async () => {
|
|
567
|
-
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
629
|
+
const { transport, uuid, logger, writeCharacteristic } = createHarness();
|
|
568
630
|
|
|
569
631
|
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
570
632
|
|
|
571
633
|
await transport.call(uuid, 'FileWrite', {});
|
|
572
|
-
|
|
634
|
+
await transport.call(uuid, 'FileWrite', {});
|
|
635
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(2);
|
|
573
636
|
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
637
|
+
expect(
|
|
638
|
+
logger.debug.mock.calls.filter(
|
|
639
|
+
([message]) =>
|
|
640
|
+
message === '[ReactNativeBleTransport] Protocol V2 high-volume write configured'
|
|
641
|
+
)
|
|
642
|
+
).toHaveLength(1);
|
|
574
643
|
await transport.release(uuid, true);
|
|
575
644
|
});
|
|
576
645
|
|
|
646
|
+
test('uses Android 517 MTU and high connection priority during Protocol V2 high-volume calls', async () => {
|
|
647
|
+
setPlatformOS('android');
|
|
648
|
+
const { transport, uuid, device } = createHarness();
|
|
649
|
+
|
|
650
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
651
|
+
expect(device.requestMTU).toHaveBeenCalledWith(517);
|
|
652
|
+
|
|
653
|
+
await transport.call(uuid, 'FileWrite', {});
|
|
654
|
+
await transport.call(uuid, 'FileWrite', {});
|
|
655
|
+
|
|
656
|
+
expect(device.requestConnectionPriority).toHaveBeenCalledTimes(1);
|
|
657
|
+
expect(device.requestConnectionPriority).toHaveBeenCalledWith(1);
|
|
658
|
+
|
|
659
|
+
await transport.release(uuid, true);
|
|
660
|
+
expect(device.requestConnectionPriority).toHaveBeenLastCalledWith(0);
|
|
661
|
+
});
|
|
662
|
+
|
|
577
663
|
test('uses withResponse for an iOS Protocol V2 firmware file write when requested', async () => {
|
|
578
664
|
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
579
665
|
|
|
@@ -630,7 +716,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
630
716
|
expect(writeWithoutResponse).not.toHaveBeenCalled();
|
|
631
717
|
});
|
|
632
718
|
|
|
633
|
-
test('
|
|
719
|
+
test('does not pace a one-packet Protocol V2 control write on iOS', async () => {
|
|
634
720
|
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
635
721
|
const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
|
|
636
722
|
const bleTransport = {
|
|
@@ -656,11 +742,10 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
656
742
|
jest.fn()
|
|
657
743
|
);
|
|
658
744
|
|
|
659
|
-
await Promise.resolve();
|
|
660
|
-
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5);
|
|
661
|
-
expect(writeWithoutResponse).not.toHaveBeenCalled();
|
|
662
|
-
|
|
663
745
|
await call;
|
|
746
|
+
expect(setTimeoutSpy.mock.calls.map(([, timeout]) => timeout)).toEqual([
|
|
747
|
+
BLE_WRITE_PACKET_TIMEOUT_MS,
|
|
748
|
+
]);
|
|
664
749
|
expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
665
750
|
} finally {
|
|
666
751
|
setTimeoutSpy.mockRestore();
|
|
@@ -766,4 +851,40 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
766
851
|
).rejects.toMatchObject({ errorCode: 205 });
|
|
767
852
|
expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
768
853
|
});
|
|
854
|
+
|
|
855
|
+
test('does not apply fixed burst or flush pauses to high-volume Protocol V2 writes', async () => {
|
|
856
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
857
|
+
const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
|
|
858
|
+
const bleTransport = {
|
|
859
|
+
mtuSize: 247,
|
|
860
|
+
writeCharacteristic: { writeWithoutResponse },
|
|
861
|
+
};
|
|
862
|
+
const context = {
|
|
863
|
+
messageName: 'FileWrite',
|
|
864
|
+
timeoutMs: 1000,
|
|
865
|
+
highVolume: true,
|
|
866
|
+
generation: 1,
|
|
867
|
+
signal: new AbortController().signal,
|
|
868
|
+
};
|
|
869
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
870
|
+
|
|
871
|
+
try {
|
|
872
|
+
await transport.writeProtocolV2Frame(
|
|
873
|
+
'test-device',
|
|
874
|
+
bleTransport,
|
|
875
|
+
new Uint8Array(600),
|
|
876
|
+
context,
|
|
877
|
+
jest.fn()
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
expect(writeWithoutResponse).toHaveBeenCalledTimes(3);
|
|
881
|
+
expect(setTimeoutSpy.mock.calls.map(([, timeout]) => timeout)).toEqual([
|
|
882
|
+
BLE_WRITE_PACKET_TIMEOUT_MS,
|
|
883
|
+
BLE_WRITE_PACKET_TIMEOUT_MS,
|
|
884
|
+
BLE_WRITE_PACKET_TIMEOUT_MS,
|
|
885
|
+
]);
|
|
886
|
+
} finally {
|
|
887
|
+
setTimeoutSpy.mockRestore();
|
|
888
|
+
}
|
|
889
|
+
});
|
|
769
890
|
});
|
package/src/bleStrategy.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ANDROID_PROTOCOL_V2_PACKET_LENGTH, IOS_PROTOCOL_V2_PACKET_LENGTH } from './constants';
|
|
2
2
|
|
|
3
3
|
export type BlePlatform = 'ios' | 'android' | string;
|
|
4
4
|
|
|
@@ -13,8 +13,8 @@ export function hasWritableCapability(characteristic: BleWriteCapability) {
|
|
|
13
13
|
|
|
14
14
|
export function resolveProtocolV2PacketCapacity({
|
|
15
15
|
platform,
|
|
16
|
-
iosPacketLength =
|
|
17
|
-
androidPacketLength =
|
|
16
|
+
iosPacketLength = IOS_PROTOCOL_V2_PACKET_LENGTH,
|
|
17
|
+
androidPacketLength = ANDROID_PROTOCOL_V2_PACKET_LENGTH,
|
|
18
18
|
mtu,
|
|
19
19
|
}: {
|
|
20
20
|
platform: BlePlatform;
|
|
@@ -22,14 +22,27 @@ export function resolveProtocolV2PacketCapacity({
|
|
|
22
22
|
androidPacketLength?: number;
|
|
23
23
|
mtu?: number | null;
|
|
24
24
|
}) {
|
|
25
|
-
if (
|
|
26
|
-
|
|
25
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= 3) {
|
|
26
|
+
throw new Error(`Protocol V2 BLE requires a negotiated MTU, received: ${String(mtu)}`);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const payloadLength = Math.floor(mtu) - 3;
|
|
30
|
+
const configuredPacketLength = platform === 'ios' ? iosPacketLength : androidPacketLength;
|
|
31
|
+
return Math.min(configuredPacketLength, payloadLength);
|
|
32
|
+
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
export function shouldWriteProtocolV2WithResponse({
|
|
35
|
+
platform,
|
|
36
|
+
highVolume,
|
|
37
|
+
requestedWithResponse,
|
|
38
|
+
characteristic,
|
|
39
|
+
}: {
|
|
40
|
+
platform: BlePlatform;
|
|
41
|
+
highVolume: boolean;
|
|
42
|
+
requestedWithResponse?: boolean;
|
|
43
|
+
characteristic: BleWriteCapability;
|
|
44
|
+
}) {
|
|
45
|
+
if (!characteristic.isWritableWithResponse) return false;
|
|
46
|
+
if (!characteristic.isWritableWithoutResponse) return true;
|
|
47
|
+
return requestedWithResponse === true || (platform === 'ios' && !highVolume);
|
|
35
48
|
}
|
package/src/constants.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { createKnownBleUuidAliases, matchesKnownBleUuid } from '@onekeyfe/hd-sha
|
|
|
2
2
|
|
|
3
3
|
export const IOS_PACKET_LENGTH = 128;
|
|
4
4
|
export const ANDROID_PACKET_LENGTH = 192;
|
|
5
|
-
export const
|
|
5
|
+
export const IOS_PROTOCOL_V2_PACKET_LENGTH = 244;
|
|
6
|
+
export const ANDROID_PROTOCOL_V2_PACKET_LENGTH = 514;
|
|
6
7
|
|
|
7
8
|
type BluetoothServices = Record<
|
|
8
9
|
string,
|
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;
|
|
@@ -162,8 +168,8 @@ export type ProtocolV2BleTuning = {
|
|
|
162
168
|
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
163
169
|
|
|
164
170
|
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
165
|
-
iosPacketLength:
|
|
166
|
-
androidPacketLength:
|
|
171
|
+
iosPacketLength: IOS_PROTOCOL_V2_PACKET_LENGTH,
|
|
172
|
+
androidPacketLength: ANDROID_PROTOCOL_V2_PACKET_LENGTH,
|
|
167
173
|
};
|
|
168
174
|
|
|
169
175
|
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
@@ -205,12 +211,19 @@ function getDeviceDisplayName(device?: Device | null) {
|
|
|
205
211
|
return device?.name || device?.localName || null;
|
|
206
212
|
}
|
|
207
213
|
|
|
208
|
-
const
|
|
214
|
+
const IOS_REQUEST_MTU = 247;
|
|
215
|
+
const ANDROID_REQUEST_MTU = 517;
|
|
216
|
+
const BLE_MTU_REFRESH_THRESHOLD = 247;
|
|
217
|
+
const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
|
|
218
|
+
const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
|
|
219
|
+
|
|
220
|
+
const getRequestedBleMtu = () =>
|
|
221
|
+
Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
|
|
209
222
|
|
|
210
223
|
const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
|
|
211
224
|
|
|
212
225
|
const connectOptions: Record<string, unknown> = {
|
|
213
|
-
requestMTU:
|
|
226
|
+
requestMTU: getRequestedBleMtu(),
|
|
214
227
|
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
215
228
|
refreshGatt: 'OnConnected',
|
|
216
229
|
};
|
|
@@ -264,23 +277,32 @@ const tryToGetConfiguration = (device: Device) => {
|
|
|
264
277
|
return infos;
|
|
265
278
|
};
|
|
266
279
|
|
|
267
|
-
const
|
|
268
|
-
|
|
280
|
+
const requestNegotiatedMtu = async (
|
|
281
|
+
device: Device,
|
|
282
|
+
stage: 'connected' | 'servicesAndNotifyReady',
|
|
283
|
+
attempt: number
|
|
284
|
+
) => {
|
|
285
|
+
if (Platform.OS !== 'ios' && Platform.OS !== 'android') return device;
|
|
269
286
|
|
|
270
287
|
try {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
requested: ANDROID_REQUEST_MTU,
|
|
275
|
-
actual: mtuDevice.mtu,
|
|
276
|
-
});
|
|
288
|
+
// iOS ignores the requested value but react-native-ble-plx returns a fresh
|
|
289
|
+
// Device snapshot whose MTU is derived from CoreBluetooth's maximum write length.
|
|
290
|
+
const mtuDevice = await device.requestMTU(getRequestedBleMtu());
|
|
277
291
|
return mtuDevice;
|
|
278
292
|
} catch (error) {
|
|
279
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
293
|
+
Log?.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
|
|
294
|
+
platform: Platform.OS,
|
|
295
|
+
stage,
|
|
296
|
+
attempt,
|
|
297
|
+
actual: device.mtu,
|
|
298
|
+
error: error instanceof Error ? error.message : String(error),
|
|
299
|
+
});
|
|
280
300
|
return device;
|
|
281
301
|
}
|
|
282
302
|
};
|
|
283
303
|
|
|
304
|
+
const resolveNegotiatedMtu = (device: Device) => requestNegotiatedMtu(device, 'connected', 0);
|
|
305
|
+
|
|
284
306
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
285
307
|
|
|
286
308
|
function remapError(error: IOBleErrorRemap) {
|
|
@@ -394,6 +416,12 @@ export default class ReactNativeBleTransport {
|
|
|
394
416
|
|
|
395
417
|
private disconnectEventTokens: Map<string, number> = new Map();
|
|
396
418
|
|
|
419
|
+
private protocolV2HighVolumeLogSignatures: Map<string, Set<string>> = new Map();
|
|
420
|
+
|
|
421
|
+
private androidHighPriorityDevices: Set<string> = new Set();
|
|
422
|
+
|
|
423
|
+
private androidPriorityResetTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
|
424
|
+
|
|
397
425
|
private nextMonitorToken = 1;
|
|
398
426
|
|
|
399
427
|
constructor(options: TransportOptions) {
|
|
@@ -775,9 +803,7 @@ export default class ReactNativeBleTransport {
|
|
|
775
803
|
const { writeCharacteristic, notifyCharacteristic } =
|
|
776
804
|
characteristics ?? (await this.resolveCharacteristicsWithTimeout(uuid, device));
|
|
777
805
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
778
|
-
|
|
779
|
-
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
780
|
-
}
|
|
806
|
+
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
|
|
781
807
|
const monitorToken = this.nextMonitorToken;
|
|
782
808
|
this.nextMonitorToken += 1;
|
|
783
809
|
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
@@ -791,6 +817,7 @@ export default class ReactNativeBleTransport {
|
|
|
791
817
|
notifyTransactionId
|
|
792
818
|
);
|
|
793
819
|
transportCache[uuid] = transport;
|
|
820
|
+
this.protocolV2HighVolumeLogSignatures.set(uuid, new Set());
|
|
794
821
|
this.protocolV2Assemblers.set(
|
|
795
822
|
uuid,
|
|
796
823
|
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
@@ -804,6 +831,40 @@ export default class ReactNativeBleTransport {
|
|
|
804
831
|
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
805
832
|
}
|
|
806
833
|
|
|
834
|
+
const initialMtu = transport.mtuSize;
|
|
835
|
+
let refreshAttempts = 0;
|
|
836
|
+
if (
|
|
837
|
+
(Platform.OS === 'ios' || Platform.OS === 'android') &&
|
|
838
|
+
(typeof transport.mtuSize !== 'number' || transport.mtuSize < BLE_MTU_REFRESH_THRESHOLD)
|
|
839
|
+
) {
|
|
840
|
+
refreshAttempts += 1;
|
|
841
|
+
let refreshedDevice = await requestNegotiatedMtu(
|
|
842
|
+
transport.device,
|
|
843
|
+
'servicesAndNotifyReady',
|
|
844
|
+
1
|
|
845
|
+
);
|
|
846
|
+
transport.device = refreshedDevice;
|
|
847
|
+
transport.mtuSize =
|
|
848
|
+
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
849
|
+
|
|
850
|
+
if (typeof transport.mtuSize !== 'number' || transport.mtuSize < BLE_MTU_REFRESH_THRESHOLD) {
|
|
851
|
+
await delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
|
|
852
|
+
refreshAttempts += 1;
|
|
853
|
+
refreshedDevice = await requestNegotiatedMtu(transport.device, 'servicesAndNotifyReady', 2);
|
|
854
|
+
transport.device = refreshedDevice;
|
|
855
|
+
transport.mtuSize =
|
|
856
|
+
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
Log?.debug('[ReactNativeBleTransport] BLE MTU ready', {
|
|
861
|
+
platform: Platform.OS,
|
|
862
|
+
requested: getRequestedBleMtu(),
|
|
863
|
+
initial: initialMtu,
|
|
864
|
+
actual: transport.mtuSize,
|
|
865
|
+
refreshAttempts,
|
|
866
|
+
});
|
|
867
|
+
|
|
807
868
|
return transport;
|
|
808
869
|
}
|
|
809
870
|
|
|
@@ -946,7 +1007,7 @@ export default class ReactNativeBleTransport {
|
|
|
946
1007
|
}
|
|
947
1008
|
}
|
|
948
1009
|
|
|
949
|
-
device = await
|
|
1010
|
+
device = await resolveNegotiatedMtu(device);
|
|
950
1011
|
const acquiredDevice = device;
|
|
951
1012
|
const { writeCharacteristic, notifyCharacteristic } =
|
|
952
1013
|
await this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
|
|
@@ -981,7 +1042,7 @@ export default class ReactNativeBleTransport {
|
|
|
981
1042
|
if (!currentTransport) {
|
|
982
1043
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
983
1044
|
}
|
|
984
|
-
this.attachDisconnectSubscription(currentTransport,
|
|
1045
|
+
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
985
1046
|
return { uuid, protocolType };
|
|
986
1047
|
} catch (error) {
|
|
987
1048
|
await this.release(uuid, true);
|
|
@@ -1151,6 +1212,8 @@ export default class ReactNativeBleTransport {
|
|
|
1151
1212
|
return Promise.resolve(true);
|
|
1152
1213
|
}
|
|
1153
1214
|
|
|
1215
|
+
await this.restoreAndroidConnectionPriority(uuid, transport);
|
|
1216
|
+
|
|
1154
1217
|
if (transport) {
|
|
1155
1218
|
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1156
1219
|
this.monitorTokens.delete(uuid);
|
|
@@ -1180,6 +1243,8 @@ export default class ReactNativeBleTransport {
|
|
|
1180
1243
|
delete transportCache[uuid];
|
|
1181
1244
|
}
|
|
1182
1245
|
|
|
1246
|
+
this.protocolV2HighVolumeLogSignatures.delete(uuid);
|
|
1247
|
+
|
|
1183
1248
|
this.deviceProtocol.delete(uuid);
|
|
1184
1249
|
this.probingProtocols.delete(uuid);
|
|
1185
1250
|
// Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
|
|
@@ -2106,9 +2171,12 @@ export default class ReactNativeBleTransport {
|
|
|
2106
2171
|
context: ProtocolV2CallContext,
|
|
2107
2172
|
assertCurrentGeneration: () => void
|
|
2108
2173
|
) {
|
|
2109
|
-
const shouldUseWriteWithResponse =
|
|
2110
|
-
|
|
2111
|
-
|
|
2174
|
+
const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
|
|
2175
|
+
platform: Platform.OS,
|
|
2176
|
+
highVolume: context.highVolume,
|
|
2177
|
+
requestedWithResponse: context.writeWithResponse,
|
|
2178
|
+
characteristic: transport.writeCharacteristic,
|
|
2179
|
+
});
|
|
2112
2180
|
let attempt = 0;
|
|
2113
2181
|
for (;;) {
|
|
2114
2182
|
assertCurrentGeneration();
|
|
@@ -2167,24 +2235,14 @@ export default class ReactNativeBleTransport {
|
|
|
2167
2235
|
platform: Platform.OS,
|
|
2168
2236
|
iosPacketLength: tuning.iosPacketLength,
|
|
2169
2237
|
androidPacketLength: tuning.androidPacketLength,
|
|
2170
|
-
mtu:
|
|
2238
|
+
mtu: transport.mtuSize,
|
|
2171
2239
|
});
|
|
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
2240
|
await writeProtocolV2BleFrame({
|
|
2179
2241
|
frame,
|
|
2180
2242
|
packetCapacity,
|
|
2181
2243
|
assertActive: assertCurrentGeneration,
|
|
2182
2244
|
signal: context.signal,
|
|
2183
2245
|
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
2246
|
wait: delay,
|
|
2189
2247
|
writePacket: packet =>
|
|
2190
2248
|
this.writeProtocolV2Packet(
|
|
@@ -2212,11 +2270,39 @@ export default class ReactNativeBleTransport {
|
|
|
2212
2270
|
|
|
2213
2271
|
if (highVolumeWrite) {
|
|
2214
2272
|
const tuning = getProtocolV2BleTuning();
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2273
|
+
const currentTransport = this.getCachedTransport(uuid);
|
|
2274
|
+
const writeWithResponse = shouldWriteProtocolV2WithResponse({
|
|
2275
|
+
platform: Platform.OS,
|
|
2276
|
+
highVolume: true,
|
|
2277
|
+
requestedWithResponse: options?.writeWithResponse,
|
|
2278
|
+
characteristic: currentTransport.writeCharacteristic,
|
|
2219
2279
|
});
|
|
2280
|
+
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
2281
|
+
platform: Platform.OS,
|
|
2282
|
+
iosPacketLength: tuning.iosPacketLength,
|
|
2283
|
+
androidPacketLength: tuning.androidPacketLength,
|
|
2284
|
+
mtu: currentTransport.mtuSize,
|
|
2285
|
+
});
|
|
2286
|
+
const writeMode = writeWithResponse ? 'withResponse' : 'withoutResponse';
|
|
2287
|
+
const logSignature = `${name}:${writeMode}:${String(
|
|
2288
|
+
currentTransport.mtuSize
|
|
2289
|
+
)}:${packetCapacity}`;
|
|
2290
|
+
const loggedSignatures =
|
|
2291
|
+
this.protocolV2HighVolumeLogSignatures.get(uuid) ?? new Set<string>();
|
|
2292
|
+
if (!loggedSignatures.has(logSignature)) {
|
|
2293
|
+
loggedSignatures.add(logSignature);
|
|
2294
|
+
this.protocolV2HighVolumeLogSignatures.set(uuid, loggedSignatures);
|
|
2295
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
2296
|
+
name,
|
|
2297
|
+
writeMode,
|
|
2298
|
+
reportedMtu: currentTransport.mtuSize,
|
|
2299
|
+
packetCapacity,
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
if (highVolumeWrite) {
|
|
2305
|
+
await this.enableAndroidHighConnectionPriority(uuid);
|
|
2220
2306
|
}
|
|
2221
2307
|
|
|
2222
2308
|
try {
|
|
@@ -2230,6 +2316,73 @@ export default class ReactNativeBleTransport {
|
|
|
2230
2316
|
} catch (e) {
|
|
2231
2317
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
2232
2318
|
throw e;
|
|
2319
|
+
} finally {
|
|
2320
|
+
if (highVolumeWrite) {
|
|
2321
|
+
this.scheduleAndroidBalancedConnectionPriority(uuid);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
private clearAndroidPriorityResetTimer(uuid: string) {
|
|
2327
|
+
const timerId = this.androidPriorityResetTimers.get(uuid);
|
|
2328
|
+
if (timerId !== undefined) {
|
|
2329
|
+
clearTimeout(timerId);
|
|
2330
|
+
this.androidPriorityResetTimers.delete(uuid);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
private async enableAndroidHighConnectionPriority(uuid: string) {
|
|
2335
|
+
if (Platform.OS !== 'android') return;
|
|
2336
|
+
|
|
2337
|
+
this.clearAndroidPriorityResetTimer(uuid);
|
|
2338
|
+
if (this.androidHighPriorityDevices.has(uuid)) return;
|
|
2339
|
+
|
|
2340
|
+
const transport = transportCache[uuid];
|
|
2341
|
+
if (!transport) return;
|
|
2342
|
+
|
|
2343
|
+
try {
|
|
2344
|
+
transport.device = await transport.device.requestConnectionPriority(ConnectionPriority.High);
|
|
2345
|
+
this.androidHighPriorityDevices.add(uuid);
|
|
2346
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
|
|
2347
|
+
priority: 'high',
|
|
2348
|
+
});
|
|
2349
|
+
} catch (error) {
|
|
2350
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE high priority request failed', {
|
|
2351
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
private scheduleAndroidBalancedConnectionPriority(uuid: string) {
|
|
2357
|
+
if (Platform.OS !== 'android' || !this.androidHighPriorityDevices.has(uuid)) return;
|
|
2358
|
+
|
|
2359
|
+
this.clearAndroidPriorityResetTimer(uuid);
|
|
2360
|
+
const timerId = setTimeout(() => {
|
|
2361
|
+
this.androidPriorityResetTimers.delete(uuid);
|
|
2362
|
+
this.restoreAndroidConnectionPriority(uuid, transportCache[uuid]).catch(error =>
|
|
2363
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE priority restore failed', error)
|
|
2364
|
+
);
|
|
2365
|
+
}, ANDROID_HIGH_PRIORITY_IDLE_MS);
|
|
2366
|
+
this.androidPriorityResetTimers.set(uuid, timerId);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
private async restoreAndroidConnectionPriority(uuid: string, transport?: BleTransport) {
|
|
2370
|
+
this.clearAndroidPriorityResetTimer(uuid);
|
|
2371
|
+
if (Platform.OS !== 'android' || !this.androidHighPriorityDevices.delete(uuid) || !transport) {
|
|
2372
|
+
return;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
try {
|
|
2376
|
+
transport.device = await transport.device.requestConnectionPriority(
|
|
2377
|
+
ConnectionPriority.Balanced
|
|
2378
|
+
);
|
|
2379
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
|
|
2380
|
+
priority: 'balanced',
|
|
2381
|
+
});
|
|
2382
|
+
} catch (error) {
|
|
2383
|
+
Log?.debug('[ReactNativeBleTransport] Android BLE balanced priority request failed', {
|
|
2384
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2385
|
+
});
|
|
2233
2386
|
}
|
|
2234
2387
|
}
|
|
2235
2388
|
|