201 lines
5.6 KiB
Python
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()
|