Skip to content

Commit 6f99d61

Browse files
committed
Merge pull request gpiozero#141 from waveform80/pins
Refactor pins implementation
2 parents c7ee499 + 8e0c6e2 commit 6f99d61

File tree

14 files changed

+1231
-133
lines changed

14 files changed

+1231
-133
lines changed

docs/api_pins.rst

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
====
2+
Pins
3+
====
4+
5+
.. currentmodule:: gpiozero
6+
7+
As of release 1.1, the GPIO Zero library can be roughly divided into two
8+
things: pins and the devices that are connected to them. The majority of the
9+
documentation focuses on devices as pins are below the level that most users
10+
are concerned with. However, some users may wish to take advantage of the
11+
capabilities of alternative GPIO implementations or (in future) use GPIO
12+
extender chips. This is the purpose of the pins portion of the library.
13+
14+
When you construct a device, you pass in a GPIO pin number. However, what the
15+
library actually expects is a :class:`Pin` implementation. If it finds a simple
16+
integer number instead, it uses one of the following classes to provide the
17+
:class:`Pin` implementation (classes are listed in favoured order):
18+
19+
1. :class:`gpiozero.pins.rpigpio.RPiGPIOPin`
20+
21+
2. :class:`gpiozero.pins.rpio.RPIOPin`
22+
23+
3. :class:`gpiozero.pins.native.NativePin`
24+
25+
You can change the default pin implementation by over-writing the
26+
``DefaultPin`` global in devices like so::
27+
28+
from gpiozero.pins.native import NativePin
29+
import gpiozero.devices
30+
# Force the default pin implementation to be NativePin
31+
gpiozero.devices.DefaultPin = NativePin
32+
33+
from gpiozero import LED
34+
35+
# This will now use NativePin instead of RPiGPIOPin
36+
led = LED(16)
37+
38+
In future, this separation should allow the library to utilize pins that are
39+
part of IO extender chips. For example::
40+
41+
from gpiozero import IOExtender, LED
42+
43+
ext = IOExtender()
44+
led = LED(ext.pins[0])
45+
led.on()
46+
47+
.. warning::
48+
49+
While the devices API is now considered stable and won't change in
50+
backwards incompatible ways, the pins API is *not* yet considered stable.
51+
It is potentially subject to change in future versions. We welcome any
52+
comments from testers!
53+
54+
55+
Abstract Pin
56+
============
57+
58+
.. autoclass:: Pin
59+
:members:
60+
61+
62+
RPiGPIOPin
63+
==========
64+
65+
.. currentmodule:: gpiozero.pins.rpigpio
66+
67+
.. autoclass:: RPiGPIOPin
68+
69+
70+
RPIOPin
71+
=======
72+
73+
.. currentmodule:: gpiozero.pins.rpio
74+
75+
.. autoclass:: RPIOPin
76+
77+
78+
NativePin
79+
=========
80+
81+
.. currentmodule:: gpiozero.pins.native
82+
83+
.. autoclass:: NativePin
84+

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ def __getattr__(cls, name):
3737

3838
sys.modules['RPi'] = Mock()
3939
sys.modules['RPi.GPIO'] = sys.modules['RPi'].GPIO
40+
sys.modules['RPIO'] = Mock()
41+
sys.modules['RPIO.PWM'] = sys.modules['RPIO'].PWM
4042
sys.modules['w1thermsensor'] = Mock()
4143
sys.modules['spidev'] = Mock()
4244

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ Table of Contents
1212
api_output
1313
api_boards
1414
api_generic
15+
api_pins
1516
changelog
1617
license

docs/recipes.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ Each button plays a different sound!
281281

282282
buttons = [Button(pin) for pin in sound_pins]
283283
for button in buttons:
284-
sound = sound_pins[button.pin]
284+
sound = sound_pins[button.pin.number]
285285
button.when_pressed = sound.play
286286

287287
pause()

gpiozero/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55
division,
66
)
77

8+
from .pins.exc import (
9+
PinError,
10+
PinFixedFunction,
11+
PinInvalidFunction,
12+
PinInvalidState,
13+
PinInvalidPull,
14+
PinInvalidEdges,
15+
PinSetInput,
16+
PinFixedPull,
17+
PinEdgeDetectUnsupported,
18+
PinPWMError,
19+
PinPWMUnsupported,
20+
PinPWMFixedValue,
21+
)
22+
from .pins import (
23+
Pin,
24+
)
825
from .exc import (
926
GPIODeviceClosed,
1027
GPIODeviceError,

gpiozero/devices.py

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,45 @@
1313
from collections import deque
1414
from types import FunctionType
1515

16-
from RPi import GPIO
17-
1816
from .exc import GPIODeviceError, GPIODeviceClosed, InputDeviceError
1917

20-
_GPIO_THREADS = set()
21-
_GPIO_PINS = set()
18+
# Get a pin implementation to use as the default; we prefer RPi.GPIO's here
19+
# as it supports PWM, and all Pi revisions. If no third-party libraries are
20+
# available, however, we fall back to a pure Python implementation which
21+
# supports platforms like PyPy
22+
from .pins import PINS_CLEANUP
23+
try:
24+
from .pins.rpigpio import RPiGPIOPin
25+
DefaultPin = RPiGPIOPin
26+
except ImportError:
27+
try:
28+
from .pins.rpio import RPIOPin
29+
DefaultPin = RPIOPin
30+
except ImportError:
31+
from .pins.native import NativePin
32+
DefaultPin = NativePin
33+
34+
35+
_THREADS = set()
36+
_PINS = set()
2237
# Due to interactions between RPi.GPIO cleanup and the GPIODevice.close()
2338
# method the same thread may attempt to acquire this lock, leading to deadlock
2439
# unless the lock is re-entrant
25-
_GPIO_PINS_LOCK = RLock()
40+
_PINS_LOCK = RLock()
2641

27-
def _gpio_threads_shutdown():
28-
while _GPIO_THREADS:
29-
for t in _GPIO_THREADS.copy():
42+
def _shutdown():
43+
while _THREADS:
44+
for t in _THREADS.copy():
3045
t.stop()
31-
with _GPIO_PINS_LOCK:
32-
while _GPIO_PINS:
33-
GPIO.remove_event_detect(_GPIO_PINS.pop())
34-
GPIO.cleanup()
46+
with _PINS_LOCK:
47+
while _PINS:
48+
_PINS.pop().close()
49+
# Any cleanup routines registered by pins libraries must be called *after*
50+
# cleanup of pin objects used by devices
51+
for routine in PINS_CLEANUP:
52+
routine()
3553

36-
atexit.register(_gpio_threads_shutdown)
37-
GPIO.setmode(GPIO.BCM)
38-
GPIO.setwarnings(False)
54+
atexit.register(_shutdown)
3955

4056

4157
class GPIOMeta(type):
@@ -196,20 +212,22 @@ def __init__(self, pin=None):
196212
# value of pin until we've verified that it isn't already allocated
197213
self._pin = None
198214
if pin is None:
199-
raise GPIODeviceError('No GPIO pin number given')
200-
with _GPIO_PINS_LOCK:
201-
if pin in _GPIO_PINS:
215+
raise GPIODeviceError('No pin given')
216+
if isinstance(pin, int):
217+
pin = DefaultPin(pin)
218+
with _PINS_LOCK:
219+
if pin in _PINS:
202220
raise GPIODeviceError(
203-
'pin %d is already in use by another gpiozero object' % pin
221+
'pin %r is already in use by another gpiozero object' % pin
204222
)
205-
_GPIO_PINS.add(pin)
223+
_PINS.add(pin)
206224
self._pin = pin
207-
self._active_state = GPIO.HIGH
208-
self._inactive_state = GPIO.LOW
225+
self._active_state = True
226+
self._inactive_state = False
209227

210228
def _read(self):
211229
try:
212-
return GPIO.input(self.pin) == self._active_state
230+
return self.pin.state == self._active_state
213231
except TypeError:
214232
self._check_open()
215233
raise
@@ -260,13 +278,12 @@ def close(self):
260278
...
261279
"""
262280
super(GPIODevice, self).close()
263-
with _GPIO_PINS_LOCK:
281+
with _PINS_LOCK:
264282
pin = self._pin
265283
self._pin = None
266-
if pin in _GPIO_PINS:
267-
_GPIO_PINS.remove(pin)
268-
GPIO.remove_event_detect(pin)
269-
GPIO.cleanup(pin)
284+
if pin in _PINS:
285+
_PINS.remove(pin)
286+
pin.close()
270287

271288
@property
272289
def closed(self):
@@ -275,9 +292,10 @@ def closed(self):
275292
@property
276293
def pin(self):
277294
"""
278-
The pin (in BCM numbering) that the device is connected to. This will
279-
be ``None`` if the device has been closed (see the :meth:`close`
280-
method).
295+
The :class:`Pin` that the device is connected to. This will be ``None``
296+
if the device has been closed (see the :meth:`close` method). When
297+
dealing with GPIO pins, query ``pin.number`` to discover the GPIO
298+
pin (in BCM numbering) that the device is connected to.
281299
"""
282300
return self._pin
283301

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

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

308326
def start(self):
309327
self.stopping.clear()
310-
_GPIO_THREADS.add(self)
328+
_THREADS.add(self)
311329
super(GPIOThread, self).start()
312330

313331
def stop(self):
@@ -316,7 +334,7 @@ def stop(self):
316334

317335
def join(self):
318336
super(GPIOThread, self).join()
319-
_GPIO_THREADS.discard(self)
337+
_THREADS.discard(self)
320338

321339

322340
class GPIOQueue(GPIOThread):

0 commit comments

Comments
 (0)