mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 17:50:37 +00:00
Merge pull request #192 from waveform80/pigpiod
Fix #180 - Add support for pigpio
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -52,13 +70,6 @@ part of IO extender chips. For example::
|
||||
comments from testers!
|
||||
|
||||
|
||||
Abstract Pin
|
||||
============
|
||||
|
||||
.. autoclass:: Pin
|
||||
:members:
|
||||
|
||||
|
||||
RPiGPIOPin
|
||||
==========
|
||||
|
||||
@@ -75,6 +86,14 @@ RPIOPin
|
||||
.. autoclass:: RPIOPin
|
||||
|
||||
|
||||
PiGPIOPin
|
||||
=========
|
||||
|
||||
.. currentmodule:: gpiozero.pins.pigpiod
|
||||
|
||||
.. autoclass:: PiGPIOPin
|
||||
|
||||
|
||||
NativePin
|
||||
=========
|
||||
|
||||
@@ -82,3 +101,10 @@ NativePin
|
||||
|
||||
.. autoclass:: NativePin
|
||||
|
||||
|
||||
Abstract Pin
|
||||
============
|
||||
|
||||
.. autoclass:: Pin
|
||||
:members:
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ 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['pigpio'] = Mock()
|
||||
sys.modules['w1thermsensor'] = Mock()
|
||||
sys.modules['spidev'] = Mock()
|
||||
|
||||
|
||||
@@ -38,8 +38,12 @@ except ImportError:
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
|
||||
240
gpiozero/pins/pigpiod.py
Normal file
240
gpiozero/pins/pigpiod.py
Normal file
@@ -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())
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
|
||||
@@ -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/
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user