Skip to content

Commit

Permalink
Merge pull request #141 from waveform80/pins
Browse files Browse the repository at this point in the history
Refactor pins implementation
  • Loading branch information
waveform80 committed Feb 8, 2016
2 parents c7ee499 + 8e0c6e2 commit 6f99d61
Show file tree
Hide file tree
Showing 14 changed files with 1,231 additions and 133 deletions.
84 changes: 84 additions & 0 deletions docs/api_pins.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
====
Pins
====

.. currentmodule:: gpiozero

As of release 1.1, the GPIO Zero library can be roughly divided into two
things: pins and the devices that are connected to them. The majority of the
documentation focuses on devices as pins are below the level that most users
are concerned with. However, some users may wish to take advantage of the
capabilities of alternative GPIO implementations or (in future) use GPIO
extender chips. This is the purpose of the pins portion of the library.

When you construct a device, you pass in a GPIO pin number. However, what the
library actually expects is a :class:`Pin` implementation. If it finds a simple
integer number instead, it uses one of the following classes to provide the
:class:`Pin` implementation (classes are listed in favoured order):

1. :class:`gpiozero.pins.rpigpio.RPiGPIOPin`

2. :class:`gpiozero.pins.rpio.RPIOPin`

3. :class:`gpiozero.pins.native.NativePin`

You can change the default pin implementation by over-writing the
``DefaultPin`` global in devices like so::

from gpiozero.pins.native import NativePin
import gpiozero.devices
# Force the default pin implementation to be NativePin
gpiozero.devices.DefaultPin = NativePin

from gpiozero import LED

# This will now use NativePin instead of RPiGPIOPin
led = LED(16)

In future, this separation should allow the library to utilize pins that are
part of IO extender chips. For example::

from gpiozero import IOExtender, LED

ext = IOExtender()
led = LED(ext.pins[0])
led.on()

.. warning::

While the devices API is now considered stable and won't change in
backwards incompatible ways, the pins API is *not* yet considered stable.
It is potentially subject to change in future versions. We welcome any
comments from testers!


Abstract Pin
============

.. autoclass:: Pin
:members:


RPiGPIOPin
==========

.. currentmodule:: gpiozero.pins.rpigpio

.. autoclass:: RPiGPIOPin


RPIOPin
=======

.. currentmodule:: gpiozero.pins.rpio

.. autoclass:: RPIOPin


NativePin
=========

.. currentmodule:: gpiozero.pins.native

.. autoclass:: NativePin

2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def __getattr__(cls, name):

sys.modules['RPi'] = Mock()
sys.modules['RPi.GPIO'] = sys.modules['RPi'].GPIO
sys.modules['RPIO'] = Mock()
sys.modules['RPIO.PWM'] = sys.modules['RPIO'].PWM
sys.modules['w1thermsensor'] = Mock()
sys.modules['spidev'] = Mock()

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ Table of Contents
api_output
api_boards
api_generic
api_pins
changelog
license
2 changes: 1 addition & 1 deletion docs/recipes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ Each button plays a different sound!

buttons = [Button(pin) for pin in sound_pins]
for button in buttons:
sound = sound_pins[button.pin]
sound = sound_pins[button.pin.number]
button.when_pressed = sound.play

pause()
Expand Down
17 changes: 17 additions & 0 deletions gpiozero/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@
division,
)

from .pins.exc import (
PinError,
PinFixedFunction,
PinInvalidFunction,
PinInvalidState,
PinInvalidPull,
PinInvalidEdges,
PinSetInput,
PinFixedPull,
PinEdgeDetectUnsupported,
PinPWMError,
PinPWMUnsupported,
PinPWMFixedValue,
)
from .pins import (
Pin,
)
from .exc import (
GPIODeviceClosed,
GPIODeviceError,
Expand Down
86 changes: 52 additions & 34 deletions gpiozero/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,45 @@
from collections import deque
from types import FunctionType

from RPi import GPIO

from .exc import GPIODeviceError, GPIODeviceClosed, InputDeviceError

_GPIO_THREADS = set()
_GPIO_PINS = set()
# Get a pin implementation to use as the default; we prefer RPi.GPIO's here
# as it supports PWM, and all Pi revisions. If no third-party libraries are
# available, however, we fall back to a pure Python implementation which
# supports platforms like PyPy
from .pins import PINS_CLEANUP
try:
from .pins.rpigpio import RPiGPIOPin
DefaultPin = RPiGPIOPin
except ImportError:
try:
from .pins.rpio import RPIOPin
DefaultPin = RPIOPin
except ImportError:
from .pins.native import NativePin
DefaultPin = NativePin


_THREADS = set()
_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()
_PINS_LOCK = RLock()

def _gpio_threads_shutdown():
while _GPIO_THREADS:
for t in _GPIO_THREADS.copy():
def _shutdown():
while _THREADS:
for t in _THREADS.copy():
t.stop()
with _GPIO_PINS_LOCK:
while _GPIO_PINS:
GPIO.remove_event_detect(_GPIO_PINS.pop())
GPIO.cleanup()
with _PINS_LOCK:
while _PINS:
_PINS.pop().close()
# Any cleanup routines registered by pins libraries must be called *after*
# cleanup of pin objects used by devices
for routine in PINS_CLEANUP:
routine()

atexit.register(_gpio_threads_shutdown)
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
atexit.register(_shutdown)


class GPIOMeta(type):
Expand Down Expand Up @@ -196,20 +212,22 @@ def __init__(self, pin=None):
# 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('No pin given')
if isinstance(pin, int):
pin = DefaultPin(pin)
with _PINS_LOCK:
if pin in _PINS:
raise GPIODeviceError(
'pin %d is already in use by another gpiozero object' % pin
'pin %r is already in use by another gpiozero object' % pin
)
_GPIO_PINS.add(pin)
_PINS.add(pin)
self._pin = pin
self._active_state = GPIO.HIGH
self._inactive_state = GPIO.LOW
self._active_state = True
self._inactive_state = False

def _read(self):
try:
return GPIO.input(self.pin) == self._active_state
return self.pin.state == self._active_state
except TypeError:
self._check_open()
raise
Expand Down Expand Up @@ -260,13 +278,12 @@ def close(self):
...
"""
super(GPIODevice, self).close()
with _GPIO_PINS_LOCK:
with _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)
if pin in _PINS:
_PINS.remove(pin)
pin.close()

@property
def closed(self):
Expand All @@ -275,9 +292,10 @@ def closed(self):
@property
def pin(self):
"""
The pin (in BCM numbering) that the device is connected to. This will
be ``None`` if the device has been closed (see the :meth:`close`
method).
The :class:`Pin` that the device is connected to. This will be ``None``
if the device has been closed (see the :meth:`close` method). When
dealing with GPIO pins, query ``pin.number`` to discover the GPIO
pin (in BCM numbering) that the device is connected to.
"""
return self._pin

Expand All @@ -293,7 +311,7 @@ def value(self):

def __repr__(self):
try:
return "<gpiozero.%s object on pin=%d, is_active=%s>" % (
return "<gpiozero.%s object on pin %r, is_active=%s>" % (
self.__class__.__name__, self.pin, self.is_active)
except GPIODeviceClosed:
return "<gpiozero.%s object closed>" % self.__class__.__name__
Expand All @@ -307,7 +325,7 @@ def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):

def start(self):
self.stopping.clear()
_GPIO_THREADS.add(self)
_THREADS.add(self)
super(GPIOThread, self).start()

def stop(self):
Expand All @@ -316,7 +334,7 @@ def stop(self):

def join(self):
super(GPIOThread, self).join()
_GPIO_THREADS.discard(self)
_THREADS.discard(self)


class GPIOQueue(GPIOThread):
Expand Down

0 comments on commit 6f99d61

Please sign in to comment.