Fix WIRELESS_DEVICE_STATUS reconfiguration: push settings and ack with proper cookie

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.
This commit is contained in:
Ken Sanislo 2026-04-06 12:47:51 -07:00
parent ad96dd395b
commit a194fd285f
3 changed files with 29 additions and 2 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)