@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.7 → 1.2.0-alpha.70
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 +207 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +179 -70
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +225 -84
- package/src/types.ts +1 -0
|
@@ -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
|
+
});
|
|
@@ -122,6 +122,49 @@ const splitFrame = (frame, index) => [
|
|
|
122
122
|
];
|
|
123
123
|
|
|
124
124
|
describe('LowlevelTransport protocol framing', () => {
|
|
125
|
+
test('falls back to Protocol V2 when a cached V1 hint is stale', async () => {
|
|
126
|
+
const plugin = createPlugin({ devices: [], responses: [] });
|
|
127
|
+
const lowlevel = configureTransport(plugin);
|
|
128
|
+
const events = [];
|
|
129
|
+
lowlevel.probeProtocolV1 = jest.fn().mockImplementation(() => {
|
|
130
|
+
events.push('probe-v1');
|
|
131
|
+
return Promise.resolve(false);
|
|
132
|
+
});
|
|
133
|
+
lowlevel.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
|
|
134
|
+
events.push('reset');
|
|
135
|
+
return Promise.resolve();
|
|
136
|
+
});
|
|
137
|
+
lowlevel.probeProtocolV2 = jest.fn().mockImplementation(() => {
|
|
138
|
+
events.push('probe-v2');
|
|
139
|
+
return Promise.resolve(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await expect(lowlevel.detectProtocol('pro-lowlevel', undefined, 'V1')).resolves.toBe('V2');
|
|
143
|
+
|
|
144
|
+
expect(events).toEqual(['probe-v1', 'reset', 'probe-v2']);
|
|
145
|
+
expect(lowlevel.getProtocolType('pro-lowlevel')).toBe('V2');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('keeps active links when the Protocol V2 schema is configured repeatedly', () => {
|
|
149
|
+
const lowlevel = new LowlevelTransport();
|
|
150
|
+
const invalidateAllLinks = jest.fn().mockResolvedValue(undefined);
|
|
151
|
+
lowlevel.protocolV2Links.invalidateAllLinks = invalidateAllLinks;
|
|
152
|
+
|
|
153
|
+
lowlevel.configureProtocolV2(protocolV2Schema);
|
|
154
|
+
lowlevel.configureProtocolV2(protocolV2Schema);
|
|
155
|
+
|
|
156
|
+
expect(invalidateAllLinks).not.toHaveBeenCalled();
|
|
157
|
+
|
|
158
|
+
lowlevel.configureProtocolV2({
|
|
159
|
+
nested: {
|
|
160
|
+
...protocolV2Schema.nested,
|
|
161
|
+
ExtraMessage: { fields: {} },
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(invalidateAllLinks).toHaveBeenCalledWith('Protocol V2 schema reconfigured');
|
|
166
|
+
});
|
|
167
|
+
|
|
125
168
|
test('keeps Protocol V1 raw notification chunks compatible', async () => {
|
|
126
169
|
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
127
170
|
message: 'ok',
|
|
@@ -142,6 +185,23 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
142
185
|
});
|
|
143
186
|
});
|
|
144
187
|
|
|
188
|
+
test('uses the Protocol V2 BLE writer with the lowlevel compatibility packet size', async () => {
|
|
189
|
+
const plugin = createPlugin({ devices: [], responses: [] });
|
|
190
|
+
const lowlevel = configureTransport(plugin);
|
|
191
|
+
const context = {
|
|
192
|
+
messageName: 'Ping',
|
|
193
|
+
timeoutMs: 1000,
|
|
194
|
+
highVolume: false,
|
|
195
|
+
generation: 1,
|
|
196
|
+
signal: new AbortController().signal,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
await lowlevel.writeProtocolV2Frame('pro2-id', new Uint8Array(130), context, jest.fn());
|
|
200
|
+
|
|
201
|
+
expect(plugin.send).toHaveBeenCalledTimes(3);
|
|
202
|
+
expect(plugin.send.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([64, 64, 2]);
|
|
203
|
+
});
|
|
204
|
+
|
|
145
205
|
test('rejects calls before protocol detection', async () => {
|
|
146
206
|
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
147
207
|
message: 'ok',
|
|
@@ -172,7 +232,7 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
172
232
|
supported_messages: [60200, 60201, 60206, 60207],
|
|
173
233
|
protobuf_definition: 'onekey-protocol-v2',
|
|
174
234
|
},
|
|
175
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
235
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: 2 }
|
|
176
236
|
);
|
|
177
237
|
const plugin = createPlugin({
|
|
178
238
|
devices: [{ id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
@@ -196,6 +256,10 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
196
256
|
},
|
|
197
257
|
});
|
|
198
258
|
expect(plugin.send).toHaveBeenCalled();
|
|
259
|
+
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
260
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
261
|
+
);
|
|
262
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
199
263
|
});
|
|
200
264
|
|
|
201
265
|
test('falls back to Protocol V2 probe for unnamed Protocol V2 devices', async () => {
|
|
@@ -218,6 +282,97 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
218
282
|
expect(lowlevel.getProtocolType('unknown-pro2-id')).toBe('V2');
|
|
219
283
|
});
|
|
220
284
|
|
|
285
|
+
test('retains the Protocol V2 hint and sequence cursor across release and reacquire', async () => {
|
|
286
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
287
|
+
schemas,
|
|
288
|
+
'Success',
|
|
289
|
+
{ message: 'ok' },
|
|
290
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
291
|
+
);
|
|
292
|
+
const plugin = createPlugin({
|
|
293
|
+
devices: [{ id: 'reconnect-pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
294
|
+
responses: [bytesToHex(probeResponse), bytesToHex(probeResponse)],
|
|
295
|
+
});
|
|
296
|
+
const lowlevel = configureTransport(plugin);
|
|
297
|
+
|
|
298
|
+
await lowlevel.enumerate();
|
|
299
|
+
await expect(lowlevel.acquire({ uuid: 'reconnect-pro2-id' })).resolves.toEqual({
|
|
300
|
+
uuid: 'reconnect-pro2-id',
|
|
301
|
+
protocolType: 'V2',
|
|
302
|
+
});
|
|
303
|
+
await lowlevel.release('reconnect-pro2-id');
|
|
304
|
+
await expect(lowlevel.acquire({ uuid: 'reconnect-pro2-id' })).resolves.toEqual({
|
|
305
|
+
uuid: 'reconnect-pro2-id',
|
|
306
|
+
protocolType: 'V2',
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
310
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
311
|
+
);
|
|
312
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('reuses the active generation when Core acquires the same BLE connection again', async () => {
|
|
316
|
+
const responses = [1, 2, 3, 4].map(seq =>
|
|
317
|
+
bytesToHex(
|
|
318
|
+
ProtocolV2.encodeFrame(
|
|
319
|
+
schemas,
|
|
320
|
+
'Success',
|
|
321
|
+
{ message: 'ok' },
|
|
322
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq }
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
);
|
|
326
|
+
const plugin = createPlugin({
|
|
327
|
+
devices: [{ id: 'repeated-acquire-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
328
|
+
responses,
|
|
329
|
+
});
|
|
330
|
+
const lowlevel = configureTransport(plugin);
|
|
331
|
+
|
|
332
|
+
await expect(
|
|
333
|
+
lowlevel.acquire({ uuid: 'repeated-acquire-id', expectedProtocol: 'V2' })
|
|
334
|
+
).resolves.toEqual({
|
|
335
|
+
uuid: 'repeated-acquire-id',
|
|
336
|
+
protocolType: 'V2',
|
|
337
|
+
});
|
|
338
|
+
await lowlevel.call('repeated-acquire-id', 'Ping', { message: 'first-acquire' });
|
|
339
|
+
await expect(
|
|
340
|
+
lowlevel.acquire({ uuid: 'repeated-acquire-id', expectedProtocol: 'V2' })
|
|
341
|
+
).resolves.toEqual({
|
|
342
|
+
uuid: 'repeated-acquire-id',
|
|
343
|
+
protocolType: 'V2',
|
|
344
|
+
});
|
|
345
|
+
await lowlevel.call('repeated-acquire-id', 'Ping', { message: 'second-acquire' });
|
|
346
|
+
|
|
347
|
+
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
348
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
349
|
+
);
|
|
350
|
+
expect(sentSeqs).toEqual([1, 2, 3, 4]);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('actively probes explicit Protocol V2 during bootloader reconnect', async () => {
|
|
354
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
355
|
+
schemas,
|
|
356
|
+
'Success',
|
|
357
|
+
{ message: 'ok' },
|
|
358
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
359
|
+
);
|
|
360
|
+
const plugin = createPlugin({
|
|
361
|
+
devices: [{ id: 'bootloader-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
362
|
+
responses: [bytesToHex(probeResponse)],
|
|
363
|
+
});
|
|
364
|
+
const lowlevel = configureTransport(plugin);
|
|
365
|
+
|
|
366
|
+
await expect(
|
|
367
|
+
lowlevel.acquire({ uuid: 'bootloader-v2-id', expectedProtocol: 'V2' })
|
|
368
|
+
).resolves.toEqual({
|
|
369
|
+
uuid: 'bootloader-v2-id',
|
|
370
|
+
protocolType: 'V2',
|
|
371
|
+
});
|
|
372
|
+
expect(plugin.send).toHaveBeenCalledTimes(1);
|
|
373
|
+
expect(plugin.receive).toHaveBeenCalledTimes(1);
|
|
374
|
+
});
|
|
375
|
+
|
|
221
376
|
test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
|
|
222
377
|
const probeResponse = ProtocolV2.encodeFrame(
|
|
223
378
|
schemas,
|
|
@@ -256,6 +411,57 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
256
411
|
expect(plugin.connect).toHaveBeenCalledTimes(2);
|
|
257
412
|
});
|
|
258
413
|
|
|
414
|
+
test('disconnects a tainted Protocol V2 link after a response timeout', async () => {
|
|
415
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
416
|
+
schemas,
|
|
417
|
+
'Success',
|
|
418
|
+
{ message: 'ok' },
|
|
419
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
420
|
+
);
|
|
421
|
+
const plugin = createPlugin({
|
|
422
|
+
devices: [{ id: 'timeout-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
423
|
+
responses: [bytesToHex(probeResponse)],
|
|
424
|
+
});
|
|
425
|
+
let receiveCount = 0;
|
|
426
|
+
plugin.receive.mockImplementation(() => {
|
|
427
|
+
receiveCount += 1;
|
|
428
|
+
return receiveCount === 1
|
|
429
|
+
? Promise.resolve(bytesToHex(probeResponse))
|
|
430
|
+
: new Promise(() => {});
|
|
431
|
+
});
|
|
432
|
+
const lowlevel = configureTransport(plugin);
|
|
433
|
+
|
|
434
|
+
await lowlevel.acquire({ uuid: 'timeout-v2-id', expectedProtocol: 'V2' });
|
|
435
|
+
plugin.disconnect.mockClear();
|
|
436
|
+
await expect(
|
|
437
|
+
lowlevel.call('timeout-v2-id', 'Ping', { message: 'timeout' }, { timeoutMs: 10 })
|
|
438
|
+
).rejects.toThrow('Lowlevel response timeout after 10ms for Ping');
|
|
439
|
+
|
|
440
|
+
expect(plugin.disconnect).toHaveBeenCalledWith('timeout-v2-id');
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test('preserves an undefined business timeout outside explicit probes', async () => {
|
|
444
|
+
const plugin = createPlugin({
|
|
445
|
+
devices: [{ id: 'v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
446
|
+
responses: [],
|
|
447
|
+
});
|
|
448
|
+
const lowlevel = configureTransport(plugin);
|
|
449
|
+
lowlevel.deviceProtocol.set('v2-id', 'V2');
|
|
450
|
+
const linkCall = jest
|
|
451
|
+
.spyOn(lowlevel.protocolV2Links, 'call')
|
|
452
|
+
.mockResolvedValue({ type: 'Success', message: {} });
|
|
453
|
+
|
|
454
|
+
await lowlevel.call('v2-id', 'Ping', { message: 'no-business-timeout' });
|
|
455
|
+
|
|
456
|
+
expect(linkCall).toHaveBeenCalledWith(
|
|
457
|
+
'v2-id',
|
|
458
|
+
expect.any(Function),
|
|
459
|
+
'Ping',
|
|
460
|
+
{ message: 'no-business-timeout' },
|
|
461
|
+
undefined
|
|
462
|
+
);
|
|
463
|
+
});
|
|
464
|
+
|
|
259
465
|
test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
|
|
260
466
|
const plugin = createPlugin({
|
|
261
467
|
devices: [{ id: 'v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,18 @@ import EventEmitter from 'events';
|
|
|
5
5
|
type LowLevelAcquireInput = {
|
|
6
6
|
uuid: string;
|
|
7
7
|
expectedProtocol?: ProtocolType;
|
|
8
|
+
protocolHint?: ProtocolType;
|
|
8
9
|
};
|
|
9
10
|
|
|
11
|
+
declare function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, lastLoggedAt, }: {
|
|
12
|
+
percent: number;
|
|
13
|
+
lastLoggedPercent: number;
|
|
14
|
+
now: number;
|
|
15
|
+
lastLoggedAt: number;
|
|
16
|
+
}): boolean;
|
|
17
|
+
declare function getProtocolV1SendOptions(name: string): {
|
|
18
|
+
withoutResponse: boolean;
|
|
19
|
+
} | undefined;
|
|
10
20
|
declare class LowlevelTransport {
|
|
11
21
|
_messages: ReturnType<typeof transport__default.parseConfigure> | undefined;
|
|
12
22
|
_messagesV2: ReturnType<typeof transport__default.parseConfigure> | undefined;
|
|
@@ -17,6 +27,10 @@ declare class LowlevelTransport {
|
|
|
17
27
|
private deviceProtocol;
|
|
18
28
|
private deviceProtocolHints;
|
|
19
29
|
private protocolV2Assemblers;
|
|
30
|
+
private protocolV2Generations;
|
|
31
|
+
private connectedDevices;
|
|
32
|
+
private protocolV2Links;
|
|
33
|
+
private protocolV2SchemaConfiguration;
|
|
20
34
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
21
35
|
init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin): void;
|
|
22
36
|
configure(signedData: any): void;
|
|
@@ -43,7 +57,9 @@ declare class LowlevelTransport {
|
|
|
43
57
|
private readProtocolV2Frame;
|
|
44
58
|
private writeProtocolV2Frame;
|
|
45
59
|
private callProtocolV2;
|
|
60
|
+
private createProtocolV2Adapter;
|
|
61
|
+
private advanceProtocolV2Generation;
|
|
46
62
|
cancel(): void;
|
|
47
63
|
}
|
|
48
64
|
|
|
49
|
-
export { LowlevelTransport as default };
|
|
65
|
+
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,EAC7B,YAAY,EAEZ,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;YA2Cd,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
|
}
|
|
@@ -91,19 +145,25 @@ class LowlevelTransport {
|
|
|
91
145
|
});
|
|
92
146
|
}
|
|
93
147
|
acquire(input) {
|
|
94
|
-
var _a;
|
|
148
|
+
var _a, _b;
|
|
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
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
166
|
+
: (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(input.uuid);
|
|
107
167
|
const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
|
|
108
168
|
return { uuid: input.uuid, protocolType };
|
|
109
169
|
});
|
|
@@ -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
|
}
|
|
@@ -194,7 +283,7 @@ class LowlevelTransport {
|
|
|
194
283
|
}
|
|
195
284
|
}
|
|
196
285
|
detectProtocol(uuid, expectedProtocol, protocolHint) {
|
|
197
|
-
var _a, _b, _c
|
|
286
|
+
var _a, _b, _c;
|
|
198
287
|
return __awaiter(this, void 0, void 0, function* () {
|
|
199
288
|
if (expectedProtocol === 'V2') {
|
|
200
289
|
if (yield this.probeProtocolV2(uuid)) {
|
|
@@ -212,28 +301,17 @@ class LowlevelTransport {
|
|
|
212
301
|
}
|
|
213
302
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
214
303
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const protocolV1Detected = yield this.probeProtocolV1(uuid);
|
|
227
|
-
if (protocolV1Detected) {
|
|
228
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
229
|
-
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
230
|
-
return 'V1';
|
|
231
|
-
}
|
|
232
|
-
yield this.resetConnectionAfterProbe(uuid, 'V1');
|
|
233
|
-
if (yield this.probeProtocolV2(uuid)) {
|
|
234
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
235
|
-
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
|
|
236
|
-
return 'V2';
|
|
304
|
+
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
305
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
306
|
+
if (index > 0) {
|
|
307
|
+
yield this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
|
|
308
|
+
}
|
|
309
|
+
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
310
|
+
if (detected) {
|
|
311
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
312
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
313
|
+
return protocol;
|
|
314
|
+
}
|
|
237
315
|
}
|
|
238
316
|
this.deviceProtocol.delete(uuid);
|
|
239
317
|
throw this.createProtocolDetectionError();
|
|
@@ -242,8 +320,10 @@ class LowlevelTransport {
|
|
|
242
320
|
resetConnectionAfterProbe(uuid, protocol) {
|
|
243
321
|
var _a, _b, _c, _d;
|
|
244
322
|
return __awaiter(this, void 0, void 0, function* () {
|
|
323
|
+
yield this.protocolV2Links.invalidateLink(uuid, `Reset connection after Protocol ${protocol} probe`);
|
|
245
324
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
246
325
|
try {
|
|
326
|
+
this.connectedDevices.delete(uuid);
|
|
247
327
|
yield this.plugin.disconnect(uuid);
|
|
248
328
|
}
|
|
249
329
|
catch (error) {
|
|
@@ -251,6 +331,8 @@ class LowlevelTransport {
|
|
|
251
331
|
}
|
|
252
332
|
try {
|
|
253
333
|
yield this.plugin.connect(uuid);
|
|
334
|
+
this.connectedDevices.add(uuid);
|
|
335
|
+
this.advanceProtocolV2Generation(uuid);
|
|
254
336
|
}
|
|
255
337
|
catch (error) {
|
|
256
338
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
@@ -307,18 +389,18 @@ class LowlevelTransport {
|
|
|
307
389
|
}
|
|
308
390
|
});
|
|
309
391
|
}
|
|
310
|
-
receiveHex(timeoutMs, commandName) {
|
|
392
|
+
receiveHex(uuid, timeoutMs, commandName) {
|
|
311
393
|
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));
|
|
394
|
+
const response = yield transport.withProtocolTimeout(this.plugin.receive(uuid), timeoutMs, () => this.createProtocolTimeoutError(commandName, timeoutMs !== null && timeoutMs !== void 0 ? timeoutMs : 0));
|
|
313
395
|
if (typeof response !== 'string') {
|
|
314
396
|
throw new Error('Returning data is not string');
|
|
315
397
|
}
|
|
316
398
|
return response;
|
|
317
399
|
});
|
|
318
400
|
}
|
|
319
|
-
readProtocolV1Message(timeoutMs) {
|
|
401
|
+
readProtocolV1Message(uuid, timeoutMs) {
|
|
320
402
|
return __awaiter(this, void 0, void 0, function* () {
|
|
321
|
-
const first = yield this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
403
|
+
const first = yield this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
322
404
|
const firstData = transport.hexToBytes(first);
|
|
323
405
|
if (!isProtocolV1TransportChunk(firstData)) {
|
|
324
406
|
return first;
|
|
@@ -327,17 +409,17 @@ class LowlevelTransport {
|
|
|
327
409
|
let buffer = firstData.slice(3);
|
|
328
410
|
const expectedLength = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE + payloadLength;
|
|
329
411
|
while (buffer.length < expectedLength) {
|
|
330
|
-
const next = yield this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
412
|
+
const next = yield this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
331
413
|
buffer = transport.concatUint8Arrays([buffer, transport.hexToBytes(next)]);
|
|
332
414
|
}
|
|
333
415
|
return transport.bytesToHex(buffer.slice(0, expectedLength));
|
|
334
416
|
});
|
|
335
417
|
}
|
|
336
|
-
readProtocolV2Frame(uuid, timeoutMs) {
|
|
418
|
+
readProtocolV2Frame(uuid, timeoutMs, commandName = 'ProtocolV2') {
|
|
337
419
|
return __awaiter(this, void 0, void 0, function* () {
|
|
338
420
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
339
421
|
if (!assembler) {
|
|
340
|
-
assembler = new transport.ProtocolV2FrameAssembler();
|
|
422
|
+
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
341
423
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
342
424
|
}
|
|
343
425
|
const queuedFrame = assembler.push(new Uint8Array(0));
|
|
@@ -345,7 +427,7 @@ class LowlevelTransport {
|
|
|
345
427
|
return queuedFrame;
|
|
346
428
|
let frame;
|
|
347
429
|
while (!frame) {
|
|
348
|
-
const response = yield this.receiveHex(timeoutMs,
|
|
430
|
+
const response = yield this.receiveHex(uuid, timeoutMs, commandName);
|
|
349
431
|
const chunk = transport.hexToBytes(response);
|
|
350
432
|
if (chunk.length > 0) {
|
|
351
433
|
frame = assembler.push(chunk);
|
|
@@ -354,47 +436,74 @@ class LowlevelTransport {
|
|
|
354
436
|
return frame;
|
|
355
437
|
});
|
|
356
438
|
}
|
|
357
|
-
writeProtocolV2Frame(uuid, frame) {
|
|
439
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
358
440
|
return __awaiter(this, void 0, void 0, function* () {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
441
|
+
yield transport.writeProtocolV2BleFrame({
|
|
442
|
+
frame,
|
|
443
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
444
|
+
assertActive: assertCurrentGeneration,
|
|
445
|
+
signal: context.signal,
|
|
446
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
447
|
+
writePacket: packet => this.plugin.send(uuid, transport.bytesToHex(packet)),
|
|
448
|
+
});
|
|
363
449
|
});
|
|
364
450
|
}
|
|
365
451
|
callProtocolV2(uuid, name, data, options) {
|
|
366
|
-
var _a, _b, _c;
|
|
367
452
|
return __awaiter(this, void 0, void 0, function* () {
|
|
368
453
|
if (!this._messages || !this._messagesV2) {
|
|
369
454
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
370
455
|
}
|
|
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
456
|
try {
|
|
386
|
-
return yield
|
|
457
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
|
|
387
458
|
}
|
|
388
459
|
catch (e) {
|
|
389
|
-
(_c = this.protocolV2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
|
|
390
460
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
391
461
|
throw e;
|
|
392
462
|
}
|
|
393
463
|
});
|
|
394
464
|
}
|
|
465
|
+
createProtocolV2Adapter(uuid) {
|
|
466
|
+
var _a;
|
|
467
|
+
const generation = (_a = this.protocolV2Generations.get(uuid)) !== null && _a !== void 0 ? _a : 0;
|
|
468
|
+
const assertCurrentGeneration = () => {
|
|
469
|
+
if (this.protocolV2Generations.get(uuid) !== generation) {
|
|
470
|
+
throw new Error(`Protocol V2 connection generation changed for ${uuid}`);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
return {
|
|
474
|
+
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
475
|
+
maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
476
|
+
generation,
|
|
477
|
+
prepareCall: () => {
|
|
478
|
+
var _a;
|
|
479
|
+
assertCurrentGeneration();
|
|
480
|
+
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
481
|
+
},
|
|
482
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
483
|
+
readFrame: (context) => {
|
|
484
|
+
assertCurrentGeneration();
|
|
485
|
+
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
486
|
+
},
|
|
487
|
+
reset: () => {
|
|
488
|
+
var _a;
|
|
489
|
+
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
490
|
+
},
|
|
491
|
+
logger: this.Log,
|
|
492
|
+
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
493
|
+
createTimeoutError: (messageName, timeout) => this.createProtocolTimeoutError(messageName, timeout),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
advanceProtocolV2Generation(uuid) {
|
|
497
|
+
var _a;
|
|
498
|
+
const nextGeneration = ((_a = this.protocolV2Generations.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
499
|
+
this.protocolV2Generations.set(uuid, nextGeneration);
|
|
500
|
+
return nextGeneration;
|
|
501
|
+
}
|
|
395
502
|
cancel() {
|
|
396
503
|
this.Log.debug('lowlevel-transport', 'cancel');
|
|
397
504
|
}
|
|
398
505
|
}
|
|
399
506
|
|
|
400
|
-
|
|
507
|
+
exports["default"] = LowlevelTransport;
|
|
508
|
+
exports.getProtocolV1SendOptions = getProtocolV1SendOptions;
|
|
509
|
+
exports.shouldLogFirmwareUploadProgress = shouldLogFirmwareUploadProgress;
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC"}
|
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.70",
|
|
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.70",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.70"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "d86b033cf970f97e060c49fe4cf618f38b9ce666"
|
|
27
27
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
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';
|
|
@@ -17,6 +18,7 @@ import type {
|
|
|
17
18
|
LowLevelDevice,
|
|
18
19
|
LowlevelTransportSharedPlugin,
|
|
19
20
|
ProtocolType,
|
|
21
|
+
ProtocolV2CallContext,
|
|
20
22
|
TransportCallOptions,
|
|
21
23
|
} from '@onekeyfe/hd-transport';
|
|
22
24
|
import type { LowLevelAcquireInput } from './types';
|
|
@@ -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,10 +187,13 @@ 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
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
196
|
+
: input.protocolHint ?? this.deviceProtocolHints.get(input.uuid);
|
|
113
197
|
const protocolType = await this.detectProtocol(
|
|
114
198
|
input.uuid,
|
|
115
199
|
input.expectedProtocol,
|
|
@@ -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 {
|
|
@@ -259,31 +371,20 @@ export default class LowlevelTransport {
|
|
|
259
371
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
260
372
|
}
|
|
261
373
|
|
|
262
|
-
|
|
263
|
-
this.deviceProtocol.
|
|
264
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
|
|
265
|
-
return 'V2';
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
269
|
-
if (cachedProtocol === 'V2' && (await this.probeProtocolV2(uuid))) {
|
|
270
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
271
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
|
|
272
|
-
return 'V2';
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const protocolV1Detected = await this.probeProtocolV1(uuid);
|
|
276
|
-
if (protocolV1Detected) {
|
|
277
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
278
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
279
|
-
return 'V1';
|
|
280
|
-
}
|
|
374
|
+
const probeOrder: ProtocolType[] =
|
|
375
|
+
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
281
376
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
377
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
378
|
+
if (index > 0) {
|
|
379
|
+
await this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
|
|
380
|
+
}
|
|
381
|
+
const detected =
|
|
382
|
+
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
383
|
+
if (detected) {
|
|
384
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
385
|
+
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
386
|
+
return protocol;
|
|
387
|
+
}
|
|
287
388
|
}
|
|
288
389
|
|
|
289
390
|
this.deviceProtocol.delete(uuid);
|
|
@@ -291,9 +392,14 @@ export default class LowlevelTransport {
|
|
|
291
392
|
}
|
|
292
393
|
|
|
293
394
|
private async resetConnectionAfterProbe(uuid: string, protocol: ProtocolType) {
|
|
395
|
+
await this.protocolV2Links.invalidateLink(
|
|
396
|
+
uuid,
|
|
397
|
+
`Reset connection after Protocol ${protocol} probe`
|
|
398
|
+
);
|
|
294
399
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
295
400
|
|
|
296
401
|
try {
|
|
402
|
+
this.connectedDevices.delete(uuid);
|
|
297
403
|
await this.plugin.disconnect(uuid);
|
|
298
404
|
} catch (error) {
|
|
299
405
|
this.Log?.debug(
|
|
@@ -304,6 +410,8 @@ export default class LowlevelTransport {
|
|
|
304
410
|
|
|
305
411
|
try {
|
|
306
412
|
await this.plugin.connect(uuid);
|
|
413
|
+
this.connectedDevices.add(uuid);
|
|
414
|
+
this.advanceProtocolV2Generation(uuid);
|
|
307
415
|
} catch (error) {
|
|
308
416
|
this.Log?.debug(
|
|
309
417
|
`[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`,
|
|
@@ -361,8 +469,8 @@ export default class LowlevelTransport {
|
|
|
361
469
|
}
|
|
362
470
|
}
|
|
363
471
|
|
|
364
|
-
private async receiveHex(timeoutMs: number | undefined, commandName: string) {
|
|
365
|
-
const response = await withProtocolTimeout(this.plugin.receive(), timeoutMs, () =>
|
|
472
|
+
private async receiveHex(uuid: string, timeoutMs: number | undefined, commandName: string) {
|
|
473
|
+
const response = await withProtocolTimeout(this.plugin.receive(uuid), timeoutMs, () =>
|
|
366
474
|
this.createProtocolTimeoutError(commandName, timeoutMs ?? 0)
|
|
367
475
|
);
|
|
368
476
|
if (typeof response !== 'string') {
|
|
@@ -371,8 +479,8 @@ export default class LowlevelTransport {
|
|
|
371
479
|
return response;
|
|
372
480
|
}
|
|
373
481
|
|
|
374
|
-
private async readProtocolV1Message(timeoutMs?: number) {
|
|
375
|
-
const first = await this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
482
|
+
private async readProtocolV1Message(uuid: string, timeoutMs?: number) {
|
|
483
|
+
const first = await this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
376
484
|
const firstData = hexToBytes(first);
|
|
377
485
|
if (!isProtocolV1TransportChunk(firstData)) {
|
|
378
486
|
return first;
|
|
@@ -383,17 +491,17 @@ export default class LowlevelTransport {
|
|
|
383
491
|
const expectedLength = PROTOCOL_V1_MESSAGE_HEADER_SIZE + payloadLength;
|
|
384
492
|
|
|
385
493
|
while (buffer.length < expectedLength) {
|
|
386
|
-
const next = await this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
494
|
+
const next = await this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
387
495
|
buffer = concatUint8Arrays([buffer, hexToBytes(next)]);
|
|
388
496
|
}
|
|
389
497
|
|
|
390
498
|
return bytesToHex(buffer.slice(0, expectedLength));
|
|
391
499
|
}
|
|
392
500
|
|
|
393
|
-
private async readProtocolV2Frame(uuid: string, timeoutMs?: number) {
|
|
501
|
+
private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
|
|
394
502
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
395
503
|
if (!assembler) {
|
|
396
|
-
assembler = new ProtocolV2FrameAssembler();
|
|
504
|
+
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
397
505
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
398
506
|
}
|
|
399
507
|
|
|
@@ -402,7 +510,7 @@ export default class LowlevelTransport {
|
|
|
402
510
|
|
|
403
511
|
let frame: Uint8Array | undefined;
|
|
404
512
|
while (!frame) {
|
|
405
|
-
const response = await this.receiveHex(timeoutMs,
|
|
513
|
+
const response = await this.receiveHex(uuid, timeoutMs, commandName);
|
|
406
514
|
const chunk = hexToBytes(response);
|
|
407
515
|
if (chunk.length > 0) {
|
|
408
516
|
frame = assembler.push(chunk);
|
|
@@ -411,11 +519,20 @@ export default class LowlevelTransport {
|
|
|
411
519
|
return frame;
|
|
412
520
|
}
|
|
413
521
|
|
|
414
|
-
private async writeProtocolV2Frame(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
522
|
+
private async writeProtocolV2Frame(
|
|
523
|
+
uuid: string,
|
|
524
|
+
frame: Uint8Array,
|
|
525
|
+
context: ProtocolV2CallContext,
|
|
526
|
+
assertCurrentGeneration: () => void
|
|
527
|
+
) {
|
|
528
|
+
await writeProtocolV2BleFrame({
|
|
529
|
+
frame,
|
|
530
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
531
|
+
assertActive: assertCurrentGeneration,
|
|
532
|
+
signal: context.signal,
|
|
533
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
534
|
+
writePacket: packet => this.plugin.send(uuid, bytesToHex(packet)),
|
|
535
|
+
});
|
|
419
536
|
}
|
|
420
537
|
|
|
421
538
|
private async callProtocolV2(
|
|
@@ -428,34 +545,58 @@ export default class LowlevelTransport {
|
|
|
428
545
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
429
546
|
}
|
|
430
547
|
|
|
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
548
|
try {
|
|
448
|
-
return await
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
549
|
+
return await this.protocolV2Links.call(
|
|
550
|
+
uuid,
|
|
551
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
552
|
+
name,
|
|
553
|
+
data,
|
|
554
|
+
options
|
|
555
|
+
);
|
|
452
556
|
} catch (e) {
|
|
453
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
454
557
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
455
558
|
throw e;
|
|
456
559
|
}
|
|
457
560
|
}
|
|
458
561
|
|
|
562
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
563
|
+
const generation = this.protocolV2Generations.get(uuid) ?? 0;
|
|
564
|
+
const assertCurrentGeneration = () => {
|
|
565
|
+
if (this.protocolV2Generations.get(uuid) !== generation) {
|
|
566
|
+
throw new Error(`Protocol V2 connection generation changed for ${uuid}`);
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
572
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
573
|
+
generation,
|
|
574
|
+
prepareCall: () => {
|
|
575
|
+
assertCurrentGeneration();
|
|
576
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
577
|
+
},
|
|
578
|
+
writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
|
|
579
|
+
this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
580
|
+
readFrame: (context: { messageName: string; timeoutMs?: number }) => {
|
|
581
|
+
assertCurrentGeneration();
|
|
582
|
+
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
583
|
+
},
|
|
584
|
+
reset: () => {
|
|
585
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
586
|
+
},
|
|
587
|
+
logger: this.Log,
|
|
588
|
+
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
589
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
590
|
+
this.createProtocolTimeoutError(messageName, timeout),
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private advanceProtocolV2Generation(uuid: string) {
|
|
595
|
+
const nextGeneration = (this.protocolV2Generations.get(uuid) ?? 0) + 1;
|
|
596
|
+
this.protocolV2Generations.set(uuid, nextGeneration);
|
|
597
|
+
return nextGeneration;
|
|
598
|
+
}
|
|
599
|
+
|
|
459
600
|
cancel() {
|
|
460
601
|
this.Log.debug('lowlevel-transport', 'cancel');
|
|
461
602
|
}
|