@onekeyfe/hd-transport-react-native 1.1.27-alpha.5 → 1.1.27-alpha.7
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/index.d.ts +24 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +241 -76
- package/package.json +5 -5
- package/src/index.ts +283 -82
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import * as transport from '@onekeyfe/hd-transport';
|
|
2
2
|
import transport__default, { OneKeyDeviceInfoBase } from '@onekeyfe/hd-transport';
|
|
3
|
-
import { Device,
|
|
3
|
+
import { Device, Characteristic, Subscription, BleManager } from 'react-native-ble-plx';
|
|
4
4
|
import { Deferred } from '@onekeyfe/hd-shared';
|
|
5
5
|
import EventEmitter from 'events';
|
|
6
6
|
|
|
7
|
+
declare class BleTransport {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
device: Device;
|
|
11
|
+
mtuSize: number;
|
|
12
|
+
writeCharacteristic: Characteristic;
|
|
13
|
+
notifyCharacteristic: Characteristic;
|
|
14
|
+
notifySubscription?: Subscription;
|
|
15
|
+
disconnectSubscription?: Subscription;
|
|
16
|
+
static MAX_RETRIES: number;
|
|
17
|
+
static RETRY_DELAY: number;
|
|
18
|
+
constructor(device: Device, writeCharacteristic: Characteristic, notifyCharacteristic: Characteristic);
|
|
19
|
+
writeWithRetry(data: string, retryCount?: number): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
7
22
|
type TransportOptions = {
|
|
8
23
|
scanTimeout?: number;
|
|
9
24
|
};
|
|
@@ -12,6 +27,10 @@ type BleAcquireInput = {
|
|
|
12
27
|
forceCleanRunPromise?: boolean;
|
|
13
28
|
};
|
|
14
29
|
|
|
30
|
+
type ResolvedBleCharacteristics = {
|
|
31
|
+
writeCharacteristic: Characteristic;
|
|
32
|
+
notifyCharacteristic: Characteristic;
|
|
33
|
+
};
|
|
15
34
|
type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
16
35
|
declare class ReactNativeBleTransport {
|
|
17
36
|
blePlxManager: BleManager | undefined;
|
|
@@ -22,11 +41,15 @@ declare class ReactNativeBleTransport {
|
|
|
22
41
|
scanTimeout: number;
|
|
23
42
|
runPromise: Deferred<any> | null;
|
|
24
43
|
emitter?: EventEmitter;
|
|
44
|
+
firmwareUploadWriteRecoveryIds: Set<string>;
|
|
25
45
|
constructor(options: TransportOptions);
|
|
26
46
|
init(_logger: any, emitter: EventEmitter): void;
|
|
27
47
|
configure(signedData: any): void;
|
|
28
48
|
listen(): void;
|
|
29
49
|
getPlxManager(): Promise<BleManager>;
|
|
50
|
+
resolveCharacteristics(device: Device): Promise<ResolvedBleCharacteristics>;
|
|
51
|
+
attachDisconnectSubscription(transport: BleTransport, device: Device, uuid: string): void;
|
|
52
|
+
reconnectFirmwareUploadTransport(uuid: string, transport: BleTransport): Promise<void>;
|
|
30
53
|
enumerate(): Promise<IOneKeyDevice[]>;
|
|
31
54
|
acquire(input: BleAcquireInput): Promise<{
|
|
32
55
|
uuid: string;
|
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,EAE5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,SAAS,EAAE,EAGhB,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAIL,UAAU,IAAI,aAAa,EAE5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,SAAS,EAAE,EAGhB,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAahC,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAG1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAkBjE,KAAK,0BAA0B,GAAG;IAChC,mBAAmB,EAAE,cAAc,CAAC;IACpC,oBAAoB,EAAE,cAAc,CAAC;CACtC,CAAC;AA+CF,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,MAAM,CAAC;AA0C1D,MAAM,CAAC,OAAO,OAAO,uBAAuB;IAC1C,aAAa,EAAE,aAAa,GAAG,SAAS,CAAC;IAEzC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,IAAI,SAA6B;IAEjC,UAAU,UAAS;IAEnB,OAAO,UAAS;IAEhB,WAAW,SAAQ;IAEnB,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAExC,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,8BAA8B,cAAqB;gBAEvC,OAAO,EAAE,gBAAgB;IAIrC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY;IAIxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,MAAM;IAIN,aAAa,IAAI,OAAO,CAAC,aAAa,CAAC;IAMjC,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAuEjF,4BAA4B,CAAC,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IA0B5E,gCAAgC,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY;IA6CtE,SAAS;IA6FT,OAAO,CAAC,KAAK,EAAE,eAAe;;;IAmIpC,sBAAsB,CAAC,cAAc,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY;IAmF5E,OAAO,CAAC,IAAI,EAAE,MAAM;IA4BpB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIjE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IA6MpE,IAAI;IAIE,UAAU,CAAC,OAAO,EAAE,MAAM;IA0EhC,MAAM;CAOP"}
|
package/dist/index.js
CHANGED
|
@@ -214,6 +214,35 @@ BleTransport.RETRY_DELAY = 2000;
|
|
|
214
214
|
const { check, buildBuffers, receiveOne, parseConfigure } = transport__default["default"];
|
|
215
215
|
const Log = hdCore.getLogger(hdCore.LoggerNames.HdBleTransport);
|
|
216
216
|
const transportCache = {};
|
|
217
|
+
const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 : 5;
|
|
218
|
+
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
|
|
219
|
+
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
|
|
220
|
+
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
221
|
+
const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
|
|
222
|
+
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
223
|
+
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
224
|
+
const ANDROID_GATT_CONGESTED_STATUS = 143;
|
|
225
|
+
const delay = (ms) => new Promise(resolve => {
|
|
226
|
+
setTimeout(resolve, ms);
|
|
227
|
+
});
|
|
228
|
+
const getFirmwareUploadWriteRetryType = (error) => {
|
|
229
|
+
if (!error || typeof error !== 'object')
|
|
230
|
+
return null;
|
|
231
|
+
const bleWriteError = error;
|
|
232
|
+
if (bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected ||
|
|
233
|
+
bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.CharacteristicNotFound) {
|
|
234
|
+
return 'reconnectable';
|
|
235
|
+
}
|
|
236
|
+
if (bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
|
|
237
|
+
bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS) {
|
|
238
|
+
return 'congested';
|
|
239
|
+
}
|
|
240
|
+
const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
|
|
241
|
+
.filter(value => typeof value === 'string')
|
|
242
|
+
.join(' ');
|
|
243
|
+
return /GATT_CONGESTED|status\s*[:=]?\s*143/.test(text) ? 'congested' : null;
|
|
244
|
+
};
|
|
245
|
+
const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs = 1200) => Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
217
246
|
let connectOptions = {
|
|
218
247
|
requestMTU: 256,
|
|
219
248
|
timeout: 3000,
|
|
@@ -254,6 +283,7 @@ class ReactNativeBleTransport {
|
|
|
254
283
|
this.stopped = false;
|
|
255
284
|
this.scanTimeout = 3000;
|
|
256
285
|
this.runPromise = null;
|
|
286
|
+
this.firmwareUploadWriteRecoveryIds = new Set();
|
|
257
287
|
this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : 3000;
|
|
258
288
|
}
|
|
259
289
|
init(_logger, emitter) {
|
|
@@ -272,6 +302,134 @@ class ReactNativeBleTransport {
|
|
|
272
302
|
this.blePlxManager = new reactNativeBlePlx.BleManager();
|
|
273
303
|
return Promise.resolve(this.blePlxManager);
|
|
274
304
|
}
|
|
305
|
+
resolveCharacteristics(device) {
|
|
306
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
307
|
+
yield device.discoverAllServicesAndCharacteristics();
|
|
308
|
+
let infos = tryToGetConfiguration(device);
|
|
309
|
+
let characteristics;
|
|
310
|
+
if (!infos) {
|
|
311
|
+
for (const serviceUuid of getBluetoothServiceUuids()) {
|
|
312
|
+
try {
|
|
313
|
+
characteristics = yield device.characteristicsForService(serviceUuid);
|
|
314
|
+
infos = getInfosForServiceUuid(serviceUuid, 'classic');
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
catch (e) {
|
|
318
|
+
Log === null || Log === void 0 ? void 0 : Log.error(e);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!infos) {
|
|
323
|
+
try {
|
|
324
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
|
|
325
|
+
yield device.cancelConnection();
|
|
326
|
+
}
|
|
327
|
+
catch (e) {
|
|
328
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection error when service not found: ', e.message || e.reason);
|
|
329
|
+
}
|
|
330
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
|
|
331
|
+
}
|
|
332
|
+
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
333
|
+
if (!characteristics) {
|
|
334
|
+
characteristics = yield device.characteristicsForService(serviceUuid);
|
|
335
|
+
}
|
|
336
|
+
if (!characteristics) {
|
|
337
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound);
|
|
338
|
+
}
|
|
339
|
+
let writeCharacteristic;
|
|
340
|
+
let notifyCharacteristic;
|
|
341
|
+
for (const c of characteristics) {
|
|
342
|
+
if (c.uuid === writeUuid) {
|
|
343
|
+
writeCharacteristic = c;
|
|
344
|
+
}
|
|
345
|
+
else if (c.uuid === notifyUuid) {
|
|
346
|
+
notifyCharacteristic = c;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!writeCharacteristic) {
|
|
350
|
+
throw hdShared.ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
|
|
351
|
+
}
|
|
352
|
+
if (!notifyCharacteristic) {
|
|
353
|
+
throw hdShared.ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
|
|
354
|
+
}
|
|
355
|
+
if (!writeCharacteristic.isWritableWithResponse) {
|
|
356
|
+
throw hdShared.ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
|
|
357
|
+
}
|
|
358
|
+
if (!notifyCharacteristic.isNotifiable) {
|
|
359
|
+
throw hdShared.ERRORS.TypedError('BLECharacteristicNotNotifiable: notify characteristic not notifiable');
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
writeCharacteristic,
|
|
363
|
+
notifyCharacteristic,
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
attachDisconnectSubscription(transport, device, uuid) {
|
|
368
|
+
var _a;
|
|
369
|
+
(_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
|
|
370
|
+
transport.disconnectSubscription = device.onDisconnected(() => {
|
|
371
|
+
var _a;
|
|
372
|
+
if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
|
|
373
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect: ', device === null || device === void 0 ? void 0 : device.id);
|
|
378
|
+
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
|
|
379
|
+
name: device === null || device === void 0 ? void 0 : device.name,
|
|
380
|
+
id: device === null || device === void 0 ? void 0 : device.id,
|
|
381
|
+
connectId: device === null || device === void 0 ? void 0 : device.id,
|
|
382
|
+
});
|
|
383
|
+
if (this.runPromise) {
|
|
384
|
+
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch (e) {
|
|
388
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect error: ', e);
|
|
389
|
+
}
|
|
390
|
+
finally {
|
|
391
|
+
this.release(uuid);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
reconnectFirmwareUploadTransport(uuid, transport) {
|
|
396
|
+
var _a, _b;
|
|
397
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
398
|
+
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
399
|
+
try {
|
|
400
|
+
(_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
|
|
401
|
+
transport.disconnectSubscription = undefined;
|
|
402
|
+
(_b = transport.notifySubscription) === null || _b === void 0 ? void 0 : _b.remove();
|
|
403
|
+
transport.notifySubscription = undefined;
|
|
404
|
+
let { device } = transport;
|
|
405
|
+
const isConnected = yield device.isConnected().catch(() => false);
|
|
406
|
+
if (!isConnected) {
|
|
407
|
+
try {
|
|
408
|
+
device = yield device.connect(connectOptions);
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
|
|
412
|
+
e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
|
|
413
|
+
connectOptions = {};
|
|
414
|
+
device = yield device.connect();
|
|
415
|
+
}
|
|
416
|
+
else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
|
|
417
|
+
throw e;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
|
|
422
|
+
transport.device = device;
|
|
423
|
+
transport.writeCharacteristic = writeCharacteristic;
|
|
424
|
+
transport.notifyCharacteristic = notifyCharacteristic;
|
|
425
|
+
transport.notifySubscription = this._monitorCharacteristic(notifyCharacteristic, uuid);
|
|
426
|
+
this.attachDisconnectSubscription(transport, device, uuid);
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
this.firmwareUploadWriteRecoveryIds.delete(uuid);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
275
433
|
enumerate() {
|
|
276
434
|
return __awaiter(this, void 0, void 0, function* () {
|
|
277
435
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -441,60 +599,7 @@ class ReactNativeBleTransport {
|
|
|
441
599
|
}
|
|
442
600
|
}
|
|
443
601
|
}
|
|
444
|
-
yield
|
|
445
|
-
let infos = tryToGetConfiguration(device);
|
|
446
|
-
let characteristics;
|
|
447
|
-
if (!infos) {
|
|
448
|
-
for (const serviceUuid of getBluetoothServiceUuids()) {
|
|
449
|
-
try {
|
|
450
|
-
characteristics = yield device.characteristicsForService(serviceUuid);
|
|
451
|
-
infos = getInfosForServiceUuid(serviceUuid, 'classic');
|
|
452
|
-
break;
|
|
453
|
-
}
|
|
454
|
-
catch (e) {
|
|
455
|
-
Log === null || Log === void 0 ? void 0 : Log.error(e);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
if (!infos) {
|
|
460
|
-
try {
|
|
461
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
|
|
462
|
-
yield device.cancelConnection();
|
|
463
|
-
}
|
|
464
|
-
catch (e) {
|
|
465
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection error when service not found: ', e.message || e.reason);
|
|
466
|
-
}
|
|
467
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
|
|
468
|
-
}
|
|
469
|
-
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
470
|
-
if (!characteristics) {
|
|
471
|
-
characteristics = yield device.characteristicsForService(serviceUuid);
|
|
472
|
-
}
|
|
473
|
-
if (!characteristics) {
|
|
474
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound);
|
|
475
|
-
}
|
|
476
|
-
let writeCharacteristic;
|
|
477
|
-
let notifyCharacteristic;
|
|
478
|
-
for (const c of characteristics) {
|
|
479
|
-
if (c.uuid === writeUuid) {
|
|
480
|
-
writeCharacteristic = c;
|
|
481
|
-
}
|
|
482
|
-
else if (c.uuid === notifyUuid) {
|
|
483
|
-
notifyCharacteristic = c;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
if (!writeCharacteristic) {
|
|
487
|
-
throw hdShared.ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
|
|
488
|
-
}
|
|
489
|
-
if (!notifyCharacteristic) {
|
|
490
|
-
throw hdShared.ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
|
|
491
|
-
}
|
|
492
|
-
if (!writeCharacteristic.isWritableWithResponse) {
|
|
493
|
-
throw hdShared.ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
|
|
494
|
-
}
|
|
495
|
-
if (!notifyCharacteristic.isNotifiable) {
|
|
496
|
-
throw hdShared.ERRORS.TypedError('BLECharacteristicNotNotifiable: notify characteristic not notifiable');
|
|
497
|
-
}
|
|
602
|
+
const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
|
|
498
603
|
yield this.release(uuid);
|
|
499
604
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
500
605
|
transport.notifySubscription = this._monitorCharacteristic(transport.notifyCharacteristic, uuid);
|
|
@@ -504,26 +609,7 @@ class ReactNativeBleTransport {
|
|
|
504
609
|
id: device.id,
|
|
505
610
|
connectId: device.id,
|
|
506
611
|
});
|
|
507
|
-
transport
|
|
508
|
-
var _a;
|
|
509
|
-
try {
|
|
510
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect: ', device === null || device === void 0 ? void 0 : device.id);
|
|
511
|
-
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
|
|
512
|
-
name: device === null || device === void 0 ? void 0 : device.name,
|
|
513
|
-
id: device === null || device === void 0 ? void 0 : device.id,
|
|
514
|
-
connectId: device === null || device === void 0 ? void 0 : device.id,
|
|
515
|
-
});
|
|
516
|
-
if (this.runPromise) {
|
|
517
|
-
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError));
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
catch (e) {
|
|
521
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect error: ', e);
|
|
522
|
-
}
|
|
523
|
-
finally {
|
|
524
|
-
this.release(uuid);
|
|
525
|
-
}
|
|
526
|
-
});
|
|
612
|
+
this.attachDisconnectSubscription(transport, device, uuid);
|
|
527
613
|
return { uuid };
|
|
528
614
|
});
|
|
529
615
|
}
|
|
@@ -534,6 +620,10 @@ class ReactNativeBleTransport {
|
|
|
534
620
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
535
621
|
if (error) {
|
|
536
622
|
Log === null || Log === void 0 ? void 0 : Log.debug(`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${error}`);
|
|
623
|
+
if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
|
|
624
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('notify error ignored during FirmwareUpload write recovery: ', uuid);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
537
627
|
if (this.runPromise) {
|
|
538
628
|
let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
|
|
539
629
|
if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
|
|
@@ -659,6 +749,36 @@ class ReactNativeBleTransport {
|
|
|
659
749
|
}
|
|
660
750
|
});
|
|
661
751
|
}
|
|
752
|
+
function writeFirmwareUploadChunkedData(buffers, writeFunction, onError) {
|
|
753
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
754
|
+
let index = 0;
|
|
755
|
+
let packetsWritten = 0;
|
|
756
|
+
let chunk = ByteBuffer__default["default"].allocate(FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY);
|
|
757
|
+
while (index < buffers.length) {
|
|
758
|
+
const buffer = buffers[index].toBuffer();
|
|
759
|
+
chunk.append(buffer);
|
|
760
|
+
index += 1;
|
|
761
|
+
if (chunk.offset === FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY || index >= buffers.length) {
|
|
762
|
+
chunk.reset();
|
|
763
|
+
try {
|
|
764
|
+
yield writeFunction(chunk.toString('base64'));
|
|
765
|
+
packetsWritten += 1;
|
|
766
|
+
chunk = ByteBuffer__default["default"].allocate(FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY);
|
|
767
|
+
if (packetsWritten % FIRMWARE_UPLOAD_WRITE_BURST_SIZE === 0 && index < buffers.length) {
|
|
768
|
+
yield delay(FIRMWARE_UPLOAD_WRITE_PAUSE_MS);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
catch (e) {
|
|
772
|
+
onError(e);
|
|
773
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (packetsWritten > 0) {
|
|
778
|
+
yield delay(FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS);
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
}
|
|
662
782
|
if (name === 'EmmcFileWrite') {
|
|
663
783
|
yield writeChunkedData(buffers, data => transport$1.writeWithRetry(data), e => {
|
|
664
784
|
this.runPromise = null;
|
|
@@ -666,8 +786,53 @@ class ReactNativeBleTransport {
|
|
|
666
786
|
});
|
|
667
787
|
}
|
|
668
788
|
else if (name === 'FirmwareUpload') {
|
|
669
|
-
|
|
670
|
-
|
|
789
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write uses throttled BLE packets:', {
|
|
790
|
+
packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
|
|
791
|
+
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
792
|
+
pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
793
|
+
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
794
|
+
maxRetries: FIRMWARE_UPLOAD_WRITE_MAX_RETRIES,
|
|
795
|
+
});
|
|
796
|
+
yield writeFirmwareUploadChunkedData(buffers, (data) => __awaiter(this, void 0, void 0, function* () {
|
|
797
|
+
let attempt = 0;
|
|
798
|
+
while (true) {
|
|
799
|
+
try {
|
|
800
|
+
yield transport$1.writeCharacteristic.writeWithoutResponse(data);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
catch (error) {
|
|
804
|
+
const retryType = getFirmwareUploadWriteRetryType(error);
|
|
805
|
+
if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
806
|
+
throw error;
|
|
807
|
+
}
|
|
808
|
+
const shouldReconnect = retryType === 'reconnectable';
|
|
809
|
+
const delayMs = shouldReconnect
|
|
810
|
+
? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
|
|
811
|
+
: resolveFirmwareUploadRetryDelay(attempt);
|
|
812
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
|
|
813
|
+
attempt: attempt + 1,
|
|
814
|
+
delayMs,
|
|
815
|
+
reconnect: shouldReconnect,
|
|
816
|
+
error,
|
|
817
|
+
});
|
|
818
|
+
if (shouldReconnect) {
|
|
819
|
+
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
820
|
+
}
|
|
821
|
+
yield delay(delayMs);
|
|
822
|
+
attempt += 1;
|
|
823
|
+
if (shouldReconnect) {
|
|
824
|
+
try {
|
|
825
|
+
yield this.reconnectFirmwareUploadTransport(uuid, transport$1);
|
|
826
|
+
}
|
|
827
|
+
catch (e) {
|
|
828
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
|
|
829
|
+
if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
830
|
+
throw e;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
671
836
|
}), e => {
|
|
672
837
|
this.runPromise = null;
|
|
673
838
|
Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-react-native",
|
|
3
|
-
"version": "1.1.27-alpha.
|
|
3
|
+
"version": "1.1.27-alpha.7",
|
|
4
4
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
"lint:fix": "eslint . --fix"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@onekeyfe/hd-core": "1.1.27-alpha.
|
|
23
|
-
"@onekeyfe/hd-shared": "1.1.27-alpha.
|
|
24
|
-
"@onekeyfe/hd-transport": "1.1.27-alpha.
|
|
22
|
+
"@onekeyfe/hd-core": "1.1.27-alpha.7",
|
|
23
|
+
"@onekeyfe/hd-shared": "1.1.27-alpha.7",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.1.27-alpha.7",
|
|
25
25
|
"@onekeyfe/react-native-ble-utils": "^0.1.4",
|
|
26
26
|
"react-native-ble-plx": "3.5.1"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "7e50a9bf14e4a67ca3b7ad67174345fe6f79fe5e"
|
|
29
29
|
}
|
package/src/index.ts
CHANGED
|
@@ -38,6 +38,60 @@ const { check, buildBuffers, receiveOne, parseConfigure } = transport;
|
|
|
38
38
|
const Log = getLogger(LoggerNames.HdBleTransport);
|
|
39
39
|
|
|
40
40
|
const transportCache: Record<string, BleTransport> = {};
|
|
41
|
+
const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
42
|
+
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
|
|
43
|
+
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
|
|
44
|
+
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
45
|
+
const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
|
|
46
|
+
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
47
|
+
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY =
|
|
48
|
+
Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
49
|
+
const ANDROID_GATT_CONGESTED_STATUS = 143;
|
|
50
|
+
|
|
51
|
+
type FirmwareUploadWriteRetryType = 'congested' | 'reconnectable';
|
|
52
|
+
type ResolvedBleCharacteristics = {
|
|
53
|
+
writeCharacteristic: Characteristic;
|
|
54
|
+
notifyCharacteristic: Characteristic;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const delay = (ms: number) =>
|
|
58
|
+
new Promise<void>(resolve => {
|
|
59
|
+
setTimeout(resolve, ms);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRetryType | null => {
|
|
63
|
+
if (!error || typeof error !== 'object') return null;
|
|
64
|
+
const bleWriteError = error as {
|
|
65
|
+
androidErrorCode?: unknown;
|
|
66
|
+
status?: unknown;
|
|
67
|
+
errorCode?: unknown;
|
|
68
|
+
reason?: unknown;
|
|
69
|
+
message?: unknown;
|
|
70
|
+
name?: unknown;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
bleWriteError.errorCode === BleErrorCode.DeviceDisconnected ||
|
|
75
|
+
bleWriteError.errorCode === BleErrorCode.CharacteristicNotFound
|
|
76
|
+
) {
|
|
77
|
+
return 'reconnectable';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
|
|
82
|
+
bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS
|
|
83
|
+
) {
|
|
84
|
+
return 'congested';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
|
|
88
|
+
.filter(value => typeof value === 'string')
|
|
89
|
+
.join(' ');
|
|
90
|
+
return /GATT_CONGESTED|status\s*[:=]?\s*143/.test(text) ? 'congested' : null;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
|
|
94
|
+
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
41
95
|
|
|
42
96
|
let connectOptions: Record<string, unknown> = {
|
|
43
97
|
requestMTU: 256,
|
|
@@ -104,6 +158,8 @@ export default class ReactNativeBleTransport {
|
|
|
104
158
|
|
|
105
159
|
emitter?: EventEmitter;
|
|
106
160
|
|
|
161
|
+
firmwareUploadWriteRecoveryIds = new Set<string>();
|
|
162
|
+
|
|
107
163
|
constructor(options: TransportOptions) {
|
|
108
164
|
this.scanTimeout = options.scanTimeout ?? 3000;
|
|
109
165
|
}
|
|
@@ -128,6 +184,143 @@ export default class ReactNativeBleTransport {
|
|
|
128
184
|
return Promise.resolve(this.blePlxManager);
|
|
129
185
|
}
|
|
130
186
|
|
|
187
|
+
async resolveCharacteristics(device: Device): Promise<ResolvedBleCharacteristics> {
|
|
188
|
+
await device.discoverAllServicesAndCharacteristics();
|
|
189
|
+
let infos = tryToGetConfiguration(device);
|
|
190
|
+
let characteristics: Characteristic[] | undefined;
|
|
191
|
+
|
|
192
|
+
if (!infos) {
|
|
193
|
+
for (const serviceUuid of getBluetoothServiceUuids()) {
|
|
194
|
+
try {
|
|
195
|
+
characteristics = await device.characteristicsForService(serviceUuid);
|
|
196
|
+
infos = getInfosForServiceUuid(serviceUuid, 'classic');
|
|
197
|
+
break;
|
|
198
|
+
} catch (e) {
|
|
199
|
+
Log?.error(e);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!infos) {
|
|
205
|
+
try {
|
|
206
|
+
Log?.debug('cancel connection when service not found');
|
|
207
|
+
await device.cancelConnection();
|
|
208
|
+
} catch (e) {
|
|
209
|
+
Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
|
|
210
|
+
}
|
|
211
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
215
|
+
|
|
216
|
+
if (!characteristics) {
|
|
217
|
+
characteristics = await device.characteristicsForService(serviceUuid);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!characteristics) {
|
|
221
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let writeCharacteristic;
|
|
225
|
+
let notifyCharacteristic;
|
|
226
|
+
for (const c of characteristics) {
|
|
227
|
+
if (c.uuid === writeUuid) {
|
|
228
|
+
writeCharacteristic = c;
|
|
229
|
+
} else if (c.uuid === notifyUuid) {
|
|
230
|
+
notifyCharacteristic = c;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!writeCharacteristic) {
|
|
235
|
+
throw ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!notifyCharacteristic) {
|
|
239
|
+
throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!writeCharacteristic.isWritableWithResponse) {
|
|
243
|
+
throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!notifyCharacteristic.isNotifiable) {
|
|
247
|
+
throw ERRORS.TypedError(
|
|
248
|
+
'BLECharacteristicNotNotifiable: notify characteristic not notifiable'
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
writeCharacteristic,
|
|
254
|
+
notifyCharacteristic,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
attachDisconnectSubscription(transport: BleTransport, device: Device, uuid: string) {
|
|
259
|
+
transport.disconnectSubscription?.remove();
|
|
260
|
+
transport.disconnectSubscription = device.onDisconnected(() => {
|
|
261
|
+
if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
|
|
262
|
+
Log?.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
Log?.debug('device disconnect: ', device?.id);
|
|
268
|
+
this.emitter?.emit('device-disconnect', {
|
|
269
|
+
name: device?.name,
|
|
270
|
+
id: device?.id,
|
|
271
|
+
connectId: device?.id,
|
|
272
|
+
});
|
|
273
|
+
if (this.runPromise) {
|
|
274
|
+
this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError));
|
|
275
|
+
}
|
|
276
|
+
} catch (e) {
|
|
277
|
+
Log?.debug('device disconnect error: ', e);
|
|
278
|
+
} finally {
|
|
279
|
+
this.release(uuid);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async reconnectFirmwareUploadTransport(uuid: string, transport: BleTransport) {
|
|
285
|
+
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
286
|
+
try {
|
|
287
|
+
transport.disconnectSubscription?.remove();
|
|
288
|
+
transport.disconnectSubscription = undefined;
|
|
289
|
+
transport.notifySubscription?.remove();
|
|
290
|
+
transport.notifySubscription = undefined;
|
|
291
|
+
|
|
292
|
+
let { device } = transport;
|
|
293
|
+
const isConnected = await device.isConnected().catch(() => false);
|
|
294
|
+
if (!isConnected) {
|
|
295
|
+
try {
|
|
296
|
+
device = await device.connect(connectOptions);
|
|
297
|
+
} catch (e) {
|
|
298
|
+
if (
|
|
299
|
+
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
300
|
+
e.errorCode === BleErrorCode.OperationCancelled
|
|
301
|
+
) {
|
|
302
|
+
connectOptions = {};
|
|
303
|
+
device = await device.connect();
|
|
304
|
+
} else if (e.errorCode !== BleErrorCode.DeviceAlreadyConnected) {
|
|
305
|
+
throw e;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(
|
|
311
|
+
device
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
transport.device = device;
|
|
315
|
+
transport.writeCharacteristic = writeCharacteristic;
|
|
316
|
+
transport.notifyCharacteristic = notifyCharacteristic;
|
|
317
|
+
transport.notifySubscription = this._monitorCharacteristic(notifyCharacteristic, uuid);
|
|
318
|
+
this.attachDisconnectSubscription(transport, device, uuid);
|
|
319
|
+
} finally {
|
|
320
|
+
this.firmwareUploadWriteRecoveryIds.delete(uuid);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
131
324
|
/**
|
|
132
325
|
* 获取设备列表
|
|
133
326
|
* 在搜索超过超时时间或设备数量大于 5 台时,返回 OneKey 设备,
|
|
@@ -334,69 +527,7 @@ export default class ReactNativeBleTransport {
|
|
|
334
527
|
}
|
|
335
528
|
}
|
|
336
529
|
|
|
337
|
-
await
|
|
338
|
-
let infos = tryToGetConfiguration(device);
|
|
339
|
-
let characteristics;
|
|
340
|
-
|
|
341
|
-
if (!infos) {
|
|
342
|
-
for (const serviceUuid of getBluetoothServiceUuids()) {
|
|
343
|
-
try {
|
|
344
|
-
characteristics = await device.characteristicsForService(serviceUuid);
|
|
345
|
-
infos = getInfosForServiceUuid(serviceUuid, 'classic');
|
|
346
|
-
break;
|
|
347
|
-
} catch (e) {
|
|
348
|
-
Log?.error(e);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (!infos) {
|
|
354
|
-
try {
|
|
355
|
-
Log?.debug('cancel connection when service not found');
|
|
356
|
-
await device.cancelConnection();
|
|
357
|
-
} catch (e) {
|
|
358
|
-
Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
|
|
359
|
-
}
|
|
360
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
364
|
-
|
|
365
|
-
if (!characteristics) {
|
|
366
|
-
characteristics = await device.characteristicsForService(serviceUuid);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (!characteristics) {
|
|
370
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
let writeCharacteristic;
|
|
374
|
-
let notifyCharacteristic;
|
|
375
|
-
for (const c of characteristics) {
|
|
376
|
-
if (c.uuid === writeUuid) {
|
|
377
|
-
writeCharacteristic = c;
|
|
378
|
-
} else if (c.uuid === notifyUuid) {
|
|
379
|
-
notifyCharacteristic = c;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (!writeCharacteristic) {
|
|
384
|
-
throw ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (!notifyCharacteristic) {
|
|
388
|
-
throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (!writeCharacteristic.isWritableWithResponse) {
|
|
392
|
-
throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (!notifyCharacteristic.isNotifiable) {
|
|
396
|
-
throw ERRORS.TypedError(
|
|
397
|
-
'BLECharacteristicNotNotifiable: notify characteristic not notifiable'
|
|
398
|
-
);
|
|
399
|
-
}
|
|
530
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(device);
|
|
400
531
|
|
|
401
532
|
// release transport before new transport instance
|
|
402
533
|
await this.release(uuid);
|
|
@@ -414,23 +545,7 @@ export default class ReactNativeBleTransport {
|
|
|
414
545
|
connectId: device.id,
|
|
415
546
|
});
|
|
416
547
|
|
|
417
|
-
transport
|
|
418
|
-
try {
|
|
419
|
-
Log?.debug('device disconnect: ', device?.id);
|
|
420
|
-
this.emitter?.emit('device-disconnect', {
|
|
421
|
-
name: device?.name,
|
|
422
|
-
id: device?.id,
|
|
423
|
-
connectId: device?.id,
|
|
424
|
-
});
|
|
425
|
-
if (this.runPromise) {
|
|
426
|
-
this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError));
|
|
427
|
-
}
|
|
428
|
-
} catch (e) {
|
|
429
|
-
Log?.debug('device disconnect error: ', e);
|
|
430
|
-
} finally {
|
|
431
|
-
this.release(uuid);
|
|
432
|
-
}
|
|
433
|
-
});
|
|
548
|
+
this.attachDisconnectSubscription(transport, device, uuid);
|
|
434
549
|
|
|
435
550
|
return { uuid };
|
|
436
551
|
}
|
|
@@ -445,6 +560,10 @@ export default class ReactNativeBleTransport {
|
|
|
445
560
|
error as unknown as string
|
|
446
561
|
}`
|
|
447
562
|
);
|
|
563
|
+
if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
|
|
564
|
+
Log?.debug('notify error ignored during FirmwareUpload write recovery: ', uuid);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
448
567
|
if (this.runPromise) {
|
|
449
568
|
let ERROR:
|
|
450
569
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
@@ -610,6 +729,41 @@ export default class ReactNativeBleTransport {
|
|
|
610
729
|
}
|
|
611
730
|
}
|
|
612
731
|
|
|
732
|
+
async function writeFirmwareUploadChunkedData(
|
|
733
|
+
buffers: ByteBuffer[],
|
|
734
|
+
writeFunction: (data: string) => Promise<void>,
|
|
735
|
+
onError: (e: any) => void
|
|
736
|
+
) {
|
|
737
|
+
let index = 0;
|
|
738
|
+
let packetsWritten = 0;
|
|
739
|
+
let chunk = ByteBuffer.allocate(FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY);
|
|
740
|
+
|
|
741
|
+
while (index < buffers.length) {
|
|
742
|
+
const buffer = buffers[index].toBuffer();
|
|
743
|
+
chunk.append(buffer);
|
|
744
|
+
index += 1;
|
|
745
|
+
|
|
746
|
+
if (chunk.offset === FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY || index >= buffers.length) {
|
|
747
|
+
chunk.reset();
|
|
748
|
+
try {
|
|
749
|
+
await writeFunction(chunk.toString('base64'));
|
|
750
|
+
packetsWritten += 1;
|
|
751
|
+
chunk = ByteBuffer.allocate(FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY);
|
|
752
|
+
if (packetsWritten % FIRMWARE_UPLOAD_WRITE_BURST_SIZE === 0 && index < buffers.length) {
|
|
753
|
+
await delay(FIRMWARE_UPLOAD_WRITE_PAUSE_MS);
|
|
754
|
+
}
|
|
755
|
+
} catch (e) {
|
|
756
|
+
onError(e);
|
|
757
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (packetsWritten > 0) {
|
|
763
|
+
await delay(FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
613
767
|
if (name === 'EmmcFileWrite') {
|
|
614
768
|
await writeChunkedData(
|
|
615
769
|
buffers,
|
|
@@ -620,10 +774,57 @@ export default class ReactNativeBleTransport {
|
|
|
620
774
|
}
|
|
621
775
|
);
|
|
622
776
|
} else if (name === 'FirmwareUpload') {
|
|
623
|
-
|
|
777
|
+
Log?.debug('[ReactNativeBleTransport] FirmwareUpload write uses throttled BLE packets:', {
|
|
778
|
+
packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
|
|
779
|
+
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
780
|
+
pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
781
|
+
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
782
|
+
maxRetries: FIRMWARE_UPLOAD_WRITE_MAX_RETRIES,
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
await writeFirmwareUploadChunkedData(
|
|
624
786
|
buffers,
|
|
625
787
|
async data => {
|
|
626
|
-
|
|
788
|
+
let attempt = 0;
|
|
789
|
+
// Retry only congestion. Other write errors should surface immediately.
|
|
790
|
+
// GATT_CONGESTED is usually transient backpressure from the Android BLE queue.
|
|
791
|
+
// eslint-disable-next-line no-constant-condition
|
|
792
|
+
while (true) {
|
|
793
|
+
try {
|
|
794
|
+
await transport.writeCharacteristic.writeWithoutResponse(data);
|
|
795
|
+
return;
|
|
796
|
+
} catch (error) {
|
|
797
|
+
const retryType = getFirmwareUploadWriteRetryType(error);
|
|
798
|
+
if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
799
|
+
throw error;
|
|
800
|
+
}
|
|
801
|
+
const shouldReconnect = retryType === 'reconnectable';
|
|
802
|
+
const delayMs = shouldReconnect
|
|
803
|
+
? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
|
|
804
|
+
: resolveFirmwareUploadRetryDelay(attempt);
|
|
805
|
+
Log?.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
|
|
806
|
+
attempt: attempt + 1,
|
|
807
|
+
delayMs,
|
|
808
|
+
reconnect: shouldReconnect,
|
|
809
|
+
error,
|
|
810
|
+
});
|
|
811
|
+
if (shouldReconnect) {
|
|
812
|
+
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
813
|
+
}
|
|
814
|
+
await delay(delayMs);
|
|
815
|
+
attempt += 1;
|
|
816
|
+
if (shouldReconnect) {
|
|
817
|
+
try {
|
|
818
|
+
await this.reconnectFirmwareUploadTransport(uuid, transport);
|
|
819
|
+
} catch (e) {
|
|
820
|
+
Log?.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
|
|
821
|
+
if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
822
|
+
throw e;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
627
828
|
},
|
|
628
829
|
e => {
|
|
629
830
|
this.runPromise = null;
|