reworked the way tasks are processed by the listener

This commit is contained in:
Daniel Pavel 2012-10-27 18:40:54 +03:00
parent 62a91b56d2
commit e7bb599689
15 changed files with 412 additions and 325 deletions

View File

@ -3,7 +3,6 @@
#
from logging import getLogger as _Logger
_LOG_LEVEL = 6
from threading import Event as _Event
from struct import pack as _pack
@ -18,22 +17,113 @@ from logitech.devices.constants import (STATUS, STATUS_NAME, PROPS, NAMES)
#
#
class _FeaturesArray(object):
__slots__ = ('device', 'features', 'supported')
def __init__(self, device):
self.device = device
self.features = None
self.supported = True
def _check(self):
if not self.supported:
return False
if self.features is not None:
return True
if self.device.status >= STATUS.CONNECTED:
handle = self.device.receiver.handle
try:
index = _api.get_feature_index(handle, self.device.number, _api.FEATURE.FEATURE_SET)
except _api._FeatureNotSupported:
index = None
if index is None:
self.supported = False
else:
count = _base.request(handle, self.device.number, _pack('!B', index) + b'\x00')
if count is None:
self.supported = False
else:
count = ord(count[:1])
self.features = [None] * (1 + count)
self.features[0] = _api.FEATURE.ROOT
self.features[index] = _api.FEATURE.FEATURE_SET
return True
return False
__bool__ = __nonzero__ = _check
def __getitem__(self, index):
if not self._check():
return None
if index < 0 or index >= len(self.features):
raise IndexError
if self.features[index] is None:
fs_index = self.features.index(_api.FEATURE.FEATURE_SET)
feature = _base.request(self.device.receiver.handle, self.device.number, _pack('!BB', fs_index, 0x10), _pack('!B', index))
if feature is not None:
self.features[index] = feature[:2]
return self.features[index]
def __contains__(self, value):
if self._check():
if value in self.features:
return True
for index in range(0, len(self.features)):
f = self.features[index] or self.__getitem__(index)
assert f is not None
if f == value:
return True
if f > value:
break
return False
def index(self, value):
if self._check():
if self.features is not None and value in self.features:
return self.features.index(value)
raise ValueError("%s not in list" % repr(value))
def __iter__(self):
if self._check():
yield _api.FEATURE.ROOT
index = 1
last_index = len(self.features)
while index < last_index:
yield self.__getitem__(index)
index += 1
def __len__(self):
return len(self.features) if self._check() else 0
class DeviceInfo(object):
"""A device attached to the receiver.
"""
def __init__(self, receiver, number, status=STATUS.UNKNOWN):
def __init__(self, receiver, number, pair_code, status=STATUS.UNKNOWN):
self.LOG = _Logger("Device[%d]" % number)
self.receiver = receiver
self.number = number
self._pair_code = pair_code
self._serial = None
self._codename = None
self._name = None
self._kind = None
self._serial = None
self._firmware = None
self._features = None
self._status = status
self.props = {}
self.features = _FeaturesArray(self)
@property
def handle(self):
return self.receiver.handle
@ -71,8 +161,8 @@ class DeviceInfo(object):
def name(self):
if self._name is None:
if self._status >= STATUS.CONNECTED:
self._name = self.receiver.call_api(_api.get_device_name, self.number, self.features)
return self._name or '?'
self._name = _api.get_device_name(self.receiver.handle, self.number, self.features)
return self._name or self.codename
@property
def device_name(self):
@ -81,33 +171,42 @@ class DeviceInfo(object):
@property
def kind(self):
if self._kind is None:
if self._status >= STATUS.CONNECTED:
self._kind = self.receiver.call_api(_api.get_device_kind, self.number, self.features)
if self._status < STATUS.CONNECTED:
codename = self.codename
if codename in NAMES:
self._kind = NAMES[codename][-1]
else:
self._kind = _api.get_device_kind(self.receiver.handle, self.number, self.features)
return self._kind or '?'
@property
def serial(self):
if self._serial is None:
if self._status >= STATUS.CONNECTED:
pass
# dodgy
b = bytearray(self._pair_code)
b[0] -= 0x10
serial = _base.request(self.receiver.handle, 0xFF, b'\x83\xB5', bytes(b))
if serial:
self._serial = _base._hex(serial[1:5])
return self._serial or '?'
@property
def codename(self):
if self._codename is None:
codename = _base.request(self.receiver.handle, 0xFF, b'\x83\xB5', self._pair_code)
if codename:
self._codename = codename[2:].rstrip(b'\x00').decode('ascii')
return self._codename or '?'
@property
def firmware(self):
if self._firmware is None:
if self._status >= STATUS.CONNECTED:
self._firmware = self.receiver.call_api(_api.get_device_firmware, self.number, self.features)
self._firmware = _api.get_device_firmware(self.receiver.handle, self.number, self.features)
return self._firmware or ()
@property
def features(self):
if self._features is None:
if self._status >= STATUS.CONNECTED:
self._features = self.receiver.call_api(_api.get_device_features, self.number)
return self._features or ()
def ping(self):
return self.receiver.call_api(_api.ping, self.number)
return _api.ping(self.receiver.handle, self.number)
def process_event(self, code, data):
if code == 0x10 and data[:1] == b'\x8F':
@ -224,20 +323,20 @@ class Receiver(_listener.EventsListener):
return self.NAME
def count_devices(self):
return self.call_api(_api.count_devices)
return _api.count_devices(self._handle)
@property
def serial(self):
if self._serial is None:
if self:
self._serial, self._firmware = self.call_api(_api.get_receiver_info)
self._serial, self._firmware = _api.get_receiver_info(self._handle)
return self._serial or '?'
@property
def firmware(self):
if self._firmware is None:
if self:
self._serial, self._firmware = self.call_api(_api.get_receiver_info)
self._serial, self._firmware = _api.get_receiver_info(self._handle)
return self._firmware or ('?', '?')
@ -303,30 +402,12 @@ class Receiver(_listener.EventsListener):
self.LOG.warn("don't know how to handle device status 0x%02X: %s", state_code, event)
return None
dev = DeviceInfo(self, event.devnumber, state)
if state == STATUS.CONNECTED:
n, k = dev.name, dev.kind
else:
# we can query the receiver for the device short name
dev_id = self.request(0xFF, b'\x83\xB5', event.data[4:5])
if dev_id:
shortname = dev_id[2:].rstrip(b'\x00').decode('ascii')
if shortname in NAMES:
dev._name, dev._kind = NAMES[shortname]
else:
self.LOG.warn("could not identify inactive device %d: %s", event.devnumber, shortname)
b = bytearray(event.data[4:5])
b[0] -= 0x10
serial = self.request(0xFF, b'\x83\xB5', bytes(b))
if serial:
dev._serial = _base._hex(serial[1:5])
return dev
return DeviceInfo(self, event.devnumber, event.data[4:5], state)
def unpair_device(self, number):
if number in self.devices:
dev = self.devices[number]
reply = self.request(0xFF, b'\x80\xB2', _pack('!BB', 0x03, number))
reply = _base.request(self._handle, 0xFF, b'\x80\xB2', _pack('!BB', 0x03, number))
if reply:
self.LOG.debug("remove device %s => %s", dev, _base._hex(reply))
del self.devices[number]
@ -346,7 +427,7 @@ class Receiver(_listener.EventsListener):
:returns: An open file handle for the found receiver, or ``None``.
"""
for rawdevice in _base.list_receiver_devices():
_Logger("receiver").log(_LOG_LEVEL, "checking %s", rawdevice)
_Logger("receiver").debug("checking %s", rawdevice)
handle = _base.try_open(rawdevice.path)
if handle:
receiver = Receiver(rawdevice.path, handle)

View File

@ -32,10 +32,8 @@ def _parse_arguments():
import logging
log_level = logging.root.level - 10 * args.verbose
log_format='%(asctime)s.%(msecs)03d %(levelname)8s [%(threadName)s] %(name)s: %(message)s'
logging.basicConfig(level=log_level if log_level > 0 else 1,
format=log_format,
datefmt='%H:%M:%S')
log_format='%(asctime)s %(levelname)8s [%(threadName)s] %(name)s: %(message)s'
logging.basicConfig(level=max(log_level, 1), format=log_format)
return args
@ -83,3 +81,6 @@ if __name__ == '__main__':
w.stop()
ui.notify.uninit()
import logging
logging.shutdown()

View File

@ -19,10 +19,18 @@ def _toggle_action(name, label, function, *args):
return action
#
#
#
def wrap_action(action, prefire):
def _wrap(aw, aa):
prefire(aa)
aa.activate()
wrapper = _action(action.get_name(), action.get_label(), None)
wrapper.set_icon_name(action.get_icon_name())
wrapper.connect('activate', _wrap, action)
return wrapper
#
#
#
def _toggle_notifications(action):
if action.get_active():
@ -57,9 +65,6 @@ import pairing
def _pair_device(action):
action.set_sensitive(False)
pair_dialog = ui.pair_window.create(action, pairing.state)
action.window.present()
pair_dialog.set_transient_for(action.window)
pair_dialog.set_destroy_with_parent(action.window)
pair_dialog.set_modal(True)
pair_dialog.present()
pair = _action('add', 'Pair new device', _pair_device)
@ -69,9 +74,11 @@ def _unpair_device(action):
dev = pairing.state.device(action.devnumber)
action.devnumber = 0
if dev:
q = Gtk.MessageDialog.new(action.window,
qdialog = Gtk.MessageDialog(action.window, 0,
Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
'Unpair device <b>%s</b>?', dev.name)
if q.run() == Gtk.ResponseType.YES:
"Unpair device '%s' ?" % dev.name)
choice = qdialog.run()
qdialog.destroy()
if choice == Gtk.ResponseType.YES:
pairing.state.unpair(dev.number)
unpair = _action('remove', 'Unpair', _unpair_device)

View File

@ -2,7 +2,7 @@
#
#
from gi.repository import (Gtk, Gdk)
from gi.repository import (Gtk, Gdk, GObject)
import ui
from logitech.devices.constants import (STATUS, PROPS)
@ -22,6 +22,7 @@ def _toggle_info_button(label, widget):
action = ui.action._toggle_action('info', label, toggle, widget)
return action.create_tool_item()
def _receiver_box(name):
icon = Gtk.Image.new_from_icon_name(name, _SMALL_DEVICE_ICON_SIZE)
@ -65,7 +66,7 @@ def _receiver_box(name):
return frame
def _device_box():
def _device_box(index):
icon = Gtk.Image.new_from_icon_name('image-missing', _DEVICE_ICON_SIZE)
icon.set_name('icon')
icon.set_alignment(0.5, 0)
@ -111,7 +112,10 @@ def _device_box():
info_box.add(info_label)
toolbar.insert(_toggle_info_button('Device info', info_box), 0)
toolbar.insert(ui.action.unpair.create_tool_item(), -1)
def _set_number(action):
action.devnumber = index
unpair_action = ui.action.wrap_action(ui.action.unpair, _set_number)
toolbar.insert(unpair_action.create_tool_item(), -1)
vbox = Gtk.VBox(homogeneous=False, spacing=4)
vbox.pack_start(label, True, True, 0)
@ -131,7 +135,6 @@ def _device_box():
def toggle(window, trigger):
# print 'window toggle', window, trigger
if window.get_visible():
position = window.get_position()
window.hide()
@ -158,7 +161,7 @@ def create(title, name, max_devices, systray=False):
rbox = _receiver_box(name)
vbox.add(rbox)
for i in range(1, 1 + max_devices):
dbox = _device_box()
dbox = _device_box(i)
vbox.add(dbox)
vbox.set_visible(True)
@ -209,18 +212,12 @@ def _update_receiver_box(frame, receiver):
def _update_device_box(frame, dev):
if dev is None:
frame.set_visible(False)
frame.set_name(_PLACEHOLDER)
return
icon, label, info_label = ui.find_children(frame, 'icon', 'label', 'info-label')
if frame.get_name() != dev.name:
frame.set_name(dev.name)
icon.set_from_icon_name(ui.get_icon(dev.name, dev.kind), _DEVICE_ICON_SIZE)
label.set_markup('<b>' + dev.name + '</b>')
frame.set_visible(True)
status = ui.find_children(frame, 'status')
status_icons = status.get_children()
@ -271,16 +268,21 @@ def _update_device_box(frame, dev):
for b in toolbar.get_children()[:-1]:
b.set_sensitive(True)
frame.set_visible(True)
def update(window, receiver):
if window and window.get_child():
window.set_icon_name(ui.appicon(receiver.status))
window.set_icon_name(ui.appicon(receiver.status))
vbox = window.get_child()
controls = list(vbox.get_children())
vbox = window.get_child()
controls = list(vbox.get_children())
_update_receiver_box(controls[0], receiver)
GObject.idle_add(_update_receiver_box, controls[0], receiver)
for index in range(1, len(controls)):
dev = receiver.devices[index] if index in receiver.devices else None
_update_device_box(controls[index], dev)
for index in range(1, len(controls)):
dev = receiver.devices[index] if index in receiver.devices else None
frame = controls[index]
if dev is None:
frame.set_visible(False)
frame.set_name(_PLACEHOLDER)
else:
GObject.idle_add(_update_device_box, frame, dev)

View File

@ -65,31 +65,19 @@ class Watcher(Thread):
continue
_l.info("receiver %s ", r)
self.update_ui(r)
self.notify(r)
if r.count_devices() > 0:
# give it some time to read all devices
r.status_changed.clear()
_sleep(8, 0.4, r.status_changed.is_set)
if r.devices:
_l.info("%d device(s) found", len(r.devices))
for d in r.devices.values():
self.notify(d)
else:
# if no devices found so far, assume none at all
_l.info("no devices found")
r.status = STATUS.CONNECTED
self._receiver = r
notify_missing = True
self.update_ui(r)
self.notify(r)
if self._active:
if self._receiver:
_l.debug("waiting for status_changed")
sc = self._receiver.status_changed
sc.wait()
if not self._active:
break
sc.clear()
if sc.urgent:
_l.info("status_changed %s", sc.reason)
@ -103,6 +91,7 @@ class Watcher(Thread):
if self._receiver:
self._receiver.close()
self._receiver = _DUMMY_RECEIVER
def stop(self):
if self._active:
@ -112,3 +101,4 @@ class Watcher(Thread):
# break out of an eventual wait()
self._receiver.status_changed.reason = None
self._receiver.status_changed.set()
self.join()

View File

@ -4,4 +4,4 @@ LIB=`dirname "$0"`/../lib
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIB/native/`uname -m`
export PYTHONPATH=$LIB
exec python -OO -u -m hidapi.hidconsole "$@"
exec python -OOu -m hidapi.hidconsole "$@"

View File

@ -4,4 +4,4 @@ LIB=`dirname "$0"`/../lib
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIB/native/`uname -m`
export PYTHONPATH=$LIB
exec python -OO -m logitech.scanner "$@"
exec python -OOu -m logitech.scanner "$@"

View File

@ -9,5 +9,5 @@ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIB/native/`uname -m`
export PYTHONPATH=$APP:$LIB
export XDG_DATA_DIRS=$SHARE:$XDG_DATA_DIRS
exec python3 -OO -m solaar "$@"
#exec python -OO -m profile -o $TMPDIR/profile.log app/solaar.py "$@"
exec python -OOu -m solaar "$@"
#exec python -OOu -m profile -o $TMPDIR/profile.log app/solaar.py "$@"

View File

@ -32,20 +32,12 @@ def _module(device_name):
def default_request_status(devinfo, listener=None):
if FEATURE.BATTERY in devinfo.features:
if listener:
reply = listener.call_api(_api.get_device_battery_level, devinfo.number, features=devinfo.features)
else:
reply = _api.get_device_battery_level(devinfo.handle, devinfo.number, features=devinfo.features)
reply = _api.get_device_battery_level(devinfo.handle, devinfo.number, features=devinfo.features)
if reply:
discharge, dischargeNext, status = reply
return STATUS.CONNECTED, {PROPS.BATTERY_LEVEL: discharge, PROPS.BATTERY_STATUS: status}
if listener:
reply = listener.call_api(_api.ping, devinfo.number)
else:
reply = _api.ping(devinfo.handle, devinfo.number)
reply = _api.ping(devinfo.handle, devinfo.number)
return STATUS.CONNECTED if reply else STATUS.UNAVAILABLE

View File

@ -29,17 +29,9 @@ def _charge_status(data, hasLux=False):
def request_status(devinfo, listener=None):
def _trigger_solar_charge_events(handle, devinfo):
return _api.request(handle, devinfo.number,
feature=FEATURE.SOLAR_CHARGE, function=b'\x03', params=b'\x78\x01',
features=devinfo.features)
if listener is None:
reply = _trigger_solar_charge_events(devinfo.handle, devinfo)
elif listener:
reply = listener.call_api(_trigger_solar_charge_events, devinfo)
else:
reply = 0
reply = _api.request(devinfo.handle, devinfo.number,
feature=FEATURE.SOLAR_CHARGE, function=b'\x03', params=b'\x78\x01',
features=devinfo.features)
if reply is None:
return STATUS.UNAVAILABLE
@ -56,9 +48,3 @@ def process_event(devinfo, data, listener=None):
if data[:2] == b'\x09\x20' and data[7:11] == b'GOOD':
logging.debug("Solar key pressed")
return request_status(devinfo, listener) or _charge_status(data)
if data[:2] == b'\x05\x00':
# wireless device status
if data[2:5] == b'\x01\x01\x01':
logging.debug("Keyboard just started")
return STATUS.CONNECTED

View File

@ -39,7 +39,7 @@ def scan_devices(receiver):
for index in range(0, len(devinfo.features)):
feature = devinfo.features[index]
if feature:
print (" ~ Feature %-20s (%s) at index %d" % (FEATURE_NAME[feature], api._hex(feature), index))
print (" ~ Feature %-20s (%s) at index %02X" % (FEATURE_NAME[feature], api._hex(feature), index))
if FEATURE.BATTERY in devinfo.features:
discharge, dischargeNext, status = api.get_device_battery_level(receiver, devinfo.number, features=devinfo.features)
@ -75,11 +75,3 @@ if __name__ == '__main__':
break
else:
print ("!! Logitech Unifying Receiver not found.")
# import pyudev
# ctx = pyudev.Context()
# m = pyudev.Monitor.from_netlink(ctx)
# m.filter_by(subsystem='hid')
# for action, device in m:
# print '%s: %s' % (action, device)

View File

@ -20,12 +20,24 @@ http://julien.danjou.info/blog/2012/logitech-k750-linux-support
http://6xq.net/git/lars/lshidpp.git/plain/doc/
"""
import logging
log = logging.getLogger('LUR')
log.propagate = 0
log.setLevel(logging.DEBUG)
if logging.root.level < logging.DEBUG:
handler = logging.FileHandler('lur.log', mode='w')
handler.setFormatter(logging.root.handlers[0].formatter)
else:
handler = logging.NullHandler()
log.addHandler(handler)
del handler
del log
del logging
from .constants import *
from .exceptions import *
from .api import *
import logging
logging.addLevelName(4, 'UR_TRACE')
logging.addLevelName(5, 'UR_DEBUG')
logging.addLevelName(6, 'UR_INFO')

View File

@ -2,7 +2,6 @@
# Logitech Unifying Receiver API.
#
from logging import getLogger as _Logger
from struct import pack as _pack
from struct import unpack as _unpack
@ -19,8 +18,10 @@ from .exceptions import FeatureNotSupported as _FeatureNotSupported
_hex = _base._hex
_LOG_LEVEL = 5
_l = _Logger('lur.api')
from logging import getLogger
_log = getLogger('LUR').getChild('api')
del getLogger
#
#
@ -61,7 +62,7 @@ def get_receiver_info(handle):
def count_devices(handle):
count = _base.request(handle, 0xFF, b'\x80\x02', b'\x02')
count = _base.request(handle, 0xFF, b'\x81\x00')
return 0 if count is None else ord(count[1:2])
@ -88,22 +89,16 @@ def request(handle, devnumber, feature, function=b'\x00', params=b'', features=N
:raises FeatureNotSupported: if the device does not support the feature.
"""
feature_index = None
if feature == FEATURE.ROOT:
feature_index = b'\x00'
else:
if features is None:
features = get_device_features(handle, devnumber)
if features is None:
_l.log(_LOG_LEVEL, "(%d) no features array available", devnumber)
return None
if feature in features:
feature_index = _pack('!B', features.index(feature))
feature_index = _get_feature_index(handle, devnumber, feature, features)
if feature_index is None:
# i/o read error
return None
if feature_index is None:
_l.warn("(%d) feature <%s:%s> not supported", devnumber, _hex(feature), FEATURE_NAME[feature])
raise _FeatureNotSupported(devnumber, feature)
feature_index = _pack('!B', feature_index)
if type(function) == int:
function = _pack('!B', function)
@ -132,9 +127,11 @@ def get_device_protocol(handle, devnumber):
def find_device_by_name(handle, name):
"""Searches for an attached device by name.
This function does it the hard way, querying all possible device numbers.
:returns: an AttachedDeviceInfo tuple, or ``None``.
"""
_l.log(_LOG_LEVEL, "searching for device '%s'", name)
_log.debug("searching for device '%s'", name)
for devnumber in range(1, 1 + MAX_ATTACHED_DEVICES):
features = get_device_features(handle, devnumber)
@ -147,9 +144,11 @@ def find_device_by_name(handle, name):
def list_devices(handle):
"""List all devices attached to the UR.
This function does it the hard way, querying all possible device numbers.
:returns: a list of AttachedDeviceInfo tuples.
"""
_l.log(_LOG_LEVEL, "listing all devices")
_log.debug("listing all devices")
devices = []
@ -162,7 +161,7 @@ def list_devices(handle):
def get_device_info(handle, devnumber, name=None, features=None):
"""Gets the complete info for a device (type, name, firmware versions, features).
"""Gets the complete info for a device (type, name, features).
:returns: an AttachedDeviceInfo tuple, or ``None``.
"""
@ -174,7 +173,7 @@ def get_device_info(handle, devnumber, name=None, features=None):
d_kind = get_device_kind(handle, devnumber, features)
d_name = get_device_name(handle, devnumber, features) if name is None else name
devinfo = _AttachedDeviceInfo(handle, devnumber, d_kind, d_name, features)
_l.log(_LOG_LEVEL, "(%d) found device %s", devnumber, devinfo)
_log.debug("(%d) found device %s", devnumber, devinfo)
return devinfo
@ -183,41 +182,53 @@ def get_feature_index(handle, devnumber, feature):
:returns: An int, or ``None`` if the feature is not available.
"""
_l.log(_LOG_LEVEL, "(%d) get feature index <%s:%s>", devnumber, _hex(feature), FEATURE_NAME[feature])
_log.debug("(%d) get feature index <%s:%s>", devnumber, _hex(feature), FEATURE_NAME[feature])
if len(feature) != 2:
raise ValueError("invalid feature <%s>: it must be a two-byte string" % feature)
# FEATURE.ROOT should always be available for any attached devices
reply = _base.request(handle, devnumber, FEATURE.ROOT, feature)
if reply:
# only consider active and supported features
feature_index = ord(reply[0:1])
if feature_index:
feature_flags = ord(reply[1:2]) & 0xE0
if _l.isEnabledFor(_LOG_LEVEL):
if feature_flags:
_l.log(_LOG_LEVEL, "(%d) feature <%s:%s> has index %d: %s",
if feature_flags:
_log.debug("(%d) feature <%s:%s> has index %d: %s",
devnumber, _hex(feature), FEATURE_NAME[feature], feature_index,
','.join([FEATURE_FLAGS[k] for k in FEATURE_FLAGS if feature_flags & k]))
else:
_l.log(_LOG_LEVEL, "(%d) feature <%s:%s> has index %d", devnumber, _hex(feature), FEATURE_NAME[feature], feature_index)
else:
_log.debug("(%d) feature <%s:%s> has index %d", devnumber, _hex(feature), FEATURE_NAME[feature], feature_index)
# only consider active and supported features?
# if feature_flags:
# raise E.FeatureNotSupported(devnumber, feature)
return feature_index
_l.warn("(%d) feature <%s:%s> not supported by the device", devnumber, _hex(feature), FEATURE_NAME[feature])
_log.warn("(%d) feature <%s:%s> not supported by the device", devnumber, _hex(feature), FEATURE_NAME[feature])
raise _FeatureNotSupported(devnumber, feature)
def _get_feature_index(handle, devnumber, feature, features=None):
if features is None:
return get_feature_index(handle, devnumber, feature)
if feature in features:
return features.index(feature)
index = get_feature_index(handle, devnumber, feature)
if index is not None:
features[index] = feature
return index
def get_device_features(handle, devnumber):
"""Returns an array of feature ids.
Their position in the array is the index to be used when requesting that
feature on the device.
"""
_l.log(_LOG_LEVEL, "(%d) get device features", devnumber)
_log.debug("(%d) get device features", devnumber)
# get the index of the FEATURE_SET
# FEATURE.ROOT should always be available for all devices
@ -235,11 +246,11 @@ def get_device_features(handle, devnumber):
if not features_count:
# 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)
_l.log(_LOG_LEVEL, "(%d) no features available?!", devnumber)
_log.debug("(%d) no features available?!", devnumber)
return None
features_count = ord(features_count[:1])
_l.log(_LOG_LEVEL, "(%d) found %d features", devnumber, features_count)
_log.debug("(%d) found %d features", devnumber, features_count)
features = [None] * 0x20
for index in range(1, 1 + features_count):
@ -250,13 +261,12 @@ def get_device_features(handle, devnumber):
feature = feature[0:2].upper()
features[index] = feature
if _l.isEnabledFor(_LOG_LEVEL):
if feature_flags:
_l.log(_LOG_LEVEL, "(%d) feature <%s:%s> at index %d: %s",
if feature_flags:
_log.debug("(%d) feature <%s:%s> at index %d: %s",
devnumber, _hex(feature), FEATURE_NAME[feature], index,
','.join([FEATURE_FLAGS[k] for k in FEATURE_FLAGS if feature_flags & k]))
else:
_l.log(_LOG_LEVEL, "(%d) feature <%s:%s> at index %d", devnumber, _hex(feature), FEATURE_NAME[feature], index)
else:
_log.debug("(%d) feature <%s:%s> at index %d", devnumber, _hex(feature), FEATURE_NAME[feature], index)
features[0] = FEATURE.ROOT
while features[-1] is None:
@ -269,16 +279,17 @@ def get_device_firmware(handle, devnumber, features=None):
:returns: a list of FirmwareInfo tuples, ordered by firmware layer.
"""
def _makeFirmwareInfo(level, kind, name='', version='', extras=None):
return _FirmwareInfo(level, kind, name, version, extras)
fw_fi = _get_feature_index(handle, devnumber, FEATURE.FIRMWARE, features)
if fw_fi is None:
return None
fw_count = request(handle, devnumber, FEATURE.FIRMWARE, features=features)
fw_count = _base.request(handle, devnumber, _pack('!BB', fw_fi, 0x00))
if fw_count:
fw_count = ord(fw_count[:1])
fw = []
for index in range(0, fw_count):
fw_info = request(handle, devnumber, FEATURE.FIRMWARE, function=b'\x10', params=index, features=features)
fw_info = _base.request(handle, devnumber, _pack('!BB', fw_fi, 0x10), params=index)
if fw_info:
level = ord(fw_info[:1]) & 0x0F
if level == 0 or level == 1:
@ -290,18 +301,15 @@ def get_device_firmware(handle, devnumber, features=None):
build, = _unpack('!H', fw_info[6:8])
if build:
version += ' b%d' % build
extras = fw_info[9:].rstrip(b'\x00')
if extras:
fw_info = _makeFirmwareInfo(level, kind, name, version, extras)
else:
fw_info = _makeFirmwareInfo(level, kind, name, version)
extras = fw_info[9:].rstrip(b'\x00') or None
fw_info = _FirmwareInfo(level, kind, name, version, extras)
elif level == 2:
fw_info = _makeFirmwareInfo(2, FIRMWARE_KIND[2], version=ord(fw_info[1:2]))
fw_info = _FirmwareInfo(2, FIRMWARE_KIND[2], '', ord(fw_info[1:2]), None)
else:
fw_info = _makeFirmwareInfo(level, FIRMWARE_KIND[-1])
fw_info = _FirmwareInfo(level, FIRMWARE_KIND[-1], '', '', None)
fw.append(fw_info)
_l.log(_LOG_LEVEL, "(%d) firmware %s", devnumber, fw_info)
_log.debug("(%d) firmware %s", devnumber, fw_info)
return tuple(fw)
@ -312,10 +320,14 @@ def get_device_kind(handle, devnumber, features=None):
:returns: a string describing the device type, or ``None`` if the device is
not available or does not support the ``NAME`` feature.
"""
d_kind = request(handle, devnumber, FEATURE.NAME, function=b'\x20', features=features)
name_fi = _get_feature_index(handle, devnumber, FEATURE.NAME, features)
if name_fi is None:
return None
d_kind = _base.request(handle, devnumber, _pack('!BB', name_fi, 0x20))
if d_kind:
d_kind = ord(d_kind[:1])
_l.log(_LOG_LEVEL, "(%d) device type %d = %s", devnumber, d_kind, DEVICE_KIND[d_kind])
_log.debug("(%d) device type %d = %s", devnumber, d_kind, DEVICE_KIND[d_kind])
return DEVICE_KIND[d_kind]
@ -325,13 +337,17 @@ def get_device_name(handle, devnumber, features=None):
:returns: a string with the device name, or ``None`` if the device is not
available or does not support the ``NAME`` feature.
"""
name_length = request(handle, devnumber, FEATURE.NAME, features=features)
name_fi = _get_feature_index(handle, devnumber, FEATURE.NAME, features)
if name_fi is None:
return None
name_length = _base.request(handle, devnumber, _pack('!BB', name_fi, 0x00))
if name_length:
name_length = ord(name_length[:1])
d_name = b''
while len(d_name) < name_length:
name_fragment = request(handle, devnumber, FEATURE.NAME, function=b'\x10', params=len(d_name), features=features)
name_fragment = _base.request(handle, devnumber, _pack('!BB', name_fi, 0x10), len(d_name))
if name_fragment:
name_fragment = name_fragment[:name_length - len(d_name)]
d_name += name_fragment
@ -339,7 +355,7 @@ def get_device_name(handle, devnumber, features=None):
break
d_name = d_name.decode('ascii')
_l.log(_LOG_LEVEL, "(%d) device name %s", devnumber, d_name)
_log.debug("(%d) device name %s", devnumber, d_name)
return d_name
@ -348,22 +364,28 @@ def get_device_battery_level(handle, devnumber, features=None):
:raises FeatureNotSupported: if the device does not support this feature.
"""
battery = request(handle, devnumber, FEATURE.BATTERY, features=features)
if battery:
discharge, dischargeNext, status = _unpack('!BBB', battery[:3])
_l.log(_LOG_LEVEL, "(%d) battery %d%% charged, next level %d%% charge, status %d = %s",
bat_fi = _get_feature_index(handle, devnumber, FEATURE.BATTERY, features)
if bat_fi is not None:
battery = _base.request(handle, devnumber, _pack('!BB', bat_fi, 0))
if battery:
discharge, dischargeNext, status = _unpack('!BBB', battery[:3])
_log.debug("(%d) battery %d%% charged, next level %d%% charge, status %d = %s",
devnumber, discharge, dischargeNext, status, BATTERY_STATUS[status])
return (discharge, dischargeNext, BATTERY_STATUS[status])
return (discharge, dischargeNext, BATTERY_STATUS[status])
def get_device_keys(handle, devnumber, features=None):
count = request(handle, devnumber, FEATURE.REPROGRAMMABLE_KEYS, features=features)
rk_fi = _get_feature_index(handle, devnumber, FEATURE.REPROGRAMMABLE_KEYS, features)
if rk_fi is None:
return None
count = _base.request(handle, devnumber, _pack('!BB', rk_fi, 0))
if count:
keys = []
count = ord(count[:1])
for index in range(0, count):
keydata = request(handle, devnumber, FEATURE.REPROGRAMMABLE_KEYS, function=b'\x10', params=index, features=features)
keydata = _base.request(handle, devnumber, _pack('!BB', rk_fi, 0x10), index)
if keydata:
key, key_task, flags = _unpack('!HHB', keydata[:5])
rki = _ReprogrammableKeyInfo(index, key, KEY_NAME[key], key_task, KEY_NAME[key_task], flags)

View File

@ -3,7 +3,6 @@
# Unlikely to be used directly unless you're expanding the API.
#
from logging import getLogger as _Logger
from struct import pack as _pack
from binascii import hexlify as _hexlify
_hex = lambda d: _hexlify(d).decode('ascii').upper()
@ -12,12 +11,13 @@ from .constants import ERROR_NAME
from .exceptions import (NoReceiver as _NoReceiver,
FeatureCallError as _FeatureCallError)
from logging import getLogger
_log = getLogger('LUR').getChild('base')
del getLogger
import hidapi as _hid
_LOG_LEVEL = 4
_l = _Logger('lur.base')
#
# These values are defined by the Logitech documentation.
# Overstepping these boundaries will only produce log warnings.
@ -48,7 +48,7 @@ DEFAULT_TIMEOUT = 1000
def _logdebug_hook(reply_code, devnumber, data):
"""Default unhandled hook, logs the reply as DEBUG."""
_l.warn("UNHANDLED [%02X %02X %s %s] (%s)", reply_code, devnumber, _hex(data[:2]), _hex(data[2:]), repr(data))
_log.warn("UNHANDLED [%02X %02X %s %s] (%s)", reply_code, devnumber, _hex(data[:2]), _hex(data[2:]), repr(data))
"""The function that will be called on unhandled incoming events.
@ -78,7 +78,7 @@ def list_receiver_devices():
return _hid.enumerate(0x046d, 0xc52b, 2)
_PING_RECEIVER = b'\x10\xFF\x81\x00\x00\x00\x00'
_COUNT_DEVICES_REQUEST = b'\x10\xFF\x81\x00\x00\x00\x00'
def try_open(path):
"""Checks if the given Linux device path points to the right UR device.
@ -97,31 +97,28 @@ def try_open(path):
if receiver_handle is None:
# could be a file permissions issue (did you add the udev rules?)
# in any case, unreachable
_l.log(_LOG_LEVEL, "[%s] open failed", path)
_log.debug("[%s] open failed", path)
return None
_l.log(_LOG_LEVEL, "[%s] receiver handle %X", path, receiver_handle)
# ping on device id 0 (always an error)
_hid.write(receiver_handle, _PING_RECEIVER)
_hid.write(receiver_handle, _COUNT_DEVICES_REQUEST)
# if this is the right hidraw device, we'll receive a 'bad device' from the UR
# otherwise, the read should produce nothing
reply = _hid.read(receiver_handle, _MAX_REPLY_SIZE, DEFAULT_TIMEOUT)
if reply:
if reply[:5] == _PING_RECEIVER[:5]:
if reply[:5] == _COUNT_DEVICES_REQUEST[:5]:
# 'device 0 unreachable' is the expected reply from a valid receiver handle
_l.log(_LOG_LEVEL, "[%s] success: handle %X", path, receiver_handle)
_log.info("[%s] success: handle %X", path, receiver_handle)
return receiver_handle
# any other replies are ignored, and will assume this is the wrong Linux device
if _l.isEnabledFor(_LOG_LEVEL):
if reply == b'\x01\x00\x00\x00\x00\x00\x00\x00':
# no idea what this is, but it comes up occasionally
_l.log(_LOG_LEVEL, "[%s] %X mistery reply [%s]", path, receiver_handle, _hex(reply))
else:
_l.log(_LOG_LEVEL, "[%s] %X unknown reply [%s]", path, receiver_handle, _hex(reply))
if reply == b'\x01\x00\x00\x00\x00\x00\x00\x00':
# no idea what this is, but it comes up occasionally
_log.debug("[%s] %X mistery reply [%s]", path, receiver_handle, _hex(reply))
else:
_log.debug("[%s] %X unknown reply [%s]", path, receiver_handle, _hex(reply))
else:
_l.log(_LOG_LEVEL, "[%s] %X no reply", path, receiver_handle)
_log.debug("[%s] %X no reply", path, receiver_handle)
close(receiver_handle)
@ -132,7 +129,7 @@ def open():
:returns: An open file handle for the found receiver, or ``None``.
"""
for rawdevice in list_receiver_devices():
_l.log(_LOG_LEVEL, "checking %s", rawdevice)
_log.info("checking %s", rawdevice)
receiver = try_open(rawdevice.path)
if receiver:
@ -146,10 +143,10 @@ def close(handle):
if handle:
try:
_hid.close(handle)
_l.log(_LOG_LEVEL, "closed receiver handle %X", handle)
_log.info("closed receiver handle %X", handle)
return True
except:
_l.exception("closing receiver handle %X", handle)
_log.exception("closing receiver handle %X", handle)
return False
@ -168,15 +165,13 @@ def write(handle, devnumber, data):
been physically removed from the machine, or the kernel driver has been
unloaded. The handle will be closed automatically.
"""
if _l.isEnabledFor(_LOG_LEVEL):
_l.log(_LOG_LEVEL, "(%d) <= w[10 %02X %s %s]", devnumber, devnumber, _hex(data[:2]), _hex(data[2:]))
assert _MIN_CALL_SIZE == 7
assert _MAX_CALL_SIZE == 20
# the data is padded to either 5 or 18 bytes
wdata = _pack('!BB18s' if len(data) > 5 else '!BB5s', 0x10, devnumber, data)
_log.debug("(%d) <= w[10 %02X %s %s]", devnumber, devnumber, _hex(wdata[2:4]), _hex(wdata[4:]))
if not _hid.write(handle, wdata):
_l.warn("(%d) write failed, assuming receiver %X no longer available", devnumber, handle)
_log.warn("(%d) write failed, assuming receiver %X no longer available", devnumber, handle)
close(handle)
raise _NoReceiver
@ -197,26 +192,33 @@ def read(handle, timeout=DEFAULT_TIMEOUT):
been physically removed from the machine, or the kernel driver has been
unloaded. The handle will be closed automatically.
"""
data = _hid.read(handle, _MAX_REPLY_SIZE * 2, timeout)
data = _hid.read(handle, _MAX_REPLY_SIZE, timeout)
if data is None:
_l.warn("(-) read failed, assuming receiver %X no longer available", handle)
_log.warn("(-) read failed, assuming receiver %X no longer available", handle)
close(handle)
raise _NoReceiver
if data:
if len(data) < _MIN_REPLY_SIZE:
_l.warn("(%d) => r[%s] read packet too short: %d bytes", ord(data[1:2]), _hex(data), len(data))
_log.warn("(%d) => r[%s] read packet too short: %d bytes", ord(data[1:2]), _hex(data), len(data))
data += b'\x00' * (_MIN_REPLY_SIZE - len(data))
if len(data) > _MAX_REPLY_SIZE:
_l.warn("(%d) => r[%s] read packet too long: %d bytes", ord(data[1:2]), _hex(data), len(data))
_log.warn("(%d) => r[%s] read packet too long: %d bytes", ord(data[1:2]), _hex(data), len(data))
code = ord(data[:1])
devnumber = ord(data[1:2])
if _l.isEnabledFor(_LOG_LEVEL):
_l.log(_LOG_LEVEL, "(%d) => r[%02X %02X %s %s]", devnumber, code, devnumber, _hex(data[2:4]), _hex(data[4:]))
_log.debug("(%d) => r[%02X %02X %s %s]", devnumber, code, devnumber, _hex(data[2:4]), _hex(data[4:]))
return code, devnumber, data[2:]
# _l.log(_LOG_LEVEL, "(-) => r[]", handle)
# _l.log(_LOG_LEVEL, "(-) => r[]")
_MAX_READ_TIMES = 3
request_context = None
from collections import namedtuple
_DEFAULT_REQUEST_CONTEXT_CLASS = namedtuple('_DEFAULT_REQUEST_CONTEXT_CLASS', ['write', 'read', 'unhandled_hook'])
_DEFAULT_REQUEST_CONTEXT = _DEFAULT_REQUEST_CONTEXT_CLASS(write=write, read=read, unhandled_hook=unhandled_hook)
del namedtuple
def request(handle, devnumber, feature_index_function, params=b'', features=None):
"""Makes a feature call to a device and waits for a matching reply.
@ -234,18 +236,27 @@ def request(handle, devnumber, feature_index_function, params=b'', features=None
available.
:raisees FeatureCallError: if the feature call replied with an error.
"""
if _l.isEnabledFor(_LOG_LEVEL):
_l.log(_LOG_LEVEL, "(%d) request {%s} params [%s]", devnumber, _hex(feature_index_function), _hex(params))
if type(params) == int:
params = _pack('!B', params)
_log.debug("(%d) request {%s} params [%s]", devnumber, _hex(feature_index_function), _hex(params))
if len(feature_index_function) != 2:
raise ValueError('invalid feature_index_function {%s}: it must be a two-byte string' % _hex(feature_index_function))
retries = 5
if request_context is None or handle != request_context.handle:
context = _DEFAULT_REQUEST_CONTEXT
_unhandled = unhandled_hook
else:
context = request_context
_unhandled = getattr(context, 'unhandled_hook')
write(handle, devnumber, feature_index_function + params)
while retries > 0:
divisor = (6 - retries)
reply = read(handle, int(DEFAULT_TIMEOUT * (divisor + 1) / 2 / divisor))
retries -= 1
context.write(handle, devnumber, feature_index_function + params)
read_times = _MAX_READ_TIMES
while read_times > 0:
divisor = (1 + _MAX_READ_TIMES - read_times)
reply = context.read(handle, int(DEFAULT_TIMEOUT * (divisor + 1) / 2 / divisor))
read_times -= 1
if not reply:
# keep waiting...
@ -258,24 +269,24 @@ def request(handle, devnumber, feature_index_function, params=b'', features=None
# _l.log(_LOG_LEVEL, "(%d) request got reply for unexpected device %d: [%s]", devnumber, reply_devnumber, _hex(reply_data))
# worst case scenario, this is a reply for a concurrent request
# on this receiver
if unhandled_hook:
unhandled_hook(reply_code, reply_devnumber, reply_data)
if _unhandled:
_unhandled(reply_code, reply_devnumber, reply_data)
continue
if reply_code == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == feature_index_function:
# device not present
_l.log(_LOG_LEVEL, "(%d) request ping failed on {%s} call: [%s]", devnumber, _hex(feature_index_function), _hex(reply_data))
_log.debug("(%d) request ping failed on {%s} call: [%s]", devnumber, _hex(feature_index_function), _hex(reply_data))
return None
if reply_code == 0x10 and reply_data[:1] == b'\x8F':
# device not present
_l.log(_LOG_LEVEL, "(%d) request ping failed: [%s]", devnumber, _hex(reply_data))
_log.debug("(%d) request ping failed: [%s]", devnumber, _hex(reply_data))
return None
if reply_code == 0x11 and reply_data[0] == b'\xFF' and reply_data[1:3] == feature_index_function:
# the feature call returned with an error
error_code = ord(reply_data[3])
_l.warn("(%d) request feature call error %d = %s: %s", devnumber, error_code, ERROR_NAME[error_code], _hex(reply_data))
_log.warn("(%d) request feature call error %d = %s: %s", devnumber, error_code, ERROR_NAME[error_code], _hex(reply_data))
feature_index = ord(feature_index_function[:1])
feature_function = feature_index_function[1:2]
feature = None if features is None else features[feature_index] if feature_index < len(features) else None
@ -283,14 +294,14 @@ def request(handle, devnumber, feature_index_function, params=b'', features=None
if reply_code == 0x11 and reply_data[:2] == feature_index_function:
# a matching reply
# _l.log(_LOG_LEVEL, "(%d) matched reply with feature-index-function [%s]", devnumber, _hex(reply_data[2:]))
# _log.debug("(%d) matched reply with feature-index-function [%s]", devnumber, _hex(reply_data[2:]))
return reply_data[2:]
if reply_code == 0x10 and devnumber == 0xFF and reply_data[:2] == feature_index_function:
# direct calls to the receiver (device 0xFF) may also return successfully with reply code 0x10
# _l.log(_LOG_LEVEL, "(%d) matched reply with feature-index-function [%s]", devnumber, _hex(reply_data[2:]))
# _log.debug("(%d) matched reply with feature-index-function [%s]", devnumber, _hex(reply_data[2:]))
return reply_data[2:]
# _l.log(_LOG_LEVEL, "(%d) unmatched reply {%s} (expected {%s})", devnumber, _hex(reply_data[:2]), _hex(feature_index_function))
if unhandled_hook:
unhandled_hook(reply_code, reply_devnumber, reply_data)
# _log.debug("(%d) unmatched reply {%s} (expected {%s})", devnumber, _hex(reply_data[:2]), _hex(feature_index_function))
if _unhandled:
_unhandled(reply_code, reply_devnumber, reply_data)

View File

@ -2,8 +2,7 @@
#
#
from logging import getLogger as _Logger
from threading import (Thread, Event, Lock)
from threading import Thread as _Thread
# from time import sleep as _sleep
from . import base as _base
@ -12,108 +11,107 @@ from .common import Packet as _Packet
# for both Python 2 and 3
try:
from Queue import Queue
from Queue import Queue as _Queue
except ImportError:
from queue import Queue
from queue import Queue as _Queue
_LOG_LEVEL = 6
_l = _Logger('lur.listener')
from logging import getLogger
_log = getLogger('LUR').getChild('listener')
del getLogger
_READ_EVENT_TIMEOUT = int(_base.DEFAULT_TIMEOUT / 4) # ms
_READ_EVENT_TIMEOUT = int(_base.DEFAULT_TIMEOUT) # ms
def _event_dispatch(listener, callback):
# _l.log(_LOG_LEVEL, "starting dispatch")
while listener._active: # or not listener._events.empty():
event = listener._events.get()
# _l.log(_LOG_LEVEL, "delivering event %s", event)
try:
event = listener._events.get(True, _READ_EVENT_TIMEOUT * 10)
except:
continue
_log.debug("delivering event %s", event)
try:
callback(event)
except:
_l.exception("callback for %s", event)
# _l.log(_LOG_LEVEL, "stopped dispatch")
_log.exception("callback for %s", event)
class EventsListener(Thread):
class EventsListener(_Thread):
"""Listener thread for events from the Unifying Receiver.
Incoming packets will be passed to the callback function in sequence, by a
separate thread.
While this listener is running, you must use the call_api() method to make
regular UR API calls; otherwise the expected API replies are most likely to
be captured by the listener and delivered to the callback.
"""
def __init__(self, receiver_handle, events_callback):
super(EventsListener, self).__init__(group='Unifying Receiver', name='%s-%X' % (self.__class__.__name__, receiver_handle))
super(EventsListener, self).__init__(group='Unifying Receiver', name=self.__class__.__name__)
self.daemon = True
self._active = False
self._handle = receiver_handle
self._task = None
self._task_processing = Lock()
self._task_reply = None
self._task_done = Event()
self._tasks = _Queue(1)
self._backup_unhandled_hook = _base.unhandled_hook
_base.unhandled_hook = self.unhandled_hook
self._events = Queue(32)
_base.unhandled_hook = self._unhandled
self._dispatcher = Thread(group='Unifying Receiver',
name='%s-%X-dispatch' % (self.__class__.__name__, receiver_handle),
self._events = _Queue(32)
self._dispatcher = _Thread(group='Unifying Receiver',
name=self.__class__.__name__ + '-dispatch',
target=_event_dispatch, args=(self, events_callback))
self._dispatcher.daemon = True
def run(self):
self._active = True
_l.log(_LOG_LEVEL, "started")
_log.debug("started")
_base.request_context = self
_base.unhandled_hook = self._backup_unhandled_hook
del self._backup_unhandled_hook
self._dispatcher.start()
while self._active:
event = None
try:
# _log.debug("read next event")
event = _base.read(self._handle, _READ_EVENT_TIMEOUT)
except _NoReceiver:
self._handle = 0
_l.warn("receiver disconnected")
_log.warn("receiver disconnected")
self._events.put(_Packet(0xFF, 0xFF, None))
self._active = False
break
if event:
event = _Packet(*event)
_l.log(_LOG_LEVEL, "queueing event %s", event)
self._events.put(event)
if event is not None:
matched = False
task = None if self._tasks.empty() else self._tasks.queue[0]
if task and task[0] and task[-1] is None:
devnumber, data = task[1:3]
if event[1] == devnumber:
# _log.debug("matching %s to %d, %s", event, devnumber, repr(data))
if event[0] == 0x11 or (event[0] == 0x10 and devnumber == 0xFF):
matched = (event[2][:2] == data[:2]) or (event[2][:1] == b'\xFF' and event[2][1:3] == data[:2])
elif event[0] == 0x10:
if event[2][:1] == b'\x8F' and event[2][1:3] == data[:2]:
matched = True
if self._task:
(api_function, args, kwargs), self._task = self._task, None
# _l.log(_LOG_LEVEL, "calling '%s.%s' with %s, %s", api_function.__module__, api_function.__name__, args, kwargs)
try:
self._task_reply = api_function.__call__(self._handle, *args, **kwargs)
except _NoReceiver as nr:
self._handle = 0
_l.warn("receiver disconnected")
self._events.put(_Packet(0xFF, 0xFF, None))
self._task_reply = nr
self._active = False
break
except Exception as e:
# _l.exception("task %s.%s", api_function.__module__, api_function.__name__)
self._task_reply = e
finally:
self._task_done.set()
if matched:
# _log.debug("request reply %s", event)
task[-1] = event
self._tasks.task_done()
else:
event = _Packet(*event)
_log.info("queueing event %s", event)
self._events.put(event)
_base.request_context = None
_base.close(self._handle)
_log.debug("stopped")
self._handle = 0
def stop(self):
"""Tells the listener to stop as soon as possible."""
if self._active:
_l.log(_LOG_LEVEL, "stopping")
_log.debug("stopping")
self._active = False
# wait for the receiver handle to be closed
self.join()
@ -122,34 +120,27 @@ class EventsListener(Thread):
def handle(self):
return self._handle
def request(self, device, feature_function_index, params=b''):
return self.call_api(_base.request, device, feature_function_index, params)
def write(self, handle, devnumber, data):
assert handle == self._handle
# _log.debug("write %02X %s", devnumber, _base._hex(data))
task = [False, devnumber, data, None]
self._tasks.put(task)
_base.write(self._handle, devnumber, data)
task[0] = True
_log.debug("task queued %s", task)
def call_api(self, api_function, *args, **kwargs):
"""Make an UR API request through this listener's receiver.
def read(self, handle, timeout=_base.DEFAULT_TIMEOUT):
assert handle == self._handle
# _log.debug("read %d", timeout)
assert not self._tasks.empty()
self._tasks.join()
task = self._tasks.get(False)
_log.debug("task ready %s", task)
return task[-1]
The api_function must have a receiver handle as a first agument, all
other passed args and kwargs will follow.
"""
# _l.log(_LOG_LEVEL, "%s request '%s.%s' with %s, %s", self, api_function.__module__, api_function.__name__, args, kwargs)
# if not self._active:
# return None
with self._task_processing:
self._task_done.clear()
self._task = (api_function, args, kwargs)
self._task_done.wait()
reply, self._task_reply = self._task_reply, None
# _l.log(_LOG_LEVEL, "%s request '%s.%s' => %s", self, api_function.__module__, api_function.__name__, repr(reply))
if isinstance(reply, Exception):
raise reply
return reply
def _unhandled(self, reply_code, devnumber, data):
def unhandled_hook(self, reply_code, devnumber, data):
event = _Packet(reply_code, devnumber, data)
# _l.log(_LOG_LEVEL, "queueing unhandled event %s", event)
_log.info("queueing unhandled event %s", event)
self._events.put(event)
def __nonzero__(self):