mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 17:50:37 +00:00
Shut down GPIO threads nicely
The motion sensor queue doesn't shut down properly at script end at the moment and prevents the interpreter shutting down. This is because it's a non-daemon thread so `__del__` never gets run and so on. This is a bit of a major PR - I can split it up if you want. Firstly it makes a common base class called `GPIODevice` for both `InputDevice` and `OutputDevice`. This just takes care of the read-only pin stuff. Next it makes a `GPIOThread` class that ensures its a daemon thread, and which also ensures proper cleanup on shutdown. Finally, it fixes `MotionSensor` to use the new `GPIOThread` class (tested this time! Works nicely) and adds the `blink` method to the `LED` class (which also works nicely this time).
This commit is contained in:
@@ -4,14 +4,11 @@ import atexit
|
||||
|
||||
from RPi import GPIO
|
||||
|
||||
|
||||
def gpiozero_shutdown():
|
||||
GPIO.cleanup()
|
||||
|
||||
atexit.register(gpiozero_shutdown)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
|
||||
from .devices import (
|
||||
_gpio_threads_shutdown,
|
||||
GPIODeviceError,
|
||||
GPIODevice,
|
||||
)
|
||||
from .input_devices import (
|
||||
InputDeviceError,
|
||||
InputDevice,
|
||||
@@ -26,3 +23,13 @@ from .output_devices import (
|
||||
Buzzer,
|
||||
Motor,
|
||||
)
|
||||
|
||||
|
||||
def gpiozero_shutdown():
|
||||
_gpio_threads_shutdown()
|
||||
GPIO.cleanup()
|
||||
|
||||
atexit.register(gpiozero_shutdown)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
|
||||
|
||||
43
gpiozero/devices.py
Normal file
43
gpiozero/devices.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from threading import Thread, Event
|
||||
|
||||
from RPi import GPIO
|
||||
|
||||
|
||||
class GPIODeviceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GPIODevice(object):
|
||||
def __init__(self, pin=None):
|
||||
if pin is None:
|
||||
raise GPIODeviceError('No GPIO pin number given')
|
||||
self._pin = pin
|
||||
|
||||
@property
|
||||
def pin(self):
|
||||
return self._pin
|
||||
|
||||
|
||||
_GPIO_THREADS = set()
|
||||
def _gpio_threads_shutdown():
|
||||
while _GPIO_THREADS:
|
||||
for t in _GPIO_THREADS.copy():
|
||||
t.stop()
|
||||
|
||||
|
||||
class GPIOThread(Thread):
|
||||
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
|
||||
super(GPIOThread, self).__init__(group, target, name, args, kwargs)
|
||||
self.stopping = Event()
|
||||
self.daemon = True
|
||||
|
||||
def start(self):
|
||||
self.stopping.clear()
|
||||
_GPIO_THREADS.add(self)
|
||||
super(GPIOThread, self).start()
|
||||
|
||||
def stop(self):
|
||||
self.stopping.set()
|
||||
self.join()
|
||||
_GPIO_THREADS.discard(self)
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
from __future__ import division
|
||||
|
||||
from time import sleep
|
||||
from threading import Thread, Event
|
||||
from threading import Event
|
||||
from collections import deque
|
||||
|
||||
from RPi import GPIO
|
||||
from w1thermsensor import W1ThermSensor
|
||||
|
||||
from .devices import GPIODeviceError, GPIODevice, GPIOThread
|
||||
|
||||
class InputDevice(object):
|
||||
|
||||
class InputDeviceError(GPIODeviceError):
|
||||
pass
|
||||
|
||||
|
||||
class InputDevice(GPIODevice):
|
||||
def __init__(self, pin=None):
|
||||
if pin is None:
|
||||
raise InputDeviceError('No GPIO pin number given')
|
||||
|
||||
self._pin = pin
|
||||
super(InputDevice, self).__init__(pin)
|
||||
self._pull = GPIO.PUD_UP
|
||||
self._edge = GPIO.FALLING
|
||||
self._active_state = 0
|
||||
self._inactive_state = 1
|
||||
GPIO.setup(pin, GPIO.IN, self._pull)
|
||||
|
||||
@property
|
||||
def pin(self):
|
||||
return self._pin
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return GPIO.input(self.pin) == self._active_state
|
||||
@@ -34,7 +33,6 @@ class InputDevice(object):
|
||||
def add_callback(self, callback=None, bouncetime=1000):
|
||||
if callback is None:
|
||||
raise InputDeviceError('No callback function given')
|
||||
|
||||
GPIO.add_event_detect(self.pin, self._edge, callback, bouncetime)
|
||||
|
||||
def remove_callback(self):
|
||||
@@ -48,30 +46,30 @@ class Button(InputDevice):
|
||||
class MotionSensor(InputDevice):
|
||||
def __init__(self, pin=None, queue_len=20, sample_rate=10, partial=False):
|
||||
super(MotionSensor, self).__init__(pin)
|
||||
self._sample_rate = sample_rate
|
||||
self._partial = partial
|
||||
self.sample_rate = sample_rate
|
||||
self.partial = partial
|
||||
self._queue = deque(maxlen=queue_len)
|
||||
self._queue_full = Event()
|
||||
self._terminated = False
|
||||
self._queue_thread = Thread(target=self._fill_queue)
|
||||
self._queue_thread = GPIOThread(target=self._fill_queue)
|
||||
self._queue_thread.start()
|
||||
|
||||
def __del__(self):
|
||||
self._terminated = True
|
||||
self._queue_thread.join()
|
||||
@property
|
||||
def queue_len(self):
|
||||
return self._queue.maxlen
|
||||
|
||||
def _fill_queue(self):
|
||||
while not self._terminated and len(self._queue) < self._queue.maxlen:
|
||||
while (
|
||||
not self._queue_thread.stopping.wait(1 / self.sample_rate) and
|
||||
len(self._queue) < self._queue.maxlen
|
||||
):
|
||||
self._queue.append(self.is_active)
|
||||
sleep(1 / self._sample_rate)
|
||||
self._queue_full.set()
|
||||
while not self._terminated:
|
||||
while not self._queue_thread.stopping.wait(1 / self.sample_rate):
|
||||
self._queue.append(self.is_active)
|
||||
sleep(1 / self._sample_rate)
|
||||
|
||||
@property
|
||||
def motion_detected(self):
|
||||
if not self._partial:
|
||||
if not self.partial:
|
||||
self._queue_full.wait()
|
||||
return sum(self._queue) > (len(self._queue) / 2)
|
||||
|
||||
@@ -124,5 +122,3 @@ class TemperatureSensor(W1ThermSensor):
|
||||
return self.get_temperature()
|
||||
|
||||
|
||||
class InputDeviceError(Exception):
|
||||
pass
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
from RPi import GPIO
|
||||
|
||||
from .devices import GPIODeviceError, GPIODevice, GPIOThread
|
||||
|
||||
class OutputDevice(object):
|
||||
def __init__(self, pin):
|
||||
self.pin = pin
|
||||
|
||||
class OutputDeviceError(GPIODeviceError):
|
||||
pass
|
||||
|
||||
|
||||
class OutputDevice(GPIODevice):
|
||||
def __init__(self, pin=None):
|
||||
super(OutputDevice, self).__init__(pin)
|
||||
GPIO.setup(pin, GPIO.OUT)
|
||||
|
||||
def on(self):
|
||||
@@ -14,7 +20,36 @@ class OutputDevice(object):
|
||||
|
||||
|
||||
class LED(OutputDevice):
|
||||
pass
|
||||
def __init__(self, pin=None):
|
||||
super(LED, self).__init__(pin)
|
||||
self._blink_thread = None
|
||||
|
||||
def blink(self, on_time, off_time):
|
||||
self._stop_blink()
|
||||
self._blink_thread = GPIOThread(target=self._blink_led, args=(on_time, off_time))
|
||||
self._blink_thread.start()
|
||||
|
||||
def _stop_blink(self):
|
||||
if self._blink_thread:
|
||||
self._blink_thread.stop()
|
||||
self._blink_thread = None
|
||||
|
||||
def _blink_led(self, on_time, off_time):
|
||||
while True:
|
||||
super(LED, self).on()
|
||||
if self._blink_thread.stopping.wait(on_time):
|
||||
break
|
||||
super(LED, self).off()
|
||||
if self._blink_thread.stopping.wait(off_time):
|
||||
break
|
||||
|
||||
def on(self):
|
||||
self._stop_blink()
|
||||
super(LED, self).on()
|
||||
|
||||
def off(self):
|
||||
self._stop_blink()
|
||||
super(LED, self).off()
|
||||
|
||||
|
||||
class Buzzer(OutputDevice):
|
||||
|
||||
Reference in New Issue
Block a user