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
|
from RPi import GPIO
|
||||||
|
|
||||||
|
from .devices import (
|
||||||
def gpiozero_shutdown():
|
_gpio_threads_shutdown,
|
||||||
GPIO.cleanup()
|
GPIODeviceError,
|
||||||
|
GPIODevice,
|
||||||
atexit.register(gpiozero_shutdown)
|
)
|
||||||
GPIO.setmode(GPIO.BCM)
|
|
||||||
GPIO.setwarnings(False)
|
|
||||||
|
|
||||||
from .input_devices import (
|
from .input_devices import (
|
||||||
InputDeviceError,
|
InputDeviceError,
|
||||||
InputDevice,
|
InputDevice,
|
||||||
@@ -26,3 +23,13 @@ from .output_devices import (
|
|||||||
Buzzer,
|
Buzzer,
|
||||||
Motor,
|
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 __future__ import division
|
||||||
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from threading import Thread, Event
|
from threading import Event
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from RPi import GPIO
|
from RPi import GPIO
|
||||||
from w1thermsensor import W1ThermSensor
|
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):
|
def __init__(self, pin=None):
|
||||||
if pin is None:
|
super(InputDevice, self).__init__(pin)
|
||||||
raise InputDeviceError('No GPIO pin number given')
|
|
||||||
|
|
||||||
self._pin = pin
|
|
||||||
self._pull = GPIO.PUD_UP
|
self._pull = GPIO.PUD_UP
|
||||||
self._edge = GPIO.FALLING
|
self._edge = GPIO.FALLING
|
||||||
self._active_state = 0
|
self._active_state = 0
|
||||||
self._inactive_state = 1
|
self._inactive_state = 1
|
||||||
GPIO.setup(pin, GPIO.IN, self._pull)
|
GPIO.setup(pin, GPIO.IN, self._pull)
|
||||||
|
|
||||||
@property
|
|
||||||
def pin(self):
|
|
||||||
return self._pin
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
return GPIO.input(self.pin) == self._active_state
|
return GPIO.input(self.pin) == self._active_state
|
||||||
@@ -34,7 +33,6 @@ class InputDevice(object):
|
|||||||
def add_callback(self, callback=None, bouncetime=1000):
|
def add_callback(self, callback=None, bouncetime=1000):
|
||||||
if callback is None:
|
if callback is None:
|
||||||
raise InputDeviceError('No callback function given')
|
raise InputDeviceError('No callback function given')
|
||||||
|
|
||||||
GPIO.add_event_detect(self.pin, self._edge, callback, bouncetime)
|
GPIO.add_event_detect(self.pin, self._edge, callback, bouncetime)
|
||||||
|
|
||||||
def remove_callback(self):
|
def remove_callback(self):
|
||||||
@@ -48,30 +46,30 @@ class Button(InputDevice):
|
|||||||
class MotionSensor(InputDevice):
|
class MotionSensor(InputDevice):
|
||||||
def __init__(self, pin=None, queue_len=20, sample_rate=10, partial=False):
|
def __init__(self, pin=None, queue_len=20, sample_rate=10, partial=False):
|
||||||
super(MotionSensor, self).__init__(pin)
|
super(MotionSensor, self).__init__(pin)
|
||||||
self._sample_rate = sample_rate
|
self.sample_rate = sample_rate
|
||||||
self._partial = partial
|
self.partial = partial
|
||||||
self._queue = deque(maxlen=queue_len)
|
self._queue = deque(maxlen=queue_len)
|
||||||
self._queue_full = Event()
|
self._queue_full = Event()
|
||||||
self._terminated = False
|
self._queue_thread = GPIOThread(target=self._fill_queue)
|
||||||
self._queue_thread = Thread(target=self._fill_queue)
|
|
||||||
self._queue_thread.start()
|
self._queue_thread.start()
|
||||||
|
|
||||||
def __del__(self):
|
@property
|
||||||
self._terminated = True
|
def queue_len(self):
|
||||||
self._queue_thread.join()
|
return self._queue.maxlen
|
||||||
|
|
||||||
def _fill_queue(self):
|
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)
|
self._queue.append(self.is_active)
|
||||||
sleep(1 / self._sample_rate)
|
|
||||||
self._queue_full.set()
|
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)
|
self._queue.append(self.is_active)
|
||||||
sleep(1 / self._sample_rate)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def motion_detected(self):
|
def motion_detected(self):
|
||||||
if not self._partial:
|
if not self.partial:
|
||||||
self._queue_full.wait()
|
self._queue_full.wait()
|
||||||
return sum(self._queue) > (len(self._queue) / 2)
|
return sum(self._queue) > (len(self._queue) / 2)
|
||||||
|
|
||||||
@@ -124,5 +122,3 @@ class TemperatureSensor(W1ThermSensor):
|
|||||||
return self.get_temperature()
|
return self.get_temperature()
|
||||||
|
|
||||||
|
|
||||||
class InputDeviceError(Exception):
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
from RPi import GPIO
|
from RPi import GPIO
|
||||||
|
|
||||||
|
from .devices import GPIODeviceError, GPIODevice, GPIOThread
|
||||||
|
|
||||||
class OutputDevice(object):
|
|
||||||
def __init__(self, pin):
|
class OutputDeviceError(GPIODeviceError):
|
||||||
self.pin = pin
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OutputDevice(GPIODevice):
|
||||||
|
def __init__(self, pin=None):
|
||||||
|
super(OutputDevice, self).__init__(pin)
|
||||||
GPIO.setup(pin, GPIO.OUT)
|
GPIO.setup(pin, GPIO.OUT)
|
||||||
|
|
||||||
def on(self):
|
def on(self):
|
||||||
@@ -14,7 +20,36 @@ class OutputDevice(object):
|
|||||||
|
|
||||||
|
|
||||||
class LED(OutputDevice):
|
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):
|
class Buzzer(OutputDevice):
|
||||||
|
|||||||
Reference in New Issue
Block a user