@onekeyfe/hd-transport-lowlevel 1.1.27-alpha.30 → 1.1.27-alpha.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintignore CHANGED
@@ -1,2 +1,4 @@
1
+ coverage/
1
2
  dist/
2
3
  __tests__/
4
+ jest.config.js
@@ -48,10 +48,28 @@ const protocolV2Schema = {
48
48
  },
49
49
  },
50
50
  },
51
+ Ping: {
52
+ fields: {
53
+ message: {
54
+ type: 'string',
55
+ id: 1,
56
+ },
57
+ },
58
+ },
59
+ Success: {
60
+ fields: {
61
+ message: {
62
+ type: 'string',
63
+ id: 1,
64
+ },
65
+ },
66
+ },
51
67
  MessageType: {
52
68
  values: {
53
69
  MessageType_GetProtoVersion: 60200,
54
70
  MessageType_ProtoVersion: 60201,
71
+ MessageType_Ping: 60206,
72
+ MessageType_Success: 60207,
55
73
  },
56
74
  },
57
75
  },
@@ -119,11 +137,9 @@ describe('LowlevelTransport protocol framing', () => {
119
137
  test('detects Protocol V2 devices and reassembles split Protocol V2 notifications', async () => {
120
138
  const probeResponse = ProtocolV2.encodeFrame(
121
139
  schemas,
122
- 'ProtoVersion',
140
+ 'Success',
123
141
  {
124
- major_version: 2,
125
- minor_version: 0,
126
- patch_version: 0,
142
+ message: 'probe',
127
143
  },
128
144
  { router: PROTOCOL_V2_CHANNEL_BLE_UART }
129
145
  );
@@ -144,7 +160,7 @@ describe('LowlevelTransport protocol framing', () => {
144
160
  const lowlevel = configureTransport(plugin);
145
161
 
146
162
  await expect(lowlevel.enumerate()).resolves.toEqual([
147
- { id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble', protocolType: 'V2' },
163
+ { id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble' },
148
164
  ]);
149
165
  await expect(lowlevel.acquire({ uuid: 'pro2-id' })).resolves.toEqual({
150
166
  uuid: 'pro2-id',
@@ -164,11 +180,9 @@ describe('LowlevelTransport protocol framing', () => {
164
180
  test('falls back to Protocol V2 probe for unnamed Protocol V2 devices', async () => {
165
181
  const probeResponse = ProtocolV2.encodeFrame(
166
182
  schemas,
167
- 'ProtoVersion',
183
+ 'Success',
168
184
  {
169
- major_version: 2,
170
- minor_version: 0,
171
- patch_version: 0,
185
+ message: 'probe',
172
186
  },
173
187
  { router: PROTOCOL_V2_CHANNEL_BLE_UART }
174
188
  );
@@ -185,6 +199,46 @@ describe('LowlevelTransport protocol framing', () => {
185
199
  expect(lowlevel.getProtocolType('unknown-pro2-id')).toBe('V2');
186
200
  });
187
201
 
202
+ test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
203
+ const probeResponse = ProtocolV2.encodeFrame(
204
+ schemas,
205
+ 'Success',
206
+ {
207
+ message: 'probe',
208
+ },
209
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
210
+ );
211
+ let staleReceivePending = false;
212
+ let resetAfterTimeout = false;
213
+ const plugin = createPlugin({
214
+ devices: [{ id: 'slow-v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
215
+ responses: [],
216
+ });
217
+ plugin.disconnect.mockImplementation(() => {
218
+ staleReceivePending = false;
219
+ resetAfterTimeout = true;
220
+ return Promise.resolve();
221
+ });
222
+ plugin.receive.mockImplementation(() => {
223
+ if (!staleReceivePending && !resetAfterTimeout) {
224
+ staleReceivePending = true;
225
+ return new Promise(() => {});
226
+ }
227
+ if (staleReceivePending) {
228
+ return Promise.reject(new Error('stale receive still pending'));
229
+ }
230
+ return Promise.resolve(bytesToHex(probeResponse));
231
+ });
232
+ const lowlevel = configureTransport(plugin);
233
+
234
+ await expect(lowlevel.acquire({ uuid: 'slow-v2-id' })).resolves.toEqual({
235
+ uuid: 'slow-v2-id',
236
+ protocolType: 'V2',
237
+ });
238
+ expect(plugin.disconnect).toHaveBeenCalledWith('slow-v2-id');
239
+ expect(plugin.connect).toHaveBeenCalledTimes(2);
240
+ });
241
+
188
242
  test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
189
243
  const plugin = createPlugin({
190
244
  devices: [{ id: 'v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as transport from '@onekeyfe/hd-transport';
2
- import transport__default, { ProtocolType, LowlevelTransportSharedPlugin, TransportCallOptions } from '@onekeyfe/hd-transport';
2
+ import transport__default, { ProtocolType, LowlevelTransportSharedPlugin, LowLevelDevice, TransportCallOptions } from '@onekeyfe/hd-transport';
3
3
  import EventEmitter from 'events';
4
4
 
5
5
  type LowLevelAcquireInput = {
@@ -15,18 +15,14 @@ declare class LowlevelTransport {
15
15
  emitter?: EventEmitter;
16
16
  plugin: LowlevelTransportSharedPlugin;
17
17
  private deviceProtocol;
18
+ private deviceProtocolHints;
18
19
  private protocolV2Assemblers;
19
20
  getProtocolType(path: string): ProtocolType;
20
21
  init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin): void;
21
22
  configure(signedData: any): void;
22
23
  configureProtocolV2(signedData: any): void;
23
24
  listen(): void;
24
- enumerate(): Promise<{
25
- protocolType?: ProtocolType | undefined;
26
- commType: transport.OneKeyDeviceCommType;
27
- id: string;
28
- name: string;
29
- }[]>;
25
+ enumerate(): Promise<LowLevelDevice[]>;
30
26
  acquire(input: LowLevelAcquireInput): Promise<{
31
27
  uuid: string;
32
28
  protocolType: ProtocolType;
@@ -37,6 +33,7 @@ declare class LowlevelTransport {
37
33
  private createProtocolTimeoutError;
38
34
  private createProtocolMismatchError;
39
35
  private detectProtocol;
36
+ private resetConnectionAfterProbe;
40
37
  private probeProtocolV1;
41
38
  private probeProtocolV2;
42
39
  private receiveHex;
@@ -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,EAEV,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,oBAAoB,CAAoD;IAEhF,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAI3C,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;IAInC,MAAM;IAIA,SAAS;;;;;;IAcT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IAiBnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAYpB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA6BlB,cAAc;IAuC5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;YAOrB,cAAc;YAsCd,eAAe;YAef,eAAe;YAmBf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAOpB,cAAc;IAsC5B,MAAM;CAGP"}
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,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAI3C,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;IAInC,MAAM;IAIA,SAAS;IAWT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IAuBnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAapB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA6BlB,cAAc;IAuC5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;YAOrB,cAAc;YAiDd,yBAAyB;YA0BzB,eAAe;YAef,eAAe;YAoBf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAOpB,cAAc;IAsC5B,MAAM;CAGP"}
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
42
42
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
43
43
  const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30000;
44
44
  const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
45
- function inferProtocolTypeFromDeviceName(name) {
45
+ function inferProtocolHintFromDeviceName(name) {
46
46
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
47
47
  }
48
48
  function isProtocolV1TransportChunk(data) {
@@ -56,6 +56,7 @@ class LowlevelTransport {
56
56
  this.configured = false;
57
57
  this.plugin = {};
58
58
  this.deviceProtocol = new Map();
59
+ this.deviceProtocolHints = new Map();
59
60
  this.protocolV2Assemblers = new Map();
60
61
  }
61
62
  getProtocolType(path) {
@@ -82,16 +83,16 @@ class LowlevelTransport {
82
83
  return __awaiter(this, void 0, void 0, function* () {
83
84
  const devices = yield this.plugin.enumerate();
84
85
  return devices.map((device) => {
85
- const protocolType = inferProtocolTypeFromDeviceName(device.name);
86
- if (protocolType) {
87
- this.deviceProtocol.set(device.id, protocolType);
86
+ const protocolHint = inferProtocolHintFromDeviceName(device.name);
87
+ if (protocolHint) {
88
+ this.deviceProtocolHints.set(device.id, protocolHint);
88
89
  }
89
- return Object.assign(Object.assign({}, device), (protocolType ? { protocolType } : {}));
90
+ return device;
90
91
  });
91
92
  });
92
93
  }
93
94
  acquire(input) {
94
- var _a, _b;
95
+ var _a;
95
96
  return __awaiter(this, void 0, void 0, function* () {
96
97
  try {
97
98
  yield this.plugin.connect(input.uuid);
@@ -101,8 +102,10 @@ class LowlevelTransport {
101
102
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_a = error.message) !== null && _a !== void 0 ? _a : error);
102
103
  }
103
104
  this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler());
104
- const expectedProtocol = (_b = input.expectedProtocol) !== null && _b !== void 0 ? _b : this.deviceProtocol.get(input.uuid);
105
- const protocolType = yield this.detectProtocol(input.uuid, expectedProtocol);
105
+ const protocolHint = input.expectedProtocol
106
+ ? undefined
107
+ : this.deviceProtocolHints.get(input.uuid);
108
+ const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
106
109
  return { uuid: input.uuid, protocolType };
107
110
  });
108
111
  }
@@ -111,6 +114,7 @@ class LowlevelTransport {
111
114
  try {
112
115
  yield this.plugin.disconnect(uuid);
113
116
  this.deviceProtocol.delete(uuid);
117
+ this.deviceProtocolHints.delete(uuid);
114
118
  this.protocolV2Assemblers.delete(uuid);
115
119
  return true;
116
120
  }
@@ -179,8 +183,8 @@ class LowlevelTransport {
179
183
  createProtocolMismatchError(expected) {
180
184
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
181
185
  }
182
- detectProtocol(uuid, expectedProtocol) {
183
- var _a, _b, _c, _d;
186
+ detectProtocol(uuid, expectedProtocol, protocolHint) {
187
+ var _a, _b, _c, _d, _e;
184
188
  return __awaiter(this, void 0, void 0, function* () {
185
189
  if (expectedProtocol === 'V2') {
186
190
  if (yield this.probeProtocolV2(uuid)) {
@@ -198,21 +202,49 @@ class LowlevelTransport {
198
202
  }
199
203
  throw this.createProtocolMismatchError(expectedProtocol);
200
204
  }
205
+ if (protocolHint === 'V2' && (yield this.probeProtocolV2(uuid))) {
206
+ this.deviceProtocol.set(uuid, 'V2');
207
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
208
+ return 'V2';
209
+ }
201
210
  const cachedProtocol = this.deviceProtocol.get(uuid);
202
211
  if (cachedProtocol === 'V2' && (yield this.probeProtocolV2(uuid))) {
203
212
  this.deviceProtocol.set(uuid, 'V2');
204
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
213
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
205
214
  return 'V2';
206
215
  }
207
216
  let protocol = 'V1';
208
- if (!(yield this.probeProtocolV1(uuid)) && (yield this.probeProtocolV2(uuid))) {
209
- protocol = 'V2';
217
+ const protocolV1Detected = yield this.probeProtocolV1(uuid);
218
+ if (!protocolV1Detected) {
219
+ yield this.resetConnectionAfterProbe(uuid, 'V1');
220
+ if (yield this.probeProtocolV2(uuid)) {
221
+ protocol = 'V2';
222
+ }
210
223
  }
211
224
  this.deviceProtocol.set(uuid, protocol);
212
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
225
+ (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
213
226
  return protocol;
214
227
  });
215
228
  }
229
+ resetConnectionAfterProbe(uuid, protocol) {
230
+ var _a, _b, _c, _d;
231
+ return __awaiter(this, void 0, void 0, function* () {
232
+ (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
233
+ try {
234
+ yield this.plugin.disconnect(uuid);
235
+ }
236
+ catch (error) {
237
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[LowlevelTransport] disconnect after Protocol ${protocol} probe failed:`, error);
238
+ }
239
+ try {
240
+ yield this.plugin.connect(uuid);
241
+ }
242
+ catch (error) {
243
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`, error);
244
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_d = error.message) !== null && _d !== void 0 ? _d : error);
245
+ }
246
+ });
247
+ }
216
248
  probeProtocolV1(uuid) {
217
249
  var _a;
218
250
  return __awaiter(this, void 0, void 0, function* () {
@@ -243,10 +275,11 @@ class LowlevelTransport {
243
275
  timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
244
276
  logger: this.Log,
245
277
  logPrefix: 'ProtocolV2 Lowlevel-BLE',
246
- onProbeFailed: () => {
247
- var _a;
248
- (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
249
- },
278
+ onProbeFailed: () => __awaiter(this, void 0, void 0, function* () {
279
+ var _b;
280
+ (_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
281
+ yield this.resetConnectionAfterProbe(uuid, 'V2');
282
+ }),
250
283
  });
251
284
  });
252
285
  }
package/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ preset: '../../jest.config.js',
3
+ testEnvironment: 'node',
4
+ modulePathIgnorePatterns: ['node_modules', '<rootDir>/lib', '<rootDir>/libDev'],
5
+ collectCoverage: true,
6
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-lowlevel",
3
- "version": "1.1.27-alpha.30",
3
+ "version": "1.1.27-alpha.32",
4
4
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -15,12 +15,13 @@
15
15
  "scripts": {
16
16
  "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
17
17
  "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
18
+ "test": "jest",
18
19
  "lint": "eslint .",
19
20
  "lint:fix": "eslint . --fix"
20
21
  },
21
22
  "dependencies": {
22
- "@onekeyfe/hd-shared": "1.1.27-alpha.30",
23
- "@onekeyfe/hd-transport": "1.1.27-alpha.30"
23
+ "@onekeyfe/hd-shared": "1.1.27-alpha.32",
24
+ "@onekeyfe/hd-transport": "1.1.27-alpha.32"
24
25
  },
25
- "gitHead": "f1e3a93e766463007436eaa9aff4a271ffd8830c"
26
+ "gitHead": "04f92052a18dcf17e4a347f3003b4c4bbf062681"
26
27
  }
package/src/index.ts CHANGED
@@ -28,7 +28,7 @@ const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
28
28
  const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30_000;
29
29
  const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
30
30
 
31
- function inferProtocolTypeFromDeviceName(name?: string | null): ProtocolType | undefined {
31
+ function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
32
32
  return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
33
33
  }
34
34
 
@@ -55,6 +55,8 @@ export default class LowlevelTransport {
55
55
 
56
56
  private deviceProtocol: Map<string, ProtocolType> = new Map();
57
57
 
58
+ private deviceProtocolHints: Map<string, ProtocolType> = new Map();
59
+
58
60
  private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
59
61
 
60
62
  getProtocolType(path: string): ProtocolType {
@@ -85,14 +87,11 @@ export default class LowlevelTransport {
85
87
  async enumerate() {
86
88
  const devices = await this.plugin.enumerate();
87
89
  return devices.map((device: LowLevelDevice) => {
88
- const protocolType = inferProtocolTypeFromDeviceName(device.name);
89
- if (protocolType) {
90
- this.deviceProtocol.set(device.id, protocolType);
90
+ const protocolHint = inferProtocolHintFromDeviceName(device.name);
91
+ if (protocolHint) {
92
+ this.deviceProtocolHints.set(device.id, protocolHint);
91
93
  }
92
- return {
93
- ...device,
94
- ...(protocolType ? { protocolType } : {}),
95
- };
94
+ return device;
96
95
  });
97
96
  }
98
97
 
@@ -108,8 +107,14 @@ export default class LowlevelTransport {
108
107
  }
109
108
 
110
109
  this.protocolV2Assemblers.set(input.uuid, new ProtocolV2FrameAssembler());
111
- const expectedProtocol = input.expectedProtocol ?? this.deviceProtocol.get(input.uuid);
112
- const protocolType = await this.detectProtocol(input.uuid, expectedProtocol);
110
+ const protocolHint = input.expectedProtocol
111
+ ? undefined
112
+ : this.deviceProtocolHints.get(input.uuid);
113
+ const protocolType = await this.detectProtocol(
114
+ input.uuid,
115
+ input.expectedProtocol,
116
+ protocolHint
117
+ );
113
118
  return { uuid: input.uuid, protocolType };
114
119
  }
115
120
 
@@ -117,6 +122,7 @@ export default class LowlevelTransport {
117
122
  try {
118
123
  await this.plugin.disconnect(uuid);
119
124
  this.deviceProtocol.delete(uuid);
125
+ this.deviceProtocolHints.delete(uuid);
120
126
  this.protocolV2Assemblers.delete(uuid);
121
127
  return true;
122
128
  } catch (error) {
@@ -213,7 +219,8 @@ export default class LowlevelTransport {
213
219
 
214
220
  private async detectProtocol(
215
221
  uuid: string,
216
- expectedProtocol?: ProtocolType
222
+ expectedProtocol?: ProtocolType,
223
+ protocolHint?: ProtocolType
217
224
  ): Promise<ProtocolType> {
218
225
  if (expectedProtocol === 'V2') {
219
226
  if (await this.probeProtocolV2(uuid)) {
@@ -233,6 +240,12 @@ export default class LowlevelTransport {
233
240
  throw this.createProtocolMismatchError(expectedProtocol);
234
241
  }
235
242
 
243
+ if (protocolHint === 'V2' && (await this.probeProtocolV2(uuid))) {
244
+ this.deviceProtocol.set(uuid, 'V2');
245
+ this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
246
+ return 'V2';
247
+ }
248
+
236
249
  const cachedProtocol = this.deviceProtocol.get(uuid);
237
250
  if (cachedProtocol === 'V2' && (await this.probeProtocolV2(uuid))) {
238
251
  this.deviceProtocol.set(uuid, 'V2');
@@ -241,14 +254,44 @@ export default class LowlevelTransport {
241
254
  }
242
255
 
243
256
  let protocol: ProtocolType = 'V1';
244
- if (!(await this.probeProtocolV1(uuid)) && (await this.probeProtocolV2(uuid))) {
245
- protocol = 'V2';
257
+ const protocolV1Detected = await this.probeProtocolV1(uuid);
258
+ if (!protocolV1Detected) {
259
+ await this.resetConnectionAfterProbe(uuid, 'V1');
260
+ if (await this.probeProtocolV2(uuid)) {
261
+ protocol = 'V2';
262
+ }
246
263
  }
247
264
  this.deviceProtocol.set(uuid, protocol);
248
265
  this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
249
266
  return protocol;
250
267
  }
251
268
 
269
+ private async resetConnectionAfterProbe(uuid: string, protocol: ProtocolType) {
270
+ this.protocolV2Assemblers.get(uuid)?.reset();
271
+
272
+ try {
273
+ await this.plugin.disconnect(uuid);
274
+ } catch (error) {
275
+ this.Log?.debug(
276
+ `[LowlevelTransport] disconnect after Protocol ${protocol} probe failed:`,
277
+ error
278
+ );
279
+ }
280
+
281
+ try {
282
+ await this.plugin.connect(uuid);
283
+ } catch (error) {
284
+ this.Log?.debug(
285
+ `[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`,
286
+ error
287
+ );
288
+ throw ERRORS.TypedError(
289
+ HardwareErrorCode.LowlevelTrasnportConnectError,
290
+ error.message ?? error
291
+ );
292
+ }
293
+ }
294
+
252
295
  private async probeProtocolV1(uuid: string) {
253
296
  if (!this._messages) {
254
297
  return false;
@@ -277,8 +320,9 @@ export default class LowlevelTransport {
277
320
  timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
278
321
  logger: this.Log,
279
322
  logPrefix: 'ProtocolV2 Lowlevel-BLE',
280
- onProbeFailed: () => {
323
+ onProbeFailed: async () => {
281
324
  this.protocolV2Assemblers.get(uuid)?.reset();
325
+ await this.resetConnectionAfterProbe(uuid, 'V2');
282
326
  },
283
327
  });
284
328
  }