@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.12 → 1.2.0-alpha.121
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 -50
- 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 -58
- 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,
|
|
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,7 +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
|
-
this.Log.debug('transport call', { name, protocol });
|
|
178
196
|
if (protocol === 'V2') {
|
|
179
197
|
return this.callProtocolV2(uuid, name, data, options);
|
|
180
198
|
}
|
|
@@ -182,16 +200,42 @@ class LowlevelTransport {
|
|
|
182
200
|
});
|
|
183
201
|
}
|
|
184
202
|
callProtocolV1(uuid, name, data, options) {
|
|
203
|
+
var _a;
|
|
185
204
|
return __awaiter(this, void 0, void 0, function* () {
|
|
186
205
|
if (!this._messages) {
|
|
187
206
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
188
207
|
}
|
|
189
208
|
const messages = this._messages;
|
|
190
209
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
191
|
-
|
|
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()) {
|
|
192
217
|
const outData = o.toString('hex');
|
|
193
218
|
try {
|
|
194
|
-
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
|
+
}
|
|
195
239
|
}
|
|
196
240
|
catch (e) {
|
|
197
241
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
@@ -204,6 +248,15 @@ class LowlevelTransport {
|
|
|
204
248
|
return check.call(jsonData);
|
|
205
249
|
}
|
|
206
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
|
+
}
|
|
207
260
|
if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
208
261
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
209
262
|
}
|
|
@@ -229,12 +282,15 @@ class LowlevelTransport {
|
|
|
229
282
|
}
|
|
230
283
|
}
|
|
231
284
|
detectProtocol(uuid, expectedProtocol, protocolHint) {
|
|
232
|
-
var _a, _b, _c
|
|
285
|
+
var _a, _b, _c;
|
|
233
286
|
return __awaiter(this, void 0, void 0, function* () {
|
|
234
287
|
if (expectedProtocol === 'V2') {
|
|
235
|
-
this.
|
|
236
|
-
|
|
237
|
-
|
|
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);
|
|
238
294
|
}
|
|
239
295
|
if (expectedProtocol === 'V1') {
|
|
240
296
|
if (yield this.probeProtocolV1(uuid)) {
|
|
@@ -244,28 +300,17 @@ class LowlevelTransport {
|
|
|
244
300
|
}
|
|
245
301
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
246
302
|
}
|
|
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';
|
|
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
|
+
}
|
|
269
314
|
}
|
|
270
315
|
this.deviceProtocol.delete(uuid);
|
|
271
316
|
throw this.createProtocolDetectionError();
|
|
@@ -373,7 +418,7 @@ class LowlevelTransport {
|
|
|
373
418
|
return __awaiter(this, void 0, void 0, function* () {
|
|
374
419
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
375
420
|
if (!assembler) {
|
|
376
|
-
assembler = new transport.ProtocolV2FrameAssembler();
|
|
421
|
+
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
377
422
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
378
423
|
}
|
|
379
424
|
const queuedFrame = assembler.push(new Uint8Array(0));
|
|
@@ -390,23 +435,25 @@ class LowlevelTransport {
|
|
|
390
435
|
return frame;
|
|
391
436
|
});
|
|
392
437
|
}
|
|
393
|
-
writeProtocolV2Frame(uuid, frame) {
|
|
438
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
394
439
|
return __awaiter(this, void 0, void 0, function* () {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
+
});
|
|
399
448
|
});
|
|
400
449
|
}
|
|
401
450
|
callProtocolV2(uuid, name, data, options) {
|
|
402
|
-
var _a;
|
|
403
451
|
return __awaiter(this, void 0, void 0, function* () {
|
|
404
452
|
if (!this._messages || !this._messagesV2) {
|
|
405
453
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
406
454
|
}
|
|
407
|
-
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
408
455
|
try {
|
|
409
|
-
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data,
|
|
456
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
|
|
410
457
|
}
|
|
411
458
|
catch (e) {
|
|
412
459
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -431,10 +478,7 @@ class LowlevelTransport {
|
|
|
431
478
|
assertCurrentGeneration();
|
|
432
479
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
433
480
|
},
|
|
434
|
-
writeFrame: (frame) =>
|
|
435
|
-
assertCurrentGeneration();
|
|
436
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
437
|
-
},
|
|
481
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
438
482
|
readFrame: (context) => {
|
|
439
483
|
assertCurrentGeneration();
|
|
440
484
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
@@ -459,4 +503,6 @@ class LowlevelTransport {
|
|
|
459
503
|
}
|
|
460
504
|
}
|
|
461
505
|
|
|
462
|
-
|
|
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.121",
|
|
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.121",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.121"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "916633f46e4d8e0599af2956a639474dd3cbd9ec"
|
|
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,10 +187,13 @@ export default class LowlevelTransport {
|
|
|
150
187
|
);
|
|
151
188
|
}
|
|
152
189
|
|
|
153
|
-
this.protocolV2Assemblers.set(
|
|
190
|
+
this.protocolV2Assemblers.set(
|
|
191
|
+
input.uuid,
|
|
192
|
+
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
193
|
+
);
|
|
154
194
|
const protocolHint = input.expectedProtocol
|
|
155
195
|
? undefined
|
|
156
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
196
|
+
: input.protocolHint ?? this.deviceProtocolHints.get(input.uuid);
|
|
157
197
|
const protocolType = await this.detectProtocol(
|
|
158
198
|
input.uuid,
|
|
159
199
|
input.expectedProtocol,
|
|
@@ -168,8 +208,8 @@ export default class LowlevelTransport {
|
|
|
168
208
|
await this.plugin.disconnect(uuid);
|
|
169
209
|
this.connectedDevices.delete(uuid);
|
|
170
210
|
this.deviceProtocol.delete(uuid);
|
|
171
|
-
//
|
|
172
|
-
//
|
|
211
|
+
// A name-derived protocol hint survives disconnect and lets fast reconnect probe
|
|
212
|
+
// Protocol V2 first without sending a redundant V1 Initialize.
|
|
173
213
|
this.protocolV2Assemblers.delete(uuid);
|
|
174
214
|
return true;
|
|
175
215
|
} catch (error) {
|
|
@@ -195,8 +235,6 @@ export default class LowlevelTransport {
|
|
|
195
235
|
`Device protocol has not been detected for ${uuid}`
|
|
196
236
|
);
|
|
197
237
|
}
|
|
198
|
-
this.Log.debug('transport call', { name, protocol });
|
|
199
|
-
|
|
200
238
|
if (protocol === 'V2') {
|
|
201
239
|
return this.callProtocolV2(uuid, name, data, options);
|
|
202
240
|
}
|
|
@@ -216,10 +254,41 @@ export default class LowlevelTransport {
|
|
|
216
254
|
|
|
217
255
|
const messages = this._messages;
|
|
218
256
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
219
|
-
|
|
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()) {
|
|
220
265
|
const outData = o.toString('hex');
|
|
221
266
|
try {
|
|
222
|
-
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
|
+
}
|
|
223
292
|
} catch (e) {
|
|
224
293
|
this.Log.debug('lowlevel transport send error: ', e);
|
|
225
294
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
|
|
@@ -231,6 +300,16 @@ export default class LowlevelTransport {
|
|
|
231
300
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
232
301
|
return check.call(jsonData);
|
|
233
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
|
+
}
|
|
234
313
|
if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
235
314
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
236
315
|
} else {
|
|
@@ -273,12 +352,12 @@ export default class LowlevelTransport {
|
|
|
273
352
|
protocolHint?: ProtocolType
|
|
274
353
|
): Promise<ProtocolType> {
|
|
275
354
|
if (expectedProtocol === 'V2') {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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);
|
|
282
361
|
}
|
|
283
362
|
|
|
284
363
|
if (expectedProtocol === 'V1') {
|
|
@@ -290,31 +369,20 @@ export default class LowlevelTransport {
|
|
|
290
369
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
291
370
|
}
|
|
292
371
|
|
|
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
|
-
}
|
|
372
|
+
const probeOrder: ProtocolType[] =
|
|
373
|
+
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
312
374
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
+
}
|
|
318
386
|
}
|
|
319
387
|
|
|
320
388
|
this.deviceProtocol.delete(uuid);
|
|
@@ -431,7 +499,7 @@ export default class LowlevelTransport {
|
|
|
431
499
|
private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
|
|
432
500
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
433
501
|
if (!assembler) {
|
|
434
|
-
assembler = new ProtocolV2FrameAssembler();
|
|
502
|
+
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
435
503
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
436
504
|
}
|
|
437
505
|
|
|
@@ -449,11 +517,20 @@ export default class LowlevelTransport {
|
|
|
449
517
|
return frame;
|
|
450
518
|
}
|
|
451
519
|
|
|
452
|
-
private async writeProtocolV2Frame(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
+
});
|
|
457
534
|
}
|
|
458
535
|
|
|
459
536
|
private async callProtocolV2(
|
|
@@ -466,18 +543,13 @@ export default class LowlevelTransport {
|
|
|
466
543
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
467
544
|
}
|
|
468
545
|
|
|
469
|
-
const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
470
|
-
|
|
471
546
|
try {
|
|
472
547
|
return await this.protocolV2Links.call(
|
|
473
548
|
uuid,
|
|
474
549
|
() => this.createProtocolV2Adapter(uuid),
|
|
475
550
|
name,
|
|
476
551
|
data,
|
|
477
|
-
|
|
478
|
-
...options,
|
|
479
|
-
timeoutMs,
|
|
480
|
-
}
|
|
552
|
+
options
|
|
481
553
|
);
|
|
482
554
|
} catch (e) {
|
|
483
555
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -501,10 +573,8 @@ export default class LowlevelTransport {
|
|
|
501
573
|
assertCurrentGeneration();
|
|
502
574
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
503
575
|
},
|
|
504
|
-
writeFrame: (frame: Uint8Array) =>
|
|
505
|
-
assertCurrentGeneration
|
|
506
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
507
|
-
},
|
|
576
|
+
writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
|
|
577
|
+
this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
508
578
|
readFrame: (context: { messageName: string; timeoutMs?: number }) => {
|
|
509
579
|
assertCurrentGeneration();
|
|
510
580
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|