Solaar/lib/logitech_receiver
Ken Sanislo 045cccb8b2 HeadsetAdvancedEQ: validate persister against live read on apply
Setting.apply uses cached=True so the persister is treated as the
source of truth and the device's live state never wins. That model is
correct for most settings, but it created a destructive bug for the
0x020D AdvancedParaEQ:

1. The V2 wire parser went through several iterations during G522
   bring-up (commits bde3c3bc, 41db76bc, 59e3dcb7) with different
   strides and gain encodings before settling at 7c73c888. Each
   intermediate produced different decoded values from the same wire
   response; whatever a user's Solaar was on when they last apply'd
   got stored to the persister.

2. PerKeyLighting-style `prepare_write` silently fills missing band
   keys with 0 dB and clamps out-of-range gain values to the
   [gain_min, gain_max] rail. A partial/stale persister dict
   therefore encodes as a complete wire payload — looking valid to
   the device.

3. Writes were disabled in the early V2 builds (until be047fd9 on
   May 10). Once writes shipped, the next apply read the stale
   persister, prepare_write filled+clamped it, and setCustomEQ
   slot 0 overwrote whatever the user had configured.

Observed on a G325 LIGHTSPEED user log: persister carried
`{0: -6, 1: 1094}` from an older build; apply pushed
`-6 +6 0 0 0 0 0 0 0 0` to slot 0 (band 1 clamped from 1094 to
the +6 dB rail, bands 2-9 zero-filled), wiping the user's
hand-tuned EQ.

Fix: override apply for HeadsetAdvancedEQ. Validate the persister
value against the current validator's count + gain range. If it's
well-formed, push it normally (preserves Solaar's "user config is
authoritative" model). If it's malformed (wrong key count, missing
indices, out-of-range gain), do a live device read and reseed both
_value and the persister from the device — without writing the
corrupt persister back. If both are invalid, log a warning and skip
this setting only; apply_all_settings continues with the rest.
2026-05-15 14:29:03 -07:00
..
__init__.py
…
advanced_para_eq.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
base.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
base_usb.py
…
centurion.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
centurion_constants.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
common.py ui: Show offline status for receiver-paired device batteries (#3217) 2026-05-13 17:32:03 -04:00
descriptors.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
desktop_notifications.py
…
device.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
device_quirks.py Add RGB lighting persistence and software LED power management for G515 2026-05-13 19:03:44 -04:00
diversion.py
…
exceptions.py
…
headset_rgb.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
hidpp10.py
…
hidpp10_constants.py
…
hidpp20.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
hidpp20_constants.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
i18n.py
…
listener.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
logivoice.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
notifications.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
onboard_eq.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
receiver.py
…
rgb_effects_probe.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
rgb_power.py rgb_control: honor the off state — don't auto-claim, init, or shutdown LEDs 2026-05-15 10:45:27 -04:00
settings.py G522 LIGHTSPEED headphones support 2026-05-15 14:29:03 -07:00
settings_new.py
…
settings_templates.py HeadsetAdvancedEQ: validate persister against live read on apply 2026-05-15 14:29:03 -07:00
settings_validator.py Add RGB lighting persistence and software LED power management for G515 2026-05-13 19:03:44 -04:00
special_keys.py
…