@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.11 → 1.2.0-alpha.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;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.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,6 @@ class LowlevelTransport {
174
193
  if (!protocol) {
175
194
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
176
195
  }
177
- if (transport.LogBlockCommand.has(name)) {
178
- this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' protocol: ', protocol);
179
- }
180
- else {
181
- this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
182
- }
183
196
  if (protocol === 'V2') {
184
197
  return this.callProtocolV2(uuid, name, data, options);
185
198
  }
@@ -187,17 +200,42 @@ class LowlevelTransport {
187
200
  });
188
201
  }
189
202
  callProtocolV1(uuid, name, data, options) {
203
+ var _a;
190
204
  return __awaiter(this, void 0, void 0, function* () {
191
205
  if (!this._messages) {
192
206
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
193
207
  }
194
208
  const messages = this._messages;
195
209
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
196
- for (const o of buffers) {
210
+ const isFirmwareUpload = name === 'FirmwareUpload';
211
+ const uploadStartedAt = Date.now();
212
+ const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
213
+ let sentBytes = 0;
214
+ let lastLoggedPercent = 0;
215
+ let lastLoggedAt = uploadStartedAt;
216
+ for (const [index, o] of buffers.entries()) {
197
217
  const outData = o.toString('hex');
198
- this.Log.debug('send hex strting: ', outData);
199
218
  try {
200
- yield this.plugin.send(uuid, outData);
219
+ yield this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
220
+ sentBytes += o.limit;
221
+ if (isFirmwareUpload) {
222
+ const now = Date.now();
223
+ const percent = Math.floor(((index + 1) / buffers.length) * 100);
224
+ if (shouldLogFirmwareUploadProgress({
225
+ percent,
226
+ lastLoggedPercent,
227
+ now,
228
+ lastLoggedAt,
229
+ })) {
230
+ const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
231
+ const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
232
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
233
+ `(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
234
+ `${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`);
235
+ lastLoggedPercent = percent;
236
+ lastLoggedAt = now;
237
+ }
238
+ }
201
239
  }
202
240
  catch (e) {
203
241
  this.Log.debug('lowlevel transport send error: ', e);
@@ -206,11 +244,19 @@ class LowlevelTransport {
206
244
  }
207
245
  try {
208
246
  const response = yield this.readProtocolV1Message(uuid, options === null || options === void 0 ? void 0 : options.timeoutMs);
209
- this.Log.debug('receive data: ', response);
210
247
  const jsonData = ProtocolV1.decodeMessage(messages, response);
211
248
  return check.call(jsonData);
212
249
  }
213
250
  catch (e) {
251
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
252
+ (options === null || options === void 0 ? void 0 : options.timeoutMs) !== PROTOCOL_PROBE_TIMEOUT_MS) {
253
+ try {
254
+ yield this.resetConnectionAfterProbe(uuid, 'V1');
255
+ }
256
+ catch (resetError) {
257
+ this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
258
+ }
259
+ }
214
260
  if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
215
261
  this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
216
262
  }
@@ -236,12 +282,15 @@ class LowlevelTransport {
236
282
  }
237
283
  }
238
284
  detectProtocol(uuid, expectedProtocol, protocolHint) {
239
- var _a, _b, _c, _d, _e, _f;
285
+ var _a, _b, _c;
240
286
  return __awaiter(this, void 0, void 0, function* () {
241
287
  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';
288
+ if (yield this.probeProtocolV2(uuid)) {
289
+ this.deviceProtocol.set(uuid, 'V2');
290
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
291
+ return 'V2';
292
+ }
293
+ throw this.createProtocolMismatchError(expectedProtocol);
245
294
  }
246
295
  if (expectedProtocol === 'V1') {
247
296
  if (yield this.probeProtocolV1(uuid)) {
@@ -251,28 +300,17 @@ class LowlevelTransport {
251
300
  }
252
301
  throw this.createProtocolMismatchError(expectedProtocol);
253
302
  }
254
- 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';
303
+ const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
304
+ for (const [index, protocol] of probeOrder.entries()) {
305
+ if (index > 0) {
306
+ yield this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
307
+ }
308
+ const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
309
+ if (detected) {
310
+ this.deviceProtocol.set(uuid, protocol);
311
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
312
+ return protocol;
313
+ }
276
314
  }
277
315
  this.deviceProtocol.delete(uuid);
278
316
  throw this.createProtocolDetectionError();
@@ -380,7 +418,7 @@ class LowlevelTransport {
380
418
  return __awaiter(this, void 0, void 0, function* () {
381
419
  let assembler = this.protocolV2Assemblers.get(uuid);
382
420
  if (!assembler) {
383
- assembler = new transport.ProtocolV2FrameAssembler();
421
+ assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
384
422
  this.protocolV2Assemblers.set(uuid, assembler);
385
423
  }
386
424
  const queuedFrame = assembler.push(new Uint8Array(0));
@@ -397,23 +435,25 @@ class LowlevelTransport {
397
435
  return frame;
398
436
  });
399
437
  }
400
- writeProtocolV2Frame(uuid, frame) {
438
+ writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
401
439
  return __awaiter(this, void 0, void 0, function* () {
402
- 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
- }
440
+ yield transport.writeProtocolV2BleFrame({
441
+ frame,
442
+ packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
443
+ assertActive: assertCurrentGeneration,
444
+ signal: context.signal,
445
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
446
+ writePacket: packet => this.plugin.send(uuid, transport.bytesToHex(packet)),
447
+ });
406
448
  });
407
449
  }
408
450
  callProtocolV2(uuid, name, data, options) {
409
- var _a;
410
451
  return __awaiter(this, void 0, void 0, function* () {
411
452
  if (!this._messages || !this._messagesV2) {
412
453
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
413
454
  }
414
- const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
415
455
  try {
416
- return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, Object.assign(Object.assign({}, options), { timeoutMs }));
456
+ return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
417
457
  }
418
458
  catch (e) {
419
459
  this.Log.error('lowlevel Protocol V2 call error: ', e);
@@ -438,10 +478,7 @@ class LowlevelTransport {
438
478
  assertCurrentGeneration();
439
479
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
440
480
  },
441
- writeFrame: (frame) => {
442
- assertCurrentGeneration();
443
- return this.writeProtocolV2Frame(uuid, frame);
444
- },
481
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
445
482
  readFrame: (context) => {
446
483
  assertCurrentGeneration();
447
484
  return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
@@ -466,4 +503,6 @@ class LowlevelTransport {
466
503
  }
467
504
  }
468
505
 
469
- module.exports = LowlevelTransport;
506
+ exports["default"] = LowlevelTransport;
507
+ exports.getProtocolV1SendOptions = getProtocolV1SendOptions;
508
+ 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.11",
3
+ "version": "1.2.0-alpha.111",
4
4
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -20,8 +20,8 @@
20
20
  "lint:fix": "eslint . --fix"
21
21
  },
22
22
  "dependencies": {
23
- "@onekeyfe/hd-shared": "1.2.0-alpha.11",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.11"
23
+ "@onekeyfe/hd-shared": "1.2.0-alpha.111",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.111"
25
25
  },
26
- "gitHead": "05d3421ec00e07e99f6665e6436a612503e32dfc"
26
+ "gitHead": "7619f316471ad423fb83f68e82e1aec89f34ac0a"
27
27
  }
package/src/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
  import transport, {
3
- LogBlockCommand,
4
3
  PROTOCOL_V1_MESSAGE_HEADER_SIZE,
5
4
  PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
6
5
  PROTOCOL_V2_CHANNEL_BLE_UART,
@@ -11,6 +10,7 @@ import transport, {
11
10
  hexToBytes,
12
11
  probeProtocolV2 as probeProtocolV2Helper,
13
12
  withProtocolTimeout,
13
+ writeProtocolV2BleFrame,
14
14
  } from '@onekeyfe/hd-transport';
15
15
 
16
16
  import type EventEmitter from 'events';
@@ -18,6 +18,7 @@ import type {
18
18
  LowLevelDevice,
19
19
  LowlevelTransportSharedPlugin,
20
20
  ProtocolType,
21
+ ProtocolV2CallContext,
21
22
  TransportCallOptions,
22
23
  } from '@onekeyfe/hd-transport';
23
24
  import type { LowLevelAcquireInput } from './types';
@@ -26,8 +27,31 @@ const { check, ProtocolV1, parseConfigure } = transport;
26
27
 
27
28
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
28
29
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
29
- const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30_000;
30
30
  const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
31
+ const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
32
+ const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10_000;
33
+
34
+ export function shouldLogFirmwareUploadProgress({
35
+ percent,
36
+ lastLoggedPercent,
37
+ now,
38
+ lastLoggedAt,
39
+ }: {
40
+ percent: number;
41
+ lastLoggedPercent: number;
42
+ now: number;
43
+ lastLoggedAt: number;
44
+ }) {
45
+ return (
46
+ percent === 100 ||
47
+ percent - lastLoggedPercent >= FIRMWARE_UPLOAD_LOG_PERCENT_STEP ||
48
+ now - lastLoggedAt >= FIRMWARE_UPLOAD_LOG_INTERVAL_MS
49
+ );
50
+ }
51
+
52
+ export function getProtocolV1SendOptions(name: string) {
53
+ return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
54
+ }
31
55
 
32
56
  function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
33
57
  return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
@@ -95,6 +119,8 @@ export default class LowlevelTransport {
95
119
  },
96
120
  });
97
121
 
122
+ private protocolV2SchemaConfiguration: string | undefined;
123
+
98
124
  getProtocolType(path: string): ProtocolType | undefined {
99
125
  return this.deviceProtocol.get(path);
100
126
  }
@@ -113,10 +139,20 @@ export default class LowlevelTransport {
113
139
  }
114
140
 
115
141
  configureProtocolV2(signedData: any) {
142
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
143
+ if (this.protocolV2SchemaConfiguration === configuration) {
144
+ return;
145
+ }
146
+
147
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
116
148
  this._messagesV2 = parseConfigure(signedData);
117
- this.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,21 +235,6 @@ export default class LowlevelTransport {
196
235
  `Device protocol has not been detected for ${uuid}`
197
236
  );
198
237
  }
199
- if (LogBlockCommand.has(name)) {
200
- this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' protocol: ', protocol);
201
- } else {
202
- this.Log.debug(
203
- 'lowlevel-transport',
204
- 'call-',
205
- ' name: ',
206
- name,
207
- ' data: ',
208
- data,
209
- ' protocol: ',
210
- protocol
211
- );
212
- }
213
-
214
238
  if (protocol === 'V2') {
215
239
  return this.callProtocolV2(uuid, name, data, options);
216
240
  }
@@ -230,12 +254,41 @@ export default class LowlevelTransport {
230
254
 
231
255
  const messages = this._messages;
232
256
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
233
- for (const o of buffers) {
257
+ const isFirmwareUpload = name === 'FirmwareUpload';
258
+ const uploadStartedAt = Date.now();
259
+ const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
260
+ let sentBytes = 0;
261
+ let lastLoggedPercent = 0;
262
+ let lastLoggedAt = uploadStartedAt;
263
+
264
+ for (const [index, o] of buffers.entries()) {
234
265
  const outData = o.toString('hex');
235
- // Upload resources on low-end phones may OOM
236
- this.Log.debug('send hex strting: ', outData);
237
266
  try {
238
- await this.plugin.send(uuid, outData);
267
+ await this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
268
+ sentBytes += o.limit;
269
+
270
+ if (isFirmwareUpload) {
271
+ const now = Date.now();
272
+ const percent = Math.floor(((index + 1) / buffers.length) * 100);
273
+ if (
274
+ shouldLogFirmwareUploadProgress({
275
+ percent,
276
+ lastLoggedPercent,
277
+ now,
278
+ lastLoggedAt,
279
+ })
280
+ ) {
281
+ const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
282
+ const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
283
+ this.Log?.debug(
284
+ `[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
285
+ `(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
286
+ `${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`
287
+ );
288
+ lastLoggedPercent = percent;
289
+ lastLoggedAt = now;
290
+ }
291
+ }
239
292
  } catch (e) {
240
293
  this.Log.debug('lowlevel transport send error: ', e);
241
294
  throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
@@ -244,10 +297,19 @@ export default class LowlevelTransport {
244
297
 
245
298
  try {
246
299
  const response = await this.readProtocolV1Message(uuid, options?.timeoutMs);
247
- this.Log.debug('receive data: ', response);
248
300
  const jsonData = ProtocolV1.decodeMessage(messages, response);
249
301
  return check.call(jsonData);
250
302
  } catch (e) {
303
+ if (
304
+ e?.errorCode === HardwareErrorCode.BleTimeoutError &&
305
+ options?.timeoutMs !== PROTOCOL_PROBE_TIMEOUT_MS
306
+ ) {
307
+ try {
308
+ await this.resetConnectionAfterProbe(uuid, 'V1');
309
+ } catch (resetError) {
310
+ this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
311
+ }
312
+ }
251
313
  if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
252
314
  this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
253
315
  } else {
@@ -290,12 +352,12 @@ export default class LowlevelTransport {
290
352
  protocolHint?: ProtocolType
291
353
  ): Promise<ProtocolType> {
292
354
  if (expectedProtocol === 'V2') {
293
- // 固件升级重启后的 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';
355
+ if (await this.probeProtocolV2(uuid)) {
356
+ this.deviceProtocol.set(uuid, 'V2');
357
+ this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
358
+ return 'V2';
359
+ }
360
+ throw this.createProtocolMismatchError(expectedProtocol);
299
361
  }
300
362
 
301
363
  if (expectedProtocol === 'V1') {
@@ -307,31 +369,20 @@ export default class LowlevelTransport {
307
369
  throw this.createProtocolMismatchError(expectedProtocol);
308
370
  }
309
371
 
310
- 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
- }
372
+ const probeOrder: ProtocolType[] =
373
+ protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
322
374
 
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';
375
+ for (const [index, protocol] of probeOrder.entries()) {
376
+ if (index > 0) {
377
+ await this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
378
+ }
379
+ const detected =
380
+ protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
381
+ if (detected) {
382
+ this.deviceProtocol.set(uuid, protocol);
383
+ this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
384
+ return protocol;
385
+ }
335
386
  }
336
387
 
337
388
  this.deviceProtocol.delete(uuid);
@@ -448,7 +499,7 @@ export default class LowlevelTransport {
448
499
  private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
449
500
  let assembler = this.protocolV2Assemblers.get(uuid);
450
501
  if (!assembler) {
451
- assembler = new ProtocolV2FrameAssembler();
502
+ assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
452
503
  this.protocolV2Assemblers.set(uuid, assembler);
453
504
  }
454
505
 
@@ -466,11 +517,20 @@ export default class LowlevelTransport {
466
517
  return frame;
467
518
  }
468
519
 
469
- private async writeProtocolV2Frame(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
- }
520
+ private async writeProtocolV2Frame(
521
+ uuid: string,
522
+ frame: Uint8Array,
523
+ context: ProtocolV2CallContext,
524
+ assertCurrentGeneration: () => void
525
+ ) {
526
+ await writeProtocolV2BleFrame({
527
+ frame,
528
+ packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
529
+ assertActive: assertCurrentGeneration,
530
+ signal: context.signal,
531
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
532
+ writePacket: packet => this.plugin.send(uuid, bytesToHex(packet)),
533
+ });
474
534
  }
475
535
 
476
536
  private async callProtocolV2(
@@ -483,18 +543,13 @@ export default class LowlevelTransport {
483
543
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
484
544
  }
485
545
 
486
- const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
487
-
488
546
  try {
489
547
  return await this.protocolV2Links.call(
490
548
  uuid,
491
549
  () => this.createProtocolV2Adapter(uuid),
492
550
  name,
493
551
  data,
494
- {
495
- ...options,
496
- timeoutMs,
497
- }
552
+ options
498
553
  );
499
554
  } catch (e) {
500
555
  this.Log.error('lowlevel Protocol V2 call error: ', e);
@@ -518,10 +573,8 @@ export default class LowlevelTransport {
518
573
  assertCurrentGeneration();
519
574
  this.protocolV2Assemblers.get(uuid)?.reset();
520
575
  },
521
- writeFrame: (frame: Uint8Array) => {
522
- assertCurrentGeneration();
523
- return this.writeProtocolV2Frame(uuid, frame);
524
- },
576
+ writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
577
+ this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
525
578
  readFrame: (context: { messageName: string; timeoutMs?: number }) => {
526
579
  assertCurrentGeneration();
527
580
  return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
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
  };