From a194fd285f8be262cf54aa4200f1b4f54ee40e97 Mon Sep 17 00:00:00 2001 From: Ken Sanislo Date: Mon, 6 Apr 2026 12:47:51 -0700 Subject: [PATCH] Fix WIRELESS_DEVICE_STATUS reconfiguration: push settings and ack with proper cookie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a device sends WIRELESS_DEVICE_STATUS with config_needed=1, the host should re-push settings and acknowledge via ConfigChange SetComplete. The existing code relied on the push flag in changed(), but operator precedence caused push=True to be ignored for devices that have the WIRELESS_DEVICE_STATUS feature — the exact devices that send this notification. Handle the reconfiguration entirely in the notification handler: explicitly push settings and ack, rather than trying to overload the changed() condition. Also replace hardcoded cookie 0x11 with proper GetCookie/SetComplete protocol: read the device's current configuration cookie and echo it back, per the ConfigChange (0x0020) specification. --- lib/logitech_receiver/device.py | 7 ++++++- lib/logitech_receiver/hidpp20.py | 19 +++++++++++++++++++ lib/logitech_receiver/notifications.py | 5 ++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index ff3565b7..209affed 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -437,6 +437,11 @@ class Device: if self.online and self.protocol >= 2.0: _hidpp20.config_change(self, configuration_, no_reply=no_reply) + def signal_configuration_complete(self): + """Read the device's config cookie and echo it back via SetComplete to ack end of configuration.""" + if self.online and self.protocol >= 2.0: + _hidpp20.set_configuration_complete(self) + def reset(self, no_reply=False): self.set_configuration(0, no_reply) @@ -541,7 +546,7 @@ class Device: if self.protocol < 2.0: # Make sure to set notification flags on the device self.notification_flags = self.enable_connection_notifications() else: - self.set_configuration(0x11) # signal end of configuration + self.signal_configuration_complete() self.read_battery() # battery information may have changed so try to read it now elif was_active and self.receiver and not isinstance(self.receiver, CenturionReceiver): hidpp10.set_configuration_pending_flags(self.receiver, 0xFF) diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index d000b409..32a7e93f 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -2051,7 +2051,26 @@ class Hidpp20: return struct.unpack("!B", result[:1])[0] return None + def get_configuration_cookie(self, device: Device): + """ConfigChange (0x0020) GetCookie — read the device's current configuration cookie.""" + response = device.feature_request(SupportedFeature.CONFIG_CHANGE, 0x00) + return response[:2] if response else None + + def set_configuration_complete(self, device: Device, cookie=None, no_reply=False): + """ConfigChange (0x0020) SetComplete — acknowledge host has synced with device configuration. + + If cookie is None, reads the current cookie and increments it to mark + a new sync point, so future cookie changes indicate device-side drift.""" + if cookie is None: + cookie = self.get_configuration_cookie(device) + if cookie and len(cookie) >= 2: + value = (cookie[0] << 8 | cookie[1]) + 1 & 0xFFFF + cookie = bytes([value >> 8, value & 0xFF]) + if cookie and len(cookie) >= 2: + return device.feature_request(SupportedFeature.CONFIG_CHANGE, 0x10, cookie[0], cookie[1], no_reply=no_reply) + def config_change(self, device: Device, configuration, no_reply=False): + """Deprecated — use set_configuration_complete() instead.""" return device.feature_request(SupportedFeature.CONFIG_CHANGE, 0x10, configuration, no_reply=no_reply) diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index 693e3afd..79c7cf5a 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -34,6 +34,7 @@ from . import diversion from . import hidpp10 from . import hidpp10_constants from . import hidpp20 +from . import settings from . import settings_templates from .common import Alert from .common import BatteryStatus @@ -324,7 +325,9 @@ def _process_feature_notification(device: Device, notification: HIDPPNotificatio reason = "powered on" if notification.data[2] == 1 else None if notification.data[1] == 1: # device is asking for software reconfiguration so need to change status alert = Alert.NONE - device.changed(active=True, alert=alert, reason=reason, push=True) + device.changed(active=True, alert=alert, reason=reason) + settings.apply_all_settings(device) + device.signal_configuration_complete() else: logger.warning("%s: unknown WIRELESS %s", device, notification)