Solaar/app/watcher.py

201 lines
5.6 KiB
Python

#
#
#
import logging
import threading
import time
import constants as C
from logitech.unifying_receiver import api
from logitech.unifying_receiver.listener import EventsListener
from logitech import devices
_STATUS_TIMEOUT = 97 # seconds
_THREAD_SLEEP = 7 # seconds
_FORGET_TIMEOUT = 5 * 60 # seconds
class _DevStatus(api.AttachedDeviceInfo):
timestamp = time.time()
code = devices.STATUS.CONNECTED
props = {devices.PROPS.TEXT: devices.STATUS_NAME[devices.STATUS.CONNECTED]}
refresh = None
class WatcherThread(threading.Thread):
def __init__(self, notify_callback=None):
super(WatcherThread, self).__init__(name='WatcherThread')
self.daemon = True
self.active = False
self.notify = notify_callback
self.status_text = None
self.status_changed = threading.Event()
self.listener = None
self.devices = {}
def run(self):
self.active = True
self._notify(0, C.UNIFYING_RECEIVER, C.SCANNING)
while self.active:
if self.listener is None:
receiver = api.open()
if receiver:
self._notify(1, C.UNIFYING_RECEIVER, C.FOUND_RECEIVER)
for devinfo in api.list_devices(receiver):
devstatus = _DevStatus(*devinfo)
self.devices[devinfo.number] = devstatus
self._notify(devices.STATUS.CONNECTED, devstatus.name, devices.STATUS_NAME[devices.STATUS.CONNECTED])
self.listener = EventsListener(receiver, self._events_callback)
self.listener.start()
self._update_status()
else:
self._notify(-1, C.UNIFYING_RECEIVER, C.NO_RECEIVER)
elif not self.listener.active:
self.listener = None
self._notify(-1, C.UNIFYING_RECEIVER, C.NO_RECEIVER)
self.devices.clear()
if self.active:
update_icon = True
if self.listener and self.devices:
update_icon &= self._check_old_statuses()
if self.active:
if update_icon:
self._update_status()
time.sleep(_THREAD_SLEEP)
def stop(self):
self.active = False
if self.listener:
self.listener.stop()
api.close(self.listener.receiver)
def has_receiver(self):
return self.listener is not None and self.listener.active
def request_all_statuses(self, _=None):
updated = False
for d in range(1, 7):
devstatus = self.devices.get(d)
if devstatus:
status = devices.request_status(devstatus, self.listener)
updated |= self._device_status_changed(devstatus, status)
else:
devstatus = self._new_device(d)
updated |= devstatus is not None
if updated:
self._update_status()
def _check_old_statuses(self):
updated = False
for devstatus in list(self.devices.values()):
if time.time() - devstatus.timestamp > _STATUS_TIMEOUT:
status = devices.ping(devstatus, self.listener)
updated |= self._device_status_changed(devstatus, status)
return updated
def _new_device(self, device):
devinfo = api.get_device_info(self.listener.receiver, device)
if devinfo:
devstatus = _DevStatus(*devinfo)
self.devices[device] = devstatus
self._notify(devstatus.code, devstatus.name, devstatus.props[devices.PROPS.TEXT])
return devinfo
def _events_callback(self, code, device, data):
logging.debug("%s: event %02x %d %s", time.asctime(), code, device, repr(data))
updated = False
if device in self.devices:
devstatus = self.devices[device]
if code == 0x10 and data[0] == 'b\x8F':
updated = True
self._device_status_changed(devstatus, devices.STATUS.UNAVAILABLE)
elif code == 0x11:
status = devices.process_event(devstatus, self.listener, data)
updated |= self._device_status_changed(devstatus, status)
else:
logging.warn("unknown event code %02x", code)
elif device:
logging.debug("got event (%d, %d, %s) for new device", code, device, repr(data))
self._new_device(device)
updated = True
else:
logging.warn("don't know how to handle event (%d, %d, %s)", code, device, data)
if updated:
self._update_status()
def _device_status_changed(self, devstatus, status):
if status is None:
return False
old_status_code = devstatus.code
devstatus.timestamp = time.time()
if type(status) == int:
devstatus.code = status
if devstatus.code in devices.STATUS_NAME:
devstatus.props[devices.PROPS.TEXT] = devices.STATUS_NAME[devstatus.code]
else:
devstatus.code = status[0]
devstatus.props.update(status[1])
if old_status_code != devstatus.code:
logging.debug("%s: device status changed %s => %s: %s", time.asctime(), old_status_code, devstatus.code, devstatus.props)
# if not (devstatus.code == 0 and old_status_code > 0):
self._notify(devstatus.code, devstatus.name, devstatus.props[devices.PROPS.TEXT])
return True
def _notify(self, *args):
if self.notify:
self.notify(*args)
def notify_full(self):
if self.listener and self.listener.active:
if self.devices:
for devstatus in self.devices.values():
self._notify(0, devstatus.name, devstatus.props[devices.PROPS.TEXT])
else:
self._notify(0, C.UNIFYING_RECEIVER, C.NO_DEVICES)
else:
self._notify(-1, C.UNIFYING_RECEIVER, C.NO_RECEIVER)
def _update_status(self):
last_status_text = self.status_text
if self.listener and self.listener.active:
if self.devices:
all_statuses = []
for d in self.devices:
devstatus = self.devices[d]
status_text = devstatus.props[devices.PROPS.TEXT]
if status_text:
if ' ' in status_text:
all_statuses.append(devstatus.name)
all_statuses.append(' ' + status_text)
else:
all_statuses.append(devstatus.name + ' ' + status_text)
else:
all_statuses.append(devstatus.name)
self.status_text = '\n'.join(all_statuses)
else:
self.status_text = C.NO_DEVICES
else:
self.status_text = C.NO_RECEIVER
if self.status_text != last_status_text:
self.status_changed.set()