@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.3 → 1.2.0-alpha.31
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/__tests__/firmware-upload-progress.test.js +42 -0
- package/__tests__/protocol-v2.test.js +205 -18
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +165 -45
- package/package.json +4 -4
- package/src/index.ts +211 -59
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
|
+
const { getProtocolV1SendOptions, shouldLogFirmwareUploadProgress } = require('../src');
|
|
3
|
+
|
|
4
|
+
describe('firmware upload progress logging', () => {
|
|
5
|
+
test('按 5% 进度限流打印', () => {
|
|
6
|
+
expect(
|
|
7
|
+
shouldLogFirmwareUploadProgress({
|
|
8
|
+
percent: 9,
|
|
9
|
+
lastLoggedPercent: 5,
|
|
10
|
+
now: 5_000,
|
|
11
|
+
lastLoggedAt: 0,
|
|
12
|
+
})
|
|
13
|
+
).toBe(false);
|
|
14
|
+
|
|
15
|
+
expect(
|
|
16
|
+
shouldLogFirmwareUploadProgress({
|
|
17
|
+
percent: 10,
|
|
18
|
+
lastLoggedPercent: 5,
|
|
19
|
+
now: 5_000,
|
|
20
|
+
lastLoggedAt: 0,
|
|
21
|
+
})
|
|
22
|
+
).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('进度不足 5% 时最长每 10 秒打印一次心跳', () => {
|
|
26
|
+
expect(
|
|
27
|
+
shouldLogFirmwareUploadProgress({
|
|
28
|
+
percent: 7,
|
|
29
|
+
lastLoggedPercent: 5,
|
|
30
|
+
now: 10_000,
|
|
31
|
+
lastLoggedAt: 0,
|
|
32
|
+
})
|
|
33
|
+
).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('firmware upload write mode', () => {
|
|
38
|
+
test('固件上传使用带响应写入,普通命令保持默认模式', () => {
|
|
39
|
+
expect(getProtocolV1SendOptions('FirmwareUpload')).toEqual({ withoutResponse: false });
|
|
40
|
+
expect(getProtocolV1SendOptions('Initialize')).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -29,21 +29,25 @@ const protocolV1Schema = {
|
|
|
29
29
|
|
|
30
30
|
const protocolV2Schema = {
|
|
31
31
|
nested: {
|
|
32
|
-
|
|
32
|
+
ProtocolInfoRequest: {
|
|
33
33
|
fields: {},
|
|
34
34
|
},
|
|
35
|
-
|
|
35
|
+
ProtocolInfo: {
|
|
36
36
|
fields: {
|
|
37
|
-
|
|
37
|
+
version: {
|
|
38
38
|
type: 'uint32',
|
|
39
39
|
id: 1,
|
|
40
40
|
},
|
|
41
|
-
|
|
41
|
+
supported_messages: {
|
|
42
|
+
rule: 'repeated',
|
|
42
43
|
type: 'uint32',
|
|
43
44
|
id: 2,
|
|
45
|
+
options: {
|
|
46
|
+
packed: false,
|
|
47
|
+
},
|
|
44
48
|
},
|
|
45
|
-
|
|
46
|
-
type: '
|
|
49
|
+
protobuf_definition: {
|
|
50
|
+
type: 'string',
|
|
47
51
|
id: 3,
|
|
48
52
|
},
|
|
49
53
|
},
|
|
@@ -66,8 +70,8 @@ const protocolV2Schema = {
|
|
|
66
70
|
},
|
|
67
71
|
MessageType: {
|
|
68
72
|
values: {
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
MessageType_ProtocolInfoRequest: 60200,
|
|
74
|
+
MessageType_ProtocolInfo: 60201,
|
|
71
75
|
MessageType_Ping: 60206,
|
|
72
76
|
MessageType_Success: 60207,
|
|
73
77
|
},
|
|
@@ -118,6 +122,26 @@ const splitFrame = (frame, index) => [
|
|
|
118
122
|
];
|
|
119
123
|
|
|
120
124
|
describe('LowlevelTransport protocol framing', () => {
|
|
125
|
+
test('keeps active links when the Protocol V2 schema is configured repeatedly', () => {
|
|
126
|
+
const lowlevel = new LowlevelTransport();
|
|
127
|
+
const invalidateAllLinks = jest.fn().mockResolvedValue(undefined);
|
|
128
|
+
lowlevel.protocolV2Links.invalidateAllLinks = invalidateAllLinks;
|
|
129
|
+
|
|
130
|
+
lowlevel.configureProtocolV2(protocolV2Schema);
|
|
131
|
+
lowlevel.configureProtocolV2(protocolV2Schema);
|
|
132
|
+
|
|
133
|
+
expect(invalidateAllLinks).not.toHaveBeenCalled();
|
|
134
|
+
|
|
135
|
+
lowlevel.configureProtocolV2({
|
|
136
|
+
nested: {
|
|
137
|
+
...protocolV2Schema.nested,
|
|
138
|
+
ExtraMessage: { fields: {} },
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(invalidateAllLinks).toHaveBeenCalledWith('Protocol V2 schema reconfigured');
|
|
143
|
+
});
|
|
144
|
+
|
|
121
145
|
test('keeps Protocol V1 raw notification chunks compatible', async () => {
|
|
122
146
|
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
123
147
|
message: 'ok',
|
|
@@ -138,6 +162,23 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
138
162
|
});
|
|
139
163
|
});
|
|
140
164
|
|
|
165
|
+
test('uses the Protocol V2 BLE writer with the lowlevel compatibility packet size', async () => {
|
|
166
|
+
const plugin = createPlugin({ devices: [], responses: [] });
|
|
167
|
+
const lowlevel = configureTransport(plugin);
|
|
168
|
+
const context = {
|
|
169
|
+
messageName: 'Ping',
|
|
170
|
+
timeoutMs: 1000,
|
|
171
|
+
highVolume: false,
|
|
172
|
+
generation: 1,
|
|
173
|
+
signal: new AbortController().signal,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
await lowlevel.writeProtocolV2Frame('pro2-id', new Uint8Array(130), context, jest.fn());
|
|
177
|
+
|
|
178
|
+
expect(plugin.send).toHaveBeenCalledTimes(3);
|
|
179
|
+
expect(plugin.send.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([64, 64, 2]);
|
|
180
|
+
});
|
|
181
|
+
|
|
141
182
|
test('rejects calls before protocol detection', async () => {
|
|
142
183
|
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
143
184
|
message: 'ok',
|
|
@@ -162,13 +203,13 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
162
203
|
);
|
|
163
204
|
const callResponse = ProtocolV2.encodeFrame(
|
|
164
205
|
schemas,
|
|
165
|
-
'
|
|
206
|
+
'ProtocolInfo',
|
|
166
207
|
{
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
208
|
+
version: 1,
|
|
209
|
+
supported_messages: [60200, 60201, 60206, 60207],
|
|
210
|
+
protobuf_definition: 'onekey-protocol-v2',
|
|
170
211
|
},
|
|
171
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
212
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: 2 }
|
|
172
213
|
);
|
|
173
214
|
const plugin = createPlugin({
|
|
174
215
|
devices: [{ id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
@@ -183,15 +224,19 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
183
224
|
uuid: 'pro2-id',
|
|
184
225
|
protocolType: 'V2',
|
|
185
226
|
});
|
|
186
|
-
await expect(lowlevel.call('pro2-id', '
|
|
187
|
-
type: '
|
|
227
|
+
await expect(lowlevel.call('pro2-id', 'ProtocolInfoRequest', {})).resolves.toEqual({
|
|
228
|
+
type: 'ProtocolInfo',
|
|
188
229
|
message: {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
230
|
+
version: 1,
|
|
231
|
+
supported_messages: [60200, 60201, 60206, 60207],
|
|
232
|
+
protobuf_definition: 'onekey-protocol-v2',
|
|
192
233
|
},
|
|
193
234
|
});
|
|
194
235
|
expect(plugin.send).toHaveBeenCalled();
|
|
236
|
+
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
237
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
238
|
+
);
|
|
239
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
195
240
|
});
|
|
196
241
|
|
|
197
242
|
test('falls back to Protocol V2 probe for unnamed Protocol V2 devices', async () => {
|
|
@@ -214,6 +259,97 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
214
259
|
expect(lowlevel.getProtocolType('unknown-pro2-id')).toBe('V2');
|
|
215
260
|
});
|
|
216
261
|
|
|
262
|
+
test('retains the Protocol V2 hint and sequence cursor across release and reacquire', async () => {
|
|
263
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
264
|
+
schemas,
|
|
265
|
+
'Success',
|
|
266
|
+
{ message: 'ok' },
|
|
267
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
268
|
+
);
|
|
269
|
+
const plugin = createPlugin({
|
|
270
|
+
devices: [{ id: 'reconnect-pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
271
|
+
responses: [bytesToHex(probeResponse), bytesToHex(probeResponse)],
|
|
272
|
+
});
|
|
273
|
+
const lowlevel = configureTransport(plugin);
|
|
274
|
+
|
|
275
|
+
await lowlevel.enumerate();
|
|
276
|
+
await expect(lowlevel.acquire({ uuid: 'reconnect-pro2-id' })).resolves.toEqual({
|
|
277
|
+
uuid: 'reconnect-pro2-id',
|
|
278
|
+
protocolType: 'V2',
|
|
279
|
+
});
|
|
280
|
+
await lowlevel.release('reconnect-pro2-id');
|
|
281
|
+
await expect(lowlevel.acquire({ uuid: 'reconnect-pro2-id' })).resolves.toEqual({
|
|
282
|
+
uuid: 'reconnect-pro2-id',
|
|
283
|
+
protocolType: 'V2',
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
287
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
288
|
+
);
|
|
289
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('reuses the active generation when Core acquires the same BLE connection again', async () => {
|
|
293
|
+
const responses = [1, 2, 3, 4].map(seq =>
|
|
294
|
+
bytesToHex(
|
|
295
|
+
ProtocolV2.encodeFrame(
|
|
296
|
+
schemas,
|
|
297
|
+
'Success',
|
|
298
|
+
{ message: 'ok' },
|
|
299
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq }
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
const plugin = createPlugin({
|
|
304
|
+
devices: [{ id: 'repeated-acquire-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
305
|
+
responses,
|
|
306
|
+
});
|
|
307
|
+
const lowlevel = configureTransport(plugin);
|
|
308
|
+
|
|
309
|
+
await expect(
|
|
310
|
+
lowlevel.acquire({ uuid: 'repeated-acquire-id', expectedProtocol: 'V2' })
|
|
311
|
+
).resolves.toEqual({
|
|
312
|
+
uuid: 'repeated-acquire-id',
|
|
313
|
+
protocolType: 'V2',
|
|
314
|
+
});
|
|
315
|
+
await lowlevel.call('repeated-acquire-id', 'Ping', { message: 'first-acquire' });
|
|
316
|
+
await expect(
|
|
317
|
+
lowlevel.acquire({ uuid: 'repeated-acquire-id', expectedProtocol: 'V2' })
|
|
318
|
+
).resolves.toEqual({
|
|
319
|
+
uuid: 'repeated-acquire-id',
|
|
320
|
+
protocolType: 'V2',
|
|
321
|
+
});
|
|
322
|
+
await lowlevel.call('repeated-acquire-id', 'Ping', { message: 'second-acquire' });
|
|
323
|
+
|
|
324
|
+
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
325
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
326
|
+
);
|
|
327
|
+
expect(sentSeqs).toEqual([1, 2, 3, 4]);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test('actively probes explicit Protocol V2 during bootloader reconnect', async () => {
|
|
331
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
332
|
+
schemas,
|
|
333
|
+
'Success',
|
|
334
|
+
{ message: 'ok' },
|
|
335
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
336
|
+
);
|
|
337
|
+
const plugin = createPlugin({
|
|
338
|
+
devices: [{ id: 'bootloader-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
339
|
+
responses: [bytesToHex(probeResponse)],
|
|
340
|
+
});
|
|
341
|
+
const lowlevel = configureTransport(plugin);
|
|
342
|
+
|
|
343
|
+
await expect(
|
|
344
|
+
lowlevel.acquire({ uuid: 'bootloader-v2-id', expectedProtocol: 'V2' })
|
|
345
|
+
).resolves.toEqual({
|
|
346
|
+
uuid: 'bootloader-v2-id',
|
|
347
|
+
protocolType: 'V2',
|
|
348
|
+
});
|
|
349
|
+
expect(plugin.send).toHaveBeenCalledTimes(1);
|
|
350
|
+
expect(plugin.receive).toHaveBeenCalledTimes(1);
|
|
351
|
+
});
|
|
352
|
+
|
|
217
353
|
test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
|
|
218
354
|
const probeResponse = ProtocolV2.encodeFrame(
|
|
219
355
|
schemas,
|
|
@@ -252,6 +388,57 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
252
388
|
expect(plugin.connect).toHaveBeenCalledTimes(2);
|
|
253
389
|
});
|
|
254
390
|
|
|
391
|
+
test('disconnects a tainted Protocol V2 link after a response timeout', async () => {
|
|
392
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
393
|
+
schemas,
|
|
394
|
+
'Success',
|
|
395
|
+
{ message: 'ok' },
|
|
396
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
397
|
+
);
|
|
398
|
+
const plugin = createPlugin({
|
|
399
|
+
devices: [{ id: 'timeout-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
400
|
+
responses: [bytesToHex(probeResponse)],
|
|
401
|
+
});
|
|
402
|
+
let receiveCount = 0;
|
|
403
|
+
plugin.receive.mockImplementation(() => {
|
|
404
|
+
receiveCount += 1;
|
|
405
|
+
return receiveCount === 1
|
|
406
|
+
? Promise.resolve(bytesToHex(probeResponse))
|
|
407
|
+
: new Promise(() => {});
|
|
408
|
+
});
|
|
409
|
+
const lowlevel = configureTransport(plugin);
|
|
410
|
+
|
|
411
|
+
await lowlevel.acquire({ uuid: 'timeout-v2-id', expectedProtocol: 'V2' });
|
|
412
|
+
plugin.disconnect.mockClear();
|
|
413
|
+
await expect(
|
|
414
|
+
lowlevel.call('timeout-v2-id', 'Ping', { message: 'timeout' }, { timeoutMs: 10 })
|
|
415
|
+
).rejects.toThrow('Lowlevel response timeout after 10ms for Ping');
|
|
416
|
+
|
|
417
|
+
expect(plugin.disconnect).toHaveBeenCalledWith('timeout-v2-id');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test('preserves an undefined business timeout outside explicit probes', async () => {
|
|
421
|
+
const plugin = createPlugin({
|
|
422
|
+
devices: [{ id: 'v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
423
|
+
responses: [],
|
|
424
|
+
});
|
|
425
|
+
const lowlevel = configureTransport(plugin);
|
|
426
|
+
lowlevel.deviceProtocol.set('v2-id', 'V2');
|
|
427
|
+
const linkCall = jest
|
|
428
|
+
.spyOn(lowlevel.protocolV2Links, 'call')
|
|
429
|
+
.mockResolvedValue({ type: 'Success', message: {} });
|
|
430
|
+
|
|
431
|
+
await lowlevel.call('v2-id', 'Ping', { message: 'no-business-timeout' });
|
|
432
|
+
|
|
433
|
+
expect(linkCall).toHaveBeenCalledWith(
|
|
434
|
+
'v2-id',
|
|
435
|
+
expect.any(Function),
|
|
436
|
+
'Ping',
|
|
437
|
+
{ message: 'no-business-timeout' },
|
|
438
|
+
undefined
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
255
442
|
test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
|
|
256
443
|
const plugin = createPlugin({
|
|
257
444
|
devices: [{ id: 'v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,15 @@ type LowLevelAcquireInput = {
|
|
|
7
7
|
expectedProtocol?: ProtocolType;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
declare function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, lastLoggedAt, }: {
|
|
11
|
+
percent: number;
|
|
12
|
+
lastLoggedPercent: number;
|
|
13
|
+
now: number;
|
|
14
|
+
lastLoggedAt: number;
|
|
15
|
+
}): boolean;
|
|
16
|
+
declare function getProtocolV1SendOptions(name: string): {
|
|
17
|
+
withoutResponse: boolean;
|
|
18
|
+
} | undefined;
|
|
10
19
|
declare class LowlevelTransport {
|
|
11
20
|
_messages: ReturnType<typeof transport__default.parseConfigure> | undefined;
|
|
12
21
|
_messagesV2: ReturnType<typeof transport__default.parseConfigure> | undefined;
|
|
@@ -17,6 +26,10 @@ declare class LowlevelTransport {
|
|
|
17
26
|
private deviceProtocol;
|
|
18
27
|
private deviceProtocolHints;
|
|
19
28
|
private protocolV2Assemblers;
|
|
29
|
+
private protocolV2Generations;
|
|
30
|
+
private connectedDevices;
|
|
31
|
+
private protocolV2Links;
|
|
32
|
+
private protocolV2SchemaConfiguration;
|
|
20
33
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
21
34
|
init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin): void;
|
|
22
35
|
configure(signedData: any): void;
|
|
@@ -43,7 +56,9 @@ declare class LowlevelTransport {
|
|
|
43
56
|
private readProtocolV2Frame;
|
|
44
57
|
private writeProtocolV2Frame;
|
|
45
58
|
private callProtocolV2;
|
|
59
|
+
private createProtocolV2Adapter;
|
|
60
|
+
private advanceProtocolV2Generation;
|
|
46
61
|
cancel(): void;
|
|
47
62
|
}
|
|
48
63
|
|
|
49
|
-
export { LowlevelTransport as default };
|
|
64
|
+
export { LowlevelTransport as default, getProtocolV1SendOptions, shouldLogFirmwareUploadProgress };
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,SAYN,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAE7B,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAUpD,wBAAgB,+BAA+B,CAAC,EAC9C,OAAO,EACP,iBAAiB,EACjB,GAAG,EACH,YAAY,GACb,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;CACtB,WAMA;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM;;cAEpD;AAcD,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,WAAW,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAErE,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,MAAM,EAAE,6BAA6B,CAAuC;IAE5E,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,oBAAoB,CAAoD;IAEhF,OAAO,CAAC,qBAAqB,CAAkC;IAE/D,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,eAAe,CA6BpB;IAEH,OAAO,CAAC,6BAA6B,CAAqB;IAE1D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIvD,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,6BAA6B;IAO9E,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAiBnC,MAAM;IAIA,SAAS;IAWT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IAgCnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAgBpB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YAsBlB,cAAc;IA6E5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;YAsDd,yBAAyB;YAiCzB,eAAe;YAgBf,eAAe;YA6Bf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAgBpB,cAAc;IAwB5B,OAAO,CAAC,uBAAuB;IAgC/B,OAAO,CAAC,2BAA2B;IAMnC,MAAM;CAGP"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
3
5
|
var hdShared = require('@onekeyfe/hd-shared');
|
|
4
6
|
var transport = require('@onekeyfe/hd-transport');
|
|
5
7
|
|
|
@@ -40,8 +42,17 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
40
42
|
const { check, ProtocolV1, parseConfigure } = transport__default["default"];
|
|
41
43
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
42
44
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
43
|
-
const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30000;
|
|
44
45
|
const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
|
|
46
|
+
const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
|
|
47
|
+
const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10000;
|
|
48
|
+
function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, lastLoggedAt, }) {
|
|
49
|
+
return (percent === 100 ||
|
|
50
|
+
percent - lastLoggedPercent >= FIRMWARE_UPLOAD_LOG_PERCENT_STEP ||
|
|
51
|
+
now - lastLoggedAt >= FIRMWARE_UPLOAD_LOG_INTERVAL_MS);
|
|
52
|
+
}
|
|
53
|
+
function getProtocolV1SendOptions(name) {
|
|
54
|
+
return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
|
|
55
|
+
}
|
|
45
56
|
function inferProtocolHintFromDeviceName(name) {
|
|
46
57
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
47
58
|
}
|
|
@@ -58,6 +69,38 @@ class LowlevelTransport {
|
|
|
58
69
|
this.deviceProtocol = new Map();
|
|
59
70
|
this.deviceProtocolHints = new Map();
|
|
60
71
|
this.protocolV2Assemblers = new Map();
|
|
72
|
+
this.protocolV2Generations = new Map();
|
|
73
|
+
this.connectedDevices = new Set();
|
|
74
|
+
this.protocolV2Links = new transport.ProtocolV2LinkManager({
|
|
75
|
+
getSchemas: () => {
|
|
76
|
+
if (!this._messages || !this._messagesV2) {
|
|
77
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
protocolV1: this._messages,
|
|
81
|
+
protocolV2: this._messagesV2,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
classifyError: () => 'link-fatal',
|
|
85
|
+
onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
var _a, _b, _c;
|
|
87
|
+
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
88
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[LowlevelTransport] Protocol V2 link invalidated: ${uuid}`, reason);
|
|
89
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
90
|
+
this.deviceProtocol.delete(uuid);
|
|
91
|
+
this.advanceProtocolV2Generation(uuid);
|
|
92
|
+
try {
|
|
93
|
+
yield this.plugin.disconnect(uuid);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] disconnect tainted Protocol V2 link failed: ${uuid}`, error);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
this.connectedDevices.delete(uuid);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
61
104
|
}
|
|
62
105
|
getProtocolType(path) {
|
|
63
106
|
return this.deviceProtocol.get(path);
|
|
@@ -74,7 +117,18 @@ class LowlevelTransport {
|
|
|
74
117
|
this._messages = messages;
|
|
75
118
|
}
|
|
76
119
|
configureProtocolV2(signedData) {
|
|
120
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
121
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
77
125
|
this._messagesV2 = parseConfigure(signedData);
|
|
126
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
127
|
+
if (isReconfiguration) {
|
|
128
|
+
this.protocolV2Links
|
|
129
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
130
|
+
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('Protocol V2 schema link cleanup failed:', error); });
|
|
131
|
+
}
|
|
78
132
|
}
|
|
79
133
|
listen() {
|
|
80
134
|
}
|
|
@@ -93,14 +147,20 @@ class LowlevelTransport {
|
|
|
93
147
|
acquire(input) {
|
|
94
148
|
var _a;
|
|
95
149
|
return __awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
const alreadyConnected = this.connectedDevices.has(input.uuid);
|
|
96
151
|
try {
|
|
97
152
|
yield this.plugin.connect(input.uuid);
|
|
153
|
+
if (!alreadyConnected) {
|
|
154
|
+
this.connectedDevices.add(input.uuid);
|
|
155
|
+
this.advanceProtocolV2Generation(input.uuid);
|
|
156
|
+
}
|
|
98
157
|
}
|
|
99
158
|
catch (error) {
|
|
159
|
+
this.connectedDevices.delete(input.uuid);
|
|
100
160
|
this.Log.debug('lowlelvel transport connect error: ', error);
|
|
101
161
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_a = error.message) !== null && _a !== void 0 ? _a : error);
|
|
102
162
|
}
|
|
103
|
-
this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler());
|
|
163
|
+
this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
104
164
|
const protocolHint = input.expectedProtocol
|
|
105
165
|
? undefined
|
|
106
166
|
: this.deviceProtocolHints.get(input.uuid);
|
|
@@ -111,9 +171,10 @@ class LowlevelTransport {
|
|
|
111
171
|
release(uuid) {
|
|
112
172
|
return __awaiter(this, void 0, void 0, function* () {
|
|
113
173
|
try {
|
|
174
|
+
yield this.protocolV2Links.invalidateLink(uuid, 'Lowlevel transport released');
|
|
114
175
|
yield this.plugin.disconnect(uuid);
|
|
176
|
+
this.connectedDevices.delete(uuid);
|
|
115
177
|
this.deviceProtocol.delete(uuid);
|
|
116
|
-
this.deviceProtocolHints.delete(uuid);
|
|
117
178
|
this.protocolV2Assemblers.delete(uuid);
|
|
118
179
|
return true;
|
|
119
180
|
}
|
|
@@ -132,12 +193,7 @@ class LowlevelTransport {
|
|
|
132
193
|
if (!protocol) {
|
|
133
194
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
134
195
|
}
|
|
135
|
-
|
|
136
|
-
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' protocol: ', protocol);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
|
|
140
|
-
}
|
|
196
|
+
this.Log.debug('transport call', { name, protocol });
|
|
141
197
|
if (protocol === 'V2') {
|
|
142
198
|
return this.callProtocolV2(uuid, name, data, options);
|
|
143
199
|
}
|
|
@@ -145,17 +201,42 @@ class LowlevelTransport {
|
|
|
145
201
|
});
|
|
146
202
|
}
|
|
147
203
|
callProtocolV1(uuid, name, data, options) {
|
|
204
|
+
var _a;
|
|
148
205
|
return __awaiter(this, void 0, void 0, function* () {
|
|
149
206
|
if (!this._messages) {
|
|
150
207
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
151
208
|
}
|
|
152
209
|
const messages = this._messages;
|
|
153
210
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
154
|
-
|
|
211
|
+
const isFirmwareUpload = name === 'FirmwareUpload';
|
|
212
|
+
const uploadStartedAt = Date.now();
|
|
213
|
+
const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
|
|
214
|
+
let sentBytes = 0;
|
|
215
|
+
let lastLoggedPercent = 0;
|
|
216
|
+
let lastLoggedAt = uploadStartedAt;
|
|
217
|
+
for (const [index, o] of buffers.entries()) {
|
|
155
218
|
const outData = o.toString('hex');
|
|
156
|
-
this.Log.debug('send hex strting: ', outData);
|
|
157
219
|
try {
|
|
158
|
-
yield this.plugin.send(uuid, outData);
|
|
220
|
+
yield this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
|
|
221
|
+
sentBytes += o.limit;
|
|
222
|
+
if (isFirmwareUpload) {
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
const percent = Math.floor(((index + 1) / buffers.length) * 100);
|
|
225
|
+
if (shouldLogFirmwareUploadProgress({
|
|
226
|
+
percent,
|
|
227
|
+
lastLoggedPercent,
|
|
228
|
+
now,
|
|
229
|
+
lastLoggedAt,
|
|
230
|
+
})) {
|
|
231
|
+
const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
|
|
232
|
+
const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
|
|
233
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
|
|
234
|
+
`(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
|
|
235
|
+
`${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`);
|
|
236
|
+
lastLoggedPercent = percent;
|
|
237
|
+
lastLoggedAt = now;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
159
240
|
}
|
|
160
241
|
catch (e) {
|
|
161
242
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
@@ -163,12 +244,20 @@ class LowlevelTransport {
|
|
|
163
244
|
}
|
|
164
245
|
}
|
|
165
246
|
try {
|
|
166
|
-
const response = yield this.readProtocolV1Message(options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
167
|
-
this.Log.debug('receive data: ', response);
|
|
247
|
+
const response = yield this.readProtocolV1Message(uuid, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
168
248
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
169
249
|
return check.call(jsonData);
|
|
170
250
|
}
|
|
171
251
|
catch (e) {
|
|
252
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
|
|
253
|
+
(options === null || options === void 0 ? void 0 : options.timeoutMs) !== PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
254
|
+
try {
|
|
255
|
+
yield this.resetConnectionAfterProbe(uuid, 'V1');
|
|
256
|
+
}
|
|
257
|
+
catch (resetError) {
|
|
258
|
+
this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
172
261
|
if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
173
262
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
174
263
|
}
|
|
@@ -242,8 +331,10 @@ class LowlevelTransport {
|
|
|
242
331
|
resetConnectionAfterProbe(uuid, protocol) {
|
|
243
332
|
var _a, _b, _c, _d;
|
|
244
333
|
return __awaiter(this, void 0, void 0, function* () {
|
|
334
|
+
yield this.protocolV2Links.invalidateLink(uuid, `Reset connection after Protocol ${protocol} probe`);
|
|
245
335
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
246
336
|
try {
|
|
337
|
+
this.connectedDevices.delete(uuid);
|
|
247
338
|
yield this.plugin.disconnect(uuid);
|
|
248
339
|
}
|
|
249
340
|
catch (error) {
|
|
@@ -251,6 +342,8 @@ class LowlevelTransport {
|
|
|
251
342
|
}
|
|
252
343
|
try {
|
|
253
344
|
yield this.plugin.connect(uuid);
|
|
345
|
+
this.connectedDevices.add(uuid);
|
|
346
|
+
this.advanceProtocolV2Generation(uuid);
|
|
254
347
|
}
|
|
255
348
|
catch (error) {
|
|
256
349
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
@@ -307,18 +400,18 @@ class LowlevelTransport {
|
|
|
307
400
|
}
|
|
308
401
|
});
|
|
309
402
|
}
|
|
310
|
-
receiveHex(timeoutMs, commandName) {
|
|
403
|
+
receiveHex(uuid, timeoutMs, commandName) {
|
|
311
404
|
return __awaiter(this, void 0, void 0, function* () {
|
|
312
|
-
const response = yield transport.withProtocolTimeout(this.plugin.receive(), timeoutMs, () => this.createProtocolTimeoutError(commandName, timeoutMs !== null && timeoutMs !== void 0 ? timeoutMs : 0));
|
|
405
|
+
const response = yield transport.withProtocolTimeout(this.plugin.receive(uuid), timeoutMs, () => this.createProtocolTimeoutError(commandName, timeoutMs !== null && timeoutMs !== void 0 ? timeoutMs : 0));
|
|
313
406
|
if (typeof response !== 'string') {
|
|
314
407
|
throw new Error('Returning data is not string');
|
|
315
408
|
}
|
|
316
409
|
return response;
|
|
317
410
|
});
|
|
318
411
|
}
|
|
319
|
-
readProtocolV1Message(timeoutMs) {
|
|
412
|
+
readProtocolV1Message(uuid, timeoutMs) {
|
|
320
413
|
return __awaiter(this, void 0, void 0, function* () {
|
|
321
|
-
const first = yield this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
414
|
+
const first = yield this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
322
415
|
const firstData = transport.hexToBytes(first);
|
|
323
416
|
if (!isProtocolV1TransportChunk(firstData)) {
|
|
324
417
|
return first;
|
|
@@ -327,17 +420,17 @@ class LowlevelTransport {
|
|
|
327
420
|
let buffer = firstData.slice(3);
|
|
328
421
|
const expectedLength = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE + payloadLength;
|
|
329
422
|
while (buffer.length < expectedLength) {
|
|
330
|
-
const next = yield this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
423
|
+
const next = yield this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
331
424
|
buffer = transport.concatUint8Arrays([buffer, transport.hexToBytes(next)]);
|
|
332
425
|
}
|
|
333
426
|
return transport.bytesToHex(buffer.slice(0, expectedLength));
|
|
334
427
|
});
|
|
335
428
|
}
|
|
336
|
-
readProtocolV2Frame(uuid, timeoutMs) {
|
|
429
|
+
readProtocolV2Frame(uuid, timeoutMs, commandName = 'ProtocolV2') {
|
|
337
430
|
return __awaiter(this, void 0, void 0, function* () {
|
|
338
431
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
339
432
|
if (!assembler) {
|
|
340
|
-
assembler = new transport.ProtocolV2FrameAssembler();
|
|
433
|
+
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
341
434
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
342
435
|
}
|
|
343
436
|
const queuedFrame = assembler.push(new Uint8Array(0));
|
|
@@ -345,7 +438,7 @@ class LowlevelTransport {
|
|
|
345
438
|
return queuedFrame;
|
|
346
439
|
let frame;
|
|
347
440
|
while (!frame) {
|
|
348
|
-
const response = yield this.receiveHex(timeoutMs,
|
|
441
|
+
const response = yield this.receiveHex(uuid, timeoutMs, commandName);
|
|
349
442
|
const chunk = transport.hexToBytes(response);
|
|
350
443
|
if (chunk.length > 0) {
|
|
351
444
|
frame = assembler.push(chunk);
|
|
@@ -354,47 +447,74 @@ class LowlevelTransport {
|
|
|
354
447
|
return frame;
|
|
355
448
|
});
|
|
356
449
|
}
|
|
357
|
-
writeProtocolV2Frame(uuid, frame) {
|
|
450
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
358
451
|
return __awaiter(this, void 0, void 0, function* () {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
452
|
+
yield transport.writeProtocolV2BleFrame({
|
|
453
|
+
frame,
|
|
454
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
455
|
+
assertActive: assertCurrentGeneration,
|
|
456
|
+
signal: context.signal,
|
|
457
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
458
|
+
writePacket: packet => this.plugin.send(uuid, transport.bytesToHex(packet)),
|
|
459
|
+
});
|
|
363
460
|
});
|
|
364
461
|
}
|
|
365
462
|
callProtocolV2(uuid, name, data, options) {
|
|
366
|
-
var _a, _b, _c;
|
|
367
463
|
return __awaiter(this, void 0, void 0, function* () {
|
|
368
464
|
if (!this._messages || !this._messagesV2) {
|
|
369
465
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
370
466
|
}
|
|
371
|
-
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
372
|
-
(_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
373
|
-
const session = new transport.ProtocolV2Session({
|
|
374
|
-
schemas: {
|
|
375
|
-
protocolV1: this._messages,
|
|
376
|
-
protocolV2: this._messagesV2,
|
|
377
|
-
},
|
|
378
|
-
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
379
|
-
writeFrame: (frame) => this.writeProtocolV2Frame(uuid, frame),
|
|
380
|
-
readFrame: () => this.readProtocolV2Frame(uuid, timeoutMs),
|
|
381
|
-
logger: this.Log,
|
|
382
|
-
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
383
|
-
createTimeoutError: (_messageName, timeout) => this.createProtocolTimeoutError(name, timeout),
|
|
384
|
-
});
|
|
385
467
|
try {
|
|
386
|
-
return yield
|
|
468
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
|
|
387
469
|
}
|
|
388
470
|
catch (e) {
|
|
389
|
-
(_c = this.protocolV2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
|
|
390
471
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
391
472
|
throw e;
|
|
392
473
|
}
|
|
393
474
|
});
|
|
394
475
|
}
|
|
476
|
+
createProtocolV2Adapter(uuid) {
|
|
477
|
+
var _a;
|
|
478
|
+
const generation = (_a = this.protocolV2Generations.get(uuid)) !== null && _a !== void 0 ? _a : 0;
|
|
479
|
+
const assertCurrentGeneration = () => {
|
|
480
|
+
if (this.protocolV2Generations.get(uuid) !== generation) {
|
|
481
|
+
throw new Error(`Protocol V2 connection generation changed for ${uuid}`);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
return {
|
|
485
|
+
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
486
|
+
maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
487
|
+
generation,
|
|
488
|
+
prepareCall: () => {
|
|
489
|
+
var _a;
|
|
490
|
+
assertCurrentGeneration();
|
|
491
|
+
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
492
|
+
},
|
|
493
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
494
|
+
readFrame: (context) => {
|
|
495
|
+
assertCurrentGeneration();
|
|
496
|
+
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
497
|
+
},
|
|
498
|
+
reset: () => {
|
|
499
|
+
var _a;
|
|
500
|
+
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
501
|
+
},
|
|
502
|
+
logger: this.Log,
|
|
503
|
+
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
504
|
+
createTimeoutError: (messageName, timeout) => this.createProtocolTimeoutError(messageName, timeout),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
advanceProtocolV2Generation(uuid) {
|
|
508
|
+
var _a;
|
|
509
|
+
const nextGeneration = ((_a = this.protocolV2Generations.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
510
|
+
this.protocolV2Generations.set(uuid, nextGeneration);
|
|
511
|
+
return nextGeneration;
|
|
512
|
+
}
|
|
395
513
|
cancel() {
|
|
396
514
|
this.Log.debug('lowlevel-transport', 'cancel');
|
|
397
515
|
}
|
|
398
516
|
}
|
|
399
517
|
|
|
400
|
-
|
|
518
|
+
exports["default"] = LowlevelTransport;
|
|
519
|
+
exports.getProtocolV1SendOptions = getProtocolV1SendOptions;
|
|
520
|
+
exports.shouldLogFirmwareUploadProgress = shouldLogFirmwareUploadProgress;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-lowlevel",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.31",
|
|
4
4
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"lint:fix": "eslint . --fix"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
24
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
23
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.31",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.31"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "01354bd66a7ef042a47f45c9ba80c49ba5b241de"
|
|
27
27
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import transport, {
|
|
3
|
-
LogBlockCommand,
|
|
4
3
|
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
4
|
+
PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
5
5
|
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
6
6
|
ProtocolV2FrameAssembler,
|
|
7
|
-
|
|
7
|
+
ProtocolV2LinkManager,
|
|
8
8
|
bytesToHex,
|
|
9
9
|
concatUint8Arrays,
|
|
10
10
|
hexToBytes,
|
|
11
11
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
12
12
|
withProtocolTimeout,
|
|
13
|
+
writeProtocolV2BleFrame,
|
|
13
14
|
} from '@onekeyfe/hd-transport';
|
|
14
15
|
|
|
15
16
|
import type EventEmitter from 'events';
|
|
16
17
|
import type {
|
|
17
18
|
LowLevelDevice,
|
|
18
19
|
LowlevelTransportSharedPlugin,
|
|
20
|
+
ProtocolV2CallContext,
|
|
19
21
|
ProtocolType,
|
|
20
22
|
TransportCallOptions,
|
|
21
23
|
} from '@onekeyfe/hd-transport';
|
|
@@ -25,8 +27,31 @@ const { check, ProtocolV1, parseConfigure } = transport;
|
|
|
25
27
|
|
|
26
28
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
27
29
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
28
|
-
const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30_000;
|
|
29
30
|
const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
|
|
31
|
+
const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
|
|
32
|
+
const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10_000;
|
|
33
|
+
|
|
34
|
+
export function shouldLogFirmwareUploadProgress({
|
|
35
|
+
percent,
|
|
36
|
+
lastLoggedPercent,
|
|
37
|
+
now,
|
|
38
|
+
lastLoggedAt,
|
|
39
|
+
}: {
|
|
40
|
+
percent: number;
|
|
41
|
+
lastLoggedPercent: number;
|
|
42
|
+
now: number;
|
|
43
|
+
lastLoggedAt: number;
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
percent === 100 ||
|
|
47
|
+
percent - lastLoggedPercent >= FIRMWARE_UPLOAD_LOG_PERCENT_STEP ||
|
|
48
|
+
now - lastLoggedAt >= FIRMWARE_UPLOAD_LOG_INTERVAL_MS
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getProtocolV1SendOptions(name: string) {
|
|
53
|
+
return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
|
|
54
|
+
}
|
|
30
55
|
|
|
31
56
|
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
32
57
|
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
@@ -59,6 +84,43 @@ export default class LowlevelTransport {
|
|
|
59
84
|
|
|
60
85
|
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
61
86
|
|
|
87
|
+
private protocolV2Generations: Map<string, number> = new Map();
|
|
88
|
+
|
|
89
|
+
private connectedDevices: Set<string> = new Set();
|
|
90
|
+
|
|
91
|
+
private protocolV2Links = new ProtocolV2LinkManager<string>({
|
|
92
|
+
getSchemas: () => {
|
|
93
|
+
if (!this._messages || !this._messagesV2) {
|
|
94
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
protocolV1: this._messages,
|
|
98
|
+
protocolV2: this._messagesV2,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
classifyError: () => 'link-fatal',
|
|
102
|
+
onLinkInvalidated: async (uuid, reason) => {
|
|
103
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
104
|
+
this.Log?.debug(`[LowlevelTransport] Protocol V2 link invalidated: ${uuid}`, reason);
|
|
105
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
106
|
+
this.deviceProtocol.delete(uuid);
|
|
107
|
+
this.advanceProtocolV2Generation(uuid);
|
|
108
|
+
try {
|
|
109
|
+
await this.plugin.disconnect(uuid);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
this.Log?.debug(
|
|
112
|
+
`[LowlevelTransport] disconnect tainted Protocol V2 link failed: ${uuid}`,
|
|
113
|
+
error
|
|
114
|
+
);
|
|
115
|
+
} finally {
|
|
116
|
+
this.connectedDevices.delete(uuid);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
123
|
+
|
|
62
124
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
63
125
|
return this.deviceProtocol.get(path);
|
|
64
126
|
}
|
|
@@ -77,7 +139,20 @@ export default class LowlevelTransport {
|
|
|
77
139
|
}
|
|
78
140
|
|
|
79
141
|
configureProtocolV2(signedData: any) {
|
|
142
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
143
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
80
148
|
this._messagesV2 = parseConfigure(signedData);
|
|
149
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
150
|
+
|
|
151
|
+
if (isReconfiguration) {
|
|
152
|
+
this.protocolV2Links
|
|
153
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
154
|
+
.catch(error => this.Log?.debug('Protocol V2 schema link cleanup failed:', error));
|
|
155
|
+
}
|
|
81
156
|
}
|
|
82
157
|
|
|
83
158
|
listen() {
|
|
@@ -96,9 +171,15 @@ export default class LowlevelTransport {
|
|
|
96
171
|
}
|
|
97
172
|
|
|
98
173
|
async acquire(input: LowLevelAcquireInput) {
|
|
174
|
+
const alreadyConnected = this.connectedDevices.has(input.uuid);
|
|
99
175
|
try {
|
|
100
176
|
await this.plugin.connect(input.uuid);
|
|
177
|
+
if (!alreadyConnected) {
|
|
178
|
+
this.connectedDevices.add(input.uuid);
|
|
179
|
+
this.advanceProtocolV2Generation(input.uuid);
|
|
180
|
+
}
|
|
101
181
|
} catch (error) {
|
|
182
|
+
this.connectedDevices.delete(input.uuid);
|
|
102
183
|
this.Log.debug('lowlelvel transport connect error: ', error);
|
|
103
184
|
throw ERRORS.TypedError(
|
|
104
185
|
HardwareErrorCode.LowlevelTrasnportConnectError,
|
|
@@ -106,7 +187,10 @@ export default class LowlevelTransport {
|
|
|
106
187
|
);
|
|
107
188
|
}
|
|
108
189
|
|
|
109
|
-
this.protocolV2Assemblers.set(
|
|
190
|
+
this.protocolV2Assemblers.set(
|
|
191
|
+
input.uuid,
|
|
192
|
+
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
193
|
+
);
|
|
110
194
|
const protocolHint = input.expectedProtocol
|
|
111
195
|
? undefined
|
|
112
196
|
: this.deviceProtocolHints.get(input.uuid);
|
|
@@ -120,9 +204,12 @@ export default class LowlevelTransport {
|
|
|
120
204
|
|
|
121
205
|
async release(uuid: string) {
|
|
122
206
|
try {
|
|
207
|
+
await this.protocolV2Links.invalidateLink(uuid, 'Lowlevel transport released');
|
|
123
208
|
await this.plugin.disconnect(uuid);
|
|
209
|
+
this.connectedDevices.delete(uuid);
|
|
124
210
|
this.deviceProtocol.delete(uuid);
|
|
125
|
-
|
|
211
|
+
// A name-derived protocol hint survives disconnect and lets fast reconnect probe
|
|
212
|
+
// Protocol V2 first without sending a redundant V1 Initialize.
|
|
126
213
|
this.protocolV2Assemblers.delete(uuid);
|
|
127
214
|
return true;
|
|
128
215
|
} catch (error) {
|
|
@@ -148,20 +235,7 @@ export default class LowlevelTransport {
|
|
|
148
235
|
`Device protocol has not been detected for ${uuid}`
|
|
149
236
|
);
|
|
150
237
|
}
|
|
151
|
-
|
|
152
|
-
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' protocol: ', protocol);
|
|
153
|
-
} else {
|
|
154
|
-
this.Log.debug(
|
|
155
|
-
'lowlevel-transport',
|
|
156
|
-
'call-',
|
|
157
|
-
' name: ',
|
|
158
|
-
name,
|
|
159
|
-
' data: ',
|
|
160
|
-
data,
|
|
161
|
-
' protocol: ',
|
|
162
|
-
protocol
|
|
163
|
-
);
|
|
164
|
-
}
|
|
238
|
+
this.Log.debug('transport call', { name, protocol });
|
|
165
239
|
|
|
166
240
|
if (protocol === 'V2') {
|
|
167
241
|
return this.callProtocolV2(uuid, name, data, options);
|
|
@@ -182,12 +256,41 @@ export default class LowlevelTransport {
|
|
|
182
256
|
|
|
183
257
|
const messages = this._messages;
|
|
184
258
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
185
|
-
|
|
259
|
+
const isFirmwareUpload = name === 'FirmwareUpload';
|
|
260
|
+
const uploadStartedAt = Date.now();
|
|
261
|
+
const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
|
|
262
|
+
let sentBytes = 0;
|
|
263
|
+
let lastLoggedPercent = 0;
|
|
264
|
+
let lastLoggedAt = uploadStartedAt;
|
|
265
|
+
|
|
266
|
+
for (const [index, o] of buffers.entries()) {
|
|
186
267
|
const outData = o.toString('hex');
|
|
187
|
-
// Upload resources on low-end phones may OOM
|
|
188
|
-
this.Log.debug('send hex strting: ', outData);
|
|
189
268
|
try {
|
|
190
|
-
await this.plugin.send(uuid, outData);
|
|
269
|
+
await this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
|
|
270
|
+
sentBytes += o.limit;
|
|
271
|
+
|
|
272
|
+
if (isFirmwareUpload) {
|
|
273
|
+
const now = Date.now();
|
|
274
|
+
const percent = Math.floor(((index + 1) / buffers.length) * 100);
|
|
275
|
+
if (
|
|
276
|
+
shouldLogFirmwareUploadProgress({
|
|
277
|
+
percent,
|
|
278
|
+
lastLoggedPercent,
|
|
279
|
+
now,
|
|
280
|
+
lastLoggedAt,
|
|
281
|
+
})
|
|
282
|
+
) {
|
|
283
|
+
const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
|
|
284
|
+
const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
|
|
285
|
+
this.Log?.debug(
|
|
286
|
+
`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
|
|
287
|
+
`(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
|
|
288
|
+
`${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`
|
|
289
|
+
);
|
|
290
|
+
lastLoggedPercent = percent;
|
|
291
|
+
lastLoggedAt = now;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
191
294
|
} catch (e) {
|
|
192
295
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
193
296
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
|
|
@@ -195,11 +298,20 @@ export default class LowlevelTransport {
|
|
|
195
298
|
}
|
|
196
299
|
|
|
197
300
|
try {
|
|
198
|
-
const response = await this.readProtocolV1Message(options?.timeoutMs);
|
|
199
|
-
this.Log.debug('receive data: ', response);
|
|
301
|
+
const response = await this.readProtocolV1Message(uuid, options?.timeoutMs);
|
|
200
302
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
201
303
|
return check.call(jsonData);
|
|
202
304
|
} catch (e) {
|
|
305
|
+
if (
|
|
306
|
+
e?.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
307
|
+
options?.timeoutMs !== PROTOCOL_PROBE_TIMEOUT_MS
|
|
308
|
+
) {
|
|
309
|
+
try {
|
|
310
|
+
await this.resetConnectionAfterProbe(uuid, 'V1');
|
|
311
|
+
} catch (resetError) {
|
|
312
|
+
this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
203
315
|
if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
204
316
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
205
317
|
} else {
|
|
@@ -291,9 +403,14 @@ export default class LowlevelTransport {
|
|
|
291
403
|
}
|
|
292
404
|
|
|
293
405
|
private async resetConnectionAfterProbe(uuid: string, protocol: ProtocolType) {
|
|
406
|
+
await this.protocolV2Links.invalidateLink(
|
|
407
|
+
uuid,
|
|
408
|
+
`Reset connection after Protocol ${protocol} probe`
|
|
409
|
+
);
|
|
294
410
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
295
411
|
|
|
296
412
|
try {
|
|
413
|
+
this.connectedDevices.delete(uuid);
|
|
297
414
|
await this.plugin.disconnect(uuid);
|
|
298
415
|
} catch (error) {
|
|
299
416
|
this.Log?.debug(
|
|
@@ -304,6 +421,8 @@ export default class LowlevelTransport {
|
|
|
304
421
|
|
|
305
422
|
try {
|
|
306
423
|
await this.plugin.connect(uuid);
|
|
424
|
+
this.connectedDevices.add(uuid);
|
|
425
|
+
this.advanceProtocolV2Generation(uuid);
|
|
307
426
|
} catch (error) {
|
|
308
427
|
this.Log?.debug(
|
|
309
428
|
`[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`,
|
|
@@ -361,8 +480,8 @@ export default class LowlevelTransport {
|
|
|
361
480
|
}
|
|
362
481
|
}
|
|
363
482
|
|
|
364
|
-
private async receiveHex(timeoutMs: number | undefined, commandName: string) {
|
|
365
|
-
const response = await withProtocolTimeout(this.plugin.receive(), timeoutMs, () =>
|
|
483
|
+
private async receiveHex(uuid: string, timeoutMs: number | undefined, commandName: string) {
|
|
484
|
+
const response = await withProtocolTimeout(this.plugin.receive(uuid), timeoutMs, () =>
|
|
366
485
|
this.createProtocolTimeoutError(commandName, timeoutMs ?? 0)
|
|
367
486
|
);
|
|
368
487
|
if (typeof response !== 'string') {
|
|
@@ -371,8 +490,8 @@ export default class LowlevelTransport {
|
|
|
371
490
|
return response;
|
|
372
491
|
}
|
|
373
492
|
|
|
374
|
-
private async readProtocolV1Message(timeoutMs?: number) {
|
|
375
|
-
const first = await this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
493
|
+
private async readProtocolV1Message(uuid: string, timeoutMs?: number) {
|
|
494
|
+
const first = await this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
376
495
|
const firstData = hexToBytes(first);
|
|
377
496
|
if (!isProtocolV1TransportChunk(firstData)) {
|
|
378
497
|
return first;
|
|
@@ -383,17 +502,17 @@ export default class LowlevelTransport {
|
|
|
383
502
|
const expectedLength = PROTOCOL_V1_MESSAGE_HEADER_SIZE + payloadLength;
|
|
384
503
|
|
|
385
504
|
while (buffer.length < expectedLength) {
|
|
386
|
-
const next = await this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
505
|
+
const next = await this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
387
506
|
buffer = concatUint8Arrays([buffer, hexToBytes(next)]);
|
|
388
507
|
}
|
|
389
508
|
|
|
390
509
|
return bytesToHex(buffer.slice(0, expectedLength));
|
|
391
510
|
}
|
|
392
511
|
|
|
393
|
-
private async readProtocolV2Frame(uuid: string, timeoutMs?: number) {
|
|
512
|
+
private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
|
|
394
513
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
395
514
|
if (!assembler) {
|
|
396
|
-
assembler = new ProtocolV2FrameAssembler();
|
|
515
|
+
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
397
516
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
398
517
|
}
|
|
399
518
|
|
|
@@ -402,7 +521,7 @@ export default class LowlevelTransport {
|
|
|
402
521
|
|
|
403
522
|
let frame: Uint8Array | undefined;
|
|
404
523
|
while (!frame) {
|
|
405
|
-
const response = await this.receiveHex(timeoutMs,
|
|
524
|
+
const response = await this.receiveHex(uuid, timeoutMs, commandName);
|
|
406
525
|
const chunk = hexToBytes(response);
|
|
407
526
|
if (chunk.length > 0) {
|
|
408
527
|
frame = assembler.push(chunk);
|
|
@@ -411,11 +530,20 @@ export default class LowlevelTransport {
|
|
|
411
530
|
return frame;
|
|
412
531
|
}
|
|
413
532
|
|
|
414
|
-
private async writeProtocolV2Frame(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
533
|
+
private async writeProtocolV2Frame(
|
|
534
|
+
uuid: string,
|
|
535
|
+
frame: Uint8Array,
|
|
536
|
+
context: ProtocolV2CallContext,
|
|
537
|
+
assertCurrentGeneration: () => void
|
|
538
|
+
) {
|
|
539
|
+
await writeProtocolV2BleFrame({
|
|
540
|
+
frame,
|
|
541
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
542
|
+
assertActive: assertCurrentGeneration,
|
|
543
|
+
signal: context.signal,
|
|
544
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
545
|
+
writePacket: packet => this.plugin.send(uuid, bytesToHex(packet)),
|
|
546
|
+
});
|
|
419
547
|
}
|
|
420
548
|
|
|
421
549
|
private async callProtocolV2(
|
|
@@ -428,34 +556,58 @@ export default class LowlevelTransport {
|
|
|
428
556
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
429
557
|
}
|
|
430
558
|
|
|
431
|
-
const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
432
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
433
|
-
const session = new ProtocolV2Session({
|
|
434
|
-
schemas: {
|
|
435
|
-
protocolV1: this._messages,
|
|
436
|
-
protocolV2: this._messagesV2,
|
|
437
|
-
},
|
|
438
|
-
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
439
|
-
writeFrame: (frame: Uint8Array) => this.writeProtocolV2Frame(uuid, frame),
|
|
440
|
-
readFrame: () => this.readProtocolV2Frame(uuid, timeoutMs),
|
|
441
|
-
logger: this.Log,
|
|
442
|
-
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
443
|
-
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
444
|
-
this.createProtocolTimeoutError(name, timeout),
|
|
445
|
-
});
|
|
446
|
-
|
|
447
559
|
try {
|
|
448
|
-
return await
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
560
|
+
return await this.protocolV2Links.call(
|
|
561
|
+
uuid,
|
|
562
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
563
|
+
name,
|
|
564
|
+
data,
|
|
565
|
+
options
|
|
566
|
+
);
|
|
452
567
|
} catch (e) {
|
|
453
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
454
568
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
455
569
|
throw e;
|
|
456
570
|
}
|
|
457
571
|
}
|
|
458
572
|
|
|
573
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
574
|
+
const generation = this.protocolV2Generations.get(uuid) ?? 0;
|
|
575
|
+
const assertCurrentGeneration = () => {
|
|
576
|
+
if (this.protocolV2Generations.get(uuid) !== generation) {
|
|
577
|
+
throw new Error(`Protocol V2 connection generation changed for ${uuid}`);
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
583
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
584
|
+
generation,
|
|
585
|
+
prepareCall: () => {
|
|
586
|
+
assertCurrentGeneration();
|
|
587
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
588
|
+
},
|
|
589
|
+
writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
|
|
590
|
+
this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
591
|
+
readFrame: (context: { messageName: string; timeoutMs?: number }) => {
|
|
592
|
+
assertCurrentGeneration();
|
|
593
|
+
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
594
|
+
},
|
|
595
|
+
reset: () => {
|
|
596
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
597
|
+
},
|
|
598
|
+
logger: this.Log,
|
|
599
|
+
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
600
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
601
|
+
this.createProtocolTimeoutError(messageName, timeout),
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private advanceProtocolV2Generation(uuid: string) {
|
|
606
|
+
const nextGeneration = (this.protocolV2Generations.get(uuid) ?? 0) + 1;
|
|
607
|
+
this.protocolV2Generations.set(uuid, nextGeneration);
|
|
608
|
+
return nextGeneration;
|
|
609
|
+
}
|
|
610
|
+
|
|
459
611
|
cancel() {
|
|
460
612
|
this.Log.debug('lowlevel-transport', 'cancel');
|
|
461
613
|
}
|