@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.13 → 1.2.0-alpha.131

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ /* eslint-disable @typescript-eslint/no-var-requires */
2
+ const { getProtocolV1SendOptions, shouldLogFirmwareUploadProgress } = require('../src');
3
+
4
+ describe('firmware upload progress logging', () => {
5
+ test('按 5% 进度限流打印', () => {
6
+ expect(
7
+ shouldLogFirmwareUploadProgress({
8
+ percent: 9,
9
+ lastLoggedPercent: 5,
10
+ now: 5_000,
11
+ lastLoggedAt: 0,
12
+ })
13
+ ).toBe(false);
14
+
15
+ expect(
16
+ shouldLogFirmwareUploadProgress({
17
+ percent: 10,
18
+ lastLoggedPercent: 5,
19
+ now: 5_000,
20
+ lastLoggedAt: 0,
21
+ })
22
+ ).toBe(true);
23
+ });
24
+
25
+ test('进度不足 5% 时最长每 10 秒打印一次心跳', () => {
26
+ expect(
27
+ shouldLogFirmwareUploadProgress({
28
+ percent: 7,
29
+ lastLoggedPercent: 5,
30
+ now: 10_000,
31
+ lastLoggedAt: 0,
32
+ })
33
+ ).toBe(true);
34
+ });
35
+ });
36
+
37
+ describe('firmware upload write mode', () => {
38
+ test('固件上传使用带响应写入,普通命令保持默认模式', () => {
39
+ expect(getProtocolV1SendOptions('FirmwareUpload')).toEqual({ withoutResponse: false });
40
+ expect(getProtocolV1SendOptions('Initialize')).toBeUndefined();
41
+ });
42
+ });
@@ -68,12 +68,25 @@ const protocolV2Schema = {
68
68
  },
69
69
  },
70
70
  },
71
+ Failure: {
72
+ fields: {
73
+ code: {
74
+ type: 'uint32',
75
+ id: 1,
76
+ },
77
+ message: {
78
+ type: 'string',
79
+ id: 2,
80
+ },
81
+ },
82
+ },
71
83
  MessageType: {
72
84
  values: {
73
85
  MessageType_ProtocolInfoRequest: 60200,
74
86
  MessageType_ProtocolInfo: 60201,
75
87
  MessageType_Ping: 60206,
76
88
  MessageType_Success: 60207,
89
+ MessageType_Failure: 60208,
77
90
  },
78
91
  },
79
92
  },
@@ -122,6 +135,49 @@ const splitFrame = (frame, index) => [
122
135
  ];
123
136
 
124
137
  describe('LowlevelTransport protocol framing', () => {
138
+ test('falls back to Protocol V2 when a cached V1 hint is stale', async () => {
139
+ const plugin = createPlugin({ devices: [], responses: [] });
140
+ const lowlevel = configureTransport(plugin);
141
+ const events = [];
142
+ lowlevel.probeProtocolV1 = jest.fn().mockImplementation(() => {
143
+ events.push('probe-v1');
144
+ return Promise.resolve(false);
145
+ });
146
+ lowlevel.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
147
+ events.push('reset');
148
+ return Promise.resolve();
149
+ });
150
+ lowlevel.probeProtocolV2 = jest.fn().mockImplementation(() => {
151
+ events.push('probe-v2');
152
+ return Promise.resolve(true);
153
+ });
154
+
155
+ await expect(lowlevel.detectProtocol('pro-lowlevel', undefined, 'V1')).resolves.toBe('V2');
156
+
157
+ expect(events).toEqual(['probe-v1', 'reset', 'probe-v2']);
158
+ expect(lowlevel.getProtocolType('pro-lowlevel')).toBe('V2');
159
+ });
160
+
161
+ test('keeps active links when the Protocol V2 schema is configured repeatedly', () => {
162
+ const lowlevel = new LowlevelTransport();
163
+ const invalidateAllLinks = jest.fn().mockResolvedValue(undefined);
164
+ lowlevel.protocolV2Links.invalidateAllLinks = invalidateAllLinks;
165
+
166
+ lowlevel.configureProtocolV2(protocolV2Schema);
167
+ lowlevel.configureProtocolV2(protocolV2Schema);
168
+
169
+ expect(invalidateAllLinks).not.toHaveBeenCalled();
170
+
171
+ lowlevel.configureProtocolV2({
172
+ nested: {
173
+ ...protocolV2Schema.nested,
174
+ ExtraMessage: { fields: {} },
175
+ },
176
+ });
177
+
178
+ expect(invalidateAllLinks).toHaveBeenCalledWith('Protocol V2 schema reconfigured');
179
+ });
180
+
125
181
  test('keeps Protocol V1 raw notification chunks compatible', async () => {
126
182
  const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
127
183
  message: 'ok',
@@ -142,6 +198,23 @@ describe('LowlevelTransport protocol framing', () => {
142
198
  });
143
199
  });
144
200
 
201
+ test('uses the Protocol V2 BLE writer with the lowlevel compatibility packet size', async () => {
202
+ const plugin = createPlugin({ devices: [], responses: [] });
203
+ const lowlevel = configureTransport(plugin);
204
+ const context = {
205
+ messageName: 'Ping',
206
+ timeoutMs: 1000,
207
+ highThroughput: false,
208
+ generation: 1,
209
+ signal: new AbortController().signal,
210
+ };
211
+
212
+ await lowlevel.writeProtocolV2Frame('pro2-id', new Uint8Array(130), context, jest.fn());
213
+
214
+ expect(plugin.send).toHaveBeenCalledTimes(3);
215
+ expect(plugin.send.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([64, 64, 2]);
216
+ });
217
+
145
218
  test('rejects calls before protocol detection', async () => {
146
219
  const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
147
220
  message: 'ok',
@@ -172,7 +245,7 @@ describe('LowlevelTransport protocol framing', () => {
172
245
  supported_messages: [60200, 60201, 60206, 60207],
173
246
  protobuf_definition: 'onekey-protocol-v2',
174
247
  },
175
- { router: PROTOCOL_V2_CHANNEL_BLE_UART }
248
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: 2 }
176
249
  );
177
250
  const plugin = createPlugin({
178
251
  devices: [{ id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
@@ -253,15 +326,19 @@ describe('LowlevelTransport protocol framing', () => {
253
326
  });
254
327
 
255
328
  test('reuses the active generation when Core acquires the same BLE connection again', async () => {
256
- const probeResponse = ProtocolV2.encodeFrame(
257
- schemas,
258
- 'Success',
259
- { message: 'ok' },
260
- { router: PROTOCOL_V2_CHANNEL_BLE_UART }
329
+ const responses = [1, 2, 3, 4].map(seq =>
330
+ bytesToHex(
331
+ ProtocolV2.encodeFrame(
332
+ schemas,
333
+ 'Success',
334
+ { message: 'ok' },
335
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART, seq }
336
+ )
337
+ )
261
338
  );
262
339
  const plugin = createPlugin({
263
340
  devices: [{ id: 'repeated-acquire-id', name: 'OneKey Pro 2', commType: 'ble' }],
264
- responses: [bytesToHex(probeResponse), bytesToHex(probeResponse)],
341
+ responses,
265
342
  });
266
343
  const lowlevel = configureTransport(plugin);
267
344
 
@@ -283,13 +360,19 @@ describe('LowlevelTransport protocol framing', () => {
283
360
  const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
284
361
  Number.parseInt(hex.slice(12, 14), 16)
285
362
  );
286
- expect(sentSeqs).toEqual([1, 2]);
363
+ expect(sentSeqs).toEqual([1, 2, 3, 4]);
287
364
  });
288
365
 
289
- test('trusts explicit Protocol V2 during bootloader reconnect without probing Ping', async () => {
366
+ test('actively probes explicit Protocol V2 during bootloader reconnect', async () => {
367
+ const probeResponse = ProtocolV2.encodeFrame(
368
+ schemas,
369
+ 'Success',
370
+ { message: 'ok' },
371
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
372
+ );
290
373
  const plugin = createPlugin({
291
374
  devices: [{ id: 'bootloader-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
292
- responses: [],
375
+ responses: [bytesToHex(probeResponse)],
293
376
  });
294
377
  const lowlevel = configureTransport(plugin);
295
378
 
@@ -299,8 +382,35 @@ describe('LowlevelTransport protocol framing', () => {
299
382
  uuid: 'bootloader-v2-id',
300
383
  protocolType: 'V2',
301
384
  });
302
- expect(plugin.send).not.toHaveBeenCalled();
303
- expect(plugin.receive).not.toHaveBeenCalled();
385
+ expect(plugin.send).toHaveBeenCalledTimes(1);
386
+ expect(plugin.receive).toHaveBeenCalledTimes(1);
387
+ });
388
+
389
+ test('disconnects and clears a lowlevel connection when Protocol V2 reports link disabled', async () => {
390
+ const failureResponse = ProtocolV2.encodeFrame(
391
+ schemas,
392
+ 'Failure',
393
+ { code: 5, message: 'link disabled' },
394
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
395
+ );
396
+ const plugin = createPlugin({
397
+ devices: [{ id: 'usb-owned-id', name: 'OneKey Pro 2', commType: 'ble' }],
398
+ responses: [bytesToHex(failureResponse)],
399
+ });
400
+ const lowlevel = configureTransport(plugin);
401
+
402
+ await expect(
403
+ lowlevel.acquire({ uuid: 'usb-owned-id', expectedProtocol: 'V2' })
404
+ ).rejects.toMatchObject({
405
+ name: 'ProtocolV2LinkDisabledError',
406
+ failureCode: 5,
407
+ firmwareMessage: 'link disabled',
408
+ });
409
+
410
+ expect(plugin.disconnect).toHaveBeenCalledWith('usb-owned-id');
411
+ expect(lowlevel.connectedDevices.has('usb-owned-id')).toBe(false);
412
+ expect(lowlevel.getProtocolType('usb-owned-id')).toBeUndefined();
413
+ expect(lowlevel.protocolV2Assemblers.has('usb-owned-id')).toBe(false);
304
414
  });
305
415
 
306
416
  test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
@@ -342,11 +452,23 @@ describe('LowlevelTransport protocol framing', () => {
342
452
  });
343
453
 
344
454
  test('disconnects a tainted Protocol V2 link after a response timeout', async () => {
455
+ const probeResponse = ProtocolV2.encodeFrame(
456
+ schemas,
457
+ 'Success',
458
+ { message: 'ok' },
459
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
460
+ );
345
461
  const plugin = createPlugin({
346
462
  devices: [{ id: 'timeout-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
347
- responses: [],
463
+ responses: [bytesToHex(probeResponse)],
464
+ });
465
+ let receiveCount = 0;
466
+ plugin.receive.mockImplementation(() => {
467
+ receiveCount += 1;
468
+ return receiveCount === 1
469
+ ? Promise.resolve(bytesToHex(probeResponse))
470
+ : new Promise(() => {});
348
471
  });
349
- plugin.receive.mockImplementation(() => new Promise(() => {}));
350
472
  const lowlevel = configureTransport(plugin);
351
473
 
352
474
  await lowlevel.acquire({ uuid: 'timeout-v2-id', expectedProtocol: 'V2' });
@@ -358,6 +480,28 @@ describe('LowlevelTransport protocol framing', () => {
358
480
  expect(plugin.disconnect).toHaveBeenCalledWith('timeout-v2-id');
359
481
  });
360
482
 
483
+ test('preserves an undefined business timeout outside explicit probes', async () => {
484
+ const plugin = createPlugin({
485
+ devices: [{ id: 'v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
486
+ responses: [],
487
+ });
488
+ const lowlevel = configureTransport(plugin);
489
+ lowlevel.deviceProtocol.set('v2-id', 'V2');
490
+ const linkCall = jest
491
+ .spyOn(lowlevel.protocolV2Links, 'call')
492
+ .mockResolvedValue({ type: 'Success', message: {} });
493
+
494
+ await lowlevel.call('v2-id', 'Ping', { message: 'no-business-timeout' });
495
+
496
+ expect(linkCall).toHaveBeenCalledWith(
497
+ 'v2-id',
498
+ expect.any(Function),
499
+ 'Ping',
500
+ { message: 'no-business-timeout' },
501
+ undefined
502
+ );
503
+ });
504
+
361
505
  test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
362
506
  const plugin = createPlugin({
363
507
  devices: [{ id: 'v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
package/dist/index.d.ts CHANGED
@@ -5,8 +5,18 @@ import EventEmitter from 'events';
5
5
  type LowLevelAcquireInput = {
6
6
  uuid: string;
7
7
  expectedProtocol?: ProtocolType;
8
+ protocolHint?: ProtocolType;
8
9
  };
9
10
 
11
+ declare function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, lastLoggedAt, }: {
12
+ percent: number;
13
+ lastLoggedPercent: number;
14
+ now: number;
15
+ lastLoggedAt: number;
16
+ }): boolean;
17
+ declare function getProtocolV1SendOptions(name: string): {
18
+ withoutResponse: boolean;
19
+ } | undefined;
10
20
  declare class LowlevelTransport {
11
21
  _messages: ReturnType<typeof transport__default.parseConfigure> | undefined;
12
22
  _messagesV2: ReturnType<typeof transport__default.parseConfigure> | undefined;
@@ -20,6 +30,7 @@ declare class LowlevelTransport {
20
30
  private protocolV2Generations;
21
31
  private connectedDevices;
22
32
  private protocolV2Links;
33
+ private protocolV2SchemaConfiguration;
23
34
  getProtocolType(path: string): ProtocolType | undefined;
24
35
  init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin): void;
25
36
  configure(signedData: any): void;
@@ -32,6 +43,7 @@ declare class LowlevelTransport {
32
43
  }>;
33
44
  release(uuid: string): Promise<boolean>;
34
45
  call(uuid: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<transport.MessageFromOneKey>;
46
+ post(uuid: string, name: string, data: Record<string, unknown>): Promise<void>;
35
47
  private callProtocolV1;
36
48
  private createProtocolTimeoutError;
37
49
  private createProtocolMismatchError;
@@ -51,4 +63,4 @@ declare class LowlevelTransport {
51
63
  cancel(): void;
52
64
  }
53
65
 
54
- export { LowlevelTransport as default };
66
+ export { LowlevelTransport as default, getProtocolV1SendOptions, shouldLogFirmwareUploadProgress };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,SAWN,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAqBpD,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,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;IAOnC,MAAM;IAIA,SAAS;IAWT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IA6BnC,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;IAoC5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;YAsDd,yBAAyB;YAiCzB,eAAe;YAgBf,eAAe;YA6Bf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAOpB,cAAc;IA6B5B,OAAO,CAAC,uBAAuB;IAkC/B,OAAO,CAAC,2BAA2B;IAMnC,MAAM;CAGP"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,SAYN,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,YAAY,EAEZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAUpD,wBAAgB,+BAA+B,CAAC,EAC9C,OAAO,EACP,iBAAiB,EACjB,GAAG,EACH,YAAY,GACb,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;CACtB,WAMA;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM;;cAEpD;AAcD,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,WAAW,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAErE,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,MAAM,EAAE,6BAA6B,CAAuC;IAE5E,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,oBAAoB,CAAoD;IAEhF,OAAO,CAAC,qBAAqB,CAAkC;IAE/D,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,eAAe,CA6BpB;IAEH,OAAO,CAAC,6BAA6B,CAAqB;IAE1D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIvD,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,6BAA6B;IAO9E,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAiBnC,MAAM;IAIA,SAAS;IAWT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IAmDnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAgBpB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;IAoB1B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;YAatD,cAAc;IA6E5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;YA2Cd,yBAAyB;YAiCzB,eAAe;YAgBf,eAAe;YA6Bf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAgBpB,cAAc;IAwB5B,OAAO,CAAC,uBAAuB;IAgC/B,OAAO,CAAC,2BAA2B;IAMnC,MAAM;CAGP"}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
3
5
  var hdShared = require('@onekeyfe/hd-shared');
4
6
  var transport = require('@onekeyfe/hd-transport');
5
7
 
@@ -40,8 +42,17 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
40
42
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
41
43
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
42
44
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
43
- const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30000;
44
45
  const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
46
+ const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
47
+ const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10000;
48
+ function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, lastLoggedAt, }) {
49
+ return (percent === 100 ||
50
+ percent - lastLoggedPercent >= FIRMWARE_UPLOAD_LOG_PERCENT_STEP ||
51
+ now - lastLoggedAt >= FIRMWARE_UPLOAD_LOG_INTERVAL_MS);
52
+ }
53
+ function getProtocolV1SendOptions(name) {
54
+ return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
55
+ }
45
56
  function inferProtocolHintFromDeviceName(name) {
46
57
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
47
58
  }
@@ -106,10 +117,18 @@ class LowlevelTransport {
106
117
  this._messages = messages;
107
118
  }
108
119
  configureProtocolV2(signedData) {
120
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
121
+ if (this.protocolV2SchemaConfiguration === configuration) {
122
+ return;
123
+ }
124
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
109
125
  this._messagesV2 = parseConfigure(signedData);
110
- this.protocolV2Links
111
- .invalidateAllLinks('Protocol V2 schema reconfigured')
112
- .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('Protocol V2 schema link cleanup failed:', error); });
126
+ this.protocolV2SchemaConfiguration = configuration;
127
+ if (isReconfiguration) {
128
+ this.protocolV2Links
129
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
130
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('Protocol V2 schema link cleanup failed:', error); });
131
+ }
113
132
  }
114
133
  listen() {
115
134
  }
@@ -126,7 +145,7 @@ class LowlevelTransport {
126
145
  });
127
146
  }
128
147
  acquire(input) {
129
- var _a;
148
+ var _a, _b, _c, _d;
130
149
  return __awaiter(this, void 0, void 0, function* () {
131
150
  const alreadyConnected = this.connectedDevices.has(input.uuid);
132
151
  try {
@@ -141,12 +160,35 @@ class LowlevelTransport {
141
160
  this.Log.debug('lowlelvel transport connect error: ', error);
142
161
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_a = error.message) !== null && _a !== void 0 ? _a : error);
143
162
  }
144
- this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler());
163
+ this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
145
164
  const protocolHint = input.expectedProtocol
146
165
  ? undefined
147
- : this.deviceProtocolHints.get(input.uuid);
148
- const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
149
- return { uuid: input.uuid, protocolType };
166
+ : (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(input.uuid);
167
+ try {
168
+ const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
169
+ return { uuid: input.uuid, protocolType };
170
+ }
171
+ catch (error) {
172
+ try {
173
+ yield this.protocolV2Links.invalidateLink(input.uuid, 'Lowlevel transport acquire failed');
174
+ }
175
+ catch (cleanupError) {
176
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[LowlevelTransport] acquire link cleanup failed:', cleanupError);
177
+ }
178
+ try {
179
+ yield this.plugin.disconnect(input.uuid);
180
+ }
181
+ catch (cleanupError) {
182
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[LowlevelTransport] acquire disconnect failed:', cleanupError);
183
+ }
184
+ finally {
185
+ this.connectedDevices.delete(input.uuid);
186
+ this.deviceProtocol.delete(input.uuid);
187
+ this.protocolV2Assemblers.delete(input.uuid);
188
+ this.advanceProtocolV2Generation(input.uuid);
189
+ }
190
+ throw error;
191
+ }
150
192
  });
151
193
  }
152
194
  release(uuid) {
@@ -174,24 +216,58 @@ class LowlevelTransport {
174
216
  if (!protocol) {
175
217
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
176
218
  }
177
- this.Log.debug('transport call', { name, protocol });
178
219
  if (protocol === 'V2') {
179
220
  return this.callProtocolV2(uuid, name, data, options);
180
221
  }
181
222
  return this.callProtocolV1(uuid, name, data, options);
182
223
  });
183
224
  }
225
+ post(uuid, name, data) {
226
+ return __awaiter(this, void 0, void 0, function* () {
227
+ if (this.getProtocolType(uuid) === 'V2') {
228
+ yield this.protocolV2Links.sendFlowControl(uuid, () => this.createProtocolV2Adapter(uuid), name, data);
229
+ return;
230
+ }
231
+ yield this.callProtocolV1(uuid, name, data);
232
+ });
233
+ }
184
234
  callProtocolV1(uuid, name, data, options) {
235
+ var _a;
185
236
  return __awaiter(this, void 0, void 0, function* () {
186
237
  if (!this._messages) {
187
238
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
188
239
  }
189
240
  const messages = this._messages;
190
241
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
191
- for (const o of buffers) {
242
+ const isFirmwareUpload = name === 'FirmwareUpload';
243
+ const uploadStartedAt = Date.now();
244
+ const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
245
+ let sentBytes = 0;
246
+ let lastLoggedPercent = 0;
247
+ let lastLoggedAt = uploadStartedAt;
248
+ for (const [index, o] of buffers.entries()) {
192
249
  const outData = o.toString('hex');
193
250
  try {
194
- yield this.plugin.send(uuid, outData);
251
+ yield this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
252
+ sentBytes += o.limit;
253
+ if (isFirmwareUpload) {
254
+ const now = Date.now();
255
+ const percent = Math.floor(((index + 1) / buffers.length) * 100);
256
+ if (shouldLogFirmwareUploadProgress({
257
+ percent,
258
+ lastLoggedPercent,
259
+ now,
260
+ lastLoggedAt,
261
+ })) {
262
+ const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
263
+ const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
264
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
265
+ `(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
266
+ `${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`);
267
+ lastLoggedPercent = percent;
268
+ lastLoggedAt = now;
269
+ }
270
+ }
195
271
  }
196
272
  catch (e) {
197
273
  this.Log.debug('lowlevel transport send error: ', e);
@@ -204,6 +280,15 @@ class LowlevelTransport {
204
280
  return check.call(jsonData);
205
281
  }
206
282
  catch (e) {
283
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
284
+ (options === null || options === void 0 ? void 0 : options.timeoutMs) !== PROTOCOL_PROBE_TIMEOUT_MS) {
285
+ try {
286
+ yield this.resetConnectionAfterProbe(uuid, 'V1');
287
+ }
288
+ catch (resetError) {
289
+ this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
290
+ }
291
+ }
207
292
  if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
208
293
  this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
209
294
  }
@@ -229,12 +314,15 @@ class LowlevelTransport {
229
314
  }
230
315
  }
231
316
  detectProtocol(uuid, expectedProtocol, protocolHint) {
232
- var _a, _b, _c, _d, _e, _f;
317
+ var _a, _b, _c;
233
318
  return __awaiter(this, void 0, void 0, function* () {
234
319
  if (expectedProtocol === 'V2') {
235
- this.deviceProtocol.set(uuid, 'V2');
236
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
237
- return 'V2';
320
+ if (yield this.probeProtocolV2(uuid)) {
321
+ this.deviceProtocol.set(uuid, 'V2');
322
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
323
+ return 'V2';
324
+ }
325
+ throw this.createProtocolMismatchError(expectedProtocol);
238
326
  }
239
327
  if (expectedProtocol === 'V1') {
240
328
  if (yield this.probeProtocolV1(uuid)) {
@@ -244,28 +332,17 @@ class LowlevelTransport {
244
332
  }
245
333
  throw this.createProtocolMismatchError(expectedProtocol);
246
334
  }
247
- if (protocolHint === 'V2' && (yield this.probeProtocolV2(uuid))) {
248
- this.deviceProtocol.set(uuid, 'V2');
249
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
250
- return 'V2';
251
- }
252
- const cachedProtocol = this.deviceProtocol.get(uuid);
253
- if (cachedProtocol === 'V2' && (yield this.probeProtocolV2(uuid))) {
254
- this.deviceProtocol.set(uuid, 'V2');
255
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
256
- return 'V2';
257
- }
258
- const protocolV1Detected = yield this.probeProtocolV1(uuid);
259
- if (protocolV1Detected) {
260
- this.deviceProtocol.set(uuid, 'V1');
261
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
262
- return 'V1';
263
- }
264
- yield this.resetConnectionAfterProbe(uuid, 'V1');
265
- if (yield this.probeProtocolV2(uuid)) {
266
- this.deviceProtocol.set(uuid, 'V2');
267
- (_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
268
- return 'V2';
335
+ const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
336
+ for (const [index, protocol] of probeOrder.entries()) {
337
+ if (index > 0) {
338
+ yield this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
339
+ }
340
+ const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
341
+ if (detected) {
342
+ this.deviceProtocol.set(uuid, protocol);
343
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
344
+ return protocol;
345
+ }
269
346
  }
270
347
  this.deviceProtocol.delete(uuid);
271
348
  throw this.createProtocolDetectionError();
@@ -373,7 +450,7 @@ class LowlevelTransport {
373
450
  return __awaiter(this, void 0, void 0, function* () {
374
451
  let assembler = this.protocolV2Assemblers.get(uuid);
375
452
  if (!assembler) {
376
- assembler = new transport.ProtocolV2FrameAssembler();
453
+ assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
377
454
  this.protocolV2Assemblers.set(uuid, assembler);
378
455
  }
379
456
  const queuedFrame = assembler.push(new Uint8Array(0));
@@ -390,23 +467,25 @@ class LowlevelTransport {
390
467
  return frame;
391
468
  });
392
469
  }
393
- writeProtocolV2Frame(uuid, frame) {
470
+ writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
394
471
  return __awaiter(this, void 0, void 0, function* () {
395
- for (let offset = 0; offset < frame.length; offset += LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH) {
396
- const chunk = frame.slice(offset, offset + LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH);
397
- yield this.plugin.send(uuid, transport.bytesToHex(chunk));
398
- }
472
+ yield transport.writeProtocolV2BleFrame({
473
+ frame,
474
+ packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
475
+ assertActive: assertCurrentGeneration,
476
+ signal: context.signal,
477
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
478
+ writePacket: packet => this.plugin.send(uuid, transport.bytesToHex(packet)),
479
+ });
399
480
  });
400
481
  }
401
482
  callProtocolV2(uuid, name, data, options) {
402
- var _a;
403
483
  return __awaiter(this, void 0, void 0, function* () {
404
484
  if (!this._messages || !this._messagesV2) {
405
485
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
406
486
  }
407
- const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
408
487
  try {
409
- return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, Object.assign(Object.assign({}, options), { timeoutMs }));
488
+ return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
410
489
  }
411
490
  catch (e) {
412
491
  this.Log.error('lowlevel Protocol V2 call error: ', e);
@@ -431,10 +510,7 @@ class LowlevelTransport {
431
510
  assertCurrentGeneration();
432
511
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
433
512
  },
434
- writeFrame: (frame) => {
435
- assertCurrentGeneration();
436
- return this.writeProtocolV2Frame(uuid, frame);
437
- },
513
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
438
514
  readFrame: (context) => {
439
515
  assertCurrentGeneration();
440
516
  return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
@@ -459,4 +535,6 @@ class LowlevelTransport {
459
535
  }
460
536
  }
461
537
 
462
- module.exports = LowlevelTransport;
538
+ exports["default"] = LowlevelTransport;
539
+ exports.getProtocolV1SendOptions = getProtocolV1SendOptions;
540
+ exports.shouldLogFirmwareUploadProgress = shouldLogFirmwareUploadProgress;
package/dist/types.d.ts CHANGED
@@ -2,5 +2,6 @@ import type { ProtocolType } from '@onekeyfe/hd-transport';
2
2
  export type LowLevelAcquireInput = {
3
3
  uuid: string;
4
4
  expectedProtocol?: ProtocolType;
5
+ protocolHint?: ProtocolType;
5
6
  };
6
7
  //# sourceMappingURL=types.d.ts.map
@@ -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;CACjC,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.13",
3
+ "version": "1.2.0-alpha.131",
4
4
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -20,8 +20,8 @@
20
20
  "lint:fix": "eslint . --fix"
21
21
  },
22
22
  "dependencies": {
23
- "@onekeyfe/hd-shared": "1.2.0-alpha.13",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.13"
23
+ "@onekeyfe/hd-shared": "1.2.0-alpha.131",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.131"
25
25
  },
26
- "gitHead": "a907a7ec140123107f6188b37550a7143f1e0d91"
26
+ "gitHead": "81b472e983e80e6aa771b991dc9c0c9b1bdcf07b"
27
27
  }
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ import transport, {
10
10
  hexToBytes,
11
11
  probeProtocolV2 as probeProtocolV2Helper,
12
12
  withProtocolTimeout,
13
+ writeProtocolV2BleFrame,
13
14
  } from '@onekeyfe/hd-transport';
14
15
 
15
16
  import type EventEmitter from 'events';
@@ -17,6 +18,7 @@ import type {
17
18
  LowLevelDevice,
18
19
  LowlevelTransportSharedPlugin,
19
20
  ProtocolType,
21
+ ProtocolV2CallContext,
20
22
  TransportCallOptions,
21
23
  } from '@onekeyfe/hd-transport';
22
24
  import type { LowLevelAcquireInput } from './types';
@@ -25,8 +27,31 @@ const { check, ProtocolV1, parseConfigure } = transport;
25
27
 
26
28
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
27
29
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
28
- const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30_000;
29
30
  const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
31
+ const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
32
+ const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10_000;
33
+
34
+ export function shouldLogFirmwareUploadProgress({
35
+ percent,
36
+ lastLoggedPercent,
37
+ now,
38
+ lastLoggedAt,
39
+ }: {
40
+ percent: number;
41
+ lastLoggedPercent: number;
42
+ now: number;
43
+ lastLoggedAt: number;
44
+ }) {
45
+ return (
46
+ percent === 100 ||
47
+ percent - lastLoggedPercent >= FIRMWARE_UPLOAD_LOG_PERCENT_STEP ||
48
+ now - lastLoggedAt >= FIRMWARE_UPLOAD_LOG_INTERVAL_MS
49
+ );
50
+ }
51
+
52
+ export function getProtocolV1SendOptions(name: string) {
53
+ return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
54
+ }
30
55
 
31
56
  function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
32
57
  return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
@@ -94,6 +119,8 @@ export default class LowlevelTransport {
94
119
  },
95
120
  });
96
121
 
122
+ private protocolV2SchemaConfiguration: string | undefined;
123
+
97
124
  getProtocolType(path: string): ProtocolType | undefined {
98
125
  return this.deviceProtocol.get(path);
99
126
  }
@@ -112,10 +139,20 @@ export default class LowlevelTransport {
112
139
  }
113
140
 
114
141
  configureProtocolV2(signedData: any) {
142
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
143
+ if (this.protocolV2SchemaConfiguration === configuration) {
144
+ return;
145
+ }
146
+
147
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
115
148
  this._messagesV2 = parseConfigure(signedData);
116
- this.protocolV2Links
117
- .invalidateAllLinks('Protocol V2 schema reconfigured')
118
- .catch(error => this.Log?.debug('Protocol V2 schema link cleanup failed:', error));
149
+ this.protocolV2SchemaConfiguration = configuration;
150
+
151
+ if (isReconfiguration) {
152
+ this.protocolV2Links
153
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
154
+ .catch(error => this.Log?.debug('Protocol V2 schema link cleanup failed:', error));
155
+ }
119
156
  }
120
157
 
121
158
  listen() {
@@ -150,16 +187,38 @@ export default class LowlevelTransport {
150
187
  );
151
188
  }
152
189
 
153
- this.protocolV2Assemblers.set(input.uuid, new ProtocolV2FrameAssembler());
154
- const protocolHint = input.expectedProtocol
155
- ? undefined
156
- : this.deviceProtocolHints.get(input.uuid);
157
- const protocolType = await this.detectProtocol(
190
+ this.protocolV2Assemblers.set(
158
191
  input.uuid,
159
- input.expectedProtocol,
160
- protocolHint
192
+ new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
161
193
  );
162
- return { uuid: input.uuid, protocolType };
194
+ const protocolHint = input.expectedProtocol
195
+ ? undefined
196
+ : input.protocolHint ?? this.deviceProtocolHints.get(input.uuid);
197
+ try {
198
+ const protocolType = await this.detectProtocol(
199
+ input.uuid,
200
+ input.expectedProtocol,
201
+ protocolHint
202
+ );
203
+ return { uuid: input.uuid, protocolType };
204
+ } catch (error) {
205
+ try {
206
+ await this.protocolV2Links.invalidateLink(input.uuid, 'Lowlevel transport acquire failed');
207
+ } catch (cleanupError) {
208
+ this.Log?.debug('[LowlevelTransport] acquire link cleanup failed:', cleanupError);
209
+ }
210
+ try {
211
+ await this.plugin.disconnect(input.uuid);
212
+ } catch (cleanupError) {
213
+ this.Log?.debug('[LowlevelTransport] acquire disconnect failed:', cleanupError);
214
+ } finally {
215
+ this.connectedDevices.delete(input.uuid);
216
+ this.deviceProtocol.delete(input.uuid);
217
+ this.protocolV2Assemblers.delete(input.uuid);
218
+ this.advanceProtocolV2Generation(input.uuid);
219
+ }
220
+ throw error;
221
+ }
163
222
  }
164
223
 
165
224
  async release(uuid: string) {
@@ -168,8 +227,8 @@ export default class LowlevelTransport {
168
227
  await this.plugin.disconnect(uuid);
169
228
  this.connectedDevices.delete(uuid);
170
229
  this.deviceProtocol.delete(uuid);
171
- // 设备名称推断出的协议提示不依赖当前连接,保留它可以让快速重连
172
- // 直接执行 Protocol V2 探测,避免先发送一次无意义的 V1 Initialize
230
+ // A name-derived protocol hint survives disconnect and lets fast reconnect probe
231
+ // Protocol V2 first without sending a redundant V1 Initialize.
173
232
  this.protocolV2Assemblers.delete(uuid);
174
233
  return true;
175
234
  } catch (error) {
@@ -195,8 +254,6 @@ export default class LowlevelTransport {
195
254
  `Device protocol has not been detected for ${uuid}`
196
255
  );
197
256
  }
198
- this.Log.debug('transport call', { name, protocol });
199
-
200
257
  if (protocol === 'V2') {
201
258
  return this.callProtocolV2(uuid, name, data, options);
202
259
  }
@@ -204,6 +261,19 @@ export default class LowlevelTransport {
204
261
  return this.callProtocolV1(uuid, name, data, options);
205
262
  }
206
263
 
264
+ async post(uuid: string, name: string, data: Record<string, unknown>) {
265
+ if (this.getProtocolType(uuid) === 'V2') {
266
+ await this.protocolV2Links.sendFlowControl(
267
+ uuid,
268
+ () => this.createProtocolV2Adapter(uuid),
269
+ name,
270
+ data
271
+ );
272
+ return;
273
+ }
274
+ await this.callProtocolV1(uuid, name, data);
275
+ }
276
+
207
277
  private async callProtocolV1(
208
278
  uuid: string,
209
279
  name: string,
@@ -216,10 +286,41 @@ export default class LowlevelTransport {
216
286
 
217
287
  const messages = this._messages;
218
288
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
219
- for (const o of buffers) {
289
+ const isFirmwareUpload = name === 'FirmwareUpload';
290
+ const uploadStartedAt = Date.now();
291
+ const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
292
+ let sentBytes = 0;
293
+ let lastLoggedPercent = 0;
294
+ let lastLoggedAt = uploadStartedAt;
295
+
296
+ for (const [index, o] of buffers.entries()) {
220
297
  const outData = o.toString('hex');
221
298
  try {
222
- await this.plugin.send(uuid, outData);
299
+ await this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
300
+ sentBytes += o.limit;
301
+
302
+ if (isFirmwareUpload) {
303
+ const now = Date.now();
304
+ const percent = Math.floor(((index + 1) / buffers.length) * 100);
305
+ if (
306
+ shouldLogFirmwareUploadProgress({
307
+ percent,
308
+ lastLoggedPercent,
309
+ now,
310
+ lastLoggedAt,
311
+ })
312
+ ) {
313
+ const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
314
+ const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
315
+ this.Log?.debug(
316
+ `[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
317
+ `(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
318
+ `${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`
319
+ );
320
+ lastLoggedPercent = percent;
321
+ lastLoggedAt = now;
322
+ }
323
+ }
223
324
  } catch (e) {
224
325
  this.Log.debug('lowlevel transport send error: ', e);
225
326
  throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
@@ -231,6 +332,16 @@ export default class LowlevelTransport {
231
332
  const jsonData = ProtocolV1.decodeMessage(messages, response);
232
333
  return check.call(jsonData);
233
334
  } catch (e) {
335
+ if (
336
+ e?.errorCode === HardwareErrorCode.BleTimeoutError &&
337
+ options?.timeoutMs !== PROTOCOL_PROBE_TIMEOUT_MS
338
+ ) {
339
+ try {
340
+ await this.resetConnectionAfterProbe(uuid, 'V1');
341
+ } catch (resetError) {
342
+ this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
343
+ }
344
+ }
234
345
  if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
235
346
  this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
236
347
  } else {
@@ -273,12 +384,12 @@ export default class LowlevelTransport {
273
384
  protocolHint?: ProtocolType
274
385
  ): Promise<ProtocolType> {
275
386
  if (expectedProtocol === 'V2') {
276
- // 固件升级重启后的 bootloader 不保证响应 Ping。上层显式传入 V2
277
- // 表示协议已经在重启前确认,这里与 RN/Electron BLE 保持一致,
278
- // 直接建立 V2 Link,首个业务命令负责验证链路是否可用。
279
- this.deviceProtocol.set(uuid, 'V2');
280
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
281
- return 'V2';
387
+ if (await this.probeProtocolV2(uuid)) {
388
+ this.deviceProtocol.set(uuid, 'V2');
389
+ this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
390
+ return 'V2';
391
+ }
392
+ throw this.createProtocolMismatchError(expectedProtocol);
282
393
  }
283
394
 
284
395
  if (expectedProtocol === 'V1') {
@@ -290,31 +401,20 @@ export default class LowlevelTransport {
290
401
  throw this.createProtocolMismatchError(expectedProtocol);
291
402
  }
292
403
 
293
- if (protocolHint === 'V2' && (await this.probeProtocolV2(uuid))) {
294
- this.deviceProtocol.set(uuid, 'V2');
295
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
296
- return 'V2';
297
- }
298
-
299
- const cachedProtocol = this.deviceProtocol.get(uuid);
300
- if (cachedProtocol === 'V2' && (await this.probeProtocolV2(uuid))) {
301
- this.deviceProtocol.set(uuid, 'V2');
302
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
303
- return 'V2';
304
- }
305
-
306
- const protocolV1Detected = await this.probeProtocolV1(uuid);
307
- if (protocolV1Detected) {
308
- this.deviceProtocol.set(uuid, 'V1');
309
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
310
- return 'V1';
311
- }
404
+ const probeOrder: ProtocolType[] =
405
+ protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
312
406
 
313
- await this.resetConnectionAfterProbe(uuid, 'V1');
314
- if (await this.probeProtocolV2(uuid)) {
315
- this.deviceProtocol.set(uuid, 'V2');
316
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
317
- return 'V2';
407
+ for (const [index, protocol] of probeOrder.entries()) {
408
+ if (index > 0) {
409
+ await this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
410
+ }
411
+ const detected =
412
+ protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
413
+ if (detected) {
414
+ this.deviceProtocol.set(uuid, protocol);
415
+ this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
416
+ return protocol;
417
+ }
318
418
  }
319
419
 
320
420
  this.deviceProtocol.delete(uuid);
@@ -431,7 +531,7 @@ export default class LowlevelTransport {
431
531
  private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
432
532
  let assembler = this.protocolV2Assemblers.get(uuid);
433
533
  if (!assembler) {
434
- assembler = new ProtocolV2FrameAssembler();
534
+ assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
435
535
  this.protocolV2Assemblers.set(uuid, assembler);
436
536
  }
437
537
 
@@ -449,11 +549,20 @@ export default class LowlevelTransport {
449
549
  return frame;
450
550
  }
451
551
 
452
- private async writeProtocolV2Frame(uuid: string, frame: Uint8Array) {
453
- for (let offset = 0; offset < frame.length; offset += LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH) {
454
- const chunk = frame.slice(offset, offset + LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH);
455
- await this.plugin.send(uuid, bytesToHex(chunk));
456
- }
552
+ private async writeProtocolV2Frame(
553
+ uuid: string,
554
+ frame: Uint8Array,
555
+ context: ProtocolV2CallContext,
556
+ assertCurrentGeneration: () => void
557
+ ) {
558
+ await writeProtocolV2BleFrame({
559
+ frame,
560
+ packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
561
+ assertActive: assertCurrentGeneration,
562
+ signal: context.signal,
563
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
564
+ writePacket: packet => this.plugin.send(uuid, bytesToHex(packet)),
565
+ });
457
566
  }
458
567
 
459
568
  private async callProtocolV2(
@@ -466,18 +575,13 @@ export default class LowlevelTransport {
466
575
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
467
576
  }
468
577
 
469
- const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
470
-
471
578
  try {
472
579
  return await this.protocolV2Links.call(
473
580
  uuid,
474
581
  () => this.createProtocolV2Adapter(uuid),
475
582
  name,
476
583
  data,
477
- {
478
- ...options,
479
- timeoutMs,
480
- }
584
+ options
481
585
  );
482
586
  } catch (e) {
483
587
  this.Log.error('lowlevel Protocol V2 call error: ', e);
@@ -501,10 +605,8 @@ export default class LowlevelTransport {
501
605
  assertCurrentGeneration();
502
606
  this.protocolV2Assemblers.get(uuid)?.reset();
503
607
  },
504
- writeFrame: (frame: Uint8Array) => {
505
- assertCurrentGeneration();
506
- return this.writeProtocolV2Frame(uuid, frame);
507
- },
608
+ writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
609
+ this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
508
610
  readFrame: (context: { messageName: string; timeoutMs?: number }) => {
509
611
  assertCurrentGeneration();
510
612
  return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
package/src/types.ts CHANGED
@@ -3,4 +3,5 @@ import type { ProtocolType } from '@onekeyfe/hd-transport';
3
3
  export type LowLevelAcquireInput = {
4
4
  uuid: string;
5
5
  expectedProtocol?: ProtocolType;
6
+ protocolHint?: ProtocolType;
6
7
  };