mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 17:50:37 +00:00
Merge pull request #39 from waveform80/explicit-cleanup
Explicit cleanup
This commit is contained in:
@@ -1,11 +1,6 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import atexit
|
||||
|
||||
from RPi import GPIO
|
||||
|
||||
from .devices import (
|
||||
_gpio_threads_shutdown,
|
||||
GPIODeviceError,
|
||||
GPIODevice,
|
||||
)
|
||||
@@ -35,11 +30,3 @@ from .boards import (
|
||||
TrafficHat,
|
||||
)
|
||||
|
||||
|
||||
def gpiozero_shutdown():
|
||||
_gpio_threads_shutdown()
|
||||
GPIO.cleanup()
|
||||
|
||||
atexit.register(gpiozero_shutdown)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
|
||||
@@ -1,31 +1,96 @@
|
||||
import atexit
|
||||
import weakref
|
||||
from threading import Thread, Event
|
||||
from threading import Thread, Event, RLock
|
||||
from collections import deque
|
||||
|
||||
from RPi import GPIO
|
||||
|
||||
|
||||
_GPIO_THREADS = set()
|
||||
_GPIO_PINS = set()
|
||||
# Due to interactions between RPi.GPIO cleanup and the GPIODevice.close()
|
||||
# method the same thread may attempt to acquire this lock, leading to deadlock
|
||||
# unless the lock is re-entrant
|
||||
_GPIO_PINS_LOCK = RLock()
|
||||
|
||||
def _gpio_threads_shutdown():
|
||||
while _GPIO_THREADS:
|
||||
for t in _GPIO_THREADS.copy():
|
||||
t.stop()
|
||||
with _GPIO_PINS_LOCK:
|
||||
while _GPIO_PINS:
|
||||
GPIO.remove_event_detect(_GPIO_PINS.pop())
|
||||
GPIO.cleanup()
|
||||
|
||||
atexit.register(_gpio_threads_shutdown)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
|
||||
|
||||
class GPIODeviceError(Exception):
|
||||
pass
|
||||
|
||||
class GPIODeviceClosed(GPIODeviceError):
|
||||
pass
|
||||
|
||||
|
||||
class GPIODevice(object):
|
||||
"""
|
||||
Generic GPIO Device.
|
||||
"""
|
||||
def __init__(self, pin=None):
|
||||
# self._pin must be set before any possible exceptions can be raised
|
||||
# because it's accessed in __del__. However, it mustn't be given the
|
||||
# value of pin until we've verified that it isn't already allocated
|
||||
self._pin = None
|
||||
if pin is None:
|
||||
raise GPIODeviceError('No GPIO pin number given')
|
||||
with _GPIO_PINS_LOCK:
|
||||
if pin in _GPIO_PINS:
|
||||
raise GPIODeviceError(
|
||||
'pin %d is already in use by another gpiozero object' % pin)
|
||||
_GPIO_PINS.add(pin)
|
||||
self._pin = pin
|
||||
self._active_state = GPIO.HIGH
|
||||
self._inactive_state = GPIO.LOW
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def _read(self):
|
||||
try:
|
||||
return GPIO.input(self.pin) == self._active_state
|
||||
except TypeError:
|
||||
self._check_open()
|
||||
raise
|
||||
|
||||
def _fire_events(self):
|
||||
pass
|
||||
|
||||
def _check_open(self):
|
||||
if self.closed:
|
||||
raise GPIODeviceClosed(
|
||||
'%s is closed or uninitialized' % self.__class__.__name__)
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._pin is None
|
||||
|
||||
def close(self):
|
||||
with _GPIO_PINS_LOCK:
|
||||
pin = self._pin
|
||||
self._pin = None
|
||||
if pin in _GPIO_PINS:
|
||||
_GPIO_PINS.remove(pin)
|
||||
GPIO.remove_event_detect(pin)
|
||||
GPIO.cleanup(pin)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||
self.close()
|
||||
|
||||
@property
|
||||
def pin(self):
|
||||
return self._pin
|
||||
@@ -35,17 +100,11 @@ class GPIODevice(object):
|
||||
return self._read()
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
return "<gpiozero.%s object on pin=%d, is_active=%s>" % (
|
||||
self.__class__.__name__, self.pin, self.is_active)
|
||||
|
||||
|
||||
_GPIO_THREADS = set()
|
||||
|
||||
|
||||
def _gpio_threads_shutdown():
|
||||
while _GPIO_THREADS:
|
||||
for t in _GPIO_THREADS.copy():
|
||||
t.stop()
|
||||
except GPIODeviceClosed:
|
||||
return "<gpiozero.%s object closed>" % self.__class__.__name__
|
||||
|
||||
|
||||
class GPIOThread(Thread):
|
||||
|
||||
@@ -9,7 +9,7 @@ from threading import Event
|
||||
from RPi import GPIO
|
||||
from w1thermsensor import W1ThermSensor
|
||||
|
||||
from .devices import GPIODeviceError, GPIODevice, GPIOQueue
|
||||
from .devices import GPIODeviceError, GPIODeviceClosed, GPIODevice, GPIOQueue
|
||||
|
||||
|
||||
def _alias(key):
|
||||
@@ -33,8 +33,12 @@ class InputDevice(GPIODevice):
|
||||
'GPIO pins 2 and 3 are fitted with physical pull up '
|
||||
'resistors; you cannot initialize them with pull_up=False'
|
||||
)
|
||||
super(InputDevice, self).__init__(pin)
|
||||
# _pull_up should be assigned first as __repr__ relies upon it to
|
||||
# support the case where __repr__ is called during debugging of an
|
||||
# instance that has failed to initialize (due to an exception in the
|
||||
# super-class __init__)
|
||||
self._pull_up = pull_up
|
||||
super(InputDevice, self).__init__(pin)
|
||||
self._active_edge = GPIO.FALLING if pull_up else GPIO.RISING
|
||||
self._inactive_edge = GPIO.RISING if pull_up else GPIO.FALLING
|
||||
self._active_state = GPIO.LOW if pull_up else GPIO.HIGH
|
||||
@@ -59,8 +63,11 @@ class InputDevice(GPIODevice):
|
||||
return self._pull_up
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
return "<gpiozero.%s object on pin=%d, pull_up=%s, is_active=%s>" % (
|
||||
self.__class__.__name__, self.pin, self.pull_up, self.is_active)
|
||||
except:
|
||||
return super(InputDevice, self).__repr__()
|
||||
|
||||
|
||||
class WaitableInputDevice(InputDevice):
|
||||
@@ -174,9 +181,6 @@ class DigitalInputDevice(WaitableInputDevice):
|
||||
# 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()
|
||||
|
||||
@@ -188,11 +192,31 @@ class SmoothedInputDevice(WaitableInputDevice):
|
||||
def __init__(
|
||||
self, pin=None, pull_up=False, threshold=0.5,
|
||||
queue_len=5, sample_wait=0.0, partial=False):
|
||||
self._queue = None
|
||||
super(SmoothedInputDevice, self).__init__(pin, pull_up)
|
||||
self._queue = GPIOQueue(self, queue_len, sample_wait, partial)
|
||||
self.threshold = float(threshold)
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self._queue.stop()
|
||||
except AttributeError:
|
||||
if self._queue is not None:
|
||||
raise
|
||||
except RuntimeError:
|
||||
# Cannot join thread before it starts; we don't care about this
|
||||
# because we're trying to close the thread anyway
|
||||
pass
|
||||
else:
|
||||
self._queue = None
|
||||
super(SmoothedInputDevice, self).close()
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
except GPIODeviceClosed:
|
||||
return super(SmoothedInputDevice, self).__repr__()
|
||||
else:
|
||||
if self.partial or self._queue.full.wait(0):
|
||||
return super(SmoothedInputDevice, self).__repr__()
|
||||
else:
|
||||
@@ -201,14 +225,17 @@ class SmoothedInputDevice(WaitableInputDevice):
|
||||
|
||||
@property
|
||||
def queue_len(self):
|
||||
self._check_open()
|
||||
return self._queue.queue.maxlen
|
||||
|
||||
@property
|
||||
def partial(self):
|
||||
self._check_open()
|
||||
return self._queue.partial
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
self._check_open()
|
||||
return self._queue.value
|
||||
|
||||
def _get_threshold(self):
|
||||
@@ -284,9 +311,6 @@ class LightSensor(SmoothedInputDevice):
|
||||
)
|
||||
self._queue.start()
|
||||
|
||||
def __del__(self):
|
||||
GPIO.remove_event_detect(self.pin)
|
||||
|
||||
@property
|
||||
def charge_time_limit(self):
|
||||
return self._charge_time_limit
|
||||
|
||||
Reference in New Issue
Block a user