Skip to content

Commit 68abba0

Browse files
authored
fix: De-conflict Sonoff dongles discovery (#1377)
1 parent 206fa80 commit 68abba0

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

src/adapter/adapterDiscovery.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,22 @@ const USB_FINGERPRINTS: Record<DiscoverableUsbAdapter, UsbAdapterFingerprint[]>
100100
pathRegex: '.*slzb-07mg24.*',
101101
},
102102
{
103-
// Sonoff ZBDongle-E V2
103+
// Sonoff ZBDongle-E V2 (CH variant)
104104
vendorId: '1a86',
105105
productId: '55d4',
106106
manufacturer: 'ITEAD',
107107
// /dev/serial/by-id/usb-ITEAD_SONOFF_Zigbee_3.0_USB_Dongle_Plus_V2_20240122184111-if00
108108
// /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_186ff44314e2ed11b891eb5162c61111-if00-port0
109109
pathRegex: '.*sonoff.*plus.*',
110110
},
111+
{
112+
// Sonoff ZBDongle-E V2 (CP variant)
113+
vendorId: '10c4',
114+
productId: 'ea60',
115+
manufacturer: 'ITEAD',
116+
// /dev/serial/by-id/usb-Itead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_V2_a6ee897e4d1fef11aa004ad0639e525b-if00-port0
117+
pathRegex: '.*sonoff.*plus_v2_.*',
118+
},
111119
// {
112120
// // TODO: Z-station by z-wave.me (EFR32MG21A020F1024IM32)
113121
// vendorId: '',
@@ -140,7 +148,7 @@ const USB_FINGERPRINTS: Record<DiscoverableUsbAdapter, UsbAdapterFingerprint[]>
140148
manufacturer: 'ITEAD',
141149
// /dev/serial/by-id/usb-Silicon_Labs_Sonoff_Zigbee_3.0_USB_Dongle_Plus_0111-if00-port0
142150
// /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_b8b49abd27a6ed11a280eba32981d111-if00-port0
143-
pathRegex: '.*sonoff.*plus.*',
151+
pathRegex: '.*sonoff.*plus(?!_v2_).*',
144152
},
145153
{
146154
// CC2538
@@ -297,7 +305,7 @@ function matchUsbFingerprint(
297305
): [path: PortInfo['path'], score: number] | undefined {
298306
if (!portInfo.vendorId || !portInfo.productId) {
299307
// port info is missing essential information for proper matching, ignore it
300-
return;
308+
return undefined;
301309
}
302310

303311
let match: UsbAdapterFingerprint | undefined;
@@ -346,7 +354,20 @@ function matchUsbFingerprint(
346354
}
347355

348356
// poor match only returned if port info not conflict-prone
349-
return match && (score > UsbFingerprintMatchScore.VID_PID || !conflictProne) ? [portInfo.path, score] : undefined;
357+
if (match) {
358+
if (score > UsbFingerprintMatchScore.VID_PID) {
359+
if (conflictProne && score < UsbFingerprintMatchScore.VID_PID_PATH && matchString(match.manufacturer!, 'itead')) {
360+
// can't trust metadata "only" on sonoff dongles with conflicts
361+
return undefined;
362+
}
363+
364+
return [portInfo.path, score];
365+
} else if (!conflictProne) {
366+
return [portInfo.path, score];
367+
}
368+
}
369+
370+
return undefined;
350371
}
351372

352373
export async function matchUsbAdapter(adapter: Adapter, path: string): Promise<boolean> {

test/adapter/adapter.test.ts

+33-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
DECONZ_CONBEE_II,
1717
EMBER_SKYCONNECT,
1818
EMBER_ZBDONGLE_E,
19+
EMBER_ZBDONGLE_E_CP,
1920
ZBOSS_NORDIC,
2021
ZIGATE_PLUSV2,
2122
ZSTACK_CC2538,
@@ -167,7 +168,7 @@ describe('Adapter', () => {
167168
// on Windows
168169
mockPlatform.mockReturnValueOnce('win32');
169170
vi.spyOn(SerialPort, 'list').mockResolvedValueOnce([
170-
Object.assign({pnpId: 'zbdongle-e', serialNumber: '', locationId: '', friendlyName: 'silicon labs cp210x'}, EMBER_ZBDONGLE_E),
171+
Object.assign({pnpId: 'zbdongle-e', serialNumber: '', locationId: '', friendlyName: 'silicon labs cp210x'}, EMBER_ZBDONGLE_E_CP),
171172
]);
172173
// `name` in `txt`, no `addresses`
173174
mockBonjourResult.mockImplementationOnce((type) => ({
@@ -196,7 +197,7 @@ describe('Adapter', () => {
196197
await expect(p).resolves.toStrictEqual([
197198
{
198199
name: 'silicon labs cp210x (ITEAD)',
199-
path: '/dev/serial/by-id/usb-ITEAD_SONOFF_Zigbee_3.0_USB_Dongle_Plus_V2_20240122184111-if00',
200+
path: EMBER_ZBDONGLE_E_CP.path,
200201
adapter: undefined,
201202
},
202203
{
@@ -514,6 +515,14 @@ describe('Adapter', () => {
514515

515516
listSpy.mockReturnValueOnce([{...ZSTACK_ZBDONGLE_P, path: '/dev/ttyACM0'}]);
516517

518+
await expect(Adapter.create({panID: 0x1a62, channelList: [11]}, {}, 'test.db.backup', {disableLED: false})).rejects.toThrow(
519+
`USB adapter discovery error (No valid USB adapter found). Specify valid 'adapter' and 'port' in your configuration.`,
520+
);
521+
522+
listSpy.mockReturnValueOnce([
523+
{...ZSTACK_ZBDONGLE_P, path: '/dev/ttyACM0', pnpId: ZSTACK_ZBDONGLE_P.path.replace('/dev/serial/by-id/', '')},
524+
]);
525+
517526
adapter = await Adapter.create({panID: 0x1a62, channelList: [11]}, {}, 'test.db.backup', {disableLED: false});
518527

519528
expect(adapter).toBeInstanceOf(ZStackAdapter);
@@ -523,6 +532,28 @@ describe('Adapter', () => {
523532
adapter: 'zstack',
524533
});
525534

535+
listSpy.mockReturnValueOnce([ZSTACK_ZBDONGLE_P]);
536+
537+
adapter = await Adapter.create({panID: 0x1a62, channelList: [11]}, {}, 'test.db.backup', {disableLED: false});
538+
539+
expect(adapter).toBeInstanceOf(ZStackAdapter);
540+
// @ts-expect-error protected
541+
expect(adapter.serialPortOptions).toStrictEqual({
542+
path: ZSTACK_ZBDONGLE_P.path,
543+
adapter: 'zstack',
544+
});
545+
546+
listSpy.mockReturnValueOnce([EMBER_ZBDONGLE_E_CP]);
547+
548+
adapter = await Adapter.create({panID: 0x1a62, channelList: [11]}, {}, 'test.db.backup', {disableLED: false});
549+
550+
expect(adapter).toBeInstanceOf(EmberAdapter);
551+
// @ts-expect-error protected
552+
expect(adapter.serialPortOptions).toStrictEqual({
553+
path: EMBER_ZBDONGLE_E_CP.path,
554+
adapter: 'ember',
555+
});
556+
526557
listSpy.mockReturnValueOnce([ZSTACK_SMLIGHT_SLZB_06P10]);
527558

528559
adapter = await Adapter.create({panID: 0x1a62, channelList: [11]}, {}, 'test.db.backup', {disableLED: false});

test/mockAdapters.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@ export const DECONZ_CONBEE_II = {
55
manufacturer: 'dresden elektronik ingenieurtechnik GmbH',
66
};
77
export const EMBER_ZBDONGLE_E = {
8+
// may or may not have `V2` (bad metadata in some batches)
89
path: '/dev/serial/by-id/usb-ITEAD_SONOFF_Zigbee_3.0_USB_Dongle_Plus_V2_20240122184111-if00',
910
vendorId: '1A86', // uppercased for extra coverage
1011
productId: '55d4',
1112
manufacturer: 'ITEAD',
1213
};
14+
export const EMBER_ZBDONGLE_E_CP = {
15+
// may or may not have `V2` (bad metadata in some batches)
16+
path: '/dev/serial/by-id/usb-Itead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_V2_a6ee897e4d1fef11aa004ad0639e525b-if00-port0',
17+
vendorId: '10c4',
18+
productId: 'ea60',
19+
manufacturer: 'ITEAD',
20+
};
1321
// vendorId+productId conflict with all 10c4:ea60
1422
export const EMBER_SKYCONNECT = {
1523
path: '/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_3abe54797c91ed118fc3cad13b20a111-if00-port0',
@@ -25,7 +33,7 @@ export const ZSTACK_CC2538 = {
2533
};
2634
// vendorId+productId conflict with all 10c4:ea60
2735
export const ZSTACK_ZBDONGLE_P = {
28-
path: '/dev/serial/by-id/usb-Silicon_Labs_Sonoff_Zigbee_3.0_USB_Dongle_Plus_0111-if00-port0',
36+
path: '/dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_b8b49abd27a6ed11a280eba32981d111-if00-port0',
2937
vendorId: '10c4',
3038
productId: 'ea60',
3139
manufacturer: 'ITEAD',

0 commit comments

Comments
 (0)