From bdc17e9fb5253c66abb7a30a1b4ebf478e73f0d3 Mon Sep 17 00:00:00 2001 From: Ken Sanislo Date: Thu, 16 Jul 2026 20:52:13 -0700 Subject: [PATCH] Add startup animation toggle for COLOR_LED_EFFECTS devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0x8070 V1+ NvConfig (GetNvConfig/SetNvConfig, funcs 4/5) carries a startup-effect toggle under capability 0x0001 — a pure on/off switch (0x01/0x02) with no color payload, unlike the 0x8071 NvConfig boot effects; the boot color comes from the NV-persisted zone effect. GetNvConfig echoes the capability ID ahead of the value. Wire format captured from G HUB driving a G560 (usbmon) and the round-trip hardware-verified. Build probes the capability and suppresses the setting on devices that NACK it. The G560's only other NvConfig capability is 0x0002, which on 0x8071 devices causes a firmware animation-loop lockup — deliberately not exposed. --- lib/logitech_receiver/settings_templates.py | 29 +++++++++++++++++++ .../test_setting_templates.py | 5 ++++ 2 files changed, 34 insertions(+) diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index cd46a604..0139001c 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -3330,6 +3330,34 @@ class LEDZoneSetting(settings.Setting): return cls.setup(device, 0xE0, 0x30, b"") +class LEDStartupAnimation(settings.Setting): + """Startup effect on/off via 0x8070 NvConfig (funcs 4/5, cap 0x0001, V1+). + + A pure toggle with no color payload; GetNvConfig echoes the capability ID + ahead of the value. + """ + + name = "led_startup_animation" + label = _("Startup Animation") + description = ( + _("Firmware-played animation when the device powers on.") + "\n" + _("Setting persists on the device (non-volatile).") + ) + feature = _F.COLOR_LED_EFFECTS + min_version = 1 + rw_options = {"read_fnid": 0x40, "write_fnid": 0x50, "prefix": b"\x00\x01"} # NvConfig capability 0x0001 + validator_class = settings_validator.BooleanValidator + validator_options = {"true_value": 0x01, "false_value": 0x02, "read_skip_byte_count": 2} + + @classmethod + def build(cls, device): + try: # gate on the device actually supporting the capability + if not device.feature_request(cls.feature, 0x40, b"\x00\x01"): + return None + except exceptions.FeatureCallError: + return None + return super().build(device) + + class RGBControl(settings.Setting): name = "rgb_control" label = _("LED Control") @@ -4503,6 +4531,7 @@ SETTINGS: list[settings.Setting] = [ Backlight3, LEDControl, LEDZoneSetting, + LEDStartupAnimation, RGBControl, RGBEffectSetting, PerKeyLighting, diff --git a/tests/logitech_receiver/test_setting_templates.py b/tests/logitech_receiver/test_setting_templates.py index 28fcd826..02a855fe 100644 --- a/tests/logitech_receiver/test_setting_templates.py +++ b/tests/logitech_receiver/test_setting_templates.py @@ -286,6 +286,11 @@ simple_tests = [ fake_hidpp.Response("00", 0x0470), fake_hidpp.Response("01", 0x0480, "01"), ), + Setup( # NvConfig startup toggle: GetNvConfig echoes the cap ID; 0x01 on / 0x02 off + FeatureTest(settings_templates.LEDStartupAnimation, False, True, version=5), + fake_hidpp.Response("00010200000000000000", 0x0440, "0001"), + fake_hidpp.Response("000101", 0x0450, "000101"), + ), Setup( FeatureTest( settings_templates.LEDZoneSetting,