Repository navigation
Logitech G PRO X 2 LIGHTSPEED headset over its receiver (Centurion, #103), unit tests - #124
Conversation
Centurion transport on ffa0:0001, report 0x51: receiver feature discovery, the CentPP bridge (0x0003), headset feature discovery, battery 0x0104. Framing as Solaar (e7304c4, base.py/device.py/centurion.py; its dump of a real 0AF7 has the bridge at 3 and the battery at 4); HeadsetControl's fixed request for firmware without 0x0104. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QmGsnjgXs8VbKmmxLkPsf6
HeyOkay#120 HeyOkay#122 HeyOkay#123 HeyOkay#124 HeyOkay#125 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QmGsnjgXs8VbKmmxLkPsf6
|
Heads-up from a fellow PR author: I've opened #126, which shortens the README's support table to Our trial merge shows #126 conflicts with this PR in |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QmGsnjgXs8VbKmmxLkPsf6
|
Thanks for this! PR #107 and a batch of other PRs were just merged into main (the listen-only PlayStation handling from e80dfd1 is kept, and |
|
I trial-merged this against today's
Full suite passes after that (439 tests). |
Resolved CHANGELOG.md (bullet under Unreleased), README.md (short row, detail in docs/protocols.md) and halo_battery.pyw (LogitechCenturionProvider in make_providers() and PROVIDER_LABELS). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QmGsnjgXs8VbKmmxLkPsf6
|
Merged |
…S 06BE - The six new device rows from HeyOkay#122, HeyOkay#123, HeyOkay#124, HeyOkay#125 and HeyOkay#141 go to docs/devices.md, where HeyOkay#128 moved the table. - tests/test_steelseries_elite.py: find make_providers( with its new parameter from HeyOkay#134. - Cloud III S: the 06BE revision and the charging state are confirmed in HeyOkay#156 (docs/protocols.md, CHANGELOG). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GfhP6tYaD8Rt4qP2YtmJ4
This PR adds the Logitech G PRO X 2 LIGHTSPEED headset on its receiver (
046D:0AF7, #103). The diagnostics in #103 list the receiver with three collections on interface 3:ff13:0001,ffa0:0001and000c:0001. This headset does not use HID++ on anff43collection, sologitech.pydoes not see it. It uses Logitech's "Centurion" transport onffa0:0001. That is a new provider,providers/logitech_centurion.py.Base and other PRs
mainate80dfd1.providers/logitech_centurion.py(new),providers/__init__.py,halo_battery.pyw(the import and the two provider lists),tests/test_logitech_centurion.py(new),README.md(one row after the G502),CHANGELOG.md.CHANGELOG.mdonly.CHANGELOG.mdandhalo_battery.pyw. Both add a provider at the end of the same lists; keep both.main. Itsmake_providers()will need this provider (and a label).Sources
lib/logitech_receiver/base.py: the frame,51 <len> <flags> <payload>,len= payload length + 1;device.py,centurion_bridge_request(): the bridge message, the acknowledgement, the answer, and the check on the echoed function byte;centurion.py,decipher_battery_centurion(): byte 0 = percent, byte 2 = charging state (0 discharging, 1 charging, 2 charging over USB, 3 charge complete);docs/devices/PRO X 2 LIGHTSPEED 0AF7.text: a dump of a real headset. Receiver features: 0 Root, 1 FeatureSet, 20100, 3 CentPP bridge, 4010A. Headset features: ... 40104battery.lib/devices/logitech_gpro_x2_lightspeed.hppandlib/devices/protocols/logitech_centurion_protocol.hpp. It uses the same discovery and bridge. For firmware without0104it has a fixed request,51 08 00 03 1a 00 03 00 04 0a. That request is a bridge message to bridge index 3, headset feature 4, with software id0x0A, which agrees with the Solaar dump.Where the two sources are different: HeadsetControl frames its direct (receiver) requests from a zero-padded 64-byte buffer, so the length byte is
0x41. Solaar uses the real length (for example51 05 00 00 01 00 01for "where is FeatureSet"). The HeadsetControl PR (#512) says the code was written with an AI model from the Solaar-NG notes. Solaar has a dump from real hardware. This provider uses Solaar's framing. The bridge frames are the same in both.What the provider sends
All requests read. No function that changes a setting is sent (a test checks each write).
fn 0"where is0001" → FeatureSet index; FeatureSetfn 0→ count; FeatureSetfn 1for each index → feature id, until the bridge (0003).0104,fn 0.The feature tables are found once and remembered. After that, each poll sends one request. After an error the tables are found again on the next poll.
Replies:
ff <index> <function> <code>) ends the attempt.sub_cpl = 00(ff= a notification from the headset), the requested feature index and the echoed function byte.ff <index>= the headset rejected the request.Tests
tests/test_logitech_centurion.py(new, 17 tests). The fake receiver has the feature layout of Solaar's dump and answers direct and bridge requests.kind="headset".51 05 00 00 01 00 01); the battery bridge frame is HeadsetControl's fixed frame with software id 1.51 0bevents are skipped.ffa0:0001of0AF7is opened.Mutation checks: I changed each rule, and each time at least one test failed. All tests pass. A live
--probeon a PC with a G502 on a Lightspeed receiver shows no change.Closes #103 when confirmed.