@onekeyfe/hd-transport-react-native 1.1.27-patch.1 → 1.2.0-alpha.0
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/BleManager.d.ts.map +1 -1
- package/dist/BleTransport.d.ts +2 -0
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/bleStrategy.d.ts +15 -0
- package/dist/bleStrategy.d.ts.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +58 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +814 -122
- package/dist/logger.d.ts +14 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/BleManager.ts +11 -14
- package/src/BleTransport.ts +9 -5
- package/src/__tests__/bleStrategy.test.ts +47 -0
- package/src/bleStrategy.ts +60 -0
- package/src/constants.ts +25 -1
- package/src/index.ts +888 -65
- package/src/logger.ts +19 -0
- package/src/types.ts +3 -0
- package/src/utils/validateNotify.ts +4 -4
package/src/index.ts
CHANGED
|
@@ -9,33 +9,46 @@ import {
|
|
|
9
9
|
} from 'react-native-ble-plx';
|
|
10
10
|
import ByteBuffer from 'bytebuffer';
|
|
11
11
|
import transport, {
|
|
12
|
-
COMMON_HEADER_SIZE,
|
|
13
12
|
LogBlockCommand,
|
|
14
13
|
type OneKeyDeviceInfoBase,
|
|
14
|
+
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
15
|
+
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
16
|
+
type ProtocolType,
|
|
17
|
+
ProtocolV2FrameAssembler,
|
|
18
|
+
ProtocolV2Session,
|
|
19
|
+
type TransportCallOptions,
|
|
20
|
+
probeProtocolV2 as probeProtocolV2Helper,
|
|
15
21
|
} from '@onekeyfe/hd-transport';
|
|
16
22
|
import { ERRORS, HardwareErrorCode, createDeferred, isOnekeyDevice } from '@onekeyfe/hd-shared';
|
|
17
|
-
import { LoggerNames, getLogger } from '@onekeyfe/hd-core';
|
|
18
23
|
|
|
19
24
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
25
|
+
import {
|
|
26
|
+
hasWritableCapability,
|
|
27
|
+
resolveBleWriteMode,
|
|
28
|
+
resolveProtocolV2PacketCapacity,
|
|
29
|
+
} from './bleStrategy';
|
|
20
30
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
21
31
|
import {
|
|
22
32
|
ANDROID_PACKET_LENGTH,
|
|
23
33
|
IOS_PACKET_LENGTH,
|
|
34
|
+
getBleUuidKey,
|
|
24
35
|
getBluetoothServiceUuids,
|
|
25
36
|
getInfosForServiceUuid,
|
|
37
|
+
isSameBleUuid,
|
|
26
38
|
} from './constants';
|
|
27
39
|
import { isHeaderChunk } from './utils/validateNotify';
|
|
28
40
|
import BleTransport from './BleTransport';
|
|
29
41
|
import timer from './utils/timer';
|
|
42
|
+
import { bleLogger, setBleLogger } from './logger';
|
|
30
43
|
|
|
31
44
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
32
45
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
33
46
|
import type EventEmitter from 'events';
|
|
34
47
|
import type { BleAcquireInput, TransportOptions } from './types';
|
|
35
48
|
|
|
36
|
-
const { check,
|
|
49
|
+
const { check, ProtocolV1, parseConfigure } = transport;
|
|
37
50
|
|
|
38
|
-
const Log =
|
|
51
|
+
const Log = bleLogger;
|
|
39
52
|
|
|
40
53
|
const transportCache: Record<string, BleTransport> = {};
|
|
41
54
|
const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
@@ -92,9 +105,103 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
|
|
|
92
105
|
|
|
93
106
|
const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
|
|
94
107
|
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
108
|
+
const BLE_RESPONSE_TIMEOUT_MS = 30_000;
|
|
109
|
+
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
110
|
+
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
111
|
+
const DEVICE_SCAN_TIMEOUT_MS = 8000;
|
|
112
|
+
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
113
|
+
const ANDROID_NOTIFY_READY_DELAY_MS = 300;
|
|
114
|
+
const HIGH_VOLUME_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 6;
|
|
115
|
+
const HIGH_VOLUME_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 6 : 2;
|
|
116
|
+
const HIGH_VOLUME_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 20 : 8;
|
|
117
|
+
|
|
118
|
+
export type ProtocolV2BleTuning = {
|
|
119
|
+
iosPacketLength?: number;
|
|
120
|
+
androidPacketLength?: number;
|
|
121
|
+
highVolumeWriteBurstSize?: number;
|
|
122
|
+
highVolumeWritePauseMs?: number;
|
|
123
|
+
highVolumeWriteFlushDelayMs?: number;
|
|
124
|
+
highVolumeWriteWithResponse?: boolean;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
128
|
+
|
|
129
|
+
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
130
|
+
iosPacketLength: IOS_PACKET_LENGTH,
|
|
131
|
+
androidPacketLength: ANDROID_PACKET_LENGTH,
|
|
132
|
+
highVolumeWriteBurstSize: HIGH_VOLUME_WRITE_BURST_SIZE,
|
|
133
|
+
highVolumeWritePauseMs: HIGH_VOLUME_WRITE_PAUSE_MS,
|
|
134
|
+
highVolumeWriteFlushDelayMs: HIGH_VOLUME_WRITE_FLUSH_DELAY_MS,
|
|
135
|
+
highVolumeWriteWithResponse: false,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
139
|
+
|
|
140
|
+
const normalizePositiveInteger = (value: unknown, fallback: number) => {
|
|
141
|
+
const normalized = Number(value);
|
|
142
|
+
if (!Number.isFinite(normalized) || normalized <= 0) return fallback;
|
|
143
|
+
return Math.floor(normalized);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
|
|
147
|
+
protocolV2BleTuning = {
|
|
148
|
+
iosPacketLength: normalizePositiveInteger(
|
|
149
|
+
tuning.iosPacketLength,
|
|
150
|
+
protocolV2BleTuning.iosPacketLength
|
|
151
|
+
),
|
|
152
|
+
androidPacketLength: normalizePositiveInteger(
|
|
153
|
+
tuning.androidPacketLength,
|
|
154
|
+
protocolV2BleTuning.androidPacketLength
|
|
155
|
+
),
|
|
156
|
+
highVolumeWriteBurstSize: normalizePositiveInteger(
|
|
157
|
+
tuning.highVolumeWriteBurstSize,
|
|
158
|
+
protocolV2BleTuning.highVolumeWriteBurstSize
|
|
159
|
+
),
|
|
160
|
+
highVolumeWritePauseMs: normalizePositiveInteger(
|
|
161
|
+
tuning.highVolumeWritePauseMs,
|
|
162
|
+
protocolV2BleTuning.highVolumeWritePauseMs
|
|
163
|
+
),
|
|
164
|
+
highVolumeWriteFlushDelayMs: normalizePositiveInteger(
|
|
165
|
+
tuning.highVolumeWriteFlushDelayMs,
|
|
166
|
+
protocolV2BleTuning.highVolumeWriteFlushDelayMs
|
|
167
|
+
),
|
|
168
|
+
highVolumeWriteWithResponse:
|
|
169
|
+
tuning.highVolumeWriteWithResponse ?? protocolV2BleTuning.highVolumeWriteWithResponse,
|
|
170
|
+
};
|
|
171
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning configured:', protocolV2BleTuning);
|
|
172
|
+
}
|
|
95
173
|
|
|
96
|
-
|
|
97
|
-
|
|
174
|
+
export function resetProtocolV2BleTuning() {
|
|
175
|
+
protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
176
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning reset:', protocolV2BleTuning);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function getProtocolV2BleTuning() {
|
|
180
|
+
return { ...protocolV2BleTuning };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
184
|
+
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getDeviceDisplayName(device?: Device | null) {
|
|
188
|
+
return device?.name || device?.localName || null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function isGenericBleService(uuid?: string | null) {
|
|
192
|
+
return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function hasKnownOneKeyService(device?: Device | null) {
|
|
196
|
+
return (device?.serviceUUIDs ?? []).some(serviceUuid =>
|
|
197
|
+
getInfosForServiceUuid(serviceUuid, 'classic')
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const ANDROID_REQUEST_MTU = 256;
|
|
202
|
+
|
|
203
|
+
const connectOptions: Record<string, unknown> = {
|
|
204
|
+
requestMTU: ANDROID_REQUEST_MTU,
|
|
98
205
|
timeout: 3000,
|
|
99
206
|
refreshGatt: 'OnConnected',
|
|
100
207
|
};
|
|
@@ -103,12 +210,29 @@ export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
|
103
210
|
|
|
104
211
|
const tryToGetConfiguration = (device: Device) => {
|
|
105
212
|
if (!device || !device.serviceUUIDs) return null;
|
|
106
|
-
const
|
|
213
|
+
const serviceUUID = device.serviceUUIDs.find(uuid => getInfosForServiceUuid(uuid, 'classic'));
|
|
214
|
+
if (!serviceUUID) return null;
|
|
107
215
|
const infos = getInfosForServiceUuid(serviceUUID, 'classic');
|
|
108
216
|
if (!infos) return null;
|
|
109
217
|
return infos;
|
|
110
218
|
};
|
|
111
219
|
|
|
220
|
+
const requestAndroidMtu = async (device: Device) => {
|
|
221
|
+
if (Platform.OS !== 'android') return device;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
|
|
225
|
+
Log?.debug('[ReactNativeBleTransport] Android MTU requested:', {
|
|
226
|
+
requested: ANDROID_REQUEST_MTU,
|
|
227
|
+
mtu: mtuDevice.mtu,
|
|
228
|
+
});
|
|
229
|
+
return mtuDevice;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
Log?.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
|
|
232
|
+
return device;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
112
236
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
113
237
|
|
|
114
238
|
function remapError(error: IOBleErrorRemap) {
|
|
@@ -146,13 +270,15 @@ export default class ReactNativeBleTransport {
|
|
|
146
270
|
|
|
147
271
|
_messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
148
272
|
|
|
273
|
+
_messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
274
|
+
|
|
149
275
|
name = 'ReactNativeBleTransport';
|
|
150
276
|
|
|
151
277
|
configured = false;
|
|
152
278
|
|
|
153
279
|
stopped = false;
|
|
154
280
|
|
|
155
|
-
scanTimeout =
|
|
281
|
+
scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
|
|
156
282
|
|
|
157
283
|
runPromise: Deferred<any> | null = null;
|
|
158
284
|
|
|
@@ -160,11 +286,31 @@ export default class ReactNativeBleTransport {
|
|
|
160
286
|
|
|
161
287
|
firmwareUploadWriteRecoveryIds = new Set<string>();
|
|
162
288
|
|
|
289
|
+
/** Per-device protocol type detected by active wire-level probe after connect. */
|
|
290
|
+
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
291
|
+
|
|
292
|
+
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
293
|
+
|
|
294
|
+
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
295
|
+
|
|
296
|
+
private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
|
|
297
|
+
|
|
298
|
+
private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
299
|
+
|
|
300
|
+
private activeProtocolV2Call: { uuid: string; token: number } | null = null;
|
|
301
|
+
|
|
302
|
+
private nextProtocolV2CallToken = 1;
|
|
303
|
+
|
|
304
|
+
private monitorTokens: Map<string, number> = new Map();
|
|
305
|
+
|
|
306
|
+
private nextMonitorToken = 1;
|
|
307
|
+
|
|
163
308
|
constructor(options: TransportOptions) {
|
|
164
|
-
this.scanTimeout = options.scanTimeout ??
|
|
309
|
+
this.scanTimeout = options.scanTimeout ?? DEVICE_SCAN_TIMEOUT_MS;
|
|
165
310
|
}
|
|
166
311
|
|
|
167
|
-
init(
|
|
312
|
+
init(logger: any, emitter: EventEmitter) {
|
|
313
|
+
setBleLogger(logger);
|
|
168
314
|
this.emitter = emitter;
|
|
169
315
|
}
|
|
170
316
|
|
|
@@ -174,6 +320,11 @@ export default class ReactNativeBleTransport {
|
|
|
174
320
|
this._messages = messages;
|
|
175
321
|
}
|
|
176
322
|
|
|
323
|
+
configureProtocolV2(signedData: any) {
|
|
324
|
+
this._messagesV2 = parseConfigure(signedData);
|
|
325
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 schema configured');
|
|
326
|
+
}
|
|
327
|
+
|
|
177
328
|
listen() {
|
|
178
329
|
// empty
|
|
179
330
|
}
|
|
@@ -201,7 +352,29 @@ export default class ReactNativeBleTransport {
|
|
|
201
352
|
}
|
|
202
353
|
}
|
|
203
354
|
|
|
355
|
+
let fallbackServiceUuid: string | undefined;
|
|
356
|
+
|
|
204
357
|
if (!infos) {
|
|
358
|
+
const services = await device.services();
|
|
359
|
+
Log?.debug(
|
|
360
|
+
'[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:',
|
|
361
|
+
services?.map(service => service.uuid)
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const knownService = services.find(service =>
|
|
365
|
+
getInfosForServiceUuid(service.uuid, 'classic')
|
|
366
|
+
);
|
|
367
|
+
const fallbackService =
|
|
368
|
+
knownService ?? services.find(service => !isGenericBleService(service.uuid)) ?? services[0];
|
|
369
|
+
|
|
370
|
+
if (fallbackService) {
|
|
371
|
+
fallbackServiceUuid = fallbackService.uuid;
|
|
372
|
+
characteristics = await device.characteristicsForService(fallbackService.uuid);
|
|
373
|
+
Log?.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!infos && !fallbackServiceUuid) {
|
|
205
378
|
try {
|
|
206
379
|
Log?.debug('cancel connection when service not found');
|
|
207
380
|
await device.cancelConnection();
|
|
@@ -211,7 +384,13 @@ export default class ReactNativeBleTransport {
|
|
|
211
384
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
212
385
|
}
|
|
213
386
|
|
|
214
|
-
const
|
|
387
|
+
const serviceUuid = infos?.serviceUuid ?? fallbackServiceUuid;
|
|
388
|
+
const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
|
|
389
|
+
const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
|
|
390
|
+
|
|
391
|
+
if (!serviceUuid) {
|
|
392
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
393
|
+
}
|
|
215
394
|
|
|
216
395
|
if (!characteristics) {
|
|
217
396
|
characteristics = await device.characteristicsForService(serviceUuid);
|
|
@@ -224,9 +403,9 @@ export default class ReactNativeBleTransport {
|
|
|
224
403
|
let writeCharacteristic;
|
|
225
404
|
let notifyCharacteristic;
|
|
226
405
|
for (const c of characteristics) {
|
|
227
|
-
if (c.uuid
|
|
406
|
+
if (isSameBleUuid(c.uuid, writeUuid)) {
|
|
228
407
|
writeCharacteristic = c;
|
|
229
|
-
} else if (c.uuid
|
|
408
|
+
} else if (isSameBleUuid(c.uuid, notifyUuid)) {
|
|
230
409
|
notifyCharacteristic = c;
|
|
231
410
|
}
|
|
232
411
|
}
|
|
@@ -239,7 +418,7 @@ export default class ReactNativeBleTransport {
|
|
|
239
418
|
throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
|
|
240
419
|
}
|
|
241
420
|
|
|
242
|
-
if (!writeCharacteristic
|
|
421
|
+
if (!hasWritableCapability(writeCharacteristic)) {
|
|
243
422
|
throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
|
|
244
423
|
}
|
|
245
424
|
|
|
@@ -262,6 +441,10 @@ export default class ReactNativeBleTransport {
|
|
|
262
441
|
Log?.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
|
|
263
442
|
return;
|
|
264
443
|
}
|
|
444
|
+
if (transportCache[uuid] !== transport) {
|
|
445
|
+
Log?.debug('device disconnect ignored for stale transport: ', device?.id);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
265
448
|
|
|
266
449
|
try {
|
|
267
450
|
Log?.debug('device disconnect: ', device?.id);
|
|
@@ -271,12 +454,14 @@ export default class ReactNativeBleTransport {
|
|
|
271
454
|
connectId: device?.id,
|
|
272
455
|
});
|
|
273
456
|
if (this.runPromise) {
|
|
274
|
-
|
|
457
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
|
|
458
|
+
this.runPromise.reject(error);
|
|
459
|
+
this.rejectAllProtocolV2Frames(error);
|
|
275
460
|
}
|
|
276
461
|
} catch (e) {
|
|
277
462
|
Log?.debug('device disconnect error: ', e);
|
|
278
463
|
} finally {
|
|
279
|
-
this.release(uuid);
|
|
464
|
+
this.release(uuid, true);
|
|
280
465
|
}
|
|
281
466
|
});
|
|
282
467
|
}
|
|
@@ -299,7 +484,6 @@ export default class ReactNativeBleTransport {
|
|
|
299
484
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
300
485
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
301
486
|
) {
|
|
302
|
-
connectOptions = {};
|
|
303
487
|
device = await device.connect();
|
|
304
488
|
} else if (e.errorCode !== BleErrorCode.DeviceAlreadyConnected) {
|
|
305
489
|
throw e;
|
|
@@ -314,7 +498,18 @@ export default class ReactNativeBleTransport {
|
|
|
314
498
|
transport.device = device;
|
|
315
499
|
transport.writeCharacteristic = writeCharacteristic;
|
|
316
500
|
transport.notifyCharacteristic = notifyCharacteristic;
|
|
317
|
-
|
|
501
|
+
const monitorToken = this.nextMonitorToken;
|
|
502
|
+
this.nextMonitorToken += 1;
|
|
503
|
+
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
504
|
+
transport.monitorToken = monitorToken;
|
|
505
|
+
transport.notifyTransactionId = notifyTransactionId;
|
|
506
|
+
this.monitorTokens.set(uuid, monitorToken);
|
|
507
|
+
transport.notifySubscription = this._monitorCharacteristic(
|
|
508
|
+
notifyCharacteristic,
|
|
509
|
+
uuid,
|
|
510
|
+
monitorToken,
|
|
511
|
+
notifyTransactionId
|
|
512
|
+
);
|
|
318
513
|
this.attachDisconnectSubscription(transport, device, uuid);
|
|
319
514
|
} finally {
|
|
320
515
|
this.firmwareUploadWriteRecoveryIds.delete(uuid);
|
|
@@ -360,6 +555,7 @@ export default class ReactNativeBleTransport {
|
|
|
360
555
|
blePlxManager.startDeviceScan(
|
|
361
556
|
null,
|
|
362
557
|
{
|
|
558
|
+
allowDuplicates: true,
|
|
363
559
|
scanMode: ScanMode.LowLatency,
|
|
364
560
|
},
|
|
365
561
|
(error, device) => {
|
|
@@ -387,14 +583,41 @@ export default class ReactNativeBleTransport {
|
|
|
387
583
|
return;
|
|
388
584
|
}
|
|
389
585
|
|
|
390
|
-
|
|
586
|
+
const displayName = getDeviceDisplayName(device);
|
|
587
|
+
const isOneKey =
|
|
588
|
+
isOnekeyDevice(device?.name ?? null, device?.id) ||
|
|
589
|
+
isOnekeyDevice(device?.localName ?? null, device?.id) ||
|
|
590
|
+
hasKnownOneKeyService(device);
|
|
591
|
+
const shouldTraceCandidate =
|
|
592
|
+
!!displayName && /onekey|bixinkey|pro\s*2|pro\b|touch|^k\d|^t\d/i.test(displayName);
|
|
593
|
+
|
|
594
|
+
if (shouldTraceCandidate) {
|
|
595
|
+
Log?.debug('[ReactNativeBleTransport] scan candidate', {
|
|
596
|
+
name: device?.name,
|
|
597
|
+
localName: device?.localName,
|
|
598
|
+
id: device?.id,
|
|
599
|
+
serviceUUIDs: device?.serviceUUIDs,
|
|
600
|
+
accepted: isOneKey,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (isOneKey) {
|
|
391
605
|
Log?.debug('search device start ======================');
|
|
392
|
-
const { name, localName, id } = device ?? {};
|
|
606
|
+
const { name, localName, id, serviceUUIDs } = device ?? {};
|
|
393
607
|
Log?.debug(
|
|
394
|
-
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${
|
|
608
|
+
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${
|
|
609
|
+
id ?? ''
|
|
610
|
+
}\nserviceUUIDs: ${(serviceUUIDs ?? []).join(',')}`
|
|
395
611
|
);
|
|
396
612
|
addDevice(device as unknown as Device);
|
|
397
613
|
Log?.debug('search device end ======================\n');
|
|
614
|
+
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
615
|
+
Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
|
|
616
|
+
name: device?.name,
|
|
617
|
+
localName: device?.localName,
|
|
618
|
+
id: device?.id,
|
|
619
|
+
serviceUUIDs: device?.serviceUUIDs,
|
|
620
|
+
});
|
|
398
621
|
}
|
|
399
622
|
}
|
|
400
623
|
);
|
|
@@ -408,7 +631,16 @@ export default class ReactNativeBleTransport {
|
|
|
408
631
|
|
|
409
632
|
const addDevice = (device: Device) => {
|
|
410
633
|
if (deviceList.every(d => d.id !== device.id)) {
|
|
411
|
-
|
|
634
|
+
const displayName = getDeviceDisplayName(device) ?? 'Unknown BLE Device';
|
|
635
|
+
const protocolHint = inferProtocolHintFromDeviceName(displayName);
|
|
636
|
+
if (protocolHint) {
|
|
637
|
+
this.deviceProtocolHints.set(device.id, protocolHint);
|
|
638
|
+
}
|
|
639
|
+
deviceList.push({
|
|
640
|
+
...device,
|
|
641
|
+
name: displayName,
|
|
642
|
+
commType: 'ble',
|
|
643
|
+
} as IOneKeyDevice);
|
|
412
644
|
}
|
|
413
645
|
};
|
|
414
646
|
|
|
@@ -420,25 +652,41 @@ export default class ReactNativeBleTransport {
|
|
|
420
652
|
}
|
|
421
653
|
|
|
422
654
|
async acquire(input: BleAcquireInput) {
|
|
423
|
-
const { uuid, forceCleanRunPromise } = input;
|
|
655
|
+
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
424
656
|
|
|
425
657
|
if (!uuid) {
|
|
426
658
|
throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
|
|
427
659
|
}
|
|
428
660
|
|
|
429
|
-
|
|
661
|
+
const cachedTransport = transportCache[uuid];
|
|
662
|
+
if (cachedTransport) {
|
|
663
|
+
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
664
|
+
const isCachedDeviceConnected = await cachedTransport.device.isConnected().catch(() => false);
|
|
665
|
+
if (
|
|
666
|
+
isCachedDeviceConnected &&
|
|
667
|
+
cachedProtocol &&
|
|
668
|
+
(!expectedProtocol || cachedProtocol === expectedProtocol)
|
|
669
|
+
) {
|
|
670
|
+
Log?.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
|
|
671
|
+
return { uuid, protocolType: cachedProtocol };
|
|
672
|
+
}
|
|
430
673
|
|
|
431
|
-
if (transportCache[uuid]) {
|
|
432
674
|
/**
|
|
433
|
-
* If the transport is not
|
|
434
|
-
* it
|
|
675
|
+
* If the transport is not reusable due to a protocol mismatch or stale
|
|
676
|
+
* connection, clean it up before creating a new transport instance.
|
|
435
677
|
*/
|
|
436
|
-
Log?.debug('transport not
|
|
437
|
-
await this.release(uuid);
|
|
678
|
+
Log?.debug('transport not reusable, will release: ', uuid);
|
|
679
|
+
await this.release(uuid, true);
|
|
438
680
|
}
|
|
439
681
|
|
|
682
|
+
let device: Device | null = null;
|
|
683
|
+
|
|
440
684
|
if (forceCleanRunPromise && this.runPromise) {
|
|
441
|
-
|
|
685
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
686
|
+
this.runPromise.reject(error);
|
|
687
|
+
this.rejectAllProtocolV2Frames(error);
|
|
688
|
+
this.runPromise = null;
|
|
689
|
+
this.activeProtocolV2Call = null;
|
|
442
690
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
443
691
|
}
|
|
444
692
|
|
|
@@ -450,11 +698,12 @@ export default class ReactNativeBleTransport {
|
|
|
450
698
|
throw error;
|
|
451
699
|
}
|
|
452
700
|
|
|
453
|
-
// check device is bonded
|
|
454
701
|
if (Platform.OS === 'android') {
|
|
455
702
|
const bondState = await pairDevice(uuid);
|
|
456
703
|
if (bondState.bonding) {
|
|
457
704
|
await onDeviceBondState(uuid);
|
|
705
|
+
} else if (!bondState.bonded) {
|
|
706
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
|
|
458
707
|
}
|
|
459
708
|
}
|
|
460
709
|
|
|
@@ -480,7 +729,6 @@ export default class ReactNativeBleTransport {
|
|
|
480
729
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
481
730
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
482
731
|
) {
|
|
483
|
-
connectOptions = {};
|
|
484
732
|
Log?.debug('first try to reconnect without params');
|
|
485
733
|
device = await blePlxManager.connectToDevice(uuid);
|
|
486
734
|
} else if (e.errorCode === BleErrorCode.DeviceAlreadyConnected) {
|
|
@@ -500,17 +748,16 @@ export default class ReactNativeBleTransport {
|
|
|
500
748
|
Log?.debug('not connected, try to connect to device: ', uuid);
|
|
501
749
|
|
|
502
750
|
try {
|
|
503
|
-
await device.connect(connectOptions);
|
|
751
|
+
device = await device.connect(connectOptions);
|
|
504
752
|
} catch (e) {
|
|
505
753
|
Log?.debug('not connected, try to connect to device has error: ', e);
|
|
506
754
|
if (
|
|
507
755
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
508
756
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
509
757
|
) {
|
|
510
|
-
connectOptions = {};
|
|
511
758
|
Log?.debug('second try to reconnect without params');
|
|
512
759
|
try {
|
|
513
|
-
await device.connect();
|
|
760
|
+
device = await device.connect();
|
|
514
761
|
} catch (e) {
|
|
515
762
|
Log?.debug('last try to reconnect error: ', e);
|
|
516
763
|
// last try to reconnect device if this issue exists
|
|
@@ -518,7 +765,7 @@ export default class ReactNativeBleTransport {
|
|
|
518
765
|
if (e.errorCode === BleErrorCode.OperationCancelled) {
|
|
519
766
|
Log?.debug('last try to reconnect');
|
|
520
767
|
await device.cancelConnection();
|
|
521
|
-
await device.connect();
|
|
768
|
+
device = await device.connect();
|
|
522
769
|
}
|
|
523
770
|
}
|
|
524
771
|
} else {
|
|
@@ -527,18 +774,50 @@ export default class ReactNativeBleTransport {
|
|
|
527
774
|
}
|
|
528
775
|
}
|
|
529
776
|
|
|
777
|
+
device = await requestAndroidMtu(device);
|
|
530
778
|
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(device);
|
|
531
779
|
|
|
780
|
+
const protocolHint = expectedProtocol
|
|
781
|
+
? undefined
|
|
782
|
+
: this.deviceProtocolHints.get(uuid) ??
|
|
783
|
+
inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
|
|
784
|
+
|
|
532
785
|
// release transport before new transport instance
|
|
533
|
-
await this.release(uuid);
|
|
786
|
+
await this.release(uuid, true);
|
|
787
|
+
if (protocolHint) {
|
|
788
|
+
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
789
|
+
}
|
|
534
790
|
|
|
535
791
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
792
|
+
if (Platform.OS === 'android') {
|
|
793
|
+
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
794
|
+
}
|
|
795
|
+
const monitorToken = this.nextMonitorToken;
|
|
796
|
+
this.nextMonitorToken += 1;
|
|
797
|
+
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
798
|
+
transport.monitorToken = monitorToken;
|
|
799
|
+
transport.notifyTransactionId = notifyTransactionId;
|
|
800
|
+
this.monitorTokens.set(uuid, monitorToken);
|
|
536
801
|
transport.notifySubscription = this._monitorCharacteristic(
|
|
537
802
|
transport.notifyCharacteristic,
|
|
538
|
-
uuid
|
|
803
|
+
uuid,
|
|
804
|
+
monitorToken,
|
|
805
|
+
notifyTransactionId
|
|
539
806
|
);
|
|
540
807
|
transportCache[uuid] = transport;
|
|
541
808
|
|
|
809
|
+
this.protocolV2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
|
|
810
|
+
|
|
811
|
+
if (Platform.OS === 'ios') {
|
|
812
|
+
await new Promise<void>(resolve => {
|
|
813
|
+
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
814
|
+
});
|
|
815
|
+
} else if (Platform.OS === 'android') {
|
|
816
|
+
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
820
|
+
|
|
542
821
|
this.emitter?.emit('device-connect', {
|
|
543
822
|
name: device.name,
|
|
544
823
|
id: device.id,
|
|
@@ -547,13 +826,19 @@ export default class ReactNativeBleTransport {
|
|
|
547
826
|
|
|
548
827
|
this.attachDisconnectSubscription(transport, device, uuid);
|
|
549
828
|
|
|
550
|
-
return { uuid };
|
|
829
|
+
return { uuid, protocolType };
|
|
551
830
|
}
|
|
552
831
|
|
|
553
|
-
_monitorCharacteristic(
|
|
832
|
+
_monitorCharacteristic(
|
|
833
|
+
characteristic: Characteristic,
|
|
834
|
+
uuid: string,
|
|
835
|
+
monitorToken: number,
|
|
836
|
+
notifyTransactionId: string
|
|
837
|
+
): Subscription {
|
|
554
838
|
let bufferLength = 0;
|
|
555
839
|
let buffer: any[] = [];
|
|
556
840
|
const subscription = characteristic.monitor((error, c) => {
|
|
841
|
+
const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
|
|
557
842
|
if (error) {
|
|
558
843
|
Log?.debug(
|
|
559
844
|
`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${
|
|
@@ -564,6 +849,10 @@ export default class ReactNativeBleTransport {
|
|
|
564
849
|
Log?.debug('notify error ignored during FirmwareUpload write recovery: ', uuid);
|
|
565
850
|
return;
|
|
566
851
|
}
|
|
852
|
+
if (!isCurrentMonitor) {
|
|
853
|
+
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
567
856
|
if (this.runPromise) {
|
|
568
857
|
let ERROR:
|
|
569
858
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
@@ -583,27 +872,45 @@ export default class ReactNativeBleTransport {
|
|
|
583
872
|
error.reason?.includes('Writing is not permitted') || // pro firmware 2.3.4 upgrade
|
|
584
873
|
error.reason?.includes('notify change failed for device')
|
|
585
874
|
) {
|
|
586
|
-
|
|
587
|
-
|
|
875
|
+
const notifyError = ERRORS.TypedError(
|
|
876
|
+
HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
588
877
|
);
|
|
878
|
+
this.runPromise.reject(notifyError);
|
|
879
|
+
this.rejectAllProtocolV2Frames(notifyError);
|
|
589
880
|
Log?.debug(
|
|
590
881
|
`${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
|
|
591
882
|
);
|
|
592
883
|
return;
|
|
593
884
|
}
|
|
594
|
-
|
|
885
|
+
const notifyError = ERRORS.TypedError(ERROR);
|
|
886
|
+
this.runPromise.reject(notifyError);
|
|
887
|
+
this.rejectAllProtocolV2Frames(notifyError);
|
|
595
888
|
Log?.debug(': monitor notify error, and has unreleased Promise', Error);
|
|
596
889
|
}
|
|
597
890
|
|
|
598
891
|
return;
|
|
599
892
|
}
|
|
600
893
|
|
|
894
|
+
if (!isCurrentMonitor) {
|
|
895
|
+
Log?.debug('monitor data ignored for stale transport: ', uuid, notifyTransactionId);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
|
|
601
899
|
if (!c) {
|
|
602
900
|
throw ERRORS.TypedError(HardwareErrorCode.BleMonitorError);
|
|
603
901
|
}
|
|
604
902
|
|
|
605
903
|
try {
|
|
606
904
|
const data = Buffer.from(c.value as string, 'base64');
|
|
905
|
+
const protocol = this.deviceProtocol.get(uuid);
|
|
906
|
+
if (!protocol) {
|
|
907
|
+
Log?.debug('monitor data ignored before protocol detection: ', uuid);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (protocol === 'V2') {
|
|
911
|
+
this.handleProtocolV2Notification(uuid, new Uint8Array(data));
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
607
914
|
// console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
|
|
608
915
|
if (isHeaderChunk(data)) {
|
|
609
916
|
bufferLength = data.readInt32BE(5);
|
|
@@ -612,7 +919,7 @@ export default class ReactNativeBleTransport {
|
|
|
612
919
|
buffer = buffer.concat([...data]);
|
|
613
920
|
}
|
|
614
921
|
|
|
615
|
-
if (buffer.length -
|
|
922
|
+
if (buffer.length - PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferLength) {
|
|
616
923
|
const value = Buffer.from(buffer);
|
|
617
924
|
// console.log(
|
|
618
925
|
// '[hd-transport-react-native] Received a complete packet of data, resolve Promise, this.runPromise: ',
|
|
@@ -626,17 +933,41 @@ export default class ReactNativeBleTransport {
|
|
|
626
933
|
}
|
|
627
934
|
} catch (error) {
|
|
628
935
|
Log?.debug('monitor data error: ', error);
|
|
629
|
-
|
|
936
|
+
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
937
|
+
this.runPromise?.reject(notifyError);
|
|
938
|
+
this.rejectAllProtocolV2Frames(notifyError);
|
|
630
939
|
}
|
|
631
|
-
},
|
|
940
|
+
}, notifyTransactionId);
|
|
632
941
|
|
|
633
942
|
return subscription;
|
|
634
943
|
}
|
|
635
944
|
|
|
636
|
-
async release(uuid: string) {
|
|
945
|
+
async release(uuid: string, onclose = false) {
|
|
637
946
|
const transport = transportCache[uuid];
|
|
947
|
+
if (this.runPromise) {
|
|
948
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
949
|
+
this.runPromise.reject(error);
|
|
950
|
+
this.runPromise = null;
|
|
951
|
+
this.rejectAllProtocolV2Frames(error);
|
|
952
|
+
this.activeProtocolV2Call = null;
|
|
953
|
+
} else {
|
|
954
|
+
this.resetProtocolV2Frames(uuid);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (Platform.OS === 'android' && !onclose && transport) {
|
|
958
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
959
|
+
this.resetProtocolV2Frames(uuid);
|
|
960
|
+
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
961
|
+
this.activeProtocolV2Call = null;
|
|
962
|
+
}
|
|
963
|
+
return Promise.resolve(true);
|
|
964
|
+
}
|
|
638
965
|
|
|
639
966
|
if (transport) {
|
|
967
|
+
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
968
|
+
this.monitorTokens.delete(uuid);
|
|
969
|
+
}
|
|
970
|
+
|
|
640
971
|
// Clean up disconnect subscription first to prevent callbacks on released transport
|
|
641
972
|
Log?.debug('release: removing disconnect subscription for device: ', uuid);
|
|
642
973
|
transport.disconnectSubscription?.remove();
|
|
@@ -650,12 +981,27 @@ export default class ReactNativeBleTransport {
|
|
|
650
981
|
transport.notifySubscription?.remove();
|
|
651
982
|
transport.notifySubscription = undefined;
|
|
652
983
|
|
|
984
|
+
if (transport.notifyTransactionId) {
|
|
985
|
+
try {
|
|
986
|
+
await this.blePlxManager?.cancelTransaction(transport.notifyTransactionId);
|
|
987
|
+
} catch (e) {
|
|
988
|
+
Log?.debug('release: cancel notify transaction error (ignored): ', e?.message || e);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
653
992
|
delete transportCache[uuid];
|
|
993
|
+
}
|
|
654
994
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
995
|
+
this.deviceProtocol.delete(uuid);
|
|
996
|
+
this.deviceProtocolHints.delete(uuid);
|
|
997
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
998
|
+
this.protocolV2Assemblers.delete(uuid);
|
|
999
|
+
this.resetProtocolV2Frames(uuid);
|
|
1000
|
+
|
|
1001
|
+
try {
|
|
1002
|
+
await this.blePlxManager?.cancelTransaction(uuid);
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
Log?.debug('release: cancel transaction error (ignored): ', e?.message || e);
|
|
659
1005
|
}
|
|
660
1006
|
|
|
661
1007
|
return Promise.resolve(true);
|
|
@@ -665,7 +1011,12 @@ export default class ReactNativeBleTransport {
|
|
|
665
1011
|
await this.call(session, name, data);
|
|
666
1012
|
}
|
|
667
1013
|
|
|
668
|
-
async call(
|
|
1014
|
+
async call(
|
|
1015
|
+
uuid: string,
|
|
1016
|
+
name: string,
|
|
1017
|
+
data: Record<string, unknown>,
|
|
1018
|
+
options?: TransportCallOptions
|
|
1019
|
+
) {
|
|
669
1020
|
if (this.stopped) {
|
|
670
1021
|
// eslint-disable-next-line prefer-promise-reject-errors
|
|
671
1022
|
return Promise.reject(ERRORS.TypedError('Transport stopped.'));
|
|
@@ -681,13 +1032,13 @@ export default class ReactNativeBleTransport {
|
|
|
681
1032
|
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
682
1033
|
}
|
|
683
1034
|
|
|
684
|
-
const
|
|
685
|
-
if (!
|
|
686
|
-
throw ERRORS.TypedError(
|
|
1035
|
+
const protocol = this.getProtocolType(uuid);
|
|
1036
|
+
if (!protocol) {
|
|
1037
|
+
throw ERRORS.TypedError(
|
|
1038
|
+
HardwareErrorCode.RuntimeError,
|
|
1039
|
+
`Device protocol has not been detected for ${uuid}`
|
|
1040
|
+
);
|
|
687
1041
|
}
|
|
688
|
-
|
|
689
|
-
this.runPromise = createDeferred();
|
|
690
|
-
const messages = this._messages;
|
|
691
1042
|
// Upload resources on low-end phones may OOM
|
|
692
1043
|
if (name === 'ResourceUpdate' || name === 'ResourceAck') {
|
|
693
1044
|
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
|
|
@@ -695,12 +1046,44 @@ export default class ReactNativeBleTransport {
|
|
|
695
1046
|
hash: data?.hash,
|
|
696
1047
|
});
|
|
697
1048
|
} else if (LogBlockCommand.has(name)) {
|
|
698
|
-
Log?.debug('transport-react-native', 'call-', ' name: ', name);
|
|
1049
|
+
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' protocol: ', protocol);
|
|
699
1050
|
} else {
|
|
700
|
-
Log?.debug(
|
|
1051
|
+
Log?.debug(
|
|
1052
|
+
'transport-react-native',
|
|
1053
|
+
'call-',
|
|
1054
|
+
' name: ',
|
|
1055
|
+
name,
|
|
1056
|
+
' data: ',
|
|
1057
|
+
data,
|
|
1058
|
+
' protocol: ',
|
|
1059
|
+
protocol
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (protocol === 'V2') {
|
|
1064
|
+
return this.callProtocolV2(uuid, name, data, options);
|
|
701
1065
|
}
|
|
702
1066
|
|
|
703
|
-
|
|
1067
|
+
return this.callProtocolV1(uuid, name, data, options);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
private async callProtocolV1(
|
|
1071
|
+
uuid: string,
|
|
1072
|
+
name: string,
|
|
1073
|
+
data: Record<string, unknown>,
|
|
1074
|
+
options?: TransportCallOptions
|
|
1075
|
+
) {
|
|
1076
|
+
if (!this._messages) {
|
|
1077
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const transport = this.getCachedTransport(uuid);
|
|
1081
|
+
const runPromise = createDeferred<string>();
|
|
1082
|
+
runPromise.promise.catch(() => undefined);
|
|
1083
|
+
this.runPromise = runPromise;
|
|
1084
|
+
const messages = this._messages;
|
|
1085
|
+
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1086
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
704
1087
|
|
|
705
1088
|
async function writeChunkedData(
|
|
706
1089
|
buffers: ByteBuffer[],
|
|
@@ -853,20 +1236,41 @@ export default class ReactNativeBleTransport {
|
|
|
853
1236
|
}
|
|
854
1237
|
|
|
855
1238
|
try {
|
|
856
|
-
const response = await
|
|
1239
|
+
const response = await Promise.race([
|
|
1240
|
+
runPromise.promise,
|
|
1241
|
+
new Promise<never>((_, reject) => {
|
|
1242
|
+
if (options?.timeoutMs) {
|
|
1243
|
+
timeout = setTimeout(() => {
|
|
1244
|
+
const error = ERRORS.TypedError(
|
|
1245
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1246
|
+
`BLE response timeout after ${options.timeoutMs}ms for ${name}`
|
|
1247
|
+
);
|
|
1248
|
+
runPromise.reject(error);
|
|
1249
|
+
reject(error);
|
|
1250
|
+
}, options.timeoutMs);
|
|
1251
|
+
}
|
|
1252
|
+
}),
|
|
1253
|
+
]);
|
|
857
1254
|
|
|
858
1255
|
if (typeof response !== 'string') {
|
|
859
1256
|
throw new Error('Returning data is not string.');
|
|
860
1257
|
}
|
|
861
1258
|
|
|
862
1259
|
Log?.debug('receive data: ', response);
|
|
863
|
-
const jsonData =
|
|
1260
|
+
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
864
1261
|
return check.call(jsonData);
|
|
865
1262
|
} catch (e) {
|
|
866
|
-
|
|
1263
|
+
if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
1264
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe call failed:', e);
|
|
1265
|
+
} else {
|
|
1266
|
+
Log?.error('call error: ', e);
|
|
1267
|
+
}
|
|
867
1268
|
throw e;
|
|
868
1269
|
} finally {
|
|
869
|
-
|
|
1270
|
+
if (timeout) clearTimeout(timeout);
|
|
1271
|
+
if (this.runPromise === runPromise) {
|
|
1272
|
+
this.runPromise = null;
|
|
1273
|
+
}
|
|
870
1274
|
}
|
|
871
1275
|
}
|
|
872
1276
|
|
|
@@ -933,6 +1337,13 @@ export default class ReactNativeBleTransport {
|
|
|
933
1337
|
if (transportCache[session]) {
|
|
934
1338
|
delete transportCache[session];
|
|
935
1339
|
}
|
|
1340
|
+
this.deviceProtocol.delete(session);
|
|
1341
|
+
this.deviceProtocolHints.delete(session);
|
|
1342
|
+
this.protocolV2Assemblers.delete(session);
|
|
1343
|
+
this.resetProtocolV2Frames(session);
|
|
1344
|
+
if (this.activeProtocolV2Call?.uuid === session) {
|
|
1345
|
+
this.activeProtocolV2Call = null;
|
|
1346
|
+
}
|
|
936
1347
|
|
|
937
1348
|
// emit the disconnect event
|
|
938
1349
|
try {
|
|
@@ -955,4 +1366,416 @@ export default class ReactNativeBleTransport {
|
|
|
955
1366
|
}
|
|
956
1367
|
this.runPromise = null;
|
|
957
1368
|
}
|
|
1369
|
+
|
|
1370
|
+
private getCachedTransport(uuid: string) {
|
|
1371
|
+
const transport = transportCache[uuid];
|
|
1372
|
+
if (!transport) {
|
|
1373
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
1374
|
+
}
|
|
1375
|
+
return transport;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
private createProtocolMismatchError(expected: ProtocolType) {
|
|
1379
|
+
return ERRORS.TypedError(
|
|
1380
|
+
HardwareErrorCode.RuntimeError,
|
|
1381
|
+
`Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
private createProtocolDetectionError() {
|
|
1386
|
+
return ERRORS.TypedError(
|
|
1387
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1388
|
+
'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
|
|
1393
|
+
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
1394
|
+
this.deviceProtocol.delete(uuid);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
private async detectProtocol(
|
|
1399
|
+
uuid: string,
|
|
1400
|
+
expectedProtocol?: ProtocolType,
|
|
1401
|
+
protocolHint?: ProtocolType
|
|
1402
|
+
): Promise<ProtocolType> {
|
|
1403
|
+
if (expectedProtocol === 'V1') {
|
|
1404
|
+
if (await this.probeProtocolV1(uuid)) {
|
|
1405
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
1406
|
+
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1 (expected)`);
|
|
1407
|
+
return 'V1';
|
|
1408
|
+
}
|
|
1409
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
if (expectedProtocol === 'V2') {
|
|
1413
|
+
// 免探测路径:调用方显式承诺该设备是 V2(例如固件升级重启后的重连场景,
|
|
1414
|
+
// 上层已经探测过协议并通过 expectedProtocol 传回),这里不再重复探测。
|
|
1415
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1416
|
+
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1417
|
+
return 'V2';
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// 项目约束:协议判断必须在连接后主动探测,不能依赖设备名/PID/descriptor。
|
|
1421
|
+
// 设备名 hint(如 "Pro 2")只用于调整探测顺序:hint=V2 时先探 V2、失败回落 V1,
|
|
1422
|
+
// 不能作为最终结论。
|
|
1423
|
+
const probeOrder: ProtocolType[] =
|
|
1424
|
+
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1425
|
+
|
|
1426
|
+
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1427
|
+
const protocol = probeOrder[i];
|
|
1428
|
+
if (i > 0) {
|
|
1429
|
+
// 上一个协议探测失败后,重置订阅与缓冲,避免残留数据干扰下一个协议的探测。
|
|
1430
|
+
await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
1431
|
+
}
|
|
1432
|
+
const detected =
|
|
1433
|
+
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
1434
|
+
if (detected) {
|
|
1435
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
1436
|
+
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
1437
|
+
return protocol;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
this.deviceProtocol.delete(uuid);
|
|
1442
|
+
throw this.createProtocolDetectionError();
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
1446
|
+
const transport = transportCache[uuid];
|
|
1447
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1448
|
+
this.resetProtocolV2Frames(uuid);
|
|
1449
|
+
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
1450
|
+
this.activeProtocolV2Call = null;
|
|
1451
|
+
}
|
|
1452
|
+
if (this.runPromise) {
|
|
1453
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1454
|
+
this.runPromise.reject(error);
|
|
1455
|
+
this.runPromise = null;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
if (!transport) return;
|
|
1459
|
+
|
|
1460
|
+
const previousNotifyTransactionId = transport.notifyTransactionId;
|
|
1461
|
+
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1462
|
+
this.monitorTokens.delete(uuid);
|
|
1463
|
+
}
|
|
1464
|
+
transport.notifySubscription?.remove();
|
|
1465
|
+
transport.notifySubscription = undefined;
|
|
1466
|
+
if (previousNotifyTransactionId) {
|
|
1467
|
+
try {
|
|
1468
|
+
await this.blePlxManager?.cancelTransaction(previousNotifyTransactionId);
|
|
1469
|
+
} catch (error) {
|
|
1470
|
+
Log?.debug(
|
|
1471
|
+
`[ReactNativeBleTransport] cancel notify after Protocol ${protocol} probe failed:`,
|
|
1472
|
+
error?.message || error
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
const monitorToken = this.nextMonitorToken;
|
|
1478
|
+
this.nextMonitorToken += 1;
|
|
1479
|
+
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
1480
|
+
transport.monitorToken = monitorToken;
|
|
1481
|
+
transport.notifyTransactionId = notifyTransactionId;
|
|
1482
|
+
this.monitorTokens.set(uuid, monitorToken);
|
|
1483
|
+
transport.notifySubscription = this._monitorCharacteristic(
|
|
1484
|
+
transport.notifyCharacteristic,
|
|
1485
|
+
uuid,
|
|
1486
|
+
monitorToken,
|
|
1487
|
+
notifyTransactionId
|
|
1488
|
+
);
|
|
1489
|
+
if (Platform.OS === 'ios') {
|
|
1490
|
+
await new Promise<void>(resolve => {
|
|
1491
|
+
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
private async probeProtocolV1(uuid: string) {
|
|
1497
|
+
if (!this._messages) {
|
|
1498
|
+
return false;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
try {
|
|
1502
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
1503
|
+
await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1504
|
+
return true;
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
this.clearProbeProtocol(uuid, 'V1');
|
|
1507
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
|
|
1508
|
+
return false;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
private async probeProtocolV2(uuid: string) {
|
|
1513
|
+
if (!this._messages || !this._messagesV2) {
|
|
1514
|
+
return false;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1518
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1519
|
+
const detected = await probeProtocolV2Helper({
|
|
1520
|
+
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
1521
|
+
this.callProtocolV2(uuid, name, data, options),
|
|
1522
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
|
|
1523
|
+
logger: Log,
|
|
1524
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1525
|
+
onProbeFailed: () => {
|
|
1526
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1527
|
+
this.resetProtocolV2Frames(uuid);
|
|
1528
|
+
},
|
|
1529
|
+
});
|
|
1530
|
+
if (!detected) {
|
|
1531
|
+
this.clearProbeProtocol(uuid, 'V2');
|
|
1532
|
+
}
|
|
1533
|
+
return detected;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
|
|
1537
|
+
try {
|
|
1538
|
+
if (!this.runPromise || this.activeProtocolV2Call?.uuid !== uuid) {
|
|
1539
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1540
|
+
this.resetProtocolV2Frames(uuid);
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (data.length === 0) return;
|
|
1545
|
+
|
|
1546
|
+
const assembler = this.protocolV2Assemblers.get(uuid);
|
|
1547
|
+
if (!assembler) return;
|
|
1548
|
+
|
|
1549
|
+
for (const frameData of assembler.drain(data)) {
|
|
1550
|
+
this.resolveProtocolV2Frame(uuid, frameData);
|
|
1551
|
+
}
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
|
|
1554
|
+
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1555
|
+
this.runPromise?.reject(notifyError);
|
|
1556
|
+
this.rejectAllProtocolV2Frames(notifyError);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
private getProtocolV2FrameQueue(uuid: string) {
|
|
1561
|
+
let queue = this.protocolV2FrameQueues.get(uuid);
|
|
1562
|
+
if (!queue) {
|
|
1563
|
+
queue = [];
|
|
1564
|
+
this.protocolV2FrameQueues.set(uuid, queue);
|
|
1565
|
+
}
|
|
1566
|
+
return queue;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
private resolveProtocolV2Frame(uuid: string, frame: Uint8Array) {
|
|
1570
|
+
const framePromise = this.protocolV2FramePromises.get(uuid);
|
|
1571
|
+
if (framePromise) {
|
|
1572
|
+
framePromise.resolve(frame);
|
|
1573
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
private rejectAllProtocolV2Frames(error: Error) {
|
|
1580
|
+
this.protocolV2FrameQueues.clear();
|
|
1581
|
+
for (const framePromise of this.protocolV2FramePromises.values()) {
|
|
1582
|
+
framePromise.reject(error);
|
|
1583
|
+
}
|
|
1584
|
+
this.protocolV2FramePromises.clear();
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
private resetProtocolV2Frames(uuid: string) {
|
|
1588
|
+
this.protocolV2FrameQueues.delete(uuid);
|
|
1589
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
private isActiveProtocolV2Call(uuid: string, token: number) {
|
|
1593
|
+
return this.activeProtocolV2Call?.uuid === uuid && this.activeProtocolV2Call.token === token;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
private async readProtocolV2Frame(uuid: string) {
|
|
1597
|
+
const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
|
|
1598
|
+
if (queuedFrame) {
|
|
1599
|
+
return queuedFrame;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
const framePromise = createDeferred<Uint8Array>();
|
|
1603
|
+
this.protocolV2FramePromises.set(uuid, framePromise);
|
|
1604
|
+
try {
|
|
1605
|
+
return await framePromise.promise;
|
|
1606
|
+
} finally {
|
|
1607
|
+
if (this.protocolV2FramePromises.get(uuid) === framePromise) {
|
|
1608
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
private async writeProtocolV2Frame(
|
|
1614
|
+
transport: BleTransport,
|
|
1615
|
+
frame: Uint8Array,
|
|
1616
|
+
options?: { highVolume?: boolean; writeWithResponse?: boolean }
|
|
1617
|
+
) {
|
|
1618
|
+
const tuning = getProtocolV2BleTuning();
|
|
1619
|
+
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
1620
|
+
platform: Platform.OS,
|
|
1621
|
+
iosPacketLength: tuning.iosPacketLength,
|
|
1622
|
+
androidPacketLength: tuning.androidPacketLength,
|
|
1623
|
+
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1624
|
+
});
|
|
1625
|
+
const writeWithResponse =
|
|
1626
|
+
!!options?.writeWithResponse || (!!options?.highVolume && tuning.highVolumeWriteWithResponse);
|
|
1627
|
+
const writeMode = resolveBleWriteMode(
|
|
1628
|
+
transport.writeCharacteristic,
|
|
1629
|
+
writeWithResponse ? 'withResponse' : 'withoutResponse'
|
|
1630
|
+
);
|
|
1631
|
+
const shouldThrottle = !!options?.highVolume && writeMode === 'withoutResponse';
|
|
1632
|
+
let packetsWritten = 0;
|
|
1633
|
+
|
|
1634
|
+
try {
|
|
1635
|
+
for (let offset = 0; offset < frame.length; offset += packetCapacity) {
|
|
1636
|
+
const chunk = frame.slice(offset, offset + packetCapacity);
|
|
1637
|
+
const base64 = Buffer.from(chunk).toString('base64');
|
|
1638
|
+
if (writeMode === 'withResponse') {
|
|
1639
|
+
await transport.writeCharacteristic.writeWithResponse(base64);
|
|
1640
|
+
} else {
|
|
1641
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1642
|
+
}
|
|
1643
|
+
packetsWritten += 1;
|
|
1644
|
+
|
|
1645
|
+
if (
|
|
1646
|
+
shouldThrottle &&
|
|
1647
|
+
packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
|
|
1648
|
+
offset + packetCapacity < frame.length
|
|
1649
|
+
) {
|
|
1650
|
+
await delay(tuning.highVolumeWritePauseMs);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (shouldThrottle) {
|
|
1655
|
+
await delay(tuning.highVolumeWriteFlushDelayMs);
|
|
1656
|
+
}
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
if (options?.highVolume && !writeWithResponse && packetsWritten === 0) {
|
|
1659
|
+
Log?.debug(
|
|
1660
|
+
'[ReactNativeBleTransport] Protocol V2 high-volume writeWithoutResponse failed before data was sent, fallback to writeWithResponse:',
|
|
1661
|
+
error
|
|
1662
|
+
);
|
|
1663
|
+
await this.writeProtocolV2Frame(transport, frame, {
|
|
1664
|
+
highVolume: true,
|
|
1665
|
+
writeWithResponse: true,
|
|
1666
|
+
});
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
throw error;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
private async callProtocolV2(
|
|
1674
|
+
uuid: string,
|
|
1675
|
+
name: string,
|
|
1676
|
+
data: Record<string, unknown>,
|
|
1677
|
+
options?: TransportCallOptions
|
|
1678
|
+
) {
|
|
1679
|
+
if (!this._messages || !this._messagesV2) {
|
|
1680
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'GetProtoVersion';
|
|
1684
|
+
if (this.runPromise) {
|
|
1685
|
+
if (!forceRun) {
|
|
1686
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1687
|
+
}
|
|
1688
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1689
|
+
this.runPromise.reject(error);
|
|
1690
|
+
this.rejectAllProtocolV2Frames(error);
|
|
1691
|
+
this.runPromise = null;
|
|
1692
|
+
this.activeProtocolV2Call = null;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
const transport = this.getCachedTransport(uuid);
|
|
1696
|
+
const runPromise = createDeferred<Uint8Array>();
|
|
1697
|
+
runPromise.promise.catch(() => undefined);
|
|
1698
|
+
this.runPromise = runPromise;
|
|
1699
|
+
const callToken = this.nextProtocolV2CallToken++;
|
|
1700
|
+
this.activeProtocolV2Call = { uuid, token: callToken };
|
|
1701
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1702
|
+
this.resetProtocolV2Frames(uuid);
|
|
1703
|
+
let completed = false;
|
|
1704
|
+
const callOptions = {
|
|
1705
|
+
...options,
|
|
1706
|
+
timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
|
|
1707
|
+
};
|
|
1708
|
+
const highVolumeWrite = LogBlockCommand.has(name);
|
|
1709
|
+
|
|
1710
|
+
if (highVolumeWrite) {
|
|
1711
|
+
const tuning = getProtocolV2BleTuning();
|
|
1712
|
+
Log?.debug(
|
|
1713
|
+
'[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
|
|
1714
|
+
name,
|
|
1715
|
+
{
|
|
1716
|
+
packetCapacity:
|
|
1717
|
+
Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1718
|
+
burstSize: tuning.highVolumeWriteBurstSize,
|
|
1719
|
+
pauseMs: tuning.highVolumeWritePauseMs,
|
|
1720
|
+
flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
|
|
1721
|
+
writeWithResponse: tuning.highVolumeWriteWithResponse,
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
try {
|
|
1727
|
+
const session = new ProtocolV2Session({
|
|
1728
|
+
schemas: {
|
|
1729
|
+
protocolV1: this._messages,
|
|
1730
|
+
protocolV2: this._messagesV2,
|
|
1731
|
+
},
|
|
1732
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1733
|
+
writeFrame: async (frame: Uint8Array) => {
|
|
1734
|
+
await this.writeProtocolV2Frame(transport, frame, {
|
|
1735
|
+
highVolume: highVolumeWrite,
|
|
1736
|
+
});
|
|
1737
|
+
},
|
|
1738
|
+
readFrame: async () => {
|
|
1739
|
+
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1740
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1741
|
+
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1742
|
+
}
|
|
1743
|
+
return rxFrame;
|
|
1744
|
+
},
|
|
1745
|
+
logger: Log,
|
|
1746
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1747
|
+
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
1748
|
+
ERRORS.TypedError(
|
|
1749
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1750
|
+
`BLE response timeout after ${timeout}ms for ${name}`
|
|
1751
|
+
),
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
const result = await session.call(name, data, callOptions);
|
|
1755
|
+
completed = true;
|
|
1756
|
+
return result;
|
|
1757
|
+
} catch (e) {
|
|
1758
|
+
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1759
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1760
|
+
this.resetProtocolV2Frames(uuid);
|
|
1761
|
+
}
|
|
1762
|
+
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1763
|
+
throw e;
|
|
1764
|
+
} finally {
|
|
1765
|
+
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1766
|
+
if (!completed) {
|
|
1767
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1768
|
+
}
|
|
1769
|
+
this.resetProtocolV2Frames(uuid);
|
|
1770
|
+
this.activeProtocolV2Call = null;
|
|
1771
|
+
}
|
|
1772
|
+
if (this.runPromise === runPromise) {
|
|
1773
|
+
this.runPromise = null;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
getProtocolType(path: string): ProtocolType | undefined {
|
|
1779
|
+
return this.deviceProtocol.get(path);
|
|
1780
|
+
}
|
|
958
1781
|
}
|