Skip to content

Commit

Permalink
Merge pull request #192 from waveform80/pigpiod
Browse files Browse the repository at this point in the history
Fix #180 - Add support for pigpio
  • Loading branch information
waveform80 committed Feb 12, 2016
2 parents 3efd522 + aebe428 commit 0978b4c
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 13 deletions.
48 changes: 37 additions & 11 deletions docs/api_pins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ integer number instead, it uses one of the following classes to provide the

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

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

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

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

from gpiozero.pins.native import NativePin
import gpiozero.devices
Expand All @@ -35,8 +37,24 @@ You can change the default pin implementation by over-writing the
# 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::
Alternatively, instead of passing an integer to the device constructor, you
can pass a :class:`Pin` object itself::

from gpiozero.pins.native import NativePin
from gpiozero import LED

led = LED(NativePin(16))

This is particularly useful with implementations that can take extra parameters
such as :class:`PiGPIOPin` which can address pins on remote machines::

from gpiozero.pins.pigpiod import PiGPIOPin
from gpiozero import LED

led = LED(PiGPIOPin(16, host='my_other_pi'))

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

from gpiozero import IOExtender, LED

Expand All @@ -52,13 +70,6 @@ part of IO extender chips. For example::
comments from testers!


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

.. autoclass:: Pin
:members:


RPiGPIOPin
==========

Expand All @@ -75,10 +86,25 @@ RPIOPin
.. autoclass:: RPIOPin


PiGPIOPin
=========

.. currentmodule:: gpiozero.pins.pigpiod

.. autoclass:: PiGPIOPin


NativePin
=========

.. currentmodule:: gpiozero.pins.native

.. autoclass:: NativePin


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

.. autoclass:: Pin
:members:

1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def __getattr__(cls, name):
sys.modules['RPi.GPIO'] = sys.modules['RPi'].GPIO
sys.modules['RPIO'] = Mock()
sys.modules['RPIO.PWM'] = sys.modules['RPIO'].PWM
sys.modules['pigpio'] = Mock()
sys.modules['w1thermsensor'] = Mock()
sys.modules['spidev'] = Mock()

Expand Down
8 changes: 6 additions & 2 deletions gpiozero/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@
from .pins.rpio import RPIOPin
DefaultPin = RPIOPin
except ImportError:
from .pins.native import NativePin
DefaultPin = NativePin
try:
from .pins.pigipod import PiGPIOPin
DefaultPin = PiGPIOPin
except ImportError:
from .pins.native import NativePin
DefaultPin = NativePin


_THREADS = set()
Expand Down
7 changes: 7 additions & 0 deletions gpiozero/pins/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ class NativePin(Pin):
use any class which requests PWM will raise an exception. This
implementation is also experimental; we make no guarantees it will
not eat your Pi for breakfast!
You can construct native pin instances manually like so::
from gpiozero.pins.native import NativePin
from gpiozero import LED
led = LED(NativePin(12))
"""

_MEM = None
Expand Down
240 changes: 240 additions & 0 deletions gpiozero/pins/pigpiod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')

import pigpio

from . import Pin
from ..exc import (
PinInvalidFunction,
PinSetInput,
PinFixedPull,
)


class PiGPIOPin(Pin):
"""
Uses the `pigpio`_ library to interface to the Pi's GPIO pins. The pigpio
library relies on a daemon (``pigpiod``) to be running as root to provide
access to the GPIO pins, and communicates with this daemon over a network
socket.
While this does mean only the daemon itself should control the pins, the
architecture does have several advantages:
* Pins can be remote controlled from another machine (the other
machine doesn't even have to be a Raspberry Pi; it simply needs the
`pigpio`_ client library installed on it)
* The daemon supports hardware PWM via the DMA controller
* Your script itself doesn't require root privileges; it just needs to
be able to communicate with the daemon
You can construct pigpiod pins manually like so::
from gpiozero.pins.pigpiod import PiGPIOPin
from gpiozero import LED
led = LED(PiGPIOPin(12))
This is particularly useful for controlling pins on a remote machine. To
accomplish this simply specify the host (and optionally port) when
constructing the pin::
from gpiozero.pins.pigpiod import PiGPIOPin
from gpiozero import LED
from signal import pause
led = LED(PiGPIOPin(12, host='192.168.0.2'))
.. note::
In some circumstances, especially when playing with PWM, it does appear
to be possible to get the daemon into "unusual" states. We would be
most interested to hear any bug reports relating to this (it may be a
bug in our pin implementation). A workaround for now is simply to
restart the ``pigpiod`` daemon.
.. _pigpio: http://abyz.co.uk/rpi/pigpio/
"""

_CONNECTIONS = {}
_PINS = {}

GPIO_FUNCTIONS = {
'input': pigpio.INPUT,
'output': pigpio.OUTPUT,
'alt0': pigpio.ALT0,
'alt1': pigpio.ALT1,
'alt2': pigpio.ALT2,
'alt3': pigpio.ALT3,
'alt4': pigpio.ALT4,
'alt5': pigpio.ALT5,
}

GPIO_PULL_UPS = {
'up': pigpio.PUD_UP,
'down': pigpio.PUD_DOWN,
'floating': pigpio.PUD_OFF,
}

GPIO_EDGES = {
'both': pigpio.EITHER_EDGE,
'rising': pigpio.RISING_EDGE,
'falling': pigpio.FALLING_EDGE,
}

GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()}
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}

def __new__(cls, number, host='localhost', port=8888):
try:
return cls._PINS[(host, port, number)]
except KeyError:
self = super(PiGPIOPin, cls).__new__(cls)
cls._PINS[(host, port, number)] = self
try:
self._connection = cls._CONNECTIONS[(host, port)]
except KeyError:
self._connection = pigpio.pi(host, port)
cls._CONNECTIONS[(host, port)] = self._connection
self._host = host
self._port = port
self._number = number
self._pull = 'up' if number in (2, 3) else 'floating'
self._pwm = False
self._bounce = None
self._when_changed = None
self._callback = None
self._edges = pigpio.EITHER_EDGE
self._connection.set_mode(self._number, pigpio.INPUT)
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[self._pull])
self._connection.set_glitch_filter(self._number, 0)
self._connection.set_PWM_range(self._number, 255)
return self

def __repr__(self):
if self._host == 'localhost':
return "GPIO%d" % self._number
else:
return "GPIO%d on %s:%d" % (self._host, self._port)

@property
def host(self):
return self._host

@property
def port(self):
return self._port

@property
def number(self):
return self._number

def close(self):
# If we're shutting down, the connection may have disconnected itself
# already. Unfortunately, the connection's "connected" property is
# rather buggy - disconnecting doesn't set it to False! So we're
# naughty and check an internal variable instead...
if self._connection.sl.s is not None:
self.frequency = None
self.when_changed = None
self.function = 'input'
self.pull = 'floating'

def _get_function(self):
return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)]

def _set_function(self, value):
if value != 'input':
self._pull = 'floating'
try:
self._connection.set_mode(self._number, self.GPIO_FUNCTIONS[value])
except KeyError:
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))

def _get_state(self):
if self._pwm:
return self._connection.get_PWM_dutycycle(self._number) / 255
else:
return bool(self._connection.read(self._number))

def _set_state(self, value):
if self._pwm:
try:
self._connection.set_PWM_dutycycle(self._number, int(value * 255))
except pigpio.error:
raise PinInvalidValue('invalid state "%s" for pin %r' % (value, self))
elif self.function == 'input':
raise PinSetInput('cannot set state of pin %r' % self)
else:
# write forces pin to OUTPUT, hence the check above
self._connection.write(self._number, bool(value))

def _get_pull(self):
return self._pull

def _set_pull(self, value):
if self.function != 'input':
raise PinFixedPull('cannot set pull on non-input pin %r' % self)
if value != 'up' and self._number in (2, 3):
raise PinFixedPull('%r has a physical pull-up resistor' % self)
try:
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value])
self._pull = value
except KeyError:
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))

def _get_frequency(self):
if self._pwm:
return self._connection.get_PWM_frequency(self._number)
return None

def _set_frequency(self, value):
if not self._pwm and value is not None:
self._connection.set_PWM_frequency(self._number, value)
self._connection.set_PWM_dutycycle(self._number, 0)
self._pwm = True
elif self._pwm and value is not None:
self._connection.set_PWM_frequency(self._number, value)
elif self._pwm and value is None:
self._connection.set_PWM_dutycycle(self._number, 0)
self._pwm = False

def _get_bounce(self):
return None if not self._bounce else self._bounce / 1000000

def _set_bounce(self, value):
if value is None:
value = 0
self._connection.set_glitch_filter(self._number, int(value * 1000000))

def _get_edges(self):
return self.GPIO_EDGES_NAMES[self._edges]

def _set_edges(self, value):
f = self.when_changed
self.when_changed = None
try:
self._edges = self.GPIO_EDGES[value]
finally:
self.when_changed = f

def _get_when_changed(self):
if self._callback is None:
return None
return self._callback.callb.func

def _set_when_changed(self, value):
if self._callback is not None:
self._callback.cancel()
self._callback = None
if value is not None:
self._callback = self._connection.callback(
self._number, self._edges,
lambda gpio, level, tick: value())

14 changes: 14 additions & 0 deletions gpiozero/pins/rpigpio.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ class RPiGPIOPin(Pin):
the default pin implementation if the RPi.GPIO library is installed.
Supports all features including PWM (via software).
Because this is the default pin implementation you can use it simply by
specifying an integer number for the pin in most operations, e.g.::
from gpiozero import LED
led = LED(12)
However, you can also construct RPi.GPIO pins manually if you wish::
from gpiozero.pins.rpigpio import RPiGPIOPin
from gpiozero import LED
led = LED(RPiGPIOPin(12))
.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO
"""

Expand Down
7 changes: 7 additions & 0 deletions gpiozero/pins/rpio.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ class RPIOPin(Pin):
Pi 1's; the Raspberry Pi 2 Model B is *not* supported. Also note that
root access is required so scripts must typically be run with ``sudo``.
You can construct RPIO pins manually like so::
from gpiozero.pins.rpio import RPIOPin
from gpiozero import LED
led = LED(RPIOPin(12))
.. _RPIO: https://pythonhosted.org/RPIO/
"""

Expand Down

0 comments on commit 0978b4c

Please sign in to comment.