mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 17:50:37 +00:00
Merge pull request #20 from waveform80/when-active-when-inactive
Nicely named event attributes
This commit is contained in:
@@ -72,7 +72,6 @@ Alternatively:
|
||||
|
||||
```python
|
||||
from gpiozero import LED
|
||||
from time import sleep
|
||||
|
||||
red = LED(2)
|
||||
red.blink(1, 1)
|
||||
@@ -118,7 +117,7 @@ from gpiozero import Button
|
||||
|
||||
button = Button(4)
|
||||
|
||||
button.wait_for_input()
|
||||
button.wait_for_press()
|
||||
print("Button was pressed")
|
||||
```
|
||||
|
||||
@@ -127,44 +126,54 @@ Run a function every time the button is pressed:
|
||||
```python
|
||||
from gpiozero import Button
|
||||
|
||||
def hello(pin):
|
||||
print("Button was pressed")
|
||||
def warning():
|
||||
print("Don't push the button!")
|
||||
|
||||
button = Button(4)
|
||||
|
||||
button.add_callback(hello)
|
||||
button.when_pressed = warning
|
||||
```
|
||||
|
||||
### Motion Sensor
|
||||
|
||||
Detect motion:
|
||||
Detect motion and light an LED when it's detected:
|
||||
|
||||
```python
|
||||
from gpiozero import MotionSensor
|
||||
from gpiozero import MotionSensor, LED
|
||||
|
||||
pir = MotionSensor(5)
|
||||
led = LED(16)
|
||||
|
||||
while True:
|
||||
if pir.motion_detected:
|
||||
print("Motion detected")
|
||||
pir.when_motion = led.on
|
||||
pir.when_no_motion = led.off
|
||||
```
|
||||
|
||||
### Light Sensor
|
||||
|
||||
Retrieve light sensor value:
|
||||
Wait for light and dark:
|
||||
|
||||
```python
|
||||
from time import sleep
|
||||
from gpiozero import LightSensor
|
||||
|
||||
sensor = LightSensor(18)
|
||||
|
||||
while True:
|
||||
sensor.wait_for_light()
|
||||
print("It's light! :)")
|
||||
sensor.wait_for_dark()
|
||||
print("It's dark :(")
|
||||
```
|
||||
|
||||
Run a function when the light changes:
|
||||
|
||||
```python
|
||||
from gpiozero import LightSensor, LED
|
||||
|
||||
sensor = LightSensor(18)
|
||||
led = LED(16)
|
||||
|
||||
sensor.when_dark = led.on
|
||||
sensor.when_light = led.off
|
||||
|
||||
while True:
|
||||
sleep(1)
|
||||
```
|
||||
|
||||
### Temperature Sensor
|
||||
@@ -196,3 +205,4 @@ sleep(5)
|
||||
left_motor.off()
|
||||
right_motor.off()
|
||||
```
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -48,3 +56,44 @@ class GPIOThread(Thread):
|
||||
self.stopping.set()
|
||||
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
|
||||
|
||||
|
||||
@@ -2,12 +2,17 @@ from __future__ import division
|
||||
|
||||
from time import sleep, time
|
||||
from threading import Event
|
||||
from collections import deque
|
||||
|
||||
from RPi import GPIO
|
||||
from w1thermsensor import W1ThermSensor
|
||||
|
||||
from .devices import GPIODeviceError, GPIODevice, GPIOThread
|
||||
from .devices import GPIODeviceError, GPIODevice, GPIOQueue
|
||||
|
||||
|
||||
def _alias(key):
|
||||
return property(
|
||||
lambda self: getattr(self, key),
|
||||
lambda self, val: setattr(self, key, val))
|
||||
|
||||
|
||||
class InputDeviceError(GPIODeviceError):
|
||||
@@ -15,194 +20,175 @@ class InputDeviceError(GPIODeviceError):
|
||||
|
||||
|
||||
class InputDevice(GPIODevice):
|
||||
def __init__(self, pin=None, pull_up=True):
|
||||
def __init__(self, pin=None, pull_up=False):
|
||||
super(InputDevice, self).__init__(pin)
|
||||
self._pull_up = pull_up
|
||||
self._edge = (GPIO.RISING, GPIO.FALLING)[pull_up]
|
||||
self._active_edge = (GPIO.RISING, GPIO.FALLING)[pull_up]
|
||||
self._inactive_edge = (GPIO.FALLING, GPIO.RISING)[pull_up]
|
||||
if pull_up:
|
||||
self._active_state = 0
|
||||
self._inactive_state = 1
|
||||
pull = GPIO.PUD_UP if pull_up else GPIO.PUD_DOWN
|
||||
GPIO.setup(pin, GPIO.IN, pull)
|
||||
self._active_state = GPIO.LOW
|
||||
self._inactive_state = GPIO.HIGH
|
||||
GPIO.setup(pin, GPIO.IN, (GPIO.PUD_DOWN, GPIO.PUD_UP)[pull_up])
|
||||
|
||||
@property
|
||||
def pull_up(self):
|
||||
return self._pull_up
|
||||
|
||||
|
||||
class Button(InputDevice):
|
||||
pass
|
||||
class WaitableInputDevice(InputDevice):
|
||||
def __init__(self, pin=None, pull_up=False):
|
||||
super(WaitableInputDevice, self).__init__(pin, pull_up)
|
||||
self._active_event = Event()
|
||||
self._inactive_event = Event()
|
||||
self._when_activated = None
|
||||
self._when_deactivated = None
|
||||
self._last_state = None
|
||||
|
||||
def wait_for_active(self, timeout=None):
|
||||
return self._active_event.wait(timeout)
|
||||
|
||||
def wait_for_inactive(self, timeout=None):
|
||||
return self._inactive_event.wait(timeout)
|
||||
|
||||
def _get_when_activated(self):
|
||||
return self._when_activated
|
||||
def _set_when_activated(self, value):
|
||||
if not callable(value) and value is not None:
|
||||
raise InputDeviceError('value must be None or a function')
|
||||
self._when_activated = value
|
||||
when_activated = property(_get_when_activated, _set_when_activated)
|
||||
|
||||
def _get_when_deactivated(self):
|
||||
return self._when_deactivated
|
||||
def _set_when_deactivated(self, value):
|
||||
if not callable(value) and value is not None:
|
||||
raise InputDeviceError('value must be None or a function')
|
||||
self._when_deactivated = value
|
||||
when_deactivated = property(_get_when_deactivated, _set_when_deactivated)
|
||||
|
||||
def _fire_events(self):
|
||||
old_state = self._last_state
|
||||
new_state = self._last_state = self.is_active
|
||||
if old_state is None:
|
||||
# Initial "indeterminate" state; set events but don't fire
|
||||
# callbacks as there's not necessarily an edge
|
||||
if new_state:
|
||||
self._active_event.set()
|
||||
else:
|
||||
self._inactive_event.set()
|
||||
else:
|
||||
if not old_state and new_state:
|
||||
self._inactive_event.clear()
|
||||
self._active_event.set()
|
||||
if self.when_activated:
|
||||
self.when_activated()
|
||||
elif old_state and not new_state:
|
||||
self._active_event.clear()
|
||||
self._inactive_event.set()
|
||||
if self.when_deactivated:
|
||||
self.when_deactivated()
|
||||
|
||||
|
||||
class MotionSensor(InputDevice):
|
||||
class DigitalInputDevice(WaitableInputDevice):
|
||||
def __init__(self, pin=None, pull_up=False, bouncetime=None):
|
||||
super(DigitalInputDevice, self).__init__(pin, pull_up)
|
||||
# Yes, that's really the default bouncetime in RPi.GPIO...
|
||||
GPIO.add_event_detect(
|
||||
self.pin, GPIO.BOTH, callback=self._fire_events,
|
||||
bouncetime=-666 if bouncetime is None else bouncetime)
|
||||
# Call _fire_events once to set initial state of events
|
||||
super(DigitalInputDevice, self)._fire_events()
|
||||
|
||||
def __del__(self):
|
||||
GPIO.remove_event_detect(self.pin)
|
||||
|
||||
def _fire_events(self, channel):
|
||||
super(DigitalInputDevice, self)._fire_events()
|
||||
|
||||
|
||||
class SmoothedInputDevice(WaitableInputDevice):
|
||||
def __init__(
|
||||
self, pin=None, pull_up=False, threshold=0.5,
|
||||
queue_len=5, sample_wait=0.0, partial=False):
|
||||
super(SmoothedInputDevice, self).__init__(pin, pull_up)
|
||||
self._queue = GPIOQueue(self, queue_len, sample_wait, partial)
|
||||
self.threshold = float(threshold)
|
||||
|
||||
@property
|
||||
def queue_len(self):
|
||||
return self._queue.queue.maxlen
|
||||
|
||||
@property
|
||||
def partial(self):
|
||||
return self._queue.partial
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._queue.value
|
||||
|
||||
def _get_threshold(self):
|
||||
return self._threshold
|
||||
|
||||
def _set_threshold(self, value):
|
||||
if not (0.0 < value < 1.0):
|
||||
raise InputDeviceError('threshold must be between zero and one exclusive')
|
||||
self._threshold = float(value)
|
||||
threshold = property(_get_threshold, _set_threshold)
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return self.value > self.threshold
|
||||
|
||||
|
||||
class Button(DigitalInputDevice):
|
||||
def __init__(self, pin=None, pull_up=True, bouncetime=None):
|
||||
super(Button, self).__init__(pin, pull_up, bouncetime)
|
||||
|
||||
when_pressed = _alias('when_activated')
|
||||
when_released = _alias('when_deactivated')
|
||||
|
||||
wait_for_press = _alias('wait_for_active')
|
||||
wait_for_release = _alias('wait_for_inactive')
|
||||
|
||||
|
||||
class MotionSensor(SmoothedInputDevice):
|
||||
def __init__(
|
||||
self, pin=None, queue_len=5, sample_rate=10, threshold=0.5,
|
||||
partial=False):
|
||||
super(MotionSensor, self).__init__(pin, pull_up=False)
|
||||
if queue_len < 1:
|
||||
raise InputDeviceError('queue_len must be at least one')
|
||||
self.sample_rate = sample_rate
|
||||
self.threshold = threshold
|
||||
self.partial = partial
|
||||
self._queue = deque(maxlen=queue_len)
|
||||
self._queue_full = Event()
|
||||
self._queue_thread = GPIOThread(target=self._fill_queue)
|
||||
self._queue_thread.start()
|
||||
super(MotionSensor, self).__init__(
|
||||
pin, pull_up=False, threshold=threshold,
|
||||
queue_len=queue_len, sample_wait=1 / sample_rate, partial=partial)
|
||||
self._queue.start()
|
||||
|
||||
@property
|
||||
def queue_len(self):
|
||||
return self._queue.maxlen
|
||||
motion_detected = _alias('is_active')
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
if not self.partial:
|
||||
self._queue_full.wait()
|
||||
try:
|
||||
return sum(self._queue) / len(self._queue)
|
||||
except ZeroDivisionError:
|
||||
# No data == no motion
|
||||
return 0.0
|
||||
when_motion = _alias('when_activated')
|
||||
when_no_motion = _alias('when_deactivated')
|
||||
|
||||
@property
|
||||
def motion_detected(self):
|
||||
return self.value > self.threshold
|
||||
|
||||
def _get_sample_rate(self):
|
||||
return self._sample_rate
|
||||
|
||||
def _set_sample_rate(self, value):
|
||||
if value <= 0:
|
||||
raise InputDeviceError('sample_rate must be greater than zero')
|
||||
self._sample_rate = value
|
||||
|
||||
sample_rate = property(_get_sample_rate, _set_sample_rate)
|
||||
|
||||
def _get_threshold(self):
|
||||
return self._threshold
|
||||
|
||||
def _set_threshold(self, value):
|
||||
if value < 0:
|
||||
raise InputDeviceError('threshold must be zero or more')
|
||||
self._threshold = value
|
||||
|
||||
threshold = property(_get_threshold, _set_threshold)
|
||||
|
||||
def _fill_queue(self):
|
||||
while (
|
||||
not self._queue_thread.stopping.wait(1 / self.sample_rate) and
|
||||
len(self._queue) < self._queue.maxlen
|
||||
):
|
||||
self._queue.append(self.is_active)
|
||||
self._queue_full.set()
|
||||
while not self._queue_thread.stopping.wait(1 / self.sample_rate):
|
||||
self._queue.append(self.is_active)
|
||||
wait_for_motion = _alias('wait_for_active')
|
||||
wait_for_no_motion = _alias('wait_for_inactive')
|
||||
|
||||
|
||||
class LightSensor(InputDevice):
|
||||
class LightSensor(SmoothedInputDevice):
|
||||
def __init__(
|
||||
self, pin=None, queue_len=5, darkness_time=0.01,
|
||||
self, pin=None, queue_len=5, charge_time_limit=0.01,
|
||||
threshold=0.1, partial=False):
|
||||
super(LightSensor, self).__init__(pin, pull_up=False)
|
||||
if queue_len < 1:
|
||||
raise InputDeviceError('queue_len must be at least one')
|
||||
self.darkness_time = darkness_time
|
||||
self.threshold = threshold
|
||||
self.partial = partial
|
||||
super(LightSensor, self).__init__(
|
||||
pin, pull_up=False, threshold=threshold,
|
||||
queue_len=queue_len, sample_wait=0.0, partial=partial)
|
||||
self._charge_time_limit = charge_time_limit
|
||||
self._charged = Event()
|
||||
GPIO.add_event_detect(
|
||||
self.pin, GPIO.RISING, lambda channel: self._charged.set()
|
||||
)
|
||||
self._queue = deque(maxlen=queue_len)
|
||||
self._queue_full = Event()
|
||||
self._queue_thread = GPIOThread(target=self._fill_queue)
|
||||
self._last_state = None
|
||||
self._when_light = None
|
||||
self._when_dark = None
|
||||
self._when_light_event = Event()
|
||||
self._when_dark_event = Event()
|
||||
self._queue_thread.start()
|
||||
GPIO.add_event_detect(self.pin, GPIO.RISING, lambda channel: self._charged.set())
|
||||
self._queue.start()
|
||||
|
||||
def __del__(self):
|
||||
GPIO.remove_event_detect(self.pin)
|
||||
|
||||
@property
|
||||
def queue_len(self):
|
||||
return self._queue.maxlen
|
||||
def charge_time_limit(self):
|
||||
return self._charge_time_limit
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
if not self.partial:
|
||||
self._queue_full.wait()
|
||||
try:
|
||||
return (
|
||||
1.0 - (sum(self._queue) / len(self._queue)) /
|
||||
self.darkness_time
|
||||
)
|
||||
except ZeroDivisionError:
|
||||
# No data == no light
|
||||
return 0.0
|
||||
|
||||
@property
|
||||
def light_detected(self):
|
||||
return self.value > self.threshold
|
||||
|
||||
def _get_when_light(self):
|
||||
return self._when_light
|
||||
|
||||
def _set_when_light(self, value):
|
||||
if not callable(value) and value is not None:
|
||||
raise InputDeviceError('when_light must be None or a function')
|
||||
self._when_light = value
|
||||
|
||||
when_light = property(_get_when_light, _set_when_light)
|
||||
|
||||
def _get_when_dark(self):
|
||||
return self._when_dark
|
||||
|
||||
def _set_when_dark(self, value):
|
||||
if not callable(value) and value is not None:
|
||||
raise InputDeviceError('when_dark must be None or a function')
|
||||
self._when_dark = value
|
||||
|
||||
def wait_for_light(self, timeout=None):
|
||||
self._when_light_event.wait(timeout)
|
||||
|
||||
def wait_for_dark(self, timeout=None):
|
||||
self._when_dark_event.wait(timeout)
|
||||
|
||||
def _get_darkness_time(self):
|
||||
return self._darkness_time
|
||||
|
||||
def _set_darkness_time(self, value):
|
||||
if value <= 0.0:
|
||||
raise InputDeviceError('darkness_time must be greater than zero')
|
||||
self._darkness_time = value
|
||||
# XXX Empty the queue and restart the thread
|
||||
|
||||
darkness_time = property(_get_darkness_time, _set_darkness_time)
|
||||
|
||||
def _get_threshold(self):
|
||||
return self._threshold
|
||||
|
||||
def _set_threshold(self, value):
|
||||
if value < 0:
|
||||
raise InputDeviceError('threshold must be zero or more')
|
||||
self._threshold = value
|
||||
|
||||
threshold = property(_get_threshold, _set_threshold)
|
||||
|
||||
def _fill_queue(self):
|
||||
try:
|
||||
while (not self._queue_thread.stopping.is_set() and
|
||||
len(self._queue) < self._queue.maxlen):
|
||||
self._queue.append(self._time_charging())
|
||||
if self.partial:
|
||||
self._fire_events()
|
||||
self._queue_full.set()
|
||||
while not self._queue_thread.stopping.is_set():
|
||||
self._queue.append(self._time_charging())
|
||||
self._fire_events()
|
||||
finally:
|
||||
GPIO.remove_event_detect(self.pin)
|
||||
|
||||
def _time_charging(self):
|
||||
def _read(self):
|
||||
# Drain charge from the capacitor
|
||||
GPIO.setup(self.pin, GPIO.OUT)
|
||||
GPIO.output(self.pin, GPIO.LOW)
|
||||
@@ -211,25 +197,21 @@ class LightSensor(InputDevice):
|
||||
start = time()
|
||||
self._charged.clear()
|
||||
GPIO.setup(self.pin, GPIO.IN)
|
||||
self._charged.wait(self.darkness_time)
|
||||
return min(self.darkness_time, time() - start)
|
||||
self._charged.wait(self.charge_time_limit)
|
||||
return 1.0 - min(self.charge_time_limit, time() - start) / self.charge_time_limit
|
||||
|
||||
light_detected = _alias('is_active')
|
||||
|
||||
when_light = _alias('when_activated')
|
||||
when_dark = _alias('when_deactivated')
|
||||
|
||||
wait_for_light = _alias('wait_for_active')
|
||||
wait_for_dark = _alias('wait_for_inactive')
|
||||
|
||||
def _fire_events(self):
|
||||
last_state = self._last_state
|
||||
self._last_state = state = self.light_detected
|
||||
if not last_state and state:
|
||||
self._when_dark_event.clear()
|
||||
self._when_light_event.set()
|
||||
if self.when_light:
|
||||
self.when_light()
|
||||
elif last_state and not state:
|
||||
self._when_light_event.clear()
|
||||
self._when_dark_event.set()
|
||||
if self.when_dark:
|
||||
self.when_dark()
|
||||
|
||||
|
||||
class TemperatureSensor(W1ThermSensor):
|
||||
@property
|
||||
def value(self):
|
||||
return self.get_temperature()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user