Add events to all input devices

Fairly major tidy up of the hierarchy as well. There's now a trivial
base class: InputDevice which simply permits reading of state.
WaitableInputDevice descends from this and introduces waitable events
and callbacks, and provides a hook for calling them but needs further
machinery to activate that hook.

DigitalInputDevice (crap name?) descends from WaitableInputDevice and
uses the standard RPi.GPIO callback mechanisms to handle events. This is
intended for use with trivial on/off devices with predictably small
bounce times.

Next is SmoothedInputDevice (crap name?) which also descends from
WaitableInputDevice. This includes a background threaded queue which
constantly monitors the state of the device and provides a running mean
of its state. This is compared to a threshold for determining active /
inactive state. This is intended for use with on/off devices that
"jitter" a lot and for which a running average is therefore appropriate
or for devices which provide an effectively analog readout (like
charging capacitor timings).

MonitorSensor and LightSensor now descend from SmoothedInputDevice, and
Button descends from DigitalInputDevice. All "concrete" classes provide
event aliases appropriate to their function (e.g. when_dark,
when_pressed, etc.)
This commit is contained in:
Dave Jones
2015-09-22 10:37:55 +01:00
parent 4d7f9eeeb3
commit b1913e5e39
3 changed files with 235 additions and 182 deletions

View File

@@ -1,4 +1,6 @@
import weakref
from threading import Thread, Event
from collections import deque
from RPi import GPIO
@@ -12,8 +14,14 @@ class GPIODevice(object):
if pin is None:
raise GPIODeviceError('No GPIO pin number given')
self._pin = pin
self._active_state = 1
self._inactive_state = 0
self._active_state = GPIO.HIGH
self._inactive_state = GPIO.LOW
def _read(self):
return GPIO.input(self.pin) == self._active_state
def _fire_events(self):
pass
@property
def pin(self):
@@ -21,7 +29,7 @@ class GPIODevice(object):
@property
def is_active(self):
return GPIO.input(self.pin) == self._active_state
return self._read()
_GPIO_THREADS = set()
@@ -47,3 +55,44 @@ class GPIOThread(Thread):
self.join()
_GPIO_THREADS.discard(self)
class GPIOQueue(GPIOThread):
def __init__(self, parent, queue_len=5, sample_wait=0.0, partial=False):
assert isinstance(parent, GPIODevice)
super(GPIOQueue, self).__init__(target=self.fill)
if queue_len < 1:
raise InputDeviceError('queue_len must be at least one')
self.queue = deque(maxlen=queue_len)
self.partial = partial
self.sample_wait = sample_wait
self.full = Event()
self.parent = weakref.proxy(parent)
@property
def value(self):
if not self.partial:
self.full.wait()
try:
return sum(self.queue) / len(self.queue)
except ZeroDivisionError:
# No data == inactive value
return 0.0
def fill(self):
try:
while (
not self.stopping.wait(self.sample_wait) and
len(self.queue) < self.queue.maxlen
):
self.queue.append(self.parent._read())
if self.partial:
self.parent._fire_events()
self.full.set()
while not self.stopping.wait(self.sample_wait):
self.queue.append(self.parent._read())
self.parent._fire_events()
except ReferenceError:
# Parent is dead; time to die!
pass