raise NoReceiver if the receiver is removed during read

This commit is contained in:
Daniel Pavel 2012-09-27 15:52:42 +03:00
parent 0dc02af78d
commit 692ea58937
3 changed files with 56 additions and 23 deletions

View File

@ -187,7 +187,7 @@ def list_devices(handle):
def get_device_info(handle, device, device_name=None, features_array=None): def get_device_info(handle, device, device_name=None, features_array=None):
"""Gets the complete info for a device. """Gets the complete info for a device (type, name, firmwares, and features_array).
:returns: an AttachedDeviceInfo tuple, or ``None``. :returns: an AttachedDeviceInfo tuple, or ``None``.
""" """

View File

@ -141,7 +141,7 @@ def write(handle, device, data):
:raises NoReceiver: if the receiver is no longer available, i.e. has :raises NoReceiver: if the receiver is no longer available, i.e. has
been physically removed from the machine, or the kernel driver has been been physically removed from the machine, or the kernel driver has been
unloaded. unloaded. The handle will be closed automatically.
""" """
wdata = b'\x10' + chr(device) + data + b'\x00' * (5 - len(data)) wdata = b'\x10' + chr(device) + data + b'\x00' * (5 - len(data))
_l.log(_LOG_LEVEL, "(%d,%d) <= w[%s]", handle, device, wdata.encode('hex')) _l.log(_LOG_LEVEL, "(%d,%d) <= w[%s]", handle, device, wdata.encode('hex'))
@ -166,8 +166,17 @@ def read(handle, timeout=DEFAULT_TIMEOUT):
(reply_code, device, message data). The reply code should be ``0x11`` for a (reply_code, device, message data). The reply code should be ``0x11`` for a
successful feature call, or ``0x10`` to indicate some error, e.g. the device successful feature call, or ``0x10`` to indicate some error, e.g. the device
is no longer available. is no longer available.
:raises NoReceiver: if the receiver is no longer available, i.e. has
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 * 2, timeout)
if data is None:
_l.warn("(%d,*) read failed, assuming receiver no longer available", handle)
close(handle)
raise NoReceiver
if data: if data:
_l.log(_LOG_LEVEL, "(%d,*) => r[%s]", handle, data.encode('hex')) _l.log(_LOG_LEVEL, "(%d,*) => r[%s]", handle, data.encode('hex'))
if len(data) < _MIN_REPLY_SIZE: if len(data) < _MIN_REPLY_SIZE:
@ -175,7 +184,6 @@ def read(handle, timeout=DEFAULT_TIMEOUT):
if len(data) > _MAX_REPLY_SIZE: if len(data) > _MAX_REPLY_SIZE:
_l.warn("(%d,*) => r[%s] read packet too long: %d bytes", handle, data.encode('hex'), len(data)) _l.warn("(%d,*) => r[%s] read packet too long: %d bytes", handle, data.encode('hex'), len(data))
return ord(data[0]), ord(data[1]), data[2:] return ord(data[0]), ord(data[1]), data[2:]
else:
_l.log(_LOG_LEVEL, "(%d,*) => r[]", handle) _l.log(_LOG_LEVEL, "(%d,*) => r[]", handle)

View File

@ -4,7 +4,7 @@
import logging import logging
import threading import threading
import time from time import sleep
from . import base from . import base
from .exceptions import * from .exceptions import *
@ -13,16 +13,29 @@ from .exceptions import *
_LOG_LEVEL = 6 _LOG_LEVEL = 6
_l = logging.getLogger('logitech.unifying_receiver.listener') _l = logging.getLogger('logitech.unifying_receiver.listener')
_EVENT_TIMEOUT = 100
_IDLE_SLEEP = 1000.0 / 1000.0 _READ_EVENT_TIMEOUT = 90 # ms
_IDLE_SLEEP = 900 # ms
class EventsListener(threading.Thread): class EventsListener(threading.Thread):
def __init__(self, receiver, callback): """Listener thread for events from the Unifying Receiver.
Incoming events (code, device, data) will be delivered to the callback
function. The callback is called in the listener thread, so it should return
as fast as possible.
While this listener is running, you should use the request() method to make
regular UR API calls, otherwise the replies will be captured by the listener
and delivered as events to the callback. As an exception, you can make UR
API calls in the events callback.
"""
def __init__(self, receiver, events_callback):
super(EventsListener, self).__init__(name='Unifying_Receiver_Listener_' + str(receiver)) super(EventsListener, self).__init__(name='Unifying_Receiver_Listener_' + str(receiver))
self.daemon = True
self.receiver = receiver self.receiver = receiver
self.callback = callback self.callback = events_callback
self.task = None self.task = None
self.task_processing = threading.Lock() self.task_processing = threading.Lock()
@ -30,38 +43,50 @@ class EventsListener(threading.Thread):
self.task_reply = None self.task_reply = None
self.task_done = threading.Event() self.task_done = threading.Event()
self.active = False
def run(self): def run(self):
_l.log(_LOG_LEVEL, "(%d) starting", self.receiver) _l.log(_LOG_LEVEL, "(%d) starting", self.receiver)
self.active = True self.active = True
while self.active: while self.active:
try:
# _l.log(_LOG_LEVEL, "(%d) reading next event", self.receiver) # _l.log(_LOG_LEVEL, "(%d) reading next event", self.receiver)
event = base.read(self.receiver, _EVENT_TIMEOUT) event = base.read(self.receiver, _READ_EVENT_TIMEOUT)
except NoReceiver:
_l.warn("(%d) receiver disconnected", self.receiver)
self.active = False
break
if self.active:
if event: if event:
_l.log(_LOG_LEVEL, "(%d) got event %s", self.receiver, event) _l.log(_LOG_LEVEL, "(%d) got event %s", self.receiver, event)
self.callback.__call__(*event) self.callback.__call__(*event)
elif self.task is None: elif self.task is None:
# _l.log(_LOG_LEVEL, "(%d) idle sleep", self.receiver) # _l.log(_LOG_LEVEL, "(%d) idle sleep", self.receiver)
time.sleep(_IDLE_SLEEP) 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()
def stop(self): def stop(self):
"""Tells the listener to stop as soon as possible."""
_l.log(_LOG_LEVEL, "(%d) stopping", self.receiver) _l.log(_LOG_LEVEL, "(%d) stopping", self.receiver)
self.active = False self.active = False
self.join()
def request(self, api_function, *args, **kwargs): def request(self, api_function, *args, **kwargs):
"""Make an UR API request.
The api_function will get the receiver handle as a first agument, all
other args and kwargs will follow.
"""
# _l.log(_LOG_LEVEL, "(%d) request '%s' with %s, %s", self.receiver, api_function.__name__, args, kwargs) # _l.log(_LOG_LEVEL, "(%d) request '%s' with %s, %s", self.receiver, 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)
self.task_done.wait() self.task_done.wait()
reply = self.task_reply reply = self.task_reply
self.task = self.task_reply = None self.task = self.task_reply = None
self.task_processing.release() self.task_processing.release()
# _l.log(_LOG_LEVEL, "(%d) request '%s' => [%s]", self.receiver, api_function.__name__, reply.encode('hex')) # _l.log(_LOG_LEVEL, "(%d) request '%s' => [%s]", self.receiver, api_function.__name__, reply.encode('hex'))
if isinstance(reply, Exception): if isinstance(reply, Exception):
raise reply raise reply