diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index 17baf770..400bfa93 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -25,7 +25,7 @@ from typing import Optional import hidapi as _hid import solaar.configuration as _configuration -from . import base, descriptors, exceptions, hidpp10, hidpp10_constants, hidpp20 +from . import base, descriptors, exceptions, hidpp10, hidpp10_constants, hidpp20, hidpp20_constants, settings from .common import ALERT, Battery from .settings_templates import check_feature_settings as _check_feature_settings @@ -66,7 +66,7 @@ class Device: if receiver: assert 0 < number <= 15 # some receivers have devices past their max # of devices self.number = number # will be None at this point for directly connected devices - self.online = online + self.online = online # is the device online? - gates many atempts to contact the device self.descriptor = None self.isDevice = True # some devices act as receiver so we need a property to distinguish them self.may_unpair = False @@ -95,6 +95,7 @@ class Device: self.notification_flags = None self.battery_info = None self.link_encrypted = None + self._active = None # lags self.online - is used to help determine when to setup devices self._feature_settings_checked = False self._gestures_lock = _threading.Lock() @@ -375,7 +376,7 @@ class Device: # update the leds on the device, if any _hidpp10.set_3leds(self, info.level, charging=info.charging(), warning=bool(alert)) if hasattr(self, "status"): - self.status.changed(active=True, alert=alert, reason=reason) + self.changed(active=True, alert=alert, reason=reason) # Retrieve and regularize battery status def read_battery(self): @@ -408,6 +409,33 @@ class Device: logger.info("%s: device notifications %s %s", self, "enabled" if enable else "disabled", flag_names) return flag_bits if ok else None + def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False): + """The status of the device had changed, so invoke the status callback. + Also push notifications and settings to the device when necessary.""" + changed_callback = self.status._changed_callback + if active is not None: + self.online = active + was_active, self._active = self._active, active + if active: + if not was_active: + if self.protocol < 2.0: # Make sure to set notification flags on the device + self.notification_flags = self.enable_connection_notifications() + self.read_battery() # battery information may have changed so try to read it now + # Push settings for new devices when devices request software reconfiguration + # and when devices become active if they don't have wireless device status feature, + if ( + was_active is None + or not was_active + or push + and (not self.features or hidpp20_constants.FEATURE.WIRELESS_DEVICE_STATUS not in self.features) + ): + if logger.isEnabledFor(logging.INFO): + logger.info("%s pushing device settings %s", self, self.settings) + settings.apply_all_settings(self) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("device %d changed: active=%s %s", self.number, self._active, self.battery_info) + changed_callback(self, alert, reason) + def add_notification_handler(self, id: str, fn): """Adds the notification handling callback `fn` to this device under name `id`. If a callback has already been registered under this name, it's replaced with diff --git a/lib/logitech_receiver/listener.py b/lib/logitech_receiver/listener.py index 20f6e95d..4898d677 100644 --- a/lib/logitech_receiver/listener.py +++ b/lib/logitech_receiver/listener.py @@ -133,7 +133,7 @@ class EventsListener(threading.Thread): if self.receiver.isDevice: # ping (wired or BT) devices to see if they are really online if self.receiver.ping(): - self.receiver.status.changed(True, reason="initialization") + self.receiver.changed(active=True, reason="initialization") while self._active: if self._queued_notifications.empty(): diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index fbdb7e15..006b3597 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -76,7 +76,7 @@ def _process_receiver_notification(receiver, status, n): receiver.pairing.error = error_string = _hidpp10_constants.PAIRING_ERRORS[pair_error] receiver.pairing.new_device = None logger.warning("pairing error %d: %s", pair_error, error_string) - status.changed(reason=reason) + receiver.changed(reason=reason) return True elif n.sub_id == _R.discovery_status_notification: # Bolt pairing @@ -94,7 +94,7 @@ def _process_receiver_notification(receiver, status, n): if discover_error: receiver.pairing.error = discover_string = _hidpp10_constants.BOLT_PAIRING_ERRORS[discover_error] logger.warning("bolt discovering error %d: %s", discover_error, discover_string) - status.changed(reason=reason) + receiver.changed(reason=reason) return True elif n.sub_id == _R.device_discovery_notification: # Bolt pairing @@ -134,7 +134,7 @@ def _process_receiver_notification(receiver, status, n): receiver.pairing.error = error_string = _hidpp10_constants.BOLT_PAIRING_ERRORS[pair_error] receiver.pairing.new_device = None logger.warning("pairing error %d: %s", pair_error, error_string) - status.changed(reason=reason) + receiver.changed(reason=reason) return True elif n.sub_id == _R.passkey_request_notification: # Bolt pairing @@ -209,7 +209,7 @@ def _process_dj_notification(device, status, n): connected = not n.address & 0x01 if logger.isEnabledFor(logging.INFO): logger.info("%s: DJ connection: %s %s", device, connected, n) - status.changed(active=connected, alert=_ALERT.NONE, reason=_("connected") if connected else _("disconnected")) + device.changed(active=connected, alert=_ALERT.NONE, reason=_("connected") if connected else _("disconnected")) return True logger.warning("%s: unrecognized DJ %s", device, n) @@ -233,10 +233,10 @@ def _process_hidpp10_notification(device, status, n): if n.address == 0x02: # device un-paired device.wpid = None - device.status = None if device.number in device.receiver: del device.receiver[device.number] - status.changed(active=False, alert=_ALERT.ALL, reason=_("unpaired")) + device.changed(active=False, alert=_ALERT.ALL, reason=_("unpaired")) + device.status = None else: logger.warning("%s: disconnection with unknown type %02X: %s", device, n.address, n) return True @@ -267,7 +267,7 @@ def _process_hidpp10_notification(device, status, n): bool(flags & 0x80), ) device.link_encrypted = link_encrypted - status.changed(active=link_established) + device.changed(active=link_established) return True if n.sub_id == 0x49: @@ -281,7 +281,7 @@ def _process_hidpp10_notification(device, status, n): if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: device powered on", device) reason = device.status_string() or _("powered on") - status.changed(active=True, alert=_ALERT.NOTIFICATION, reason=reason) + device.changed(active=True, alert=_ALERT.NOTIFICATION, reason=reason) else: logger.warning("%s: unknown %s", device, n) return True @@ -320,7 +320,7 @@ def _process_feature_notification(device, status, n, feature): if result: device.set_battery_info(result[1]) else: # this feature is used to signal device becoming inactive - status.changed(active=False) + device.changed(active=False) else: logger.warning("%s: unknown ADC MEASUREMENT %s", device, n) @@ -339,7 +339,7 @@ def _process_feature_notification(device, status, n, feature): elif n.address == 0x20: if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: Light Check button pressed", device) - status.changed(alert=_ALERT.SHOW_WINDOW) + device.changed(alert=_ALERT.SHOW_WINDOW) # first cancel any reporting # device.feature_request(_F.SOLAR_DASHBOARD) # trigger a new report chain @@ -358,7 +358,7 @@ def _process_feature_notification(device, status, n, feature): reason = "powered on" if n.data[2] == 1 else None if n.data[1] == 1: # device is asking for software reconfiguration so need to change status alert = _ALERT.NONE - status.changed(active=True, alert=alert, reason=reason, push=True) + device.changed(active=True, alert=alert, reason=reason, push=True) else: logger.warning("%s: unknown WIRELESS %s", device, n) diff --git a/lib/logitech_receiver/receiver.py b/lib/logitech_receiver/receiver.py index 01b7ab2d..dfbb6afa 100644 --- a/lib/logitech_receiver/receiver.py +++ b/lib/logitech_receiver/receiver.py @@ -25,6 +25,7 @@ import hidapi as _hid from . import base as _base from . import exceptions, hidpp10, hidpp10_constants +from .common import ALERT from .device import Device from .i18n import _, ngettext @@ -102,6 +103,10 @@ class Receiver: def __del__(self): self.close() + def changed(self, alert=ALERT.NOTIFICATION, reason=None): + """The status of the device had changed, so invoke the status callback""" + self.status._changed_callback(self, alert=alert, reason=reason) + @property def firmware(self): if self._firmware is None and self.handle: diff --git a/lib/logitech_receiver/status.py b/lib/logitech_receiver/status.py index 6c98f342..adb37703 100644 --- a/lib/logitech_receiver/status.py +++ b/lib/logitech_receiver/status.py @@ -19,8 +19,6 @@ import logging from . import hidpp10 from . import hidpp10_constants as _hidpp10_constants -from . import hidpp20_constants as _hidpp20_constants -from . import settings as _settings from .common import NamedInts logger = logging.getLogger(__name__) @@ -52,9 +50,6 @@ class ReceiverStatus: assert changed_callback self._changed_callback = changed_callback - def changed(self, alert=ALERT.NOTIFICATION, reason=None): - self._changed_callback(self._receiver, alert=alert, reason=reason) - class DeviceStatus: """Holds the 'runtime' status of a peripheral @@ -73,35 +68,3 @@ class DeviceStatus: return bool(self._active) __nonzero__ = __bool__ - - def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False): - d = self._device - - if active is not None: - d.online = active - was_active, self._active = self._active, active - if active: - if not was_active: - # Make sure to set notification flags on the device, they - # get cleared when the device is turned off (but not when the device - # goes idle, and we can't tell the difference right now). - if d.protocol < 2.0: - self._device.notification_flags = d.enable_connection_notifications() - # battery information may have changed so try to read it now - self._device.read_battery() - - # Push settings for new devices when devices request software reconfiguration - # and when devices become active if they don't have wireless device status feature, - if ( - was_active is None - or push - or not was_active - and (not d.features or _hidpp20_constants.FEATURE.WIRELESS_DEVICE_STATUS not in d.features) - ): - if logger.isEnabledFor(logging.INFO): - logger.info("%s pushing device settings %s", d, d.settings) - _settings.apply_all_settings(d) - - if logger.isEnabledFor(logging.DEBUG): - logger.debug("device %d changed: active=%s %s", d.number, self._active, self.battery) - self._changed_callback(d, alert, reason) diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index f25ee0fc..e03ddecf 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -291,7 +291,7 @@ def ping_all(resuming=False): if resuming and hasattr(listener_thread.receiver, "status"): listener_thread.receiver.status._active = None # ensure that settings are pushed if listener_thread.receiver.ping(): - listener_thread.receiver.status.changed(active=True, push=True) + listener_thread.receiver.changed(active=True, push=True) listener_thread._status_changed(listener_thread.receiver) else: count = listener_thread.receiver.count() @@ -300,7 +300,7 @@ def ping_all(resuming=False): if resuming and hasattr(dev, "status"): dev.status._active = None # ensure that settings are pushed if dev.ping(): - dev.status.changed(active=True, push=True) + dev.changed(active=True, push=True) listener_thread._status_changed(dev) count -= 1 if not count: