@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.13 → 1.2.0-alpha.131
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 +158 -14
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -52
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +166 -64
- 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
|
+
});
|
|
@@ -68,12 +68,25 @@ const protocolV2Schema = {
|
|
|
68
68
|
},
|
|
69
69
|
},
|
|
70
70
|
},
|
|
71
|
+
Failure: {
|
|
72
|
+
fields: {
|
|
73
|
+
code: {
|
|
74
|
+
type: 'uint32',
|
|
75
|
+
id: 1,
|
|
76
|
+
},
|
|
77
|
+
message: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
id: 2,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
71
83
|
MessageType: {
|
|
72
84
|
values: {
|
|
73
85
|
MessageType_ProtocolInfoRequest: 60200,
|
|
74
86
|
MessageType_ProtocolInfo: 60201,
|
|
75
87
|
MessageType_Ping: 60206,
|
|
76
88
|
MessageType_Success: 60207,
|
|
89
|
+
MessageType_Failure: 60208,
|
|
77
90
|
},
|
|
78
91
|
},
|
|
79
92
|
},
|
|
@@ -122,6 +135,49 @@ const splitFrame = (frame, index) => [
|
|
|
122
135
|
];
|
|
123
136
|
|
|
124
137
|
describe('LowlevelTransport protocol framing', () => {
|
|
138
|
+
test('falls back to Protocol V2 when a cached V1 hint is stale', async () => {
|
|
139
|
+
const plugin = createPlugin({ devices: [], responses: [] });
|
|
140
|
+
const lowlevel = configureTransport(plugin);
|
|
141
|
+
const events = [];
|
|
142
|
+
lowlevel.probeProtocolV1 = jest.fn().mockImplementation(() => {
|
|
143
|
+
events.push('probe-v1');
|
|
144
|
+
return Promise.resolve(false);
|
|
145
|
+
});
|
|
146
|
+
lowlevel.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
|
|
147
|
+
events.push('reset');
|
|
148
|
+
return Promise.resolve();
|
|
149
|
+
});
|
|
150
|
+
lowlevel.probeProtocolV2 = jest.fn().mockImplementation(() => {
|
|
151
|
+
events.push('probe-v2');
|
|
152
|
+
return Promise.resolve(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await expect(lowlevel.detectProtocol('pro-lowlevel', undefined, 'V1')).resolves.toBe('V2');
|
|
156
|
+
|
|
157
|
+
expect(events).toEqual(['probe-v1', 'reset', 'probe-v2']);
|
|
158
|
+
expect(lowlevel.getProtocolType('pro-lowlevel')).toBe('V2');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('keeps active links when the Protocol V2 schema is configured repeatedly', () => {
|
|
162
|
+
const lowlevel = new LowlevelTransport();
|
|
163
|
+
const invalidateAllLinks = jest.fn().mockResolvedValue(undefined);
|
|
164
|
+
lowlevel.protocolV2Links.invalidateAllLinks = invalidateAllLinks;
|
|
165
|
+
|
|
166
|
+
lowlevel.configureProtocolV2(protocolV2Schema);
|
|
167
|
+
lowlevel.configureProtocolV2(protocolV2Schema);
|
|
168
|
+
|
|
169
|
+
expect(invalidateAllLinks).not.toHaveBeenCalled();
|
|
170
|
+
|
|
171
|
+
lowlevel.configureProtocolV2({
|
|
172
|
+
nested: {
|
|
173
|
+
...protocolV2Schema.nested,
|
|
174
|
+
ExtraMessage: { fields: {} },
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(invalidateAllLinks).toHaveBeenCalledWith('Protocol V2 schema reconfigured');
|
|
179
|
+
});
|
|
180
|
+
|
|
125
181
|
test('keeps Protocol V1 raw notification chunks compatible', async () => {
|
|
126
182
|
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
127
183
|
message: 'ok',
|
|
@@ -142,6 +198,23 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
142
198
|
});
|
|
143
199
|
});
|
|
144
200
|
|
|
201
|
+
test('uses the Protocol V2 BLE writer with the lowlevel compatibility packet size', async () => {
|
|
202
|
+
const plugin = createPlugin({ devices: [], responses: [] });
|
|
203
|
+
const lowlevel = configureTransport(plugin);
|
|
204
|
+
const context = {
|
|
205
|
+
messageName: 'Ping',
|
|
206
|
+
timeoutMs: 1000,
|
|
207
|
+
highThroughput: false,
|
|
208
|
+
generation: 1,
|
|
209
|
+
signal: new AbortController().signal,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
await lowlevel.writeProtocolV2Frame('pro2-id', new Uint8Array(130), context, jest.fn());
|
|
213
|
+
|
|
214
|
+
expect(plugin.send).toHaveBeenCalledTimes(3);
|
|
215
|
+
expect(plugin.send.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([64, 64, 2]);
|
|
216
|
+
});
|
|
217
|
+
|
|
145
218
|
test('rejects calls before protocol detection', async () => {
|
|
146
219
|
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
147
220
|
message: 'ok',
|
|
@@ -172,7 +245,7 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
172
245
|
supported_messages: [60200, 60201, 60206, 60207],
|
|
173
246
|
protobuf_definition: 'onekey-protocol-v2',
|
|
174
247
|
},
|
|
175
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
248
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: 2 }
|
|
176
249
|
);
|
|
177
250
|
const plugin = createPlugin({
|
|
178
251
|
devices: [{ id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
@@ -253,15 +326,19 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
253
326
|
});
|
|
254
327
|
|
|
255
328
|
test('reuses the active generation when Core acquires the same BLE connection again', async () => {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
329
|
+
const responses = [1, 2, 3, 4].map(seq =>
|
|
330
|
+
bytesToHex(
|
|
331
|
+
ProtocolV2.encodeFrame(
|
|
332
|
+
schemas,
|
|
333
|
+
'Success',
|
|
334
|
+
{ message: 'ok' },
|
|
335
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq }
|
|
336
|
+
)
|
|
337
|
+
)
|
|
261
338
|
);
|
|
262
339
|
const plugin = createPlugin({
|
|
263
340
|
devices: [{ id: 'repeated-acquire-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
264
|
-
responses
|
|
341
|
+
responses,
|
|
265
342
|
});
|
|
266
343
|
const lowlevel = configureTransport(plugin);
|
|
267
344
|
|
|
@@ -283,13 +360,19 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
283
360
|
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
284
361
|
Number.parseInt(hex.slice(12, 14), 16)
|
|
285
362
|
);
|
|
286
|
-
expect(sentSeqs).toEqual([1, 2]);
|
|
363
|
+
expect(sentSeqs).toEqual([1, 2, 3, 4]);
|
|
287
364
|
});
|
|
288
365
|
|
|
289
|
-
test('
|
|
366
|
+
test('actively probes explicit Protocol V2 during bootloader reconnect', async () => {
|
|
367
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
368
|
+
schemas,
|
|
369
|
+
'Success',
|
|
370
|
+
{ message: 'ok' },
|
|
371
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
372
|
+
);
|
|
290
373
|
const plugin = createPlugin({
|
|
291
374
|
devices: [{ id: 'bootloader-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
292
|
-
responses: [],
|
|
375
|
+
responses: [bytesToHex(probeResponse)],
|
|
293
376
|
});
|
|
294
377
|
const lowlevel = configureTransport(plugin);
|
|
295
378
|
|
|
@@ -299,8 +382,35 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
299
382
|
uuid: 'bootloader-v2-id',
|
|
300
383
|
protocolType: 'V2',
|
|
301
384
|
});
|
|
302
|
-
expect(plugin.send).
|
|
303
|
-
expect(plugin.receive).
|
|
385
|
+
expect(plugin.send).toHaveBeenCalledTimes(1);
|
|
386
|
+
expect(plugin.receive).toHaveBeenCalledTimes(1);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test('disconnects and clears a lowlevel connection when Protocol V2 reports link disabled', async () => {
|
|
390
|
+
const failureResponse = ProtocolV2.encodeFrame(
|
|
391
|
+
schemas,
|
|
392
|
+
'Failure',
|
|
393
|
+
{ code: 5, message: 'link disabled' },
|
|
394
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
395
|
+
);
|
|
396
|
+
const plugin = createPlugin({
|
|
397
|
+
devices: [{ id: 'usb-owned-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
398
|
+
responses: [bytesToHex(failureResponse)],
|
|
399
|
+
});
|
|
400
|
+
const lowlevel = configureTransport(plugin);
|
|
401
|
+
|
|
402
|
+
await expect(
|
|
403
|
+
lowlevel.acquire({ uuid: 'usb-owned-id', expectedProtocol: 'V2' })
|
|
404
|
+
).rejects.toMatchObject({
|
|
405
|
+
name: 'ProtocolV2LinkDisabledError',
|
|
406
|
+
failureCode: 5,
|
|
407
|
+
firmwareMessage: 'link disabled',
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
expect(plugin.disconnect).toHaveBeenCalledWith('usb-owned-id');
|
|
411
|
+
expect(lowlevel.connectedDevices.has('usb-owned-id')).toBe(false);
|
|
412
|
+
expect(lowlevel.getProtocolType('usb-owned-id')).toBeUndefined();
|
|
413
|
+
expect(lowlevel.protocolV2Assemblers.has('usb-owned-id')).toBe(false);
|
|
304
414
|
});
|
|
305
415
|
|
|
306
416
|
test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
|
|
@@ -342,11 +452,23 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
342
452
|
});
|
|
343
453
|
|
|
344
454
|
test('disconnects a tainted Protocol V2 link after a response timeout', async () => {
|
|
455
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
456
|
+
schemas,
|
|
457
|
+
'Success',
|
|
458
|
+
{ message: 'ok' },
|
|
459
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
460
|
+
);
|
|
345
461
|
const plugin = createPlugin({
|
|
346
462
|
devices: [{ id: 'timeout-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
347
|
-
responses: [],
|
|
463
|
+
responses: [bytesToHex(probeResponse)],
|
|
464
|
+
});
|
|
465
|
+
let receiveCount = 0;
|
|
466
|
+
plugin.receive.mockImplementation(() => {
|
|
467
|
+
receiveCount += 1;
|
|
468
|
+
return receiveCount === 1
|
|
469
|
+
? Promise.resolve(bytesToHex(probeResponse))
|
|
470
|
+
: new Promise(() => {});
|
|
348
471
|
});
|
|
349
|
-
plugin.receive.mockImplementation(() => new Promise(() => {}));
|
|
350
472
|
const lowlevel = configureTransport(plugin);
|
|
351
473
|
|
|
352
474
|
await lowlevel.acquire({ uuid: 'timeout-v2-id', expectedProtocol: 'V2' });
|
|
@@ -358,6 +480,28 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
358
480
|
expect(plugin.disconnect).toHaveBeenCalledWith('timeout-v2-id');
|
|
359
481
|
});
|
|
360
482
|
|
|
483
|
+
test('preserves an undefined business timeout outside explicit probes', async () => {
|
|
484
|
+
const plugin = createPlugin({
|
|
485
|
+
devices: [{ id: 'v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
486
|
+
responses: [],
|
|
487
|
+
});
|
|
488
|
+
const lowlevel = configureTransport(plugin);
|
|
489
|
+
lowlevel.deviceProtocol.set('v2-id', 'V2');
|
|
490
|
+
const linkCall = jest
|
|
491
|
+
.spyOn(lowlevel.protocolV2Links, 'call')
|
|
492
|
+
.mockResolvedValue({ type: 'Success', message: {} });
|
|
493
|
+
|
|
494
|
+
await lowlevel.call('v2-id', 'Ping', { message: 'no-business-timeout' });
|
|
495
|
+
|
|
496
|
+
expect(linkCall).toHaveBeenCalledWith(
|
|
497
|
+
'v2-id',
|
|
498
|
+
expect.any(Function),
|
|
499
|
+
'Ping',
|
|
500
|
+
{ message: 'no-business-timeout' },
|
|
501
|
+
undefined
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
|
|
361
505
|
test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
|
|
362
506
|
const plugin = createPlugin({
|
|
363
507
|
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;
|
|
@@ -20,6 +30,7 @@ declare class LowlevelTransport {
|
|
|
20
30
|
private protocolV2Generations;
|
|
21
31
|
private connectedDevices;
|
|
22
32
|
private protocolV2Links;
|
|
33
|
+
private protocolV2SchemaConfiguration;
|
|
23
34
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
24
35
|
init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin): void;
|
|
25
36
|
configure(signedData: any): void;
|
|
@@ -32,6 +43,7 @@ declare class LowlevelTransport {
|
|
|
32
43
|
}>;
|
|
33
44
|
release(uuid: string): Promise<boolean>;
|
|
34
45
|
call(uuid: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<transport.MessageFromOneKey>;
|
|
46
|
+
post(uuid: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
35
47
|
private callProtocolV1;
|
|
36
48
|
private createProtocolTimeoutError;
|
|
37
49
|
private createProtocolMismatchError;
|
|
@@ -51,4 +63,4 @@ declare class LowlevelTransport {
|
|
|
51
63
|
cancel(): void;
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
export { LowlevelTransport as default };
|
|
66
|
+
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;;;;IAmDnC,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;IAoB1B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;YAatD,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
|
}
|
|
@@ -106,10 +117,18 @@ class LowlevelTransport {
|
|
|
106
117
|
this._messages = messages;
|
|
107
118
|
}
|
|
108
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;
|
|
109
125
|
this._messagesV2 = parseConfigure(signedData);
|
|
110
|
-
this.
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
}
|
|
113
132
|
}
|
|
114
133
|
listen() {
|
|
115
134
|
}
|
|
@@ -126,7 +145,7 @@ class LowlevelTransport {
|
|
|
126
145
|
});
|
|
127
146
|
}
|
|
128
147
|
acquire(input) {
|
|
129
|
-
var _a;
|
|
148
|
+
var _a, _b, _c, _d;
|
|
130
149
|
return __awaiter(this, void 0, void 0, function* () {
|
|
131
150
|
const alreadyConnected = this.connectedDevices.has(input.uuid);
|
|
132
151
|
try {
|
|
@@ -141,12 +160,35 @@ class LowlevelTransport {
|
|
|
141
160
|
this.Log.debug('lowlelvel transport connect error: ', error);
|
|
142
161
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_a = error.message) !== null && _a !== void 0 ? _a : error);
|
|
143
162
|
}
|
|
144
|
-
this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler());
|
|
163
|
+
this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
145
164
|
const protocolHint = input.expectedProtocol
|
|
146
165
|
? undefined
|
|
147
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
148
|
-
|
|
149
|
-
|
|
166
|
+
: (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(input.uuid);
|
|
167
|
+
try {
|
|
168
|
+
const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
|
|
169
|
+
return { uuid: input.uuid, protocolType };
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
try {
|
|
173
|
+
yield this.protocolV2Links.invalidateLink(input.uuid, 'Lowlevel transport acquire failed');
|
|
174
|
+
}
|
|
175
|
+
catch (cleanupError) {
|
|
176
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[LowlevelTransport] acquire link cleanup failed:', cleanupError);
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
yield this.plugin.disconnect(input.uuid);
|
|
180
|
+
}
|
|
181
|
+
catch (cleanupError) {
|
|
182
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[LowlevelTransport] acquire disconnect failed:', cleanupError);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
this.connectedDevices.delete(input.uuid);
|
|
186
|
+
this.deviceProtocol.delete(input.uuid);
|
|
187
|
+
this.protocolV2Assemblers.delete(input.uuid);
|
|
188
|
+
this.advanceProtocolV2Generation(input.uuid);
|
|
189
|
+
}
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
150
192
|
});
|
|
151
193
|
}
|
|
152
194
|
release(uuid) {
|
|
@@ -174,24 +216,58 @@ class LowlevelTransport {
|
|
|
174
216
|
if (!protocol) {
|
|
175
217
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
176
218
|
}
|
|
177
|
-
this.Log.debug('transport call', { name, protocol });
|
|
178
219
|
if (protocol === 'V2') {
|
|
179
220
|
return this.callProtocolV2(uuid, name, data, options);
|
|
180
221
|
}
|
|
181
222
|
return this.callProtocolV1(uuid, name, data, options);
|
|
182
223
|
});
|
|
183
224
|
}
|
|
225
|
+
post(uuid, name, data) {
|
|
226
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
227
|
+
if (this.getProtocolType(uuid) === 'V2') {
|
|
228
|
+
yield this.protocolV2Links.sendFlowControl(uuid, () => this.createProtocolV2Adapter(uuid), name, data);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
yield this.callProtocolV1(uuid, name, data);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
184
234
|
callProtocolV1(uuid, name, data, options) {
|
|
235
|
+
var _a;
|
|
185
236
|
return __awaiter(this, void 0, void 0, function* () {
|
|
186
237
|
if (!this._messages) {
|
|
187
238
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
188
239
|
}
|
|
189
240
|
const messages = this._messages;
|
|
190
241
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
191
|
-
|
|
242
|
+
const isFirmwareUpload = name === 'FirmwareUpload';
|
|
243
|
+
const uploadStartedAt = Date.now();
|
|
244
|
+
const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
|
|
245
|
+
let sentBytes = 0;
|
|
246
|
+
let lastLoggedPercent = 0;
|
|
247
|
+
let lastLoggedAt = uploadStartedAt;
|
|
248
|
+
for (const [index, o] of buffers.entries()) {
|
|
192
249
|
const outData = o.toString('hex');
|
|
193
250
|
try {
|
|
194
|
-
yield this.plugin.send(uuid, outData);
|
|
251
|
+
yield this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
|
|
252
|
+
sentBytes += o.limit;
|
|
253
|
+
if (isFirmwareUpload) {
|
|
254
|
+
const now = Date.now();
|
|
255
|
+
const percent = Math.floor(((index + 1) / buffers.length) * 100);
|
|
256
|
+
if (shouldLogFirmwareUploadProgress({
|
|
257
|
+
percent,
|
|
258
|
+
lastLoggedPercent,
|
|
259
|
+
now,
|
|
260
|
+
lastLoggedAt,
|
|
261
|
+
})) {
|
|
262
|
+
const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
|
|
263
|
+
const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
|
|
264
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
|
|
265
|
+
`(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
|
|
266
|
+
`${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`);
|
|
267
|
+
lastLoggedPercent = percent;
|
|
268
|
+
lastLoggedAt = now;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
195
271
|
}
|
|
196
272
|
catch (e) {
|
|
197
273
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
@@ -204,6 +280,15 @@ class LowlevelTransport {
|
|
|
204
280
|
return check.call(jsonData);
|
|
205
281
|
}
|
|
206
282
|
catch (e) {
|
|
283
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
|
|
284
|
+
(options === null || options === void 0 ? void 0 : options.timeoutMs) !== PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
285
|
+
try {
|
|
286
|
+
yield this.resetConnectionAfterProbe(uuid, 'V1');
|
|
287
|
+
}
|
|
288
|
+
catch (resetError) {
|
|
289
|
+
this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
207
292
|
if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
208
293
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
209
294
|
}
|
|
@@ -229,12 +314,15 @@ class LowlevelTransport {
|
|
|
229
314
|
}
|
|
230
315
|
}
|
|
231
316
|
detectProtocol(uuid, expectedProtocol, protocolHint) {
|
|
232
|
-
var _a, _b, _c
|
|
317
|
+
var _a, _b, _c;
|
|
233
318
|
return __awaiter(this, void 0, void 0, function* () {
|
|
234
319
|
if (expectedProtocol === 'V2') {
|
|
235
|
-
this.
|
|
236
|
-
|
|
237
|
-
|
|
320
|
+
if (yield this.probeProtocolV2(uuid)) {
|
|
321
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
322
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
323
|
+
return 'V2';
|
|
324
|
+
}
|
|
325
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
238
326
|
}
|
|
239
327
|
if (expectedProtocol === 'V1') {
|
|
240
328
|
if (yield this.probeProtocolV1(uuid)) {
|
|
@@ -244,28 +332,17 @@ class LowlevelTransport {
|
|
|
244
332
|
}
|
|
245
333
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
246
334
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const protocolV1Detected = yield this.probeProtocolV1(uuid);
|
|
259
|
-
if (protocolV1Detected) {
|
|
260
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
261
|
-
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
262
|
-
return 'V1';
|
|
263
|
-
}
|
|
264
|
-
yield this.resetConnectionAfterProbe(uuid, 'V1');
|
|
265
|
-
if (yield this.probeProtocolV2(uuid)) {
|
|
266
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
267
|
-
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
|
|
268
|
-
return 'V2';
|
|
335
|
+
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
336
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
337
|
+
if (index > 0) {
|
|
338
|
+
yield this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
|
|
339
|
+
}
|
|
340
|
+
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
341
|
+
if (detected) {
|
|
342
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
343
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
344
|
+
return protocol;
|
|
345
|
+
}
|
|
269
346
|
}
|
|
270
347
|
this.deviceProtocol.delete(uuid);
|
|
271
348
|
throw this.createProtocolDetectionError();
|
|
@@ -373,7 +450,7 @@ class LowlevelTransport {
|
|
|
373
450
|
return __awaiter(this, void 0, void 0, function* () {
|
|
374
451
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
375
452
|
if (!assembler) {
|
|
376
|
-
assembler = new transport.ProtocolV2FrameAssembler();
|
|
453
|
+
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
377
454
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
378
455
|
}
|
|
379
456
|
const queuedFrame = assembler.push(new Uint8Array(0));
|
|
@@ -390,23 +467,25 @@ class LowlevelTransport {
|
|
|
390
467
|
return frame;
|
|
391
468
|
});
|
|
392
469
|
}
|
|
393
|
-
writeProtocolV2Frame(uuid, frame) {
|
|
470
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
394
471
|
return __awaiter(this, void 0, void 0, function* () {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
472
|
+
yield transport.writeProtocolV2BleFrame({
|
|
473
|
+
frame,
|
|
474
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
475
|
+
assertActive: assertCurrentGeneration,
|
|
476
|
+
signal: context.signal,
|
|
477
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
478
|
+
writePacket: packet => this.plugin.send(uuid, transport.bytesToHex(packet)),
|
|
479
|
+
});
|
|
399
480
|
});
|
|
400
481
|
}
|
|
401
482
|
callProtocolV2(uuid, name, data, options) {
|
|
402
|
-
var _a;
|
|
403
483
|
return __awaiter(this, void 0, void 0, function* () {
|
|
404
484
|
if (!this._messages || !this._messagesV2) {
|
|
405
485
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
406
486
|
}
|
|
407
|
-
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
408
487
|
try {
|
|
409
|
-
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data,
|
|
488
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
|
|
410
489
|
}
|
|
411
490
|
catch (e) {
|
|
412
491
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -431,10 +510,7 @@ class LowlevelTransport {
|
|
|
431
510
|
assertCurrentGeneration();
|
|
432
511
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
433
512
|
},
|
|
434
|
-
writeFrame: (frame) =>
|
|
435
|
-
assertCurrentGeneration();
|
|
436
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
437
|
-
},
|
|
513
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
438
514
|
readFrame: (context) => {
|
|
439
515
|
assertCurrentGeneration();
|
|
440
516
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
@@ -459,4 +535,6 @@ class LowlevelTransport {
|
|
|
459
535
|
}
|
|
460
536
|
}
|
|
461
537
|
|
|
462
|
-
|
|
538
|
+
exports["default"] = LowlevelTransport;
|
|
539
|
+
exports.getProtocolV1SendOptions = getProtocolV1SendOptions;
|
|
540
|
+
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.131",
|
|
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.131",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.131"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "81b472e983e80e6aa771b991dc9c0c9b1bdcf07b"
|
|
27
27
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import transport, {
|
|
|
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;
|
|
@@ -94,6 +119,8 @@ export default class LowlevelTransport {
|
|
|
94
119
|
},
|
|
95
120
|
});
|
|
96
121
|
|
|
122
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
123
|
+
|
|
97
124
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
98
125
|
return this.deviceProtocol.get(path);
|
|
99
126
|
}
|
|
@@ -112,10 +139,20 @@ export default class LowlevelTransport {
|
|
|
112
139
|
}
|
|
113
140
|
|
|
114
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;
|
|
115
148
|
this._messagesV2 = parseConfigure(signedData);
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
|
|
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
|
+
}
|
|
119
156
|
}
|
|
120
157
|
|
|
121
158
|
listen() {
|
|
@@ -150,16 +187,38 @@ export default class LowlevelTransport {
|
|
|
150
187
|
);
|
|
151
188
|
}
|
|
152
189
|
|
|
153
|
-
this.protocolV2Assemblers.set(
|
|
154
|
-
const protocolHint = input.expectedProtocol
|
|
155
|
-
? undefined
|
|
156
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
157
|
-
const protocolType = await this.detectProtocol(
|
|
190
|
+
this.protocolV2Assemblers.set(
|
|
158
191
|
input.uuid,
|
|
159
|
-
|
|
160
|
-
protocolHint
|
|
192
|
+
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
161
193
|
);
|
|
162
|
-
|
|
194
|
+
const protocolHint = input.expectedProtocol
|
|
195
|
+
? undefined
|
|
196
|
+
: input.protocolHint ?? this.deviceProtocolHints.get(input.uuid);
|
|
197
|
+
try {
|
|
198
|
+
const protocolType = await this.detectProtocol(
|
|
199
|
+
input.uuid,
|
|
200
|
+
input.expectedProtocol,
|
|
201
|
+
protocolHint
|
|
202
|
+
);
|
|
203
|
+
return { uuid: input.uuid, protocolType };
|
|
204
|
+
} catch (error) {
|
|
205
|
+
try {
|
|
206
|
+
await this.protocolV2Links.invalidateLink(input.uuid, 'Lowlevel transport acquire failed');
|
|
207
|
+
} catch (cleanupError) {
|
|
208
|
+
this.Log?.debug('[LowlevelTransport] acquire link cleanup failed:', cleanupError);
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
await this.plugin.disconnect(input.uuid);
|
|
212
|
+
} catch (cleanupError) {
|
|
213
|
+
this.Log?.debug('[LowlevelTransport] acquire disconnect failed:', cleanupError);
|
|
214
|
+
} finally {
|
|
215
|
+
this.connectedDevices.delete(input.uuid);
|
|
216
|
+
this.deviceProtocol.delete(input.uuid);
|
|
217
|
+
this.protocolV2Assemblers.delete(input.uuid);
|
|
218
|
+
this.advanceProtocolV2Generation(input.uuid);
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
163
222
|
}
|
|
164
223
|
|
|
165
224
|
async release(uuid: string) {
|
|
@@ -168,8 +227,8 @@ export default class LowlevelTransport {
|
|
|
168
227
|
await this.plugin.disconnect(uuid);
|
|
169
228
|
this.connectedDevices.delete(uuid);
|
|
170
229
|
this.deviceProtocol.delete(uuid);
|
|
171
|
-
//
|
|
172
|
-
//
|
|
230
|
+
// A name-derived protocol hint survives disconnect and lets fast reconnect probe
|
|
231
|
+
// Protocol V2 first without sending a redundant V1 Initialize.
|
|
173
232
|
this.protocolV2Assemblers.delete(uuid);
|
|
174
233
|
return true;
|
|
175
234
|
} catch (error) {
|
|
@@ -195,8 +254,6 @@ export default class LowlevelTransport {
|
|
|
195
254
|
`Device protocol has not been detected for ${uuid}`
|
|
196
255
|
);
|
|
197
256
|
}
|
|
198
|
-
this.Log.debug('transport call', { name, protocol });
|
|
199
|
-
|
|
200
257
|
if (protocol === 'V2') {
|
|
201
258
|
return this.callProtocolV2(uuid, name, data, options);
|
|
202
259
|
}
|
|
@@ -204,6 +261,19 @@ export default class LowlevelTransport {
|
|
|
204
261
|
return this.callProtocolV1(uuid, name, data, options);
|
|
205
262
|
}
|
|
206
263
|
|
|
264
|
+
async post(uuid: string, name: string, data: Record<string, unknown>) {
|
|
265
|
+
if (this.getProtocolType(uuid) === 'V2') {
|
|
266
|
+
await this.protocolV2Links.sendFlowControl(
|
|
267
|
+
uuid,
|
|
268
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
269
|
+
name,
|
|
270
|
+
data
|
|
271
|
+
);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
await this.callProtocolV1(uuid, name, data);
|
|
275
|
+
}
|
|
276
|
+
|
|
207
277
|
private async callProtocolV1(
|
|
208
278
|
uuid: string,
|
|
209
279
|
name: string,
|
|
@@ -216,10 +286,41 @@ export default class LowlevelTransport {
|
|
|
216
286
|
|
|
217
287
|
const messages = this._messages;
|
|
218
288
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
219
|
-
|
|
289
|
+
const isFirmwareUpload = name === 'FirmwareUpload';
|
|
290
|
+
const uploadStartedAt = Date.now();
|
|
291
|
+
const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
|
|
292
|
+
let sentBytes = 0;
|
|
293
|
+
let lastLoggedPercent = 0;
|
|
294
|
+
let lastLoggedAt = uploadStartedAt;
|
|
295
|
+
|
|
296
|
+
for (const [index, o] of buffers.entries()) {
|
|
220
297
|
const outData = o.toString('hex');
|
|
221
298
|
try {
|
|
222
|
-
await this.plugin.send(uuid, outData);
|
|
299
|
+
await this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
|
|
300
|
+
sentBytes += o.limit;
|
|
301
|
+
|
|
302
|
+
if (isFirmwareUpload) {
|
|
303
|
+
const now = Date.now();
|
|
304
|
+
const percent = Math.floor(((index + 1) / buffers.length) * 100);
|
|
305
|
+
if (
|
|
306
|
+
shouldLogFirmwareUploadProgress({
|
|
307
|
+
percent,
|
|
308
|
+
lastLoggedPercent,
|
|
309
|
+
now,
|
|
310
|
+
lastLoggedAt,
|
|
311
|
+
})
|
|
312
|
+
) {
|
|
313
|
+
const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
|
|
314
|
+
const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
|
|
315
|
+
this.Log?.debug(
|
|
316
|
+
`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
|
|
317
|
+
`(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
|
|
318
|
+
`${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`
|
|
319
|
+
);
|
|
320
|
+
lastLoggedPercent = percent;
|
|
321
|
+
lastLoggedAt = now;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
223
324
|
} catch (e) {
|
|
224
325
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
225
326
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
|
|
@@ -231,6 +332,16 @@ export default class LowlevelTransport {
|
|
|
231
332
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
232
333
|
return check.call(jsonData);
|
|
233
334
|
} catch (e) {
|
|
335
|
+
if (
|
|
336
|
+
e?.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
337
|
+
options?.timeoutMs !== PROTOCOL_PROBE_TIMEOUT_MS
|
|
338
|
+
) {
|
|
339
|
+
try {
|
|
340
|
+
await this.resetConnectionAfterProbe(uuid, 'V1');
|
|
341
|
+
} catch (resetError) {
|
|
342
|
+
this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
234
345
|
if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
235
346
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
236
347
|
} else {
|
|
@@ -273,12 +384,12 @@ export default class LowlevelTransport {
|
|
|
273
384
|
protocolHint?: ProtocolType
|
|
274
385
|
): Promise<ProtocolType> {
|
|
275
386
|
if (expectedProtocol === 'V2') {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
387
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
388
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
389
|
+
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
390
|
+
return 'V2';
|
|
391
|
+
}
|
|
392
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
282
393
|
}
|
|
283
394
|
|
|
284
395
|
if (expectedProtocol === 'V1') {
|
|
@@ -290,31 +401,20 @@ export default class LowlevelTransport {
|
|
|
290
401
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
291
402
|
}
|
|
292
403
|
|
|
293
|
-
|
|
294
|
-
this.deviceProtocol.
|
|
295
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
|
|
296
|
-
return 'V2';
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
300
|
-
if (cachedProtocol === 'V2' && (await this.probeProtocolV2(uuid))) {
|
|
301
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
302
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
|
|
303
|
-
return 'V2';
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const protocolV1Detected = await this.probeProtocolV1(uuid);
|
|
307
|
-
if (protocolV1Detected) {
|
|
308
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
309
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
310
|
-
return 'V1';
|
|
311
|
-
}
|
|
404
|
+
const probeOrder: ProtocolType[] =
|
|
405
|
+
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
312
406
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
407
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
408
|
+
if (index > 0) {
|
|
409
|
+
await this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
|
|
410
|
+
}
|
|
411
|
+
const detected =
|
|
412
|
+
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
413
|
+
if (detected) {
|
|
414
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
415
|
+
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
416
|
+
return protocol;
|
|
417
|
+
}
|
|
318
418
|
}
|
|
319
419
|
|
|
320
420
|
this.deviceProtocol.delete(uuid);
|
|
@@ -431,7 +531,7 @@ export default class LowlevelTransport {
|
|
|
431
531
|
private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
|
|
432
532
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
433
533
|
if (!assembler) {
|
|
434
|
-
assembler = new ProtocolV2FrameAssembler();
|
|
534
|
+
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
435
535
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
436
536
|
}
|
|
437
537
|
|
|
@@ -449,11 +549,20 @@ export default class LowlevelTransport {
|
|
|
449
549
|
return frame;
|
|
450
550
|
}
|
|
451
551
|
|
|
452
|
-
private async writeProtocolV2Frame(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
552
|
+
private async writeProtocolV2Frame(
|
|
553
|
+
uuid: string,
|
|
554
|
+
frame: Uint8Array,
|
|
555
|
+
context: ProtocolV2CallContext,
|
|
556
|
+
assertCurrentGeneration: () => void
|
|
557
|
+
) {
|
|
558
|
+
await writeProtocolV2BleFrame({
|
|
559
|
+
frame,
|
|
560
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
561
|
+
assertActive: assertCurrentGeneration,
|
|
562
|
+
signal: context.signal,
|
|
563
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
564
|
+
writePacket: packet => this.plugin.send(uuid, bytesToHex(packet)),
|
|
565
|
+
});
|
|
457
566
|
}
|
|
458
567
|
|
|
459
568
|
private async callProtocolV2(
|
|
@@ -466,18 +575,13 @@ export default class LowlevelTransport {
|
|
|
466
575
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
467
576
|
}
|
|
468
577
|
|
|
469
|
-
const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
470
|
-
|
|
471
578
|
try {
|
|
472
579
|
return await this.protocolV2Links.call(
|
|
473
580
|
uuid,
|
|
474
581
|
() => this.createProtocolV2Adapter(uuid),
|
|
475
582
|
name,
|
|
476
583
|
data,
|
|
477
|
-
|
|
478
|
-
...options,
|
|
479
|
-
timeoutMs,
|
|
480
|
-
}
|
|
584
|
+
options
|
|
481
585
|
);
|
|
482
586
|
} catch (e) {
|
|
483
587
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -501,10 +605,8 @@ export default class LowlevelTransport {
|
|
|
501
605
|
assertCurrentGeneration();
|
|
502
606
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
503
607
|
},
|
|
504
|
-
writeFrame: (frame: Uint8Array) =>
|
|
505
|
-
assertCurrentGeneration
|
|
506
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
507
|
-
},
|
|
608
|
+
writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
|
|
609
|
+
this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
508
610
|
readFrame: (context: { messageName: string; timeoutMs?: number }) => {
|
|
509
611
|
assertCurrentGeneration();
|
|
510
612
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|