Fix G502 X receiver persistence matching

This commit is contained in:
Aron W 2026-05-15 20:03:52 +02:00
parent 4d1f9dc6c1
commit e5bd8db19e
3 changed files with 93 additions and 3 deletions

View File

@ -423,6 +423,7 @@ _D("G703 Hero Gaming Mouse", codename="G703 Hero", usbid=0xC090)
_D("G903 Hero Gaming Mouse", codename="G903 Hero", usbid=0xC091)
_D(None, kind=DEVICE_KIND.mouse, usbid=0xC092, interface=1) # two mice share this ID
_D("M500S Mouse", codename="M500S", usbid=0xC093, interface=1)
_D("G502 X Lightspeed Gaming Mouse", codename="G502 X Lightspeed", wpid="409F", usbid=0xC098)
# _D('G600 Gaming Mouse', codename='G600 Gaming', usbid=0xc24a, interface=1) # not an HID++ device
_D("G500 Gaming Mouse", codename="G500 Gaming", usbid=0xC068, interface=1, protocol=1.0)
_D("G500s Gaming Mouse", codename="G500s Gaming", usbid=0xC24E, interface=1, protocol=1.0)

View File

@ -224,16 +224,23 @@ yaml.add_representer(NamedInt, named_int_representer)
# A device can be identified by a combination of WPID and serial number (for receiver-connected devices)
# or a combination of modelId and unitId (for direct-connected devices).
# or a combination of modelId and unitId (for direct-connected devices). Some devices report the same
# physical ID as the receiver serial in one mode and as the direct unitId in another, so match those too
# when another stable identifier agrees.
# But some devices have empty (all zero) modelIds and unitIds. Use the device name as a backup for the modelId.
# The worst situation is a receiver-connected device that Solaar has never seen on-line
# that is directly connected. Here there is no way to realize that the two devices are the same.
# So new entries are not created for unseen off-line receiver-connected devices
def persister(device):
def match(wpid, serial, modelId, unitId, c):
same_model = modelId and modelId == c.get(_KEY_MODEL_ID)
same_wpid = wpid and wpid == c.get(_KEY_WPID)
same_name = device_name and device_name == c.get(_KEY_NAME)
cross_unit_serial = (unitId and unitId == c.get(_KEY_SERIAL)) or (serial and serial == c.get(_KEY_UNIT_ID))
return (
(wpid and wpid == c.get(_KEY_WPID) and serial and serial == c.get(_KEY_SERIAL))
or (modelId and modelId == c.get(_KEY_MODEL_ID) and unitId and unitId == c.get(_KEY_UNIT_ID))
(same_wpid and serial and serial == c.get(_KEY_SERIAL))
or (same_model and unitId and unitId == c.get(_KEY_UNIT_ID))
or (cross_unit_serial and (same_model or same_wpid or same_name))
or (
c.get(_KEY_WPID) is None
and c.get(_KEY_SERIAL) is None
@ -250,6 +257,7 @@ def persister(device):
# some devices report modelId and unitId as zero so use name and serial for them
modelId = device.modelId if device.modelId != "000000000000" else device._name if device._name else None
unitId = device.unitId if device.unitId != "00000000" else device._serial if device._serial else None
device_name = device.name
for c in _config:
if isinstance(c, _DeviceEntry) and match(device.wpid, device._serial, modelId, unitId, c):
entry = c

View File

@ -0,0 +1,81 @@
from dataclasses import dataclass
import pytest
from solaar import configuration
@dataclass
class FakeDevice:
name: str
wpid: str
_serial: str
modelId: str
unitId: str
online: bool = True
@property
def _name(self):
return self.name
@property
def serial(self):
return self._serial
@pytest.fixture(autouse=True)
def reset_config():
original_config = configuration._config
configuration._config = ["test-version"]
yield
configuration._config = original_config
def test_persister_matches_receiver_serial_to_direct_unit_id():
direct_entry = configuration._DeviceEntry(
_NAME="G502 X LIGHTSPEED",
_wpid="409F",
_modelId="409FC0980000",
_unitId="92050B60",
sensitivity=1200,
)
configuration._config.append(direct_entry)
receiver_device = FakeDevice(
name="G502 X LIGHTSPEED",
wpid="409F",
_serial="92050B60",
modelId="409FC0980000",
unitId=None,
)
entry = configuration.persister(receiver_device)
assert entry is direct_entry
assert entry["sensitivity"] == 1200
assert entry["_serial"] == "92050B60"
def test_persister_matches_direct_unit_id_to_receiver_serial():
receiver_entry = configuration._DeviceEntry(
_NAME="G502 X LIGHTSPEED",
_wpid="409F",
_serial="92050B60",
sensitivity=1200,
)
configuration._config.append(receiver_entry)
direct_device = FakeDevice(
name="G502 X LIGHTSPEED",
wpid="409F",
_serial=None,
modelId="409FC0980000",
unitId="92050B60",
)
entry = configuration.persister(direct_device)
assert entry is receiver_entry
assert entry["sensitivity"] == 1200
assert entry["_modelId"] == "409FC0980000"
assert entry["_unitId"] == "92050B60"