From a59368f3e7d0ece2fc83ba779c3c6097b9032431 Mon Sep 17 00:00:00 2001 From: Ben Wolsieffer Date: Fri, 10 Aug 2018 23:05:34 -0400 Subject: [PATCH] Fix issues with HID++ >= 2.0 devices (particularly related to sleep). * Don't assume 0x41 messages only occur when a device is first paired (prevents errors when waking from sleep or turning a device on) * Delay reads/writes when a device is powered on, to prevent broken pipe errors (hacky solution). * Don't clear status when a device connects, preventing settings from being cleared when a device sleeps or is turned off. * Fix typos. --- lib/logitech_receiver/listener.py | 2 +- lib/logitech_receiver/notifications.py | 6 +++--- lib/logitech_receiver/status.py | 9 +++++---- lib/solaar/listener.py | 22 ++++++++++++++++------ 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/logitech_receiver/listener.py b/lib/logitech_receiver/listener.py index 3f665ba7..c00a81b8 100644 --- a/lib/logitech_receiver/listener.py +++ b/lib/logitech_receiver/listener.py @@ -150,7 +150,7 @@ class EventsListener(_threading.Thread): # replace the handle with a threaded one self.receiver.handle = _ThreadedHandle(self, self.receiver.path, self.receiver.handle) - # get the right low-level handle for this thead + # get the right low-level handle for this thread ihandle = int(self.receiver.handle) if _log.isEnabledFor(_INFO): _log.info("started with %s (%d)", self.receiver, ihandle) diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index 376e7567..f9b7e4a7 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -164,14 +164,14 @@ def _process_hidpp10_notification(device, status, n): assert wpid == device.wpid, "%s wpid mismatch, got %s" % (device, wpid) flags = ord(n.data[:1]) & 0xF0 - link_encrypyed = bool(flags & 0x20) + link_encrypted = bool(flags & 0x20) link_established = not (flags & 0x40) if _log.isEnabledFor(_DEBUG): sw_present = bool(flags & 0x10) has_payload = bool(flags & 0x80) _log.debug("%s: %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s", - device, protocol_name, sw_present, link_encrypyed, link_established, has_payload) - status[_K.LINK_ENCRYPTED] = link_encrypyed + device, protocol_name, sw_present, link_encrypted, link_established, has_payload) + status[_K.LINK_ENCRYPTED] = link_encrypted status.changed(active=link_established) else: _log.warn("%s: connection notification with unknown protocol %02X: %s", device.number, n.address, n) diff --git a/lib/logitech_receiver/status.py b/lib/logitech_receiver/status.py index e55381f6..5dbe3614 100644 --- a/lib/logitech_receiver/status.py +++ b/lib/logitech_receiver/status.py @@ -67,10 +67,11 @@ def attach_to(device, changed_callback): assert device assert changed_callback - if device.kind is None: - device.status = ReceiverStatus(device, changed_callback) - else: - device.status = DeviceStatus(device, changed_callback) + if not hasattr(device, 'status') or device.status is None: + if device.kind is None: + device.status = ReceiverStatus(device, changed_callback) + else: + device.status = DeviceStatus(device, changed_callback) # # diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index 04efd436..88318527 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -18,6 +18,7 @@ ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals +import time from logging import getLogger, INFO as _INFO _log = getLogger(__name__) @@ -181,11 +182,20 @@ class ReceiverListener(_listener.EventsListener): return # a device notification - assert n.devnumber > 0 and n.devnumber <= self.receiver.max_devices + assert 0 < n.devnumber <= self.receiver.max_devices already_known = n.devnumber in self.receiver - if n.sub_id == 0x41: - already_known = False + # FIXME: hacky fix for kernel/hardware race condition + # If the device was just turned on or woken up from sleep, it may not + # be ready to receive commands. The "payload" bit of the wireless + # status notification seems to tell us this. If this is the case, we + # must wait a short amount of time to avoid causing a broken pipe + # error. + device_ready = not bool(ord(n.data[0:1]) & 0x80) or n.sub_id != 0x41 + if not device_ready: + time.sleep(0.01) + + if n.sub_id == 0x41 and not already_known: dev = self.receiver.register_new_device(n.devnumber, n) else: dev = self.receiver[n.devnumber] @@ -194,7 +204,8 @@ class ReceiverListener(_listener.EventsListener): _log.warn("%s: received %s for invalid device %d: %r", self.receiver, n, n.devnumber, dev) return - if not already_known: + # Apply settings every time the device connects + if n.sub_id == 0x41: if _log.isEnabledFor(_INFO): _log.info("%s triggered new device %s (%s)", n, dev, dev.kind) # If there are saved configs, bring the device's settings up-to-date. @@ -213,8 +224,7 @@ class ReceiverListener(_listener.EventsListener): if _log.isEnabledFor(_INFO): _log.info("%s: pairing detected new device", self.receiver) self.receiver.status.new_device = dev - elif dev: - if dev.online is None: + elif dev.online is None: dev.ping() def __str__(self):