@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.9 → 1.2.0-alpha.90

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
+ });
@@ -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 probeResponse = ProtocolV2.encodeFrame(
257
- schemas,
258
- 'Success',
259
- { message: 'ok' },
260
- { router: PROTOCOL_V2_CHANNEL_BLE_UART }
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: [bytesToHex(probeResponse), bytesToHex(probeResponse)],
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('trusts explicit Protocol V2 during bootloader reconnect without probing Ping', async () => {
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).not.toHaveBeenCalled();
303
- expect(plugin.receive).not.toHaveBeenCalled();
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 };
@@ -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,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;YAmClB,cAAc;IAuC5B,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;;;;IAgCnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAgBpB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YAsBlB,cAAc;IA6E5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;YA2Cd,yBAAyB;YAiCzB,eAAe;YAgBf,eAAe;YA6Bf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAgBpB,cAAc;IAwB5B,OAAO,CAAC,uBAAuB;IAgC/B,OAAO,CAAC,2BAA2B;IAMnC,MAAM;CAGP"}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
3
5
  var hdShared = require('@onekeyfe/hd-shared');
4
6
  var transport = require('@onekeyfe/hd-transport');
5
7
 
@@ -40,8 +42,17 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
40
42
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
41
43
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
42
44
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
43
- const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30000;
44
45
  const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
46
+ const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
47
+ const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10000;
48
+ function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, lastLoggedAt, }) {
49
+ return (percent === 100 ||
50
+ percent - lastLoggedPercent >= FIRMWARE_UPLOAD_LOG_PERCENT_STEP ||
51
+ now - lastLoggedAt >= FIRMWARE_UPLOAD_LOG_INTERVAL_MS);
52
+ }
53
+ function getProtocolV1SendOptions(name) {
54
+ return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
55
+ }
45
56
  function inferProtocolHintFromDeviceName(name) {
46
57
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
47
58
  }
@@ -106,10 +117,18 @@ class LowlevelTransport {
106
117
  this._messages = messages;
107
118
  }
108
119
  configureProtocolV2(signedData) {
120
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
121
+ if (this.protocolV2SchemaConfiguration === configuration) {
122
+ return;
123
+ }
124
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
109
125
  this._messagesV2 = parseConfigure(signedData);
110
- this.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;
130
149
  return __awaiter(this, void 0, void 0, function* () {
131
150
  const alreadyConnected = this.connectedDevices.has(input.uuid);
132
151
  try {
@@ -141,10 +160,10 @@ class LowlevelTransport {
141
160
  this.Log.debug('lowlelvel transport connect error: ', error);
142
161
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_a = error.message) !== null && _a !== void 0 ? _a : error);
143
162
  }
144
- this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler());
163
+ this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
145
164
  const protocolHint = input.expectedProtocol
146
165
  ? undefined
147
- : this.deviceProtocolHints.get(input.uuid);
166
+ : (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(input.uuid);
148
167
  const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
149
168
  return { uuid: input.uuid, protocolType };
150
169
  });
@@ -174,12 +193,7 @@ class LowlevelTransport {
174
193
  if (!protocol) {
175
194
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
176
195
  }
177
- 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
- }
196
+ this.Log.debug('transport call', { name, protocol });
183
197
  if (protocol === 'V2') {
184
198
  return this.callProtocolV2(uuid, name, data, options);
185
199
  }
@@ -187,17 +201,42 @@ class LowlevelTransport {
187
201
  });
188
202
  }
189
203
  callProtocolV1(uuid, name, data, options) {
204
+ var _a;
190
205
  return __awaiter(this, void 0, void 0, function* () {
191
206
  if (!this._messages) {
192
207
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
193
208
  }
194
209
  const messages = this._messages;
195
210
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
196
- for (const o of buffers) {
211
+ const isFirmwareUpload = name === 'FirmwareUpload';
212
+ const uploadStartedAt = Date.now();
213
+ const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
214
+ let sentBytes = 0;
215
+ let lastLoggedPercent = 0;
216
+ let lastLoggedAt = uploadStartedAt;
217
+ for (const [index, o] of buffers.entries()) {
197
218
  const outData = o.toString('hex');
198
- this.Log.debug('send hex strting: ', outData);
199
219
  try {
200
- yield this.plugin.send(uuid, outData);
220
+ yield this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
221
+ sentBytes += o.limit;
222
+ if (isFirmwareUpload) {
223
+ const now = Date.now();
224
+ const percent = Math.floor(((index + 1) / buffers.length) * 100);
225
+ if (shouldLogFirmwareUploadProgress({
226
+ percent,
227
+ lastLoggedPercent,
228
+ now,
229
+ lastLoggedAt,
230
+ })) {
231
+ const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
232
+ const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
233
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
234
+ `(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
235
+ `${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`);
236
+ lastLoggedPercent = percent;
237
+ lastLoggedAt = now;
238
+ }
239
+ }
201
240
  }
202
241
  catch (e) {
203
242
  this.Log.debug('lowlevel transport send error: ', e);
@@ -206,11 +245,19 @@ class LowlevelTransport {
206
245
  }
207
246
  try {
208
247
  const response = yield this.readProtocolV1Message(uuid, options === null || options === void 0 ? void 0 : options.timeoutMs);
209
- this.Log.debug('receive data: ', response);
210
248
  const jsonData = ProtocolV1.decodeMessage(messages, response);
211
249
  return check.call(jsonData);
212
250
  }
213
251
  catch (e) {
252
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
253
+ (options === null || options === void 0 ? void 0 : options.timeoutMs) !== PROTOCOL_PROBE_TIMEOUT_MS) {
254
+ try {
255
+ yield this.resetConnectionAfterProbe(uuid, 'V1');
256
+ }
257
+ catch (resetError) {
258
+ this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
259
+ }
260
+ }
214
261
  if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
215
262
  this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
216
263
  }
@@ -236,12 +283,15 @@ class LowlevelTransport {
236
283
  }
237
284
  }
238
285
  detectProtocol(uuid, expectedProtocol, protocolHint) {
239
- var _a, _b, _c, _d, _e, _f;
286
+ var _a, _b, _c;
240
287
  return __awaiter(this, void 0, void 0, function* () {
241
288
  if (expectedProtocol === 'V2') {
242
- this.deviceProtocol.set(uuid, 'V2');
243
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
244
- return 'V2';
289
+ if (yield this.probeProtocolV2(uuid)) {
290
+ this.deviceProtocol.set(uuid, 'V2');
291
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
292
+ return 'V2';
293
+ }
294
+ throw this.createProtocolMismatchError(expectedProtocol);
245
295
  }
246
296
  if (expectedProtocol === 'V1') {
247
297
  if (yield this.probeProtocolV1(uuid)) {
@@ -251,28 +301,17 @@ class LowlevelTransport {
251
301
  }
252
302
  throw this.createProtocolMismatchError(expectedProtocol);
253
303
  }
254
- if (protocolHint === 'V2' && (yield this.probeProtocolV2(uuid))) {
255
- this.deviceProtocol.set(uuid, 'V2');
256
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
257
- return 'V2';
258
- }
259
- const cachedProtocol = this.deviceProtocol.get(uuid);
260
- if (cachedProtocol === 'V2' && (yield this.probeProtocolV2(uuid))) {
261
- this.deviceProtocol.set(uuid, 'V2');
262
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
263
- return 'V2';
264
- }
265
- const protocolV1Detected = yield this.probeProtocolV1(uuid);
266
- if (protocolV1Detected) {
267
- this.deviceProtocol.set(uuid, 'V1');
268
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
269
- return 'V1';
270
- }
271
- yield this.resetConnectionAfterProbe(uuid, 'V1');
272
- if (yield this.probeProtocolV2(uuid)) {
273
- this.deviceProtocol.set(uuid, 'V2');
274
- (_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
275
- return 'V2';
304
+ const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
305
+ for (const [index, protocol] of probeOrder.entries()) {
306
+ if (index > 0) {
307
+ yield this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
308
+ }
309
+ const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
310
+ if (detected) {
311
+ this.deviceProtocol.set(uuid, protocol);
312
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
313
+ return protocol;
314
+ }
276
315
  }
277
316
  this.deviceProtocol.delete(uuid);
278
317
  throw this.createProtocolDetectionError();
@@ -380,7 +419,7 @@ class LowlevelTransport {
380
419
  return __awaiter(this, void 0, void 0, function* () {
381
420
  let assembler = this.protocolV2Assemblers.get(uuid);
382
421
  if (!assembler) {
383
- assembler = new transport.ProtocolV2FrameAssembler();
422
+ assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
384
423
  this.protocolV2Assemblers.set(uuid, assembler);
385
424
  }
386
425
  const queuedFrame = assembler.push(new Uint8Array(0));
@@ -397,23 +436,25 @@ class LowlevelTransport {
397
436
  return frame;
398
437
  });
399
438
  }
400
- writeProtocolV2Frame(uuid, frame) {
439
+ writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
401
440
  return __awaiter(this, void 0, void 0, function* () {
402
- for (let offset = 0; offset < frame.length; offset += LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH) {
403
- const chunk = frame.slice(offset, offset + LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH);
404
- yield this.plugin.send(uuid, transport.bytesToHex(chunk));
405
- }
441
+ yield transport.writeProtocolV2BleFrame({
442
+ frame,
443
+ packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
444
+ assertActive: assertCurrentGeneration,
445
+ signal: context.signal,
446
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
447
+ writePacket: packet => this.plugin.send(uuid, transport.bytesToHex(packet)),
448
+ });
406
449
  });
407
450
  }
408
451
  callProtocolV2(uuid, name, data, options) {
409
- var _a;
410
452
  return __awaiter(this, void 0, void 0, function* () {
411
453
  if (!this._messages || !this._messagesV2) {
412
454
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
413
455
  }
414
- const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
415
456
  try {
416
- return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, Object.assign(Object.assign({}, options), { timeoutMs }));
457
+ return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
417
458
  }
418
459
  catch (e) {
419
460
  this.Log.error('lowlevel Protocol V2 call error: ', e);
@@ -438,10 +479,7 @@ class LowlevelTransport {
438
479
  assertCurrentGeneration();
439
480
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
440
481
  },
441
- writeFrame: (frame) => {
442
- assertCurrentGeneration();
443
- return this.writeProtocolV2Frame(uuid, frame);
444
- },
482
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
445
483
  readFrame: (context) => {
446
484
  assertCurrentGeneration();
447
485
  return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
@@ -466,4 +504,6 @@ class LowlevelTransport {
466
504
  }
467
505
  }
468
506
 
469
- module.exports = LowlevelTransport;
507
+ exports["default"] = LowlevelTransport;
508
+ exports.getProtocolV1SendOptions = getProtocolV1SendOptions;
509
+ 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.9",
3
+ "version": "1.2.0-alpha.90",
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.9",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.9"
23
+ "@onekeyfe/hd-shared": "1.2.0-alpha.90",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.90"
25
25
  },
26
- "gitHead": "f90ef9921b742f68cce9f4e7a0425e45e8ae7ec9"
26
+ "gitHead": "e8271225634f3058498af8beee06d9de705b6710"
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.protocolV2Links
118
- .invalidateAllLinks('Protocol V2 schema reconfigured')
119
- .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
+ }
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(input.uuid, new ProtocolV2FrameAssembler());
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
- // 直接执行 Protocol V2 探测,避免先发送一次无意义的 V1 Initialize
211
+ // A name-derived protocol hint survives disconnect and lets fast reconnect probe
212
+ // Protocol V2 first without sending a redundant V1 Initialize.
174
213
  this.protocolV2Assemblers.delete(uuid);
175
214
  return true;
176
215
  } catch (error) {
@@ -196,20 +235,7 @@ export default class LowlevelTransport {
196
235
  `Device protocol has not been detected for ${uuid}`
197
236
  );
198
237
  }
199
- 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
- }
238
+ this.Log.debug('transport call', { name, protocol });
213
239
 
214
240
  if (protocol === 'V2') {
215
241
  return this.callProtocolV2(uuid, name, data, options);
@@ -230,12 +256,41 @@ export default class LowlevelTransport {
230
256
 
231
257
  const messages = this._messages;
232
258
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
233
- for (const o of buffers) {
259
+ const isFirmwareUpload = name === 'FirmwareUpload';
260
+ const uploadStartedAt = Date.now();
261
+ const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
262
+ let sentBytes = 0;
263
+ let lastLoggedPercent = 0;
264
+ let lastLoggedAt = uploadStartedAt;
265
+
266
+ for (const [index, o] of buffers.entries()) {
234
267
  const outData = o.toString('hex');
235
- // Upload resources on low-end phones may OOM
236
- this.Log.debug('send hex strting: ', outData);
237
268
  try {
238
- await this.plugin.send(uuid, outData);
269
+ await this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
270
+ sentBytes += o.limit;
271
+
272
+ if (isFirmwareUpload) {
273
+ const now = Date.now();
274
+ const percent = Math.floor(((index + 1) / buffers.length) * 100);
275
+ if (
276
+ shouldLogFirmwareUploadProgress({
277
+ percent,
278
+ lastLoggedPercent,
279
+ now,
280
+ lastLoggedAt,
281
+ })
282
+ ) {
283
+ const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
284
+ const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
285
+ this.Log?.debug(
286
+ `[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
287
+ `(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
288
+ `${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`
289
+ );
290
+ lastLoggedPercent = percent;
291
+ lastLoggedAt = now;
292
+ }
293
+ }
239
294
  } catch (e) {
240
295
  this.Log.debug('lowlevel transport send error: ', e);
241
296
  throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
@@ -244,10 +299,19 @@ export default class LowlevelTransport {
244
299
 
245
300
  try {
246
301
  const response = await this.readProtocolV1Message(uuid, options?.timeoutMs);
247
- this.Log.debug('receive data: ', response);
248
302
  const jsonData = ProtocolV1.decodeMessage(messages, response);
249
303
  return check.call(jsonData);
250
304
  } catch (e) {
305
+ if (
306
+ e?.errorCode === HardwareErrorCode.BleTimeoutError &&
307
+ options?.timeoutMs !== PROTOCOL_PROBE_TIMEOUT_MS
308
+ ) {
309
+ try {
310
+ await this.resetConnectionAfterProbe(uuid, 'V1');
311
+ } catch (resetError) {
312
+ this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
313
+ }
314
+ }
251
315
  if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
252
316
  this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
253
317
  } else {
@@ -290,12 +354,12 @@ export default class LowlevelTransport {
290
354
  protocolHint?: ProtocolType
291
355
  ): Promise<ProtocolType> {
292
356
  if (expectedProtocol === 'V2') {
293
- // 固件升级重启后的 bootloader 不保证响应 Ping。上层显式传入 V2
294
- // 表示协议已经在重启前确认,这里与 RN/Electron BLE 保持一致,
295
- // 直接建立 V2 Link,首个业务命令负责验证链路是否可用。
296
- this.deviceProtocol.set(uuid, 'V2');
297
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
298
- return 'V2';
357
+ if (await this.probeProtocolV2(uuid)) {
358
+ this.deviceProtocol.set(uuid, 'V2');
359
+ this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
360
+ return 'V2';
361
+ }
362
+ throw this.createProtocolMismatchError(expectedProtocol);
299
363
  }
300
364
 
301
365
  if (expectedProtocol === 'V1') {
@@ -307,31 +371,20 @@ export default class LowlevelTransport {
307
371
  throw this.createProtocolMismatchError(expectedProtocol);
308
372
  }
309
373
 
310
- if (protocolHint === 'V2' && (await this.probeProtocolV2(uuid))) {
311
- this.deviceProtocol.set(uuid, 'V2');
312
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
313
- return 'V2';
314
- }
315
-
316
- const cachedProtocol = this.deviceProtocol.get(uuid);
317
- if (cachedProtocol === 'V2' && (await this.probeProtocolV2(uuid))) {
318
- this.deviceProtocol.set(uuid, 'V2');
319
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
320
- return 'V2';
321
- }
374
+ const probeOrder: ProtocolType[] =
375
+ protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
322
376
 
323
- const protocolV1Detected = await this.probeProtocolV1(uuid);
324
- if (protocolV1Detected) {
325
- this.deviceProtocol.set(uuid, 'V1');
326
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
327
- return 'V1';
328
- }
329
-
330
- await this.resetConnectionAfterProbe(uuid, 'V1');
331
- if (await this.probeProtocolV2(uuid)) {
332
- this.deviceProtocol.set(uuid, 'V2');
333
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
334
- return 'V2';
377
+ for (const [index, protocol] of probeOrder.entries()) {
378
+ if (index > 0) {
379
+ await this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
380
+ }
381
+ const detected =
382
+ protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
383
+ if (detected) {
384
+ this.deviceProtocol.set(uuid, protocol);
385
+ this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
386
+ return protocol;
387
+ }
335
388
  }
336
389
 
337
390
  this.deviceProtocol.delete(uuid);
@@ -448,7 +501,7 @@ export default class LowlevelTransport {
448
501
  private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
449
502
  let assembler = this.protocolV2Assemblers.get(uuid);
450
503
  if (!assembler) {
451
- assembler = new ProtocolV2FrameAssembler();
504
+ assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
452
505
  this.protocolV2Assemblers.set(uuid, assembler);
453
506
  }
454
507
 
@@ -466,11 +519,20 @@ export default class LowlevelTransport {
466
519
  return frame;
467
520
  }
468
521
 
469
- private async writeProtocolV2Frame(uuid: string, frame: Uint8Array) {
470
- for (let offset = 0; offset < frame.length; offset += LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH) {
471
- const chunk = frame.slice(offset, offset + LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH);
472
- await this.plugin.send(uuid, bytesToHex(chunk));
473
- }
522
+ private async writeProtocolV2Frame(
523
+ uuid: string,
524
+ frame: Uint8Array,
525
+ context: ProtocolV2CallContext,
526
+ assertCurrentGeneration: () => void
527
+ ) {
528
+ await writeProtocolV2BleFrame({
529
+ frame,
530
+ packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
531
+ assertActive: assertCurrentGeneration,
532
+ signal: context.signal,
533
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
534
+ writePacket: packet => this.plugin.send(uuid, bytesToHex(packet)),
535
+ });
474
536
  }
475
537
 
476
538
  private async callProtocolV2(
@@ -483,18 +545,13 @@ export default class LowlevelTransport {
483
545
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
484
546
  }
485
547
 
486
- const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
487
-
488
548
  try {
489
549
  return await this.protocolV2Links.call(
490
550
  uuid,
491
551
  () => this.createProtocolV2Adapter(uuid),
492
552
  name,
493
553
  data,
494
- {
495
- ...options,
496
- timeoutMs,
497
- }
554
+ options
498
555
  );
499
556
  } catch (e) {
500
557
  this.Log.error('lowlevel Protocol V2 call error: ', e);
@@ -518,10 +575,8 @@ export default class LowlevelTransport {
518
575
  assertCurrentGeneration();
519
576
  this.protocolV2Assemblers.get(uuid)?.reset();
520
577
  },
521
- writeFrame: (frame: Uint8Array) => {
522
- assertCurrentGeneration();
523
- return this.writeProtocolV2Frame(uuid, frame);
524
- },
578
+ writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
579
+ this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
525
580
  readFrame: (context: { messageName: string; timeoutMs?: number }) => {
526
581
  assertCurrentGeneration();
527
582
  return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
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
  };