Replace the hardcoded descriptor entry with dynamic discovery of all device
properties via the Centurion protocol. The headset name, kind, serial,
firmware, and battery are now probed at runtime — matching how the device
actually presents itself rather than relying on static data.
Key changes:
- Discover sub-device features via CentPPBridge and route requests through
the bridge automatically
- Infer device kind from feature IDs (0x06xx = headset) for both wireless
and direct USB connections
- Read device name from USB product string with protocol probe fallback
- Parse bridge error responses (sub_feat_idx=0xFF) instead of timing out
- Handle unknown HID++ error codes gracefully in base.py
- Fix firmware deduplication for Centurion parent devices
- Prefer sub-device serial/firmware over parent (non-printable) values
- Add Centurion-aware display in solaar show with parent/sub-device sections
- Support both wireless (0AF7 dongle) and direct USB (0AF8) connections
Adds support for the Logitech PRO X 2 LIGHTSPEED Gaming Headset (PID 0x0AF7)
which uses the Centurion transport protocol (report ID 0x51 on USB usage page
0xFFA0) instead of standard HID++ report IDs.
Changes:
- HID enumeration: detect Centurion devices via report descriptor parsing
(usage page 0xFFA0, report ID 0x51, 63-byte frames)
- Centurion transport: wrap/unwrap HID++ 2.0 frames in Centurion framing
for write, read, and ping operations
- Feature discovery: enumerate features individually on Centurion devices
(different response format: [remaining_count, feat_hi, feat_lo])
- Device descriptor for PRO X 2 LIGHTSPEED Gaming Headset
- New feature enum entries for Centurion-era headset features (0x06xx)
- CenturionRawRW class for write-only headset settings controlled via
raw Centurion commands reverse-engineered from HeadsetControl
- HeadsetSidetone setting (0-100 range, persisted locally)
Known limitations:
- Only sidetone control is implemented; other features need RE work
- Settings are write-only (no read-back from device)
- Headset features (0x06xx) not discoverable via IRoot; registered manually
* refactoring(logitech_receiver/notifications): change to enums PairingError and BoltPairingError
* refactoring(logitech_receiver/notifications): change to enums PairingError and BoltPairingError (Fix pre-commit checks)
* refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum
* refactor(logitech_receiver/base.py): create unit tests for request function before replacing ERROR NamedInts by IntEnum
* refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum (add exclusion for macOS)
* refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum (fix for python < 3.10)
* refactor(solaar/cli./probe.py): create unit tests for run function before replacing ERROR NamedInts by IntEnum (focusing on the call order when receiving errors)
* refactor(solaar/cli./probe.py): refactor register processing to handle short and long registers in a single loop structure for improved readability and reduced code duplication.
* refactor(logitech_receiver/hidpp10_constants.py): replace ERROR NamedInt by IntEnum.
* refactor(logitech_receiver/hidpp10_constants.py): distinguish hidpp10 and hidpp20 errors in the code for readibility.
* refactor(logitech_receiver/hidpp20_constants.py): replace ERROR NamedInt by IntEnum.
* refactor(logitech_receiver/hidpp20_constants.py): replace ERROR NamedInt by IntEnum. (fix problem with | operator when typing with python 3.8)
* feature(hide on startup option): Visual test (not binded yet) DRAFT
* refactor(solaar/cli./probe.py): create unit tests for run function before replacing ERROR NamedInts by IntEnum (focusing on the call order when receiving errors)
* refactor(solaar/cli./probe.py): refactor register processing to handle short and long registers in a single loop structure for improved readability and reduced code duplication.
* refactor(logitech_receiver/hidpp10_constants.py): replace ERROR NamedInt by IntEnum.
* refactor(logitech_receiver/hidpp20_constants.py): replace ERROR NamedInt by IntEnum.
* refactor(logitech_receiver/hidpp20_constants.py): replace ERROR NamedInt by IntEnum. (fix problem with | operator when typing with python 3.8)
* feature(hide on startup option): Visual test (not binded yet) DRAFT
* Merge: Refactor: hidpp20 to use enum
* Merge: Refactor: hidpp20 to use enum (fix test)
---------
Co-authored-by: some_developer <some.developper.44@gmail.com>
* Remove duplicated Param definition
Use constants from hidpp20 constants
Related #2273
* hidpp20/Param: Refactor to use IntEnum
Related #2273
* hidpp20_constants: Refactor to use IntEnum
Related #2273
Warnings found by automatic code inspection and partially tackled
- Drop distuitls inf favour of setuptools
- Replace deprecated pyudev.Device.from_device_number
- Remove unnecessary brackets
- Avoid access to private variables etc.
- Shadows built-in name
- Line length >120 characters
- Not a module level variable
- Simplify clause
and more
The hidapi hardware layer must not know or depend on any UI libraries.
Removes all GDK dependencies from the hidapi packages, which makes
testing of these modules easier and removes unwanted cross-dependencies.
Related #2480
device: move some imports to top of modules
device: break up imports loop with device descriptors
device: break up imports loop by moving a function from notifications.py to setting_templates.py
device: break import loop between device.py and diversion.py by using device to access method