@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.9 → 1.2.0-alpha.91
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 +118 -14
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -57
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +129 -74
- 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
|
+
highThroughput: 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' }],
|
|
@@ -253,15 +313,19 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
253
313
|
});
|
|
254
314
|
|
|
255
315
|
test('reuses the active generation when Core acquires the same BLE connection again', async () => {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
)
|
|
261
325
|
);
|
|
262
326
|
const plugin = createPlugin({
|
|
263
327
|
devices: [{ id: 'repeated-acquire-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
264
|
-
responses
|
|
328
|
+
responses,
|
|
265
329
|
});
|
|
266
330
|
const lowlevel = configureTransport(plugin);
|
|
267
331
|
|
|
@@ -283,13 +347,19 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
283
347
|
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
284
348
|
Number.parseInt(hex.slice(12, 14), 16)
|
|
285
349
|
);
|
|
286
|
-
expect(sentSeqs).toEqual([1, 2]);
|
|
350
|
+
expect(sentSeqs).toEqual([1, 2, 3, 4]);
|
|
287
351
|
});
|
|
288
352
|
|
|
289
|
-
test('
|
|
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
|
+
);
|
|
290
360
|
const plugin = createPlugin({
|
|
291
361
|
devices: [{ id: 'bootloader-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
292
|
-
responses: [],
|
|
362
|
+
responses: [bytesToHex(probeResponse)],
|
|
293
363
|
});
|
|
294
364
|
const lowlevel = configureTransport(plugin);
|
|
295
365
|
|
|
@@ -299,8 +369,8 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
299
369
|
uuid: 'bootloader-v2-id',
|
|
300
370
|
protocolType: 'V2',
|
|
301
371
|
});
|
|
302
|
-
expect(plugin.send).
|
|
303
|
-
expect(plugin.receive).
|
|
372
|
+
expect(plugin.send).toHaveBeenCalledTimes(1);
|
|
373
|
+
expect(plugin.receive).toHaveBeenCalledTimes(1);
|
|
304
374
|
});
|
|
305
375
|
|
|
306
376
|
test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
|
|
@@ -342,11 +412,23 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
342
412
|
});
|
|
343
413
|
|
|
344
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
|
+
);
|
|
345
421
|
const plugin = createPlugin({
|
|
346
422
|
devices: [{ id: 'timeout-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
347
|
-
responses: [],
|
|
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(() => {});
|
|
348
431
|
});
|
|
349
|
-
plugin.receive.mockImplementation(() => new Promise(() => {}));
|
|
350
432
|
const lowlevel = configureTransport(plugin);
|
|
351
433
|
|
|
352
434
|
await lowlevel.acquire({ uuid: 'timeout-v2-id', expectedProtocol: 'V2' });
|
|
@@ -358,6 +440,28 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
358
440
|
expect(plugin.disconnect).toHaveBeenCalledWith('timeout-v2-id');
|
|
359
441
|
});
|
|
360
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
|
+
|
|
361
465
|
test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
|
|
362
466
|
const plugin = createPlugin({
|
|
363
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;
|
|
@@ -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;
|
|
@@ -51,4 +62,4 @@ declare class LowlevelTransport {
|
|
|
51
62
|
cancel(): void;
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
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,SAYN,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,YAAY,
|
|
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
|
}
|
|
@@ -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;
|
|
130
149
|
return __awaiter(this, void 0, void 0, function* () {
|
|
131
150
|
const alreadyConnected = this.connectedDevices.has(input.uuid);
|
|
132
151
|
try {
|
|
@@ -141,10 +160,10 @@ 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);
|
|
166
|
+
: (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(input.uuid);
|
|
148
167
|
const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
|
|
149
168
|
return { uuid: input.uuid, protocolType };
|
|
150
169
|
});
|
|
@@ -174,12 +193,7 @@ class LowlevelTransport {
|
|
|
174
193
|
if (!protocol) {
|
|
175
194
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
176
195
|
}
|
|
177
|
-
|
|
178
|
-
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' protocol: ', protocol);
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
|
|
182
|
-
}
|
|
196
|
+
this.Log.debug('transport call', { name, protocol });
|
|
183
197
|
if (protocol === 'V2') {
|
|
184
198
|
return this.callProtocolV2(uuid, name, data, options);
|
|
185
199
|
}
|
|
@@ -187,17 +201,42 @@ class LowlevelTransport {
|
|
|
187
201
|
});
|
|
188
202
|
}
|
|
189
203
|
callProtocolV1(uuid, name, data, options) {
|
|
204
|
+
var _a;
|
|
190
205
|
return __awaiter(this, void 0, void 0, function* () {
|
|
191
206
|
if (!this._messages) {
|
|
192
207
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
193
208
|
}
|
|
194
209
|
const messages = this._messages;
|
|
195
210
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
196
|
-
|
|
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()) {
|
|
197
218
|
const outData = o.toString('hex');
|
|
198
|
-
this.Log.debug('send hex strting: ', outData);
|
|
199
219
|
try {
|
|
200
|
-
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
|
+
}
|
|
201
240
|
}
|
|
202
241
|
catch (e) {
|
|
203
242
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
@@ -206,11 +245,19 @@ class LowlevelTransport {
|
|
|
206
245
|
}
|
|
207
246
|
try {
|
|
208
247
|
const response = yield this.readProtocolV1Message(uuid, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
209
|
-
this.Log.debug('receive data: ', response);
|
|
210
248
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
211
249
|
return check.call(jsonData);
|
|
212
250
|
}
|
|
213
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
|
+
}
|
|
214
261
|
if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
215
262
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
216
263
|
}
|
|
@@ -236,12 +283,15 @@ class LowlevelTransport {
|
|
|
236
283
|
}
|
|
237
284
|
}
|
|
238
285
|
detectProtocol(uuid, expectedProtocol, protocolHint) {
|
|
239
|
-
var _a, _b, _c
|
|
286
|
+
var _a, _b, _c;
|
|
240
287
|
return __awaiter(this, void 0, void 0, function* () {
|
|
241
288
|
if (expectedProtocol === 'V2') {
|
|
242
|
-
this.
|
|
243
|
-
|
|
244
|
-
|
|
289
|
+
if (yield this.probeProtocolV2(uuid)) {
|
|
290
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
291
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
292
|
+
return 'V2';
|
|
293
|
+
}
|
|
294
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
245
295
|
}
|
|
246
296
|
if (expectedProtocol === 'V1') {
|
|
247
297
|
if (yield this.probeProtocolV1(uuid)) {
|
|
@@ -251,28 +301,17 @@ class LowlevelTransport {
|
|
|
251
301
|
}
|
|
252
302
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
253
303
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const protocolV1Detected = yield this.probeProtocolV1(uuid);
|
|
266
|
-
if (protocolV1Detected) {
|
|
267
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
268
|
-
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
269
|
-
return 'V1';
|
|
270
|
-
}
|
|
271
|
-
yield this.resetConnectionAfterProbe(uuid, 'V1');
|
|
272
|
-
if (yield this.probeProtocolV2(uuid)) {
|
|
273
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
274
|
-
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
|
|
275
|
-
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
|
+
}
|
|
276
315
|
}
|
|
277
316
|
this.deviceProtocol.delete(uuid);
|
|
278
317
|
throw this.createProtocolDetectionError();
|
|
@@ -380,7 +419,7 @@ class LowlevelTransport {
|
|
|
380
419
|
return __awaiter(this, void 0, void 0, function* () {
|
|
381
420
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
382
421
|
if (!assembler) {
|
|
383
|
-
assembler = new transport.ProtocolV2FrameAssembler();
|
|
422
|
+
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
384
423
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
385
424
|
}
|
|
386
425
|
const queuedFrame = assembler.push(new Uint8Array(0));
|
|
@@ -397,23 +436,25 @@ class LowlevelTransport {
|
|
|
397
436
|
return frame;
|
|
398
437
|
});
|
|
399
438
|
}
|
|
400
|
-
writeProtocolV2Frame(uuid, frame) {
|
|
439
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
401
440
|
return __awaiter(this, void 0, void 0, function* () {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
+
});
|
|
406
449
|
});
|
|
407
450
|
}
|
|
408
451
|
callProtocolV2(uuid, name, data, options) {
|
|
409
|
-
var _a;
|
|
410
452
|
return __awaiter(this, void 0, void 0, function* () {
|
|
411
453
|
if (!this._messages || !this._messagesV2) {
|
|
412
454
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
413
455
|
}
|
|
414
|
-
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
415
456
|
try {
|
|
416
|
-
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data,
|
|
457
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
|
|
417
458
|
}
|
|
418
459
|
catch (e) {
|
|
419
460
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -438,10 +479,7 @@ class LowlevelTransport {
|
|
|
438
479
|
assertCurrentGeneration();
|
|
439
480
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
440
481
|
},
|
|
441
|
-
writeFrame: (frame) =>
|
|
442
|
-
assertCurrentGeneration();
|
|
443
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
444
|
-
},
|
|
482
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
445
483
|
readFrame: (context) => {
|
|
446
484
|
assertCurrentGeneration();
|
|
447
485
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
@@ -466,4 +504,6 @@ class LowlevelTransport {
|
|
|
466
504
|
}
|
|
467
505
|
}
|
|
468
506
|
|
|
469
|
-
|
|
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.91",
|
|
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.91",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.91"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "4a314a4b114a3aa01dd92e1d0a227e1c5ccbd4f3"
|
|
27
27
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import transport, {
|
|
3
|
-
LogBlockCommand,
|
|
4
3
|
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
5
4
|
PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
6
5
|
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
@@ -11,6 +10,7 @@ import transport, {
|
|
|
11
10
|
hexToBytes,
|
|
12
11
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
13
12
|
withProtocolTimeout,
|
|
13
|
+
writeProtocolV2BleFrame,
|
|
14
14
|
} from '@onekeyfe/hd-transport';
|
|
15
15
|
|
|
16
16
|
import type EventEmitter from 'events';
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
LowLevelDevice,
|
|
19
19
|
LowlevelTransportSharedPlugin,
|
|
20
20
|
ProtocolType,
|
|
21
|
+
ProtocolV2CallContext,
|
|
21
22
|
TransportCallOptions,
|
|
22
23
|
} from '@onekeyfe/hd-transport';
|
|
23
24
|
import type { LowLevelAcquireInput } from './types';
|
|
@@ -26,8 +27,31 @@ const { check, ProtocolV1, parseConfigure } = transport;
|
|
|
26
27
|
|
|
27
28
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
28
29
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
29
|
-
const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30_000;
|
|
30
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
|
+
}
|
|
31
55
|
|
|
32
56
|
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
33
57
|
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
@@ -95,6 +119,8 @@ export default class LowlevelTransport {
|
|
|
95
119
|
},
|
|
96
120
|
});
|
|
97
121
|
|
|
122
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
123
|
+
|
|
98
124
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
99
125
|
return this.deviceProtocol.get(path);
|
|
100
126
|
}
|
|
@@ -113,10 +139,20 @@ export default class LowlevelTransport {
|
|
|
113
139
|
}
|
|
114
140
|
|
|
115
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;
|
|
116
148
|
this._messagesV2 = parseConfigure(signedData);
|
|
117
|
-
this.
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
listen() {
|
|
@@ -151,10 +187,13 @@ export default class LowlevelTransport {
|
|
|
151
187
|
);
|
|
152
188
|
}
|
|
153
189
|
|
|
154
|
-
this.protocolV2Assemblers.set(
|
|
190
|
+
this.protocolV2Assemblers.set(
|
|
191
|
+
input.uuid,
|
|
192
|
+
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
193
|
+
);
|
|
155
194
|
const protocolHint = input.expectedProtocol
|
|
156
195
|
? undefined
|
|
157
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
196
|
+
: input.protocolHint ?? this.deviceProtocolHints.get(input.uuid);
|
|
158
197
|
const protocolType = await this.detectProtocol(
|
|
159
198
|
input.uuid,
|
|
160
199
|
input.expectedProtocol,
|
|
@@ -169,8 +208,8 @@ export default class LowlevelTransport {
|
|
|
169
208
|
await this.plugin.disconnect(uuid);
|
|
170
209
|
this.connectedDevices.delete(uuid);
|
|
171
210
|
this.deviceProtocol.delete(uuid);
|
|
172
|
-
//
|
|
173
|
-
//
|
|
211
|
+
// A name-derived protocol hint survives disconnect and lets fast reconnect probe
|
|
212
|
+
// Protocol V2 first without sending a redundant V1 Initialize.
|
|
174
213
|
this.protocolV2Assemblers.delete(uuid);
|
|
175
214
|
return true;
|
|
176
215
|
} catch (error) {
|
|
@@ -196,20 +235,7 @@ export default class LowlevelTransport {
|
|
|
196
235
|
`Device protocol has not been detected for ${uuid}`
|
|
197
236
|
);
|
|
198
237
|
}
|
|
199
|
-
|
|
200
|
-
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' protocol: ', protocol);
|
|
201
|
-
} else {
|
|
202
|
-
this.Log.debug(
|
|
203
|
-
'lowlevel-transport',
|
|
204
|
-
'call-',
|
|
205
|
-
' name: ',
|
|
206
|
-
name,
|
|
207
|
-
' data: ',
|
|
208
|
-
data,
|
|
209
|
-
' protocol: ',
|
|
210
|
-
protocol
|
|
211
|
-
);
|
|
212
|
-
}
|
|
238
|
+
this.Log.debug('transport call', { name, protocol });
|
|
213
239
|
|
|
214
240
|
if (protocol === 'V2') {
|
|
215
241
|
return this.callProtocolV2(uuid, name, data, options);
|
|
@@ -230,12 +256,41 @@ export default class LowlevelTransport {
|
|
|
230
256
|
|
|
231
257
|
const messages = this._messages;
|
|
232
258
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
233
|
-
|
|
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()) {
|
|
234
267
|
const outData = o.toString('hex');
|
|
235
|
-
// Upload resources on low-end phones may OOM
|
|
236
|
-
this.Log.debug('send hex strting: ', outData);
|
|
237
268
|
try {
|
|
238
|
-
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
|
+
}
|
|
239
294
|
} catch (e) {
|
|
240
295
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
241
296
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
|
|
@@ -244,10 +299,19 @@ export default class LowlevelTransport {
|
|
|
244
299
|
|
|
245
300
|
try {
|
|
246
301
|
const response = await this.readProtocolV1Message(uuid, options?.timeoutMs);
|
|
247
|
-
this.Log.debug('receive data: ', response);
|
|
248
302
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
249
303
|
return check.call(jsonData);
|
|
250
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
|
+
}
|
|
251
315
|
if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
252
316
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
253
317
|
} else {
|
|
@@ -290,12 +354,12 @@ export default class LowlevelTransport {
|
|
|
290
354
|
protocolHint?: ProtocolType
|
|
291
355
|
): Promise<ProtocolType> {
|
|
292
356
|
if (expectedProtocol === 'V2') {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
357
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
358
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
359
|
+
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
360
|
+
return 'V2';
|
|
361
|
+
}
|
|
362
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
299
363
|
}
|
|
300
364
|
|
|
301
365
|
if (expectedProtocol === 'V1') {
|
|
@@ -307,31 +371,20 @@ export default class LowlevelTransport {
|
|
|
307
371
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
308
372
|
}
|
|
309
373
|
|
|
310
|
-
|
|
311
|
-
this.deviceProtocol.
|
|
312
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
|
|
313
|
-
return 'V2';
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
317
|
-
if (cachedProtocol === 'V2' && (await this.probeProtocolV2(uuid))) {
|
|
318
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
319
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
|
|
320
|
-
return 'V2';
|
|
321
|
-
}
|
|
374
|
+
const probeOrder: ProtocolType[] =
|
|
375
|
+
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
322
376
|
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return 'V2';
|
|
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
|
+
}
|
|
335
388
|
}
|
|
336
389
|
|
|
337
390
|
this.deviceProtocol.delete(uuid);
|
|
@@ -448,7 +501,7 @@ export default class LowlevelTransport {
|
|
|
448
501
|
private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
|
|
449
502
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
450
503
|
if (!assembler) {
|
|
451
|
-
assembler = new ProtocolV2FrameAssembler();
|
|
504
|
+
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
452
505
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
453
506
|
}
|
|
454
507
|
|
|
@@ -466,11 +519,20 @@ export default class LowlevelTransport {
|
|
|
466
519
|
return frame;
|
|
467
520
|
}
|
|
468
521
|
|
|
469
|
-
private async writeProtocolV2Frame(
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
+
});
|
|
474
536
|
}
|
|
475
537
|
|
|
476
538
|
private async callProtocolV2(
|
|
@@ -483,18 +545,13 @@ export default class LowlevelTransport {
|
|
|
483
545
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
484
546
|
}
|
|
485
547
|
|
|
486
|
-
const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
487
|
-
|
|
488
548
|
try {
|
|
489
549
|
return await this.protocolV2Links.call(
|
|
490
550
|
uuid,
|
|
491
551
|
() => this.createProtocolV2Adapter(uuid),
|
|
492
552
|
name,
|
|
493
553
|
data,
|
|
494
|
-
|
|
495
|
-
...options,
|
|
496
|
-
timeoutMs,
|
|
497
|
-
}
|
|
554
|
+
options
|
|
498
555
|
);
|
|
499
556
|
} catch (e) {
|
|
500
557
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -518,10 +575,8 @@ export default class LowlevelTransport {
|
|
|
518
575
|
assertCurrentGeneration();
|
|
519
576
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
520
577
|
},
|
|
521
|
-
writeFrame: (frame: Uint8Array) =>
|
|
522
|
-
assertCurrentGeneration
|
|
523
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
524
|
-
},
|
|
578
|
+
writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
|
|
579
|
+
this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
525
580
|
readFrame: (context: { messageName: string; timeoutMs?: number }) => {
|
|
526
581
|
assertCurrentGeneration();
|
|
527
582
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|