@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.11 → 1.2.0-alpha.111
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 +96 -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 +128 -75
- 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;YAoBlB,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,6 @@ 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
|
-
if (transport.LogBlockCommand.has(name)) {
|
|
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
|
-
}
|
|
183
196
|
if (protocol === 'V2') {
|
|
184
197
|
return this.callProtocolV2(uuid, name, data, options);
|
|
185
198
|
}
|
|
@@ -187,17 +200,42 @@ class LowlevelTransport {
|
|
|
187
200
|
});
|
|
188
201
|
}
|
|
189
202
|
callProtocolV1(uuid, name, data, options) {
|
|
203
|
+
var _a;
|
|
190
204
|
return __awaiter(this, void 0, void 0, function* () {
|
|
191
205
|
if (!this._messages) {
|
|
192
206
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
193
207
|
}
|
|
194
208
|
const messages = this._messages;
|
|
195
209
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
196
|
-
|
|
210
|
+
const isFirmwareUpload = name === 'FirmwareUpload';
|
|
211
|
+
const uploadStartedAt = Date.now();
|
|
212
|
+
const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
|
|
213
|
+
let sentBytes = 0;
|
|
214
|
+
let lastLoggedPercent = 0;
|
|
215
|
+
let lastLoggedAt = uploadStartedAt;
|
|
216
|
+
for (const [index, o] of buffers.entries()) {
|
|
197
217
|
const outData = o.toString('hex');
|
|
198
|
-
this.Log.debug('send hex strting: ', outData);
|
|
199
218
|
try {
|
|
200
|
-
yield this.plugin.send(uuid, outData);
|
|
219
|
+
yield this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
|
|
220
|
+
sentBytes += o.limit;
|
|
221
|
+
if (isFirmwareUpload) {
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
const percent = Math.floor(((index + 1) / buffers.length) * 100);
|
|
224
|
+
if (shouldLogFirmwareUploadProgress({
|
|
225
|
+
percent,
|
|
226
|
+
lastLoggedPercent,
|
|
227
|
+
now,
|
|
228
|
+
lastLoggedAt,
|
|
229
|
+
})) {
|
|
230
|
+
const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
|
|
231
|
+
const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
|
|
232
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
|
|
233
|
+
`(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
|
|
234
|
+
`${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`);
|
|
235
|
+
lastLoggedPercent = percent;
|
|
236
|
+
lastLoggedAt = now;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
201
239
|
}
|
|
202
240
|
catch (e) {
|
|
203
241
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
@@ -206,11 +244,19 @@ class LowlevelTransport {
|
|
|
206
244
|
}
|
|
207
245
|
try {
|
|
208
246
|
const response = yield this.readProtocolV1Message(uuid, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
209
|
-
this.Log.debug('receive data: ', response);
|
|
210
247
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
211
248
|
return check.call(jsonData);
|
|
212
249
|
}
|
|
213
250
|
catch (e) {
|
|
251
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
|
|
252
|
+
(options === null || options === void 0 ? void 0 : options.timeoutMs) !== PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
253
|
+
try {
|
|
254
|
+
yield this.resetConnectionAfterProbe(uuid, 'V1');
|
|
255
|
+
}
|
|
256
|
+
catch (resetError) {
|
|
257
|
+
this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
214
260
|
if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
215
261
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
216
262
|
}
|
|
@@ -236,12 +282,15 @@ class LowlevelTransport {
|
|
|
236
282
|
}
|
|
237
283
|
}
|
|
238
284
|
detectProtocol(uuid, expectedProtocol, protocolHint) {
|
|
239
|
-
var _a, _b, _c
|
|
285
|
+
var _a, _b, _c;
|
|
240
286
|
return __awaiter(this, void 0, void 0, function* () {
|
|
241
287
|
if (expectedProtocol === 'V2') {
|
|
242
|
-
this.
|
|
243
|
-
|
|
244
|
-
|
|
288
|
+
if (yield this.probeProtocolV2(uuid)) {
|
|
289
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
290
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
291
|
+
return 'V2';
|
|
292
|
+
}
|
|
293
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
245
294
|
}
|
|
246
295
|
if (expectedProtocol === 'V1') {
|
|
247
296
|
if (yield this.probeProtocolV1(uuid)) {
|
|
@@ -251,28 +300,17 @@ class LowlevelTransport {
|
|
|
251
300
|
}
|
|
252
301
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
253
302
|
}
|
|
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';
|
|
303
|
+
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
304
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
305
|
+
if (index > 0) {
|
|
306
|
+
yield this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
|
|
307
|
+
}
|
|
308
|
+
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
309
|
+
if (detected) {
|
|
310
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
311
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
312
|
+
return protocol;
|
|
313
|
+
}
|
|
276
314
|
}
|
|
277
315
|
this.deviceProtocol.delete(uuid);
|
|
278
316
|
throw this.createProtocolDetectionError();
|
|
@@ -380,7 +418,7 @@ class LowlevelTransport {
|
|
|
380
418
|
return __awaiter(this, void 0, void 0, function* () {
|
|
381
419
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
382
420
|
if (!assembler) {
|
|
383
|
-
assembler = new transport.ProtocolV2FrameAssembler();
|
|
421
|
+
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
384
422
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
385
423
|
}
|
|
386
424
|
const queuedFrame = assembler.push(new Uint8Array(0));
|
|
@@ -397,23 +435,25 @@ class LowlevelTransport {
|
|
|
397
435
|
return frame;
|
|
398
436
|
});
|
|
399
437
|
}
|
|
400
|
-
writeProtocolV2Frame(uuid, frame) {
|
|
438
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
401
439
|
return __awaiter(this, void 0, void 0, function* () {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
440
|
+
yield transport.writeProtocolV2BleFrame({
|
|
441
|
+
frame,
|
|
442
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
443
|
+
assertActive: assertCurrentGeneration,
|
|
444
|
+
signal: context.signal,
|
|
445
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
446
|
+
writePacket: packet => this.plugin.send(uuid, transport.bytesToHex(packet)),
|
|
447
|
+
});
|
|
406
448
|
});
|
|
407
449
|
}
|
|
408
450
|
callProtocolV2(uuid, name, data, options) {
|
|
409
|
-
var _a;
|
|
410
451
|
return __awaiter(this, void 0, void 0, function* () {
|
|
411
452
|
if (!this._messages || !this._messagesV2) {
|
|
412
453
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
413
454
|
}
|
|
414
|
-
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
415
455
|
try {
|
|
416
|
-
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data,
|
|
456
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
|
|
417
457
|
}
|
|
418
458
|
catch (e) {
|
|
419
459
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -438,10 +478,7 @@ class LowlevelTransport {
|
|
|
438
478
|
assertCurrentGeneration();
|
|
439
479
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
440
480
|
},
|
|
441
|
-
writeFrame: (frame) =>
|
|
442
|
-
assertCurrentGeneration();
|
|
443
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
444
|
-
},
|
|
481
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
445
482
|
readFrame: (context) => {
|
|
446
483
|
assertCurrentGeneration();
|
|
447
484
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
@@ -466,4 +503,6 @@ class LowlevelTransport {
|
|
|
466
503
|
}
|
|
467
504
|
}
|
|
468
505
|
|
|
469
|
-
|
|
506
|
+
exports["default"] = LowlevelTransport;
|
|
507
|
+
exports.getProtocolV1SendOptions = getProtocolV1SendOptions;
|
|
508
|
+
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.111",
|
|
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.111",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.111"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "7619f316471ad423fb83f68e82e1aec89f34ac0a"
|
|
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,21 +235,6 @@ export default class LowlevelTransport {
|
|
|
196
235
|
`Device protocol has not been detected for ${uuid}`
|
|
197
236
|
);
|
|
198
237
|
}
|
|
199
|
-
if (LogBlockCommand.has(name)) {
|
|
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
|
-
}
|
|
213
|
-
|
|
214
238
|
if (protocol === 'V2') {
|
|
215
239
|
return this.callProtocolV2(uuid, name, data, options);
|
|
216
240
|
}
|
|
@@ -230,12 +254,41 @@ export default class LowlevelTransport {
|
|
|
230
254
|
|
|
231
255
|
const messages = this._messages;
|
|
232
256
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
233
|
-
|
|
257
|
+
const isFirmwareUpload = name === 'FirmwareUpload';
|
|
258
|
+
const uploadStartedAt = Date.now();
|
|
259
|
+
const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
|
|
260
|
+
let sentBytes = 0;
|
|
261
|
+
let lastLoggedPercent = 0;
|
|
262
|
+
let lastLoggedAt = uploadStartedAt;
|
|
263
|
+
|
|
264
|
+
for (const [index, o] of buffers.entries()) {
|
|
234
265
|
const outData = o.toString('hex');
|
|
235
|
-
// Upload resources on low-end phones may OOM
|
|
236
|
-
this.Log.debug('send hex strting: ', outData);
|
|
237
266
|
try {
|
|
238
|
-
await this.plugin.send(uuid, outData);
|
|
267
|
+
await this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
|
|
268
|
+
sentBytes += o.limit;
|
|
269
|
+
|
|
270
|
+
if (isFirmwareUpload) {
|
|
271
|
+
const now = Date.now();
|
|
272
|
+
const percent = Math.floor(((index + 1) / buffers.length) * 100);
|
|
273
|
+
if (
|
|
274
|
+
shouldLogFirmwareUploadProgress({
|
|
275
|
+
percent,
|
|
276
|
+
lastLoggedPercent,
|
|
277
|
+
now,
|
|
278
|
+
lastLoggedAt,
|
|
279
|
+
})
|
|
280
|
+
) {
|
|
281
|
+
const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
|
|
282
|
+
const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
|
|
283
|
+
this.Log?.debug(
|
|
284
|
+
`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
|
|
285
|
+
`(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
|
|
286
|
+
`${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`
|
|
287
|
+
);
|
|
288
|
+
lastLoggedPercent = percent;
|
|
289
|
+
lastLoggedAt = now;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
239
292
|
} catch (e) {
|
|
240
293
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
241
294
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
|
|
@@ -244,10 +297,19 @@ export default class LowlevelTransport {
|
|
|
244
297
|
|
|
245
298
|
try {
|
|
246
299
|
const response = await this.readProtocolV1Message(uuid, options?.timeoutMs);
|
|
247
|
-
this.Log.debug('receive data: ', response);
|
|
248
300
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
249
301
|
return check.call(jsonData);
|
|
250
302
|
} catch (e) {
|
|
303
|
+
if (
|
|
304
|
+
e?.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
305
|
+
options?.timeoutMs !== PROTOCOL_PROBE_TIMEOUT_MS
|
|
306
|
+
) {
|
|
307
|
+
try {
|
|
308
|
+
await this.resetConnectionAfterProbe(uuid, 'V1');
|
|
309
|
+
} catch (resetError) {
|
|
310
|
+
this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
251
313
|
if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
252
314
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
253
315
|
} else {
|
|
@@ -290,12 +352,12 @@ export default class LowlevelTransport {
|
|
|
290
352
|
protocolHint?: ProtocolType
|
|
291
353
|
): Promise<ProtocolType> {
|
|
292
354
|
if (expectedProtocol === 'V2') {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
355
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
356
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
357
|
+
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
358
|
+
return 'V2';
|
|
359
|
+
}
|
|
360
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
299
361
|
}
|
|
300
362
|
|
|
301
363
|
if (expectedProtocol === 'V1') {
|
|
@@ -307,31 +369,20 @@ export default class LowlevelTransport {
|
|
|
307
369
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
308
370
|
}
|
|
309
371
|
|
|
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
|
-
}
|
|
372
|
+
const probeOrder: ProtocolType[] =
|
|
373
|
+
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
322
374
|
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return 'V2';
|
|
375
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
376
|
+
if (index > 0) {
|
|
377
|
+
await this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
|
|
378
|
+
}
|
|
379
|
+
const detected =
|
|
380
|
+
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
381
|
+
if (detected) {
|
|
382
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
383
|
+
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
384
|
+
return protocol;
|
|
385
|
+
}
|
|
335
386
|
}
|
|
336
387
|
|
|
337
388
|
this.deviceProtocol.delete(uuid);
|
|
@@ -448,7 +499,7 @@ export default class LowlevelTransport {
|
|
|
448
499
|
private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
|
|
449
500
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
450
501
|
if (!assembler) {
|
|
451
|
-
assembler = new ProtocolV2FrameAssembler();
|
|
502
|
+
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
452
503
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
453
504
|
}
|
|
454
505
|
|
|
@@ -466,11 +517,20 @@ export default class LowlevelTransport {
|
|
|
466
517
|
return frame;
|
|
467
518
|
}
|
|
468
519
|
|
|
469
|
-
private async writeProtocolV2Frame(
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
520
|
+
private async writeProtocolV2Frame(
|
|
521
|
+
uuid: string,
|
|
522
|
+
frame: Uint8Array,
|
|
523
|
+
context: ProtocolV2CallContext,
|
|
524
|
+
assertCurrentGeneration: () => void
|
|
525
|
+
) {
|
|
526
|
+
await writeProtocolV2BleFrame({
|
|
527
|
+
frame,
|
|
528
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
529
|
+
assertActive: assertCurrentGeneration,
|
|
530
|
+
signal: context.signal,
|
|
531
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
532
|
+
writePacket: packet => this.plugin.send(uuid, bytesToHex(packet)),
|
|
533
|
+
});
|
|
474
534
|
}
|
|
475
535
|
|
|
476
536
|
private async callProtocolV2(
|
|
@@ -483,18 +543,13 @@ export default class LowlevelTransport {
|
|
|
483
543
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
484
544
|
}
|
|
485
545
|
|
|
486
|
-
const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
487
|
-
|
|
488
546
|
try {
|
|
489
547
|
return await this.protocolV2Links.call(
|
|
490
548
|
uuid,
|
|
491
549
|
() => this.createProtocolV2Adapter(uuid),
|
|
492
550
|
name,
|
|
493
551
|
data,
|
|
494
|
-
|
|
495
|
-
...options,
|
|
496
|
-
timeoutMs,
|
|
497
|
-
}
|
|
552
|
+
options
|
|
498
553
|
);
|
|
499
554
|
} catch (e) {
|
|
500
555
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -518,10 +573,8 @@ export default class LowlevelTransport {
|
|
|
518
573
|
assertCurrentGeneration();
|
|
519
574
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
520
575
|
},
|
|
521
|
-
writeFrame: (frame: Uint8Array) =>
|
|
522
|
-
assertCurrentGeneration
|
|
523
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
524
|
-
},
|
|
576
|
+
writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
|
|
577
|
+
this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
525
578
|
readFrame: (context: { messageName: string; timeoutMs?: number }) => {
|
|
526
579
|
assertCurrentGeneration();
|
|
527
580
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|