reworked the event listener

This commit is contained in:
Daniel Pavel 2012-10-10 06:37:03 +03:00
parent c61297a871
commit e34ad5104f
20 changed files with 304 additions and 214 deletions

View File

@ -2,27 +2,28 @@
# #
# #
import threading
from gi.repository import Gtk
from gi.repository import GObject from gi.repository import GObject
from .watcher import WatcherThread
from . import ui from . import ui
from logitech.devices import constants as C
APP_TITLE = 'Solaar' APP_TITLE = 'Solaar'
def _status_updated(watcher, icon, window): def _status_updated(watcher, icon, window):
while True: while True:
watcher.status_changed.wait() watcher.status_changed.wait()
text = watcher.status_text text = watcher.status_text
watcher.status_changed.clear() watcher.status_changed.clear()
icon_name = APP_TITLE + '-fail' if watcher.rstatus.code < C.STATUS.CONNECTED else APP_TITLE
if icon: if icon:
GObject.idle_add(ui.icon.update, icon, watcher.rstatus, text) GObject.idle_add(ui.icon.update, icon, watcher.rstatus, text, icon_name)
if window: if window:
GObject.idle_add(ui.window.update, window, watcher.rstatus, dict(watcher.devices)) GObject.idle_add(ui.window.update, window, watcher.rstatus, dict(watcher.devices), icon_name)
def run(config): def run(config):
@ -30,16 +31,22 @@ def run(config):
ui.notify.init(APP_TITLE, config.notifications) ui.notify.init(APP_TITLE, config.notifications)
from .watcher import WatcherThread
watcher = WatcherThread(ui.notify.show) watcher = WatcherThread(ui.notify.show)
watcher.start() watcher.start()
window = ui.window.create(APP_TITLE, watcher.rstatus, not config.start_hidden, config.close_to_tray) window = ui.window.create(APP_TITLE, watcher.rstatus, not config.start_hidden, config.close_to_tray)
tray_icon = ui.icon.create(APP_TITLE, (ui.window.toggle, window)) window.set_icon_name(APP_TITLE + '-fail')
tray_icon = ui.icon.create(APP_TITLE, (ui.window.toggle, window))
tray_icon.set_from_icon_name(APP_TITLE + '-fail')
import threading
ui_update_thread = threading.Thread(target=_status_updated, name='ui_update', args=(watcher, tray_icon, window)) ui_update_thread = threading.Thread(target=_status_updated, name='ui_update', args=(watcher, tray_icon, window))
ui_update_thread.daemon = True ui_update_thread.daemon = True
ui_update_thread.start() ui_update_thread.start()
from gi.repository import Gtk
Gtk.main() Gtk.main()
watcher.stop() watcher.stop()

View File

@ -0,0 +1,41 @@
#
#
#
import logging
from logitech.unifying_receiver import api
from logitech import devices
from . import ui
import ui.pair
def full_scan(button, watcher):
if watcher.active and watcher.listener:
updated = False
for devnumber in range(1, 1 + api.C.MAX_ATTACHED_DEVICES):
devstatus = watcher.devices.get(devnumber)
if devstatus:
status = devices.request_status(devstatus, watcher.listener)
updated |= watcher._device_status_changed(devstatus, status)
else:
devstatus = watcher._new_device(devnumber)
updated |= devstatus is not None
if updated:
watcher._update_status_text()
return updated
def pair(button, watcher):
if watcher.active and watcher.listener:
logging.debug("pair")
parent = button.get_toplevel()
title = parent.get_title() + ': ' + button.get_tooltip_text()
w = ui.pair.create(parent, title)
w.run()
w.destroy()

View File

@ -4,14 +4,9 @@
from gi.repository import Gtk from gi.repository import Gtk
from logitech.devices import constants as C
_ICON_OK = 'Solaar'
_ICON_FAIL = _ICON_OK + '-fail'
def create(title, click_action=None): def create(title, click_action=None):
icon = Gtk.StatusIcon.new_from_icon_name(_ICON_OK) icon = Gtk.StatusIcon()
icon.set_title(title) icon.set_title(title)
icon.set_name(title) icon.set_name(title)
@ -37,9 +32,8 @@ def create(title, click_action=None):
return icon return icon
def update(icon, receiver, tooltip): def update(icon, receiver, tooltip=None, icon_name=None):
icon.set_tooltip_markup(tooltip) if tooltip is not None:
if receiver.code < C.STATUS.CONNECTED: icon.set_tooltip_markup(tooltip)
icon.set_from_icon_name(_ICON_FAIL) if icon_name is not None:
else: icon.set_from_icon_name(icon_name)
icon.set_from_icon_name(_ICON_OK)

17
app/ui/pair.py Normal file
View File

@ -0,0 +1,17 @@
#
#
#
from gi.repository import Gtk
def create(parent_window, title):
window = Gtk.Dialog(title, parent_window, Gtk.DialogFlags.MODAL, buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))
Gtk.Window.set_default_icon_name('add')
window.set_resizable(False)
# window.set_wmclass(title, 'status-window')
# window.set_role('pair')
return window

View File

@ -77,8 +77,11 @@ def _update_device_box(frame, devstatus):
expander.set_label(_PLACEHOLDER) expander.set_label(_PLACEHOLDER)
def update(window, receiver, devices): def update(window, receiver, devices, icon_name=None):
if window and window.get_child(): if window and window.get_child():
if icon_name is not None:
window.set_icon_name(icon_name)
controls = list(window.get_child().get_children()) controls = list(window.get_child().get_children())
_update_receiver_box(controls[0], receiver) _update_receiver_box(controls[0], receiver)
for index in range(1, 1 + _MAX_DEVICES): for index in range(1, 1 + _MAX_DEVICES):
@ -107,9 +110,9 @@ def _receiver_box(rstatus):
buttons_box.set_layout(Gtk.ButtonBoxStyle.START) buttons_box.set_layout(Gtk.ButtonBoxStyle.START)
vbox.pack_start(buttons_box, True, True, 0) vbox.pack_start(buttons_box, True, True, 0)
def _action(button, action): def _action(button, function, params):
button.set_sensitive(False) button.set_sensitive(False)
action() function(button, *params)
button.set_sensitive(True) button.set_sensitive(True)
def _add_button(name, icon, action): def _add_button(name, icon, action):
@ -119,7 +122,9 @@ def _receiver_box(rstatus):
button.set_tooltip_text(name) button.set_tooltip_text(name)
button.set_focus_on_click(False) button.set_focus_on_click(False)
if action: if action:
button.connect('clicked', _action, action) function = action[0]
params = action[1:]
button.connect('clicked', _action, function, params)
else: else:
button.set_sensitive(False) button.set_sensitive(False)
buttons_box.pack_start(button, False, False, 0) buttons_box.pack_start(button, False, False, 0)
@ -167,14 +172,12 @@ def _device_box():
def create(title, rstatus, show=True, close_to_tray=False): def create(title, rstatus, show=True, close_to_tray=False):
window = Gtk.Window() window = Gtk.Window()
Gtk.Window.set_default_icon_name('mouse')
window.set_icon_name(title)
window.set_title(title) window.set_title(title)
window.set_keep_above(True) window.set_keep_above(True)
window.set_deletable(False) window.set_deletable(False)
window.set_resizable(False) window.set_resizable(False)
window.set_size_request(200, 50) # window.set_size_request(200, 50)
window.set_default_size(200, 50) window.set_default_size(200, 50)
window.set_position(Gtk.WindowPosition.MOUSE) window.set_position(Gtk.WindowPosition.MOUSE)

View File

@ -5,13 +5,17 @@
import logging import logging
import threading import threading
import time import time
from binascii import hexlify as _hexlify
from logitech.unifying_receiver import api from logitech.unifying_receiver import api
from logitech.unifying_receiver.listener import EventsListener from logitech.unifying_receiver.listener import EventsListener
from logitech import devices from logitech import devices
from logitech.devices import constants as C from logitech.devices import constants as C
from . import actions
_l = logging.getLogger('watcher')
_STATUS_TIMEOUT = 31 # seconds _STATUS_TIMEOUT = 31 # seconds
_THREAD_SLEEP = 2 # seconds _THREAD_SLEEP = 2 # seconds
@ -30,11 +34,14 @@ class _DevStatus(api.AttachedDeviceInfo):
text = _INITIALIZING text = _INITIALIZING
refresh = None refresh = None
def __str__(self):
return 'DevStatus(%d,%s,%d)' % (self.number, self.name, self.code)
class WatcherThread(threading.Thread): class WatcherThread(threading.Thread):
"""Keeps a map of all attached devices and their statuses.""" """Keeps a map of all attached devices and their statuses."""
def __init__(self, notify_callback=None): def __init__(self, notify_callback=None):
super(WatcherThread, self).__init__(name='WatcherThread') super(WatcherThread, self).__init__(group='Solaar', name='Watcher')
self.daemon = True self.daemon = True
self.active = False self.active = False
@ -44,9 +51,9 @@ class WatcherThread(threading.Thread):
self.listener = None self.listener = None
self.rstatus = _DevStatus(0, _UNIFYING_RECEIVER, _UNIFYING_RECEIVER, None, None) self.rstatus = _DevStatus(0, 0xFF, None, _UNIFYING_RECEIVER, None, None)
self.rstatus.refresh = self.full_scan self.rstatus.refresh = (actions.full_scan, self)
self.rstatus.pair = None self.rstatus.pair = None # (actions.pair, self)
self.devices = {} self.devices = {}
@ -55,7 +62,7 @@ class WatcherThread(threading.Thread):
while self.active: while self.active:
if self.listener is None: if self.listener is None:
self._device_status_changed(self.rstatus, (C.STATUS.UNKNOWN, _INITIALIZING)) self._device_status_changed(self.rstatus, (C.STATUS.UNKNOWN, _SCANNING))
self._update_status_text() self._update_status_text()
receiver = api.open() receiver = api.open()
@ -65,57 +72,38 @@ class WatcherThread(threading.Thread):
for devinfo in api.list_devices(receiver): for devinfo in api.list_devices(receiver):
self._new_device(devinfo) self._new_device(devinfo)
logging.debug("initial scan finished: %s", self.devices)
if self.devices: if self.devices:
self._device_status_changed(self.rstatus, (C.STATUS.CONNECTED, _OKAY)) self._update_status_text()
else:
self._device_status_changed(self.rstatus, (C.STATUS.CONNECTED, _NO_DEVICES))
self._update_status_text()
self.listener = EventsListener(receiver, self._events_callback) self.listener = EventsListener(receiver, self._events_callback)
self.listener.start() self.listener.start()
else: elif not self.listener:
self._device_status_changed(self.rstatus, (C.STATUS.UNAVAILABLE, _NO_RECEIVER))
elif not self.listener.active:
self.listener = None self.listener = None
self._device_status_changed(self.rstatus, (C.STATUS.UNAVAILABLE, _NO_RECEIVER))
self.devices.clear() self.devices.clear()
if self.active: if self.listener:
update_icon = True if self.devices:
if self.listener and self.devices: update_icon = self._check_old_statuses()
update_icon &= self._check_old_statuses() else:
update_icon = self._device_status_changed(self.rstatus, (C.STATUS.CONNECTED, _NO_DEVICES))
else:
update_icon = self._device_status_changed(self.rstatus, (C.STATUS.UNAVAILABLE, _NO_RECEIVER))
if update_icon:
self._update_status_text()
if self.active: if self.active:
if update_icon:
self._update_status_text()
time.sleep(_THREAD_SLEEP) time.sleep(_THREAD_SLEEP)
self.listener.stop()
if self.listener: if self.listener:
self.listener.stop()
api.close(self.listener.receiver) api.close(self.listener.receiver)
self.listener = None self.listener = None
def stop(self): def stop(self):
self.active = False self.active = False
self.join() self.join()
def full_scan(self, *args):
if self.active and self.listener:
updated = False
for devnumber in range(1, 1 + api.C.MAX_ATTACHED_DEVICES):
devstatus = self.devices.get(devnumber)
if devstatus:
status = devices.request_status(devstatus, self.listener)
updated |= self._device_status_changed(devstatus, status)
else:
devstatus = self._new_device(devnumber)
updated |= devstatus is not None
if updated:
self._update_status_text()
def _request_status(self, devstatus): def _request_status(self, devstatus):
if self.listener and devstatus: if self.listener and devstatus:
status = devices.request_status(devstatus, self.listener) status = devices.request_status(devstatus, self.listener)
@ -124,7 +112,7 @@ class WatcherThread(threading.Thread):
def _check_old_statuses(self): def _check_old_statuses(self):
updated = False updated = False
for devstatus in list(self.devices.values()): for devstatus in self.devices.values():
if devstatus != self.rstatus: if devstatus != self.rstatus:
if time.time() - devstatus.timestamp > _STATUS_TIMEOUT: if time.time() - devstatus.timestamp > _STATUS_TIMEOUT:
status = devices.ping(devstatus, self.listener) status = devices.ping(devstatus, self.listener)
@ -137,17 +125,20 @@ class WatcherThread(threading.Thread):
return None return None
if type(dev) == int: if type(dev) == int:
# assert self.listener
dev = self.listener.request(api.get_device_info, dev) dev = self.listener.request(api.get_device_info, dev)
if dev: if dev:
devstatus = _DevStatus(*dev) devstatus = _DevStatus(*dev)
devstatus.refresh = self._request_status devstatus.refresh = self._request_status
self.devices[dev.number] = devstatus self.devices[dev.number] = devstatus
_l.debug("new devstatus %s", devstatus)
self._device_status_changed(devstatus, C.STATUS.CONNECTED) self._device_status_changed(devstatus, C.STATUS.CONNECTED)
logging.debug("new devstatus %s", devstatus) self._device_status_changed(self.rstatus, (C.STATUS.CONNECTED, _OKAY))
return devstatus return devstatus
def _events_callback(self, code, devnumber, data): def _events_callback(self, code, devnumber, data):
logging.debug("%s: event (%02x %02x [%s])", time.asctime(), code, devnumber, _hexlify(data)) # _l.debug("event %s", (code, devnumber, data))
updated = False updated = False
@ -160,12 +151,12 @@ class WatcherThread(threading.Thread):
status = devices.process_event(devstatus, self.listener, data) status = devices.process_event(devstatus, self.listener, data)
updated |= self._device_status_changed(devstatus, status) updated |= self._device_status_changed(devstatus, status)
else: else:
logging.warn("unknown event code %02x", code) _l.warn("unknown event code %02x", code)
elif devnumber: elif devnumber:
self._new_device(devnumber) self._new_device(devnumber)
updated = True updated = True
else: else:
logging.warn("don't know how to handle event (%02x, %02x, [%s])", code, devnumber, _hexlify(data)) _l.warn("don't know how to handle event %s", (code, devnumber, data))
if updated: if updated:
self._update_status_text() self._update_status_text()
@ -196,21 +187,21 @@ class WatcherThread(threading.Thread):
status_code = C.STATUS.UNKNOWN status_code = C.STATUS.UNKNOWN
status_text = '' status_text = ''
if not (status_code == C.STATUS.CONNECTED and old_status_code > C.STATUS.CONNECTED): if ((status_code == old_status_code and status_text == devstatus.text) or
# if this is not just a ping for a device with an already known status (status_code == C.STATUS.CONNECTED and old_status_code > C.STATUS.CONNECTED)):
devstatus.code = status_code # this is just successful ping for a device with an already known status
devstatus.text = status_text return False
logging.debug("%s: device '%s' status update %s => %s: %s", time.asctime(), devstatus.name, old_status_code, status_code, status_text)
devstatus.code = status_code
devstatus.text = status_text
_l.debug("%s status update %s => %s: %s", devstatus, old_status_code, status_code, status_text)
if self.notify:
if status_code < C.STATUS.CONNECTED or old_status_code < C.STATUS.CONNECTED or status_code < old_status_code: if status_code < C.STATUS.CONNECTED or old_status_code < C.STATUS.CONNECTED or status_code < old_status_code:
self._notify(devstatus.code, devstatus.name, devstatus.text) self.notify(devstatus.code, devstatus.name, devstatus.text)
return True return True
def _notify(self, *args):
if self.notify:
self.notify(*args)
def _update_status_text(self): def _update_status_text(self):
last_status_text = self.status_text last_status_text = self.status_text

View File

@ -44,10 +44,10 @@ def _continuous_read(handle, timeout=1000):
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
arg_parser = argparse.ArgumentParser() arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('device', default=None,
help='linux device to connect to')
arg_parser.add_argument('--history', default='.hidconsole-history', arg_parser.add_argument('--history', default='.hidconsole-history',
help='history file') help='history file')
arg_parser.add_argument('device', default=None,
help='linux device to connect to')
args = arg_parser.parse_args() args = arg_parser.parse_args()
import hidapi import hidapi

View File

@ -18,8 +18,8 @@ NAME = 'Wireless Solar Keyboard K750'
# #
# #
def _trigger_solar_charge_events(receiver, devinfo): def _trigger_solar_charge_events(handle, devinfo):
return _api.request(receiver, devinfo.number, return _api.request(handle, devinfo.number,
feature=_api.C.FEATURE.SOLAR_CHARGE, function=b'\x03', params=b'\x78\x01', feature=_api.C.FEATURE.SOLAR_CHARGE, function=b'\x03', params=b'\x78\x01',
features_array=devinfo.features) features_array=devinfo.features)

View File

@ -23,3 +23,9 @@ http://6xq.net/git/lars/lshidpp.git/plain/doc/
from .constants import * from .constants import *
from .exceptions import * from .exceptions import *
from .api import * from .api import *
import logging
logging.addLevelName(4, 'UR_TRACE')
logging.addLevelName(5, 'UR_DEBUG')
logging.addLevelName(6, 'UR_INFO')

View File

@ -58,13 +58,13 @@ def request(handle, devnumber, feature, function=b'\x00', params=b'', features_a
if features_array is None: if features_array is None:
features_array = get_device_features(handle, devnumber) features_array = get_device_features(handle, devnumber)
if features_array is None: if features_array is None:
_l.log(_LOG_LEVEL, "(%d,%d) no features array available", handle, devnumber) _l.log(_LOG_LEVEL, "(%d) no features array available", devnumber)
return None return None
if feature in features_array: if feature in features_array:
feature_index = _pack('!B', features_array.index(feature)) feature_index = _pack('!B', features_array.index(feature))
if feature_index is None: if feature_index is None:
_l.warn("(%d,%d) feature <%s:%s> not supported", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature]) _l.warn("(%d) feature <%s:%s> not supported", devnumber, _hexlify(feature), C.FEATURE_NAME[feature])
raise E.FeatureNotSupported(devnumber, feature) raise E.FeatureNotSupported(devnumber, feature)
return _base.request(handle, devnumber, feature_index + function, params) return _base.request(handle, devnumber, feature_index + function, params)
@ -91,7 +91,7 @@ def find_device_by_name(handle, device_name):
:returns: an AttachedDeviceInfo tuple, or ``None``. :returns: an AttachedDeviceInfo tuple, or ``None``.
""" """
_l.log(_LOG_LEVEL, "(%d,) searching for device '%s'", handle, device_name) _l.log(_LOG_LEVEL, "searching for device '%s'", device_name)
for devnumber in range(1, 1 + C.MAX_ATTACHED_DEVICES): for devnumber in range(1, 1 + C.MAX_ATTACHED_DEVICES):
features_array = get_device_features(handle, devnumber) features_array = get_device_features(handle, devnumber)
@ -106,7 +106,7 @@ def list_devices(handle):
:returns: a list of AttachedDeviceInfo tuples. :returns: a list of AttachedDeviceInfo tuples.
""" """
_l.log(_LOG_LEVEL, "(%d,) listing all devices", handle) _l.log(_LOG_LEVEL, "listing all devices")
devices = [] devices = []
@ -131,8 +131,8 @@ def get_device_info(handle, devnumber, device_name=None, features_array=None):
d_type = get_device_type(handle, devnumber, features_array) d_type = get_device_type(handle, devnumber, features_array)
d_name = get_device_name(handle, devnumber, features_array) if device_name is None else device_name d_name = get_device_name(handle, devnumber, features_array) if device_name is None else device_name
d_firmware = get_device_firmware(handle, devnumber, features_array) d_firmware = get_device_firmware(handle, devnumber, features_array)
devinfo = AttachedDeviceInfo(devnumber, d_type, d_name, d_firmware, features_array) devinfo = AttachedDeviceInfo(handle, devnumber, d_type, d_name, d_firmware, features_array)
_l.log(_LOG_LEVEL, "(%d,%d) found device %s", handle, devnumber, devinfo) _l.log(_LOG_LEVEL, "(%d) found device %s", devnumber, devinfo)
return devinfo return devinfo
@ -141,7 +141,7 @@ def get_feature_index(handle, devnumber, feature):
:returns: An int, or ``None`` if the feature is not available. :returns: An int, or ``None`` if the feature is not available.
""" """
_l.log(_LOG_LEVEL, "(%d,%d) get feature index <%s:%s>", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature]) _l.log(_LOG_LEVEL, "(%d) get feature index <%s:%s>", devnumber, _hexlify(feature), C.FEATURE_NAME[feature])
if len(feature) != 2: if len(feature) != 2:
raise ValueError("invalid feature <%s>: it must be a two-byte string" % feature) raise ValueError("invalid feature <%s>: it must be a two-byte string" % feature)
@ -154,18 +154,18 @@ def get_feature_index(handle, devnumber, feature):
feature_flags = ord(reply[1:2]) & 0xE0 feature_flags = ord(reply[1:2]) & 0xE0
if _l.isEnabledFor(_LOG_LEVEL): if _l.isEnabledFor(_LOG_LEVEL):
if feature_flags: if feature_flags:
_l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> has index %d: %s", _l.log(_LOG_LEVEL, "(%d) feature <%s:%s> has index %d: %s",
handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], feature_index, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], feature_index,
','.join([C.FEATURE_FLAGS[k] for k in C.FEATURE_FLAGS if feature_flags & k])) ','.join([C.FEATURE_FLAGS[k] for k in C.FEATURE_FLAGS if feature_flags & k]))
else: else:
_l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> has index %d", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], feature_index) _l.log(_LOG_LEVEL, "(%d) feature <%s:%s> has index %d", devnumber, _hexlify(feature), C.FEATURE_NAME[feature], feature_index)
# if feature_flags: # if feature_flags:
# raise E.FeatureNotSupported(devnumber, feature) # raise E.FeatureNotSupported(devnumber, feature)
return feature_index return feature_index
_l.warn("(%d,%d) feature <%s:%s> not supported by the device", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature]) _l.warn("(%d) feature <%s:%s> not supported by the device", devnumber, _hexlify(feature), C.FEATURE_NAME[feature])
raise E.FeatureNotSupported(devnumber, feature) raise E.FeatureNotSupported(devnumber, feature)
@ -175,13 +175,13 @@ def get_device_features(handle, devnumber):
Their position in the array is the index to be used when requesting that Their position in the array is the index to be used when requesting that
feature on the device. feature on the device.
""" """
_l.log(_LOG_LEVEL, "(%d,%d) get device features", handle, devnumber) _l.log(_LOG_LEVEL, "(%d) get device features", devnumber)
# get the index of the FEATURE_SET # get the index of the FEATURE_SET
# FEATURE.ROOT should always be available for all devices # FEATURE.ROOT should always be available for all devices
fs_index = _base.request(handle, devnumber, C.FEATURE.ROOT, C.FEATURE.FEATURE_SET) fs_index = _base.request(handle, devnumber, C.FEATURE.ROOT, C.FEATURE.FEATURE_SET)
if fs_index is None: if fs_index is None:
# _l.warn("(%d,%d) FEATURE_SET not available", handle, device) # _l.warn("(%d) FEATURE_SET not available", device)
return None return None
fs_index = fs_index[:1] fs_index = fs_index[:1]
@ -193,11 +193,11 @@ def get_device_features(handle, devnumber):
if not features_count: if not features_count:
# this can happen if the device disappeard since the fs_index request # this can happen if the device disappeard since the fs_index request
# otherwise we should get at least a count of 1 (the FEATURE_SET we've just used above) # otherwise we should get at least a count of 1 (the FEATURE_SET we've just used above)
_l.log(_LOG_LEVEL, "(%d,%d) no features available?!", handle, devnumber) _l.log(_LOG_LEVEL, "(%d) no features available?!", devnumber)
return None return None
features_count = ord(features_count[:1]) features_count = ord(features_count[:1])
_l.log(_LOG_LEVEL, "(%d,%d) found %d features", handle, devnumber, features_count) _l.log(_LOG_LEVEL, "(%d) found %d features", devnumber, features_count)
features = [None] * 0x20 features = [None] * 0x20
for index in range(1, 1 + features_count): for index in range(1, 1 + features_count):
@ -210,11 +210,11 @@ def get_device_features(handle, devnumber):
if _l.isEnabledFor(_LOG_LEVEL): if _l.isEnabledFor(_LOG_LEVEL):
if feature_flags: if feature_flags:
_l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> at index %d: %s", _l.log(_LOG_LEVEL, "(%d) feature <%s:%s> at index %d: %s",
handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], index, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], index,
','.join([C.FEATURE_FLAGS[k] for k in C.FEATURE_FLAGS if feature_flags & k])) ','.join([C.FEATURE_FLAGS[k] for k in C.FEATURE_FLAGS if feature_flags & k]))
else: else:
_l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> at index %d", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], index) _l.log(_LOG_LEVEL, "(%d) feature <%s:%s> at index %d", devnumber, _hexlify(feature), C.FEATURE_NAME[feature], index)
features[0] = C.FEATURE.ROOT features[0] = C.FEATURE.ROOT
while features[-1] is None: while features[-1] is None:
@ -258,7 +258,7 @@ def get_device_firmware(handle, devnumber, features_array=None):
fw_info = _makeFirmwareInfo(level=fw_level, type=C.FIRMWARE_TYPE[-1]) fw_info = _makeFirmwareInfo(level=fw_level, type=C.FIRMWARE_TYPE[-1])
fw.append(fw_info) fw.append(fw_info)
_l.log(_LOG_LEVEL, "(%d:%d) firmware %s", handle, devnumber, fw_info) _l.log(_LOG_LEVEL, "(%d) firmware %s", devnumber, fw_info)
return fw return fw
@ -272,7 +272,7 @@ def get_device_type(handle, devnumber, features_array=None):
d_type = request(handle, devnumber, C.FEATURE.NAME, function=b'\x20', features_array=features_array) d_type = request(handle, devnumber, C.FEATURE.NAME, function=b'\x20', features_array=features_array)
if d_type: if d_type:
d_type = ord(d_type[:1]) d_type = ord(d_type[:1])
_l.log(_LOG_LEVEL, "(%d,%d) device type %d = %s", handle, devnumber, d_type, C.DEVICE_TYPE[d_type]) _l.log(_LOG_LEVEL, "(%d) device type %d = %s", devnumber, d_type, C.DEVICE_TYPE[d_type])
return C.DEVICE_TYPE[d_type] return C.DEVICE_TYPE[d_type]
@ -297,7 +297,7 @@ def get_device_name(handle, devnumber, features_array=None):
break break
d_name = d_name.decode('ascii') d_name = d_name.decode('ascii')
_l.log(_LOG_LEVEL, "(%d,%d) device name %s", handle, devnumber, d_name) _l.log(_LOG_LEVEL, "(%d) device name %s", devnumber, d_name)
return d_name return d_name
@ -309,7 +309,8 @@ def get_device_battery_level(handle, devnumber, features_array=None):
battery = request(handle, devnumber, C.FEATURE.BATTERY, features_array=features_array) battery = request(handle, devnumber, C.FEATURE.BATTERY, features_array=features_array)
if battery: if battery:
discharge, dischargeNext, status = _unpack('!BBB', battery[:3]) discharge, dischargeNext, status = _unpack('!BBB', battery[:3])
_l.log(_LOG_LEVEL, "(%d:%d) battery %d%% charged, next level %d%% charge, status %d = %s", discharge, dischargeNext, status, C.BATTERY_STATUSE[status]) _l.log(_LOG_LEVEL, "(%d) battery %d%% charged, next level %d%% charge, status %d = %s",
devnumber, discharge, dischargeNext, status, C.BATTERY_STATUSE[status])
return (discharge, dischargeNext, C.BATTERY_STATUS[status]) return (discharge, dischargeNext, C.BATTERY_STATUS[status])

View File

@ -10,11 +10,10 @@ from binascii import hexlify as _hexlify
from . import constants as C from . import constants as C
from . import exceptions as E from . import exceptions as E
from . import unhandled as _unhandled
import hidapi as _hid import hidapi as _hid
_LOG_LEVEL = 5 _LOG_LEVEL = 4
_l = logging.getLogger('lur.base') _l = logging.getLogger('lur.base')
# #
@ -45,6 +44,31 @@ DEFAULT_TIMEOUT = 1000
# #
# #
def _logdebug_hook(reply_code, devnumber, data):
"""Default unhandled hook, logs the reply as DEBUG."""
_l.debug("UNHANDLED %s", (reply_code, devnumber, reply_code, data))
"""The function that will be called on unhandled incoming events.
The hook must be a function with the signature: ``_(int, int, str)``, where
the parameters are: (reply_code, devnumber, data).
This hook will only be called by the request() function, when it receives
replies that do not match the requested feature call. As such, it is not
suitable for intercepting broadcast events from the device (e.g. special
keys being pressed, battery charge events, etc), at least not in a timely
manner. However, these events *may* be delivered here if they happen while
doing a feature call to the device.
The default implementation logs the unhandled reply as DEBUG.
"""
unhandled_hook = _logdebug_hook
#
#
#
def list_receiver_devices(): def list_receiver_devices():
"""List all the Linux devices exposed by the UR attached to the machine.""" """List all the Linux devices exposed by the UR attached to the machine."""
# (Vendor ID, Product ID) = ('Logitech', 'Unifying Receiver') # (Vendor ID, Product ID) = ('Logitech', 'Unifying Receiver')
@ -72,7 +96,7 @@ def try_open(path):
_l.log(_LOG_LEVEL, "[%s] open failed", path) _l.log(_LOG_LEVEL, "[%s] open failed", path)
return None return None
_l.log(_LOG_LEVEL, "[%s] receiver handle (%d,)", path, receiver_handle) _l.log(_LOG_LEVEL, "[%s] receiver handle 0x%x", path, receiver_handle)
# ping on device id 0 (always an error) # ping on device id 0 (always an error)
_hid.write(receiver_handle, b'\x10\x00\x00\x10\x00\x00\xAA') _hid.write(receiver_handle, b'\x10\x00\x00\x10\x00\x00\xAA')
@ -82,18 +106,18 @@ def try_open(path):
if reply: if reply:
if reply[:4] == b'\x10\x00\x8F\x00': if reply[:4] == b'\x10\x00\x8F\x00':
# 'device 0 unreachable' is the expected reply from a valid receiver handle # 'device 0 unreachable' is the expected reply from a valid receiver handle
_l.log(_LOG_LEVEL, "[%s] success: handle (%d,)", path, receiver_handle) _l.log(_LOG_LEVEL, "[%s] success: handle %x", path, receiver_handle)
return receiver_handle return receiver_handle
# any other replies are ignored, and will assume this is the wrong Linux device # any other replies are ignored, and will assume this is the wrong Linux device
if _l.isEnabledFor(_LOG_LEVEL): if _l.isEnabledFor(_LOG_LEVEL):
if reply == b'\x01\x00\x00\x00\x00\x00\x00\x00': if reply == b'\x01\x00\x00\x00\x00\x00\x00\x00':
# no idea what this is, but it comes up occasionally # no idea what this is, but it comes up occasionally
_l.log(_LOG_LEVEL, "[%s] (%d,) mistery reply [%s]", path, receiver_handle, _hexlify(reply)) _l.log(_LOG_LEVEL, "[%s] %x mistery reply [%s]", path, receiver_handle, _hexlify(reply))
else: else:
_l.log(_LOG_LEVEL, "[%s] (%d,) unknown reply [%s]", path, receiver_handle, _hexlify(reply)) _l.log(_LOG_LEVEL, "[%s] %x unknown reply [%s]", path, receiver_handle, _hexlify(reply))
else: else:
_l.log(_LOG_LEVEL, "[%s] (%d,) no reply", path, receiver_handle) _l.log(_LOG_LEVEL, "[%s] %x no reply", path, receiver_handle)
close(receiver_handle) close(receiver_handle)
@ -118,10 +142,10 @@ def close(handle):
if handle: if handle:
try: try:
_hid.close(handle) _hid.close(handle)
_l.log(_LOG_LEVEL, "(%d,) closed", handle) _l.log(_LOG_LEVEL, "%x closed", handle)
return True return True
except: except:
_l.exception("(%d,) closing", handle) _l.exception("%x closing", handle)
return False return False
@ -147,10 +171,10 @@ def write(handle, devnumber, data):
if _l.isEnabledFor(_LOG_LEVEL): if _l.isEnabledFor(_LOG_LEVEL):
hexs = _hexlify(wdata) hexs = _hexlify(wdata)
_l.log(_LOG_LEVEL, "(%d,%d) <= w[%s %s %s %s]", handle, devnumber, hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:]) _l.log(_LOG_LEVEL, "(%d) <= w[%s %s %s %s]", devnumber, hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:])
if not _hid.write(handle, wdata): if not _hid.write(handle, wdata):
_l.warn("(%d,%d) write failed, assuming receiver no longer available", handle, devnumber) _l.warn("(%d) write failed, assuming receiver %x no longer available", devnumber, handle)
close(handle) close(handle)
raise E.NoReceiver raise E.NoReceiver
@ -173,23 +197,23 @@ def read(handle, timeout=DEFAULT_TIMEOUT):
""" """
data = _hid.read(handle, _MAX_REPLY_SIZE * 2, timeout) data = _hid.read(handle, _MAX_REPLY_SIZE * 2, timeout)
if data is None: if data is None:
_l.warn("(%d,*) read failed, assuming receiver no longer available", handle) _l.warn("(-) read failed, assuming receiver %x no longer available", handle)
close(handle) close(handle)
raise E.NoReceiver raise E.NoReceiver
if data: if data:
if len(data) < _MIN_REPLY_SIZE: if len(data) < _MIN_REPLY_SIZE:
_l.warn("(%d,*) => r[%s] read packet too short: %d bytes", handle, _hexlify(data), len(data)) _l.warn("(%d) => r[%s] read packet too short: %d bytes", ord(data[1:2]), _hexlify(data), len(data))
if len(data) > _MAX_REPLY_SIZE: if len(data) > _MAX_REPLY_SIZE:
_l.warn("(%d,*) => r[%s] read packet too long: %d bytes", handle, _hexlify(data), len(data)) _l.warn("(%d) => r[%s] read packet too long: %d bytes", ord(data[1:2]), _hexlify(data), len(data))
if _l.isEnabledFor(_LOG_LEVEL): if _l.isEnabledFor(_LOG_LEVEL):
hexs = _hexlify(data) hexs = _hexlify(data)
_l.log(_LOG_LEVEL, "(%d,*) => r[%s %s %s %s]", handle, hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:]) _l.log(_LOG_LEVEL, "(%d) => r[%s %s %s %s]", ord(data[1:2]), hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:])
code = ord(data[:1]) code = ord(data[:1])
devnumber = ord(data[1:2]) devnumber = ord(data[1:2])
return code, devnumber, data[2:] return code, devnumber, data[2:]
# _l.log(_LOG_LEVEL, "(%d,*) => r[]", handle) # _l.log(_LOG_LEVEL, "(-) => r[]", handle)
def request(handle, devnumber, feature_index_function, params=b'', features_array=None): def request(handle, devnumber, feature_index_function, params=b'', features_array=None):
@ -210,7 +234,7 @@ def request(handle, devnumber, feature_index_function, params=b'', features_arra
:raisees FeatureCallError: if the feature call replied with an error. :raisees FeatureCallError: if the feature call replied with an error.
""" """
if _l.isEnabledFor(_LOG_LEVEL): if _l.isEnabledFor(_LOG_LEVEL):
_l.log(_LOG_LEVEL, "(%d,%d) request {%s} params [%s]", handle, devnumber, _hexlify(feature_index_function), _hexlify(params)) _l.log(_LOG_LEVEL, "(%d) request {%s} params [%s]", devnumber, _hexlify(feature_index_function), _hexlify(params))
if len(feature_index_function) != 2: if len(feature_index_function) != 2:
raise ValueError('invalid feature_index_function {%s}: it must be a two-byte string' % _hexlify(feature_index_function)) raise ValueError('invalid feature_index_function {%s}: it must be a two-byte string' % _hexlify(feature_index_function))
@ -230,26 +254,27 @@ def request(handle, devnumber, feature_index_function, params=b'', features_arra
if reply_devnumber != devnumber: if reply_devnumber != devnumber:
# this message not for the device we're interested in # this message not for the device we're interested in
_l.log(_LOG_LEVEL, "(%d,%d) request got reply for unexpected device %d: [%s]", handle, devnumber, reply_devnumber, _hexlify(reply_data)) _l.log(_LOG_LEVEL, "(%d) request got reply for unexpected device %d: [%s]", devnumber, reply_devnumber, _hexlify(reply_data))
# worst case scenario, this is a reply for a concurrent request # worst case scenario, this is a reply for a concurrent request
# on this receiver # on this receiver
_unhandled._publish(reply_code, reply_devnumber, reply_data) if unhandled_hook:
unhandled_hook(reply_code, reply_devnumber, reply_data)
continue continue
if reply_code == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == feature_index_function: if reply_code == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == feature_index_function:
# device not present # device not present
_l.log(_LOG_LEVEL, "(%d,%d) request ping failed on {%s} call: [%s]", handle, devnumber, _hexlify(feature_index_function), _hexlify(reply_data)) _l.log(_LOG_LEVEL, "(%d) request ping failed on {%s} call: [%s]", devnumber, _hexlify(feature_index_function), _hexlify(reply_data))
return None return None
if reply_code == 0x10 and reply_data[:1] == b'\x8F': if reply_code == 0x10 and reply_data[:1] == b'\x8F':
# device not present # device not present
_l.log(_LOG_LEVEL, "(%d,%d) request ping failed: [%s]", handle, devnumber, _hexlify(reply_data)) _l.log(_LOG_LEVEL, "(%d) request ping failed: [%s]", devnumber, _hexlify(reply_data))
return None return None
if reply_code == 0x11 and reply_data[0] == b'\xFF' and reply_data[1:3] == feature_index_function: if reply_code == 0x11 and reply_data[0] == b'\xFF' and reply_data[1:3] == feature_index_function:
# an error returned from the device # an error returned from the device
error_code = ord(reply_data[3]) error_code = ord(reply_data[3])
_l.warn("(%d,%d) request feature call error %d = %s: %s", handle, devnumber, error_code, C.ERROR_NAME[error_code], _hexlify(reply_data)) _l.warn("(%d) request feature call error %d = %s: %s", devnumber, error_code, C.ERROR_NAME[error_code], _hexlify(reply_data))
feature_index = ord(feature_index_function[:1]) feature_index = ord(feature_index_function[:1])
feature_function = feature_index_function[1:2] feature_function = feature_index_function[1:2]
feature = None if features_array is None else features_array[feature_index] feature = None if features_array is None else features_array[feature_index]
@ -257,8 +282,9 @@ def request(handle, devnumber, feature_index_function, params=b'', features_arra
if reply_code == 0x11 and reply_data[:2] == feature_index_function: if reply_code == 0x11 and reply_data[:2] == feature_index_function:
# a matching reply # a matching reply
# _l.log(_LOG_LEVEL, "(%d,%d) matched reply with feature-index-function [%s]", handle, devnumber, _hexlify(reply_data[2:])) # _l.log(_LOG_LEVEL, "(%d) matched reply with feature-index-function [%s]", devnumber, _hexlify(reply_data[2:]))
return reply_data[2:] return reply_data[2:]
_l.log(_LOG_LEVEL, "(%d,%d) unmatched reply {%s} (expected {%s})", handle, devnumber, _hexlify(reply_data[:2]), _hexlify(feature_index_function)) _l.log(_LOG_LEVEL, "(%d) unmatched reply {%s} (expected {%s})", devnumber, _hexlify(reply_data[:2]), _hexlify(feature_index_function))
_unhandled._publish(reply_code, reply_devnumber, reply_data) if unhandled_hook:
unhandled_hook(reply_code, reply_devnumber, reply_data)

View File

@ -22,6 +22,7 @@ from collections import namedtuple
"""Tuple returned by list_devices and find_device_by_name.""" """Tuple returned by list_devices and find_device_by_name."""
AttachedDeviceInfo = namedtuple('AttachedDeviceInfo', [ AttachedDeviceInfo = namedtuple('AttachedDeviceInfo', [
'handle',
'number', 'number',
'type', 'type',
'name', 'name',

View File

@ -5,40 +5,52 @@
import logging import logging
import threading import threading
from time import sleep as _sleep from time import sleep as _sleep
from binascii import hexlify as _hexlify
from . import base as _base from . import base as _base
from . import exceptions as E from . import exceptions as E
# from . import unhandled as _unhandled
# for both Python 2 and 3
try:
import Queue as queue
except ImportError:
import queue
_LOG_LEVEL = 5 _LOG_LEVEL = 5
_l = logging.getLogger('lur.listener') _l = logging.getLogger('lur.listener')
_READ_EVENT_TIMEOUT = int(_base.DEFAULT_TIMEOUT / 5) # ms _READ_EVENT_TIMEOUT = int(_base.DEFAULT_TIMEOUT / 4) # ms
_IDLE_SLEEP = _base.DEFAULT_TIMEOUT / 2 # ms _IDLE_SLEEP = _base.DEFAULT_TIMEOUT / 4 # ms
def _callback_caller(listener, callback):
# _l.log(_LOG_LEVEL, "%s starting callback caller", listener)
while listener._active:
event = listener.events.get()
if _l.isEnabledFor(_LOG_LEVEL):
_l.log(_LOG_LEVEL, "%s delivering event %s", listener, event)
callback.__call__(*event)
# _l.log(_LOG_LEVEL, "%s stopped callback caller", listener)
class EventsListener(threading.Thread): class EventsListener(threading.Thread):
"""Listener thread for events from the Unifying Receiver. """Listener thread for events from the Unifying Receiver.
Incoming events (reply_code, devnumber, data) will be passed to the callback Incoming events (reply_code, devnumber, data) will be passed to the callback
function. The callback is called in the listener thread, so for best results function. The callback is called in a separate thread.
it should return as fast as possible.
While this listener is running, you should use the request() method to make While this listener is running, you should use the request() method to make
regular UR API calls, otherwise the replies may be captured by the listener regular UR API calls, otherwise the replies are very likely to be captured
and delivered as events to the callback. As an exception, you can make API by the listener and delivered as events to the callback. As an exception,
calls in the events callback. you can make API calls in the events callback.
""" """
def __init__(self, receiver, events_callback): def __init__(self, receiver, events_callback):
super(EventsListener, self).__init__(name='Unifying_Receiver_Listener_' + hex(receiver)) super(EventsListener, self).__init__(group='Unifying Receiver', name='Events-%x' % receiver)
self.daemon = True self.daemon = True
self.active = False self._active = False
self.receiver = receiver self.receiver = receiver
self.callback = events_callback
self.task = None self.task = None
self.task_processing = threading.Lock() self.task_processing = threading.Lock()
@ -46,40 +58,50 @@ class EventsListener(threading.Thread):
self.task_reply = None self.task_reply = None
self.task_done = threading.Event() self.task_done = threading.Event()
self.events = queue.Queue(32)
self.event_caller = threading.Thread(group='Unifying Receiver', name='Callback-%x' % receiver, target=_callback_caller, args=(self, events_callback))
self.event_caller.daemon = True
self.__str_cached = 'Events(%x)' % self.receiver
def run(self): def run(self):
self.active = True self._active = True
_l.log(_LOG_LEVEL, "(%d) starting", self.receiver) _l.log(_LOG_LEVEL, "%s started", self)
# last_hook = _unhandled.hook self.__str_cached = 'Events(%x:active)' % self.receiver
# _unhandled.hook = self.callback self.event_caller.start()
while self.active: last_hook = _base.unhandled_hook
_base.unhandled_hook = self._unhandled
while self._active:
try: try:
# _l.log(_LOG_LEVEL, "(%d) reading next event", self.receiver)
event = _base.read(self.receiver, _READ_EVENT_TIMEOUT) event = _base.read(self.receiver, _READ_EVENT_TIMEOUT)
except E.NoReceiver: except E.NoReceiver:
_l.warn("(%d) receiver disconnected", self.receiver) _l.warn("%s receiver disconnected", self)
self.active = False self._active = False
break
if self.active: if self._active:
if event: if event:
if _l.isEnabledFor(_LOG_LEVEL): # _l.log(_LOG_LEVEL, "%s queueing event %s", self, event)
_l.log(_LOG_LEVEL, "(%d) got event (%02x %02x [%s])", self.receiver, event[0], event[1], _hexlify(event[2])) self.events.put(event)
self.callback.__call__(*event)
elif self.task is None: if self.task is None:
# _l.log(_LOG_LEVEL, "(%d) idle sleep", self.receiver) # _l.log(_LOG_LEVEL, "%s idle sleep", self)
_sleep(_IDLE_SLEEP / 1000.0) _sleep(_IDLE_SLEEP / 1000.0)
else: else:
self.task_reply = self._make_request(*self.task) self.task_reply = self._make_request(*self.task)
self.task_done.set() self.task_done.set()
# _unhandled.hook = last_hook self.__str_cached = 'Events(%x)' % self.receiver
_base.unhandled_hook = last_hook
def stop(self): def stop(self):
"""Tells the listener to stop as soon as possible.""" """Tells the listener to stop as soon as possible."""
_l.log(_LOG_LEVEL, "(%d) stopping", self.receiver) _l.log(_LOG_LEVEL, "%s stopping", self)
self.active = False self._active = False
def request(self, api_function, *args, **kwargs): def request(self, api_function, *args, **kwargs):
"""Make an UR API request. """Make an UR API request.
@ -88,7 +110,8 @@ class EventsListener(threading.Thread):
other args and kwargs will follow. other args and kwargs will follow.
""" """
# if _l.isEnabledFor(_LOG_LEVEL): # if _l.isEnabledFor(_LOG_LEVEL):
# _l.log(_LOG_LEVEL, "(%d) request '%s.%s' with %s, %s", self.receiver, api_function.__module__, api_function.__name__, args, kwargs) # _l.log(_LOG_LEVEL, "%s request '%s.%s' with %s, %s", self, api_function.__module__, api_function.__name__, args, kwargs)
self.task_processing.acquire() self.task_processing.acquire()
self.task_done.clear() self.task_done.clear()
self.task = (api_function, args, kwargs) self.task = (api_function, args, kwargs)
@ -99,18 +122,29 @@ class EventsListener(threading.Thread):
self.task_processing.release() self.task_processing.release()
# if _l.isEnabledFor(_LOG_LEVEL): # if _l.isEnabledFor(_LOG_LEVEL):
# _l.log(_LOG_LEVEL, "(%d) request '%s.%s' => [%s]", self.receiver, api_function.__module__, api_function.__name__, _hexlify(reply)) # _l.log(_LOG_LEVEL, "%s request '%s.%s' => %s", self, api_function.__module__, api_function.__name__, repr(reply))
if isinstance(reply, Exception): if isinstance(reply, Exception):
raise reply raise reply
return reply return reply
def _make_request(self, api_function, args, kwargs): def _make_request(self, api_function, args, kwargs):
if _l.isEnabledFor(_LOG_LEVEL): if _l.isEnabledFor(_LOG_LEVEL):
_l.log(_LOG_LEVEL, "(%d) calling '%s.%s' with %s, %s", self.receiver, api_function.__module__, api_function.__name__, args, kwargs) _l.log(_LOG_LEVEL, "%s calling '%s.%s' with %s, %s", self, api_function.__module__, api_function.__name__, args, kwargs)
try: try:
return api_function.__call__(self.receiver, *args, **kwargs) return api_function.__call__(self.receiver, *args, **kwargs)
except E.NoReceiver as nr: except E.NoReceiver as nr:
self.task_reply = nr self.task_reply = nr
self.active = False self._active = False
except Exception as e: except Exception as e:
self.task_reply = e self.task_reply = e
def _unhandled(self, reply_code, devnumber, data):
event = (reply_code, devnumber, data)
_l.log(_LOG_LEVEL, "%s queueing unhandled event %s", self, event)
self.events.put(event)
def __str__(self):
return self.__str_cached
def __nonzero__(self):
return self._active

View File

@ -8,7 +8,6 @@ from binascii import hexlify
from .. import base from .. import base
from ..exceptions import * from ..exceptions import *
from ..constants import * from ..constants import *
from .. import unhandled
class Test_UR_Base(unittest.TestCase): class Test_UR_Base(unittest.TestCase):
@ -157,7 +156,7 @@ class Test_UR_Base(unittest.TestCase):
global received_unhandled global received_unhandled
received_unhandled = (code, device, data) received_unhandled = (code, device, data)
unhandled.hook = _unhandled base.unhandled_hook = _unhandled
base.write(self.handle, self.device, FEATURE.ROOT + FEATURE.FEATURE_SET) base.write(self.handle, self.device, FEATURE.ROOT + FEATURE.FEATURE_SET)
reply = base.request(self.handle, self.device, fs_index + b'\x00') reply = base.request(self.handle, self.device, fs_index + b'\x00')
self.assertIsNotNone(reply, "request returned None reply") self.assertIsNotNone(reply, "request returned None reply")
@ -165,7 +164,7 @@ class Test_UR_Base(unittest.TestCase):
self.assertIsNotNone(received_unhandled, "extra message not received by unhandled hook") self.assertIsNotNone(received_unhandled, "extra message not received by unhandled hook")
received_unhandled = None received_unhandled = None
unhandled.hook = None base.unhandled_hook = None
base.write(self.handle, self.device, FEATURE.ROOT + FEATURE.FEATURE_SET) base.write(self.handle, self.device, FEATURE.ROOT + FEATURE.FEATURE_SET)
reply = base.request(self.handle, self.device, fs_index + b'\x00') reply = base.request(self.handle, self.device, fs_index + b'\x00')
self.assertIsNotNone(reply, "request returned None reply") self.assertIsNotNone(reply, "request returned None reply")

View File

@ -1,36 +0,0 @@
#
# Optional hook for unhandled data packets received while talking to the UR.
# These are usually broadcast events received from the attached devices.
#
import logging
from binascii import hexlify as _hexlify
def _logdebug_hook(reply_code, devnumber, data):
"""Default unhandled hook, logs the reply as DEBUG."""
_l = logging.getLogger('lur.unhandled')
_l.debug("UNHANDLED (,%d) code 0x%02x data [%s]", devnumber, reply_code, _hexlify(data))
"""The function that will be called on unhandled incoming events.
The hook must be a function with the signature: ``_(int, int, str)``, where
the parameters are: (reply_code, devnumber, data).
This hook will only be called by the request() function, when it receives
replies that do not match the requested feature call. As such, it is not
suitable for intercepting broadcast events from the device (e.g. special
keys being pressed, battery charge events, etc), at least not in a timely
manner. However, these events *may* be delivered here if they happen while
doing a feature call to the device.
The default implementation logs the unhandled reply as DEBUG.
"""
hook = _logdebug_hook
def _publish(reply_code, devnumber, data):
"""Delivers a reply to the unhandled hook, if any."""
if hook is not None:
hook.__call__(reply_code, devnumber, data)

View File

@ -1,6 +1,7 @@
#!/bin/sh #!/bin/sh
cd `dirname "$0"` cd -P `dirname "$0"`
export LD_LIBRARY_PATH=$PWD
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/native/`uname -m`
exec python -m unittest discover -v "$@" exec python -m unittest discover -v "$@"

6
solaar
View File

@ -1,10 +1,12 @@
#!/bin/sh #!/bin/sh
cd `dirname "$0"` cd -P `dirname "$0"`
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/lib/native/`uname -m`
export PYTHONPATH=$PWD:$PWD/lib export PYTHONPATH=$PWD:$PWD/lib
export XDG_DATA_DIRS=$PWD/resources:$XDG_DATA_DIRS export XDG_DATA_DIRS=$PWD/resources:$XDG_DATA_DIRS
cd -
exec python -OO solaar.py "$@" exec python -OO solaar.py "$@"
# exec python -OO -m profile -o $TMPDIR/profile.log solaar.py "$@" # exec python -OO -m profile -o $TMPDIR/profile.log solaar.py "$@"

View File

@ -22,7 +22,8 @@ if __name__ == '__main__':
import logging import logging
log_level = logging.root.level - 10 * args.verbose log_level = logging.root.level - 10 * args.verbose
logging.basicConfig(level=log_level if log_level > 0 else 1) log_format='%(asctime)s %(levelname)8s [%(threadName)s] %(name)s: %(message)s'
logging.basicConfig(level=log_level if log_level > 0 else 1, format=log_format)
import app import app
app.run(args) app.run(args)

View File

@ -1,8 +1,8 @@
#!/bin/sh #!/bin/sh
cd `dirname "$0"`/.. cd -P `dirname "$0"`/..
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/lib/native/`uname -m`
export PYTHONPATH=$PWD/lib export PYTHONPATH=$PWD/lib
cd - cd -

View File

@ -1,8 +1,10 @@
#!/bin/sh #!/bin/sh
cd `dirname "$0"`/../lib cd -P `dirname "$0"`/..
export LD_LIBRARY_PATH=$PWD export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/lib/native/`uname -m`
export PYTHONPATH=$PWD export PYTHONPATH=$PWD/lib
cd -
exec python -OO -m cli.ur_scanner "$@" exec python -OO -m cli.ur_scanner "$@"