@onekeyfe/hd-transport-react-native 1.1.27-alpha.41 → 1.1.27-alpha.6
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 +0 -2
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/constants.d.ts +0 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +29 -56
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +346 -885
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/BleManager.ts +14 -14
- package/src/BleTransport.ts +5 -9
- package/src/constants.ts +1 -25
- package/src/index.ts +326 -955
- package/src/types.ts +0 -3
- package/src/utils/validateNotify.ts +4 -4
- package/dist/bleStrategy.d.ts +0 -15
- package/dist/bleStrategy.d.ts.map +0 -1
- package/dist/logger.d.ts +0 -14
- package/dist/logger.d.ts.map +0 -1
- package/src/__tests__/bleStrategy.test.ts +0 -47
- package/src/bleStrategy.ts +0 -62
- package/src/logger.ts +0 -19
package/src/index.ts
CHANGED
|
@@ -9,150 +9,92 @@ import {
|
|
|
9
9
|
} from 'react-native-ble-plx';
|
|
10
10
|
import ByteBuffer from 'bytebuffer';
|
|
11
11
|
import transport, {
|
|
12
|
+
COMMON_HEADER_SIZE,
|
|
12
13
|
LogBlockCommand,
|
|
13
14
|
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,
|
|
21
15
|
} from '@onekeyfe/hd-transport';
|
|
22
16
|
import { ERRORS, HardwareErrorCode, createDeferred, isOnekeyDevice } from '@onekeyfe/hd-shared';
|
|
17
|
+
import { LoggerNames, getLogger } from '@onekeyfe/hd-core';
|
|
23
18
|
|
|
24
19
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
25
|
-
import {
|
|
26
|
-
hasWritableCapability,
|
|
27
|
-
resolveBleWriteMode,
|
|
28
|
-
resolveProtocolV2PacketCapacity,
|
|
29
|
-
} from './bleStrategy';
|
|
30
20
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
31
21
|
import {
|
|
32
22
|
ANDROID_PACKET_LENGTH,
|
|
33
23
|
IOS_PACKET_LENGTH,
|
|
34
|
-
getBleUuidKey,
|
|
35
24
|
getBluetoothServiceUuids,
|
|
36
25
|
getInfosForServiceUuid,
|
|
37
|
-
isSameBleUuid,
|
|
38
26
|
} from './constants';
|
|
39
27
|
import { isHeaderChunk } from './utils/validateNotify';
|
|
40
28
|
import BleTransport from './BleTransport';
|
|
41
29
|
import timer from './utils/timer';
|
|
42
|
-
import { bleLogger, setBleLogger } from './logger';
|
|
43
30
|
|
|
44
31
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
45
32
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
46
33
|
import type EventEmitter from 'events';
|
|
47
34
|
import type { BleAcquireInput, TransportOptions } from './types';
|
|
48
35
|
|
|
49
|
-
const { check,
|
|
36
|
+
const { check, buildBuffers, receiveOne, parseConfigure } = transport;
|
|
50
37
|
|
|
51
|
-
const Log =
|
|
38
|
+
const Log = getLogger(LoggerNames.HdBleTransport);
|
|
52
39
|
|
|
53
40
|
const transportCache: Record<string, BleTransport> = {};
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const
|
|
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
|
+
};
|
|
63
56
|
|
|
64
57
|
const delay = (ms: number) =>
|
|
65
58
|
new Promise<void>(resolve => {
|
|
66
59
|
setTimeout(resolve, ms);
|
|
67
60
|
});
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
79
|
-
|
|
80
|
-
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
81
|
-
iosPacketLength: IOS_PACKET_LENGTH,
|
|
82
|
-
androidPacketLength: ANDROID_PACKET_LENGTH,
|
|
83
|
-
highVolumeWriteBurstSize: HIGH_VOLUME_WRITE_BURST_SIZE,
|
|
84
|
-
highVolumeWritePauseMs: HIGH_VOLUME_WRITE_PAUSE_MS,
|
|
85
|
-
highVolumeWriteFlushDelayMs: HIGH_VOLUME_WRITE_FLUSH_DELAY_MS,
|
|
86
|
-
highVolumeWriteWithResponse: false,
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
90
|
-
|
|
91
|
-
const normalizePositiveInteger = (value: unknown, fallback: number) => {
|
|
92
|
-
const normalized = Number(value);
|
|
93
|
-
if (!Number.isFinite(normalized) || normalized <= 0) return fallback;
|
|
94
|
-
return Math.floor(normalized);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
|
|
98
|
-
protocolV2BleTuning = {
|
|
99
|
-
iosPacketLength: normalizePositiveInteger(
|
|
100
|
-
tuning.iosPacketLength,
|
|
101
|
-
protocolV2BleTuning.iosPacketLength
|
|
102
|
-
),
|
|
103
|
-
androidPacketLength: normalizePositiveInteger(
|
|
104
|
-
tuning.androidPacketLength,
|
|
105
|
-
protocolV2BleTuning.androidPacketLength
|
|
106
|
-
),
|
|
107
|
-
highVolumeWriteBurstSize: normalizePositiveInteger(
|
|
108
|
-
tuning.highVolumeWriteBurstSize,
|
|
109
|
-
protocolV2BleTuning.highVolumeWriteBurstSize
|
|
110
|
-
),
|
|
111
|
-
highVolumeWritePauseMs: normalizePositiveInteger(
|
|
112
|
-
tuning.highVolumeWritePauseMs,
|
|
113
|
-
protocolV2BleTuning.highVolumeWritePauseMs
|
|
114
|
-
),
|
|
115
|
-
highVolumeWriteFlushDelayMs: normalizePositiveInteger(
|
|
116
|
-
tuning.highVolumeWriteFlushDelayMs,
|
|
117
|
-
protocolV2BleTuning.highVolumeWriteFlushDelayMs
|
|
118
|
-
),
|
|
119
|
-
highVolumeWriteWithResponse:
|
|
120
|
-
tuning.highVolumeWriteWithResponse ?? protocolV2BleTuning.highVolumeWriteWithResponse,
|
|
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;
|
|
121
71
|
};
|
|
122
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning configured:', protocolV2BleTuning);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function resetProtocolV2BleTuning() {
|
|
126
|
-
protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
127
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning reset:', protocolV2BleTuning);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function getProtocolV2BleTuning() {
|
|
131
|
-
return { ...protocolV2BleTuning };
|
|
132
|
-
}
|
|
133
72
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
73
|
+
if (
|
|
74
|
+
bleWriteError.errorCode === BleErrorCode.DeviceDisconnected ||
|
|
75
|
+
bleWriteError.errorCode === BleErrorCode.CharacteristicNotFound
|
|
76
|
+
) {
|
|
77
|
+
return 'reconnectable';
|
|
78
|
+
}
|
|
141
79
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
80
|
+
if (
|
|
81
|
+
bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
|
|
82
|
+
bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS
|
|
83
|
+
) {
|
|
84
|
+
return 'congested';
|
|
85
|
+
}
|
|
145
86
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
);
|
|
150
|
-
}
|
|
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
|
+
};
|
|
151
92
|
|
|
152
|
-
const
|
|
93
|
+
const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
|
|
94
|
+
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
153
95
|
|
|
154
|
-
|
|
155
|
-
requestMTU:
|
|
96
|
+
let connectOptions: Record<string, unknown> = {
|
|
97
|
+
requestMTU: 256,
|
|
156
98
|
timeout: 3000,
|
|
157
99
|
refreshGatt: 'OnConnected',
|
|
158
100
|
};
|
|
@@ -161,29 +103,12 @@ export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
|
161
103
|
|
|
162
104
|
const tryToGetConfiguration = (device: Device) => {
|
|
163
105
|
if (!device || !device.serviceUUIDs) return null;
|
|
164
|
-
const serviceUUID = device.serviceUUIDs
|
|
165
|
-
if (!serviceUUID) return null;
|
|
106
|
+
const [serviceUUID] = device.serviceUUIDs;
|
|
166
107
|
const infos = getInfosForServiceUuid(serviceUUID, 'classic');
|
|
167
108
|
if (!infos) return null;
|
|
168
109
|
return infos;
|
|
169
110
|
};
|
|
170
111
|
|
|
171
|
-
const requestAndroidMtu = async (device: Device) => {
|
|
172
|
-
if (Platform.OS !== 'android') return device;
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
|
|
176
|
-
Log?.debug('[ReactNativeBleTransport] Android MTU requested:', {
|
|
177
|
-
requested: ANDROID_REQUEST_MTU,
|
|
178
|
-
mtu: mtuDevice.mtu,
|
|
179
|
-
});
|
|
180
|
-
return mtuDevice;
|
|
181
|
-
} catch (error) {
|
|
182
|
-
Log?.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
|
|
183
|
-
return device;
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
112
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
188
113
|
|
|
189
114
|
function remapError(error: IOBleErrorRemap) {
|
|
@@ -221,45 +146,25 @@ export default class ReactNativeBleTransport {
|
|
|
221
146
|
|
|
222
147
|
_messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
223
148
|
|
|
224
|
-
_messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
225
|
-
|
|
226
149
|
name = 'ReactNativeBleTransport';
|
|
227
150
|
|
|
228
151
|
configured = false;
|
|
229
152
|
|
|
230
153
|
stopped = false;
|
|
231
154
|
|
|
232
|
-
scanTimeout =
|
|
155
|
+
scanTimeout = 3000;
|
|
233
156
|
|
|
234
157
|
runPromise: Deferred<any> | null = null;
|
|
235
158
|
|
|
236
159
|
emitter?: EventEmitter;
|
|
237
160
|
|
|
238
|
-
|
|
239
|
-
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
240
|
-
|
|
241
|
-
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
242
|
-
|
|
243
|
-
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
244
|
-
|
|
245
|
-
private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
|
|
246
|
-
|
|
247
|
-
private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
248
|
-
|
|
249
|
-
private activeProtocolV2Call: { uuid: string; token: number } | null = null;
|
|
250
|
-
|
|
251
|
-
private nextProtocolV2CallToken = 1;
|
|
252
|
-
|
|
253
|
-
private monitorTokens: Map<string, number> = new Map();
|
|
254
|
-
|
|
255
|
-
private nextMonitorToken = 1;
|
|
161
|
+
firmwareUploadWriteRecoveryIds = new Set<string>();
|
|
256
162
|
|
|
257
163
|
constructor(options: TransportOptions) {
|
|
258
|
-
this.scanTimeout = options.scanTimeout ??
|
|
164
|
+
this.scanTimeout = options.scanTimeout ?? 3000;
|
|
259
165
|
}
|
|
260
166
|
|
|
261
|
-
init(
|
|
262
|
-
setBleLogger(logger);
|
|
167
|
+
init(_logger: any, emitter: EventEmitter) {
|
|
263
168
|
this.emitter = emitter;
|
|
264
169
|
}
|
|
265
170
|
|
|
@@ -269,11 +174,6 @@ export default class ReactNativeBleTransport {
|
|
|
269
174
|
this._messages = messages;
|
|
270
175
|
}
|
|
271
176
|
|
|
272
|
-
configureProtocolV2(signedData: any) {
|
|
273
|
-
this._messagesV2 = parseConfigure(signedData);
|
|
274
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V2 schema configured');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
177
|
listen() {
|
|
278
178
|
// empty
|
|
279
179
|
}
|
|
@@ -284,6 +184,143 @@ export default class ReactNativeBleTransport {
|
|
|
284
184
|
return Promise.resolve(this.blePlxManager);
|
|
285
185
|
}
|
|
286
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
|
+
|
|
287
324
|
/**
|
|
288
325
|
* 获取设备列表
|
|
289
326
|
* 在搜索超过超时时间或设备数量大于 5 台时,返回 OneKey 设备,
|
|
@@ -323,7 +360,6 @@ export default class ReactNativeBleTransport {
|
|
|
323
360
|
blePlxManager.startDeviceScan(
|
|
324
361
|
null,
|
|
325
362
|
{
|
|
326
|
-
allowDuplicates: true,
|
|
327
363
|
scanMode: ScanMode.LowLatency,
|
|
328
364
|
},
|
|
329
365
|
(error, device) => {
|
|
@@ -351,41 +387,14 @@ export default class ReactNativeBleTransport {
|
|
|
351
387
|
return;
|
|
352
388
|
}
|
|
353
389
|
|
|
354
|
-
|
|
355
|
-
const isOneKey =
|
|
356
|
-
isOnekeyDevice(device?.name ?? null, device?.id) ||
|
|
357
|
-
isOnekeyDevice(device?.localName ?? null, device?.id) ||
|
|
358
|
-
hasKnownOneKeyService(device);
|
|
359
|
-
const shouldTraceCandidate =
|
|
360
|
-
!!displayName && /onekey|bixinkey|pro\s*2|pro\b|touch|^k\d|^t\d/i.test(displayName);
|
|
361
|
-
|
|
362
|
-
if (shouldTraceCandidate) {
|
|
363
|
-
Log?.debug('[ReactNativeBleTransport] scan candidate', {
|
|
364
|
-
name: device?.name,
|
|
365
|
-
localName: device?.localName,
|
|
366
|
-
id: device?.id,
|
|
367
|
-
serviceUUIDs: device?.serviceUUIDs,
|
|
368
|
-
accepted: isOneKey,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (isOneKey) {
|
|
390
|
+
if (isOnekeyDevice(device?.name ?? null, device?.id)) {
|
|
373
391
|
Log?.debug('search device start ======================');
|
|
374
|
-
const { name, localName, id
|
|
392
|
+
const { name, localName, id } = device ?? {};
|
|
375
393
|
Log?.debug(
|
|
376
|
-
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${
|
|
377
|
-
id ?? ''
|
|
378
|
-
}\nserviceUUIDs: ${(serviceUUIDs ?? []).join(',')}`
|
|
394
|
+
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${id ?? ''}`
|
|
379
395
|
);
|
|
380
396
|
addDevice(device as unknown as Device);
|
|
381
397
|
Log?.debug('search device end ======================\n');
|
|
382
|
-
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
383
|
-
Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
|
|
384
|
-
name: device?.name,
|
|
385
|
-
localName: device?.localName,
|
|
386
|
-
id: device?.id,
|
|
387
|
-
serviceUUIDs: device?.serviceUUIDs,
|
|
388
|
-
});
|
|
389
398
|
}
|
|
390
399
|
}
|
|
391
400
|
);
|
|
@@ -399,16 +408,7 @@ export default class ReactNativeBleTransport {
|
|
|
399
408
|
|
|
400
409
|
const addDevice = (device: Device) => {
|
|
401
410
|
if (deviceList.every(d => d.id !== device.id)) {
|
|
402
|
-
|
|
403
|
-
const protocolHint = inferProtocolHintFromDeviceName(displayName);
|
|
404
|
-
if (protocolHint) {
|
|
405
|
-
this.deviceProtocolHints.set(device.id, protocolHint);
|
|
406
|
-
}
|
|
407
|
-
deviceList.push({
|
|
408
|
-
...device,
|
|
409
|
-
name: displayName,
|
|
410
|
-
commType: 'ble',
|
|
411
|
-
} as IOneKeyDevice);
|
|
411
|
+
deviceList.push({ ...device, commType: 'ble' } as IOneKeyDevice);
|
|
412
412
|
}
|
|
413
413
|
};
|
|
414
414
|
|
|
@@ -420,45 +420,25 @@ export default class ReactNativeBleTransport {
|
|
|
420
420
|
}
|
|
421
421
|
|
|
422
422
|
async acquire(input: BleAcquireInput) {
|
|
423
|
-
const { uuid, forceCleanRunPromise
|
|
423
|
+
const { uuid, forceCleanRunPromise } = input;
|
|
424
424
|
|
|
425
425
|
if (!uuid) {
|
|
426
426
|
throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
if (cachedTransport) {
|
|
431
|
-
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
432
|
-
const isCachedDeviceConnected = await cachedTransport.device.isConnected().catch(() => false);
|
|
433
|
-
if (
|
|
434
|
-
isCachedDeviceConnected &&
|
|
435
|
-
cachedProtocol &&
|
|
436
|
-
(!expectedProtocol || cachedProtocol === expectedProtocol)
|
|
437
|
-
) {
|
|
438
|
-
Log?.debug(
|
|
439
|
-
'[ReactNativeBleTransport] reuse cached BLE transport:',
|
|
440
|
-
uuid,
|
|
441
|
-
cachedProtocol
|
|
442
|
-
);
|
|
443
|
-
return { uuid, protocolType: cachedProtocol };
|
|
444
|
-
}
|
|
429
|
+
let device: Device | null = null;
|
|
445
430
|
|
|
431
|
+
if (transportCache[uuid]) {
|
|
446
432
|
/**
|
|
447
|
-
* If the transport is not
|
|
448
|
-
*
|
|
433
|
+
* If the transport is not released due to an exception operation
|
|
434
|
+
* it will be handled again here
|
|
449
435
|
*/
|
|
450
|
-
Log?.debug('transport not
|
|
451
|
-
await this.release(uuid
|
|
436
|
+
Log?.debug('transport not be released, will release: ', uuid);
|
|
437
|
+
await this.release(uuid);
|
|
452
438
|
}
|
|
453
439
|
|
|
454
|
-
let device: Device | null = null;
|
|
455
|
-
|
|
456
440
|
if (forceCleanRunPromise && this.runPromise) {
|
|
457
|
-
|
|
458
|
-
this.runPromise.reject(error);
|
|
459
|
-
this.rejectAllProtocolV2Frames(error);
|
|
460
|
-
this.runPromise = null;
|
|
461
|
-
this.activeProtocolV2Call = null;
|
|
441
|
+
this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
|
|
462
442
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
463
443
|
}
|
|
464
444
|
|
|
@@ -470,12 +450,11 @@ export default class ReactNativeBleTransport {
|
|
|
470
450
|
throw error;
|
|
471
451
|
}
|
|
472
452
|
|
|
453
|
+
// check device is bonded
|
|
473
454
|
if (Platform.OS === 'android') {
|
|
474
455
|
const bondState = await pairDevice(uuid);
|
|
475
456
|
if (bondState.bonding) {
|
|
476
457
|
await onDeviceBondState(uuid);
|
|
477
|
-
} else if (!bondState.bonded) {
|
|
478
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
|
|
479
458
|
}
|
|
480
459
|
}
|
|
481
460
|
|
|
@@ -501,6 +480,7 @@ export default class ReactNativeBleTransport {
|
|
|
501
480
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
502
481
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
503
482
|
) {
|
|
483
|
+
connectOptions = {};
|
|
504
484
|
Log?.debug('first try to reconnect without params');
|
|
505
485
|
device = await blePlxManager.connectToDevice(uuid);
|
|
506
486
|
} else if (e.errorCode === BleErrorCode.DeviceAlreadyConnected) {
|
|
@@ -520,16 +500,17 @@ export default class ReactNativeBleTransport {
|
|
|
520
500
|
Log?.debug('not connected, try to connect to device: ', uuid);
|
|
521
501
|
|
|
522
502
|
try {
|
|
523
|
-
|
|
503
|
+
await device.connect(connectOptions);
|
|
524
504
|
} catch (e) {
|
|
525
505
|
Log?.debug('not connected, try to connect to device has error: ', e);
|
|
526
506
|
if (
|
|
527
507
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
528
508
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
529
509
|
) {
|
|
510
|
+
connectOptions = {};
|
|
530
511
|
Log?.debug('second try to reconnect without params');
|
|
531
512
|
try {
|
|
532
|
-
|
|
513
|
+
await device.connect();
|
|
533
514
|
} catch (e) {
|
|
534
515
|
Log?.debug('last try to reconnect error: ', e);
|
|
535
516
|
// last try to reconnect device if this issue exists
|
|
@@ -537,7 +518,7 @@ export default class ReactNativeBleTransport {
|
|
|
537
518
|
if (e.errorCode === BleErrorCode.OperationCancelled) {
|
|
538
519
|
Log?.debug('last try to reconnect');
|
|
539
520
|
await device.cancelConnection();
|
|
540
|
-
|
|
521
|
+
await device.connect();
|
|
541
522
|
}
|
|
542
523
|
}
|
|
543
524
|
} else {
|
|
@@ -546,194 +527,41 @@ export default class ReactNativeBleTransport {
|
|
|
546
527
|
}
|
|
547
528
|
}
|
|
548
529
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
await device.discoverAllServicesAndCharacteristics();
|
|
552
|
-
let infos = tryToGetConfiguration(device);
|
|
553
|
-
let characteristics;
|
|
554
|
-
let fallbackServiceUuid: string | undefined;
|
|
555
|
-
|
|
556
|
-
if (!infos) {
|
|
557
|
-
for (const serviceUuid of getBluetoothServiceUuids()) {
|
|
558
|
-
try {
|
|
559
|
-
characteristics = await device.characteristicsForService(serviceUuid);
|
|
560
|
-
infos = getInfosForServiceUuid(serviceUuid, 'classic');
|
|
561
|
-
break;
|
|
562
|
-
} catch (e) {
|
|
563
|
-
Log?.error(e);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
if (!infos) {
|
|
569
|
-
const services = await device.services();
|
|
570
|
-
Log?.debug(
|
|
571
|
-
'[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:',
|
|
572
|
-
services?.map(service => service.uuid)
|
|
573
|
-
);
|
|
574
|
-
|
|
575
|
-
const knownService = services.find(service =>
|
|
576
|
-
getInfosForServiceUuid(service.uuid, 'classic')
|
|
577
|
-
);
|
|
578
|
-
const fallbackService =
|
|
579
|
-
knownService ?? services.find(service => !isGenericBleService(service.uuid)) ?? services[0];
|
|
580
|
-
|
|
581
|
-
if (fallbackService) {
|
|
582
|
-
fallbackServiceUuid = fallbackService.uuid;
|
|
583
|
-
characteristics = await device.characteristicsForService(fallbackService.uuid);
|
|
584
|
-
Log?.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
if (!infos) {
|
|
589
|
-
if (!fallbackServiceUuid) {
|
|
590
|
-
try {
|
|
591
|
-
Log?.debug('cancel connection when service not found');
|
|
592
|
-
await device.cancelConnection();
|
|
593
|
-
} catch (e) {
|
|
594
|
-
Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
|
|
595
|
-
}
|
|
596
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
const serviceUuid = infos?.serviceUuid ?? fallbackServiceUuid;
|
|
601
|
-
const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
|
|
602
|
-
const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
|
|
603
|
-
|
|
604
|
-
if (!serviceUuid) {
|
|
605
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
if (!characteristics) {
|
|
609
|
-
characteristics = await device.characteristicsForService(serviceUuid);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
if (!characteristics) {
|
|
613
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
let writeCharacteristic;
|
|
617
|
-
let notifyCharacteristic;
|
|
618
|
-
for (const c of characteristics) {
|
|
619
|
-
if (isSameBleUuid(c.uuid, writeUuid)) {
|
|
620
|
-
writeCharacteristic = c;
|
|
621
|
-
} else if (isSameBleUuid(c.uuid, notifyUuid)) {
|
|
622
|
-
notifyCharacteristic = c;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
if (!writeCharacteristic) {
|
|
627
|
-
throw ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (!notifyCharacteristic) {
|
|
631
|
-
throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (!hasWritableCapability(writeCharacteristic)) {
|
|
635
|
-
throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
if (!notifyCharacteristic.isNotifiable) {
|
|
639
|
-
throw ERRORS.TypedError(
|
|
640
|
-
'BLECharacteristicNotNotifiable: notify characteristic not notifiable'
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
const protocolHint = expectedProtocol
|
|
645
|
-
? undefined
|
|
646
|
-
: this.deviceProtocolHints.get(uuid) ??
|
|
647
|
-
inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
|
|
530
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(device);
|
|
648
531
|
|
|
649
532
|
// release transport before new transport instance
|
|
650
|
-
await this.release(uuid
|
|
651
|
-
if (protocolHint) {
|
|
652
|
-
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
653
|
-
}
|
|
533
|
+
await this.release(uuid);
|
|
654
534
|
|
|
655
535
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
656
|
-
if (Platform.OS === 'android') {
|
|
657
|
-
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
658
|
-
}
|
|
659
|
-
const monitorToken = this.nextMonitorToken;
|
|
660
|
-
this.nextMonitorToken += 1;
|
|
661
|
-
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
662
|
-
transport.monitorToken = monitorToken;
|
|
663
|
-
transport.notifyTransactionId = notifyTransactionId;
|
|
664
|
-
this.monitorTokens.set(uuid, monitorToken);
|
|
665
536
|
transport.notifySubscription = this._monitorCharacteristic(
|
|
666
537
|
transport.notifyCharacteristic,
|
|
667
|
-
uuid
|
|
668
|
-
monitorToken,
|
|
669
|
-
notifyTransactionId
|
|
538
|
+
uuid
|
|
670
539
|
);
|
|
671
540
|
transportCache[uuid] = transport;
|
|
672
541
|
|
|
673
|
-
this.protocolV2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
|
|
674
|
-
|
|
675
|
-
if (Platform.OS === 'ios') {
|
|
676
|
-
await new Promise<void>(resolve => {
|
|
677
|
-
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
678
|
-
});
|
|
679
|
-
} else if (Platform.OS === 'android') {
|
|
680
|
-
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
684
|
-
|
|
685
542
|
this.emitter?.emit('device-connect', {
|
|
686
543
|
name: device.name,
|
|
687
544
|
id: device.id,
|
|
688
545
|
connectId: device.id,
|
|
689
546
|
});
|
|
690
547
|
|
|
691
|
-
transport
|
|
692
|
-
if (transportCache[uuid] !== transport) {
|
|
693
|
-
Log?.debug('device disconnect ignored for stale transport: ', device?.id);
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
try {
|
|
698
|
-
Log?.debug('device disconnect: ', device?.id);
|
|
699
|
-
this.emitter?.emit('device-disconnect', {
|
|
700
|
-
name: device?.name,
|
|
701
|
-
id: device?.id,
|
|
702
|
-
connectId: device?.id,
|
|
703
|
-
});
|
|
704
|
-
if (this.runPromise) {
|
|
705
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
|
|
706
|
-
this.runPromise.reject(error);
|
|
707
|
-
this.rejectAllProtocolV2Frames(error);
|
|
708
|
-
}
|
|
709
|
-
} catch (e) {
|
|
710
|
-
Log?.debug('device disconnect error: ', e);
|
|
711
|
-
} finally {
|
|
712
|
-
this.release(uuid, true);
|
|
713
|
-
}
|
|
714
|
-
});
|
|
548
|
+
this.attachDisconnectSubscription(transport, device, uuid);
|
|
715
549
|
|
|
716
|
-
return { uuid
|
|
550
|
+
return { uuid };
|
|
717
551
|
}
|
|
718
552
|
|
|
719
|
-
_monitorCharacteristic(
|
|
720
|
-
characteristic: Characteristic,
|
|
721
|
-
uuid: string,
|
|
722
|
-
monitorToken: number,
|
|
723
|
-
notifyTransactionId: string
|
|
724
|
-
): Subscription {
|
|
553
|
+
_monitorCharacteristic(characteristic: Characteristic, uuid: string): Subscription {
|
|
725
554
|
let bufferLength = 0;
|
|
726
555
|
let buffer: any[] = [];
|
|
727
556
|
const subscription = characteristic.monitor((error, c) => {
|
|
728
|
-
const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
|
|
729
557
|
if (error) {
|
|
730
558
|
Log?.debug(
|
|
731
559
|
`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${
|
|
732
560
|
error as unknown as string
|
|
733
561
|
}`
|
|
734
562
|
);
|
|
735
|
-
if (
|
|
736
|
-
Log?.debug('
|
|
563
|
+
if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
|
|
564
|
+
Log?.debug('notify error ignored during FirmwareUpload write recovery: ', uuid);
|
|
737
565
|
return;
|
|
738
566
|
}
|
|
739
567
|
if (this.runPromise) {
|
|
@@ -755,45 +583,27 @@ export default class ReactNativeBleTransport {
|
|
|
755
583
|
error.reason?.includes('Writing is not permitted') || // pro firmware 2.3.4 upgrade
|
|
756
584
|
error.reason?.includes('notify change failed for device')
|
|
757
585
|
) {
|
|
758
|
-
|
|
759
|
-
HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
586
|
+
this.runPromise.reject(
|
|
587
|
+
ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotifyChangeFailure)
|
|
760
588
|
);
|
|
761
|
-
this.runPromise.reject(notifyError);
|
|
762
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
763
589
|
Log?.debug(
|
|
764
590
|
`${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
|
|
765
591
|
);
|
|
766
592
|
return;
|
|
767
593
|
}
|
|
768
|
-
|
|
769
|
-
this.runPromise.reject(notifyError);
|
|
770
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
594
|
+
this.runPromise.reject(ERRORS.TypedError(ERROR));
|
|
771
595
|
Log?.debug(': monitor notify error, and has unreleased Promise', Error);
|
|
772
596
|
}
|
|
773
597
|
|
|
774
598
|
return;
|
|
775
599
|
}
|
|
776
600
|
|
|
777
|
-
if (!isCurrentMonitor) {
|
|
778
|
-
Log?.debug('monitor data ignored for stale transport: ', uuid, notifyTransactionId);
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
601
|
if (!c) {
|
|
783
602
|
throw ERRORS.TypedError(HardwareErrorCode.BleMonitorError);
|
|
784
603
|
}
|
|
785
604
|
|
|
786
605
|
try {
|
|
787
606
|
const data = Buffer.from(c.value as string, 'base64');
|
|
788
|
-
const protocol = this.deviceProtocol.get(uuid);
|
|
789
|
-
if (!protocol) {
|
|
790
|
-
Log?.debug('monitor data ignored before protocol detection: ', uuid);
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
if (protocol === 'V2') {
|
|
794
|
-
this.handleProtocolV2Notification(uuid, new Uint8Array(data));
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
607
|
// console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
|
|
798
608
|
if (isHeaderChunk(data)) {
|
|
799
609
|
bufferLength = data.readInt32BE(5);
|
|
@@ -802,7 +612,7 @@ export default class ReactNativeBleTransport {
|
|
|
802
612
|
buffer = buffer.concat([...data]);
|
|
803
613
|
}
|
|
804
614
|
|
|
805
|
-
if (buffer.length -
|
|
615
|
+
if (buffer.length - COMMON_HEADER_SIZE >= bufferLength) {
|
|
806
616
|
const value = Buffer.from(buffer);
|
|
807
617
|
// console.log(
|
|
808
618
|
// '[hd-transport-react-native] Received a complete packet of data, resolve Promise, this.runPromise: ',
|
|
@@ -816,41 +626,17 @@ export default class ReactNativeBleTransport {
|
|
|
816
626
|
}
|
|
817
627
|
} catch (error) {
|
|
818
628
|
Log?.debug('monitor data error: ', error);
|
|
819
|
-
|
|
820
|
-
this.runPromise?.reject(notifyError);
|
|
821
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
629
|
+
this.runPromise?.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
|
|
822
630
|
}
|
|
823
|
-
},
|
|
631
|
+
}, uuid);
|
|
824
632
|
|
|
825
633
|
return subscription;
|
|
826
634
|
}
|
|
827
635
|
|
|
828
|
-
async release(uuid: string
|
|
636
|
+
async release(uuid: string) {
|
|
829
637
|
const transport = transportCache[uuid];
|
|
830
|
-
if (this.runPromise) {
|
|
831
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
832
|
-
this.runPromise.reject(error);
|
|
833
|
-
this.runPromise = null;
|
|
834
|
-
this.rejectAllProtocolV2Frames(error);
|
|
835
|
-
this.activeProtocolV2Call = null;
|
|
836
|
-
} else {
|
|
837
|
-
this.resetProtocolV2Frames(uuid);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
if (Platform.OS === 'android' && !onclose && transport) {
|
|
841
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
842
|
-
this.resetProtocolV2Frames(uuid);
|
|
843
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
844
|
-
this.activeProtocolV2Call = null;
|
|
845
|
-
}
|
|
846
|
-
return Promise.resolve(true);
|
|
847
|
-
}
|
|
848
638
|
|
|
849
639
|
if (transport) {
|
|
850
|
-
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
851
|
-
this.monitorTokens.delete(uuid);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
640
|
// Clean up disconnect subscription first to prevent callbacks on released transport
|
|
855
641
|
Log?.debug('release: removing disconnect subscription for device: ', uuid);
|
|
856
642
|
transport.disconnectSubscription?.remove();
|
|
@@ -864,27 +650,12 @@ export default class ReactNativeBleTransport {
|
|
|
864
650
|
transport.notifySubscription?.remove();
|
|
865
651
|
transport.notifySubscription = undefined;
|
|
866
652
|
|
|
867
|
-
if (transport.notifyTransactionId) {
|
|
868
|
-
try {
|
|
869
|
-
await this.blePlxManager?.cancelTransaction(transport.notifyTransactionId);
|
|
870
|
-
} catch (e) {
|
|
871
|
-
Log?.debug('release: cancel notify transaction error (ignored): ', e?.message || e);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
653
|
delete transportCache[uuid];
|
|
876
|
-
}
|
|
877
654
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
this.resetProtocolV2Frames(uuid);
|
|
883
|
-
|
|
884
|
-
try {
|
|
885
|
-
await this.blePlxManager?.cancelTransaction(uuid);
|
|
886
|
-
} catch (e) {
|
|
887
|
-
Log?.debug('release: cancel transaction error (ignored): ', e?.message || e);
|
|
655
|
+
// Temporary close the Android disconnect after each request
|
|
656
|
+
if (Platform.OS === 'android') {
|
|
657
|
+
// await this.blePlxManager?.cancelDeviceConnection(uuid);
|
|
658
|
+
}
|
|
888
659
|
}
|
|
889
660
|
|
|
890
661
|
return Promise.resolve(true);
|
|
@@ -894,12 +665,7 @@ export default class ReactNativeBleTransport {
|
|
|
894
665
|
await this.call(session, name, data);
|
|
895
666
|
}
|
|
896
667
|
|
|
897
|
-
async call(
|
|
898
|
-
uuid: string,
|
|
899
|
-
name: string,
|
|
900
|
-
data: Record<string, unknown>,
|
|
901
|
-
options?: TransportCallOptions
|
|
902
|
-
) {
|
|
668
|
+
async call(uuid: string, name: string, data: Record<string, unknown>) {
|
|
903
669
|
if (this.stopped) {
|
|
904
670
|
// eslint-disable-next-line prefer-promise-reject-errors
|
|
905
671
|
return Promise.reject(ERRORS.TypedError('Transport stopped.'));
|
|
@@ -915,13 +681,13 @@ export default class ReactNativeBleTransport {
|
|
|
915
681
|
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
916
682
|
}
|
|
917
683
|
|
|
918
|
-
const
|
|
919
|
-
if (!
|
|
920
|
-
throw ERRORS.TypedError(
|
|
921
|
-
HardwareErrorCode.RuntimeError,
|
|
922
|
-
`Device protocol has not been detected for ${uuid}`
|
|
923
|
-
);
|
|
684
|
+
const transport = transportCache[uuid];
|
|
685
|
+
if (!transport) {
|
|
686
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
924
687
|
}
|
|
688
|
+
|
|
689
|
+
this.runPromise = createDeferred();
|
|
690
|
+
const messages = this._messages;
|
|
925
691
|
// Upload resources on low-end phones may OOM
|
|
926
692
|
if (name === 'ResourceUpdate' || name === 'ResourceAck') {
|
|
927
693
|
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
|
|
@@ -929,44 +695,12 @@ export default class ReactNativeBleTransport {
|
|
|
929
695
|
hash: data?.hash,
|
|
930
696
|
});
|
|
931
697
|
} else if (LogBlockCommand.has(name)) {
|
|
932
|
-
Log?.debug('transport-react-native', 'call-', ' name: ', name
|
|
698
|
+
Log?.debug('transport-react-native', 'call-', ' name: ', name);
|
|
933
699
|
} else {
|
|
934
|
-
Log?.debug(
|
|
935
|
-
'transport-react-native',
|
|
936
|
-
'call-',
|
|
937
|
-
' name: ',
|
|
938
|
-
name,
|
|
939
|
-
' data: ',
|
|
940
|
-
data,
|
|
941
|
-
' protocol: ',
|
|
942
|
-
protocol
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
if (protocol === 'V2') {
|
|
947
|
-
return this.callProtocolV2(uuid, name, data, options);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
return this.callProtocolV1(uuid, name, data, options);
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
private async callProtocolV1(
|
|
954
|
-
uuid: string,
|
|
955
|
-
name: string,
|
|
956
|
-
data: Record<string, unknown>,
|
|
957
|
-
options?: TransportCallOptions
|
|
958
|
-
) {
|
|
959
|
-
if (!this._messages) {
|
|
960
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
700
|
+
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', data);
|
|
961
701
|
}
|
|
962
702
|
|
|
963
|
-
const
|
|
964
|
-
const runPromise = createDeferred<string>();
|
|
965
|
-
runPromise.promise.catch(() => undefined);
|
|
966
|
-
this.runPromise = runPromise;
|
|
967
|
-
const messages = this._messages;
|
|
968
|
-
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
969
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
703
|
+
const buffers = buildBuffers(messages, name, data);
|
|
970
704
|
|
|
971
705
|
async function writeChunkedData(
|
|
972
706
|
buffers: ByteBuffer[],
|
|
@@ -995,6 +729,41 @@ export default class ReactNativeBleTransport {
|
|
|
995
729
|
}
|
|
996
730
|
}
|
|
997
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
|
+
|
|
998
767
|
if (name === 'EmmcFileWrite') {
|
|
999
768
|
await writeChunkedData(
|
|
1000
769
|
buffers,
|
|
@@ -1005,10 +774,57 @@ export default class ReactNativeBleTransport {
|
|
|
1005
774
|
}
|
|
1006
775
|
);
|
|
1007
776
|
} else if (name === 'FirmwareUpload') {
|
|
1008
|
-
|
|
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(
|
|
1009
786
|
buffers,
|
|
1010
787
|
async data => {
|
|
1011
|
-
|
|
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
|
+
}
|
|
1012
828
|
},
|
|
1013
829
|
e => {
|
|
1014
830
|
this.runPromise = null;
|
|
@@ -1037,41 +853,20 @@ export default class ReactNativeBleTransport {
|
|
|
1037
853
|
}
|
|
1038
854
|
|
|
1039
855
|
try {
|
|
1040
|
-
const response = await
|
|
1041
|
-
runPromise.promise,
|
|
1042
|
-
new Promise<never>((_, reject) => {
|
|
1043
|
-
if (options?.timeoutMs) {
|
|
1044
|
-
timeout = setTimeout(() => {
|
|
1045
|
-
const error = ERRORS.TypedError(
|
|
1046
|
-
HardwareErrorCode.BleTimeoutError,
|
|
1047
|
-
`BLE response timeout after ${options.timeoutMs}ms for ${name}`
|
|
1048
|
-
);
|
|
1049
|
-
runPromise.reject(error);
|
|
1050
|
-
reject(error);
|
|
1051
|
-
}, options.timeoutMs);
|
|
1052
|
-
}
|
|
1053
|
-
}),
|
|
1054
|
-
]);
|
|
856
|
+
const response = await this.runPromise.promise;
|
|
1055
857
|
|
|
1056
858
|
if (typeof response !== 'string') {
|
|
1057
859
|
throw new Error('Returning data is not string.');
|
|
1058
860
|
}
|
|
1059
861
|
|
|
1060
862
|
Log?.debug('receive data: ', response);
|
|
1061
|
-
const jsonData =
|
|
863
|
+
const jsonData = receiveOne(messages, response);
|
|
1062
864
|
return check.call(jsonData);
|
|
1063
865
|
} catch (e) {
|
|
1064
|
-
|
|
1065
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe call failed:', e);
|
|
1066
|
-
} else {
|
|
1067
|
-
Log?.error('call error: ', e);
|
|
1068
|
-
}
|
|
866
|
+
Log?.error('call error: ', e);
|
|
1069
867
|
throw e;
|
|
1070
868
|
} finally {
|
|
1071
|
-
|
|
1072
|
-
if (this.runPromise === runPromise) {
|
|
1073
|
-
this.runPromise = null;
|
|
1074
|
-
}
|
|
869
|
+
this.runPromise = null;
|
|
1075
870
|
}
|
|
1076
871
|
}
|
|
1077
872
|
|
|
@@ -1138,13 +933,6 @@ export default class ReactNativeBleTransport {
|
|
|
1138
933
|
if (transportCache[session]) {
|
|
1139
934
|
delete transportCache[session];
|
|
1140
935
|
}
|
|
1141
|
-
this.deviceProtocol.delete(session);
|
|
1142
|
-
this.deviceProtocolHints.delete(session);
|
|
1143
|
-
this.protocolV2Assemblers.delete(session);
|
|
1144
|
-
this.resetProtocolV2Frames(session);
|
|
1145
|
-
if (this.activeProtocolV2Call?.uuid === session) {
|
|
1146
|
-
this.activeProtocolV2Call = null;
|
|
1147
|
-
}
|
|
1148
936
|
|
|
1149
937
|
// emit the disconnect event
|
|
1150
938
|
try {
|
|
@@ -1167,421 +955,4 @@ export default class ReactNativeBleTransport {
|
|
|
1167
955
|
}
|
|
1168
956
|
this.runPromise = null;
|
|
1169
957
|
}
|
|
1170
|
-
|
|
1171
|
-
private getCachedTransport(uuid: string) {
|
|
1172
|
-
const transport = transportCache[uuid];
|
|
1173
|
-
if (!transport) {
|
|
1174
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
1175
|
-
}
|
|
1176
|
-
return transport;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
private createProtocolMismatchError(expected: ProtocolType) {
|
|
1180
|
-
return ERRORS.TypedError(
|
|
1181
|
-
HardwareErrorCode.RuntimeError,
|
|
1182
|
-
`Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
|
|
1183
|
-
);
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
private createProtocolDetectionError() {
|
|
1187
|
-
return ERRORS.TypedError(
|
|
1188
|
-
HardwareErrorCode.BleTimeoutError,
|
|
1189
|
-
'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
|
|
1190
|
-
);
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
|
|
1194
|
-
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
1195
|
-
this.deviceProtocol.delete(uuid);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
private async detectProtocol(
|
|
1200
|
-
uuid: string,
|
|
1201
|
-
expectedProtocol?: ProtocolType,
|
|
1202
|
-
protocolHint?: ProtocolType
|
|
1203
|
-
): Promise<ProtocolType> {
|
|
1204
|
-
if (expectedProtocol === 'V1') {
|
|
1205
|
-
if (await this.probeProtocolV1(uuid)) {
|
|
1206
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
1207
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1 (expected)`);
|
|
1208
|
-
return 'V1';
|
|
1209
|
-
}
|
|
1210
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
if (expectedProtocol === 'V2') {
|
|
1214
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
1215
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1216
|
-
return 'V2';
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
if (protocolHint === 'V2') {
|
|
1220
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
1221
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
|
|
1222
|
-
return 'V2';
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
1226
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
1227
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
|
|
1228
|
-
return 'V2';
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
const protocolV1Detected = await this.probeProtocolV1(uuid);
|
|
1232
|
-
if (protocolV1Detected) {
|
|
1233
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
1234
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
1235
|
-
return 'V1';
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
await this.resetProbeStateAfterProtocolProbe(uuid, 'V1');
|
|
1239
|
-
if (await this.probeProtocolV2(uuid)) {
|
|
1240
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
1241
|
-
Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2`);
|
|
1242
|
-
return 'V2';
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
this.deviceProtocol.delete(uuid);
|
|
1246
|
-
throw this.createProtocolDetectionError();
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
1250
|
-
const transport = transportCache[uuid];
|
|
1251
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1252
|
-
this.resetProtocolV2Frames(uuid);
|
|
1253
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
1254
|
-
this.activeProtocolV2Call = null;
|
|
1255
|
-
}
|
|
1256
|
-
if (this.runPromise) {
|
|
1257
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1258
|
-
this.runPromise.reject(error);
|
|
1259
|
-
this.runPromise = null;
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
if (!transport) return;
|
|
1263
|
-
|
|
1264
|
-
const previousNotifyTransactionId = transport.notifyTransactionId;
|
|
1265
|
-
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1266
|
-
this.monitorTokens.delete(uuid);
|
|
1267
|
-
}
|
|
1268
|
-
transport.notifySubscription?.remove();
|
|
1269
|
-
transport.notifySubscription = undefined;
|
|
1270
|
-
if (previousNotifyTransactionId) {
|
|
1271
|
-
try {
|
|
1272
|
-
await this.blePlxManager?.cancelTransaction(previousNotifyTransactionId);
|
|
1273
|
-
} catch (error) {
|
|
1274
|
-
Log?.debug(
|
|
1275
|
-
`[ReactNativeBleTransport] cancel notify after Protocol ${protocol} probe failed:`,
|
|
1276
|
-
error?.message || error
|
|
1277
|
-
);
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
const monitorToken = this.nextMonitorToken;
|
|
1282
|
-
this.nextMonitorToken += 1;
|
|
1283
|
-
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
1284
|
-
transport.monitorToken = monitorToken;
|
|
1285
|
-
transport.notifyTransactionId = notifyTransactionId;
|
|
1286
|
-
this.monitorTokens.set(uuid, monitorToken);
|
|
1287
|
-
transport.notifySubscription = this._monitorCharacteristic(
|
|
1288
|
-
transport.notifyCharacteristic,
|
|
1289
|
-
uuid,
|
|
1290
|
-
monitorToken,
|
|
1291
|
-
notifyTransactionId
|
|
1292
|
-
);
|
|
1293
|
-
if (Platform.OS === 'ios') {
|
|
1294
|
-
await new Promise<void>(resolve => {
|
|
1295
|
-
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
private async probeProtocolV1(uuid: string) {
|
|
1301
|
-
if (!this._messages) {
|
|
1302
|
-
return false;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
try {
|
|
1306
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
1307
|
-
await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1308
|
-
return true;
|
|
1309
|
-
} catch (error) {
|
|
1310
|
-
this.clearProbeProtocol(uuid, 'V1');
|
|
1311
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
|
|
1312
|
-
return false;
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
private async probeProtocolV2(uuid: string) {
|
|
1317
|
-
if (!this._messages || !this._messagesV2) {
|
|
1318
|
-
return false;
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
1322
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1323
|
-
const detected = await probeProtocolV2Helper({
|
|
1324
|
-
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
1325
|
-
this.callProtocolV2(uuid, name, data, options),
|
|
1326
|
-
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
|
|
1327
|
-
logger: Log,
|
|
1328
|
-
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1329
|
-
onProbeFailed: () => {
|
|
1330
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1331
|
-
this.resetProtocolV2Frames(uuid);
|
|
1332
|
-
},
|
|
1333
|
-
});
|
|
1334
|
-
if (!detected) {
|
|
1335
|
-
this.clearProbeProtocol(uuid, 'V2');
|
|
1336
|
-
}
|
|
1337
|
-
return detected;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
|
|
1341
|
-
try {
|
|
1342
|
-
if (!this.runPromise || this.activeProtocolV2Call?.uuid !== uuid) {
|
|
1343
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1344
|
-
this.resetProtocolV2Frames(uuid);
|
|
1345
|
-
return;
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
if (data.length === 0) return;
|
|
1349
|
-
|
|
1350
|
-
const assembler = this.protocolV2Assemblers.get(uuid);
|
|
1351
|
-
if (!assembler) return;
|
|
1352
|
-
|
|
1353
|
-
let frameData = assembler.push(data);
|
|
1354
|
-
while (frameData) {
|
|
1355
|
-
this.resolveProtocolV2Frame(uuid, frameData);
|
|
1356
|
-
frameData = assembler.push(new Uint8Array(0));
|
|
1357
|
-
}
|
|
1358
|
-
} catch (error) {
|
|
1359
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
|
|
1360
|
-
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1361
|
-
this.runPromise?.reject(notifyError);
|
|
1362
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
private getProtocolV2FrameQueue(uuid: string) {
|
|
1367
|
-
let queue = this.protocolV2FrameQueues.get(uuid);
|
|
1368
|
-
if (!queue) {
|
|
1369
|
-
queue = [];
|
|
1370
|
-
this.protocolV2FrameQueues.set(uuid, queue);
|
|
1371
|
-
}
|
|
1372
|
-
return queue;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
private resolveProtocolV2Frame(uuid: string, frame: Uint8Array) {
|
|
1376
|
-
const framePromise = this.protocolV2FramePromises.get(uuid);
|
|
1377
|
-
if (framePromise) {
|
|
1378
|
-
framePromise.resolve(frame);
|
|
1379
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1380
|
-
return;
|
|
1381
|
-
}
|
|
1382
|
-
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
private rejectAllProtocolV2Frames(error: Error) {
|
|
1386
|
-
this.protocolV2FrameQueues.clear();
|
|
1387
|
-
for (const framePromise of this.protocolV2FramePromises.values()) {
|
|
1388
|
-
framePromise.reject(error);
|
|
1389
|
-
}
|
|
1390
|
-
this.protocolV2FramePromises.clear();
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
private resetProtocolV2Frames(uuid: string) {
|
|
1394
|
-
this.protocolV2FrameQueues.delete(uuid);
|
|
1395
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
private isActiveProtocolV2Call(uuid: string, token: number) {
|
|
1399
|
-
return this.activeProtocolV2Call?.uuid === uuid && this.activeProtocolV2Call.token === token;
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
private async readProtocolV2Frame(uuid: string) {
|
|
1403
|
-
const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
|
|
1404
|
-
if (queuedFrame) {
|
|
1405
|
-
return queuedFrame;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
const framePromise = createDeferred<Uint8Array>();
|
|
1409
|
-
this.protocolV2FramePromises.set(uuid, framePromise);
|
|
1410
|
-
try {
|
|
1411
|
-
return await framePromise.promise;
|
|
1412
|
-
} finally {
|
|
1413
|
-
if (this.protocolV2FramePromises.get(uuid) === framePromise) {
|
|
1414
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
private async writeProtocolV2Frame(
|
|
1420
|
-
transport: BleTransport,
|
|
1421
|
-
frame: Uint8Array,
|
|
1422
|
-
options?: { highVolume?: boolean; writeWithResponse?: boolean }
|
|
1423
|
-
) {
|
|
1424
|
-
const tuning = getProtocolV2BleTuning();
|
|
1425
|
-
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
1426
|
-
platform: Platform.OS,
|
|
1427
|
-
iosPacketLength: tuning.iosPacketLength,
|
|
1428
|
-
androidPacketLength: tuning.androidPacketLength,
|
|
1429
|
-
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1430
|
-
});
|
|
1431
|
-
const writeWithResponse =
|
|
1432
|
-
!!options?.writeWithResponse || (!!options?.highVolume && tuning.highVolumeWriteWithResponse);
|
|
1433
|
-
const writeMode = resolveBleWriteMode(
|
|
1434
|
-
transport.writeCharacteristic,
|
|
1435
|
-
writeWithResponse ? 'withResponse' : 'withoutResponse'
|
|
1436
|
-
);
|
|
1437
|
-
const shouldThrottle = !!options?.highVolume && writeMode === 'withoutResponse';
|
|
1438
|
-
let packetsWritten = 0;
|
|
1439
|
-
|
|
1440
|
-
try {
|
|
1441
|
-
for (let offset = 0; offset < frame.length; offset += packetCapacity) {
|
|
1442
|
-
const chunk = frame.slice(offset, offset + packetCapacity);
|
|
1443
|
-
const base64 = Buffer.from(chunk).toString('base64');
|
|
1444
|
-
if (writeMode === 'withResponse') {
|
|
1445
|
-
await transport.writeCharacteristic.writeWithResponse(base64);
|
|
1446
|
-
} else {
|
|
1447
|
-
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1448
|
-
}
|
|
1449
|
-
packetsWritten += 1;
|
|
1450
|
-
|
|
1451
|
-
if (
|
|
1452
|
-
shouldThrottle &&
|
|
1453
|
-
packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
|
|
1454
|
-
offset + packetCapacity < frame.length
|
|
1455
|
-
) {
|
|
1456
|
-
await delay(tuning.highVolumeWritePauseMs);
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
if (shouldThrottle) {
|
|
1461
|
-
await delay(tuning.highVolumeWriteFlushDelayMs);
|
|
1462
|
-
}
|
|
1463
|
-
} catch (error) {
|
|
1464
|
-
if (options?.highVolume && !writeWithResponse && packetsWritten === 0) {
|
|
1465
|
-
Log?.debug(
|
|
1466
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume writeWithoutResponse failed before data was sent, fallback to writeWithResponse:',
|
|
1467
|
-
error
|
|
1468
|
-
);
|
|
1469
|
-
await this.writeProtocolV2Frame(transport, frame, {
|
|
1470
|
-
highVolume: true,
|
|
1471
|
-
writeWithResponse: true,
|
|
1472
|
-
});
|
|
1473
|
-
return;
|
|
1474
|
-
}
|
|
1475
|
-
throw error;
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
private async callProtocolV2(
|
|
1480
|
-
uuid: string,
|
|
1481
|
-
name: string,
|
|
1482
|
-
data: Record<string, unknown>,
|
|
1483
|
-
options?: TransportCallOptions
|
|
1484
|
-
) {
|
|
1485
|
-
if (!this._messages || !this._messagesV2) {
|
|
1486
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'GetProtoVersion';
|
|
1490
|
-
if (this.runPromise) {
|
|
1491
|
-
if (!forceRun) {
|
|
1492
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1493
|
-
}
|
|
1494
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1495
|
-
this.runPromise.reject(error);
|
|
1496
|
-
this.rejectAllProtocolV2Frames(error);
|
|
1497
|
-
this.runPromise = null;
|
|
1498
|
-
this.activeProtocolV2Call = null;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
const transport = this.getCachedTransport(uuid);
|
|
1502
|
-
const runPromise = createDeferred<Uint8Array>();
|
|
1503
|
-
runPromise.promise.catch(() => undefined);
|
|
1504
|
-
this.runPromise = runPromise;
|
|
1505
|
-
const callToken = this.nextProtocolV2CallToken++;
|
|
1506
|
-
this.activeProtocolV2Call = { uuid, token: callToken };
|
|
1507
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1508
|
-
this.resetProtocolV2Frames(uuid);
|
|
1509
|
-
let completed = false;
|
|
1510
|
-
const callOptions = {
|
|
1511
|
-
...options,
|
|
1512
|
-
timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
|
|
1513
|
-
};
|
|
1514
|
-
const highVolumeWrite = LogBlockCommand.has(name);
|
|
1515
|
-
|
|
1516
|
-
if (highVolumeWrite) {
|
|
1517
|
-
const tuning = getProtocolV2BleTuning();
|
|
1518
|
-
Log?.debug(
|
|
1519
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
|
|
1520
|
-
name,
|
|
1521
|
-
{
|
|
1522
|
-
packetCapacity:
|
|
1523
|
-
Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1524
|
-
burstSize: tuning.highVolumeWriteBurstSize,
|
|
1525
|
-
pauseMs: tuning.highVolumeWritePauseMs,
|
|
1526
|
-
flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
|
|
1527
|
-
writeWithResponse: tuning.highVolumeWriteWithResponse,
|
|
1528
|
-
}
|
|
1529
|
-
);
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
try {
|
|
1533
|
-
const session = new ProtocolV2Session({
|
|
1534
|
-
schemas: {
|
|
1535
|
-
protocolV1: this._messages,
|
|
1536
|
-
protocolV2: this._messagesV2,
|
|
1537
|
-
},
|
|
1538
|
-
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1539
|
-
writeFrame: async (frame: Uint8Array) => {
|
|
1540
|
-
await this.writeProtocolV2Frame(transport, frame, {
|
|
1541
|
-
highVolume: highVolumeWrite,
|
|
1542
|
-
});
|
|
1543
|
-
},
|
|
1544
|
-
readFrame: async () => {
|
|
1545
|
-
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1546
|
-
if (!(rxFrame instanceof Uint8Array)) {
|
|
1547
|
-
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1548
|
-
}
|
|
1549
|
-
return rxFrame;
|
|
1550
|
-
},
|
|
1551
|
-
logger: Log,
|
|
1552
|
-
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1553
|
-
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
1554
|
-
ERRORS.TypedError(
|
|
1555
|
-
HardwareErrorCode.BleTimeoutError,
|
|
1556
|
-
`BLE response timeout after ${timeout}ms for ${name}`
|
|
1557
|
-
),
|
|
1558
|
-
});
|
|
1559
|
-
|
|
1560
|
-
const result = await session.call(name, data, callOptions);
|
|
1561
|
-
completed = true;
|
|
1562
|
-
return result;
|
|
1563
|
-
} catch (e) {
|
|
1564
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1565
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1566
|
-
this.resetProtocolV2Frames(uuid);
|
|
1567
|
-
}
|
|
1568
|
-
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1569
|
-
throw e;
|
|
1570
|
-
} finally {
|
|
1571
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1572
|
-
if (!completed) {
|
|
1573
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1574
|
-
}
|
|
1575
|
-
this.resetProtocolV2Frames(uuid);
|
|
1576
|
-
this.activeProtocolV2Call = null;
|
|
1577
|
-
}
|
|
1578
|
-
if (this.runPromise === runPromise) {
|
|
1579
|
-
this.runPromise = null;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
getProtocolType(path: string): ProtocolType | undefined {
|
|
1585
|
-
return this.deviceProtocol.get(path);
|
|
1586
|
-
}
|
|
1587
958
|
}
|