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):
"""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``.
"""

View File

@ -141,7 +141,7 @@ def write(handle, device, data):
: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.
unloaded. The handle will be closed automatically.
"""
wdata = b'\x10' + chr(device) + data + b'\x00' * (5 - len(data))
_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
successful feature call, or ``0x10`` to indicate some error, e.g. the device
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)
if data is None:
_l.warn("(%d,*) read failed, assuming receiver no longer available", handle)
close(handle)
raise NoReceiver
if data:
_l.log(_LOG_LEVEL, "(%d,*) => r[%s]", handle, data.encode('hex'))
if len(data) < _MIN_REPLY_SIZE:
@ -175,8 +184,7 @@ def read(handle, timeout=DEFAULT_TIMEOUT):
if len(data) > _MAX_REPLY_SIZE:
_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:]
else:
_l.log(_LOG_LEVEL, "(%d,*) => r[]", handle)
_l.log(_LOG_LEVEL, "(%d,*) => r[]", handle)
def request(handle, device, feature_index_function, params=b'', features_array=None):

View File

@ -4,7 +4,7 @@
import logging
import threading
import time
from time import sleep
from . import base
from .exceptions import *
@ -13,16 +13,29 @@ from .exceptions import *
_LOG_LEVEL = 6
_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):
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))
self.daemon = True
self.receiver = receiver
self.callback = callback
self.callback = events_callback
self.task = None
self.task_processing = threading.Lock()
@ -30,38 +43,50 @@ class EventsListener(threading.Thread):
self.task_reply = None
self.task_done = threading.Event()
self.active = False
def run(self):
_l.log(_LOG_LEVEL, "(%d) starting", self.receiver)
self.active = True
while self.active:
# _l.log(_LOG_LEVEL, "(%d) reading next event", self.receiver)
event = base.read(self.receiver, _EVENT_TIMEOUT)
if event:
_l.log(_LOG_LEVEL, "(%d) got event %s", self.receiver, event)
self.callback.__call__(*event)
elif self.task is None:
# _l.log(_LOG_LEVEL, "(%d) idle sleep", self.receiver)
time.sleep(_IDLE_SLEEP)
else:
self.task_reply = self._make_request(*self.task)
self.task_done.set()
try:
# _l.log(_LOG_LEVEL, "(%d) reading next event", self.receiver)
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:
_l.log(_LOG_LEVEL, "(%d) got event %s", self.receiver, event)
self.callback.__call__(*event)
elif self.task is None:
# _l.log(_LOG_LEVEL, "(%d) idle sleep", self.receiver)
sleep(_IDLE_SLEEP / 1000.0)
else:
self.task_reply = self._make_request(*self.task)
self.task_done.set()
def stop(self):
"""Tells the listener to stop as soon as possible."""
_l.log(_LOG_LEVEL, "(%d) stopping", self.receiver)
self.active = False
self.join()
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)
self.task_processing.acquire()
self.task_done.clear()
self.task = (api_function, args, kwargs)
self.task_done.wait()
reply = self.task_reply
self.task = self.task_reply = None
self.task_processing.release()
# _l.log(_LOG_LEVEL, "(%d) request '%s' => [%s]", self.receiver, api_function.__name__, reply.encode('hex'))
if isinstance(reply, Exception):
raise reply