From ed5ce48f65fb4ea3681bd7e9bdf00638c9d1516b Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Sat, 8 Jun 2013 16:16:12 +0200 Subject: [PATCH] fixes to polling receiver/device status --- lib/logitech/unifying_receiver/listener.py | 13 ++++++--- lib/logitech/unifying_receiver/status.py | 22 +++++++++++---- lib/solaar/listener.py | 32 ++++++++++++++++------ 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/lib/logitech/unifying_receiver/listener.py b/lib/logitech/unifying_receiver/listener.py index d53a39a3..61344d25 100644 --- a/lib/logitech/unifying_receiver/listener.py +++ b/lib/logitech/unifying_receiver/listener.py @@ -40,6 +40,7 @@ class _ThreadedHandle(object): self._listener = listener self.path = path self._local = _threading.local() + # take over the current handle for the thread doing the replacement self._local.handle = handle self._handles = [handle] @@ -104,7 +105,8 @@ class _ThreadedHandle(object): _EVENT_READ_TIMEOUT = 500 # After this many reads that did not produce a packet, call the tick() method. -_IDLE_READS = 4 +# This only happens if tick_period is enabled (>0) for the Listener instance. +_IDLE_READS = 5 class EventsListener(_threading.Thread): @@ -113,7 +115,7 @@ class EventsListener(_threading.Thread): Incoming packets will be passed to the callback function in sequence. """ def __init__(self, receiver, notifications_callback): - super(EventsListener, self).__init__(name=self.__class__.__name__) + super(EventsListener, self).__init__(name=self.__class__.__name__ + ':' + receiver.path) self.daemon = True self._active = False @@ -128,14 +130,17 @@ class EventsListener(_threading.Thread): self._active = True # replace the handle with a threaded one - ihandle = int(self.receiver.handle) self.receiver.handle = _ThreadedHandle(self, self.receiver.path, self.receiver.handle) + # get the right low-level handle for this thead + ihandle = int(self.receiver.handle) _log.info("started with %s (%d)", self.receiver, ihandle) self.has_started() last_tick = 0 - idle_reads = _IDLE_READS * 10 + # the first idle read -- delay it a bit, and make sure to stagger + # idle reads for multiple receivers + idle_reads = _IDLE_READS + (ihandle % 3) * 2 while self._active: if self._queued_notifications.empty(): diff --git a/lib/logitech/unifying_receiver/status.py b/lib/logitech/unifying_receiver/status.py index de84f877..da5f505a 100644 --- a/lib/logitech/unifying_receiver/status.py +++ b/lib/logitech/unifying_receiver/status.py @@ -79,6 +79,16 @@ class ReceiverStatus(dict): # self.updated = _timestamp() self._changed_callback(self._receiver, alert=alert, reason=reason) + def poll(self, timestamp): + r = self._receiver + assert r + + if _log.isEnabledFor(_DEBUG): + _log.debug("polling status of %s", r) + + # make sure to read some stuff that may be read later by the UI + r.serial, r.firmware, None + def process_notification(self, n): if n.sub_id == 0x4A: self.lock_open = bool(n.address & 0x01) @@ -227,16 +237,16 @@ class DeviceStatus(dict): if _log.isEnabledFor(_DEBUG): _log.debug("polling status of %s", d) - # read these from the device in case they haven't been read already - d.protocol, d.serial, d.firmware + # read these from the device, the UI may need them later + d.protocol, d.firmware, d.kind, d.name, d.settings, None + + # make sure we know all the features of the device + # if d.features: + # d.features[:] if BATTERY_LEVEL not in self: self.read_battery(timestamp) - # make sure we know all the features of the device - if d.features: - d.features[:] - elif len(self) > 0 and timestamp - self.updated > _STATUS_TIMEOUT: # if the device has been inactive for too long, clear out any known # properties, they are most likely obsolete anyway diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index 32e8d3a6..252548f2 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -32,7 +32,7 @@ def _ghost(device): # how often to poll devices that haven't updated their statuses on their own # (through notifications) -_POLL_TICK = 100 # seconds +_POLL_TICK = 3 * 60 # seconds class ReceiverListener(_listener.EventsListener): @@ -41,7 +41,7 @@ class ReceiverListener(_listener.EventsListener): def __init__(self, receiver, status_changed_callback): super(ReceiverListener, self).__init__(receiver, self._notifications_handler) # no reason to enable polling yet - # self.tick_period = _POLL_TICK + self.tick_period = _POLL_TICK self._last_tick = 0 assert status_changed_callback @@ -58,7 +58,8 @@ class ReceiverListener(_listener.EventsListener): r, self.receiver = self.receiver, None assert r is not None _log.info("%s: notifications listener has stopped", r) - r.status = 'The device was unplugged.' + + r.status = 'The receiver was unplugged.' if r: try: pass @@ -75,8 +76,12 @@ class ReceiverListener(_listener.EventsListener): # configuration.save() def tick(self, timestamp): - # if _log.isEnabledFor(_DEBUG): - # _log.debug("%s: polling status: %s", self.receiver, list(iter(self.receiver))) + if not self.tick_period: + raise Exception("tick() should not be called without a tick_period: %s", self) + + if not self.receiver: + # just in case the receiver was just removed + return # not necessary anymore, we're now using udev monitor to watch for receiver status # if self._last_tick > 0 and timestamp - self._last_tick > _POLL_TICK * 2: @@ -94,9 +99,20 @@ class ReceiverListener(_listener.EventsListener): # don't mess with stuff while pairing return - for dev in self.receiver: - if dev.status is not None: - dev.status.poll(timestamp) + self.receiver.status.poll(timestamp) + + # Iterating directly through the reciver would unnecessarily probe + # all possible devices, even unpaired ones. + # Checking for each device number in turn makes sure only already + # known devices are polled. + # This is okay because we should have already known about them all + # long before the first poll() happents, through notifications. + for number in range(1, 6): + if number in self.receiver: + dev = self.receiver[number] + assert dev + if dev.status is not None: + dev.status.poll(timestamp) def _status_changed(self, device, alert=_status.ALERT.NONE, reason=None): assert device is not None