Merge branch 'master' of github.com:rpi-distro/python-gpiozero

This commit is contained in:
Ben Nuttall
2016-02-11 21:51:38 +00:00
22 changed files with 1022 additions and 78 deletions

View File

@@ -5,7 +5,20 @@ from __future__ import (
division,
)
from .pins.exc import (
from .pins import (
Pin,
)
from .exc import (
GPIOZeroError,
CompositeDeviceError,
GPIODeviceError,
GPIODeviceClosed,
GPIOPinInUse,
GPIOPinMissing,
GPIOBadQueueLen,
InputDeviceError,
OutputDeviceError,
OutputDeviceBadValue,
PinError,
PinFixedFunction,
PinInvalidFunction,
@@ -19,15 +32,6 @@ from .pins.exc import (
PinPWMUnsupported,
PinPWMFixedValue,
)
from .pins import (
Pin,
)
from .exc import (
GPIODeviceClosed,
GPIODeviceError,
InputDeviceError,
OutputDeviceError,
)
from .devices import (
GPIODevice,
CompositeDevice,

27
gpiozero/compat.py Normal file
View File

@@ -0,0 +1,27 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import cmath
# Back-ported from python 3.5; see
# github.com/PythonCHB/close_pep/blob/master/is_close.py for original
# implementation
def isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
if rel_tol < 0.0 or abs_tol < 0.0:
raise ValueError('error tolerances must be non-negative')
if a == b: # fast-path for exact equality
return True
if cmath.isinf(a) or cmath.isinf(b):
return False
diff = abs(b - a)
return (
(diff <= abs(rel_tol * b)) or
(diff <= abs(rel_tol * a)) or
(diff <= abs_tol)
)

View File

@@ -13,7 +13,12 @@ from threading import Thread, Event, RLock
from collections import deque
from types import FunctionType
from .exc import GPIODeviceError, GPIODeviceClosed, InputDeviceError
from .exc import (
GPIOPinMissing,
GPIOPinInUse,
GPIODeviceClosed,
GPIOBadQueueLen,
)
# 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
@@ -203,7 +208,8 @@ class GPIODevice(ValuesMixin, GPIOBase):
:param int pin:
The GPIO pin (in BCM numbering) that the device is connected to. If
this is ``None`` a :exc:`GPIODeviceError` will be raised.
this is ``None``, :exc:`GPIOPinMissing` will be raised. If the pin is
already in use by another device, :exc:`GPIOPinInUse` will be raised.
"""
def __init__(self, pin=None):
super(GPIODevice, self).__init__()
@@ -212,12 +218,12 @@ class GPIODevice(ValuesMixin, GPIOBase):
# value of pin until we've verified that it isn't already allocated
self._pin = None
if pin is None:
raise GPIODeviceError('No pin given')
raise GPIOPinMissing('No pin given')
if isinstance(pin, int):
pin = DefaultPin(pin)
with _PINS_LOCK:
if pin in _PINS:
raise GPIODeviceError(
raise GPIOPinInUse(
'pin %r is already in use by another gpiozero object' % pin
)
_PINS.add(pin)
@@ -342,7 +348,7 @@ class GPIOQueue(GPIOThread):
assert isinstance(parent, GPIODevice)
super(GPIOQueue, self).__init__(target=self.fill)
if queue_len < 1:
raise InputDeviceError('queue_len must be at least one')
raise GPIOBadQueueLen('queue_len must be at least one')
self.queue = deque(maxlen=queue_len)
self.partial = partial
self.sample_wait = sample_wait

View File

@@ -4,16 +4,72 @@ from __future__ import (
absolute_import,
division,
)
str = type('')
class GPIODeviceError(Exception):
pass
class GPIOZeroError(Exception):
"Base class for all exceptions in GPIO Zero"
class CompositeDeviceError(GPIOZeroError):
"Base class for errors specific to the CompositeDevice hierarchy"
class GPIODeviceError(GPIOZeroError):
"Base class for errors specific to the GPIODevice hierarchy"
class GPIODeviceClosed(GPIODeviceError):
pass
"Error raised when an operation is attempted on a closed device"
class GPIOPinInUse(GPIODeviceError):
"Error raised when attempting to use a pin already in use by another device"
class GPIOPinMissing(GPIODeviceError, ValueError):
"Error raised when a pin number is not specified"
class GPIOBadQueueLen(GPIODeviceError, ValueError):
"Error raised when non-positive queue length is specified"
class InputDeviceError(GPIODeviceError):
pass
"Base class for errors specific to the InputDevice hierarchy"
class OutputDeviceError(GPIODeviceError):
pass
"Base class for errors specified to the OutputDevice hierarchy"
class OutputDeviceBadValue(OutputDeviceError, ValueError):
"Error raised when ``value`` is set to an invalid value"
class PinError(GPIOZeroError):
"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"

View File

@@ -10,7 +10,7 @@ from time import sleep
from threading import Lock
from itertools import repeat, cycle, chain
from .exc import OutputDeviceError, GPIODeviceError, GPIODeviceClosed
from .exc import OutputDeviceBadValue, GPIOPinMissing, GPIODeviceClosed
from .devices import GPIODevice, GPIOThread, CompositeDevice, SourceMixin
@@ -24,7 +24,7 @@ class OutputDevice(SourceMixin, GPIODevice):
:param int pin:
The GPIO pin (in BCM numbering) that the device is connected to. If
this is ``None`` a :exc:`GPIODeviceError` will be raised.
this is ``None`` a :exc:`GPIOPinMissing` will be raised.
:param bool active_high:
If ``True`` (the default), the :meth:`on` method will set the GPIO to
@@ -54,7 +54,7 @@ class OutputDevice(SourceMixin, GPIODevice):
value = not value
try:
self.pin.state = bool(value)
except ValueError:
except AttributeError:
self._check_open()
raise
@@ -269,7 +269,7 @@ class PWMOutputDevice(OutputDevice):
def __init__(self, pin=None, active_high=True, initial_value=0, frequency=100):
self._blink_thread = None
if not 0 <= initial_value <= 1:
raise OutputDeviceError("initial_value must be between 0 and 1")
raise OutputDeviceBadValue("initial_value must be between 0 and 1")
super(PWMOutputDevice, self).__init__(pin, active_high)
try:
# XXX need a way of setting these together
@@ -299,7 +299,7 @@ class PWMOutputDevice(OutputDevice):
if not self.active_high:
value = 1 - value
if not 0 <= value <= 1:
raise OutputDeviceError("PWM value must be between 0 and 1")
raise OutputDeviceBadValue("PWM value must be between 0 and 1")
try:
self.pin.state = value
except AttributeError:
@@ -505,7 +505,7 @@ class RGBLED(SourceMixin, CompositeDevice):
self._leds = ()
self._blink_thread = None
if not all([red, green, blue]):
raise OutputDeviceError('red, green, and blue pins must be provided')
raise GPIOPinMissing('red, green, and blue pins must be provided')
super(RGBLED, self).__init__()
self._leds = tuple(PWMLED(pin, active_high) for pin in (red, green, blue))
self.value = initial_value
@@ -680,7 +680,7 @@ class Motor(SourceMixin, CompositeDevice):
"""
def __init__(self, forward=None, backward=None):
if not all([forward, backward]):
raise OutputDeviceError(
raise GPIOPinMissing(
'forward and backward pins must be provided'
)
super(Motor, self).__init__()
@@ -722,7 +722,7 @@ class Motor(SourceMixin, CompositeDevice):
@value.setter
def value(self, value):
if not -1 <= value <= 1:
raise OutputDeviceError("Motor value must be between -1 and 1")
raise OutputDeviceBadValue("Motor value must be between -1 and 1")
if value > 0:
self.forward(value)
elif value < 0:

View File

@@ -6,7 +6,7 @@ from __future__ import (
)
str = type('')
from .exc import (
from ..exc import (
PinFixedFunction,
PinSetInput,
PinFixedPull,

View File

@@ -1,45 +0,0 @@
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"

171
gpiozero/pins/mock.py Normal file
View File

@@ -0,0 +1,171 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
from collections import namedtuple
from time import time
try:
from math import isclose
except ImportError:
from ..compat import isclose
from . import Pin, PINS_CLEANUP
from ..exc import PinSetInput, PinPWMUnsupported
PinState = namedtuple('PinState', ('timestamp', 'state'))
class MockPin(Pin):
"""
A mock pin used primarily for testing. This class does *not* support PWM.
"""
def __init__(self, number):
if not (0 <= number < 54):
raise ValueError('invalid pin %d specified (must be 0..53)' % number)
self._number = number
self._function = 'input'
self._state = False
self._pull = 'floating'
self._bounce = None
self._edges = 'both'
self._when_changed = None
self._last_change = time()
self.states = [PinState(0.0, False)]
def __repr__(self):
return 'MOCK%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
def _set_function(self, value):
assert value in ('input', 'output')
self._function = value
if value == 'input':
# Drive the input to the pull
self._set_pull(self._get_pull())
def _get_state(self):
return self._state
def _set_state(self, value):
if self._function == 'input':
raise PinSetInput()
assert self._function == 'output'
assert 0 <= value <= 1
if self._state != value:
t = time()
self._state = value
self.states.append(PinState(t - self._last_change, value))
self._last_change = t
def _get_frequency(self):
return None
def _set_frequency(self, value):
raise PinPWMUnsupported()
def _get_pull(self):
return self._pull
def _set_pull(self, value):
assert self._function == 'input'
assert value in ('floating', 'up', 'down')
self._pull = value
if value == 'up':
self.drive_high()
elif value == 'down':
self.drive_low()
def _get_bounce(self):
return self._bounce
def _set_bounce(self, value):
# XXX Need to implement this
self._bounce = value
def _get_edges(self):
return self._edges
def _set_edges(self, value):
assert value in ('none', 'falling', 'rising', 'both')
self._edges = value
def _get_when_changed(self):
return self._when_changed
def _set_when_changed(self, value):
self._when_changed = value
def drive_high(self):
assert self._function == 'input'
if not self._state:
t = time()
self._state = True
self.states.append(PinState(t - self._last_change, True))
self._last_change = t
if self._edges in ('both', 'rising') and self._when_changed is not None:
self._when_changed()
def drive_low(self):
assert self._function == 'input'
if self._state:
t = time()
self._state = False
self.states.append(PinState(t - self._last_change, False))
self._last_change = t
if self._edges in ('both', 'falling') and self._when_changed is not None:
self._when_changed()
def clear_states(self):
self._last_change = time()
self.states = [PinState(0.0, self.state)]
def assert_states(self, expected_states):
# Tests that the pin went through the expected states (a list of values)
for actual, expected in zip(self.states, expected_states):
assert actual.state == expected
def assert_states_and_times(self, expected_states):
# Tests that the pin went through the expected states at the expected
# times (times are compared with a tolerance of tens-of-milliseconds as
# that's about all we can reasonably expect in a non-realtime
# environment on a Pi 1)
for actual, expected in zip(self.states, expected_states):
assert isclose(actual.timestamp, expected[0], rel_tol=0.01, abs_tol=0.01)
assert isclose(actual.state, expected[1])
class MockPWMPin(MockPin):
"""
This derivative of :class:`MockPin` adds PWM support.
"""
def __init__(self, number):
super(MockPWMPin, self).__init__(number)
self._frequency = None
def _get_frequency(self):
return self._frequency
def _set_frequency(self, value):
if value is not None:
assert self._function == 'output'
self._frequency = value
if value is None:
self.state = False

View File

@@ -17,7 +17,7 @@ from threading import Thread, Event, Lock
from collections import Counter
from . import Pin, PINS_CLEANUP
from .exc import (
from ..exc import (
PinInvalidPull,
PinInvalidEdges,
PinInvalidFunction,

View File

@@ -9,7 +9,7 @@ str = type('')
from RPi import GPIO
from . import Pin
from .exc import (
from ..exc import (
PinInvalidFunction,
PinSetInput,
PinFixedPull,

View File

@@ -13,7 +13,7 @@ import RPIO
import RPIO.PWM
from . import Pin, PINS_CLEANUP
from .exc import (
from ..exc import (
PinInvalidFunction,
PinSetInput,
PinFixedPull,