mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 17:50:37 +00:00
Refactor low level implementation
This commit is a fairly major piece of work that abstracts all pin operations (function, state, edge detection, PWM, etc.) into a base "Pin" class which is then used by input/output/composite devices to perform all required configuration. The idea is to pave the way for I2C based IO extenders which can present additional GPIO ports with similar capabilities to the Pi's "native" GPIO ports. As a bonus it also abstracts away the reliance on the RPi.GPIO library to allow alternative pin implementations (e.g. using RPIO to take advantage of DMA based PWM), or even pure Python implementations.
This commit is contained in:
241
gpiozero/pins/__init__.py
Normal file
241
gpiozero/pins/__init__.py
Normal file
@@ -0,0 +1,241 @@
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
from .exc import (
|
||||
PinFixedFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinPWMUnsupported,
|
||||
PinEdgeDetectUnsupported,
|
||||
)
|
||||
|
||||
|
||||
PINS_CLEANUP = []
|
||||
|
||||
|
||||
class Pin(object):
|
||||
"""
|
||||
Abstract base class representing a GPIO pin or a pin from an IO extender.
|
||||
|
||||
Descendents should override property getters and setters to accurately
|
||||
represent the capabilities of pins. The following functions *must* be
|
||||
overridden:
|
||||
|
||||
* :meth:`_get_function`
|
||||
* :meth:`_get_state`
|
||||
|
||||
The following functions *may* be overridden if applicable:
|
||||
|
||||
* :meth:`close`
|
||||
* :meth:`_set_function`
|
||||
* :meth:`_set_state`
|
||||
* :meth:`_get_frequency`
|
||||
* :meth:`_set_frequency`
|
||||
* :meth:`_get_pull`
|
||||
* :meth:`_set_pull`
|
||||
* :meth:`_get_bounce`
|
||||
* :meth:`_set_bounce`
|
||||
* :meth:`_get_edges`
|
||||
* :meth:`_set_edges`
|
||||
* :meth:`_get_when_changed`
|
||||
* :meth:`_set_when_changed`
|
||||
* :meth:`output_with_state`
|
||||
* :meth:`input_with_pull`
|
||||
|
||||
.. warning::
|
||||
|
||||
Descendents must ensure that pin instances representing the same
|
||||
physical hardware are identical, right down to object identity. The
|
||||
framework relies on this to correctly clean up resources at interpreter
|
||||
shutdown.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return "Abstract pin"
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Cleans up the resources allocated to the pin. After this method is
|
||||
called, this :class:`Pin` instance may no longer be used to query or
|
||||
control the pin's state.
|
||||
"""
|
||||
pass
|
||||
|
||||
def output_with_state(self, state):
|
||||
"""
|
||||
Sets the pin's function to "output" and specifies an initial state
|
||||
for the pin. By default this is equivalent to performing::
|
||||
|
||||
pin.function = 'output'
|
||||
pin.state = state
|
||||
|
||||
However, descendents may override this in order to provide the smallest
|
||||
possible delay between configuring the pin for output and specifying an
|
||||
initial value (which can be important for avoiding "blips" in
|
||||
active-low configurations).
|
||||
"""
|
||||
self.function = 'output'
|
||||
self.state = state
|
||||
|
||||
def input_with_pull(self, pull):
|
||||
"""
|
||||
Sets the pin's function to "input" and specifies an initial pull-up
|
||||
for the pin. By default this is equivalent to performing::
|
||||
|
||||
pin.function = 'input'
|
||||
pin.pull = pull
|
||||
|
||||
However, descendents may override this order to provide the smallest
|
||||
possible delay between configuring the pin for input and pulling the
|
||||
pin up/down (which can be important for avoiding "blips" in some
|
||||
configurations).
|
||||
"""
|
||||
self.function = 'input'
|
||||
self.pull = pull
|
||||
|
||||
def _get_function(self):
|
||||
return "input"
|
||||
|
||||
def _set_function(self, value):
|
||||
raise PinFixedFunction("Cannot set the function of pin %r" % self)
|
||||
|
||||
function = property(
|
||||
lambda self: self._get_function(),
|
||||
lambda self, value: self._set_function(value),
|
||||
doc="""\
|
||||
The function of the pin. This property is a string indicating the
|
||||
current function or purpose of the pin. Typically this is the string
|
||||
"input" or "output". However, in some circumstances it can be other
|
||||
strings indicating non-GPIO related functionality.
|
||||
|
||||
With certain pin types (e.g. GPIO pins), this attribute can be changed
|
||||
to configure the function of a pin. If an invalid function is
|
||||
specified, for this attribute, :exc:`PinInvalidFunction` will be
|
||||
raised. If this pin is fixed function and an attempt is made to set
|
||||
this attribute, :exc:`PinFixedFunction` will be raised.
|
||||
""")
|
||||
|
||||
def _get_state(self):
|
||||
return 0
|
||||
|
||||
def _set_state(self, value):
|
||||
raise PinSetInput("Cannot set the state of input pin %r" % self)
|
||||
|
||||
state = property(
|
||||
lambda self: self._get_state(),
|
||||
lambda self, value: self._set_state(value),
|
||||
doc="""\
|
||||
The state of the pin. This is 0 for low, and 1 for high. As a low level
|
||||
view of the pin, no swapping is performed in the case of pull ups (see
|
||||
:attr:`pull` for more information).
|
||||
|
||||
If PWM is currently active (when :attr:`frequency` is not ``None``),
|
||||
this represents the PWM duty cycle as a value between 0.0 and 1.0.
|
||||
|
||||
If a pin is currently configured for input, and an attempt is made to
|
||||
set this attribute, :exc:`PinSetInput` will be raised. If an invalid
|
||||
value is specified for this attribute, :exc:`PinInvalidState` will be
|
||||
raised.
|
||||
""")
|
||||
|
||||
def _get_pull(self):
|
||||
return 'floating'
|
||||
|
||||
def _set_pull(self, value):
|
||||
raise PinFixedPull("Cannot change pull-up on pin %r" % self)
|
||||
|
||||
pull = property(
|
||||
lambda self: self._get_pull(),
|
||||
lambda self, value: self._set_pull(value),
|
||||
doc="""\
|
||||
The pull-up state of the pin represented as a string. This is typically
|
||||
one of the strings "up", "down", or "floating" but additional values
|
||||
may be supported by the underlying hardware.
|
||||
|
||||
If the pin does not support changing pull-up state (for example because
|
||||
of a fixed pull-up resistor), attempts to set this property will raise
|
||||
:exc:`PinFixedPull`. If the specified value is not supported by the
|
||||
underlying hardware, :exc:`PinInvalidPull` is raised.
|
||||
""")
|
||||
|
||||
def _get_frequency(self):
|
||||
return None
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if value is not None:
|
||||
raise PinPWMUnsupported("PWM is not supported on pin %r" % self)
|
||||
|
||||
frequency = property(
|
||||
lambda self: self._get_frequency(),
|
||||
lambda self, value: self._set_frequency(value),
|
||||
doc="""\
|
||||
The frequency (in Hz) for the pin's PWM implementation, or ``None`` if
|
||||
PWM is not currently in use. This value always defaults to ``None`` and
|
||||
may be changed with certain pin types to activate or deactivate PWM.
|
||||
|
||||
If the pin does not support PWM, :exc:`PinPWMUnsupported` will be
|
||||
raised when attempting to set this to a value other than ``None``.
|
||||
""")
|
||||
|
||||
def _get_bounce(self):
|
||||
return None
|
||||
|
||||
def _set_bounce(self, value):
|
||||
if value is not None:
|
||||
raise PinEdgeDetectUnsupported("Edge detection is not supported on pin %r" % self)
|
||||
|
||||
bounce = property(
|
||||
lambda self: self._get_bounce(),
|
||||
lambda self, value: self._set_bounce(value),
|
||||
doc="""\
|
||||
The amount of bounce detection (elimination) currently in use by edge
|
||||
detection, measured in seconds. If bounce detection is not currently in
|
||||
use, this is ``None``.
|
||||
|
||||
If the pin does not support edge detection, attempts to set this
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`. If the pin
|
||||
supports edge detection, the class must implement bounce detection,
|
||||
even if only in software.
|
||||
""")
|
||||
|
||||
def _get_edges(self):
|
||||
return 'both'
|
||||
|
||||
def _set_edges(self, value):
|
||||
raise PinEdgeDetectUnsupported("Edge detection is not supported on pin %r" % self)
|
||||
|
||||
edges = property(
|
||||
lambda self: self._get_edges(),
|
||||
lambda self, value: self._set_edges(value),
|
||||
doc="""\
|
||||
The edge that will trigger execution of the function or bound method
|
||||
assigned to :attr:`when_changed`. This can be one of the strings
|
||||
"both" (the default), "rising", or "falling".
|
||||
|
||||
If the pin does not support edge detection, attempts to set this
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`.
|
||||
""")
|
||||
|
||||
def _get_when_changed(self):
|
||||
return None
|
||||
|
||||
def _set_when_changed(self, value):
|
||||
raise PinEdgeDetectUnsupported("Edge detection is not supported on pin %r" % self)
|
||||
|
||||
when_changed = property(
|
||||
lambda self: self._get_when_changed(),
|
||||
lambda self, value: self._set_when_changed(value),
|
||||
doc="""\
|
||||
A function or bound method to be called when the pin's state changes
|
||||
(more specifically when the edge specified by :attr:`edges` is detected
|
||||
on the pin). The function or bound method must take no parameters.
|
||||
|
||||
If the pin does not support edge detection, attempts to set this
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`.
|
||||
""")
|
||||
|
||||
45
gpiozero/pins/exc.py
Normal file
45
gpiozero/pins/exc.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
|
||||
class PinError(Exception):
|
||||
"Base class for errors related to pin implementations"
|
||||
|
||||
class PinFixedFunction(PinError, AttributeError):
|
||||
"Error raised when attempting to change the function of a fixed type pin"
|
||||
|
||||
class PinInvalidFunction(PinError, ValueError):
|
||||
"Error raised when attempting to change the function of a pin to an invalid value"
|
||||
|
||||
class PinInvalidState(PinError, ValueError):
|
||||
"Error raised when attempting to assign an invalid state to a pin"
|
||||
|
||||
class PinInvalidPull(PinError, ValueError):
|
||||
"Error raised when attempting to assign an invalid pull-up to a pin"
|
||||
|
||||
class PinInvalidEdges(PinError, ValueError):
|
||||
"Error raised when attempting to assign an invalid edge detection to a pin"
|
||||
|
||||
class PinSetInput(PinError, AttributeError):
|
||||
"Error raised when attempting to set a read-only pin"
|
||||
|
||||
class PinFixedPull(PinError, AttributeError):
|
||||
"Error raised when attempting to set the pull of a pin with fixed pull-up"
|
||||
|
||||
class PinEdgeDetectUnsupported(PinError, AttributeError):
|
||||
"Error raised when attempting to use edge detection on unsupported pins"
|
||||
|
||||
class PinPWMError(PinError):
|
||||
"Base class for errors related to PWM implementations"
|
||||
|
||||
class PinPWMUnsupported(PinPWMError, AttributeError):
|
||||
"Error raised when attempting to activate PWM on unsupported pins"
|
||||
|
||||
class PinPWMFixedValue(PinPWMError, AttributeError):
|
||||
"Error raised when attempting to initialize PWM on an input pin"
|
||||
|
||||
336
gpiozero/pins/native.py
Normal file
336
gpiozero/pins/native.py
Normal file
@@ -0,0 +1,336 @@
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
nstr = str
|
||||
str = type('')
|
||||
|
||||
import io
|
||||
import os
|
||||
import mmap
|
||||
import errno
|
||||
import struct
|
||||
from time import sleep
|
||||
from threading import Thread, Event, Lock
|
||||
from collections import Counter
|
||||
|
||||
from . import Pin, PINS_CLEANUP
|
||||
from .exc import (
|
||||
PinInvalidPull,
|
||||
PinInvalidEdges,
|
||||
PinInvalidFunction,
|
||||
PinFixedPull,
|
||||
)
|
||||
|
||||
|
||||
class GPIOMemory(object):
|
||||
|
||||
GPIO_BASE_OFFSET = 0x200000
|
||||
PERI_BASE_OFFSET = {
|
||||
'BCM2708': 0x20000000,
|
||||
'BCM2835': 0x20000000,
|
||||
'BCM2709': 0x3f000000,
|
||||
'BCM2836': 0x3f000000,
|
||||
}
|
||||
|
||||
# From BCM2835 data-sheet, p.91
|
||||
GPFSEL_OFFSET = 0x00 >> 2
|
||||
GPSET_OFFSET = 0x1c >> 2
|
||||
GPCLR_OFFSET = 0x28 >> 2
|
||||
GPLEV_OFFSET = 0x34 >> 2
|
||||
GPEDS_OFFSET = 0x40 >> 2
|
||||
GPREN_OFFSET = 0x4c >> 2
|
||||
GPFEN_OFFSET = 0x58 >> 2
|
||||
GPHEN_OFFSET = 0x64 >> 2
|
||||
GPLEN_OFFSET = 0x70 >> 2
|
||||
GPAREN_OFFSET = 0x7c >> 2
|
||||
GPAFEN_OFFSET = 0x88 >> 2
|
||||
GPPUD_OFFSET = 0x94 >> 2
|
||||
GPPUDCLK_OFFSET = 0x98 >> 2
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
self.fd = os.open('/dev/gpiomem', os.O_RDWR | os.O_SYNC)
|
||||
except OSError:
|
||||
try:
|
||||
self.fd = os.open('/dev/mem', os.O_RDWR | os.O_SYNC)
|
||||
except OSError:
|
||||
raise IOError(
|
||||
'unable to open /dev/gpiomem or /dev/mem; '
|
||||
'upgrade your kernel or run as root')
|
||||
else:
|
||||
offset = self.peripheral_base() + self.GPIO_BASE_OFFSET
|
||||
else:
|
||||
offset = 0
|
||||
self.mem = mmap.mmap(self.fd, 4096, offset=offset)
|
||||
|
||||
def close(self):
|
||||
self.mem.close()
|
||||
os.close(self.fd)
|
||||
|
||||
def peripheral_base(self):
|
||||
try:
|
||||
with io.open('/proc/device-tree/soc/ranges', 'rb') as f:
|
||||
f.seek(4)
|
||||
return struct.unpack(nstr('>L'), f.read(4))[0]
|
||||
except IOError:
|
||||
with io.open('/proc/cpuinfo', 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('Hardware'):
|
||||
try:
|
||||
return self.PERI_BASE_OFFSET[line.split(':')[1].strip()]
|
||||
except KeyError:
|
||||
raise IOError('unable to determine RPi revision')
|
||||
raise IOError('unable to determine peripheral base')
|
||||
|
||||
def __getitem__(self, index):
|
||||
return struct.unpack_from(nstr('<L'), self.mem, index * 4)[0]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
struct.pack_into(nstr('<L'), self.mem, index * 4, value)
|
||||
|
||||
|
||||
class GPIOFS(object):
|
||||
|
||||
GPIO_PATH = '/sys/class/gpio'
|
||||
|
||||
def __init__(self):
|
||||
self._lock = Lock()
|
||||
self._pin_refs = Counter()
|
||||
|
||||
def path(self, name):
|
||||
return os.path.join(self.GPIO_PATH, name)
|
||||
|
||||
def export(self, pin):
|
||||
with self._lock:
|
||||
if self._pin_refs[pin] == 0:
|
||||
# Set the count to 1 to indicate the GPIO is already exported
|
||||
# (we'll correct this if we find it isn't, but this enables us
|
||||
# to "leave the system in the state we found it")
|
||||
self._pin_refs[pin] = 1
|
||||
result = None
|
||||
# Dirty hack to wait for udev to set permissions on
|
||||
# gpioN/direction; there's no other way around this as there's
|
||||
# no synchronous mechanism for setting permissions on sysfs
|
||||
for i in range(10):
|
||||
try:
|
||||
result = io.open(self.path('gpio%d/value' % pin), 'w+b', buffering=0)
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
with io.open(self.path('export'), 'wb') as f:
|
||||
f.write(str(pin).encode('ascii'))
|
||||
# Pin wasn't exported, so correct the ref-count
|
||||
self._pin_refs[pin] = 0
|
||||
elif e.errno == errno.EACCES:
|
||||
sleep(i / 100)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
break
|
||||
if not result:
|
||||
raise RuntimeError('failed to export pin %d' % pin)
|
||||
else:
|
||||
result = io.open(self.path('gpio%d/value' % pin), 'w+b', buffering=0)
|
||||
self._pin_refs[pin] += 1
|
||||
return result
|
||||
|
||||
def unexport(self, pin):
|
||||
with self._lock:
|
||||
self._pin_refs[pin] -= 1
|
||||
if self._pin_refs[pin] == 0:
|
||||
with io.open(self.path('unexport'), 'wb') as f:
|
||||
f.write(str(pin).encode('ascii'))
|
||||
|
||||
|
||||
class NativePin(Pin):
|
||||
"""
|
||||
Uses a built-in pure Python implementation to interface to the Pi's GPIO
|
||||
pins. This is the default pin implementation if no third-party libraries
|
||||
are discovered.
|
||||
|
||||
.. warning::
|
||||
|
||||
This implementation does *not* currently support PWM. Attempting to
|
||||
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!
|
||||
"""
|
||||
|
||||
_MEM = None
|
||||
_PINS = {}
|
||||
|
||||
PULL_NAMES = {
|
||||
0b00: 'floating',
|
||||
0b01: 'down',
|
||||
0b10: 'up',
|
||||
0b11: 'reserved',
|
||||
}
|
||||
|
||||
FUNCTION_NAMES = {
|
||||
0b000: 'input',
|
||||
0b001: 'output',
|
||||
0b100: 'alt0',
|
||||
0b101: 'alt1',
|
||||
0b110: 'alt2',
|
||||
0b111: 'alt3',
|
||||
0b011: 'alt4',
|
||||
0b010: 'alt5',
|
||||
}
|
||||
|
||||
EDGE_NAMES = {
|
||||
(True, True): 'both',
|
||||
(True, False): 'rising',
|
||||
(False, True): 'falling',
|
||||
(False, False): 'none',
|
||||
}
|
||||
|
||||
PULL_VALUES = {v: k for (k, v) in PULL_NAMES.items()}
|
||||
FUNCTION_VALUES = {v: k for (k, v) in FUNCTION_NAMES.items()}
|
||||
EDGE_VALUES = {v: k for (k, v) in EDGE_NAMES.items()}
|
||||
|
||||
def __new__(cls, number):
|
||||
if not cls._PINS:
|
||||
cls._MEM = GPIOMemory()
|
||||
PINS_CLEANUP.append(cls._MEM.close)
|
||||
if not (0 <= number < 54):
|
||||
raise ValueError('invalid pin %d specified (must be 0..53)' % number)
|
||||
try:
|
||||
return cls._PINS[number]
|
||||
except KeyError:
|
||||
self = super(NativePin, cls).__new__(cls)
|
||||
cls._PINS[number] = self
|
||||
self._number = number
|
||||
self._func_offset = self._MEM.GPFSEL_OFFSET + (number // 10)
|
||||
self._func_shift = (number % 10) * 3
|
||||
self._set_offset = self._MEM.GPSET_OFFSET + (number // 32)
|
||||
self._set_shift = number % 32
|
||||
self._clear_offset = self._MEM.GPCLR_OFFSET + (number // 32)
|
||||
self._clear_shift = number % 32
|
||||
self._level_offset = self._MEM.GPLEV_OFFSET + (number // 32)
|
||||
self._level_shift = number % 32
|
||||
self._pull_offset = self._MEM.GPPUDCLK_OFFSET + (number // 32)
|
||||
self._pull_shift = number % 32
|
||||
self._edge_offset = self._MEM.GPEDS_OFFSET + (number // 32)
|
||||
self._edge_shift = number % 32
|
||||
self._rising_offset = self._MEM.GPREN_OFFSET + (number // 32)
|
||||
self._rising_shift = number % 32
|
||||
self._falling_offset = self._MEM.GPFEN_OFFSET + (number // 32)
|
||||
self._falling_shift = number % 32
|
||||
self._when_changed = None
|
||||
self._change_thread = None
|
||||
self._change_event = Event()
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if number in (2, 3) else 'floating'
|
||||
self.bounce = None
|
||||
self.edges = 'both'
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "GPIO%d" % self._number
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return self._number
|
||||
|
||||
def close(self):
|
||||
self.when_changed = None
|
||||
self.function = 'input'
|
||||
|
||||
def _get_function(self):
|
||||
return self.FUNCTION_NAMES[(self._MEM[self._func_offset] >> self._func_shift) & 7]
|
||||
|
||||
def _set_function(self, value):
|
||||
try:
|
||||
value = self.FUNCTION_VALUES[value]
|
||||
except KeyError:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % self)
|
||||
self._MEM[self._func_offset] = (
|
||||
self._MEM[self._func_offset]
|
||||
& ~(7 << self._func_shift)
|
||||
| (value << self._func_shift)
|
||||
)
|
||||
|
||||
def _get_state(self):
|
||||
return bool(self._MEM[self._level_offset] & (1 << self._level_shift))
|
||||
|
||||
def _set_state(self, value):
|
||||
if value:
|
||||
self._MEM[self._set_offset] = 1 << self._set_shift
|
||||
else:
|
||||
self._MEM[self._clear_offset] = 1 << self._clear_shift
|
||||
|
||||
def _get_pull(self):
|
||||
return self.PULL_NAMES[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:
|
||||
value = self.PULL_VALUES[value]
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull direction "%s" for pin %r' % self)
|
||||
self._MEM[self._MEM.GPPUD_OFFSET] = value
|
||||
sleep(0.000000214)
|
||||
self._MEM[self._pull_offset] = 1 << self._pull_shift
|
||||
sleep(0.000000214)
|
||||
self._MEM[self._MEM.GPPUD_OFFSET] = 0
|
||||
self._MEM[self._pull_offset] = 0
|
||||
self._pull = value
|
||||
|
||||
def _get_edges(self):
|
||||
rising = bool(self._MEM[self._rising_offset] & (1 << self._rising_shift))
|
||||
falling = bool(self._MEM[self._falling_offset] & (1 << self._falling_shift))
|
||||
return self.EDGE_NAMES[(rising, falling)]
|
||||
|
||||
def _set_edges(self, value):
|
||||
try:
|
||||
rising, falling = self.EDGE_VALUES[value]
|
||||
except KeyError:
|
||||
raise PinInvalidEdges('invalid edge specification "%s" for pin %r' % self)
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._MEM[self._rising_offset] = (
|
||||
self._MEM[self._rising_offset]
|
||||
& ~(1 << self._rising_shift)
|
||||
| (rising << self._rising_shift)
|
||||
)
|
||||
self._MEM[self._falling_offset] = (
|
||||
self._MEM[self._falling_offset]
|
||||
& ~(1 << self._falling_shift)
|
||||
| (falling << self._falling_shift)
|
||||
)
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _get_when_changed(self):
|
||||
return self._when_changed
|
||||
|
||||
def _set_when_changed(self, value):
|
||||
if self._when_changed is None and value is not None:
|
||||
self._when_changed = value
|
||||
self._change_thread = Thread(target=self._change_watch)
|
||||
self._change_thread.daemon = True
|
||||
self._change_event.clear()
|
||||
self._change_thread.start()
|
||||
elif self._when_changed is not None and value is None:
|
||||
self._change_event.set()
|
||||
self._change_thread.join()
|
||||
self._change_thread = None
|
||||
self._when_changed = None
|
||||
else:
|
||||
self._when_changed = value
|
||||
|
||||
def _change_watch(self):
|
||||
offset = self._edge_offset
|
||||
mask = 1 << self._edge_shift
|
||||
self._MEM[offset] = mask # clear any existing detection bit
|
||||
while not self._change_event.wait(0.001):
|
||||
if self._MEM[offset] & mask:
|
||||
self._MEM[offset] = mask
|
||||
self._when_changed()
|
||||
|
||||
205
gpiozero/pins/rpigpio.py
Normal file
205
gpiozero/pins/rpigpio.py
Normal file
@@ -0,0 +1,205 @@
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
from RPi import GPIO
|
||||
|
||||
from . import Pin
|
||||
from .exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
)
|
||||
|
||||
|
||||
class RPiGPIOPin(Pin):
|
||||
"""
|
||||
Uses the `RPi.GPIO`_ library to interface to the Pi's GPIO pins. This is
|
||||
the default pin implementation if the RPi.GPIO library is installed.
|
||||
Supports all features including PWM (via software).
|
||||
|
||||
.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO
|
||||
"""
|
||||
|
||||
_PINS = {}
|
||||
|
||||
GPIO_FUNCTIONS = {
|
||||
'input': GPIO.IN,
|
||||
'output': GPIO.OUT,
|
||||
'i2c': GPIO.I2C,
|
||||
'spi': GPIO.SPI,
|
||||
'pwm': GPIO.PWM,
|
||||
'serial': GPIO.SERIAL,
|
||||
'unknown': GPIO.UNKNOWN,
|
||||
}
|
||||
|
||||
GPIO_PULL_UPS = {
|
||||
'up': GPIO.PUD_UP,
|
||||
'down': GPIO.PUD_DOWN,
|
||||
'floating': GPIO.PUD_OFF,
|
||||
}
|
||||
|
||||
GPIO_EDGES = {
|
||||
'both': GPIO.BOTH,
|
||||
'rising': GPIO.RISING,
|
||||
'falling': GPIO.FALLING,
|
||||
}
|
||||
|
||||
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):
|
||||
if not cls._PINS:
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
try:
|
||||
return cls._PINS[number]
|
||||
except KeyError:
|
||||
self = super(RPiGPIOPin, cls).__new__(cls)
|
||||
cls._PINS[number] = self
|
||||
self._number = number
|
||||
self._pull = 'up' if number in (2, 3) else 'floating'
|
||||
self._pwm = None
|
||||
self._frequency = None
|
||||
self._duty_cycle = None
|
||||
self._bounce = -666
|
||||
self._when_changed = None
|
||||
self._edges = GPIO.BOTH
|
||||
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "GPIO%d" % self._number
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return self._number
|
||||
|
||||
def close(self):
|
||||
self.frequency = None
|
||||
self.when_changed = None
|
||||
GPIO.cleanup(self._number)
|
||||
|
||||
def output_with_state(self, state):
|
||||
self._pull = 'floating'
|
||||
GPIO.setup(self._number, GPIO.OUT, initial=state)
|
||||
|
||||
def input_with_pull(self, pull):
|
||||
if pull != 'up' and self._number in (2, 3):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[pull])
|
||||
self._pull = pull
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (pull, self))
|
||||
|
||||
def _get_function(self):
|
||||
return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self._number)]
|
||||
|
||||
def _set_function(self, value):
|
||||
if value != 'input':
|
||||
self._pull = 'floating'
|
||||
try:
|
||||
GPIO.setup(self._number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull])
|
||||
except KeyError:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
def _get_state(self):
|
||||
if self._pwm:
|
||||
return self._duty_cycle
|
||||
else:
|
||||
return GPIO.input(self._number)
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._pwm:
|
||||
try:
|
||||
self._pwm.ChangeDutyCycle(value * 100)
|
||||
except ValueError:
|
||||
raise PinInvalidValue('invalid state "%s" for pin %r' % (value, self))
|
||||
self._duty_cycle = value
|
||||
else:
|
||||
try:
|
||||
GPIO.output(self._number, value)
|
||||
except ValueError:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
except RuntimeError:
|
||||
raise PinSetInput('cannot set state of pin %r' % self)
|
||||
|
||||
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:
|
||||
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[value])
|
||||
self._pull = value
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
|
||||
|
||||
def _get_frequency(self):
|
||||
return self._frequency
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if self._frequency is None and value is not None:
|
||||
try:
|
||||
self._pwm = GPIO.PWM(self._number, value)
|
||||
except RuntimeError:
|
||||
raise PinPWMFixedValue('cannot start PWM on pin %r' % self)
|
||||
self._pwm.start(0)
|
||||
self._duty_cycle = 0
|
||||
self._frequency = value
|
||||
elif self._frequency is not None and value is not None:
|
||||
self._pwm.ChangeFrequency(value)
|
||||
self._frequency = value
|
||||
elif self._frequency is not None and value is None:
|
||||
self._pwm.stop()
|
||||
self._pwm = None
|
||||
self._duty_cycle = None
|
||||
self._frequency = None
|
||||
|
||||
def _get_bounce(self):
|
||||
return None if self._bounce == -666 else (self._bounce / 1000)
|
||||
|
||||
def _set_bounce(self, value):
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._bounce = -666 if value is None else (value * 1000)
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
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):
|
||||
return self._when_changed
|
||||
|
||||
def _set_when_changed(self, value):
|
||||
if self._when_changed is None and value is not None:
|
||||
self._when_changed = value
|
||||
GPIO.add_event_detect(
|
||||
self._number, self._edges,
|
||||
callback=lambda channel: self._when_changed(),
|
||||
bouncetime=self._bounce)
|
||||
elif self._when_changed is not None and value is None:
|
||||
GPIO.remove_event_detect(self._number)
|
||||
self._when_changed = None
|
||||
else:
|
||||
self._when_changed = value
|
||||
|
||||
204
gpiozero/pins/rpio.py
Normal file
204
gpiozero/pins/rpio.py
Normal file
@@ -0,0 +1,204 @@
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
|
||||
from threading import Lock
|
||||
|
||||
import RPIO
|
||||
import RPIO.PWM
|
||||
|
||||
from . import Pin, PINS_CLEANUP
|
||||
from .exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
)
|
||||
|
||||
|
||||
class RPIOPin(Pin):
|
||||
"""
|
||||
Uses the `RPIO`_ library to interface to the Pi's GPIO pins. This is
|
||||
the default pin implementation if the RPi.GPIO library is not installed,
|
||||
but RPIO is. Supports all features including PWM (hardware via DMA).
|
||||
|
||||
.. note::
|
||||
|
||||
Please note that at the time of writing, RPIO is only compatible with
|
||||
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``.
|
||||
|
||||
.. _RPIO: https://pythonhosted.org/RPIO/
|
||||
"""
|
||||
|
||||
_PINS = {}
|
||||
|
||||
GPIO_FUNCTIONS = {
|
||||
'input': RPIO.IN,
|
||||
'output': RPIO.OUT,
|
||||
'alt0': RPIO.ALT0,
|
||||
}
|
||||
|
||||
GPIO_PULL_UPS = {
|
||||
'up': RPIO.PUD_UP,
|
||||
'down': RPIO.PUD_DOWN,
|
||||
'floating': RPIO.PUD_OFF,
|
||||
}
|
||||
|
||||
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()}
|
||||
|
||||
def __new__(cls, number):
|
||||
if not cls._PINS:
|
||||
RPIO.setmode(RPIO.BCM)
|
||||
RPIO.setwarnings(False)
|
||||
RPIO.wait_for_interrupts(threaded=True)
|
||||
RPIO.PWM.setup()
|
||||
RPIO.PWM.init_channel(0, 10000)
|
||||
PINS_CLEANUP.append(RPIO.PWM.cleanup)
|
||||
PINS_CLEANUP.append(RPIO.stop_waiting_for_interrupts)
|
||||
PINS_CLEANUP.append(RPIO.cleanup)
|
||||
try:
|
||||
return cls._PINS[number]
|
||||
except KeyError:
|
||||
self = super(RPIOPin, cls).__new__(cls)
|
||||
cls._PINS[number] = self
|
||||
self._number = number
|
||||
self._pull = 'up' if number in (2, 3) else 'floating'
|
||||
self._pwm = False
|
||||
self._duty_cycle = None
|
||||
self._bounce = None
|
||||
self._when_changed = None
|
||||
self._edges = 'both'
|
||||
RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "GPIO%d" % self._number
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return self._number
|
||||
|
||||
def close(self):
|
||||
self.frequency = None
|
||||
self.when_changed = None
|
||||
RPIO.setup(self._number, RPIO.IN, RPIO.PUD_OFF)
|
||||
|
||||
def _get_function(self):
|
||||
return self.GPIO_FUNCTION_NAMES[RPIO.gpio_function(self._number)]
|
||||
|
||||
def _set_function(self, value):
|
||||
if value != 'input':
|
||||
self._pull = 'floating'
|
||||
try:
|
||||
RPIO.setup(self._number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull])
|
||||
except KeyError:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
def _get_state(self):
|
||||
if self._pwm:
|
||||
return self._duty_cycle
|
||||
else:
|
||||
return RPIO.input(self._number)
|
||||
|
||||
def _set_state(self, value):
|
||||
if not 0 <= value <= 1:
|
||||
raise PinInvalidValue('invalid state "%s" for pin %r' % (value, self))
|
||||
if self._pwm:
|
||||
RPIO.PWM.clear_channel_gpio(0, self._number)
|
||||
if value == 0:
|
||||
RPIO.output(self._number, False)
|
||||
elif value == 1:
|
||||
RPIO.output(self._number, True)
|
||||
else:
|
||||
RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=int(1000 * value))
|
||||
self._duty_cycle = value
|
||||
else:
|
||||
try:
|
||||
RPIO.output(self._number, value)
|
||||
except ValueError:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
except RuntimeError:
|
||||
raise PinSetInput('cannot set state of pin %r' % self)
|
||||
|
||||
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:
|
||||
RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[value])
|
||||
self._pull = value
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
|
||||
|
||||
def _get_frequency(self):
|
||||
return 100
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if value is not None and value != 100:
|
||||
raise PinPWMError(
|
||||
'RPIOPin implementation is currently limited to '
|
||||
'100Hz sub-cycles')
|
||||
if not self._pwm and value is not None:
|
||||
self._pwm = True
|
||||
# Dirty hack to get RPIO's PWM support to setup, but do nothing,
|
||||
# for a given GPIO pin
|
||||
RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=0)
|
||||
RPIO.PWM.clear_channel_gpio(0, self._number)
|
||||
elif self._pwm and value is None:
|
||||
RPIO.PWM.clear_channel_gpio(0, self._number)
|
||||
self._pwm = False
|
||||
|
||||
def _get_bounce(self):
|
||||
return None if self._bounce is None else (self._bounce / 1000)
|
||||
|
||||
def _set_bounce(self, value):
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._bounce = None if value is None else (value * 1000)
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _get_edges(self):
|
||||
return self._edges
|
||||
|
||||
def _set_edges(self, value):
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._edges = value
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _get_when_changed(self):
|
||||
return self._when_changed
|
||||
|
||||
def _set_when_changed(self, value):
|
||||
if self._when_changed is None and value is not None:
|
||||
self._when_changed = value
|
||||
RPIO.add_interrupt_callback(
|
||||
self._number,
|
||||
lambda channel, value: self._when_changed(),
|
||||
self._edges, self.GPIO_PULL_UPS[self._pull], self._bounce)
|
||||
elif self._when_changed is not None and value is None:
|
||||
try:
|
||||
RPIO.del_interrupt_callback(self._number)
|
||||
except KeyError:
|
||||
# Ignore this exception which occurs during shutdown; this
|
||||
# simply means RPIO's built-in cleanup has already run and
|
||||
# removed the handler
|
||||
pass
|
||||
self._when_changed = None
|
||||
else:
|
||||
self._when_changed = value
|
||||
|
||||
Reference in New Issue
Block a user