gpiozero-1.4.1/0000755000175000017500000000000013243117413013232 5ustar davedave00000000000000gpiozero-1.4.1/README.rst0000644000175000017500000000704313243115414014724 0ustar davedave00000000000000======== gpiozero ======== .. image:: https://badge.fury.io/py/gpiozero.svg :target: https://badge.fury.io/py/gpiozero :alt: Latest Version .. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master :target: https://travis-ci.org/RPi-Distro/python-gpiozero :alt: Build Tests .. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000 :target: https://codecov.io/github/RPi-Distro/python-gpiozero :alt: Code Coverage A simple interface to GPIO devices with Raspberry Pi. Created by `Ben Nuttall`_ of the `Raspberry Pi Foundation`_, `Dave Jones`_, and other contributors. About ===== Component interfaces are provided to allow a frictionless way to get started with physical computing: .. code:: python from gpiozero import LED from time import sleep led = LED(17) while True: led.on() sleep(1) led.off() sleep(1) With very little code, you can quickly get going connecting your components together: .. code:: python from gpiozero import LED, Button from signal import pause led = LED(17) button = Button(3) button.when_pressed = led.on button.when_released = led.off pause() The library includes interfaces to many simple everyday components, as well as some more complex things like sensors, analogue-to-digital converters, full colour LEDs, robotics kits and more. See the `Recipes`_ chapter of the documentation for ideas on how to get started. Installation ============ GPIO Zero is installed by default in the Raspbian desktop image, available from `raspberrypi.org`_. To install on Raspbian Lite or other operating systems, including for PCs using remote GPIO, see the `Installing`_ chapter. Documentation ============= Comprehensive documentation is available at https://gpiozero.readthedocs.io/. Please refer to the `Contributing`_ and `Development`_ chapters in the documentation for information on contributing to the project. Contributors ============ Core developers: - `Ben Nuttall`_ - `Dave Jones`_ - `Andrew Scheller`_ Other contributors: - `Martin O'Hanlon`_ - `Steve Amor`_ - `David Glaude`_ - `Edward Betts`_ - `Alex Chan`_ - `Thijs Triemstra`_ - `Schelto vanDoorn`_ - `Alex Eames`_ - `Barry Byford`_ - `Clare Macrae`_ - `Tim Golden`_ - `Phil Howard`_ - `Stewart Adcock`_ - `Ian Harcombe`_ .. _Raspberry Pi Foundation: https://www.raspberrypi.org/ .. _raspberrypi.org: https://www.raspberrypi.org/downloads/ .. _Recipes: https://gpiozero.readthedocs.io/en/stable/recipes.html .. _Contributing: https://gpiozero.readthedocs.io/en/stable/contributing.html .. _Development: https://gpiozero.readthedocs.io/en/stable/development.html .. _Installing: https://gpiozero.readthedocs.io/en/stable/installing.html .. _Ben Nuttall: https://github.com/bennuttall .. _Dave Jones: https://github.com/waveform80 .. _Andrew Scheller: https://github.com/lurch .. _Martin O'Hanlon: https://github.com/martinohanlon .. _Steve Amor: https://github.com/SteveAmor .. _David Glaude: https://github.com/dglaude .. _Edward Betts: https://github.com/edwardbetts .. _Alex Chan: https://github.com/alexwlchan .. _Thijs Triemstra: https://github.com/thijstriemstra .. _Schelto vanDoorn: https://github.com/goloplo .. _Alex Eames: https://github.com/raspitv .. _Barry Byford: https://github.com/ukBaz .. _Clare Macrae: https://github.com/claremacrae .. _Tim Golden: https://github.com/tjguk .. _Phil Howard: https://github.com/Gadgetoid .. _Stewart Adcock: https://github.com/stewartadcock .. _Ian Harcombe: https://github.com/MrHarcombe gpiozero-1.4.1/setup.cfg0000644000175000017500000000004613243117413015053 0ustar davedave00000000000000[egg_info] tag_build = tag_date = 0 gpiozero-1.4.1/gpiozerocli/0000755000175000017500000000000013243117413015560 5ustar davedave00000000000000gpiozero-1.4.1/gpiozerocli/pinout.py0000755000175000017500000000610213165454031017455 0ustar davedave00000000000000""" A utility for querying Raspberry Pi GPIO pin-out information. """ from __future__ import ( unicode_literals, absolute_import, print_function, division, ) import argparse import sys import textwrap import warnings class PinoutTool(object): def __init__(self): self.parser = argparse.ArgumentParser( description=__doc__ ) self.parser.add_argument( '-r', '--revision', dest='revision', default='', help='RPi revision. Default is to autodetect revision of current device' ) self.parser.add_argument( '-c', '--color', action="store_true", default=None, help='Force colored output (by default, the output will include ANSI' 'color codes if run in a color-capable terminal). See also --monochrome' ) self.parser.add_argument( '-m', '--monochrome', dest='color', action='store_false', help='Force monochrome output. See also --color' ) def __call__(self, args=None): if args is None: args = sys.argv[1:] try: return self.main(self.parser.parse_args(args)) or 0 except argparse.ArgumentError as e: # argparse errors are already nicely formatted, print to stderr and # exit with code 2 raise e except Exception as e: raise # Output anything else nicely formatted on stderr and exit code 1 self.parser.exit(1, '{prog}: error: {message}\n'.format( prog=self.parser.prog, message=e)) def main(self, args): warnings.simplefilter('ignore') try: from gpiozero import pi_info except ImportError: formatter = self.parser._get_formatter() formatter.add_text( "Unable to initialize GPIO Zero. This usually means that you " "are not running %(prog)s on a Raspberry Pi. If you still wish " "to run %(prog)s, set the GPIOZERO_PIN_FACTORY environment " "variable to 'mock' and retry, or refer to the Remote GPIO " "section of the manual* to configure your environment to " "remotely access your Pi." ) formatter.add_text( "* https://gpiozero.readthedocs.io/en/latest/remote_gpio.html" ) sys.stderr.write(formatter.format_help()) else: if args.revision == '': try: pi_info().pprint(color=args.color) except IOError: raise IOError('This device is not a Raspberry Pi') else: pi_info(args.revision).pprint(color=args.color) formatter = self.parser._get_formatter() formatter.add_text( "For further information, please refer to https://pinout.xyz/" ) sys.stdout.write('\n') sys.stdout.write(formatter.format_help()) main = PinoutTool() gpiozero-1.4.1/gpiozerocli/__init__.py0000644000175000017500000000000013165454031017662 0ustar davedave00000000000000gpiozero-1.4.1/PKG-INFO0000644000175000017500000001301713243117413014331 0ustar davedave00000000000000Metadata-Version: 1.1 Name: gpiozero Version: 1.4.1 Summary: UNKNOWN Home-page: https://github.com/RPi-Distro/python-gpiozero Author: Ben Nuttall Author-email: ben@raspberrypi.org License: BSD License Description-Content-Type: UNKNOWN Description: ======== gpiozero ======== .. image:: https://badge.fury.io/py/gpiozero.svg :target: https://badge.fury.io/py/gpiozero :alt: Latest Version .. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master :target: https://travis-ci.org/RPi-Distro/python-gpiozero :alt: Build Tests .. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000 :target: https://codecov.io/github/RPi-Distro/python-gpiozero :alt: Code Coverage A simple interface to GPIO devices with Raspberry Pi. Created by `Ben Nuttall`_ of the `Raspberry Pi Foundation`_, `Dave Jones`_, and other contributors. About ===== Component interfaces are provided to allow a frictionless way to get started with physical computing: .. code:: python from gpiozero import LED from time import sleep led = LED(17) while True: led.on() sleep(1) led.off() sleep(1) With very little code, you can quickly get going connecting your components together: .. code:: python from gpiozero import LED, Button from signal import pause led = LED(17) button = Button(3) button.when_pressed = led.on button.when_released = led.off pause() The library includes interfaces to many simple everyday components, as well as some more complex things like sensors, analogue-to-digital converters, full colour LEDs, robotics kits and more. See the `Recipes`_ chapter of the documentation for ideas on how to get started. Installation ============ GPIO Zero is installed by default in the Raspbian desktop image, available from `raspberrypi.org`_. To install on Raspbian Lite or other operating systems, including for PCs using remote GPIO, see the `Installing`_ chapter. Documentation ============= Comprehensive documentation is available at https://gpiozero.readthedocs.io/. Please refer to the `Contributing`_ and `Development`_ chapters in the documentation for information on contributing to the project. Contributors ============ Core developers: - `Ben Nuttall`_ - `Dave Jones`_ - `Andrew Scheller`_ Other contributors: - `Martin O'Hanlon`_ - `Steve Amor`_ - `David Glaude`_ - `Edward Betts`_ - `Alex Chan`_ - `Thijs Triemstra`_ - `Schelto vanDoorn`_ - `Alex Eames`_ - `Barry Byford`_ - `Clare Macrae`_ - `Tim Golden`_ - `Phil Howard`_ - `Stewart Adcock`_ - `Ian Harcombe`_ .. _Raspberry Pi Foundation: https://www.raspberrypi.org/ .. _raspberrypi.org: https://www.raspberrypi.org/downloads/ .. _Recipes: https://gpiozero.readthedocs.io/en/stable/recipes.html .. _Contributing: https://gpiozero.readthedocs.io/en/stable/contributing.html .. _Development: https://gpiozero.readthedocs.io/en/stable/development.html .. _Installing: https://gpiozero.readthedocs.io/en/stable/installing.html .. _Ben Nuttall: https://github.com/bennuttall .. _Dave Jones: https://github.com/waveform80 .. _Andrew Scheller: https://github.com/lurch .. _Martin O'Hanlon: https://github.com/martinohanlon .. _Steve Amor: https://github.com/SteveAmor .. _David Glaude: https://github.com/dglaude .. _Edward Betts: https://github.com/edwardbetts .. _Alex Chan: https://github.com/alexwlchan .. _Thijs Triemstra: https://github.com/thijstriemstra .. _Schelto vanDoorn: https://github.com/goloplo .. _Alex Eames: https://github.com/raspitv .. _Barry Byford: https://github.com/ukBaz .. _Clare Macrae: https://github.com/claremacrae .. _Tim Golden: https://github.com/tjguk .. _Phil Howard: https://github.com/Gadgetoid .. _Stewart Adcock: https://github.com/stewartadcock .. _Ian Harcombe: https://github.com/MrHarcombe Keywords: raspberrypi,gpio Platform: ALL Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Education Classifier: Intended Audience :: Developers Classifier: Topic :: Education Classifier: Topic :: System :: Hardware Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: PyPy gpiozero-1.4.1/gpiozero/0000755000175000017500000000000013243117413015070 5ustar davedave00000000000000gpiozero-1.4.1/gpiozero/compat.py0000644000175000017500000000751713165454031016742 0ustar davedave00000000000000# vim: set fileencoding=utf-8: from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import cmath import weakref import collections import operator import functools # 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) ) # Backported from py3.4 def mean(data): if iter(data) is data: data = list(data) n = len(data) if not n: raise ValueError('cannot calculate mean of empty data') return sum(data) / n # Backported from py3.4 def median(data): data = sorted(data) n = len(data) if not n: raise ValueError('cannot calculate median of empty data') elif n % 2: return data[n // 2] else: i = n // 2 return (data[i - 1] + data[i]) / 2 # Copied from the MIT-licensed https://github.com/slezica/python-frozendict class frozendict(collections.Mapping): def __init__(self, *args, **kwargs): self.__dict = dict(*args, **kwargs) self.__hash = None def __getitem__(self, key): return self.__dict[key] def copy(self, **add_or_replace): return frozendict(self, **add_or_replace) def __iter__(self): return iter(self.__dict) def __len__(self): return len(self.__dict) def __repr__(self): return '' % repr(self.__dict) def __hash__(self): if self.__hash is None: hashes = map(hash, self.items()) self.__hash = functools.reduce(operator.xor, hashes, 0) return self.__hash # Backported from py3.4 class WeakMethod(weakref.ref): """ A custom `weakref.ref` subclass which simulates a weak reference to a bound method, working around the lifetime problem of bound methods. """ __slots__ = "_func_ref", "_meth_type", "_alive", "__weakref__" def __new__(cls, meth, callback=None): try: obj = meth.__self__ func = meth.__func__ except AttributeError: raise TypeError("argument should be a bound method, not {0}" .format(type(meth))) def _cb(arg): # The self-weakref trick is needed to avoid creating a reference # cycle. self = self_wr() if self._alive: self._alive = False if callback is not None: callback(self) self = weakref.ref.__new__(cls, obj, _cb) self._func_ref = weakref.ref(func, _cb) self._meth_type = type(meth) self._alive = True self_wr = weakref.ref(self) return self def __call__(self): obj = super(WeakMethod, self).__call__() func = self._func_ref() if obj is None or func is None: return None return self._meth_type(func, obj) def __eq__(self, other): if isinstance(other, WeakMethod): if not self._alive or not other._alive: return self is other return weakref.ref.__eq__(self, other) and self._func_ref == other._func_ref return False def __ne__(self, other): if isinstance(other, WeakMethod): if not self._alive or not other._alive: return self is not other return weakref.ref.__ne__(self, other) or self._func_ref != other._func_ref return True __hash__ = weakref.ref.__hash__ gpiozero-1.4.1/gpiozero/output_devices.py0000644000175000017500000013717313243115414020517 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) from threading import Lock from itertools import repeat, cycle, chain from .exc import OutputDeviceBadValue, GPIOPinMissing from .devices import GPIODevice, Device, CompositeDevice from .mixins import SourceMixin from .threads import GPIOThread class OutputDevice(SourceMixin, GPIODevice): """ Represents a generic GPIO output device. This class extends :class:`GPIODevice` to add facilities common to GPIO output devices: an :meth:`on` method to switch the device on, a corresponding :meth:`off` method, and a :meth:`toggle` method. :param int pin: The GPIO pin (in BCM numbering) that the device is connected to. If 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 HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the :meth:`off` method always does the opposite). :param bool initial_value: If ``False`` (the default), the device will be off initially. If ``None``, the device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__( self, pin=None, active_high=True, initial_value=False, pin_factory=None): super(OutputDevice, self).__init__(pin, pin_factory=pin_factory) self._lock = Lock() self.active_high = active_high if initial_value is None: self.pin.function = 'output' else: self.pin.output_with_state(self._value_to_state(initial_value)) def _value_to_state(self, value): return bool(self._active_state if value else self._inactive_state) def _write(self, value): try: self.pin.state = self._value_to_state(value) except AttributeError: self._check_open() raise def on(self): """ Turns the device on. """ self._write(True) def off(self): """ Turns the device off. """ self._write(False) def toggle(self): """ Reverse the state of the device. If it's on, turn it off; if it's off, turn it on. """ with self._lock: if self.is_active: self.off() else: self.on() @property def value(self): """ Returns ``True`` if the device is currently active and ``False`` otherwise. Setting this property changes the state of the device. """ return super(OutputDevice, self).value @value.setter def value(self, value): self._write(value) @property def active_high(self): """ When ``True``, the :attr:`value` property is ``True`` when the device's :attr:`pin` is high. When ``False`` the :attr:`value` property is ``True`` when the device's pin is low (i.e. the value is inverted). This property can be set after construction; be warned that changing it will invert :attr:`value` (i.e. changing this property doesn't change the device's pin state - it just changes how that state is interpreted). """ return self._active_state @active_high.setter def active_high(self, value): self._active_state = True if value else False self._inactive_state = False if value else True def __repr__(self): try: return '' % ( self.__class__.__name__, self.pin, self.active_high, self.is_active) except: return super(OutputDevice, self).__repr__() class DigitalOutputDevice(OutputDevice): """ Represents a generic output device with typical on/off behaviour. This class extends :class:`OutputDevice` with a :meth:`blink` method which uses an optional background thread to handle toggling the device state without further interaction. """ def __init__( self, pin=None, active_high=True, initial_value=False, pin_factory=None): self._blink_thread = None self._controller = None super(DigitalOutputDevice, self).__init__( pin, active_high, initial_value, pin_factory=pin_factory ) @property def value(self): return self._read() @value.setter def value(self, value): self._stop_blink() self._write(value) def close(self): self._stop_blink() super(DigitalOutputDevice, self).close() def on(self): self._stop_blink() self._write(True) def off(self): self._stop_blink() self._write(False) def blink(self, on_time=1, off_time=1, n=None, background=True): """ Make the device turn on and off repeatedly. :param float on_time: Number of seconds on. Defaults to 1 second. :param float off_time: Number of seconds off. Defaults to 1 second. :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, args=(on_time, off_time, n) ) self._blink_thread.start() if not background: self._blink_thread.join() self._blink_thread = None def _stop_blink(self): if getattr(self, '_controller', None): self._controller._stop_blink(self) self._controller = None if getattr(self, '_blink_thread', None): self._blink_thread.stop() self._blink_thread = None def _blink_device(self, on_time, off_time, n): iterable = repeat(0) if n is None else repeat(0, n) for _ in iterable: self._write(True) if self._blink_thread.stopping.wait(on_time): break self._write(False) if self._blink_thread.stopping.wait(off_time): break class LED(DigitalOutputDevice): """ Extends :class:`DigitalOutputDevice` and represents a light emitting diode (LED). Connect the cathode (short leg, flat side) of the LED to a ground pin; connect the anode (longer leg) to a limiting resistor; connect the other side of the limiting resistor to a GPIO pin (the limiting resistor can be placed either side of the LED). The following example will light the LED:: from gpiozero import LED led = LED(17) led.on() :param int pin: The GPIO pin which the LED is attached to. See :ref:`pin-numbering` for valid pin numbers. :param bool active_high: If ``True`` (the default), the LED will operate normally with the circuit described above. If ``False`` you should wire the cathode to the GPIO pin, and the anode to a 3V3 pin (via a limiting resistor). :param bool initial_value: If ``False`` (the default), the LED will be off initially. If ``None``, the LED will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the LED will be switched on initially. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ pass LED.is_lit = LED.is_active class Buzzer(DigitalOutputDevice): """ Extends :class:`DigitalOutputDevice` and represents a digital buzzer component. Connect the cathode (negative pin) of the buzzer to a ground pin; connect the other side to any GPIO pin. The following example will sound the buzzer:: from gpiozero import Buzzer bz = Buzzer(3) bz.on() :param int pin: The GPIO pin which the buzzer is attached to. See :ref:`pin-numbering` for valid pin numbers. :param bool active_high: If ``True`` (the default), the buzzer will operate normally with the circuit described above. If ``False`` you should wire the cathode to the GPIO pin, and the anode to a 3V3 pin. :param bool initial_value: If ``False`` (the default), the buzzer will be silent initially. If ``None``, the buzzer will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the buzzer will be switched on initially. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ pass Buzzer.beep = Buzzer.blink class PWMOutputDevice(OutputDevice): """ Generic output device configured for pulse-width modulation (PWM). :param int pin: The GPIO pin which the device is attached to. See :ref:`pin-numbering` for valid pin numbers. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set the GPIO to HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the :meth:`off` method always does the opposite). :param float initial_value: If ``0`` (the default), the device's duty cycle will be 0 initially. Other values between 0 and 1 can be specified as an initial duty cycle. Note that ``None`` cannot be specified (unlike the parent class) as there is no way to tell PWM not to alter the state of the pin. :param int frequency: The frequency (in Hz) of pulses emitted to drive the device. Defaults to 100Hz. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__( self, pin=None, active_high=True, initial_value=0, frequency=100, pin_factory=None): self._blink_thread = None self._controller = None if not 0 <= initial_value <= 1: raise OutputDeviceBadValue("initial_value must be between 0 and 1") super(PWMOutputDevice, self).__init__( pin, active_high, initial_value=None, pin_factory=pin_factory ) try: # XXX need a way of setting these together self.pin.frequency = frequency self.value = initial_value except: self.close() raise def close(self): try: self._stop_blink() except AttributeError: pass try: self.pin.frequency = None except AttributeError: # If the pin's already None, ignore the exception pass super(PWMOutputDevice, self).close() def _state_to_value(self, state): return float(state if self.active_high else 1 - state) def _value_to_state(self, value): return float(value if self.active_high else 1 - value) def _write(self, value): if not 0 <= value <= 1: raise OutputDeviceBadValue("PWM value must be between 0 and 1") super(PWMOutputDevice, self)._write(value) @property def value(self): """ The duty cycle of the PWM device. 0.0 is off, 1.0 is fully on. Values in between may be specified for varying levels of power in the device. """ return self._read() @value.setter def value(self, value): self._stop_blink() self._write(value) def on(self): self._stop_blink() self._write(1) def off(self): self._stop_blink() self._write(0) def toggle(self): """ Toggle the state of the device. If the device is currently off (:attr:`value` is 0.0), this changes it to "fully" on (:attr:`value` is 1.0). If the device has a duty cycle (:attr:`value`) of 0.1, this will toggle it to 0.9, and so on. """ self._stop_blink() self.value = 1 - self.value @property def is_active(self): """ Returns ``True`` if the device is currently active (:attr:`value` is non-zero) and ``False`` otherwise. """ return self.value != 0 @property def frequency(self): """ The frequency of the pulses used with the PWM device, in Hz. The default is 100Hz. """ return self.pin.frequency @frequency.setter def frequency(self, value): self.pin.frequency = value def blink( self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0, n=None, background=True): """ Make the device turn on and off repeatedly. :param float on_time: Number of seconds on. Defaults to 1 second. :param float off_time: Number of seconds off. Defaults to 1 second. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 0. :param float fade_out_time: Number of seconds to spend fading out. Defaults to 0. :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, args=(on_time, off_time, fade_in_time, fade_out_time, n) ) self._blink_thread.start() if not background: self._blink_thread.join() self._blink_thread = None def pulse(self, fade_in_time=1, fade_out_time=1, n=None, background=True): """ Make the device fade in and out repeatedly. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 1. :param float fade_out_time: Number of seconds to spend fading out. Defaults to 1. :param int n: Number of times to pulse; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue pulsing and return immediately. If ``False``, only return when the pulse is finished (warning: the default value of *n* will result in this method never returning). """ on_time = off_time = 0 self.blink( on_time, off_time, fade_in_time, fade_out_time, n, background ) def _stop_blink(self): if self._controller: self._controller._stop_blink(self) self._controller = None if self._blink_thread: self._blink_thread.stop() self._blink_thread = None def _blink_device( self, on_time, off_time, fade_in_time, fade_out_time, n, fps=25): sequence = [] if fade_in_time > 0: sequence += [ (i * (1 / fps) / fade_in_time, 1 / fps) for i in range(int(fps * fade_in_time)) ] sequence.append((1, on_time)) if fade_out_time > 0: sequence += [ (1 - (i * (1 / fps) / fade_out_time), 1 / fps) for i in range(int(fps * fade_out_time)) ] sequence.append((0, off_time)) sequence = ( cycle(sequence) if n is None else chain.from_iterable(repeat(sequence, n)) ) for value, delay in sequence: self._write(value) if self._blink_thread.stopping.wait(delay): break class PWMLED(PWMOutputDevice): """ Extends :class:`PWMOutputDevice` and represents a light emitting diode (LED) with variable brightness. A typical configuration of such a device is to connect a GPIO pin to the anode (long leg) of the LED, and the cathode (short leg) to ground, with an optional resistor to prevent the LED from burning out. :param int pin: The GPIO pin which the LED is attached to. See :ref:`pin-numbering` for valid pin numbers. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set the GPIO to HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the :meth:`off` method always does the opposite). :param float initial_value: If ``0`` (the default), the LED will be off initially. Other values between 0 and 1 can be specified as an initial brightness for the LED. Note that ``None`` cannot be specified (unlike the parent class) as there is no way to tell PWM not to alter the state of the pin. :param int frequency: The frequency (in Hz) of pulses emitted to drive the LED. Defaults to 100Hz. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ pass PWMLED.is_lit = PWMLED.is_active def _led_property(index, doc=None): def getter(self): return self._leds[index].value def setter(self, value): self._stop_blink() self._leds[index].value = value return property(getter, setter, doc=doc) class RGBLED(SourceMixin, Device): """ Extends :class:`Device` and represents a full color LED component (composed of red, green, and blue LEDs). Connect the common cathode (longest leg) to a ground pin; connect each of the other legs (representing the red, green, and blue anodes) to any GPIO pins. You can either use three limiting resistors (one per anode) or a single limiting resistor on the cathode. The following code will make the LED purple:: from gpiozero import RGBLED led = RGBLED(2, 3, 4) led.color = (1, 0, 1) :param int red: The GPIO pin that controls the red component of the RGB LED. :param int green: The GPIO pin that controls the green component of the RGB LED. :param int blue: The GPIO pin that controls the blue component of the RGB LED. :param bool active_high: Set to ``True`` (the default) for common cathode RGB LEDs. If you are using a common anode RGB LED, set this to ``False``. :param tuple initial_value: The initial color for the RGB LED. Defaults to black ``(0, 0, 0)``. :param bool pwm: If ``True`` (the default), construct :class:`PWMLED` instances for each component of the RGBLED. If ``False``, construct regular :class:`LED` instances, which prevents smooth color graduations. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__( self, red=None, green=None, blue=None, active_high=True, initial_value=(0, 0, 0), pwm=True, pin_factory=None): self._leds = () self._blink_thread = None if not all(p is not None for p in [red, green, blue]): raise GPIOPinMissing('red, green, and blue pins must be provided') LEDClass = PWMLED if pwm else LED super(RGBLED, self).__init__(pin_factory=pin_factory) self._leds = tuple( LEDClass(pin, active_high, pin_factory=pin_factory) for pin in (red, green, blue) ) self.value = initial_value red = _led_property(0) green = _led_property(1) blue = _led_property(2) def close(self): if getattr(self, '_leds', None): self._stop_blink() for led in self._leds: led.close() self._leds = () super(RGBLED, self).close() @property def closed(self): return len(self._leds) == 0 @property def value(self): """ Represents the color of the LED as an RGB 3-tuple of ``(red, green, blue)`` where each value is between 0 and 1 if ``pwm`` was ``True`` when the class was constructed (and only 0 or 1 if not). For example, purple would be ``(1, 0, 1)`` and yellow would be ``(1, 1, 0)``, while orange would be ``(1, 0.5, 0)``. """ return (self.red, self.green, self.blue) @value.setter def value(self, value): for component in value: if not 0 <= component <= 1: raise OutputDeviceBadValue('each RGB color component must be between 0 and 1') if isinstance(self._leds[0], LED): if component not in (0, 1): raise OutputDeviceBadValue('each RGB color component must be 0 or 1 with non-PWM RGBLEDs') self._stop_blink() self.red, self.green, self.blue = value @property def is_active(self): """ Returns ``True`` if the LED is currently active (not black) and ``False`` otherwise. """ return self.value != (0, 0, 0) is_lit = is_active color = value def on(self): """ Turn the LED on. This equivalent to setting the LED color to white ``(1, 1, 1)``. """ self.value = (1, 1, 1) def off(self): """ Turn the LED off. This is equivalent to setting the LED color to black ``(0, 0, 0)``. """ self.value = (0, 0, 0) def toggle(self): """ Toggle the state of the device. If the device is currently off (:attr:`value` is ``(0, 0, 0)``), this changes it to "fully" on (:attr:`value` is ``(1, 1, 1)``). If the device has a specific color, this method inverts the color. """ r, g, b = self.value self.value = (1 - r, 1 - g, 1 - b) def blink( self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0, on_color=(1, 1, 1), off_color=(0, 0, 0), n=None, background=True): """ Make the device turn on and off repeatedly. :param float on_time: Number of seconds on. Defaults to 1 second. :param float off_time: Number of seconds off. Defaults to 1 second. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 0. Must be 0 if ``pwm`` was ``False`` when the class was constructed (:exc:`ValueError` will be raised if not). :param float fade_out_time: Number of seconds to spend fading out. Defaults to 0. Must be 0 if ``pwm`` was ``False`` when the class was constructed (:exc:`ValueError` will be raised if not). :param tuple on_color: The color to use when the LED is "on". Defaults to white. :param tuple off_color: The color to use when the LED is "off". Defaults to black. :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ if isinstance(self._leds[0], LED): if fade_in_time: raise ValueError('fade_in_time must be 0 with non-PWM RGBLEDs') if fade_out_time: raise ValueError('fade_out_time must be 0 with non-PWM RGBLEDs') self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, args=( on_time, off_time, fade_in_time, fade_out_time, on_color, off_color, n ) ) self._blink_thread.start() if not background: self._blink_thread.join() self._blink_thread = None def pulse( self, fade_in_time=1, fade_out_time=1, on_color=(1, 1, 1), off_color=(0, 0, 0), n=None, background=True): """ Make the device fade in and out repeatedly. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 1. :param float fade_out_time: Number of seconds to spend fading out. Defaults to 1. :param tuple on_color: The color to use when the LED is "on". Defaults to white. :param tuple off_color: The color to use when the LED is "off". Defaults to black. :param int n: Number of times to pulse; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue pulsing and return immediately. If ``False``, only return when the pulse is finished (warning: the default value of *n* will result in this method never returning). """ on_time = off_time = 0 self.blink( on_time, off_time, fade_in_time, fade_out_time, on_color, off_color, n, background ) def _stop_blink(self, led=None): # If this is called with a single led, we stop all blinking anyway if self._blink_thread: self._blink_thread.stop() self._blink_thread = None def _blink_device( self, on_time, off_time, fade_in_time, fade_out_time, on_color, off_color, n, fps=25): # Define some simple lambdas to perform linear interpolation between # off_color and on_color lerp = lambda t, fade_in: tuple( (1 - t) * off + t * on if fade_in else (1 - t) * on + t * off for off, on in zip(off_color, on_color) ) sequence = [] if fade_in_time > 0: sequence += [ (lerp(i * (1 / fps) / fade_in_time, True), 1 / fps) for i in range(int(fps * fade_in_time)) ] sequence.append((on_color, on_time)) if fade_out_time > 0: sequence += [ (lerp(i * (1 / fps) / fade_out_time, False), 1 / fps) for i in range(int(fps * fade_out_time)) ] sequence.append((off_color, off_time)) sequence = ( cycle(sequence) if n is None else chain.from_iterable(repeat(sequence, n)) ) for l in self._leds: l._controller = self for value, delay in sequence: for l, v in zip(self._leds, value): l._write(v) if self._blink_thread.stopping.wait(delay): break class Motor(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` and represents a generic motor connected to a bi-directional motor driver circuit (i.e. an `H-bridge`_). Attach an `H-bridge`_ motor controller to your Pi; connect a power source (e.g. a battery pack or the 5V pin) to the controller; connect the outputs of the controller board to the two terminals of the motor; connect the inputs of the controller board to two GPIO pins. .. _H-bridge: https://en.wikipedia.org/wiki/H_bridge The following code will make the motor turn "forwards":: from gpiozero import Motor motor = Motor(17, 18) motor.forward() :param int forward: The GPIO pin that the forward input of the motor driver chip is connected to. :param int backward: The GPIO pin that the backward input of the motor driver chip is connected to. :param bool pwm: If ``True`` (the default), construct :class:`PWMOutputDevice` instances for the motor controller pins, allowing both direction and variable speed control. If ``False``, construct :class:`DigitalOutputDevice` instances, allowing only direction control. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__(self, forward=None, backward=None, pwm=True, pin_factory=None): if not all(p is not None for p in [forward, backward]): raise GPIOPinMissing( 'forward and backward pins must be provided' ) PinClass = PWMOutputDevice if pwm else DigitalOutputDevice super(Motor, self).__init__( forward_device=PinClass(forward, pin_factory=pin_factory), backward_device=PinClass(backward, pin_factory=pin_factory), _order=('forward_device', 'backward_device'), pin_factory=pin_factory ) @property def value(self): """ Represents the speed of the motor as a floating point value between -1 (full speed backward) and 1 (full speed forward), with 0 representing stopped. """ return self.forward_device.value - self.backward_device.value @value.setter def value(self, value): if not -1 <= value <= 1: raise OutputDeviceBadValue("Motor value must be between -1 and 1") if value > 0: try: self.forward(value) except ValueError as e: raise OutputDeviceBadValue(e) elif value < 0: try: self.backward(-value) except ValueError as e: raise OutputDeviceBadValue(e) else: self.stop() @property def is_active(self): """ Returns ``True`` if the motor is currently running and ``False`` otherwise. """ return self.value != 0 def forward(self, speed=1): """ Drive the motor forwards. :param float speed: The speed at which the motor should turn. Can be any value between 0 (stopped) and the default 1 (maximum speed) if ``pwm`` was ``True`` when the class was constructed (and only 0 or 1 if not). """ if not 0 <= speed <= 1: raise ValueError('forward speed must be between 0 and 1') if isinstance(self.forward_device, DigitalOutputDevice): if speed not in (0, 1): raise ValueError('forward speed must be 0 or 1 with non-PWM Motors') self.backward_device.off() self.forward_device.value = speed def backward(self, speed=1): """ Drive the motor backwards. :param float speed: The speed at which the motor should turn. Can be any value between 0 (stopped) and the default 1 (maximum speed) if ``pwm`` was ``True`` when the class was constructed (and only 0 or 1 if not). """ if not 0 <= speed <= 1: raise ValueError('backward speed must be between 0 and 1') if isinstance(self.backward_device, DigitalOutputDevice): if speed not in (0, 1): raise ValueError('backward speed must be 0 or 1 with non-PWM Motors') self.forward_device.off() self.backward_device.value = speed def reverse(self): """ Reverse the current direction of the motor. If the motor is currently idle this does nothing. Otherwise, the motor's direction will be reversed at the current speed. """ self.value = -self.value def stop(self): """ Stop the motor. """ self.forward_device.off() self.backward_device.off() class PhaseEnableMotor(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` and represents a generic motor connected to a Phase/Enable motor driver circuit; the phase of the driver controls whether the motor turns forwards or backwards, while enable controls the speed with PWM. The following code will make the motor turn "forwards":: from gpiozero import PhaseEnableMotor motor = PhaseEnableMotor(12, 5) motor.forward() :param int phase: The GPIO pin that the phase (direction) input of the motor driver chip is connected to. :param int enable: The GPIO pin that the enable (speed) input of the motor driver chip is connected to. :param bool pwm: If ``True`` (the default), construct :class:`PWMOutputDevice` instances for the motor controller pins, allowing both direction and variable speed control. If ``False``, construct :class:`DigitalOutputDevice` instances, allowing only direction control. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__(self, phase=None, enable=None, pwm=True, pin_factory=None): if not all([phase, enable]): raise GPIOPinMissing('phase and enable pins must be provided') PinClass = PWMOutputDevice if pwm else DigitalOutputDevice super(PhaseEnableMotor, self).__init__( phase_device=OutputDevice(phase, pin_factory=pin_factory), enable_device=PinClass(enable, pin_factory=pin_factory), _order=('phase_device', 'enable_device'), pin_factory=pin_factory ) @property def value(self): """ Represents the speed of the motor as a floating point value between -1 (full speed backward) and 1 (full speed forward). """ return -self.enable_device.value if self.phase_device.is_active else self.enable_device.value @value.setter def value(self, value): if not -1 <= value <= 1: raise OutputDeviceBadValue("Motor value must be between -1 and 1") if value > 0: self.forward(value) elif value < 0: self.backward(-value) else: self.stop() @property def is_active(self): """ Returns ``True`` if the motor is currently running and ``False`` otherwise. """ return self.value != 0 def forward(self, speed=1): """ Drive the motor forwards. :param float speed: The speed at which the motor should turn. Can be any value between 0 (stopped) and the default 1 (maximum speed). """ if isinstance(self.enable_device, DigitalOutputDevice): if speed not in (0, 1): raise ValueError('forward speed must be 0 or 1 with non-PWM Motors') self.enable_device.off() self.phase_device.off() self.enable_device.value = speed def backward(self, speed=1): """ Drive the motor backwards. :param float speed: The speed at which the motor should turn. Can be any value between 0 (stopped) and the default 1 (maximum speed). """ if isinstance(self.enable_device, DigitalOutputDevice): if speed not in (0, 1): raise ValueError('backward speed must be 0 or 1 with non-PWM Motors') self.enable_device.off() self.phase_device.on() self.enable_device.value = speed def reverse(self): """ Reverse the current direction of the motor. If the motor is currently idle this does nothing. Otherwise, the motor's direction will be reversed at the current speed. """ self.value = -self.value def stop(self): """ Stop the motor. """ self.enable_device.off() class Servo(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` and represents a PWM-controlled servo motor connected to a GPIO pin. Connect a power source (e.g. a battery pack or the 5V pin) to the power cable of the servo (this is typically colored red); connect the ground cable of the servo (typically colored black or brown) to the negative of your battery pack, or a GND pin; connect the final cable (typically colored white or orange) to the GPIO pin you wish to use for controlling the servo. The following code will make the servo move between its minimum, maximum, and mid-point positions with a pause between each:: from gpiozero import Servo from time import sleep servo = Servo(17) while True: servo.min() sleep(1) servo.mid() sleep(1) servo.max() sleep(1) :param int pin: The GPIO pin which the device is attached to. See :ref:`pin-numbering` for valid pin numbers. :param float initial_value: If ``0`` (the default), the device's mid-point will be set initially. Other values between -1 and +1 can be specified as an initial position. ``None`` means to start the servo un-controlled (see :attr:`value`). :param float min_pulse_width: The pulse width corresponding to the servo's minimum position. This defaults to 1ms. :param float max_pulse_width: The pulse width corresponding to the servo's maximum position. This defaults to 2ms. :param float frame_width: The length of time between servo control pulses measured in seconds. This defaults to 20ms which is a common value for servos. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__( self, pin=None, initial_value=0.0, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000, pin_factory=None): if min_pulse_width >= max_pulse_width: raise ValueError('min_pulse_width must be less than max_pulse_width') if max_pulse_width >= frame_width: raise ValueError('max_pulse_width must be less than frame_width') self._frame_width = frame_width self._min_dc = min_pulse_width / frame_width self._dc_range = (max_pulse_width - min_pulse_width) / frame_width self._min_value = -1 self._value_range = 2 super(Servo, self).__init__( pwm_device=PWMOutputDevice( pin, frequency=int(1 / frame_width), pin_factory=pin_factory ), pin_factory=pin_factory ) try: self.value = initial_value except: self.close() raise @property def frame_width(self): """ The time between control pulses, measured in seconds. """ return self._frame_width @property def min_pulse_width(self): """ The control pulse width corresponding to the servo's minimum position, measured in seconds. """ return self._min_dc * self.frame_width @property def max_pulse_width(self): """ The control pulse width corresponding to the servo's maximum position, measured in seconds. """ return (self._dc_range * self.frame_width) + self.min_pulse_width @property def pulse_width(self): """ Returns the current pulse width controlling the servo. """ if self.pwm_device.pin.frequency is None: return None else: return self.pwm_device.pin.state * self.frame_width def min(self): """ Set the servo to its minimum position. """ self.value = -1 def mid(self): """ Set the servo to its mid-point position. """ self.value = 0 def max(self): """ Set the servo to its maximum position. """ self.value = 1 def detach(self): """ Temporarily disable control of the servo. This is equivalent to setting :attr:`value` to ``None``. """ self.value = None def _get_value(self): if self.pwm_device.pin.frequency is None: return None else: return ( ((self.pwm_device.pin.state - self._min_dc) / self._dc_range) * self._value_range + self._min_value) @property def value(self): """ Represents the position of the servo as a value between -1 (the minimum position) and +1 (the maximum position). This can also be the special value ``None`` indicating that the servo is currently "uncontrolled", i.e. that no control signal is being sent. Typically this means the servo's position remains unchanged, but that it can be moved by hand. """ result = self._get_value() if result is None: return result else: # NOTE: This round() only exists to ensure we don't confuse people # by returning 2.220446049250313e-16 as the default initial value # instead of 0. The reason _get_value and _set_value are split # out is for descendents that require the un-rounded values for # accuracy return round(result, 14) @value.setter def value(self, value): if value is None: self.pwm_device.pin.frequency = None elif -1 <= value <= 1: self.pwm_device.pin.frequency = int(1 / self.frame_width) self.pwm_device.pin.state = ( self._min_dc + self._dc_range * ((value - self._min_value) / self._value_range) ) else: raise OutputDeviceBadValue( "Servo value must be between -1 and 1, or None") @property def is_active(self): return self.value is not None class AngularServo(Servo): """ Extends :class:`Servo` and represents a rotational PWM-controlled servo motor which can be set to particular angles (assuming valid minimum and maximum angles are provided to the constructor). Connect a power source (e.g. a battery pack or the 5V pin) to the power cable of the servo (this is typically colored red); connect the ground cable of the servo (typically colored black or brown) to the negative of your battery pack, or a GND pin; connect the final cable (typically colored white or orange) to the GPIO pin you wish to use for controlling the servo. Next, calibrate the angles that the servo can rotate to. In an interactive Python session, construct a :class:`Servo` instance. The servo should move to its mid-point by default. Set the servo to its minimum value, and measure the angle from the mid-point. Set the servo to its maximum value, and again measure the angle:: >>> from gpiozero import Servo >>> s = Servo(17) >>> s.min() # measure the angle >>> s.max() # measure the angle You should now be able to construct an :class:`AngularServo` instance with the correct bounds:: >>> from gpiozero import AngularServo >>> s = AngularServo(17, min_angle=-42, max_angle=44) >>> s.angle = 0.0 >>> s.angle 0.0 >>> s.angle = 15 >>> s.angle 15.0 .. note:: You can set *min_angle* greater than *max_angle* if you wish to reverse the sense of the angles (e.g. ``min_angle=45, max_angle=-45``). This can be useful with servos that rotate in the opposite direction to your expectations of minimum and maximum. :param int pin: The GPIO pin which the device is attached to. See :ref:`pin-numbering` for valid pin numbers. :param float initial_angle: Sets the servo's initial angle to the specified value. The default is 0. The value specified must be between *min_angle* and *max_angle* inclusive. ``None`` means to start the servo un-controlled (see :attr:`value`). :param float min_angle: Sets the minimum angle that the servo can rotate to. This defaults to -90, but should be set to whatever you measure from your servo during calibration. :param float max_angle: Sets the maximum angle that the servo can rotate to. This defaults to 90, but should be set to whatever you measure from your servo during calibration. :param float min_pulse_width: The pulse width corresponding to the servo's minimum position. This defaults to 1ms. :param float max_pulse_width: The pulse width corresponding to the servo's maximum position. This defaults to 2ms. :param float frame_width: The length of time between servo control pulses measured in seconds. This defaults to 20ms which is a common value for servos. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__( self, pin=None, initial_angle=0.0, min_angle=-90, max_angle=90, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000, pin_factory=None): self._min_angle = min_angle self._angular_range = max_angle - min_angle if initial_angle is None: initial_value = None elif ((min_angle <= initial_angle <= max_angle) or (max_angle <= initial_angle <= min_angle)): initial_value = 2 * ((initial_angle - min_angle) / self._angular_range) - 1 else: raise OutputDeviceBadValue( "AngularServo angle must be between %s and %s, or None" % (min_angle, max_angle)) super(AngularServo, self).__init__( pin, initial_value, min_pulse_width, max_pulse_width, frame_width, pin_factory=pin_factory ) @property def min_angle(self): """ The minimum angle that the servo will rotate to when :meth:`min` is called. """ return self._min_angle @property def max_angle(self): """ The maximum angle that the servo will rotate to when :meth:`max` is called. """ return self._min_angle + self._angular_range @property def angle(self): """ The position of the servo as an angle measured in degrees. This will only be accurate if *min_angle* and *max_angle* have been set appropriately in the constructor. This can also be the special value ``None`` indicating that the servo is currently "uncontrolled", i.e. that no control signal is being sent. Typically this means the servo's position remains unchanged, but that it can be moved by hand. """ result = self._get_value() if result is None: return None else: # NOTE: Why round(n, 12) here instead of 14? Angle ranges can be # much larger than -1..1 so we need a little more rounding to # smooth off the rough corners! return round( self._angular_range * ((result - self._min_value) / self._value_range) + self._min_angle, 12) @angle.setter def angle(self, angle): if angle is None: self.value = None elif ((self.min_angle <= angle <= self.max_angle) or (self.max_angle <= angle <= self.min_angle)): self.value = ( self._value_range * ((angle - self._min_angle) / self._angular_range) + self._min_value) else: raise OutputDeviceBadValue( "AngularServo angle must be between %s and %s, or None" % (self.min_angle, self.max_angle)) gpiozero-1.4.1/gpiozero/devices.py0000644000175000017500000004407213243115414017072 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) nstr = str str = type('') import os import atexit import weakref import warnings from collections import namedtuple from itertools import chain from types import FunctionType from threading import Lock import pkg_resources from .pins import Pin from .threads import _threads_shutdown from .mixins import ( ValuesMixin, SharedMixin, ) from .exc import ( BadPinFactory, DeviceClosed, CompositeDeviceBadName, CompositeDeviceBadOrder, CompositeDeviceBadDevice, GPIOPinMissing, GPIOPinInUse, GPIODeviceClosed, PinFactoryFallback, ) from .compat import frozendict class GPIOMeta(type): # NOTE Yes, this is a metaclass. Don't be scared - it's a simple one. def __new__(mcls, name, bases, cls_dict): # Construct the class as normal cls = super(GPIOMeta, mcls).__new__(mcls, name, bases, cls_dict) # If there's a method in the class which has no docstring, search # the base classes recursively for a docstring to copy for attr_name, attr in cls_dict.items(): if isinstance(attr, FunctionType) and not attr.__doc__: for base_cls in cls.__mro__: if hasattr(base_cls, attr_name): base_fn = getattr(base_cls, attr_name) if base_fn.__doc__: attr.__doc__ = base_fn.__doc__ break return cls def __call__(cls, *args, **kwargs): # Make sure cls has GPIOBase somewhere in its ancestry (otherwise # setting __attrs__ below will be rather pointless) assert issubclass(cls, GPIOBase) if issubclass(cls, SharedMixin): # If SharedMixin appears in the class' ancestry, convert the # constructor arguments to a key and check whether an instance # already exists. Only construct the instance if the key's new. key = cls._shared_key(*args, **kwargs) try: self = cls._instances[key] self._refs += 1 except (KeyError, ReferenceError) as e: self = super(GPIOMeta, cls).__call__(*args, **kwargs) self._refs = 1 # Replace the close method with one that merely decrements # the refs counter and calls the original close method when # it reaches zero old_close = self.close def close(): self._refs = max(0, self._refs - 1) if not self._refs: try: old_close() finally: try: del cls._instances[key] except KeyError: # If the _refs go negative (too many closes) # just ignore the resulting KeyError here - # it's already gone pass self.close = close cls._instances[key] = weakref.proxy(self) else: # Construct the instance as normal self = super(GPIOMeta, cls).__call__(*args, **kwargs) # At this point __new__ and __init__ have all been run. We now fix the # set of attributes on the class by dir'ing the instance and creating a # frozenset of the result called __attrs__ (which is queried by # GPIOBase.__setattr__). An exception is made for SharedMixin devices # which can be constructed multiple times, returning the same instance if not issubclass(cls, SharedMixin) or self._refs == 1: self.__attrs__ = frozenset(dir(self)) return self # Cross-version compatible method of using a metaclass class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})): def __setattr__(self, name, value): # This overridden __setattr__ simply ensures that additional attributes # cannot be set on the class after construction (it manages this in # conjunction with the meta-class above). Traditionally, this is # managed with __slots__; however, this doesn't work with Python's # multiple inheritance system which we need to use in order to avoid # repeating the "source" and "values" property code in myriad places if hasattr(self, '__attrs__') and name not in self.__attrs__: raise AttributeError( "'%s' object has no attribute '%s'" % ( self.__class__.__name__, name)) return super(GPIOBase, self).__setattr__(name, value) def __del__(self): self.close() def close(self): """ Shut down the device and release all associated resources. This method can be called on an already closed device without raising an exception. This method is primarily intended for interactive use at the command line. It disables the device and releases its pin(s) for use by another device. You can attempt to do this simply by deleting an object, but unless you've cleaned up all references to the object this may not work (even if you've cleaned up all references, there's still no guarantee the garbage collector will actually delete the object at that point). By contrast, the close method provides a means of ensuring that the object is shut down. For example, if you have a breadboard with a buzzer connected to pin 16, but then wish to attach an LED instead: >>> from gpiozero import * >>> bz = Buzzer(16) >>> bz.on() >>> bz.off() >>> bz.close() >>> led = LED(16) >>> led.blink() :class:`Device` descendents can also be used as context managers using the :keyword:`with` statement. For example: >>> from gpiozero import * >>> with Buzzer(16) as bz: ... bz.on() ... >>> with LED(16) as led: ... led.on() ... """ # This is a placeholder which is simply here to ensure close() can be # safely called from subclasses without worrying whether super-class' # have it (which in turn is useful in conjunction with the SourceMixin # class). pass @property def closed(self): """ Returns ``True`` if the device is closed (see the :meth:`close` method). Once a device is closed you can no longer use any other methods or properties to control or query the device. """ raise NotImplementedError def _check_open(self): if self.closed: raise DeviceClosed( '%s is closed or uninitialized' % self.__class__.__name__) def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_tb): self.close() class Device(ValuesMixin, GPIOBase): """ Represents a single device of any type; GPIO-based, SPI-based, I2C-based, etc. This is the base class of the device hierarchy. It defines the basic services applicable to all devices (specifically the :attr:`is_active` property, the :attr:`value` property, and the :meth:`close` method). """ pin_factory = None # instance of a Factory sub-class def __init__(self, **kwargs): # Force pin_factory to be keyword-only, even in Python 2 pin_factory = kwargs.pop('pin_factory', None) if pin_factory is None: self.pin_factory = Device.pin_factory else: self.pin_factory = pin_factory if kwargs: raise TypeError("Device.__init__() got unexpected keyword " "argument '%s'" % kwargs.popitem()[0]) super(Device, self).__init__() def __repr__(self): return "" % (self.__class__.__name__) def _conflicts_with(self, other): """ Called by :meth:`Factory.reserve_pins` to test whether the *other* :class:`Device` using a common pin conflicts with this device's intent to use it. The default is ``True`` indicating that all devices conflict with common pins. Sub-classes may override this to permit more nuanced replies. """ return True @property def value(self): """ Returns a value representing the device's state. Frequently, this is a boolean value, or a number between 0 and 1 but some devices use larger ranges (e.g. -1 to +1) and composite devices usually use tuples to return the states of all their subordinate components. """ raise NotImplementedError @property def is_active(self): """ Returns ``True`` if the device is currently active and ``False`` otherwise. This property is usually derived from :attr:`value`. Unlike :attr:`value`, this is *always* a boolean. """ return bool(self.value) class CompositeDevice(Device): """ Extends :class:`Device`. Represents a device composed of multiple devices like simple HATs, H-bridge motor controllers, robots composed of multiple motors, etc. The constructor accepts subordinate devices as positional or keyword arguments. Positional arguments form unnamed devices accessed via the :attr:`all` attribute, while keyword arguments are added to the device as named (read-only) attributes. :param list _order: If specified, this is the order of named items specified by keyword arguments (to ensure that the :attr:`value` tuple is constructed with a specific order). All keyword arguments *must* be included in the collection. If omitted, an alphabetically sorted order will be selected for keyword arguments. """ def __init__(self, *args, **kwargs): self._all = () self._named = frozendict({}) self._namedtuple = None self._order = kwargs.pop('_order', None) pin_factory = kwargs.pop('pin_factory', None) try: if self._order is None: self._order = sorted(kwargs.keys()) else: for missing_name in set(kwargs.keys()) - set(self._order): raise CompositeDeviceBadOrder('%s missing from _order' % missing_name) self._order = tuple(self._order) for name in set(self._order) & set(dir(self)): raise CompositeDeviceBadName('%s is a reserved name' % name) for dev in chain(args, kwargs.values()): if not isinstance(dev, Device): raise CompositeDeviceBadDevice("%s doesn't inherit from Device" % dev) self._named = frozendict(kwargs) self._namedtuple = namedtuple('%sValue' % self.__class__.__name__, chain( ('device_%d' % i for i in range(len(args))), self._order)) except: for dev in chain(args, kwargs.values()): if isinstance(dev, Device): dev.close() raise self._all = args + tuple(kwargs[v] for v in self._order) super(CompositeDevice, self).__init__(pin_factory=pin_factory) def __getattr__(self, name): # if _named doesn't exist yet, pretend it's an empty dict if name == '_named': return frozendict({}) try: return self._named[name] except KeyError: raise AttributeError("no such attribute %s" % name) def __setattr__(self, name, value): # make named components read-only properties if name in self._named: raise AttributeError("can't set attribute %s" % name) return super(CompositeDevice, self).__setattr__(name, value) def __repr__(self): try: self._check_open() return "" % ( self.__class__.__name__, len(self), ','.join(self._order), len(self) - len(self._named) ) except DeviceClosed: return "" % (self.__class__.__name__) def __len__(self): return len(self._all) def __getitem__(self, index): return self._all[index] def __iter__(self): return iter(self._all) @property def all(self): # XXX Deprecate this in favour of using the instance as a container return self._all def close(self): if getattr(self, '_all', None): for device in self._all: if isinstance(device, Device): device.close() self._all = () @property def closed(self): return all(device.closed for device in self) @property def namedtuple(self): return self._namedtuple @property def value(self): return self.namedtuple(*(device.value for device in self)) @property def is_active(self): return any(self.value) class GPIODevice(Device): """ Extends :class:`Device`. Represents a generic GPIO device and provides the services common to all single-pin GPIO devices (like ensuring two GPIO devices do no share a :attr:`pin`). :param int pin: The GPIO pin (in BCM numbering) that the device is connected to. If 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, **kwargs): super(GPIODevice, self).__init__(**kwargs) # self._pin must be set before any possible exceptions can be raised # because it's accessed in __del__. However, it mustn't be given the # value of pin until we've verified that it isn't already allocated self._pin = None if pin is None: raise GPIOPinMissing('No pin given') # Check you can reserve *before* constructing the pin self.pin_factory.reserve_pins(self, pin) pin = self.pin_factory.pin(pin) self._pin = pin self._active_state = True self._inactive_state = False def _state_to_value(self, state): return bool(state == self._active_state) def _read(self): try: return self._state_to_value(self.pin.state) except (AttributeError, TypeError): self._check_open() raise def close(self): super(GPIODevice, self).close() if getattr(self, '_pin', None) is not None: self.pin_factory.release_pins(self, self._pin.number) self._pin.close() self._pin = None @property def closed(self): return self._pin is None def _check_open(self): try: super(GPIODevice, self)._check_open() except DeviceClosed as e: # For backwards compatibility; GPIODeviceClosed is deprecated raise GPIODeviceClosed(str(e)) @property def pin(self): """ The :class:`Pin` that the device is connected to. This will be ``None`` if the device has been closed (see the :meth:`close` method). When dealing with GPIO pins, query ``pin.number`` to discover the GPIO pin (in BCM numbering) that the device is connected to. """ return self._pin @property def value(self): return self._read() def __repr__(self): try: return "" % ( self.__class__.__name__, self.pin, self.is_active) except DeviceClosed: return "" % self.__class__.__name__ # Defined last to ensure Device is defined before attempting to load any pin # factory; pin factories want to load spi which in turn relies on devices (for # the soft-SPI implementation) def _default_pin_factory(name=os.getenv('GPIOZERO_PIN_FACTORY', None)): group = 'gpiozero_pin_factories' if name is None: # If no factory is explicitly specified, try various names in # "preferred" order. Note that in this case we only select from # gpiozero distribution so without explicitly specifying a name (via # the environment) it's impossible to auto-select a factory from # outside the base distribution # # We prefer RPi.GPIO here as it supports PWM, and all Pi revisions. If # no third-party libraries are available, however, we fall back to a # pure Python implementation which supports platforms like PyPy dist = pkg_resources.get_distribution('gpiozero') for name in ('rpigpio', 'rpio', 'pigpio', 'native'): try: return pkg_resources.load_entry_point(dist, group, name)() except Exception as e: warnings.warn( PinFactoryFallback( 'Falling back from %s: %s' % (name, str(e)))) raise BadPinFactory('Unable to load any default pin factory!') else: # Try with the name verbatim first. If that fails, attempt with the # lower-cased name (this ensures compatibility names work but we're # still case insensitive for all factories) for factory in pkg_resources.iter_entry_points(group, name): return factory.load()() for factory in pkg_resources.iter_entry_points(group, name.lower()): return factory.load()() raise BadPinFactory('Unable to find pin factory "%s"' % name) def _devices_shutdown(): if Device.pin_factory: with Device.pin_factory._res_lock: reserved_devices = { dev for ref_list in Device.pin_factory._reservations.values() for ref in ref_list for dev in (ref(),) if dev is not None } for dev in reserved_devices: dev.close() Device.pin_factory.close() Device.pin_factory = None def _shutdown(): _threads_shutdown() _devices_shutdown() Device.pin_factory = _default_pin_factory() atexit.register(_shutdown) gpiozero-1.4.1/gpiozero/boards.py0000644000175000017500000016160313243115414016722 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) try: from itertools import izip as zip except ImportError: pass from time import sleep from itertools import repeat, cycle, chain from threading import Lock from collections import OrderedDict, Counter from .exc import ( DeviceClosed, GPIOPinMissing, EnergenieSocketMissing, EnergenieBadSocket, OutputDeviceBadValue, ) from .input_devices import Button from .output_devices import ( OutputDevice, LED, PWMLED, RGBLED, Buzzer, Motor, PhaseEnableMotor, ) from .threads import GPIOThread from .devices import Device, CompositeDevice from .mixins import SharedMixin, SourceMixin, HoldMixin class CompositeOutputDevice(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` with :meth:`on`, :meth:`off`, and :meth:`toggle` methods for controlling subordinate output devices. Also extends :attr:`value` to be writeable. :param list _order: If specified, this is the order of named items specified by keyword arguments (to ensure that the :attr:`value` tuple is constructed with a specific order). All keyword arguments *must* be included in the collection. If omitted, an alphabetically sorted order will be selected for keyword arguments. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def on(self): """ Turn all the output devices on. """ for device in self: if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.on() def off(self): """ Turn all the output devices off. """ for device in self: if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.off() def toggle(self): """ Toggle all the output devices. For each device, if it's on, turn it off; if it's off, turn it on. """ for device in self: if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.toggle() @property def value(self): """ A tuple containing a value for each subordinate device. This property can also be set to update the state of all subordinate output devices. """ return super(CompositeOutputDevice, self).value @value.setter def value(self, value): for device, v in zip(self, value): if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.value = v # Simply ignore values for non-output devices class ButtonBoard(HoldMixin, CompositeDevice): """ Extends :class:`CompositeDevice` and represents a generic button board or collection of buttons. :param int \*pins: Specify the GPIO pins that the buttons of the board are attached to. You can designate as many pins as necessary. :param bool pull_up: If ``True`` (the default), the GPIO pins will be pulled high by default. In this case, connect the other side of the buttons to ground. If ``False``, the GPIO pins will be pulled low by default. In this case, connect the other side of the buttons to 3V3. This parameter can only be specified as a keyword parameter. :param float bounce_time: If ``None`` (the default), no software bounce compensation will be performed. Otherwise, this is the length of time (in seconds) that the buttons will ignore changes in state after an initial change. This parameter can only be specified as a keyword parameter. :param float hold_time: The length of time (in seconds) to wait after any button is pushed, until executing the :attr:`when_held` handler. Defaults to ``1``. This parameter can only be specified as a keyword parameter. :param bool hold_repeat: If ``True``, the :attr:`when_held` handler will be repeatedly executed as long as any buttons remain held, every *hold_time* seconds. If ``False`` (the default) the :attr:`when_held` handler will be only be executed once per hold. This parameter can only be specified as a keyword parameter. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). :param \*\*named_pins: Specify GPIO pins that buttons of the board are attached to, associating each button with a property name. You can designate as many pins as necessary and use any names, provided they're not already in use by something else. """ def __init__(self, *args, **kwargs): pull_up = kwargs.pop('pull_up', True) bounce_time = kwargs.pop('bounce_time', None) hold_time = kwargs.pop('hold_time', 1) hold_repeat = kwargs.pop('hold_repeat', False) pin_factory = kwargs.pop('pin_factory', None) order = kwargs.pop('_order', None) super(ButtonBoard, self).__init__( *( Button(pin, pull_up, bounce_time, hold_time, hold_repeat) for pin in args ), _order=order, pin_factory=pin_factory, **{ name: Button(pin, pull_up, bounce_time, hold_time, hold_repeat) for name, pin in kwargs.items() }) def get_new_handler(device): def fire_both_events(): device._fire_events() self._fire_events() return fire_both_events for button in self: button.pin.when_changed = get_new_handler(button) self._when_changed = None self._last_value = None # Call _fire_events once to set initial state of events self._fire_events() self.hold_time = hold_time self.hold_repeat = hold_repeat @property def pull_up(self): """ If ``True``, the device uses a pull-up resistor to set the GPIO pin "high" by default. """ return self[0].pull_up @property def when_changed(self): return self._when_changed @when_changed.setter def when_changed(self, value): self._when_changed = self._wrap_callback(value) def _fire_changed(self): if self.when_changed: self.when_changed() def _fire_events(self): super(ButtonBoard, self)._fire_events() old_value = self._last_value new_value = self._last_value = self.value if old_value is None: # Initial "indeterminate" value; don't do anything pass elif old_value != new_value: self._fire_changed() ButtonBoard.is_pressed = ButtonBoard.is_active ButtonBoard.pressed_time = ButtonBoard.active_time ButtonBoard.when_pressed = ButtonBoard.when_activated ButtonBoard.when_released = ButtonBoard.when_deactivated ButtonBoard.wait_for_press = ButtonBoard.wait_for_active ButtonBoard.wait_for_release = ButtonBoard.wait_for_inactive class LEDCollection(CompositeOutputDevice): """ Extends :class:`CompositeOutputDevice`. Abstract base class for :class:`LEDBoard` and :class:`LEDBarGraph`. """ def __init__(self, *args, **kwargs): pwm = kwargs.pop('pwm', False) active_high = kwargs.pop('active_high', True) initial_value = kwargs.pop('initial_value', False) pin_factory = kwargs.pop('pin_factory', None) order = kwargs.pop('_order', None) LEDClass = PWMLED if pwm else LED super(LEDCollection, self).__init__( *( pin_or_collection if isinstance(pin_or_collection, LEDCollection) else LEDClass( pin_or_collection, active_high, initial_value, pin_factory=pin_factory ) for pin_or_collection in args ), _order=order, pin_factory=pin_factory, **{ name: pin_or_collection if isinstance(pin_or_collection, LEDCollection) else LEDClass( pin_or_collection, active_high, initial_value, pin_factory=pin_factory ) for name, pin_or_collection in kwargs.items() }) leds = [] for item in self: if isinstance(item, LEDCollection): for subitem in item.leds: leds.append(subitem) else: leds.append(item) self._leds = tuple(leds) @property def leds(self): """ A flat tuple of all LEDs contained in this collection (and all sub-collections). """ return self._leds @property def active_high(self): return self[0].active_high class LEDBoard(LEDCollection): """ Extends :class:`LEDCollection` and represents a generic LED board or collection of LEDs. The following example turns on all the LEDs on a board containing 5 LEDs attached to GPIO pins 2 through 6:: from gpiozero import LEDBoard leds = LEDBoard(2, 3, 4, 5, 6) leds.on() :param int \*pins: Specify the GPIO pins that the LEDs of the board are attached to. You can designate as many pins as necessary. You can also specify :class:`LEDBoard` instances to create trees of LEDs. :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. This parameter can only be specified as a keyword parameter. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set all the associated pins to HIGH. If ``False``, the :meth:`on` method will set all pins to LOW (the :meth:`off` method always does the opposite). This parameter can only be specified as a keyword parameter. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. This parameter can only be specified as a keyword parameter. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). :param \*\*named_pins: Specify GPIO pins that LEDs of the board are attached to, associating each LED with a property name. You can designate as many pins as necessary and use any names, provided they're not already in use by something else. You can also specify :class:`LEDBoard` instances to create trees of LEDs. """ def __init__(self, *args, **kwargs): self._blink_thread = None self._blink_leds = [] self._blink_lock = Lock() super(LEDBoard, self).__init__(*args, **kwargs) def close(self): try: self._stop_blink() except AttributeError: pass super(LEDBoard, self).close() def on(self, *args): self._stop_blink() if args: for index in args: self[index].on() else: super(LEDBoard, self).on() def off(self, *args): self._stop_blink() if args: for index in args: self[index].off() else: super(LEDBoard, self).off() def toggle(self, *args): self._stop_blink() if args: for index in args: self[index].toggle() else: super(LEDBoard, self).toggle() def blink( self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0, n=None, background=True): """ Make all the LEDs turn on and off repeatedly. :param float on_time: Number of seconds on. Defaults to 1 second. :param float off_time: Number of seconds off. Defaults to 1 second. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 0. Must be 0 if ``pwm`` was ``False`` when the class was constructed (:exc:`ValueError` will be raised if not). :param float fade_out_time: Number of seconds to spend fading out. Defaults to 0. Must be 0 if ``pwm`` was ``False`` when the class was constructed (:exc:`ValueError` will be raised if not). :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True``, start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ for led in self.leds: if isinstance(led, LED): if fade_in_time: raise ValueError('fade_in_time must be 0 with non-PWM LEDs') if fade_out_time: raise ValueError('fade_out_time must be 0 with non-PWM LEDs') self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, args=(on_time, off_time, fade_in_time, fade_out_time, n) ) self._blink_thread.start() if not background: self._blink_thread.join() self._blink_thread = None def _stop_blink(self, led=None): if led is None: if self._blink_thread: self._blink_thread.stop() self._blink_thread = None else: with self._blink_lock: self._blink_leds.remove(led) def pulse(self, fade_in_time=1, fade_out_time=1, n=None, background=True): """ Make the device fade in and out repeatedly. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 1. :param float fade_out_time: Number of seconds to spend fading out. Defaults to 1. :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ on_time = off_time = 0 self.blink( on_time, off_time, fade_in_time, fade_out_time, n, background ) def _blink_device(self, on_time, off_time, fade_in_time, fade_out_time, n, fps=25): sequence = [] if fade_in_time > 0: sequence += [ (i * (1 / fps) / fade_in_time, 1 / fps) for i in range(int(fps * fade_in_time)) ] sequence.append((1, on_time)) if fade_out_time > 0: sequence += [ (1 - (i * (1 / fps) / fade_out_time), 1 / fps) for i in range(int(fps * fade_out_time)) ] sequence.append((0, off_time)) sequence = ( cycle(sequence) if n is None else chain.from_iterable(repeat(sequence, n)) ) with self._blink_lock: self._blink_leds = list(self.leds) for led in self._blink_leds: if led._controller not in (None, self): led._controller._stop_blink(led) led._controller = self for value, delay in sequence: with self._blink_lock: if not self._blink_leds: break for led in self._blink_leds: led._write(value) if self._blink_thread.stopping.wait(delay): break class LEDBarGraph(LEDCollection): """ Extends :class:`LEDCollection` to control a line of LEDs representing a bar graph. Positive values (0 to 1) light the LEDs from first to last. Negative values (-1 to 0) light the LEDs from last to first. The following example demonstrates turning on the first two and last two LEDs in a board containing five LEDs attached to GPIOs 2 through 6:: from gpiozero import LEDBarGraph from time import sleep graph = LEDBarGraph(2, 3, 4, 5, 6) graph.value = 2/5 # Light the first two LEDs only sleep(1) graph.value = -2/5 # Light the last two LEDs only sleep(1) graph.off() As with other output devices, :attr:`source` and :attr:`values` are supported:: from gpiozero import LEDBarGraph, MCP3008 from signal import pause graph = LEDBarGraph(2, 3, 4, 5, 6, pwm=True) pot = MCP3008(channel=0) graph.source = pot.values pause() :param int \*pins: Specify the GPIO pins that the LEDs of the bar graph are attached to. You can designate as many pins as necessary. :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. This parameter can only be specified as a keyword parameter. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set all the associated pins to HIGH. If ``False``, the :meth:`on` method will set all pins to LOW (the :meth:`off` method always does the opposite). This parameter can only be specified as a keyword parameter. :param float initial_value: The initial :attr:`value` of the graph given as a float between -1 and +1. Defaults to ``0.0``. This parameter can only be specified as a keyword parameter. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__(self, *pins, **kwargs): # Don't allow graphs to contain collections for pin in pins: assert not isinstance(pin, LEDCollection) pwm = kwargs.pop('pwm', False) active_high = kwargs.pop('active_high', True) initial_value = kwargs.pop('initial_value', 0.0) pin_factory = kwargs.pop('pin_factory', None) if kwargs: raise TypeError('unexpected keyword argument: %s' % kwargs.popitem()[0]) super(LEDBarGraph, self).__init__( *pins, pwm=pwm, active_high=active_high, pin_factory=pin_factory ) try: self.value = initial_value except: self.close() raise @property def value(self): """ The value of the LED bar graph. When no LEDs are lit, the value is 0. When all LEDs are lit, the value is 1. Values between 0 and 1 light LEDs linearly from first to last. Values between 0 and -1 light LEDs linearly from last to first. To light a particular number of LEDs, simply divide that number by the number of LEDs. For example, if your graph contains 3 LEDs, the following will light the first:: from gpiozero import LEDBarGraph graph = LEDBarGraph(12, 16, 19) graph.value = 1/3 .. note:: Setting value to -1 will light all LEDs. However, querying it subsequently will return 1 as both representations are the same in hardware. The readable range of :attr:`value` is effectively -1 < value <= 1. """ result = sum(led.value for led in self) if self[0].value < self[-1].value: result = -result return result / len(self) @value.setter def value(self, value): if not -1 <= value <= 1: raise OutputDeviceBadValue('LEDBarGraph value must be between -1 and 1') count = len(self) leds = self if value < 0: leds = reversed(leds) value = -value if isinstance(self[0], PWMLED): calc_value = lambda index: min(1, max(0, count * value - index)) else: calc_value = lambda index: value >= ((index + 1) / count) for index, led in enumerate(leds): led.value = calc_value(index) @property def lit_count(self): """ The number of LEDs on the bar graph actually lit up. Note that just like ``value``, this can be negative if the LEDs are lit from last to first. """ lit_value = self.value * len(self) if not isinstance(self[0], PWMLED): lit_value = int(lit_value) return lit_value @lit_count.setter def lit_count(self, value): self.value = value / len(self) class LedBorg(RGBLED): """ Extends :class:`RGBLED` for the `PiBorg LedBorg`_: an add-on board containing a very bright RGB LED. The LedBorg pins are fixed and therefore there's no need to specify them when constructing this class. The following example turns the LedBorg purple:: from gpiozero import LedBorg led = LedBorg() led.color = (1, 0, 1) :param tuple initial_value: The initial color for the LedBorg. Defaults to black ``(0, 0, 0)``. :param bool pwm: If ``True`` (the default), construct :class:`PWMLED` instances for each component of the LedBorg. If ``False``, construct regular :class:`LED` instances, which prevents smooth color graduations. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _PiBorg LedBorg: https://www.piborg.org/ledborg """ def __init__(self, initial_value=(0, 0, 0), pwm=True, pin_factory=None): super(LedBorg, self).__init__(red=17, green=27, blue=22, pwm=pwm, initial_value=initial_value, pin_factory=pin_factory) class PiLiter(LEDBoard): """ Extends :class:`LEDBoard` for the `Ciseco Pi-LITEr`_: a strip of 8 very bright LEDs. The Pi-LITEr pins are fixed and therefore there's no need to specify them when constructing this class. The following example turns on all the LEDs of the Pi-LITEr:: from gpiozero import PiLiter lite = PiLiter() lite.on() :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ """ def __init__(self, pwm=False, initial_value=False, pin_factory=None): super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25, pwm=pwm, initial_value=initial_value, pin_factory=pin_factory) class PiLiterBarGraph(LEDBarGraph): """ Extends :class:`LEDBarGraph` to treat the `Ciseco Pi-LITEr`_ as an 8-segment bar graph. The Pi-LITEr pins are fixed and therefore there's no need to specify them when constructing this class. The following example sets the graph value to 0.5:: from gpiozero import PiLiterBarGraph graph = PiLiterBarGraph() graph.value = 0.5 :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. :param float initial_value: The initial :attr:`value` of the graph given as a float between -1 and +1. Defaults to ``0.0``. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ """ def __init__(self, pwm=False, initial_value=0.0, pin_factory=None): pins = (4, 17, 27, 18, 22, 23, 24, 25) super(PiLiterBarGraph, self).__init__( *pins, pwm=pwm, initial_value=initial_value, pin_factory=pin_factory ) class TrafficLights(LEDBoard): """ Extends :class:`LEDBoard` for devices containing red, yellow, and green LEDs. The following example initializes a device connected to GPIO pins 2, 3, and 4, then lights the amber (yellow) LED attached to GPIO 3:: from gpiozero import TrafficLights traffic = TrafficLights(2, 3, 4) traffic.amber.on() :param int red: The GPIO pin that the red LED is attached to. :param int amber: The GPIO pin that the amber LED is attached to. :param int green: The GPIO pin that the green LED is attached to. :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. :param int yellow: The GPIO pin that the yellow LED is attached to. This is merely an alias for the ``amber`` parameter - you can't specify both ``amber`` and ``yellow``. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__(self, red=None, amber=None, green=None, pwm=False, initial_value=False, yellow=None, pin_factory=None): if amber is not None and yellow is not None: raise OutputDeviceBadValue( 'Only one of amber or yellow can be specified' ) devices = OrderedDict((('red', red), )) self._display_yellow = amber is None and yellow is not None if self._display_yellow: devices['yellow'] = yellow else: devices['amber'] = amber devices['green'] = green if not all(p is not None for p in devices.values()): raise GPIOPinMissing( ', '.join(devices.keys())+' pins must be provided' ) super(TrafficLights, self).__init__( pwm=pwm, initial_value=initial_value, _order=devices.keys(), pin_factory=pin_factory, **devices) def __getattr__(self, name): if name == 'amber' and self._display_yellow: name = 'yellow' elif name == 'yellow' and not self._display_yellow: name = 'amber' return super(TrafficLights, self).__getattr__(name) class PiTraffic(TrafficLights): """ Extends :class:`TrafficLights` for the `Low Voltage Labs PI-TRAFFIC`_ vertical traffic lights board when attached to GPIO pins 9, 10, and 11. There's no need to specify the pins if the PI-TRAFFIC is connected to the default pins (9, 10, 11). The following example turns on the amber LED on the PI-TRAFFIC:: from gpiozero import PiTraffic traffic = PiTraffic() traffic.amber.on() To use the PI-TRAFFIC board when attached to a non-standard set of pins, simply use the parent class, :class:`TrafficLights`. :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _Low Voltage Labs PI-TRAFFIC: http://lowvoltagelabs.com/products/pi-traffic/ """ def __init__(self, pwm=False, initial_value=False, pin_factory=None): super(PiTraffic, self).__init__(9, 10, 11, pwm=pwm, initial_value=initial_value, pin_factory=pin_factory) class PiStop(TrafficLights): """ Extends :class:`TrafficLights` for the `PiHardware Pi-Stop`_: a vertical traffic lights board. The following example turns on the amber LED on a Pi-Stop connected to location ``A+``:: from gpiozero import PiStop traffic = PiStop('A+') traffic.amber.on() :param str location: The `location`_ on the GPIO header to which the Pi-Stop is connected. Must be one of: ``A``, ``A+``, ``B``, ``B+``, ``C``, ``D``. :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _PiHardware Pi-Stop: https://pihw.wordpress.com/meltwaters-pi-hardware-kits/pi-stop/ .. _location: https://github.com/PiHw/Pi-Stop/blob/master/markdown_source/markdown/Discover-PiStop.md """ LOCATIONS = { 'A': (7, 8, 25), 'A+': (21, 20, 16), 'B': (10, 9, 11), 'B+': (13, 19, 26), 'C': (18, 15, 14), 'D': (2, 3, 4), } def __init__( self, location=None, pwm=False, initial_value=False, pin_factory=None): gpios = self.LOCATIONS.get(location, None) if gpios is None: raise ValueError('location must be one of: %s' % ', '.join(sorted(self.LOCATIONS.keys()))) super(PiStop, self).__init__( *gpios, pwm=pwm, initial_value=initial_value, pin_factory=pin_factory ) class StatusZero(LEDBoard): """ Extends :class:`LEDBoard` for The Pi Hut's `STATUS Zero`_: a Pi Zero sized add-on board with three sets of red/green LEDs to provide a status indicator. The following example designates the first strip the label "wifi" and the second "raining", and turns them green and red respectfully:: from gpiozero import StatusZero status = StatusZero('wifi', 'raining') status.wifi.green.on() status.raining.red.on() :param str \*labels: Specify the names of the labels you wish to designate the strips to. You can list up to three labels. If no labels are given, three strips will be initialised with names 'one', 'two', and 'three'. If some, but not all strips are given labels, any remaining strips will not be initialised. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _STATUS Zero: https://thepihut.com/statuszero """ default_labels = ('one', 'two', 'three') def __init__(self, *labels, **kwargs): pins = ( (17, 4), (22, 27), (9, 10), ) pin_factory = kwargs.pop('pin_factory', None) if len(labels) == 0: labels = self.default_labels elif len(labels) > len(pins): raise ValueError("StatusZero doesn't support more than three labels") dup, count = Counter(labels).most_common(1)[0] if count > 1: raise ValueError("Duplicate label %s" % dup) super(StatusZero, self).__init__( _order=labels, pin_factory=pin_factory, **{ label: LEDBoard( red=red, green=green, _order=('red', 'green'), pin_factory=pin_factory, **kwargs ) for (green, red), label in zip(pins, labels) } ) class StatusBoard(CompositeOutputDevice): """ Extends :class:`CompositeOutputDevice` for The Pi Hut's `STATUS`_ board: a HAT sized add-on board with five sets of red/green LEDs and buttons to provide a status indicator with additional input. The following example designates the first strip the label "wifi" and the second "raining", turns the wifi green and then activates the button to toggle its lights when pressed:: from gpiozero import StatusBoard status = StatusBoard('wifi', 'raining') status.wifi.lights.green.on() status.wifi.button.when_pressed = status.wifi.lights.toggle :param str \*labels: Specify the names of the labels you wish to designate the strips to. You can list up to five labels. If no labels are given, five strips will be initialised with names 'one' to 'five'. If some, but not all strips are given labels, any remaining strips will not be initialised. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _STATUS: https://thepihut.com/status """ default_labels = ('one', 'two', 'three', 'four', 'five') def __init__(self, *labels, **kwargs): pins = ( (17, 4, 14), (22, 27, 19), (9, 10, 15), (5, 11, 26), (13, 6, 18), ) pin_factory = kwargs.pop('pin_factory', None) if len(labels) == 0: labels = self.default_labels elif len(labels) > len(pins): raise ValueError("StatusBoard doesn't support more than five labels") dup, count = Counter(labels).most_common(1)[0] if count > 1: raise ValueError("Duplicate label %s" % dup) super(StatusBoard, self).__init__( _order=labels, pin_factory=pin_factory, **{ label: CompositeOutputDevice( button=Button(button, pin_factory=pin_factory), lights=LEDBoard( red=red, green=green, _order=('red', 'green'), pin_factory=pin_factory, **kwargs ), _order=('button', 'lights'), pin_factory=pin_factory ) for (green, red, button), label in zip(pins, labels) } ) class SnowPi(LEDBoard): """ Extends :class:`LEDBoard` for the `Ryanteck SnowPi`_ board. The SnowPi pins are fixed and therefore there's no need to specify them when constructing this class. The following example turns on the eyes, sets the nose pulsing, and the arms blinking:: from gpiozero import SnowPi snowman = SnowPi(pwm=True) snowman.eyes.on() snowman.nose.pulse() snowman.arms.blink() :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _Ryanteck SnowPi: https://ryanteck.uk/raspberry-pi/114-snowpi-the-gpio-snowman-for-raspberry-pi-0635648608303.html """ def __init__(self, pwm=False, initial_value=False, pin_factory=None): super(SnowPi, self).__init__( arms=LEDBoard( left=LEDBoard( top=17, middle=18, bottom=22, pwm=pwm, initial_value=initial_value, _order=('top', 'middle', 'bottom'), pin_factory=pin_factory), right=LEDBoard( top=7, middle=8, bottom=9, pwm=pwm, initial_value=initial_value, _order=('top', 'middle', 'bottom'), pin_factory=pin_factory), _order=('left', 'right'), pin_factory=pin_factory ), eyes=LEDBoard( left=23, right=24, pwm=pwm, initial_value=initial_value, _order=('left', 'right'), pin_factory=pin_factory ), nose=25, pwm=pwm, initial_value=initial_value, _order=('eyes', 'nose', 'arms'), pin_factory=pin_factory ) class TrafficLightsBuzzer(CompositeOutputDevice): """ Extends :class:`CompositeOutputDevice` and is a generic class for HATs with traffic lights, a button and a buzzer. :param TrafficLights lights: An instance of :class:`TrafficLights` representing the traffic lights of the HAT. :param Buzzer buzzer: An instance of :class:`Buzzer` representing the buzzer on the HAT. :param Button button: An instance of :class:`Button` representing the button on the HAT. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__(self, lights, buzzer, button, pin_factory=None): super(TrafficLightsBuzzer, self).__init__( lights=lights, buzzer=buzzer, button=button, _order=('lights', 'buzzer', 'button'), pin_factory=pin_factory ) class FishDish(TrafficLightsBuzzer): """ Extends :class:`TrafficLightsBuzzer` for the `Pi Supply FishDish`_: traffic light LEDs, a button and a buzzer. The FishDish pins are fixed and therefore there's no need to specify them when constructing this class. The following example waits for the button to be pressed on the FishDish, then turns on all the LEDs:: from gpiozero import FishDish fish = FishDish() fish.button.wait_for_press() fish.lights.on() :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _Pi Supply FishDish: https://www.pi-supply.com/product/fish-dish-raspberry-pi-led-buzzer-board/ """ def __init__(self, pwm=False, pin_factory=None): super(FishDish, self).__init__( TrafficLights(9, 22, 4, pwm=pwm, pin_factory=pin_factory), Buzzer(8, pin_factory=pin_factory), Button(7, pull_up=False, pin_factory=pin_factory), pin_factory=pin_factory ) class TrafficHat(TrafficLightsBuzzer): """ Extends :class:`TrafficLightsBuzzer` for the `Ryanteck Traffic HAT`_: traffic light LEDs, a button and a buzzer. The Traffic HAT pins are fixed and therefore there's no need to specify them when constructing this class. The following example waits for the button to be pressed on the Traffic HAT, then turns on all the LEDs:: from gpiozero import TrafficHat hat = TrafficHat() hat.button.wait_for_press() hat.lights.on() :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _Ryanteck Traffic HAT: https://ryanteck.uk/hats/1-traffichat-0635648607122.html """ def __init__(self, pwm=False, pin_factory=None): super(TrafficHat, self).__init__( TrafficLights(24, 23, 22, pwm=pwm, pin_factory=pin_factory), Buzzer(5, pin_factory=pin_factory), Button(25, pin_factory=pin_factory), pin_factory=pin_factory ) class Robot(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` to represent a generic dual-motor robot. This class is constructed with two tuples representing the forward and backward pins of the left and right controllers respectively. For example, if the left motor's controller is connected to GPIOs 4 and 14, while the right motor's controller is connected to GPIOs 17 and 18 then the following example will drive the robot forward:: from gpiozero import Robot robot = Robot(left=(4, 14), right=(17, 18)) robot.forward() :param tuple left: A tuple of two GPIO pins representing the forward and backward inputs of the left motor's controller. :param tuple right: A tuple of two GPIO pins representing the forward and backward inputs of the right motor's controller. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__(self, left=None, right=None, pin_factory=None): super(Robot, self).__init__( left_motor=Motor(*left, pin_factory=pin_factory), right_motor=Motor(*right, pin_factory=pin_factory), _order=('left_motor', 'right_motor'), pin_factory=pin_factory ) @property def value(self): """ Represents the motion of the robot as a tuple of (left_motor_speed, right_motor_speed) with ``(-1, -1)`` representing full speed backwards, ``(1, 1)`` representing full speed forwards, and ``(0, 0)`` representing stopped. """ return super(Robot, self).value @value.setter def value(self, value): self.left_motor.value, self.right_motor.value = value def forward(self, speed=1, **kwargs): """ Drive the robot forward by running both motors forward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. :param float curve_left: The amount to curve left while moving forwards, by driving the left motor at a slower speed. Maximum ``curve_left`` is 1, the default is 0 (no curve). This parameter can only be specified as a keyword parameter, and is mutually exclusive with ``curve_right``. :param float curve_right: The amount to curve right while moving forwards, by driving the right motor at a slower speed. Maximum ``curve_right`` is 1, the default is 0 (no curve). This parameter can only be specified as a keyword parameter, and is mutually exclusive with ``curve_left``. """ curve_left = kwargs.pop('curve_left', 0) curve_right = kwargs.pop('curve_right', 0) if kwargs: raise TypeError('unexpected argument %s' % kwargs.popitem()[0]) if not 0 <= curve_left <= 1: raise ValueError('curve_left must be between 0 and 1') if not 0 <= curve_right <= 1: raise ValueError('curve_right must be between 0 and 1') if curve_left != 0 and curve_right != 0: raise ValueError('curve_left and curve_right can\'t be used at the same time') self.left_motor.forward(speed * (1 - curve_left)) self.right_motor.forward(speed * (1 - curve_right)) def backward(self, speed=1, **kwargs): """ Drive the robot backward by running both motors backward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. :param float curve_left: The amount to curve left while moving backwards, by driving the left motor at a slower speed. Maximum ``curve_left`` is 1, the default is 0 (no curve). This parameter can only be specified as a keyword parameter, and is mutually exclusive with ``curve_right``. :param float curve_right: The amount to curve right while moving backwards, by driving the right motor at a slower speed. Maximum ``curve_right`` is 1, the default is 0 (no curve). This parameter can only be specified as a keyword parameter, and is mutually exclusive with ``curve_left``. """ curve_left = kwargs.pop('curve_left', 0) curve_right = kwargs.pop('curve_right', 0) if kwargs: raise TypeError('unexpected argument %s' % kwargs.popitem()[0]) if not 0 <= curve_left <= 1: raise ValueError('curve_left must be between 0 and 1') if not 0 <= curve_right <= 1: raise ValueError('curve_right must be between 0 and 1') if curve_left != 0 and curve_right != 0: raise ValueError('curve_left and curve_right can\'t be used at the same time') self.left_motor.backward(speed * (1 - curve_left)) self.right_motor.backward(speed * (1 - curve_right)) def left(self, speed=1): """ Make the robot turn left by running the right motor forward and left motor backward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.right_motor.forward(speed) self.left_motor.backward(speed) def right(self, speed=1): """ Make the robot turn right by running the left motor forward and right motor backward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.left_motor.forward(speed) self.right_motor.backward(speed) def reverse(self): """ Reverse the robot's current motor directions. If the robot is currently running full speed forward, it will run full speed backward. If the robot is turning left at half-speed, it will turn right at half-speed. If the robot is currently stopped it will remain stopped. """ self.left_motor.reverse() self.right_motor.reverse() def stop(self): """ Stop the robot. """ self.left_motor.stop() self.right_motor.stop() class RyanteckRobot(Robot): """ Extends :class:`Robot` for the `Ryanteck motor controller board`_. The Ryanteck MCB pins are fixed and therefore there's no need to specify them when constructing this class. The following example drives the robot forward:: from gpiozero import RyanteckRobot robot = RyanteckRobot() robot.forward() :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _Ryanteck motor controller board: https://ryanteck.uk/add-ons/6-ryanteck-rpi-motor-controller-board-0635648607160.html """ def __init__(self, pin_factory=None): super(RyanteckRobot, self).__init__( (17, 18), (22, 23), pin_factory=pin_factory ) class CamJamKitRobot(Robot): """ Extends :class:`Robot` for the `CamJam #3 EduKit`_ motor controller board. The CamJam robot controller pins are fixed and therefore there's no need to specify them when constructing this class. The following example drives the robot forward:: from gpiozero import CamJamKitRobot robot = CamJamKitRobot() robot.forward() :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 """ def __init__(self, pin_factory=None): super(CamJamKitRobot, self).__init__( (9, 10), (7, 8), pin_factory=pin_factory ) class PhaseEnableRobot(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` to represent a dual-motor robot based around a Phase/Enable motor board. This class is constructed with two tuples representing the phase (direction) and enable (speed) pins of the left and right controllers respectively. For example, if the left motor's controller is connected to GPIOs 12 and 5, while the right motor's controller is connected to GPIOs 13 and 6 so the following example will drive the robot forward:: from gpiozero import PhaseEnableRobot robot = PhaseEnableRobot(left=(5, 12), right=(6, 13)) robot.forward() :param tuple left: A tuple of two GPIO pins representing the phase and enable inputs of the left motor's controller. :param tuple right: A tuple of two GPIO pins representing the phase and enable inputs of the right motor's controller. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__(self, left=None, right=None, pin_factory=None): super(PhaseEnableRobot, self).__init__( left_motor=PhaseEnableMotor(*left, pin_factory=pin_factory), right_motor=PhaseEnableMotor(*right, pin_factory=pin_factory), _order=('left_motor', 'right_motor'), pin_factory=pin_factory ) @property def value(self): """ Returns a tuple of two floating point values (-1 to 1) representing the speeds of the robot's two motors (left and right). This property can also be set to alter the speed of both motors. """ return super(PhaseEnableRobot, self).value @value.setter def value(self, value): self.left_motor.value, self.right_motor.value = value def forward(self, speed=1): """ Drive the robot forward by running both motors forward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.left_motor.forward(speed) self.right_motor.forward(speed) def backward(self, speed=1): """ Drive the robot backward by running both motors backward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.left_motor.backward(speed) self.right_motor.backward(speed) def left(self, speed=1): """ Make the robot turn left by running the right motor forward and left motor backward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.right_motor.forward(speed) self.left_motor.backward(speed) def right(self, speed=1): """ Make the robot turn right by running the left motor forward and right motor backward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.left_motor.forward(speed) self.right_motor.backward(speed) def reverse(self): """ Reverse the robot's current motor directions. If the robot is currently running full speed forward, it will run full speed backward. If the robot is turning left at half-speed, it will turn right at half-speed. If the robot is currently stopped it will remain stopped. """ self.left_motor.value = -self.left_motor.value self.right_motor.value = -self.right_motor.value def stop(self): """ Stop the robot. """ self.left_motor.stop() self.right_motor.stop() class PololuDRV8835Robot(PhaseEnableRobot): """ Extends :class:`PhaseEnableRobot` for the `Pololu DRV8835 Dual Motor Driver Kit`_. The Pololu DRV8835 pins are fixed and therefore there's no need to specify them when constructing this class. The following example drives the robot forward:: from gpiozero import PololuDRV8835Robot robot = PololuDRV8835Robot() robot.forward() :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _Pololu DRV8835 Dual Motor Driver Kit: https://www.pololu.com/product/2753 """ def __init__(self, pin_factory=None): super(PololuDRV8835Robot, self).__init__( (5, 12), (6, 13), pin_factory=pin_factory ) class _EnergenieMaster(SharedMixin, CompositeOutputDevice): def __init__(self, pin_factory=None): self._lock = Lock() super(_EnergenieMaster, self).__init__( *( OutputDevice(pin, pin_factory=pin_factory) for pin in (17, 22, 23, 27) ), mode=OutputDevice(24, pin_factory=pin_factory), enable=OutputDevice(25, pin_factory=pin_factory), _order=('mode', 'enable'), pin_factory=pin_factory ) def close(self): if getattr(self, '_lock', None): with self._lock: super(_EnergenieMaster, self).close() self._lock = None @classmethod def _shared_key(cls, pin_factory): # There's only one Energenie master return None def transmit(self, socket, enable): with self._lock: try: code = (8 * bool(enable)) + (8 - socket) for bit in self[:4]: bit.value = (code & 1) code >>= 1 sleep(0.1) self.enable.on() sleep(0.25) finally: self.enable.off() class Energenie(SourceMixin, Device): """ Extends :class:`Device` to represent an `Energenie socket`_ controller. This class is constructed with a socket number and an optional initial state (defaults to ``False``, meaning off). Instances of this class can be used to switch peripherals on and off. For example:: from gpiozero import Energenie lamp = Energenie(1) lamp.on() :param int socket: Which socket this instance should control. This is an integer number between 1 and 4. :param bool initial_value: The initial state of the socket. As Energenie sockets provide no means of reading their state, you must provide an initial state for the socket, which will be set upon construction. This defaults to ``False`` which will switch the socket off. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _Energenie socket: https://energenie4u.co.uk/index.php/catalogue/product/ENER002-2PI """ def __init__(self, socket=None, initial_value=False, pin_factory=None): if socket is None: raise EnergenieSocketMissing('socket number must be provided') if not (1 <= socket <= 4): raise EnergenieBadSocket('socket number must be between 1 and 4') self._value = None super(Energenie, self).__init__(pin_factory=pin_factory) self._socket = socket self._master = _EnergenieMaster(pin_factory=pin_factory) if initial_value: self.on() else: self.off() def close(self): if getattr(self, '_master', None): self._master.close() self._master = None @property def closed(self): return self._master is None def __repr__(self): try: self._check_open() return "" % self._socket except DeviceClosed: return "" @property def value(self): return self._value @value.setter def value(self, value): value = bool(value) self._master.transmit(self._socket, value) self._value = value def on(self): self.value = True def off(self): self.value = False gpiozero-1.4.1/gpiozero/threads.py0000644000175000017500000000152713165454031017104 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') from threading import Thread, Event _THREADS = set() def _threads_shutdown(): while _THREADS: for t in _THREADS.copy(): t.stop() class GPIOThread(Thread): def __init__(self, group=None, target=None, name=None, args=(), kwargs=None): if kwargs is None: kwargs = {} self.stopping = Event() super(GPIOThread, self).__init__(group, target, name, args, kwargs) self.daemon = True def start(self): self.stopping.clear() _THREADS.add(self) super(GPIOThread, self).start() def stop(self): self.stopping.set() self.join() def join(self): super(GPIOThread, self).join() _THREADS.discard(self) gpiozero-1.4.1/gpiozero/__init__.py0000644000175000017500000000325613243115414017206 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) from .pins import ( Factory, Pin, SPI, ) from .pins.data import ( PiBoardInfo, HeaderInfo, PinInfo, pi_info, ) # Yes, import * is naughty, but exc imports nothing else so there's no cross # contamination here ... and besides, have you *seen* the list lately?! from .exc import * from .devices import ( Device, GPIODevice, CompositeDevice, ) from .mixins import ( SharedMixin, SourceMixin, ValuesMixin, EventsMixin, HoldMixin, ) from .input_devices import ( InputDevice, DigitalInputDevice, SmoothedInputDevice, Button, LineSensor, MotionSensor, LightSensor, DistanceSensor, ) from .spi_devices import ( SPIDevice, AnalogInputDevice, MCP3001, MCP3002, MCP3004, MCP3008, MCP3201, MCP3202, MCP3204, MCP3208, MCP3301, MCP3302, MCP3304, ) from .output_devices import ( OutputDevice, DigitalOutputDevice, PWMOutputDevice, PWMLED, LED, Buzzer, Motor, PhaseEnableMotor, Servo, AngularServo, RGBLED, ) from .boards import ( CompositeOutputDevice, ButtonBoard, LEDCollection, LEDBoard, LEDBarGraph, LedBorg, PiLiter, PiLiterBarGraph, TrafficLights, PiTraffic, PiStop, StatusZero, StatusBoard, SnowPi, TrafficLightsBuzzer, FishDish, TrafficHat, Robot, RyanteckRobot, CamJamKitRobot, PhaseEnableRobot, PololuDRV8835Robot, Energenie, ) from .other_devices import ( InternalDevice, PingServer, CPUTemperature, TimeOfDay, ) gpiozero-1.4.1/gpiozero/pins/0000755000175000017500000000000013243117413016041 5ustar davedave00000000000000gpiozero-1.4.1/gpiozero/pins/pi.py0000644000175000017500000002415113241541436017032 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import io from threading import RLock, Lock from types import MethodType from collections import defaultdict try: from weakref import ref, WeakMethod except ImportError: from ..compat import WeakMethod import warnings try: from spidev import SpiDev except ImportError: SpiDev = None from . import Factory, Pin from .data import pi_info from ..exc import ( PinNoPins, PinNonPhysical, PinInvalidPin, SPIBadArgs, SPISoftwareFallback, ) SPI_HARDWARE_PINS = { 0: { 'clock': 11, 'mosi': 10, 'miso': 9, 'select': (8, 7), }, } class PiFactory(Factory): """ Abstract base class representing hardware attached to a Raspberry Pi. This forms the base of :class:`~gpiozero.pins.local.LocalPiFactory`. """ def __init__(self): super(PiFactory, self).__init__() self._info = None self.pins = {} self.pin_class = None self.spi_classes = { ('hardware', 'exclusive'): None, ('hardware', 'shared'): None, ('software', 'exclusive'): None, ('software', 'shared'): None, } def close(self): for pin in self.pins.values(): pin.close() self.pins.clear() def pin(self, spec): n = self._to_gpio(spec) try: pin = self.pins[n] except KeyError: pin = self.pin_class(self, n) self.pins[n] = pin return pin def _to_gpio(self, spec): """ Converts the pin *spec* to a GPIO port number. """ if not 0 <= spec < 54: raise PinInvalidPin('invalid GPIO port %d specified (range 0..53) ' % spec) return spec def _get_revision(self): raise NotImplementedError def _get_pi_info(self): if self._info is None: self._info = pi_info(self._get_revision()) return self._info def spi(self, **spi_args): """ Returns an SPI interface, for the specified SPI *port* and *device*, or for the specified pins (*clock_pin*, *mosi_pin*, *miso_pin*, and *select_pin*). Only one of the schemes can be used; attempting to mix *port* and *device* with pin numbers will raise :exc:`SPIBadArgs`. If the pins specified match the hardware SPI pins (clock on GPIO11, MOSI on GPIO10, MISO on GPIO9, and chip select on GPIO8 or GPIO7), and the spidev module can be imported, a :class:`SPIHardwareInterface` instance will be returned. Otherwise, a :class:`SPISoftwareInterface` will be returned which will use simple bit-banging to communicate. Both interfaces have the same API, support clock polarity and phase attributes, and can handle half and full duplex communications, but the hardware interface is significantly faster (though for many things this doesn't matter). """ spi_args, kwargs = self._extract_spi_args(**spi_args) shared = 'shared' if kwargs.pop('shared', False) else 'exclusive' if kwargs: raise SPIBadArgs( 'unrecognized keyword argument %s' % kwargs.popitem()[0]) for port, pins in SPI_HARDWARE_PINS.items(): if all(( spi_args['clock_pin'] == pins['clock'], spi_args['mosi_pin'] == pins['mosi'], spi_args['miso_pin'] == pins['miso'], spi_args['select_pin'] in pins['select'], )): try: return self.spi_classes[('hardware', shared)]( self, port=port, device=pins['select'].index(spi_args['select_pin']) ) except Exception as e: warnings.warn( SPISoftwareFallback( 'failed to initialize hardware SPI, falling back to ' 'software (error was: %s)' % str(e))) break # Convert all pin arguments to integer GPIO numbers. This is necessary # to ensure the shared-key for shared implementations get matched # correctly, and is a bit of a hack for the pigpio bit-bang # implementation which just wants the pin numbers too. spi_args = { key: pin.number if isinstance(pin, Pin) else pin for key, pin in spi_args.items() } return self.spi_classes[('software', shared)](self, **spi_args) def _extract_spi_args(self, **kwargs): """ Given a set of keyword arguments, splits it into those relevant to SPI implementations and all the rest. SPI arguments are augmented with defaults and converted into the pin format (from the port/device format) if necessary. Returns a tuple of ``(spi_args, other_args)``. """ dev_defaults = { 'port': 0, 'device': 0, } default_hw = SPI_HARDWARE_PINS[dev_defaults['port']] pin_defaults = { 'clock_pin': default_hw['clock'], 'mosi_pin': default_hw['mosi'], 'miso_pin': default_hw['miso'], 'select_pin': default_hw['select'][dev_defaults['device']], } spi_args = { key: value for (key, value) in kwargs.items() if key in pin_defaults or key in dev_defaults } kwargs = { key: value for (key, value) in kwargs.items() if key not in spi_args } if not spi_args: spi_args = pin_defaults elif set(spi_args) <= set(pin_defaults): spi_args = { key: self._to_gpio(spi_args.get(key, default)) for key, default in pin_defaults.items() } elif set(spi_args) <= set(dev_defaults): spi_args = { key: spi_args.get(key, default) for key, default in dev_defaults.items() } if spi_args['port'] != 0: raise SPIBadArgs('port 0 is the only valid SPI port') selected_hw = SPI_HARDWARE_PINS[spi_args['port']] try: selected_hw['select'][spi_args['device']] except IndexError: raise SPIBadArgs( 'device must be in the range 0..%d' % len(selected_hw['select'])) spi_args = { key: value if key != 'select_pin' else selected_hw['select'][spi_args['device']] for key, value in pin_defaults.items() } else: raise SPIBadArgs( 'you must either specify port and device, or clock_pin, ' 'mosi_pin, miso_pin, and select_pin; combinations of the two ' 'schemes (e.g. port and clock_pin) are not permitted') return spi_args, kwargs class PiPin(Pin): """ Abstract base class representing a multi-function GPIO pin attached to a Raspberry Pi. This overrides several methods in the abstract base :class:`~gpiozero.Pin`. Descendents must override the following methods: * :meth:`_get_function` * :meth:`_set_function` * :meth:`_get_state` * :meth:`_call_when_changed` * :meth:`_enable_event_detect` * :meth:`_disable_event_detect` Descendents *may* additionally override the following methods, if applicable: * :meth:`close` * :meth:`output_with_state` * :meth:`input_with_pull` * :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` """ def __init__(self, factory, number): super(PiPin, self).__init__() self._factory = factory self._when_changed_lock = RLock() self._when_changed = None self._number = number try: factory.pi_info.physical_pin(repr(self)) except PinNoPins: warnings.warn( PinNonPhysical( 'no physical pins exist for %s' % repr(self))) @property def number(self): return self._number def __repr__(self): return 'GPIO%d' % self._number @property def factory(self): return self._factory def _call_when_changed(self): """ Called to fire the :attr:`when_changed` event handler; override this in descendents if additional (currently redundant) parameters need to be passed. """ method = self.when_changed() if method is None: self.when_changed = None else: method() def _get_when_changed(self): return self._when_changed def _set_when_changed(self, value): with self._when_changed_lock: if value is None: if self._when_changed is not None: self._disable_event_detect() self._when_changed = None else: enabled = self._when_changed is not None # Have to take care, if value is either a closure or a bound # method, not to keep a strong reference to the containing # object if isinstance(value, MethodType): self._when_changed = WeakMethod(value) else: self._when_changed = ref(value) if not enabled: self._enable_event_detect() def _enable_event_detect(self): """ Enables event detection. This is called to activate event detection on pin :attr:`number`, watching for the specified :attr:`edges`. In response, :meth:`_call_when_changed` should be executed. """ raise NotImplementedError def _disable_event_detect(self): """ Disables event detection. This is called to deactivate event detection on pin :attr:`number`. """ raise NotImplementedError gpiozero-1.4.1/gpiozero/pins/spi.py0000644000175000017500000000643613243115414017216 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') import operator from threading import RLock from ..devices import Device, SharedMixin from ..input_devices import InputDevice from ..output_devices import OutputDevice class SPISoftwareBus(SharedMixin, Device): def __init__(self, clock_pin, mosi_pin, miso_pin): self.lock = None self.clock = None self.mosi = None self.miso = None super(SPISoftwareBus, self).__init__() self.lock = RLock() try: self.clock = OutputDevice(clock_pin, active_high=True) if mosi_pin is not None: self.mosi = OutputDevice(mosi_pin) if miso_pin is not None: self.miso = InputDevice(miso_pin) except: self.close() raise def close(self): super(SPISoftwareBus, self).close() if getattr(self, 'lock', None): with self.lock: if self.miso is not None: self.miso.close() self.miso = None if self.mosi is not None: self.mosi.close() self.mosi = None if self.clock is not None: self.clock.close() self.clock = None self.lock = None @property def closed(self): return self.lock is None @classmethod def _shared_key(cls, clock_pin, mosi_pin, miso_pin): return (clock_pin, mosi_pin, miso_pin) def transfer(self, data, clock_phase=False, lsb_first=False, bits_per_word=8): """ Writes data (a list of integer words where each word is assumed to have :attr:`bits_per_word` bits or less) to the SPI interface, and reads an equivalent number of words, returning them as a list of integers. """ result = [] with self.lock: # See https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus # (specifically the section "Example of bit-banging the master # protocol") for a simpler C implementation of this which ignores # clock polarity, phase, variable word-size, and multiple input # words if lsb_first: shift = operator.lshift init_mask = 1 else: shift = operator.rshift init_mask = 1 << (bits_per_word - 1) for write_word in data: mask = init_mask read_word = 0 for _ in range(bits_per_word): if self.mosi is not None: self.mosi.value = bool(write_word & mask) # read bit on clock activation self.clock.on() if not clock_phase: if self.miso is not None and self.miso.value: read_word |= mask # read bit on clock deactivation self.clock.off() if clock_phase: if self.miso is not None and self.miso.value: read_word |= mask mask = shift(mask, 1) result.append(read_word) return result gpiozero-1.4.1/gpiozero/pins/mock.py0000644000175000017500000004027013241530546017353 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import os from collections import namedtuple from time import time, sleep from threading import Thread, Event try: from math import isclose except ImportError: from ..compat import isclose import pkg_resources from ..exc import ( PinPWMUnsupported, PinSetInput, PinFixedPull, PinInvalidFunction, PinInvalidPull, ) from ..devices import Device from .pi import PiPin from .local import LocalPiFactory PinState = namedtuple('PinState', ('timestamp', 'state')) class MockPin(PiPin): """ A mock pin used primarily for testing. This class does *not* support PWM. """ def __init__(self, factory, number): super(MockPin, self).__init__(factory, number) self._function = 'input' self._pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating' self._state = self._pull == 'up' self._bounce = None self._edges = 'both' self._when_changed = None self.clear_states() def close(self): self.when_changed = None self.function = 'input' def _get_function(self): return self._function def _set_function(self, value): if value not in ('input', 'output'): raise PinInvalidFunction('function must be input or 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('cannot set state of pin %r' % self) assert self._function == 'output' assert 0 <= value <= 1 self._change_state(bool(value)) def _change_state(self, value): if self._state != value: t = time() self._state = value self.states.append(PinState(t - self._last_change, value)) self._last_change = t return True return False def _get_frequency(self): return None def _set_frequency(self, value): if value is not None: raise PinPWMUnsupported() 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.factory.pi_info.pulled_up(repr(self)): raise PinFixedPull('%r has a physical pull-up resistor' % self) if value not in ('floating', 'up', 'down'): raise PinInvalidPull('pull must be floating, up, or 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 self._change_state(True): 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._change_state(False): 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.05, abs_tol=0.05) assert isclose(actual.state, expected[1]) class MockConnectedPin(MockPin): """ This derivative of :class:`MockPin` emulates a pin connected to another mock pin. This is used in the "real pins" portion of the test suite to check that one pin can influence another. """ def __init__(self, factory, number, input_pin=None): super(MockConnectedPin, self).__init__(factory, number) self.input_pin = input_pin def _change_state(self, value): if self.input_pin: if value: self.input_pin.drive_high() else: self.input_pin.drive_low() return super(MockConnectedPin, self)._change_state(value) class MockChargingPin(MockPin): """ This derivative of :class:`MockPin` emulates a pin which, when set to input, waits a predetermined length of time and then drives itself high (as if attached to, e.g. a typical circuit using an LDR and a capacitor to time the charging rate). """ def __init__(self, factory, number, charge_time=0.01): super(MockChargingPin, self).__init__(factory, number) self.charge_time = charge_time # dark charging time self._charge_stop = Event() self._charge_thread = None def _set_function(self, value): super(MockChargingPin, self)._set_function(value) if value == 'input': if self._charge_thread: self._charge_stop.set() self._charge_thread.join() self._charge_stop.clear() self._charge_thread = Thread(target=self._charge) self._charge_thread.start() elif value == 'output': if self._charge_thread: self._charge_stop.set() self._charge_thread.join() def _charge(self): if not self._charge_stop.wait(self.charge_time): try: self.drive_high() except AssertionError: # Charging pins are typically flipped between input and output # repeatedly; if another thread has already flipped us to # output ignore the assertion-error resulting from attempting # to drive the pin high pass class MockTriggerPin(MockPin): """ This derivative of :class:`MockPin` is intended to be used with another :class:`MockPin` to emulate a distance sensor. Set *echo_pin* to the corresponding pin instance. When this pin is driven high it will trigger the echo pin to drive high for the echo time. """ def __init__(self, factory, number, echo_pin=None, echo_time=0.04): super(MockTriggerPin, self).__init__(factory, number) self.echo_pin = echo_pin self.echo_time = echo_time # longest echo time self._echo_thread = None def _set_state(self, value): super(MockTriggerPin, self)._set_state(value) if value: if self._echo_thread: self._echo_thread.join() self._echo_thread = Thread(target=self._echo) self._echo_thread.start() def _echo(self): sleep(0.001) self.echo_pin.drive_high() sleep(self.echo_time) self.echo_pin.drive_low() class MockPWMPin(MockPin): """ This derivative of :class:`MockPin` adds PWM support. """ def __init__(self, factory, number): super(MockPWMPin, self).__init__(factory, number) self._frequency = None def close(self): self.frequency = None super(MockPWMPin, self).close() def _set_state(self, value): if self._function == 'input': raise PinSetInput('cannot set state of pin %r' % self) assert self._function == 'output' assert 0 <= value <= 1 self._change_state(float(value)) 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._change_state(0.0) class MockSPIClockPin(MockPin): """ This derivative of :class:`MockPin` is intended to be used as the clock pin of a mock SPI device. It is not intended for direct construction in tests; rather, construct a :class:`MockSPIDevice` with various pin numbers, and this class will be used for the clock pin. """ def __init__(self, factory, number): super(MockSPIClockPin, self).__init__(factory, number) if not hasattr(self, 'spi_devices'): self.spi_devices = [] def _set_state(self, value): super(MockSPIClockPin, self)._set_state(value) for dev in self.spi_devices: dev.on_clock() class MockSPISelectPin(MockPin): """ This derivative of :class:`MockPin` is intended to be used as the select pin of a mock SPI device. It is not intended for direct construction in tests; rather, construct a :class:`MockSPIDevice` with various pin numbers, and this class will be used for the select pin. """ def __init__(self, factory, number): super(MockSPISelectPin, self).__init__(factory, number) if not hasattr(self, 'spi_device'): self.spi_device = None def _set_state(self, value): super(MockSPISelectPin, self)._set_state(value) if self.spi_device: self.spi_device.on_select() class MockSPIDevice(object): def __init__( self, clock_pin, mosi_pin=None, miso_pin=None, select_pin=None, clock_polarity=False, clock_phase=False, lsb_first=False, bits_per_word=8, select_high=False): self.clock_pin = Device.pin_factory.pin(clock_pin, pin_class=MockSPIClockPin) self.mosi_pin = None if mosi_pin is None else Device.pin_factory.pin(mosi_pin) self.miso_pin = None if miso_pin is None else Device.pin_factory.pin(miso_pin) self.select_pin = None if select_pin is None else Device.pin_factory.pin(select_pin, pin_class=MockSPISelectPin) self.clock_polarity = clock_polarity self.clock_phase = clock_phase self.lsb_first = lsb_first self.bits_per_word = bits_per_word self.select_high = select_high self.rx_bit = 0 self.rx_buf = [] self.tx_buf = [] self.clock_pin.spi_devices.append(self) self.select_pin.spi_device = self def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_tb): self.close() def close(self): if self in self.clock_pin.spi_devices: self.clock_pin.spi_devices.remove(self) if self.select_pin is not None: self.select_pin.spi_device = None def on_select(self): if self.select_pin.state == self.select_high: self.on_start() def on_clock(self): # Don't do anything if this SPI device isn't currently selected if self.select_pin is None or self.select_pin.state == self.select_high: # The XOR of the clock pin's values, polarity and phase indicates # whether we're meant to be acting on this edge if self.clock_pin.state ^ self.clock_polarity ^ self.clock_phase: self.rx_bit += 1 if self.mosi_pin is not None: self.rx_buf.append(self.mosi_pin.state) if self.miso_pin is not None: try: tx_value = self.tx_buf.pop(0) except IndexError: tx_value = 0 if tx_value: self.miso_pin.drive_high() else: self.miso_pin.drive_low() self.on_bit() def on_start(self): """ Override this in descendents to detect when the mock SPI device's select line is activated. """ self.rx_bit = 0 self.rx_buf = [] self.tx_buf = [] def on_bit(self): """ Override this in descendents to react to receiving a bit. The :attr:`rx_bit` attribute gives the index of the bit received (this is reset to 0 by default by :meth:`on_select`). The :attr:`rx_buf` sequence gives the sequence of 1s and 0s that have been recevied so far. The :attr:`tx_buf` sequence gives the sequence of 1s and 0s to transmit on the next clock pulses. All these attributes can be modified within this method. The :meth:`rx_word` and :meth:`tx_word` methods can also be used to read and append to the buffers using integers instead of bool bits. """ pass def rx_word(self): result = 0 bits = reversed(self.rx_buf) if self.lsb_first else self.rx_buf for bit in bits: result <<= 1 result |= bit return result def tx_word(self, value, bits_per_word=None): if bits_per_word is None: bits_per_word = self.bits_per_word bits = [0] * bits_per_word for bit in range(bits_per_word): bits[bit] = value & 1 value >>= 1 assert not value if not self.lsb_first: bits = reversed(bits) self.tx_buf.extend(bits) class MockFactory(LocalPiFactory): """ Factory for generating mock pins. The *revision* parameter specifies what revision of Pi the mock factory pretends to be (this affects the result of the :attr:`pi_info` attribute as well as where pull-ups are assumed to be). The *pin_class* attribute specifies which mock pin class will be generated by the :meth:`pin` method by default. This can be changed after construction by modifying the :attr:`pin_class` attribute. """ def __init__( self, revision=os.getenv('GPIOZERO_MOCK_REVISION', 'a02082'), pin_class=os.getenv('GPIOZERO_MOCK_PIN_CLASS', MockPin)): super(MockFactory, self).__init__() self._revision = revision if isinstance(pin_class, bytes): pin_class = pin_class.decode('ascii') if isinstance(pin_class, str): dist = pkg_resources.get_distribution('gpiozero') group = 'gpiozero_mock_pin_classes' pin_class = pkg_resources.load_entry_point(dist, group, pin_class.lower()) if not issubclass(pin_class, MockPin): raise ValueError('invalid mock pin_class: %r' % pin_class) self.pin_class = pin_class def _get_revision(self): return self._revision def reset(self): """ Clears the pins and reservations sets. This is primarily useful in test suites to ensure the pin factory is back in a "clean" state before the next set of tests are run. """ self.pins.clear() self._reservations.clear() def pin(self, spec, pin_class=None, **kwargs): """ The pin method for :class:`MockFactory` additionally takes a *pin_class* attribute which can be used to override the class' :attr:`pin_class` attribute. Any additional keyword arguments will be passed along to the pin constructor (useful with things like :class:`MockConnectedPin` which expect to be constructed with another pin). """ if pin_class is None: pin_class = self.pin_class n = self._to_gpio(spec) try: pin = self.pins[n] except KeyError: pin = pin_class(self, n, **kwargs) self.pins[n] = pin else: # Ensure the pin class expected supports PWM (or not) if issubclass(pin_class, MockPWMPin) != isinstance(pin, MockPWMPin): raise ValueError('pin %d is already in use as a %s' % (n, pin.__class__.__name__)) return pin gpiozero-1.4.1/gpiozero/pins/data.py0000644000175000017500000015047613241530546017345 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import os import sys from textwrap import dedent from itertools import cycle from operator import attrgetter from collections import namedtuple from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins, PinInvalidPin # Some useful constants for describing pins V1_8 = '1V8' V3_3 = '3V3' V5 = '5V' GND = 'GND' NC = 'NC' # not connected GPIO0 = 'GPIO0' GPIO1 = 'GPIO1' GPIO2 = 'GPIO2' GPIO3 = 'GPIO3' GPIO4 = 'GPIO4' GPIO5 = 'GPIO5' GPIO6 = 'GPIO6' GPIO7 = 'GPIO7' GPIO8 = 'GPIO8' GPIO9 = 'GPIO9' GPIO10 = 'GPIO10' GPIO11 = 'GPIO11' GPIO12 = 'GPIO12' GPIO13 = 'GPIO13' GPIO14 = 'GPIO14' GPIO15 = 'GPIO15' GPIO16 = 'GPIO16' GPIO17 = 'GPIO17' GPIO18 = 'GPIO18' GPIO19 = 'GPIO19' GPIO20 = 'GPIO20' GPIO21 = 'GPIO21' GPIO22 = 'GPIO22' GPIO23 = 'GPIO23' GPIO24 = 'GPIO24' GPIO25 = 'GPIO25' GPIO26 = 'GPIO26' GPIO27 = 'GPIO27' GPIO28 = 'GPIO28' GPIO29 = 'GPIO29' GPIO30 = 'GPIO30' GPIO31 = 'GPIO31' GPIO32 = 'GPIO32' GPIO33 = 'GPIO33' GPIO34 = 'GPIO34' GPIO35 = 'GPIO35' GPIO36 = 'GPIO36' GPIO37 = 'GPIO37' GPIO38 = 'GPIO38' GPIO39 = 'GPIO39' GPIO40 = 'GPIO40' GPIO41 = 'GPIO41' GPIO42 = 'GPIO42' GPIO43 = 'GPIO43' GPIO44 = 'GPIO44' GPIO45 = 'GPIO45' # Board layout ASCII art REV1_BOARD = """\ {style:white on green}+------------------{style:black on white}| |{style:white on green}--{style:on cyan}| |{style:on green}------+{style:reset} {style:white on green}| {P1:{style} col2}{style:white on green} P1 {style:black on yellow}|C|{style:white on green} {style:on cyan}|A|{style:on green} |{style:reset} {style:white on green}| {P1:{style} col1}{style:white on green} {style:black on yellow}+-+{style:white on green} {style:on cyan}+-+{style:on green} |{style:reset} {style:white on green}| |{style:reset} {style:white on green}| {style:on black}+---+{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|SoC|{style:on green} {style:black on white}| USB{style:reset} {style:white on green}| {style:on black}|D|{style:on green} {style:bold}Pi Model{style:normal} {style:on black}+---+{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|S|{style:on green} {style:bold}{model:3s}V{pcb_revision:3s}{style:normal} |{style:reset} {style:white on green}| {style:on black}|I|{style:on green} {style:on black}|C|{style:black on white}+======{style:reset} {style:white on green}| {style:on black}|S|{style:black on white}| Net{style:reset} {style:white on green}| {style:on black}|I|{style:black on white}+======{style:reset} {style:black on white}=pwr{style:on green} {style:on white}|HDMI|{style:white on green} |{style:reset} {style:white on green}+----------------{style:black on white}| |{style:white on green}----------+{style:reset}""" REV2_BOARD = """\ {style:white on green}+------------------{style:black on white}| |{style:white on green}--{style:on cyan}| |{style:on green}------+{style:reset} {style:white on green}| {P1:{style} col2}{style:white on green} P1 {style:black on yellow}|C|{style:white on green} {style:on cyan}|A|{style:on green} |{style:reset} {style:white on green}| {P1:{style} col1}{style:white on green} {style:black on yellow}+-+{style:white on green} {style:on cyan}+-+{style:on green} |{style:reset} {style:white on green}| {P5:{style} col1}{style:white on green} |{style:reset} {style:white on green}| P5 {P5:{style} col2}{style:white on green} {style:on black}+---+{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|SoC|{style:on green} {style:black on white}| USB{style:reset} {style:white on green}| {style:on black}|D|{style:on green} {style:bold}Pi Model{style:normal} {style:on black}+---+{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|S|{style:on green} {style:bold}{model:3s}V{pcb_revision:3s}{style:normal} |{style:reset} {style:white on green}| {style:on black}|I|{style:on green} {style:on black}|C|{style:black on white}+======{style:reset} {style:white on green}| {style:on black}|S|{style:black on white}| Net{style:reset} {style:white on green}| {style:on black}|I|{style:black on white}+======{style:reset} {style:black on white}=pwr{style:on green} {style:on white}|HDMI|{style:white on green} |{style:reset} {style:white on green}+----------------{style:black on white}| |{style:white on green}----------+{style:reset}""" A_BOARD = """\ {style:white on green}+------------------{style:black on white}| |{style:white on green}--{style:on cyan}| |{style:on green}------+{style:reset} {style:white on green}| {P1:{style} col2}{style:white on green} P1 {style:black on yellow}|C|{style:white on green} {style:on cyan}|A|{style:on green} |{style:reset} {style:white on green}| {P1:{style} col1}{style:white on green} {style:black on yellow}+-+{style:white on green} {style:on cyan}+-+{style:on green} |{style:reset} {style:white on green}| {P5:{style} col1}{style:white on green} |{style:reset} {style:white on green}| P5 {P5:{style} col2}{style:white on green} {style:on black}+---+{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|SoC|{style:on green} {style:black on white}| USB{style:reset} {style:white on green}| {style:on black}|D|{style:on green} {style:bold}Pi Model{style:normal} {style:on black}+---+{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|S|{style:on green} {style:bold}{model:3s}V{pcb_revision:3s}{style:normal} |{style:reset} {style:white on green}| {style:on black}|I|{style:on green} {style:on black}|C|{style:on green} |{style:reset} {style:white on green}| {style:on black}|S|{style:on green} |{style:reset} {style:white on green}| {style:on black}|I|{style:on green} |{style:reset} {style:black on white}=pwr{style:on green} {style:on white}|HDMI|{style:white on green} |{style:reset} {style:white on green}+----------------{style:black on white}| |{style:white on green}----------+{style:reset}""" BPLUS_BOARD = """\ {style:white on green},--------------------------------.{style:reset} {style:white on green}| {J8:{style} col2}{style:white on green} J8 {style:black on white}+===={style:reset} {style:white on green}| {J8:{style} col1}{style:white on green} {style:black on white}| USB{style:reset} {style:white on green}| {style:black on white}+===={style:reset} {style:white on green}| {style:bold}Pi Model {model:3s}V{pcb_revision:3s}{style:normal} |{style:reset} {style:white on green}| {style:on black}+----+{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|D|{style:on green} {style:on black}|SoC |{style:on green} {style:black on white}| USB{style:reset} {style:white on green}| {style:on black}|S|{style:on green} {style:on black}| |{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|I|{style:on green} {style:on black}+----+{style:on green} |{style:reset} {style:white on green}| {style:on black}|C|{style:on green} {style:black on white}+======{style:reset} {style:white on green}| {style:on black}|S|{style:on green} {style:black on white}| Net{style:reset} {style:white on green}| {style:black on white}pwr{style:white on green} {style:black on white}|HDMI|{style:white on green} {style:on black}|I||A|{style:on green} {style:black on white}+======{style:reset} {style:white on green}`-{style:black on white}| |{style:white on green}--------{style:black on white}| |{style:white on green}----{style:on black}|V|{style:on green}-------'{style:reset}""" APLUS_BOARD = """\ {style:white on green},--------------------------.{style:reset} {style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset} {style:white on green}| {J8:{style} col1}{style:white on green} |{style:reset} {style:white on green}| |{style:reset} {style:white on green}| {style:bold}Pi Model {model:3s}V{pcb_revision:3s}{style:normal} |{style:reset} {style:white on green}| {style:on black}+----+{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|D|{style:on green} {style:on black}|SoC |{style:on green} {style:black on white}| USB{style:reset} {style:white on green}| {style:on black}|S|{style:on green} {style:on black}| |{style:on green} {style:black on white}+===={style:reset} {style:white on green}| {style:on black}|I|{style:on green} {style:on black}+----+{style:on green} |{style:reset} {style:white on green}| {style:on black}|C|{style:on green} |{style:reset} {style:white on green}| {style:on black}|S|{style:on green} |{style:reset} {style:white on green}| {style:black on white}pwr{style:white on green} {style:black on white}|HDMI|{style:white on green} {style:on black}|I||A|{style:on green} |{style:reset} {style:white on green}`-{style:black on white}| |{style:white on green}--------{style:black on white}| |{style:white on green}----{style:on black}|V|{style:on green}-'{style:reset}""" ZERO12_BOARD = """\ {style:white on green},-------------------------.{style:reset} {style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset} {style:white on green}| {J8:{style} col1}{style:white on green} |{style:reset} {style:black on white}---+{style:white on green} {style:on black}+---+{style:on green} {style:bold}PiZero{style:normal} |{style:reset} {style:black on white} sd|{style:white on green} {style:on black}|SoC|{style:on green} {style:bold}V{pcb_revision:3s}{style:normal} |{style:reset} {style:black on white}---+|hdmi|{style:white on green} {style:on black}+---+{style:on green} {style:black on white}usb{style:on green} {style:black on white}pwr{style:white on green} |{style:reset} {style:white on green}`---{style:black on white}| |{style:white on green}--------{style:black on white}| |{style:white on green}-{style:black on white}| |{style:white on green}-'{style:reset}""" ZERO13_BOARD = """\ {style:white on green}.-------------------------.{style:reset} {style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset} {style:white on green}| {J8:{style} col1}{style:white on green} {style:black on white}|c{style:reset} {style:black on white}---+{style:white on green} {style:on black}+---+{style:on green} {style:bold}Pi{model:6s}{style:normal}{style:black on white}|s{style:reset} {style:black on white} sd|{style:white on green} {style:on black}|SoC|{style:on green} {style:bold}V{pcb_revision:3s}{style:normal} {style:black on white}|i{style:reset} {style:black on white}---+|hdmi|{style:white on green} {style:on black}+---+{style:on green} {style:black on white}usb{style:on green} {style:on white}pwr{style:white on green} |{style:reset} {style:white on green}`---{style:black on white}| |{style:white on green}--------{style:black on white}| |{style:white on green}-{style:black on white}| |{style:white on green}-'{style:reset}""" CM_BOARD = """\ {style:white on green}+-----------------------------------------------------------------------------------------------------------------------+{style:reset} {style:white on green}| Raspberry Pi Compute Module |{style:reset} {style:white on green}| |{style:reset} {style:white on green}| You were expecting more detail? Sorry, the Compute Module's a bit hard to do right now! |{style:reset} {style:white on green}| |{style:reset} {style:white on green}| |{style:reset} {style:white on green}||||||||||||||||||||-||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||{style:reset}""" # Pin maps for various board revisions and headers REV1_P1 = { # pin func pullup pin func pullup 1: (V3_3, False), 2: (V5, False), 3: (GPIO0, True), 4: (V5, False), 5: (GPIO1, True), 6: (GND, False), 7: (GPIO4, False), 8: (GPIO14, False), 9: (GND, False), 10: (GPIO15, False), 11: (GPIO17, False), 12: (GPIO18, False), 13: (GPIO21, False), 14: (GND, False), 15: (GPIO22, False), 16: (GPIO23, False), 17: (V3_3, False), 18: (GPIO24, False), 19: (GPIO10, False), 20: (GND, False), 21: (GPIO9, False), 22: (GPIO25, False), 23: (GPIO11, False), 24: (GPIO8, False), 25: (GND, False), 26: (GPIO7, False), } REV2_P1 = { 1: (V3_3, False), 2: (V5, False), 3: (GPIO2, True), 4: (V5, False), 5: (GPIO3, True), 6: (GND, False), 7: (GPIO4, False), 8: (GPIO14, False), 9: (GND, False), 10: (GPIO15, False), 11: (GPIO17, False), 12: (GPIO18, False), 13: (GPIO27, False), 14: (GND, False), 15: (GPIO22, False), 16: (GPIO23, False), 17: (V3_3, False), 18: (GPIO24, False), 19: (GPIO10, False), 20: (GND, False), 21: (GPIO9, False), 22: (GPIO25, False), 23: (GPIO11, False), 24: (GPIO8, False), 25: (GND, False), 26: (GPIO7, False), } REV2_P5 = { 1: (V5, False), 2: (V3_3, False), 3: (GPIO28, False), 4: (GPIO29, False), 5: (GPIO30, False), 6: (GPIO31, False), 7: (GND, False), 8: (GND, False), } PLUS_J8 = { 1: (V3_3, False), 2: (V5, False), 3: (GPIO2, True), 4: (V5, False), 5: (GPIO3, True), 6: (GND, False), 7: (GPIO4, False), 8: (GPIO14, False), 9: (GND, False), 10: (GPIO15, False), 11: (GPIO17, False), 12: (GPIO18, False), 13: (GPIO27, False), 14: (GND, False), 15: (GPIO22, False), 16: (GPIO23, False), 17: (V3_3, False), 18: (GPIO24, False), 19: (GPIO10, False), 20: (GND, False), 21: (GPIO9, False), 22: (GPIO25, False), 23: (GPIO11, False), 24: (GPIO8, False), 25: (GND, False), 26: (GPIO7, False), 27: (GPIO0, False), 28: (GPIO1, False), 29: (GPIO5, False), 30: (GND, False), 31: (GPIO6, False), 32: (GPIO12, False), 33: (GPIO13, False), 34: (GND, False), 35: (GPIO19, False), 36: (GPIO16, False), 37: (GPIO26, False), 38: (GPIO20, False), 39: (GND, False), 40: (GPIO21, False), } CM_SODIMM = { 1: (GND, False), 2: ('EMMC DISABLE N', False), 3: (GPIO0, False), 4: (NC, False), 5: (GPIO1, False), 6: (NC, False), 7: (GND, False), 8: (NC, False), 9: (GPIO2, False), 10: (NC, False), 11: (GPIO3, False), 12: (NC, False), 13: (GND, False), 14: (NC, False), 15: (GPIO4, False), 16: (NC, False), 17: (GPIO5, False), 18: (NC, False), 19: (GND, False), 20: (NC, False), 21: (GPIO6, False), 22: (NC, False), 23: (GPIO7, False), 24: (NC, False), 25: (GND, False), 26: (GND, False), 27: (GPIO8, False), 28: (GPIO28, False), 29: (GPIO9, False), 30: (GPIO29, False), 31: (GND, False), 32: (GND, False), 33: (GPIO10, False), 34: (GPIO30, False), 35: (GPIO11, False), 36: (GPIO31, False), 37: (GND, False), 38: (GND, False), 39: ('GPIO0-27 VREF', False), 40: ('GPIO0-27 VREF', False), # Gap in SODIMM pins 41: ('GPIO28-45 VREF', False), 42: ('GPIO28-45 VREF', False), 43: (GND, False), 44: (GND, False), 45: (GPIO12, False), 46: (GPIO32, False), 47: (GPIO13, False), 48: (GPIO33, False), 49: (GND, False), 50: (GND, False), 51: (GPIO14, False), 52: (GPIO34, False), 53: (GPIO15, False), 54: (GPIO35, False), 55: (GND, False), 56: (GND, False), 57: (GPIO16, False), 58: (GPIO36, False), 59: (GPIO17, False), 60: (GPIO37, False), 61: (GND, False), 62: (GND, False), 63: (GPIO18, False), 64: (GPIO38, False), 65: (GPIO19, False), 66: (GPIO39, False), 67: (GND, False), 68: (GND, False), 69: (GPIO20, False), 70: (GPIO40, False), 71: (GPIO21, False), 72: (GPIO41, False), 73: (GND, False), 74: (GND, False), 75: (GPIO22, False), 76: (GPIO42, False), 77: (GPIO23, False), 78: (GPIO43, False), 79: (GND, False), 80: (GND, False), 81: (GPIO24, False), 82: (GPIO44, False), 83: (GPIO25, False), 84: (GPIO45, False), 85: (GND, False), 86: (GND, False), 87: (GPIO26, False), 88: ('GPIO46 1V8', False), 89: (GPIO27, False), 90: ('GPIO47 1V8', False), 91: (GND, False), 92: (GND, False), 93: ('DSI0 DN1', False), 94: ('DSI1 DP0', False), 95: ('DSI0 DP1', False), 96: ('DSI1 DN0', False), 97: (GND, False), 98: (GND, False), 99: ('DSI0 DN0', False), 100: ('DSI1 CP', False), 101: ('DSI0 DP0', False), 102: ('DSI1 CN', False), 103: (GND, False), 104: (GND, False), 105: ('DSI0 CN', False), 106: ('DSI1 DP3', False), 107: ('DSI0 CP', False), 108: ('DSI1 DN3', False), 109: (GND, False), 110: (GND, False), 111: ('HDMI CK N', False), 112: ('DSI1 DP2', False), 113: ('HDMI CK P', False), 114: ('DSI1 DN2', False), 115: (GND, False), 116: (GND, False), 117: ('HDMI D0 N', False), 118: ('DSI1 DP1', False), 119: ('HDMI D0 P', False), 120: ('DSI1 DN1', False), 121: (GND, False), 122: (GND, False), 123: ('HDMI D1 N', False), 124: (NC, False), 125: ('HDMI D1 P', False), 126: (NC, False), 127: (GND, False), 128: (NC, False), 129: ('HDMI D2 N', False), 130: (NC, False), 131: ('HDMI D2 P', False), 132: (NC, False), 133: (GND, False), 134: (GND, False), 135: ('CAM1 DP3', False), 136: ('CAM0 DP0', False), 137: ('CAM1 DN3', False), 138: ('CAM0 DN0', False), 139: (GND, False), 140: (GND, False), 141: ('CAM1 DP2', False), 142: ('CAM0 CP', False), 143: ('CAM1 DN2', False), 144: ('CAM0 CN', False), 145: (GND, False), 146: (GND, False), 147: ('CAM1 CP', False), 148: ('CAM0 DP1', False), 149: ('CAM1 CN', False), 150: ('CAM0 DN1', False), 151: (GND, False), 152: (GND, False), 153: ('CAM1 DP1', False), 154: (NC, False), 155: ('CAM1 DN1', False), 156: (NC, False), 157: (GND, False), 158: (NC, False), 159: ('CAM1 DP0', False), 160: (NC, False), 161: ('CAM1 DN0', False), 162: (NC, False), 163: (GND, False), 164: (GND, False), 165: ('USB DP', False), 166: ('TVDAC', False), 167: ('USB DM', False), 168: ('USB OTGID', False), 169: (GND, False), 170: (GND, False), 171: ('HDMI CEC', False), 172: ('VC TRST N', False), 173: ('HDMI SDA', False), 174: ('VC TDI', False), 175: ('HDMI SCL', False), 176: ('VC TMS', False), 177: ('RUN', False), 178: ('VC TDO', False), 179: ('VDD CORE', False), 180: ('VC TCK', False), 181: (GND, False), 182: (GND, False), 183: (V1_8, False), 184: (V1_8, False), 185: (V1_8, False), 186: (V1_8, False), 187: (GND, False), 188: (GND, False), 189: ('VDAC', False), 190: ('VDAC', False), 191: (V3_3, False), 192: (V3_3, False), 193: (V3_3, False), 194: (V3_3, False), 195: (GND, False), 196: (GND, False), 197: ('VBAT', False), 198: ('VBAT', False), 199: ('VBAT', False), 200: ('VBAT', False), } CM3_SODIMM = CM_SODIMM.copy() CM3_SODIMM.update({ 4: ('NC / SDX VREF', False), 6: ('NC / SDX VREF', False), 8: (GND, False), 10: ('NC / SDX CLK', False), 12: ('NC / SDX CMD', False), 14: (GND, False), 16: ('NC / SDX D0', False), 18: ('NC / SDX D1', False), 20: (GND, False), 22: ('NC / SDX D2', False), 24: ('NC / SDX D3', False), 88: ('HDMI HPD N 1V8', False), 90: ('EMMC EN N 1V8', False), }) # The following data is sourced from a combination of the following locations: # # http://elinux.org/RPi_HardwareHistory # http://elinux.org/RPi_Low-level_peripherals # https://git.drogon.net/?p=wiringPi;a=blob;f=wiringPi/wiringPi.c#l807 PI_REVISIONS = { # rev model pcb_rev released soc manufacturer ram storage usb eth wifi bt csi dsi headers board 0x2: ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, REV1_BOARD, ), 0x3: ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, REV1_BOARD, ), 0x4: ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD, ), 0x5: ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD, ), 0x6: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD, ), 0x7: ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, A_BOARD, ), 0x8: ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, A_BOARD, ), 0x9: ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, A_BOARD, ), 0xd: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD, ), 0xe: ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD, ), 0xf: ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD, ), 0x10: ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'J8': PLUS_J8}, BPLUS_BOARD, ), 0x11: ('CM', '1.1', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, CM_BOARD, ), 0x12: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'J8': PLUS_J8}, APLUS_BOARD, ), 0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'J8': PLUS_J8}, BPLUS_BOARD, ), 0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Embest', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, CM_BOARD, ), 0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Embest', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'J8': PLUS_J8}, APLUS_BOARD, ), } # ANSI color codes, for the pretty printers (nothing comprehensive, just enough # for our purposes) class Style(object): def __init__(self, color=None): self.color = self._term_supports_color() if color is None else bool(color) self.effects = { 'reset': 0, 'bold': 1, 'normal': 22, } self.colors = { 'black': 0, 'red': 1, 'green': 2, 'yellow': 3, 'blue': 4, 'magenta': 5, 'cyan': 6, 'white': 7, 'default': 9, } @staticmethod def _term_supports_color(): try: stdout_fd = sys.stdout.fileno() except IOError: return False else: is_a_tty = os.isatty(stdout_fd) is_windows = sys.platform.startswith('win') return is_a_tty and not is_windows @classmethod def from_style_content(cls, format_spec): specs = set(format_spec.split()) style = specs & {'mono', 'color'} content = specs - style if len(style) > 1: raise ValueError('cannot specify both mono and color styles') try: style = style.pop() except KeyError: style = 'color' if cls._term_supports_color() else 'mono' if len(content) > 1: raise ValueError('cannot specify more than one content element') try: content = content.pop() except KeyError: content = 'full' return cls(style == 'color'), content def __call__(self, format_spec): specs = format_spec.split() codes = [] fore = True for spec in specs: if spec == 'on': fore = False else: try: codes.append(self.effects[spec]) except KeyError: try: if fore: codes.append(30 + self.colors[spec]) else: codes.append(40 + self.colors[spec]) except KeyError: raise ValueError('invalid format specification "%s"' % spec) if self.color: return '\x1b[%sm' % (';'.join(str(code) for code in codes)) else: return '' def __format__(self, format_spec): if format_spec == '': return 'color' if self.color else 'mono' else: return self(format_spec) class PinInfo(namedtuple('PinInfo', ( 'number', 'function', 'pull_up', 'row', 'col', ))): """ This class is a :func:`~collections.namedtuple` derivative used to represent information about a pin present on a GPIO header. The following attributes are defined: .. attribute:: number An integer containing the physical pin number on the header (starting from 1 in accordance with convention). .. attribute:: function A string describing the function of the pin. Some common examples include "GND" (for pins connecting to ground), "3V3" (for pins which output 3.3 volts), "GPIO9" (for GPIO9 in the Broadcom numbering scheme), etc. .. attribute:: pull_up A bool indicating whether the pin has a physical pull-up resistor permanently attached (this is usually ``False`` but GPIO2 and GPIO3 are *usually* ``True``). This is used internally by gpiozero to raise errors when pull-down is requested on a pin with a physical pull-up resistor. .. attribute:: row An integer indicating on which row the pin is physically located in the header (1-based) .. attribute:: col An integer indicating in which column the pin is physically located in the header (1-based) """ __slots__ = () # workaround python issue #24931 class HeaderInfo(namedtuple('HeaderInfo', ( 'name', 'rows', 'columns', 'pins', ))): """ This class is a :func:`~collections.namedtuple` derivative used to represent information about a pin header on a board. The object can be used in a format string with various custom specifications:: from gpiozero import * print('{0}'.format(pi_info().headers['J8'])) print('{0:full}'.format(pi_info().headers['J8'])) print('{0:col2}'.format(pi_info().headers['P1'])) print('{0:row1}'.format(pi_info().headers['P1'])) `'color'` and `'mono'` can be prefixed to format specifications to force the use of `ANSI color codes`_. If neither is specified, ANSI codes will only be used if stdout is detected to be a tty:: print('{0:color row2}'.format(pi_info().headers['J8'])) # force use of ANSI codes print('{0:mono row2}'.format(pi_info().headers['P1'])) # force plain ASCII The following attributes are defined: .. automethod:: pprint .. attribute:: name The name of the header, typically as it appears silk-screened on the board (e.g. "P1" or "J8"). .. attribute:: rows The number of rows on the header. .. attribute:: columns The number of columns on the header. .. attribute:: pins A dictionary mapping physical pin numbers to :class:`PinInfo` tuples. .. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code """ __slots__ = () # workaround python issue #24931 def _func_style(self, function, style): if function == V5: return style('bold red') elif function in (V3_3, V1_8): return style('bold cyan') elif function in (GND, NC): return style('bold black') elif function.startswith('GPIO') and function[4:].isdigit(): return style('bold green') else: return style('yellow') def _format_full(self, style): Cell = namedtuple('Cell', ('content', 'align', 'style')) lines = [] for row in range(self.rows): line = [] for col in range(self.columns): pin = (row * self.columns) + col + 1 try: pin = self.pins[pin] cells = [ Cell(pin.function, '><'[col % 2], self._func_style(pin.function, style)), Cell('(%d)' % pin.number, '><'[col % 2], ''), ] if col % 2: cells = reversed(cells) line.extend(cells) except KeyError: line.append(Cell('', '<', '')) lines.append(line) cols = list(zip(*lines)) col_lens = [max(len(cell.content) for cell in col) for col in cols] lines = [ ' '.join( '{cell.style}{cell.content:{cell.align}{width}s}{style:reset}'.format( cell=cell, width=width, style=style) for cell, width, align in zip(line, col_lens, cycle('><'))) for line in lines ] return '\n'.join(lines) def _format_pin(self, pin, style): return ''.join(( style('on black'), ( ' ' if pin is None else self._func_style(pin.function, style) + ('1' if pin.number == 1 else 'o') ), style('reset') )) def _format_row(self, row, style): if row > self.rows: raise ValueError('invalid row %d for header %s' % (row, self.name)) start_pin = (row - 1) * self.columns + 1 return ''.join( self._format_pin(pin, style) for n in range(start_pin, start_pin + self.columns) for pin in (self.pins.get(n),) ) def _format_col(self, col, style): if col > self.columns: raise ValueError('invalid col %d for header %s' % (col, self.name)) return ''.join( self._format_pin(pin, style) for n in range(col, self.rows * self.columns + 1, self.columns) for pin in (self.pins.get(n),) ) def __format__(self, format_spec): style, content = Style.from_style_content(format_spec) if content == 'full': return self._format_full(style) elif content.startswith('row') and content[3:].isdigit(): return self._format_row(int(content[3:]), style) elif content.startswith('col') and content[3:].isdigit(): return self._format_col(int(content[3:]), style) def pprint(self, color=None): """ Pretty-print a diagram of the header pins. If *color* is ``None`` (the default, the diagram will include ANSI color codes if stdout is a color-capable terminal). Otherwise *color* can be set to ``True`` or ``False`` to force color or monochrome output. """ print('{0:{style} full}'.format(self, style=Style(color))) class PiBoardInfo(namedtuple('PiBoardInfo', ( 'revision', 'model', 'pcb_revision', 'released', 'soc', 'manufacturer', 'memory', 'storage', 'usb', 'ethernet', 'wifi', 'bluetooth', 'csi', 'dsi', 'headers', 'board', ))): """ This class is a :func:`~collections.namedtuple` derivative used to represent information about a particular model of Raspberry Pi. While it is a tuple, it is strongly recommended that you use the following named attributes to access the data contained within. The object can be used in format strings with various custom format specifications:: from gpiozero import * print('{0}'.format(pi_info())) print('{0:full}'.format(pi_info())) print('{0:board}'.format(pi_info())) print('{0:specs}'.format(pi_info())) print('{0:headers}'.format(pi_info())) `'color'` and `'mono'` can be prefixed to format specifications to force the use of `ANSI color codes`_. If neither is specified, ANSI codes will only be used if stdout is detected to be a tty:: print('{0:color board}'.format(pi_info())) # force use of ANSI codes print('{0:mono board}'.format(pi_info())) # force plain ASCII .. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code .. automethod:: physical_pin .. automethod:: physical_pins .. automethod:: pprint .. automethod:: pulled_up .. attribute:: revision A string indicating the revision of the Pi. This is unique to each revision and can be considered the "key" from which all other attributes are derived. However, in itself the string is fairly meaningless. .. attribute:: model A string containing the model of the Pi (for example, "B", "B+", "A+", "2B", "CM" (for the Compute Module), or "Zero"). .. attribute:: pcb_revision A string containing the PCB revision number which is silk-screened onto the Pi (on some models). .. note:: This is primarily useful to distinguish between the model B revision 1.0 and 2.0 (not to be confused with the model 2B) which had slightly different pinouts on their 26-pin GPIO headers. .. attribute:: released A string containing an approximate release date for this revision of the Pi (formatted as yyyyQq, e.g. 2012Q1 means the first quarter of 2012). .. attribute:: soc A string indicating the SoC (`system on a chip`_) that this revision of the Pi is based upon. .. attribute:: manufacturer A string indicating the name of the manufacturer (usually "Sony" but a few others exist). .. attribute:: memory An integer indicating the amount of memory (in Mb) connected to the SoC. .. note:: This can differ substantially from the amount of RAM available to the operating system as the GPU's memory is shared with the CPU. When the camera module is activated, at least 128Mb of RAM is typically reserved for the GPU. .. attribute:: storage A string indicating the type of bootable storage used with this revision of Pi, e.g. "SD", "MicroSD", or "eMMC" (for the Compute Module). .. attribute:: usb An integer indicating how many USB ports are physically present on this revision of the Pi. .. note:: This does *not* include the micro-USB port used to power the Pi. .. attribute:: ethernet An integer indicating how many Ethernet ports are physically present on this revision of the Pi. .. attribute:: wifi A bool indicating whether this revision of the Pi has wifi built-in. .. attribute:: bluetooth A bool indicating whether this revision of the Pi has bluetooth built-in. .. attribute:: csi An integer indicating the number of CSI (camera) ports available on this revision of the Pi. .. attribute:: dsi An integer indicating the number of DSI (display) ports available on this revision of the Pi. .. attribute:: headers A dictionary which maps header labels to :class:`HeaderInfo` tuples. For example, to obtain information about header P1 you would query ``headers['P1']``. To obtain information about pin 12 on header J8 you would query ``headers['J8'].pins[12]``. A rendered version of this data can be obtained by using the :class:`PiBoardInfo` object in a format string:: from gpiozero import * print('{0:headers}'.format(pi_info())) .. attribute:: board An ASCII art rendition of the board, primarily intended for console pretty-print usage. A more usefully rendered version of this data can be obtained by using the :class:`PiBoardInfo` object in a format string. For example:: from gpiozero import * print('{0:board}'.format(pi_info())) .. _system on a chip: https://en.wikipedia.org/wiki/System_on_a_chip """ __slots__ = () # workaround python issue #24931 @classmethod def from_revision(cls, revision): if revision & 0x800000: # New-style revision, parse information from bit-pattern: # # MSB -----------------------> LSB # uuuuuuuuFMMMCCCCPPPPTTTTTTTTRRRR # # uuuuuuuu - Unused # F - New flag (1=valid new-style revision, 0=old-style) # MMM - Memory size (0=256, 1=512, 2=1024) # CCCC - Manufacturer (0=Sony, 1=Egoman, 2=Embest, 3=Sony Japan) # PPPP - Processor (0=2835, 1=2836, 2=2837) # TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM, # 8=3B, 9=Zero, 10=CM3, 12=Zero W) # RRRR - Revision (0, 1, 2, etc.) revcode_memory = (revision & 0x700000) >> 20 revcode_manufacturer = (revision & 0xf0000) >> 16 revcode_processor = (revision & 0xf000) >> 12 revcode_type = (revision & 0xff0) >> 4 revcode_revision = (revision & 0x0f) try: model = { 0: 'A', 1: 'B', 2: 'A+', 3: 'B+', 4: '2B', 6: 'CM', 8: '3B', 9: 'Zero', 10: 'CM3', 12: 'Zero W', }.get(revcode_type, '???') if model in ('A', 'B'): pcb_revision = { 0: '1.0', # is this right? 1: '1.0', 2: '2.0', }.get(revcode_revision, 'Unknown') else: pcb_revision = '1.%d' % revcode_revision soc = { 0: 'BCM2835', 1: 'BCM2836', 2: 'BCM2837', }.get(revcode_processor, 'Unknown') manufacturer = { 0: 'Sony', 1: 'Egoman', 2: 'Embest', 3: 'Sony Japan', }.get(revcode_manufacturer, 'Unknown') memory = { 0: 256, 1: 512, 2: 1024, }.get(revcode_memory, None) released = { 'A': '2013Q1', 'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4', 'A+': '2014Q4' if memory == 512 else '2016Q3', 'B+': '2014Q3', '2B': '2015Q1' if pcb_revision in ('1.0', '1.1') else '2016Q3', 'CM': '2014Q2', '3B': '2016Q1' if manufacturer in ('Sony', 'Embest') else '2016Q4', 'Zero': '2015Q4' if pcb_revision == '1.2' else '2016Q2', 'CM3': '2017Q1', 'Zero W': '2017Q1', }.get(model, 'Unknown') storage = { 'A': 'SD', 'B': 'SD', 'CM': 'eMMC', 'CM3': 'eMMC / off-board', }.get(model, 'MicroSD') usb = { 'A': 1, 'A+': 1, 'Zero': 1, 'Zero W': 1, 'B': 2, 'CM': 0, 'CM3': 1, }.get(model, 4) ethernet = { 'A': 0, 'A+': 0, 'Zero': 0, 'Zero W': 0, 'CM': 0, 'CM3': 0, }.get(model, 1) wifi = { '3B': True, 'Zero W': True, }.get(model, False) bluetooth = { '3B': True, 'Zero W': True, }.get(model, False) csi = { 'Zero': 0 if pcb_revision == '1.0' else 1, 'Zero W': 1, 'CM': 2, 'CM3': 2, }.get(model, 1) dsi = { 'Zero': 0, 'Zero W': 0, }.get(model, csi) headers = { 'A': {'P1': REV2_P1, 'P5': REV2_P5}, 'B': {'P1': REV1_P1} if pcb_revision == '1.0' else {'P1': REV2_P1, 'P5': REV2_P5}, 'CM': {'SODIMM': CM_SODIMM}, 'CM3': {'SODIMM': CM3_SODIMM}, }.get(model, {'J8': PLUS_J8}) board = { 'A': A_BOARD, 'B': REV1_BOARD if pcb_revision == '1.0' else REV2_BOARD, 'A+': APLUS_BOARD, 'CM': CM_BOARD, 'CM3': CM_BOARD, 'Zero': ZERO12_BOARD if pcb_revision == '1.2' else ZERO13_BOARD, 'Zero W': ZERO13_BOARD, }.get(model, BPLUS_BOARD) except KeyError: raise PinUnknownPi('unable to parse new-style revision "%x"' % revision) else: # Old-style revision, use the lookup table try: ( model, pcb_revision, released, soc, manufacturer, memory, storage, usb, ethernet, wifi, bluetooth, csi, dsi, headers, board, ) = PI_REVISIONS[revision] except KeyError: raise PinUnknownPi('unknown old-style revision "%x"' % revision) headers = { header: HeaderInfo(name=header, rows=max(header_data) // 2, columns=2, pins={ number: PinInfo( number=number, function=function, pull_up=pull_up, row=row + 1, col=col + 1) for number, (function, pull_up) in header_data.items() for row, col in (divmod(number, 2),) }) for header, header_data in headers.items() } return cls( '%04x' % revision, model, pcb_revision, released, soc, manufacturer, memory, storage, usb, ethernet, wifi, bluetooth, csi, dsi, headers, board, ) def physical_pins(self, function): """ Return the physical pins supporting the specified *function* as tuples of ``(header, pin_number)`` where *header* is a string specifying the header containing the *pin_number*. Note that the return value is a :class:`set` which is not indexable. Use :func:`physical_pin` if you are expecting a single return value. :param str function: The pin function you wish to search for. Usually this is something like "GPIO9" for Broadcom GPIO pin 9, or "GND" for all the pins connecting to electrical ground. """ return { (header, pin.number) for (header, info) in self.headers.items() for pin in info.pins.values() if pin.function == function } def physical_pin(self, function): """ Return the physical pin supporting the specified *function*. If no pins support the desired *function*, this function raises :exc:`PinNoPins`. If multiple pins support the desired *function*, :exc:`PinMultiplePins` will be raised (use :func:`physical_pins` if you expect multiple pins in the result, such as for electrical ground). :param str function: The pin function you wish to search for. Usually this is something like "GPIO9" for Broadcom GPIO pin 9. """ result = self.physical_pins(function) if len(result) > 1: raise PinMultiplePins('multiple pins can be used for %s' % function) elif result: return result.pop() else: raise PinNoPins('no pins can be used for %s' % function) def pulled_up(self, function): """ Returns a bool indicating whether a physical pull-up is attached to the pin supporting the specified *function*. Either :exc:`PinNoPins` or :exc:`PinMultiplePins` may be raised if the function is not associated with a single pin. :param str function: The pin function you wish to determine pull-up for. Usually this is something like "GPIO9" for Broadcom GPIO pin 9. """ try: header, number = self.physical_pin(function) except PinNoPins: return False else: return self.headers[header].pins[number].pull_up def __repr__(self): return '{cls}({fields})'.format( cls=self.__class__.__name__, fields=', '.join( ( '{name}=...' if name in ('headers', 'board') else '{name}={value!r}').format(name=name, value=value) for name, value in zip(self._fields, self) ) ) def __format__(self, format_spec): style, content = Style.from_style_content(format_spec) if content == 'full': return dedent("""\ {self:{style} board} {self:{style} specs} {self:{style} headers}""" ).format(self=self, style=style) elif content == 'board': kw = self._asdict() kw.update({ name: header for name, header in self.headers.items() }) return self.board.format(style=style, **kw) elif content == 'specs': return dedent("""\ {style:bold}Revision {style:reset}: {revision} {style:bold}SoC {style:reset}: {soc} {style:bold}RAM {style:reset}: {memory}Mb {style:bold}Storage {style:reset}: {storage} {style:bold}USB ports {style:reset}: {usb} {style:yellow}(excluding power){style:reset} {style:bold}Ethernet ports {style:reset}: {ethernet} {style:bold}Wi-fi {style:reset}: {wifi} {style:bold}Bluetooth {style:reset}: {bluetooth} {style:bold}Camera ports (CSI) {style:reset}: {csi} {style:bold}Display ports (DSI){style:reset}: {dsi}""" ).format(style=style, **self._asdict()) elif content == 'headers': return '\n\n'.join( dedent("""\ {style:bold}{header.name}{style:reset}: {header:{style} full}""" ).format(header=header, style=style) for header in sorted(self.headers.values(), key=attrgetter('name')) ) def pprint(self, color=None): """ Pretty-print a representation of the board along with header diagrams. If *color* is ``None`` (the default), the diagram will include ANSI color codes if stdout is a color-capable terminal. Otherwise *color* can be set to ``True`` or ``False`` to force color or monochrome output. """ print('{0:{style} full}'.format(self, style=Style(color))) def pi_info(revision=None): """ Returns a :class:`PiBoardInfo` instance containing information about a *revision* of the Raspberry Pi. :param str revision: The revision of the Pi to return information about. If this is omitted or ``None`` (the default), then the library will attempt to determine the model of Pi it is running on and return information about that. """ if revision is None: # The reason this import is located here is to avoid a circular # dependency; devices->pins.local->pins.data->devices from ..devices import Device result = Device.pin_factory.pi_info if result is None: raise PinUnknownPi('The default pin_factory is not attached to a Pi') else: return result else: if isinstance(revision, bytes): revision = revision.decode('ascii') if isinstance(revision, str): revision = int(revision, base=16) else: # be nice to people passing an int (or something numeric anyway) revision = int(revision) return PiBoardInfo.from_revision(revision) gpiozero-1.4.1/gpiozero/pins/__init__.py0000644000175000017500000006442213165454031020165 0ustar davedave00000000000000# vim: set fileencoding=utf-8: from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') from weakref import ref from collections import defaultdict from threading import Lock from ..exc import ( PinInvalidFunction, PinSetInput, PinFixedPull, PinUnsupported, PinSPIUnsupported, PinPWMUnsupported, PinEdgeDetectUnsupported, SPIFixedClockMode, SPIFixedBitOrder, SPIFixedSelect, SPIFixedWordSize, GPIOPinInUse, ) class Factory(object): """ Generates pins and SPI interfaces for devices. This is an abstract base class for pin factories. Descendents *may* override the following methods, if applicable: * :meth:`close` * :meth:`reserve_pins` * :meth:`release_pins` * :meth:`release_all` * :meth:`pin` * :meth:`spi` * :meth:`_get_pi_info` """ def __init__(self): self._reservations = defaultdict(list) self._res_lock = Lock() def reserve_pins(self, requester, *pins): """ Called to indicate that the device reserves the right to use the specified *pins*. This should be done during device construction. If pins are reserved, you must ensure that the reservation is released by eventually called :meth:`release_pins`. """ with self._res_lock: for pin in pins: for reserver_ref in self._reservations[pin]: reserver = reserver_ref() if reserver is not None and requester._conflicts_with(reserver): raise GPIOPinInUse('pin %s is already in use by %r' % (pin, reserver)) self._reservations[pin].append(ref(requester)) def release_pins(self, reserver, *pins): """ Releases the reservation of *reserver* against *pins*. This is typically called during :meth:`Device.close` to clean up reservations taken during construction. Releasing a reservation that is not currently held will be silently ignored (to permit clean-up after failed / partial construction). """ with self._res_lock: for pin in pins: self._reservations[pin] = [ ref for ref in self._reservations[pin] if ref() not in (reserver, None) # may as well clean up dead refs ] def release_all(self, reserver): """ Releases all pin reservations taken out by *reserver*. See :meth:`release_pins` for further information). """ with self._res_lock: self._reservations = defaultdict(list, { pin: [ ref for ref in conflictors if ref() not in (reserver, None) ] for pin, conflictors in self._reservations.items() }) def close(self): """ Closes the pin factory. This is expected to clean up all resources manipulated by the factory. It it typically called at script termination. """ pass def pin(self, spec): """ Creates an instance of a :class:`Pin` descendent representing the specified pin. .. warning:: Descendents must ensure that pin instances representing the same hardware are identical; i.e. two separate invocations of :meth:`pin` for the same pin specification must return the same object. """ raise PinUnsupported("Individual pins are not supported by this pin factory") def spi(self, **spi_args): """ Returns an instance of an :class:`SPI` interface, for the specified SPI *port* and *device*, or for the specified pins (*clock_pin*, *mosi_pin*, *miso_pin*, and *select_pin*). Only one of the schemes can be used; attempting to mix *port* and *device* with pin numbers will raise :exc:`SPIBadArgs`. """ raise PinSPIUnsupported('SPI not supported by this pin factory') def _get_pi_info(self): return None pi_info = property( lambda self: self._get_pi_info(), doc="""\ Returns a :class:`PiBoardInfo` instance representing the Pi that instances generated by this factory will be attached to. If the pins represented by this class are not *directly* attached to a Pi (e.g. the pin is attached to a board attached to the Pi, or the pins are not on a Pi at all), this may return ``None``. """) class Pin(object): """ Abstract base class representing a pin attached to some form of controller, be it GPIO, SPI, ADC, etc. Descendents should override property getters and setters to accurately represent the capabilities of pins. Descendents *must* override the following methods: * :meth:`_get_function` * :meth:`_set_function` * :meth:`_get_state` Descendents *may* additionally override the following methods, if applicable: * :meth:`close` * :meth:`output_with_state` * :meth:`input_with_pull` * :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` """ def __repr__(self): return "" 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): if value != "input": raise PinInvalidFunction( "Cannot set the function of pin %r to %s" % (self, value)) 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. """) 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): .. code-block:: text HIGH - - - - > ,---------------------- | | LOW ----------------' Descendents which implement analog, or analog-like capabilities can return values between 0 and 1. For example, pins implementing PWM (where :attr:`frequency` is not ``None``) return a value between 0.0 and 1.0 representing the current PWM duty cycle. 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``. For example, if :attr:`edges` is currently "rising", :attr:`bounce` is currently 5/1000 (5ms), then the waveform below will only fire :attr:`when_changed` on two occasions despite there being three rising edges: .. code-block:: text TIME 0...1...2...3...4...5...6...7...8...9...10..11..12 ms bounce elimination |===================| |============== HIGH - - - - > ,--. ,--------------. ,--. | | | | | | | | | | | | LOW ----------------' `-' `-' `----------- : : : : when_changed when_changed fires fires 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 'none' 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", "falling", or "none": .. code-block:: text HIGH - - - - > ,--------------. | | | | LOW --------------------' `-------------- : : : : Fires when_changed "both" "both" when edges is ... "rising" "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`. """) class SPI(object): """ Abstract interface for `Serial Peripheral Interface`_ (SPI) implementations. Descendents *must* override the following methods: * :meth:`transfer` * :meth:`_get_clock_mode` Descendents *may* override the following methods, if applicable: * :meth:`read` * :meth:`write` * :meth:`_set_clock_mode` * :meth:`_get_lsb_first` * :meth:`_set_lsb_first` * :meth:`_get_select_high` * :meth:`_set_select_high` * :meth:`_get_bits_per_word` * :meth:`_set_bits_per_word` .. _Serial Peripheral Interface: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus """ def read(self, n): """ Read *n* words of data from the SPI interface, returning them as a sequence of unsigned ints, each no larger than the configured :attr:`bits_per_word` of the interface. This method is typically used with read-only devices that feature half-duplex communication. See :meth:`transfer` for full duplex communication. """ return self.transfer((0,) * n) def write(self, data): """ Write *data* to the SPI interface. *data* must be a sequence of unsigned integer words each of which will fit within the configured :attr:`bits_per_word` of the interface. The method returns the number of words written to the interface (which may be less than or equal to the length of *data*). This method is typically used with write-only devices that feature half-duplex communication. See :meth:`transfer` for full duplex communication. """ return len(self.transfer(data)) def transfer(self, data): """ Write *data* to the SPI interface. *data* must be a sequence of unsigned integer words each of which will fit within the configured :attr:`bits_per_word` of the interface. The method returns the sequence of words read from the interface while writing occurred (full duplex communication). The length of the sequence returned dictates the number of words of *data* written to the interface. Each word in the returned sequence will be an unsigned integer no larger than the configured :attr:`bits_per_word` of the interface. """ raise NotImplementedError @property def clock_polarity(self): """ The polarity of the SPI clock pin. If this is ``False`` (the default), the clock pin will idle low, and pulse high. Setting this to ``True`` will cause the clock pin to idle high, and pulse low. On many data sheets this is documented as the CPOL value. The following diagram illustrates the waveform when :attr:`clock_polarity` is ``False`` (the default), equivalent to CPOL 0: .. code-block:: text on on on on on on on ,---. ,---. ,---. ,---. ,---. ,---. ,---. CLK | | | | | | | | | | | | | | | | | | | | | | | | | | | | ------' `---' `---' `---' `---' `---' `---' `------ idle off off off off off off idle The following diagram illustrates the waveform when :attr:`clock_polarity` is ``True``, equivalent to CPOL 1: .. code-block:: text idle off off off off off off idle ------. ,---. ,---. ,---. ,---. ,---. ,---. ,------ | | | | | | | | | | | | | | CLK | | | | | | | | | | | | | | `---' `---' `---' `---' `---' `---' `---' on on on on on on on """ return bool(self.clock_mode & 2) @clock_polarity.setter def clock_polarity(self, value): self.clock_mode = self.clock_mode & (~2) | (bool(value) << 1) @property def clock_phase(self): """ The phase of the SPI clock pin. If this is ``False`` (the default), data will be read from the MISO pin when the clock pin activates. Setting this to ``True`` will cause data to be read from the MISO pin when the clock pin deactivates. On many data sheets this is documented as the CPHA value. Whether the clock edge is rising or falling when the clock is considered activated is controlled by the :attr:`clock_polarity` attribute (corresponding to CPOL). The following diagram indicates when data is read when :attr:`clock_polarity` is ``False``, and :attr:`clock_phase` is ``False`` (the default), equivalent to CPHA 0: .. code-block:: text ,---. ,---. ,---. ,---. ,---. ,---. ,---. CLK | | | | | | | | | | | | | | | | | | | | | | | | | | | | ----' `---' `---' `---' `---' `---' `---' `------- : : : : : : : MISO---. ,---. ,---. ,---. ,---. ,---. ,---. / \ / \ / \ / \ / \ / \ / \\ -{ Bit X Bit X Bit X Bit X Bit X Bit X Bit }------ \ / \ / \ / \ / \ / \ / \ / `---' `---' `---' `---' `---' `---' `---' The following diagram indicates when data is read when :attr:`clock_polarity` is ``False``, but :attr:`clock_phase` is ``True``, equivalent to CPHA 1: .. code-block:: text ,---. ,---. ,---. ,---. ,---. ,---. ,---. CLK | | | | | | | | | | | | | | | | | | | | | | | | | | | | ----' `---' `---' `---' `---' `---' `---' `------- : : : : : : : MISO ,---. ,---. ,---. ,---. ,---. ,---. ,---. / \ / \ / \ / \ / \ / \ / \\ -----{ Bit X Bit X Bit X Bit X Bit X Bit X Bit }-- \ / \ / \ / \ / \ / \ / \ / `---' `---' `---' `---' `---' `---' `---' """ return bool(self.clock_mode & 1) @clock_phase.setter def clock_phase(self, value): self.clock_mode = self.clock_mode & (~1) | bool(value) def _get_clock_mode(self): raise NotImplementedError def _set_clock_mode(self, value): raise SPIFixedClockMode("clock_mode cannot be changed on %r" % self) clock_mode = property( lambda self: self._get_clock_mode(), lambda self, value: self._set_clock_mode(value), doc="""\ Presents a value representing the :attr:`clock_polarity` and :attr:`clock_phase` attributes combined according to the following table: +------+-----------------+--------------+ | mode | polarity (CPOL) | phase (CPHA) | +======+=================+==============+ | 0 | False | False | +------+-----------------+--------------+ | 1 | False | True | +------+-----------------+--------------+ | 2 | True | False | +------+-----------------+--------------+ | 3 | True | True | +------+-----------------+--------------+ Adjusting this value adjusts both the :attr:`clock_polarity` and :attr:`clock_phase` attributes simultaneously. """) def _get_lsb_first(self): return False def _set_lsb_first(self, value): raise SPIFixedBitOrder("lsb_first cannot be changed on %r" % self) lsb_first = property( lambda self: self._get_lsb_first(), lambda self, value: self._set_lsb_first(value), doc="""\ Controls whether words are read and written LSB in (Least Significant Bit first) order. The default is ``False`` indicating that words are read and written in MSB (Most Significant Bit first) order. Effectively, this controls the `Bit endianness`_ of the connection. The following diagram shows the a word containing the number 5 (binary 0101) transmitted on MISO with :attr:`bits_per_word` set to 4, and :attr:`clock_mode` set to 0, when :attr:`lsb_first` is ``False`` (the default): .. code-block:: text ,---. ,---. ,---. ,---. CLK | | | | | | | | | | | | | | | | ----' `---' `---' `---' `----- : ,-------. : ,-------. MISO: | : | : | : | : | : | : | : | ----------' : `-------' : `---- : : : : MSB LSB And now with :attr:`lsb_first` set to ``True`` (and all other parameters the same): .. code-block:: text ,---. ,---. ,---. ,---. CLK | | | | | | | | | | | | | | | | ----' `---' `---' `---' `----- ,-------. : ,-------. : MISO: | : | : | : | : | : | : | : --' : `-------' : `----------- : : : : LSB MSB .. _Bit endianness: https://en.wikipedia.org/wiki/Endianness#Bit_endianness """) def _get_select_high(self): return False def _set_select_high(self, value): raise SPIFixedSelect("select_high cannot be changed on %r" % self) select_high = property( lambda self: self._get_select_high(), lambda self, value: self._set_select_high(value), doc="""\ If ``False`` (the default), the chip select line is considered active when it is pulled low. When set to ``True``, the chip select line is considered active when it is driven high. The following diagram shows the waveform of the chip select line, and the clock when :attr:`clock_polarity` is ``False``, and :attr:`select_high` is ``False`` (the default): .. code-block:: text ---. ,------ __ | | CS | chip is selected, and will react to clock | idle `-----------------------------------------------------' ,---. ,---. ,---. ,---. ,---. ,---. ,---. CLK | | | | | | | | | | | | | | | | | | | | | | | | | | | | ----' `---' `---' `---' `---' `---' `---' `------- And when :attr:`select_high` is ``True``: .. code-block:: text ,-----------------------------------------------------. CS | chip is selected, and will react to clock | idle | | ---' `------ ,---. ,---. ,---. ,---. ,---. ,---. ,---. CLK | | | | | | | | | | | | | | | | | | | | | | | | | | | | ----' `---' `---' `---' `---' `---' `---' `------- """) def _get_bits_per_word(self): return 8 def _set_bits_per_word(self, value): raise SPIFixedWordSize("bits_per_word cannot be changed on %r" % self) bits_per_word = property( lambda self: self._get_bits_per_word(), lambda self, value: self._set_bits_per_word(value), doc="""\ Controls the number of bits that make up a word, and thus where the word boundaries appear in the data stream, and the maximum value of a word. Defaults to 8 meaning that words are effectively bytes. Several implementations do not support non-byte-sized words. """) gpiozero-1.4.1/gpiozero/pins/rpigpio.py0000644000175000017500000001560113241527113020067 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import warnings from RPi import GPIO from .local import LocalPiFactory, LocalPiPin from ..exc import ( PinInvalidFunction, PinSetInput, PinFixedPull, PinInvalidPull, PinInvalidState, PinInvalidBounce, PinPWMFixedValue, ) class RPiGPIOFactory(LocalPiFactory): """ 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). 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 RPiGPIOFactory from gpiozero import LED factory = RPiGPIOFactory() led = LED(12, pin_factory=factory) .. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO """ def __init__(self): super(RPiGPIOFactory, self).__init__() GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) self.pin_class = RPiGPIOPin def close(self): super(RPiGPIOFactory, self).close() GPIO.cleanup() class RPiGPIOPin(LocalPiPin): """ Pin implementation for the `RPi.GPIO`_ library. See :class:`RPiGPIOFactory` for more information. .. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO """ GPIO_FUNCTIONS = { 'input': GPIO.IN, 'output': GPIO.OUT, 'i2c': GPIO.I2C, 'spi': GPIO.SPI, 'pwm': GPIO.HARD_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 __init__(self, factory, number): super(RPiGPIOPin, self).__init__(factory, number) self._pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating' self._pwm = None self._frequency = None self._duty_cycle = None self._bounce = -666 self._edges = GPIO.BOTH GPIO.setup(self.number, GPIO.IN, self.GPIO_PULL_UPS[self._pull]) 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.factory.pi_info.pulled_up(repr(self)): 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' if value in ('input', 'output') and value in self.GPIO_FUNCTIONS: GPIO.setup(self.number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull]) else: 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 PinInvalidState('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.factory.pi_info.pulled_up(repr(self)): 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): if value is not None and value < 0: raise PinInvalidBounce('bounce must be 0 or greater') f = self.when_changed self.when_changed = None try: self._bounce = -666 if value is None else int(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 _call_when_changed(self, channel): super(RPiGPIOPin, self)._call_when_changed() def _enable_event_detect(self): GPIO.add_event_detect( self.number, self._edges, callback=self._call_when_changed, bouncetime=self._bounce) def _disable_event_detect(self): GPIO.remove_event_detect(self.number) gpiozero-1.4.1/gpiozero/pins/pigpio.py0000644000175000017500000004500013165454031017704 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import os import pigpio from . import SPI from .pi import PiPin, PiFactory, SPI_HARDWARE_PINS from .data import pi_info from ..devices import Device from ..mixins import SharedMixin from ..exc import ( PinInvalidFunction, PinSetInput, PinFixedPull, PinInvalidPull, PinInvalidBounce, PinInvalidState, SPIBadArgs, SPIInvalidClockMode, ) class PiGPIOFactory(PiFactory): """ 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 pigpio pins manually like so:: from gpiozero.pins.pigpio import PiGPIOFactory from gpiozero import LED factory = PiGPIOFactory() led = LED(12, pin_factory=factory) 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.pigpio import PiGPIOFactory from gpiozero import LED factory = PiGPIOFactory(host='192.168.0.2') led = LED(12, pin_factory=factory) .. 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/ """ def __init__( self, host=os.getenv('PIGPIO_ADDR', 'localhost'), port=int(os.getenv('PIGPIO_PORT', 8888))): super(PiGPIOFactory, self).__init__() self.pin_class = PiGPIOPin self.spi_classes = { ('hardware', 'exclusive'): PiGPIOHardwareSPI, ('hardware', 'shared'): PiGPIOHardwareSPIShared, ('software', 'exclusive'): PiGPIOSoftwareSPI, ('software', 'shared'): PiGPIOSoftwareSPIShared, } self._connection = pigpio.pi(host, port) # Annoyingly, pigpio doesn't raise an exception when it fails to make a # connection; it returns a valid (but disconnected) pi object if self.connection is None: raise IOError('failed to connect to %s:%s' % (host, port)) self._host = host self._port = port self._spis = [] def close(self): super(PiGPIOFactory, self).close() # We *have* to keep track of SPI interfaces constructed with pigpio; # if we fail to close them they prevent future interfaces from using # the same pins if self.connection: while self._spis: self._spis[0].close() self.connection.stop() self._connection = None @property def connection(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... try: if self._connection.sl.s is not None: return self._connection except AttributeError: pass @property def host(self): return self._host @property def port(self): return self._port def _get_revision(self): return self.connection.get_hardware_revision() def spi(self, **spi_args): intf = super(PiGPIOFactory, self).spi(**spi_args) self._spis.append(intf) return intf class PiGPIOPin(PiPin): """ Pin implementation for the `pigpio`_ library. See :class:`PiGPIOFactory` for more information. .. _pigpio: http://abyz.co.uk/rpi/pigpio/ """ _CONNECTIONS = {} # maps (host, port) to (connection, pi_info) 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 __init__(self, factory, number): super(PiGPIOPin, self).__init__(factory, number) self._pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating' self._pwm = False self._bounce = None self._callback = None self._edges = pigpio.EITHER_EDGE try: self.factory.connection.set_mode(self.number, pigpio.INPUT) except pigpio.error as e: raise ValueError(e) self.factory.connection.set_pull_up_down(self.number, self.GPIO_PULL_UPS[self._pull]) self.factory.connection.set_glitch_filter(self.number, 0) def close(self): if self.factory.connection: self.frequency = None self.when_changed = None self.function = 'input' self.pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating' def _get_function(self): return self.GPIO_FUNCTION_NAMES[self.factory.connection.get_mode(self.number)] def _set_function(self, value): if value != 'input': self._pull = 'floating' try: self.factory.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.factory.connection.get_PWM_dutycycle(self.number) / self.factory.connection.get_PWM_range(self.number) ) else: return bool(self.factory.connection.read(self.number)) def _set_state(self, value): if self._pwm: try: value = int(value * self.factory.connection.get_PWM_range(self.number)) if value != self.factory.connection.get_PWM_dutycycle(self.number): self.factory.connection.set_PWM_dutycycle(self.number, value) except pigpio.error: raise PinInvalidState('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.factory.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.factory.pi_info.pulled_up(repr(self)): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: self.factory.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.factory.connection.get_PWM_frequency(self.number) return None def _set_frequency(self, value): if not self._pwm and value is not None: if self.function != 'output': raise PinPWMFixedValue('cannot start PWM on pin %r' % self) # NOTE: the pin's state *must* be set to zero; if it's currently # high, starting PWM and setting a 0 duty-cycle *doesn't* bring # the pin low; it stays high! self.factory.connection.write(self.number, 0) self.factory.connection.set_PWM_frequency(self.number, value) self.factory.connection.set_PWM_range(self.number, 10000) self.factory.connection.set_PWM_dutycycle(self.number, 0) self._pwm = True elif self._pwm and value is not None: if value != self.factory.connection.get_PWM_frequency(self.number): self.factory.connection.set_PWM_frequency(self.number, value) self.factory.connection.set_PWM_range(self.number, 10000) elif self._pwm and value is None: self.factory.connection.write(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 elif value < 0: raise PinInvalidBounce('bounce must be 0 or greater') self.factory.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 _call_when_changed(self, gpio, level, tick): super(PiGPIOPin, self)._call_when_changed() def _enable_event_detect(self): self._callback = self.factory.connection.callback( self.number, self._edges, self._call_when_changed) def _disable_event_detect(self): if self._callback is not None: self._callback.cancel() self._callback = None class PiGPIOHardwareSPI(SPI, Device): """ Hardware SPI implementation for the `pigpio`_ library. Uses the ``spi_*`` functions from the pigpio API. .. _pigpio: http://abyz.co.uk/rpi/pigpio/ """ def __init__(self, factory, port, device): self._port = port self._device = device self._factory = factory self._handle = None super(PiGPIOHardwareSPI, self).__init__() pins = SPI_HARDWARE_PINS[port] self._factory.reserve_pins( self, pins['clock'], pins['mosi'], pins['miso'], pins['select'][device] ) self._spi_flags = 8 << 16 self._baud = 500000 self._handle = self._factory.connection.spi_open( device, self._baud, self._spi_flags) def _conflicts_with(self, other): return not ( isinstance(other, PiGPIOHardwareSPI) and (self._port, self._device) != (other._port, other._device) ) def close(self): try: self._factory._spis.remove(self) except (ReferenceError, ValueError): # If the factory has died already or we're not present in its # internal list, ignore the error pass if not self.closed: self._factory.connection.spi_close(self._handle) self._handle = None self._factory.release_all(self) super(PiGPIOHardwareSPI, self).close() @property def closed(self): return self._handle is None or self._factory.connection is None @property def factory(self): return self._factory def __repr__(self): try: self._check_open() return 'SPI(port=%d, device=%d)' % (self._port, self._device) except DeviceClosed: return 'SPI(closed)' def _get_clock_mode(self): return self._spi_flags & 0x3 def _set_clock_mode(self, value): self._check_open() if not 0 <= value < 4: raise SPIInvalidClockMode("%d is not a valid SPI clock mode" % value) self._factory.connection.spi_close(self._handle) self._spi_flags = (self._spi_flags & ~0x3) | value self._handle = self._factory.connection.spi_open( self._device, self._baud, self._spi_flags) def _get_select_high(self): return bool((self._spi_flags >> (2 + self._device)) & 0x1) def _set_select_high(self, value): self._check_open() self._factory.connection.spi_close(self._handle) self._spi_flags = (self._spi_flags & ~0x1c) | (bool(value) << (2 + self._device)) self._handle = self._factory.connection.spi_open( self._device, self._baud, self._spi_flags) def _get_bits_per_word(self): return (self._spi_flags >> 16) & 0x3f def _set_bits_per_word(self, value): self._check_open() self._factory.connection.spi_close(self._handle) self._spi_flags = (self._spi_flags & ~0x3f0000) | ((value & 0x3f) << 16) self._handle = self._factory.connection.spi_open( self._device, self._baud, self._spi_flags) def transfer(self, data): self._check_open() count, data = self._factory.connection.spi_xfer(self._handle, data) if count < 0: raise IOError('SPI transfer error %d' % count) # Convert returned bytearray to list of ints. XXX Not sure how non-byte # sized words (aux intf only) are returned ... padded to 16/32-bits? return [int(b) for b in data] class PiGPIOSoftwareSPI(SPI, Device): """ Software SPI implementation for the `pigpio`_ library. Uses the ``bb_spi_*`` functions from the pigpio API. .. _pigpio: http://abyz.co.uk/rpi/pigpio/ """ def __init__(self, factory, clock_pin, mosi_pin, miso_pin, select_pin): self._closed = True self._select_pin = select_pin self._clock_pin = clock_pin self._mosi_pin = mosi_pin self._miso_pin = miso_pin self._factory = factory super(PiGPIOSoftwareSPI, self).__init__() self._factory.reserve_pins( self, clock_pin, mosi_pin, miso_pin, select_pin, ) self._spi_flags = 0 self._baud = 100000 try: self._factory.connection.bb_spi_open( select_pin, miso_pin, mosi_pin, clock_pin, self._baud, self._spi_flags) # Only set after opening bb_spi; if that fails then close() will # also fail if bb_spi_close is attempted on an un-open interface self._closed = False except: self.close() raise def _conflicts_with(self, other): return not ( isinstance(other, PiGPIOSoftwareSPI) and (self._select_pin) != (other._select_pin) ) def close(self): try: self._factory._spis.remove(self) except (ReferenceError, ValueError): # If the factory has died already or we're not present in its # internal list, ignore the error pass if not self.closed: self._closed = True self._factory.connection.bb_spi_close(self._select_pin) self.factory.release_all(self) super(PiGPIOSoftwareSPI, self).close() @property def closed(self): return self._closed def __repr__(self): try: self._check_open() return ( 'SPI(clock_pin=%d, mosi_pin=%d, miso_pin=%d, select_pin=%d)' % ( self._clock_pin, self._mosi_pin, self._miso_pin, self._select_pin )) except DeviceClosed: return 'SPI(closed)' def _spi_flags(self): return ( self._mode << 0 | self._select_high << 2 | self._lsb_first << 14 | self._lsb_first << 15 ) def _get_clock_mode(self): return self._spi_flags & 0x3 def _set_clock_mode(self, value): self._check_open() if not 0 <= value < 4: raise SPIInvalidClockmode("%d is not a valid SPI clock mode" % value) self._factory.connection.bb_spi_close(self._select_pin) self._spi_flags = (self._spi_flags & ~0x3) | value self._factory.connection.bb_spi_open( self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin, self._baud, self._spi_flags) def _get_select_high(self): return bool(self._spi_flags & 0x4) def _set_select_high(self, value): self._check_open() self._factory.connection.bb_spi_close(self._select_pin) self._spi_flags = (self._spi_flags & ~0x4) | (bool(value) << 2) self._factory.connection.bb_spi_open( self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin, self._baud, self._spi_flags) def _get_lsb_first(self): return bool(self._spi_flags & 0xc000) def _set_lsb_first(self, value): self._check_open() self._factory.connection.bb_spi_close(self._select_pin) self._spi_flags = ( (self._spi_flags & ~0xc000) | (bool(value) << 14) | (bool(value) << 15) ) self._factory.connection.bb_spi_open( self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin, self._baud, self._spi_flags) def transfer(self, data): self._check_open() count, data = self._factory.connection.bb_spi_xfer(self._select_pin, data) if count < 0: raise IOError('SPI transfer error %d' % count) # Convert returned bytearray to list of ints. bb_spi only supports # byte-sized words so no issues here return [int(b) for b in data] class PiGPIOHardwareSPIShared(SharedMixin, PiGPIOHardwareSPI): @classmethod def _shared_key(cls, factory, port, device): return (factory, port, device) class PiGPIOSoftwareSPIShared(SharedMixin, PiGPIOSoftwareSPI): @classmethod def _shared_key(cls, factory, clock_pin, mosi_pin, miso_pin, select_pin): return (factory, select_pin) gpiozero-1.4.1/gpiozero/pins/local.py0000644000175000017500000001665313243115414017517 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import io import warnings from collections import defaultdict from threading import Lock try: from spidev import SpiDev except ImportError: SpiDev = None from . import SPI from .pi import PiFactory, PiPin, SPI_HARDWARE_PINS from .spi import SPISoftwareBus from ..devices import Device, SharedMixin from ..output_devices import OutputDevice from ..exc import DeviceClosed, PinUnknownPi, SPIInvalidClockMode class LocalPiFactory(PiFactory): """ Abstract base class representing pins attached locally to a Pi. This forms the base class for local-only pin interfaces (:class:`~gpiozero.pins.rpigpio.RPiGPIOPin`, :class:`~gpiozero.pins.rpio.RPIOPin`, and :class:`~gpiozero.pins.native.NativePin`). """ pins = {} _reservations = defaultdict(list) _res_lock = Lock() def __init__(self): super(LocalPiFactory, self).__init__() self.spi_classes = { ('hardware', 'exclusive'): LocalPiHardwareSPI, ('hardware', 'shared'): LocalPiHardwareSPIShared, ('software', 'exclusive'): LocalPiSoftwareSPI, ('software', 'shared'): LocalPiSoftwareSPIShared, } # Override the reservations and pins dict to be this class' attributes. # This is a bit of a dirty hack, but ensures that anyone evil enough to # mix pin implementations doesn't try and control the same pin with # different backends self.pins = LocalPiFactory.pins self._reservations = LocalPiFactory._reservations self._res_lock = LocalPiFactory._res_lock def _get_revision(self): # Cache the result as we can reasonably assume it won't change during # runtime (this is LocalPin after all; descendents that deal with # remote Pis should inherit from Pin instead) with io.open('/proc/cpuinfo', 'r') as f: for line in f: if line.startswith('Revision'): revision = line.split(':')[1].strip().lower() overvolted = revision.startswith('100') if overvolted: revision = revision[-4:] return revision raise PinUnknownPi('unable to locate Pi revision in /proc/cpuinfo') class LocalPiPin(PiPin): """ Abstract base class representing a multi-function GPIO pin attached to the local Raspberry Pi. """ pass class LocalPiHardwareSPI(SPI, Device): def __init__(self, factory, port, device): self._port = port self._device = device self._interface = None if SpiDev is None: raise ImportError('failed to import spidev') super(LocalPiHardwareSPI, self).__init__() pins = SPI_HARDWARE_PINS[port] self.pin_factory.reserve_pins( self, pins['clock'], pins['mosi'], pins['miso'], pins['select'][device] ) self._interface = SpiDev() self._interface.open(port, device) self._interface.max_speed_hz = 500000 def close(self): if getattr(self, '_interface', None): self._interface.close() self._interface = None self.pin_factory.release_all(self) super(LocalPiHardwareSPI, self).close() @property def closed(self): return self._interface is None def __repr__(self): try: self._check_open() return 'SPI(port=%d, device=%d)' % (self._port, self._device) except DeviceClosed: return 'SPI(closed)' def transfer(self, data): """ Writes data (a list of integer words where each word is assumed to have :attr:`bits_per_word` bits or less) to the SPI interface, and reads an equivalent number of words, returning them as a list of integers. """ return self._interface.xfer2(data) def _get_clock_mode(self): return self._interface.mode def _set_clock_mode(self, value): self._interface.mode = value def _get_lsb_first(self): return self._interface.lsbfirst def _set_lsb_first(self, value): self._interface.lsbfirst = bool(value) def _get_select_high(self): return self._interface.cshigh def _set_select_high(self, value): self._interface.cshigh = bool(value) def _get_bits_per_word(self): return self._interface.bits_per_word def _set_bits_per_word(self, value): self._interface.bits_per_word = value class LocalPiSoftwareSPI(SPI, OutputDevice): def __init__(self, factory, clock_pin, mosi_pin, miso_pin, select_pin): self._bus = None super(LocalPiSoftwareSPI, self).__init__(select_pin, active_high=False) try: self._clock_phase = False self._lsb_first = False self._bits_per_word = 8 self._bus = SPISoftwareBus(clock_pin, mosi_pin, miso_pin) except: self.close() raise def _conflicts_with(self, other): # XXX Need to refine this return not ( isinstance(other, LocalPiSoftwareSPI) and (self.pin.number != other.pin.number) ) def close(self): if getattr(self, '_bus', None): self._bus.close() self._bus = None super(LocalPiSoftwareSPI, self).close() @property def closed(self): return self._bus is None def __repr__(self): try: self._check_open() return 'SPI(clock_pin=%d, mosi_pin=%d, miso_pin=%d, select_pin=%d)' % ( self._bus.clock.pin.number, self._bus.mosi.pin.number, self._bus.miso.pin.number, self.pin.number) except DeviceClosed: return 'SPI(closed)' def transfer(self, data): with self._bus.lock: self.on() try: return self._bus.transfer( data, self._clock_phase, self._lsb_first, self._bits_per_word) finally: self.off() def _get_clock_mode(self): with self._bus.lock: return (not self._bus.clock.active_high) << 1 | self._clock_phase def _set_clock_mode(self, value): if not (0 <= value < 4): raise SPIInvalidClockMode("%d is not a valid clock mode" % value) with self._bus.lock: self._bus.clock.active_high = not (value & 2) self._clock_phase = bool(value & 1) def _get_lsb_first(self): return self._lsb_first def _set_lsb_first(self, value): self._lsb_first = bool(value) def _get_bits_per_word(self): return self._bits_per_word def _set_bits_per_word(self, value): if value < 1: raise ValueError('bits_per_word must be positive') self._bits_per_word = int(value) def _get_select_high(self): return self.active_high def _set_select_high(self, value): with self._bus.lock: self.active_high = value self.off() class LocalPiHardwareSPIShared(SharedMixin, LocalPiHardwareSPI): @classmethod def _shared_key(cls, factory, port, device): return (port, device) class LocalPiSoftwareSPIShared(SharedMixin, LocalPiSoftwareSPI): @classmethod def _shared_key(cls, factory, clock_pin, mosi_pin, miso_pin, select_pin): return (select_pin,) gpiozero-1.4.1/gpiozero/pins/native.py0000644000175000017500000002671413165454031017716 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) nstr = str str = type('') import io import os import mmap import errno import struct import warnings from time import sleep from threading import Thread, Event, Lock from collections import Counter from .local import LocalPiPin, LocalPiFactory from ..exc import ( PinInvalidPull, PinInvalidEdges, PinInvalidFunction, PinFixedPull, PinSetInput, ) 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('> self._func_shift) & 7] def _set_function(self, value): try: value = self.GPIO_FUNCTIONS[value] except KeyError: raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) self.factory.mem[self._func_offset] = ( self.factory.mem[self._func_offset] & ~(7 << self._func_shift) | (value << self._func_shift) ) def _get_state(self): return bool(self.factory.mem[self._level_offset] & (1 << self._level_shift)) def _set_state(self, value): if self.function == 'input': raise PinSetInput('cannot set state of pin %r' % self) if value: self.factory.mem[self._set_offset] = 1 << self._set_shift else: self.factory.mem[self._clear_offset] = 1 << self._clear_shift def _get_pull(self): return self.GPIO_PULL_UP_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.factory.pi_info.pulled_up(repr(self)): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: value = self.GPIO_PULL_UPS[value] except KeyError: raise PinInvalidPull('invalid pull direction "%s" for pin %r' % (value, self)) self.factory.mem[self.factory.mem.GPPUD_OFFSET] = value sleep(0.000000214) self.factory.mem[self._pull_offset] = 1 << self._pull_shift sleep(0.000000214) self.factory.mem[self.factory.mem.GPPUD_OFFSET] = 0 self.factory.mem[self._pull_offset] = 0 self._pull = value def _get_edges(self): rising = bool(self.factory.mem[self._rising_offset] & (1 << self._rising_shift)) falling = bool(self.factory.mem[self._falling_offset] & (1 << self._falling_shift)) return self.GPIO_EDGES_NAMES[(rising, falling)] def _set_edges(self, value): try: rising, falling = self.GPIO_EDGES[value] except KeyError: raise PinInvalidEdges('invalid edge specification "%s" for pin %r' % self) f = self.when_changed self.when_changed = None try: self.factory.mem[self._rising_offset] = ( self.factory.mem[self._rising_offset] & ~(1 << self._rising_shift) | (rising << self._rising_shift) ) self.factory.mem[self._falling_offset] = ( self.factory.mem[self._falling_offset] & ~(1 << self._falling_shift) | (falling << self._falling_shift) ) finally: self.when_changed = f def _enable_event_detect(self): self._change_thread = Thread(target=self._change_watch) self._change_thread.daemon = True self._change_event.clear() self._change_thread.start() def _disable_event_detect(self): self._change_event.set() self._change_thread.join() self._change_thread = None def _change_watch(self): offset = self._edge_offset mask = 1 << self._edge_shift self.factory.mem[offset] = mask # clear any existing detection bit while not self._change_event.wait(0.001): if self.factory.mem[offset] & mask: self.factory.mem[offset] = mask self._call_when_changed() gpiozero-1.4.1/gpiozero/pins/rpio.py0000644000175000017500000001534413165454031017376 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import warnings import RPIO import RPIO.PWM from RPIO.Exceptions import InvalidChannelException from .local import LocalPiPin, LocalPiFactory from .data import pi_info from ..exc import ( PinInvalidFunction, PinSetInput, PinFixedPull, PinInvalidPull, PinInvalidBounce, PinInvalidState, PinPWMError, ) class RPIOFactory(LocalPiFactory): """ 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``. You can construct RPIO pins manually like so:: from gpiozero.pins.rpio import RPIOFactory from gpiozero import LED factory = RPIOFactory() led = LED(12, pin_factory=factory) .. _RPIO: https://pythonhosted.org/RPIO/ """ def __init__(self): super(RPIOFactory, self).__init__() RPIO.setmode(RPIO.BCM) RPIO.setwarnings(False) RPIO.wait_for_interrupts(threaded=True) RPIO.PWM.setup() RPIO.PWM.init_channel(0, 10000) self.pin_class = RPIOPin def close(self): RPIO.PWM.cleanup() RPIO.stop_waiting_for_interrupts() RPIO.cleanup() class RPIOPin(LocalPiPin): """ Pin implementation for the `RPIO`_ library. See :class:`RPIOFactory` for more information. .. _RPIO: https://pythonhosted.org/RPIO/ """ 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 __init__(self, factory, number): super(RPIOPin, self).__init__(factory, number) self._pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating' self._pwm = False self._duty_cycle = None self._bounce = None self._edges = 'both' try: RPIO.setup(self.number, RPIO.IN, self.GPIO_PULL_UPS[self._pull]) except InvalidChannelException as e: raise ValueError(e) 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 PinInvalidState('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.factory.pi_info.pulled_up(repr(self)): 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): if self._pwm: return 100 else: return None 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): if value is not None and value < 0: raise PinInvalidBounce('bounce must be 0 or greater') f = self.when_changed self.when_changed = None try: self._bounce = None if value is None else int(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 _call_when_changed(self, channel, value): super(RPIOPin, self)._call_when_changed() def _enable_event_detect(self): RPIO.add_interrupt_callback( self.number, self._call_when_changed, self._edges, self.GPIO_PULL_UPS[self._pull], self._bounce) def _disable_event_detect(self): 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 gpiozero-1.4.1/gpiozero/other_devices.py0000644000175000017500000001710213165454031020271 0ustar davedave00000000000000# vim: set fileencoding=utf-8: from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') import os import io import subprocess from datetime import datetime, time from .devices import Device from .mixins import EventsMixin class InternalDevice(EventsMixin, Device): """ Extends :class:`Device` to provide a basis for devices which have no specific hardware representation. These are effectively pseudo-devices and usually represent operating system services like the internal clock, file systems or network facilities. """ class PingServer(InternalDevice): """ Extends :class:`InternalDevice` to provide a device which is active when a *host* on the network can be pinged. The following example lights an LED while a server is reachable (note the use of :attr:`~SourceMixin.source_delay` to ensure the server is not flooded with pings):: from gpiozero import PingServer, LED from signal import pause google = PingServer('google.com') led = LED(4) led.source_delay = 60 # check once per minute led.source = google.values pause() :param str host: The hostname or IP address to attempt to ping. """ def __init__(self, host): self.host = host super(PingServer, self).__init__() self._fire_events() def __repr__(self): return '' % self.host @property def value(self): # XXX This is doing a DNS lookup every time it's queried; should we # call gethostbyname in the constructor and ping that instead (good # for consistency, but what if the user *expects* the host to change # address?) with io.open(os.devnull, 'wb') as devnull: try: subprocess.check_call( ['ping', '-c1', self.host], stdout=devnull, stderr=devnull) except subprocess.CalledProcessError: return False else: return True class CPUTemperature(InternalDevice): """ Extends :class:`InternalDevice` to provide a device which is active when the CPU temperature exceeds the *threshold* value. The following example plots the CPU's temperature on an LED bar graph:: from gpiozero import LEDBarGraph, CPUTemperature from signal import pause # Use minimums and maximums that are closer to "normal" usage so the # bar graph is a bit more "lively" cpu = CPUTemperature(min_temp=50, max_temp=90) print('Initial temperature: {}C'.format(cpu.temperature)) graph = LEDBarGraph(5, 6, 13, 19, 25, pwm=True) graph.source = cpu.values pause() :param str sensor_file: The file from which to read the temperature. This defaults to the sysfs file :file:`/sys/class/thermal/thermal_zone0/temp`. Whatever file is specified is expected to contain a single line containing the temperature in milli-degrees celsius. :param float min_temp: The temperature at which :attr:`value` will read 0.0. This defaults to 0.0. :param float max_temp: The temperature at which :attr:`value` will read 1.0. This defaults to 100.0. :param float threshold: The temperature above which the device will be considered "active". This defaults to 80.0. """ def __init__(self, sensor_file='/sys/class/thermal/thermal_zone0/temp', min_temp=0.0, max_temp=100.0, threshold=80.0): self.sensor_file = sensor_file super(CPUTemperature, self).__init__() self.min_temp = min_temp self.max_temp = max_temp self.threshold = threshold self._fire_events() def __repr__(self): return '' % self.temperature @property def temperature(self): """ Returns the current CPU temperature in degrees celsius. """ with io.open(self.sensor_file, 'r') as f: return float(f.readline().strip()) / 1000 @property def value(self): """ Returns the current CPU temperature as a value between 0.0 (representing the *min_temp* value) and 1.0 (representing the *max_temp* value). These default to 0.0 and 100.0 respectively, hence :attr:`value` is :attr:`temperature` divided by 100 by default. """ temp_range = self.max_temp - self.min_temp return (self.temperature - self.min_temp) / temp_range @property def is_active(self): """ Returns ``True`` when the CPU :attr:`temperature` exceeds the :attr:`threshold`. """ return self.temperature > self.threshold class TimeOfDay(InternalDevice): """ Extends :class:`InternalDevice` to provide a device which is active when the computer's clock indicates that the current time is between *start_time* and *end_time* (inclusive) which are :class:`~datetime.time` instances. The following example turns on a lamp attached to an :class:`Energenie` plug between 7 and 8 AM:: from gpiozero import TimeOfDay, Energenie from datetime import time from signal import pause lamp = Energenie(0) morning = TimeOfDay(time(7), time(8)) lamp.source = morning.values pause() :param ~datetime.time start_time: The time from which the device will be considered active. :param ~datetime.time end_time: The time after which the device will be considered inactive. :param bool utc: If ``True`` (the default), a naive UTC time will be used for the comparison rather than a local time-zone reading. """ def __init__(self, start_time, end_time, utc=True): self._start_time = None self._end_time = None self._utc = True super(TimeOfDay, self).__init__() self.start_time = start_time self.end_time = end_time self.utc = utc self._fire_events() def __repr__(self): return '' % ( self.start_time, self.end_time, ('local', 'UTC')[self.utc]) @property def start_time(self): """ The time of day after which the device will be considered active. """ return self._start_time @start_time.setter def start_time(self, value): if isinstance(value, datetime): value = value.time() if not isinstance(value, time): raise ValueError('start_time must be a datetime, or time instance') self._start_time = value @property def end_time(self): """ The time of day after which the device will be considered inactive. """ return self._end_time @end_time.setter def end_time(self, value): if isinstance(value, datetime): value = value.time() if not isinstance(value, time): raise ValueError('end_time must be a datetime, or time instance') self._end_time = value @property def utc(self): """ If ``True``, use a naive UTC time reading for comparison instead of a local timezone reading. """ return self._utc @utc.setter def utc(self, value): self._utc = bool(value) @property def value(self): if self.utc: return self.start_time <= datetime.utcnow().time() <= self.end_time else: return self.start_time <= datetime.now().time() <= self.end_time gpiozero-1.4.1/gpiozero/input_devices.py0000644000175000017500000007173113243115414020313 0ustar davedave00000000000000# vim: set fileencoding=utf-8: from __future__ import ( unicode_literals, print_function, absolute_import, division, ) import warnings from time import sleep, time from threading import Event, Lock try: from statistics import median except ImportError: from .compat import median from .exc import InputDeviceError, DeviceClosed, DistanceSensorNoEcho from .devices import GPIODevice from .mixins import GPIOQueue, EventsMixin, HoldMixin class InputDevice(GPIODevice): """ Represents a generic GPIO input device. This class extends :class:`GPIODevice` to add facilities common to GPIO input devices. The constructor adds the optional *pull_up* parameter to specify how the pin should be pulled by the internal resistors. The :attr:`~GPIODevice.is_active` property is adjusted accordingly so that ``True`` still means active regardless of the :attr:`pull_up` setting. :param int pin: The GPIO pin (in Broadcom numbering) that the device is connected to. If this is ``None`` a :exc:`GPIODeviceError` will be raised. :param bool pull_up: If ``True``, the pin will be pulled high with an internal resistor. If ``False`` (the default), the pin will be pulled low. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__(self, pin=None, pull_up=False, pin_factory=None): super(InputDevice, self).__init__(pin, pin_factory=pin_factory) try: self.pin.function = 'input' pull = 'up' if pull_up else 'down' if self.pin.pull != pull: self.pin.pull = pull except: self.close() raise self._active_state = False if pull_up else True self._inactive_state = True if pull_up else False @property def pull_up(self): """ If ``True``, the device uses a pull-up resistor to set the GPIO pin "high" by default. """ return self.pin.pull == 'up' def __repr__(self): try: return "" % ( self.__class__.__name__, self.pin, self.pull_up, self.is_active) except: return super(InputDevice, self).__repr__() class DigitalInputDevice(EventsMixin, InputDevice): """ Represents a generic input device with typical on/off behaviour. This class extends :class:`InputDevice` with machinery to fire the active and inactive events for devices that operate in a typical digital manner: straight forward on / off states with (reasonably) clean transitions between the two. :param float bounce_time: Specifies the length of time (in seconds) that the component will ignore changes in state after an initial change. This defaults to ``None`` which indicates that no bounce compensation will be performed. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__( self, pin=None, pull_up=False, bounce_time=None, pin_factory=None): super(DigitalInputDevice, self).__init__( pin, pull_up, pin_factory=pin_factory ) try: self.pin.bounce = bounce_time self.pin.edges = 'both' self.pin.when_changed = self._fire_events # Call _fire_events once to set initial state of events self._fire_events() except: self.close() raise class SmoothedInputDevice(EventsMixin, InputDevice): """ Represents a generic input device which takes its value from the average of a queue of historical values. This class extends :class:`InputDevice` with a queue which is filled by a background thread which continually polls the state of the underlying device. The average (a configurable function) of the values in the queue is compared to a threshold which is used to determine the state of the :attr:`is_active` property. .. note:: The background queue is not automatically started upon construction. This is to allow descendents to set up additional components before the queue starts reading values. Effectively this is an abstract base class. This class is intended for use with devices which either exhibit analog behaviour (such as the charging time of a capacitor with an LDR), or those which exhibit "twitchy" behaviour (such as certain motion sensors). :param float threshold: The value above which the device will be considered "on". :param int queue_len: The length of the internal queue which is filled by the background thread. :param float sample_wait: The length of time to wait between retrieving the state of the underlying device. Defaults to 0.0 indicating that values are retrieved as fast as possible. :param bool partial: If ``False`` (the default), attempts to read the state of the device (from the :attr:`is_active` property) will block until the queue has filled. If ``True``, a value will be returned immediately, but be aware that this value is likely to fluctuate excessively. :param average: The function used to average the values in the internal queue. This defaults to :func:`statistics.median` which a good selection for discarding outliers from jittery sensors. The function specific must accept a sequence of numbers and return a single number. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__( self, pin=None, pull_up=False, threshold=0.5, queue_len=5, sample_wait=0.0, partial=False, average=median, pin_factory=None): self._queue = None super(SmoothedInputDevice, self).__init__( pin, pull_up, pin_factory=pin_factory ) try: self._queue = GPIOQueue(self, queue_len, sample_wait, partial, average) self.threshold = float(threshold) except: self.close() raise def close(self): try: self._queue.stop() except AttributeError: # If the queue isn't initialized (it's None), or _queue hasn't been # set ignore the error because we're trying to close anyway if getattr(self, '_queue', None) is not None: raise except RuntimeError: # Cannot join thread before it starts; we don't care about this # because we're trying to close the thread anyway pass self._queue = None super(SmoothedInputDevice, self).close() def __repr__(self): try: self._check_open() except DeviceClosed: return super(SmoothedInputDevice, self).__repr__() else: if self.partial or self._queue.full.is_set(): return super(SmoothedInputDevice, self).__repr__() else: return "" % ( self.__class__.__name__, self.pin, self.pull_up) @property def queue_len(self): """ The length of the internal queue of values which is averaged to determine the overall state of the device. This defaults to 5. """ self._check_open() return self._queue.queue.maxlen @property def partial(self): """ If ``False`` (the default), attempts to read the :attr:`value` or :attr:`is_active` properties will block until the queue has filled. """ self._check_open() return self._queue.partial @property def value(self): """ Returns the mean of the values in the internal queue. This is compared to :attr:`threshold` to determine whether :attr:`is_active` is ``True``. """ self._check_open() return self._queue.value @property def threshold(self): """ If :attr:`value` exceeds this amount, then :attr:`is_active` will return ``True``. """ return self._threshold @threshold.setter def threshold(self, value): if not (0.0 < value < 1.0): raise InputDeviceError( 'threshold must be between zero and one exclusive' ) self._threshold = float(value) @property def is_active(self): """ Returns ``True`` if the device is currently active and ``False`` otherwise. """ return self.value > self.threshold class Button(HoldMixin, DigitalInputDevice): """ Extends :class:`DigitalInputDevice` and represents a simple push button or switch. Connect one side of the button to a ground pin, and the other to any GPIO pin. Alternatively, connect one side of the button to the 3V3 pin, and the other to any GPIO pin, then set *pull_up* to ``False`` in the :class:`Button` constructor. The following example will print a line of text when the button is pushed:: from gpiozero import Button button = Button(4) button.wait_for_press() print("The button was pressed!") :param int pin: The GPIO pin which the button is attached to. See :ref:`pin-numbering` for valid pin numbers. :param bool pull_up: If ``True`` (the default), the GPIO pin will be pulled high by default. In this case, connect the other side of the button to ground. If ``False``, the GPIO pin will be pulled low by default. In this case, connect the other side of the button to 3V3. :param float bounce_time: If ``None`` (the default), no software bounce compensation will be performed. Otherwise, this is the length of time (in seconds) that the component will ignore changes in state after an initial change. :param float hold_time: The length of time (in seconds) to wait after the button is pushed, until executing the :attr:`when_held` handler. Defaults to ``1``. :param bool hold_repeat: If ``True``, the :attr:`when_held` handler will be repeatedly executed as long as the device remains active, every *hold_time* seconds. If ``False`` (the default) the :attr:`when_held` handler will be only be executed once per hold. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__( self, pin=None, pull_up=True, bounce_time=None, hold_time=1, hold_repeat=False, pin_factory=None): super(Button, self).__init__( pin, pull_up, bounce_time, pin_factory=pin_factory ) self.hold_time = hold_time self.hold_repeat = hold_repeat Button.is_pressed = Button.is_active Button.pressed_time = Button.active_time Button.when_pressed = Button.when_activated Button.when_released = Button.when_deactivated Button.wait_for_press = Button.wait_for_active Button.wait_for_release = Button.wait_for_inactive class LineSensor(SmoothedInputDevice): """ Extends :class:`SmoothedInputDevice` and represents a single pin line sensor like the TCRT5000 infra-red proximity sensor found in the `CamJam #3 EduKit`_. A typical line sensor has a small circuit board with three pins: VCC, GND, and OUT. VCC should be connected to a 3V3 pin, GND to one of the ground pins, and finally OUT to the GPIO specified as the value of the *pin* parameter in the constructor. The following code will print a line of text indicating when the sensor detects a line, or stops detecting a line:: from gpiozero import LineSensor from signal import pause sensor = LineSensor(4) sensor.when_line = lambda: print('Line detected') sensor.when_no_line = lambda: print('No line detected') pause() :param int pin: The GPIO pin which the sensor is attached to. See :ref:`pin-numbering` for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the sensor. This defaults to 5. :param float sample_rate: The number of values to read from the device (and append to the internal queue) per second. Defaults to 100. :param float threshold: Defaults to 0.5. When the mean of all values in the internal queue rises above this value, the sensor will be considered "active" by the :attr:`~SmoothedInputDevice.is_active` property, and all appropriate events will be fired. :param bool partial: When ``False`` (the default), the object will not return a value for :attr:`~SmoothedInputDevice.is_active` until the internal queue has filled with values. Only set this to ``True`` if you require values immediately after object construction. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 """ def __init__( self, pin=None, queue_len=5, sample_rate=100, threshold=0.5, partial=False, pin_factory=None): super(LineSensor, self).__init__( pin, pull_up=False, threshold=threshold, queue_len=queue_len, sample_wait=1 / sample_rate, partial=partial, pin_factory=pin_factory ) try: self._queue.start() except: self.close() raise @property def line_detected(self): return not self.is_active LineSensor.when_line = LineSensor.when_deactivated LineSensor.when_no_line = LineSensor.when_activated LineSensor.wait_for_line = LineSensor.wait_for_inactive LineSensor.wait_for_no_line = LineSensor.wait_for_active class MotionSensor(SmoothedInputDevice): """ Extends :class:`SmoothedInputDevice` and represents a passive infra-red (PIR) motion sensor like the sort found in the `CamJam #2 EduKit`_. .. _CamJam #2 EduKit: http://camjam.me/?page_id=623 A typical PIR device has a small circuit board with three pins: VCC, OUT, and GND. VCC should be connected to a 5V pin, GND to one of the ground pins, and finally OUT to the GPIO specified as the value of the *pin* parameter in the constructor. The following code will print a line of text when motion is detected:: from gpiozero import MotionSensor pir = MotionSensor(4) pir.wait_for_motion() print("Motion detected!") :param int pin: The GPIO pin which the sensor is attached to. See :ref:`pin-numbering` for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the sensor. This defaults to 1 which effectively disables the queue. If your motion sensor is particularly "twitchy" you may wish to increase this value. :param float sample_rate: The number of values to read from the device (and append to the internal queue) per second. Defaults to 100. :param float threshold: Defaults to 0.5. When the mean of all values in the internal queue rises above this value, the sensor will be considered "active" by the :attr:`~SmoothedInputDevice.is_active` property, and all appropriate events will be fired. :param bool partial: When ``False`` (the default), the object will not return a value for :attr:`~SmoothedInputDevice.is_active` until the internal queue has filled with values. Only set this to ``True`` if you require values immediately after object construction. :param bool pull_up: If ``False`` (the default), the GPIO pin will be pulled low by default. If ``True``, the GPIO pin will be pulled high by the sensor. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). """ def __init__( self, pin=None, queue_len=1, sample_rate=10, threshold=0.5, partial=False, pull_up=False, pin_factory=None): super(MotionSensor, self).__init__( pin, pull_up=pull_up, threshold=threshold, queue_len=queue_len, sample_wait=1 / sample_rate, partial=partial, pin_factory=pin_factory ) try: self._queue.start() except: self.close() raise MotionSensor.motion_detected = MotionSensor.is_active MotionSensor.when_motion = MotionSensor.when_activated MotionSensor.when_no_motion = MotionSensor.when_deactivated MotionSensor.wait_for_motion = MotionSensor.wait_for_active MotionSensor.wait_for_no_motion = MotionSensor.wait_for_inactive class LightSensor(SmoothedInputDevice): """ Extends :class:`SmoothedInputDevice` and represents a light dependent resistor (LDR). Connect one leg of the LDR to the 3V3 pin; connect one leg of a 1µF capacitor to a ground pin; connect the other leg of the LDR and the other leg of the capacitor to the same GPIO pin. This class repeatedly discharges the capacitor, then times the duration it takes to charge (which will vary according to the light falling on the LDR). The following code will print a line of text when light is detected:: from gpiozero import LightSensor ldr = LightSensor(18) ldr.wait_for_light() print("Light detected!") :param int pin: The GPIO pin which the sensor is attached to. See :ref:`pin-numbering` for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the circuit. This defaults to 5. :param float charge_time_limit: If the capacitor in the circuit takes longer than this length of time to charge, it is assumed to be dark. The default (0.01 seconds) is appropriate for a 1µF capacitor coupled with the LDR from the `CamJam #2 EduKit`_. You may need to adjust this value for different valued capacitors or LDRs. :param float threshold: Defaults to 0.1. When the mean of all values in the internal queue rises above this value, the area will be considered "light", and all appropriate events will be fired. :param bool partial: When ``False`` (the default), the object will not return a value for :attr:`~SmoothedInputDevice.is_active` until the internal queue has filled with values. Only set this to ``True`` if you require values immediately after object construction. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _CamJam #2 EduKit: http://camjam.me/?page_id=623 """ def __init__( self, pin=None, queue_len=5, charge_time_limit=0.01, threshold=0.1, partial=False, pin_factory=None): super(LightSensor, self).__init__( pin, pull_up=False, threshold=threshold, queue_len=queue_len, sample_wait=0.0, partial=partial, pin_factory=pin_factory ) try: self._charge_time_limit = charge_time_limit self._charged = Event() self.pin.edges = 'rising' self.pin.bounce = None self.pin.when_changed = self._charged.set self._queue.start() except: self.close() raise @property def charge_time_limit(self): return self._charge_time_limit def _read(self): # Drain charge from the capacitor self.pin.function = 'output' self.pin.state = False sleep(0.1) # Time the charging of the capacitor start = time() self._charged.clear() self.pin.function = 'input' self._charged.wait(self.charge_time_limit) return ( 1.0 - min(self.charge_time_limit, time() - start) / self.charge_time_limit ) LightSensor.light_detected = LightSensor.is_active LightSensor.when_light = LightSensor.when_activated LightSensor.when_dark = LightSensor.when_deactivated LightSensor.wait_for_light = LightSensor.wait_for_active LightSensor.wait_for_dark = LightSensor.wait_for_inactive class DistanceSensor(SmoothedInputDevice): """ Extends :class:`SmoothedInputDevice` and represents an HC-SR04 ultrasonic distance sensor, as found in the `CamJam #3 EduKit`_. The distance sensor requires two GPIO pins: one for the *trigger* (marked TRIG on the sensor) and another for the *echo* (marked ECHO on the sensor). However, a voltage divider is required to ensure the 5V from the ECHO pin doesn't damage the Pi. Wire your sensor according to the following instructions: 1. Connect the GND pin of the sensor to a ground pin on the Pi. 2. Connect the TRIG pin of the sensor a GPIO pin. 3. Connect one end of a 330Ω resistor to the ECHO pin of the sensor. 4. Connect one end of a 470Ω resistor to the GND pin of the sensor. 5. Connect the free ends of both resistors to another GPIO pin. This forms the required `voltage divider`_. 6. Finally, connect the VCC pin of the sensor to a 5V pin on the Pi. .. note:: If you do not have the precise values of resistor specified above, don't worry! What matters is the *ratio* of the resistors to each other. You also don't need to be absolutely precise; the `voltage divider`_ given above will actually output ~3V (rather than 3.3V). A simple 2:3 ratio will give 3.333V which implies you can take three resistors of equal value, use one of them instead of the 330Ω resistor, and two of them in series instead of the 470Ω resistor. .. _voltage divider: https://en.wikipedia.org/wiki/Voltage_divider The following code will periodically report the distance measured by the sensor in cm assuming the TRIG pin is connected to GPIO17, and the ECHO pin to GPIO18:: from gpiozero import DistanceSensor from time import sleep sensor = DistanceSensor(echo=18, trigger=17) while True: print('Distance: ', sensor.distance * 100) sleep(1) :param int echo: The GPIO pin which the ECHO pin is attached to. See :ref:`pin-numbering` for valid pin numbers. :param int trigger: The GPIO pin which the TRIG pin is attached to. See :ref:`pin-numbering` for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the sensor. This defaults to 30. :param float max_distance: The :attr:`value` attribute reports a normalized value between 0 (too close to measure) and 1 (maximum distance). This parameter specifies the maximum distance expected in meters. This defaults to 1. :param float threshold_distance: Defaults to 0.3. This is the distance (in meters) that will trigger the ``in_range`` and ``out_of_range`` events when crossed. :param bool partial: When ``False`` (the default), the object will not return a value for :attr:`~SmoothedInputDevice.is_active` until the internal queue has filled with values. Only set this to ``True`` if you require values immediately after object construction. :param Factory pin_factory: See :doc:`api_pins` for more information (this is an advanced feature which most users can ignore). .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 """ ECHO_LOCK = Lock() def __init__( self, echo=None, trigger=None, queue_len=10, max_distance=1, threshold_distance=0.3, partial=False, pin_factory=None): if max_distance <= 0: raise ValueError('invalid maximum distance (must be positive)') self._trigger = None super(DistanceSensor, self).__init__( echo, pull_up=False, threshold=threshold_distance / max_distance, queue_len=queue_len, sample_wait=0.06, partial=partial, pin_factory=pin_factory ) try: self.speed_of_sound = 343.26 # m/s self._max_distance = max_distance self._trigger = GPIODevice(trigger) self._echo = Event() self._echo_rise = None self._echo_fall = None self._trigger.pin.function = 'output' self._trigger.pin.state = False self.pin.edges = 'both' self.pin.bounce = None self.pin.when_changed = self._echo_changed self._queue.start() except: self.close() raise def close(self): try: self._trigger.close() except AttributeError: if getattr(self, '_trigger', None) is not None: raise self._trigger = None super(DistanceSensor, self).close() @property def max_distance(self): """ The maximum distance that the sensor will measure in meters. This value is specified in the constructor and is used to provide the scaling for the :attr:`value` attribute. When :attr:`distance` is equal to :attr:`max_distance`, :attr:`value` will be 1. """ return self._max_distance @max_distance.setter def max_distance(self, value): if value <= 0: raise ValueError('invalid maximum distance (must be positive)') t = self.threshold_distance self._max_distance = value self.threshold_distance = t @property def threshold_distance(self): """ The distance, measured in meters, that will trigger the :attr:`when_in_range` and :attr:`when_out_of_range` events when crossed. This is simply a meter-scaled variant of the usual :attr:`threshold` attribute. """ return self.threshold * self.max_distance @threshold_distance.setter def threshold_distance(self, value): self.threshold = value / self.max_distance @property def distance(self): """ Returns the current distance measured by the sensor in meters. Note that this property will have a value between 0 and :attr:`max_distance`. """ return self.value * self._max_distance @property def trigger(self): """ Returns the :class:`Pin` that the sensor's trigger is connected to. """ return self._trigger.pin @property def echo(self): """ Returns the :class:`Pin` that the sensor's echo is connected to. This is simply an alias for the usual :attr:`pin` attribute. """ return self.pin def _echo_changed(self): if self._echo_rise is None: self._echo_rise = time() else: self._echo_fall = time() self._echo.set() def _read(self): # Make sure the echo pin is low then ensure the echo event is clear while self.pin.state: sleep(0.00001) self._echo.clear() # Obtain ECHO_LOCK to ensure multiple distance sensors don't listen # for each other's "pings" with DistanceSensor.ECHO_LOCK: # Fire the trigger self._trigger.pin.state = True sleep(0.00001) self._trigger.pin.state = False # Wait up to 1 second for the echo pin to rise if self._echo.wait(1): self._echo.clear() # Wait up to 40ms for the echo pin to fall (35ms is maximum # pulse time so any longer means something's gone wrong). # Calculate distance as time for echo multiplied by speed of # sound divided by two to compensate for travel to and from the # reflector if self._echo.wait(0.04) and self._echo_fall is not None and self._echo_rise is not None: distance = (self._echo_fall - self._echo_rise) * self.speed_of_sound / 2.0 self._echo_fall = None self._echo_rise = None return min(1.0, distance / self._max_distance) else: # If we only saw one edge it means we missed the echo # because it was too fast; report minimum distance return 0.0 else: # The echo pin never rose or fell; something's gone horribly # wrong warnings.warn(DistanceSensorNoEcho('no echo received')) return 1.0 @property def in_range(self): return not self.is_active DistanceSensor.when_out_of_range = DistanceSensor.when_activated DistanceSensor.when_in_range = DistanceSensor.when_deactivated DistanceSensor.wait_for_out_of_range = DistanceSensor.wait_for_active DistanceSensor.wait_for_in_range = DistanceSensor.wait_for_inactive gpiozero-1.4.1/gpiozero/tools.py0000644000175000017500000004505113243115414016606 0ustar davedave00000000000000# vim: set fileencoding=utf-8: from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') from random import random from time import sleep try: from itertools import izip as zip except ImportError: pass from itertools import cycle from math import sin, cos, pi try: from statistics import mean except ImportError: from .compat import mean try: from math import isclose except ImportError: from .compat import isclose def negated(values): """ Returns the negation of the supplied values (``True`` becomes ``False``, and ``False`` becomes ``True``). For example:: from gpiozero import Button, LED from gpiozero.tools import negated from signal import pause led = LED(4) btn = Button(17) led.source = negated(btn.values) pause() """ for v in values: yield not v def inverted(values, input_min=0, input_max=1): """ Returns the inversion of the supplied values (*input_min* becomes *input_max*, *input_max* becomes *input_min*, `input_min + 0.1` becomes `input_max - 0.1`, etc.). All items in *values* are assumed to be between *input_min* and *input_max* (which default to 0 and 1 respectively), and the output will be in the same range. For example:: from gpiozero import MCP3008, PWMLED from gpiozero.tools import inverted from signal import pause led = PWMLED(4) pot = MCP3008(channel=0) led.source = inverted(pot.values) pause() """ if input_min >= input_max: raise ValueError('input_min must be smaller than input_max') for v in values: yield input_min + input_max - v def scaled(values, output_min, output_max, input_min=0, input_max=1): """ Returns *values* scaled from *output_min* to *output_max*, assuming that all items in *values* lie between *input_min* and *input_max* (which default to 0 and 1 respectively). For example, to control the direction of a motor (which is represented as a value between -1 and 1) using a potentiometer (which typically provides values between 0 and 1):: from gpiozero import Motor, MCP3008 from gpiozero.tools import scaled from signal import pause motor = Motor(20, 21) pot = MCP3008(channel=0) motor.source = scaled(pot.values, -1, 1) pause() .. warning:: If *values* contains elements that lie outside *input_min* to *input_max* (inclusive) then the function will not produce values that lie within *output_min* to *output_max* (inclusive). """ if input_min >= input_max: raise ValueError('input_min must be smaller than input_max') input_size = input_max - input_min output_size = output_max - output_min for v in values: yield (((v - input_min) / input_size) * output_size) + output_min def clamped(values, output_min=0, output_max=1): """ Returns *values* clamped from *output_min* to *output_max*, i.e. any items less than *output_min* will be returned as *output_min* and any items larger than *output_max* will be returned as *output_max* (these default to 0 and 1 respectively). For example:: from gpiozero import PWMLED, MCP3008 from gpiozero.tools import clamped from signal import pause led = PWMLED(4) pot = MCP3008(channel=0) led.source = clamped(pot.values, 0.5, 1.0) pause() """ if output_min >= output_max: raise ValueError('output_min must be smaller than output_max') for v in values: yield min(max(v, output_min), output_max) def absoluted(values): """ Returns *values* with all negative elements negated (so that they're positive). For example:: from gpiozero import PWMLED, Motor, MCP3008 from gpiozero.tools import absoluted, scaled from signal import pause led = PWMLED(4) motor = Motor(22, 27) pot = MCP3008(channel=0) motor.source = scaled(pot.values, -1, 1) led.source = absoluted(motor.values) pause() """ for v in values: yield abs(v) def quantized(values, steps, input_min=0, input_max=1): """ Returns *values* quantized to *steps* increments. All items in *values* are assumed to be between *input_min* and *input_max* (which default to 0 and 1 respectively), and the output will be in the same range. For example, to quantize values between 0 and 1 to 5 "steps" (0.0, 0.25, 0.5, 0.75, 1.0):: from gpiozero import PWMLED, MCP3008 from gpiozero.tools import quantized from signal import pause led = PWMLED(4) pot = MCP3008(channel=0) led.source = quantized(pot.values, 4) pause() """ if steps < 1: raise ValueError("steps must be 1 or larger") if input_min >= input_max: raise ValueError('input_min must be smaller than input_max') input_size = input_max - input_min for v in scaled(values, 0, 1, input_min, input_max): yield ((int(v * steps) / steps) * input_size) + input_min def booleanized(values, min_value, max_value, hysteresis=0): """ Returns True for each item in *values* between *min_value* and *max_value*, and False otherwise. *hysteresis* can optionally be used to add `hysteresis`_ which prevents the output value rapidly flipping when the input value is fluctuating near the *min_value* or *max_value* thresholds. For example, to light an LED only when a potentiometer is between 1/4 and 3/4 of its full range:: from gpiozero import LED, MCP3008 from gpiozero.tools import booleanized from signal import pause led = LED(4) pot = MCP3008(channel=0) led.source = booleanized(pot.values, 0.25, 0.75) pause() .. _hysteresis: https://en.wikipedia.org/wiki/Hysteresis """ if min_value >= max_value: raise ValueError('min_value must be smaller than max_value') min_value = float(min_value) max_value = float(max_value) if hysteresis < 0: raise ValueError("hysteresis must be 0 or larger") else: hysteresis = float(hysteresis) if (max_value - min_value) <= hysteresis: raise ValueError('The gap between min_value and max_value must be larger than hysteresis') last_state = None for v in values: if v < min_value: new_state = 'below' elif v > max_value: new_state = 'above' else: new_state = 'in' switch = False if last_state == None or not hysteresis: switch = True elif new_state == last_state: pass else: # new_state != last_state if last_state == 'below' and new_state == 'in': switch = v >= min_value + hysteresis elif last_state == 'in' and new_state == 'below': switch = v < min_value - hysteresis elif last_state == 'in' and new_state == 'above': switch = v > max_value + hysteresis elif last_state == 'above' and new_state == 'in': switch = v <= max_value - hysteresis else: # above->below or below->above switch = True if switch: last_state = new_state yield last_state == 'in' def all_values(*values): """ Returns the `logical conjunction`_ of all supplied values (the result is only ``True`` if and only if all input values are simultaneously ``True``). One or more *values* can be specified. For example, to light an :class:`LED` only when *both* buttons are pressed:: from gpiozero import LED, Button from gpiozero.tools import all_values from signal import pause led = LED(4) btn1 = Button(20) btn2 = Button(21) led.source = all_values(btn1.values, btn2.values) pause() .. _logical conjunction: https://en.wikipedia.org/wiki/Logical_conjunction """ for v in zip(*values): yield all(v) def any_values(*values): """ Returns the `logical disjunction`_ of all supplied values (the result is ``True`` if any of the input values are currently ``True``). One or more *values* can be specified. For example, to light an :class:`LED` when *any* button is pressed:: from gpiozero import LED, Button from gpiozero.tools import any_values from signal import pause led = LED(4) btn1 = Button(20) btn2 = Button(21) led.source = any_values(btn1.values, btn2.values) pause() .. _logical disjunction: https://en.wikipedia.org/wiki/Logical_disjunction """ for v in zip(*values): yield any(v) def averaged(*values): """ Returns the mean of all supplied values. One or more *values* can be specified. For example, to light a :class:`PWMLED` as the average of several potentiometers connected to an :class:`MCP3008` ADC:: from gpiozero import MCP3008, PWMLED from gpiozero.tools import averaged from signal import pause pot1 = MCP3008(channel=0) pot2 = MCP3008(channel=1) pot3 = MCP3008(channel=2) led = PWMLED(4) led.source = averaged(pot1.values, pot2.values, pot3.values) pause() """ for v in zip(*values): yield mean(v) def summed(*values): """ Returns the sum of all supplied values. One or more *values* can be specified. For example, to light a :class:`PWMLED` as the (scaled) sum of several potentiometers connected to an :class:`MCP3008` ADC:: from gpiozero import MCP3008, PWMLED from gpiozero.tools import summed, scaled from signal import pause pot1 = MCP3008(channel=0) pot2 = MCP3008(channel=1) pot3 = MCP3008(channel=2) led = PWMLED(4) led.source = scaled(summed(pot1.values, pot2.values, pot3.values), 0, 1, 0, 3) pause() """ for v in zip(*values): yield sum(v) def multiplied(*values): """ Returns the product of all supplied values. One or more *values* can be specified. For example, to light a :class:`PWMLED` as the product (i.e. multiplication) of several potentiometers connected to an :class:`MCP3008` ADC:: from gpiozero import MCP3008, PWMLED from gpiozero.tools import multiplied from signal import pause pot1 = MCP3008(channel=0) pot2 = MCP3008(channel=1) pot3 = MCP3008(channel=2) led = PWMLED(4) led.source = multiplied(pot1.values, pot2.values, pot3.values) pause() """ def _product(it): p = 1 for n in it: p *= n return p for v in zip(*values): yield _product(v) def queued(values, qsize): """ Queues up readings from *values* (the number of readings queued is determined by *qsize*) and begins yielding values only when the queue is full. For example, to "cascade" values along a sequence of LEDs:: from gpiozero import LEDBoard, Button from gpiozero.tools import queued from signal import pause leds = LEDBoard(5, 6, 13, 19, 26) btn = Button(17) for i in range(4): leds[i].source = queued(leds[i + 1].values, 5) leds[i].source_delay = 0.01 leds[4].source = btn.values pause() """ if qsize < 1: raise ValueError("qsize must be 1 or larger") q = [] it = iter(values) for i in range(qsize): q.append(next(it)) for i in cycle(range(qsize)): yield q[i] try: q[i] = next(it) except StopIteration: break def smoothed(values, qsize, average=mean): """ Queues up readings from *values* (the number of readings queued is determined by *qsize*) and begins yielding the *average* of the last *qsize* values when the queue is full. The larger the *qsize*, the more the values are smoothed. For example, to smooth the analog values read from an ADC:: from gpiozero import MCP3008 from gpiozero.tools import smoothed adc = MCP3008(channel=0) for value in smoothed(adc.values, 5): print(value) """ if qsize < 1: raise ValueError("qsize must be 1 or larger") q = [] it = iter(values) for i in range(qsize): q.append(next(it)) for i in cycle(range(qsize)): yield average(q) try: q[i] = next(it) except StopIteration: break def pre_delayed(values, delay): """ Waits for *delay* seconds before returning each item from *values*. """ if delay < 0: raise ValueError("delay must be 0 or larger") for v in values: sleep(delay) yield v def post_delayed(values, delay): """ Waits for *delay* seconds after returning each item from *values*. """ if delay < 0: raise ValueError("delay must be 0 or larger") for v in values: yield v sleep(delay) def pre_periodic_filtered(values, block, repeat_after): """ Blocks the first *block* items from *values*, repeating the block after every *repeat_after* items, if *repeat_after* is non-zero. For example, to discard the first 50 values read from an ADC:: from gpiozero import MCP3008 from gpiozero.tools import pre_periodic_filtered adc = MCP3008(channel=0) for value in pre_periodic_filtered(adc.values, 50, 0): print(value) Or to only display every even item read from an ADC:: from gpiozero import MCP3008 from gpiozero.tools import pre_periodic_filtered adc = MCP3008(channel=0) for value in pre_periodic_filtered(adc.values, 1, 1): print(value) """ if block < 1: raise ValueError("block must be 1 or larger") if repeat_after < 0: raise ValueError("repeat_after must be 0 or larger") it = iter(values) if repeat_after == 0: for _ in range(block): next(it) while True: yield next(it) else: while True: for _ in range(block): next(it) for _ in range(repeat_after): yield next(it) def post_periodic_filtered(values, repeat_after, block): """ After every *repeat_after* items, blocks the next *block* items from *values*. Note that unlike :func:`pre_periodic_filtered`, *repeat_after* can't be 0. For example, to block every tenth item read from an ADC:: from gpiozero import MCP3008 from gpiozero.tools import post_periodic_filtered adc = MCP3008(channel=0) for value in post_periodic_filtered(adc.values, 9, 1): print(value) """ if repeat_after < 1: raise ValueError("repeat_after must be 1 or larger") if block < 1: raise ValueError("block must be 1 or larger") it = iter(values) while True: for _ in range(repeat_after): yield next(it) for _ in range(block): next(it) def random_values(): """ Provides an infinite source of random values between 0 and 1. For example, to produce a "flickering candle" effect with an LED:: from gpiozero import PWMLED from gpiozero.tools import random_values from signal import pause led = PWMLED(4) led.source = random_values() pause() If you require a wider range than 0 to 1, see :func:`scaled`. """ while True: yield random() def sin_values(period=360): """ Provides an infinite source of values representing a sine wave (from -1 to +1) which repeats every *period* values. For example, to produce a "siren" effect with a couple of LEDs that repeats once a second:: from gpiozero import PWMLED from gpiozero.tools import sin_values, scaled, inverted from signal import pause red = PWMLED(2) blue = PWMLED(3) red.source_delay = 0.01 blue.source_delay = red.source_delay red.source = scaled(sin_values(100), 0, 1, -1, 1) blue.source = inverted(red.values) pause() If you require a different range than -1 to +1, see :func:`scaled`. """ angles = (2 * pi * i / period for i in range(period)) for a in cycle(angles): yield sin(a) def cos_values(period=360): """ Provides an infinite source of values representing a cosine wave (from -1 to +1) which repeats every *period* values. For example, to produce a "siren" effect with a couple of LEDs that repeats once a second:: from gpiozero import PWMLED from gpiozero.tools import cos_values, scaled, inverted from signal import pause red = PWMLED(2) blue = PWMLED(3) red.source_delay = 0.01 blue.source_delay = red.source_delay red.source = scaled(cos_values(100), 0, 1, -1, 1) blue.source = inverted(red.values) pause() If you require a different range than -1 to +1, see :func:`scaled`. """ angles = (2 * pi * i / period for i in range(period)) for a in cycle(angles): yield cos(a) def alternating_values(initial_value=False): """ Provides an infinite source of values alternating between ``True`` and ``False``, starting wth *initial_value* (which defaults to ``False``). For example, to produce a flashing LED:: from gpiozero import LED from gpiozero.tools import alternating_values from signal import pause red = LED(2) red.source_delay = 0.5 red.source = alternating_values() pause() """ value = initial_value while True: yield value value = not value def ramping_values(period=360): """ Provides an infinite source of values representing a triangle wave (from 0 to 1 and back again) which repeats every *period* values. For example, to pulse an LED once a second:: from gpiozero import PWMLED from gpiozero.tools import ramping_values from signal import pause red = PWMLED(2) red.source_delay = 0.01 red.source = ramping_values(100) pause() If you require a wider range than 0 to 1, see :func:`scaled`. """ step = 2 / period value = 0 while True: yield value value += step if isclose(value, 1, abs_tol=1e-9): value = 1 step *= -1 elif isclose(value, 0, abs_tol=1e-9): value = 0 step *= -1 elif value > 1 or value < 0: step *= -1 value += step gpiozero-1.4.1/gpiozero/spi_devices.py0000644000175000017500000004730713243115414017751 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') from math import log, ceil from operator import or_ try: from functools import reduce except ImportError: pass # py2's reduce is built-in from .exc import DeviceClosed, SPIBadChannel from .devices import Device class SPIDevice(Device): """ Extends :class:`Device`. Represents a device that communicates via the SPI protocol. See :ref:`spi_args` for information on the keyword arguments that can be specified with the constructor. """ def __init__(self, **spi_args): self._spi = None super(SPIDevice, self).__init__( pin_factory=spi_args.pop('pin_factory', None) ) self._spi = self.pin_factory.spi(**spi_args) def close(self): if getattr(self, '_spi', None): self._spi.close() self._spi = None super(SPIDevice, self).close() @property def closed(self): return self._spi is None def _int_to_words(self, pattern): """ Given a bit-pattern expressed an integer number, return a sequence of the individual words that make up the pattern. The number of bits per word will be obtained from the internal SPI interface. """ try: bits_required = int(ceil(log(pattern, 2))) + 1 except ValueError: # pattern == 0 (technically speaking, no bits are required to # transmit the value zero ;) bits_required = 1 shifts = range(0, bits_required, self._spi.bits_per_word)[::-1] mask = 2 ** self._spi.bits_per_word - 1 return [(pattern >> shift) & mask for shift in shifts] def _words_to_int(self, words, expected_bits=None): """ Given a sequence of words which each fit in the internal SPI interface's number of bits per word, returns the value obtained by concatenating each word into a single bit-string. If *expected_bits* is specified, it limits the size of the output to the specified number of bits (by masking off bits above the expected number). If unspecified, no limit will be applied. """ if expected_bits is None: expected_bits = len(words) * self._spi.bits_per_word shifts = range(0, expected_bits, self._spi.bits_per_word)[::-1] mask = 2 ** expected_bits - 1 return reduce(or_, (word << shift for word, shift in zip(words, shifts))) & mask def __repr__(self): try: self._check_open() return "" % (self.__class__.__name__, self._spi) except DeviceClosed: return "" % self.__class__.__name__ class AnalogInputDevice(SPIDevice): """ Represents an analog input device connected to SPI (serial interface). Typical analog input devices are `analog to digital converters`_ (ADCs). Several classes are provided for specific ADC chips, including :class:`MCP3004`, :class:`MCP3008`, :class:`MCP3204`, and :class:`MCP3208`. The following code demonstrates reading the first channel of an MCP3008 chip attached to the Pi's SPI pins:: from gpiozero import MCP3008 pot = MCP3008(0) print(pot.value) The :attr:`value` attribute is normalized such that its value is always between 0.0 and 1.0 (or in special cases, such as differential sampling, -1 to +1). Hence, you can use an analog input to control the brightness of a :class:`PWMLED` like so:: from gpiozero import MCP3008, PWMLED pot = MCP3008(0) led = PWMLED(17) led.source = pot.values The :attr:`voltage` attribute reports values between 0.0 and *max_voltage* (which defaults to 3.3, the logic level of the GPIO pins). .. _analog to digital converters: https://en.wikipedia.org/wiki/Analog-to-digital_converter """ def __init__(self, bits, max_voltage=3.3, **spi_args): if bits is None: raise InputDeviceError('you must specify the bit resolution of the device') self._bits = bits self._min_value = -(2 ** bits) self._range = 2 ** (bits + 1) - 1 if max_voltage <= 0: raise InputDeviceError('max_voltage must be positive') self._max_voltage = float(max_voltage) super(AnalogInputDevice, self).__init__(shared=True, **spi_args) @property def bits(self): """ The bit-resolution of the device/channel. """ return self._bits def _read(self): raise NotImplementedError @property def value(self): """ The current value read from the device, scaled to a value between 0 and 1 (or -1 to +1 for certain devices operating in differential mode). """ return (2 * (self._read() - self._min_value) / self._range) - 1 @property def raw_value(self): """ The raw value as read from the device. """ return self._read() @property def max_voltage(self): """ The voltage required to set the device's value to 1. """ return self._max_voltage @property def voltage(self): """ The current voltage read from the device. This will be a value between 0 and the *max_voltage* parameter specified in the constructor. """ return self.value * self._max_voltage class MCP3xxx(AnalogInputDevice): """ Extends :class:`AnalogInputDevice` to implement an interface for all ADC chips with a protocol similar to the Microchip MCP3xxx series of devices. """ def __init__(self, channel=0, bits=10, differential=False, max_voltage=3.3, **spi_args): self._channel = channel self._differential = bool(differential) super(MCP3xxx, self).__init__(bits, max_voltage, **spi_args) @property def channel(self): """ The channel to read data from. The MCP3008/3208/3304 have 8 channels (0-7), while the MCP3004/3204/3302 have 4 channels (0-3), the MCP3002/3202 have 2 channels (0-1), and the MCP3001/3201/3301 only have 1 channel. """ return self._channel @property def differential(self): """ If ``True``, the device is operated in differential mode. In this mode one channel (specified by the channel attribute) is read relative to the value of a second channel (implied by the chip's design). Please refer to the device data-sheet to determine which channel is used as the relative base value (for example, when using an :class:`MCP3008` in differential mode, channel 0 is read relative to channel 1). """ return self._differential def _read(self): return self._words_to_int( self._spi.transfer(self._send())[-2:], self.bits ) def _send(self): # MCP3004/08 protocol looks like the following: # # Byte 0 1 2 # ==== ======== ======== ======== # Tx 00000001 MCCCxxxx xxxxxxxx # Rx xxxxxxxx xxxxx0RR RRRRRRRR # # MCP3204/08 protocol looks like the following: # # Byte 0 1 2 # ==== ======== ======== ======== # Tx 000001MC CCxxxxxx xxxxxxxx # Rx xxxxxxxx xxx0RRRR RRRRRRRR # # The transmit bits start with several preamble "0" bits, the number # of which is determined by the amount required to align the last byte # of the result with the final byte of output. A start "1" bit is then # transmitted, followed by the single/differential bit (M); 1 for # single-ended read, 0 for differential read. Next comes three bits for # channel (C). # # Read-out begins with a don't care bit (x), then a null bit (0) # followed by the result bits (R). All other bits are don't care (x). # # The 3x01 variant of the chips always operates in differential mode # and effectively only has one channel (composed of an IN+ and IN-). As # such it requires no input, just output. return self._int_to_words( (0b10000 | (not self.differential) << 3 | self.channel) << (self.bits + 2) ) class MCP3xx2(MCP3xxx): def _send(self): # MCP3002 protocol looks like the following: # # Byte 0 1 # ==== ======== ======== # Tx 01MCLxxx xxxxxxxx # Rx xxxxx0RR RRRRRRRR for the 3002 # # MCP3202 protocol looks like the following: # # Byte 0 1 2 # ==== ======== ======== ======== # Tx 00000001 MCLxxxxx xxxxxxxx # Rx xxxxxxxx xxx0RRRR RRRRRRRR # # The transmit bits start with several preamble "0" bits, the number of # which is determined by the amount required to align the last byte of # the result with the final byte of output. A start "1" bit is then # transmitted, followed by the single/differential bit (M); 1 for # single-ended read, 0 for differential read. Next comes a single bit # for channel (M) then the MSBF bit (L) which selects whether the data # will be read out in MSB form only (1) or whether LSB read-out will # occur after MSB read-out (0). # # Read-out begins with a null bit (0) followed by the result bits (R). # All other bits are don't care (x). return self._int_to_words( (0b1001 | (not self.differential) << 2 | self.channel << 1) << (self.bits + 1) ) class MCP30xx(MCP3xxx): """ Extends :class:`MCP3xxx` to implement an interface for all ADC chips with a protocol similar to the Microchip MCP30xx series of devices. """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): super(MCP30xx, self).__init__(channel, 10, differential, max_voltage, **spi_args) class MCP32xx(MCP3xxx): """ Extends :class:`MCP3xxx` to implement an interface for all ADC chips with a protocol similar to the Microchip MCP32xx series of devices. """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): super(MCP32xx, self).__init__(channel, 12, differential, max_voltage, **spi_args) class MCP33xx(MCP3xxx): """ Extends :class:`MCP3xxx` with functionality specific to the MCP33xx family of ADCs; specifically this handles the full differential capability of these chips supporting the full 13-bit signed range of output values. """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): super(MCP33xx, self).__init__(channel, 12, differential, max_voltage, **spi_args) def _read(self): if self.differential: result = self._words_to_int( self._spi.transfer(self._send())[-2:], self.bits + 1) # Account for the sign bit if result > 4095: return -(8192 - result) else: return result else: return super(MCP33xx, self)._read() def _send(self): # MCP3302/04 protocol looks like the following: # # Byte 0 1 2 # ==== ======== ======== ======== # Tx 00001MCC Cxxxxxxx xxxxxxxx # Rx xxxxxxxx xx0SRRRR RRRRRRRR # # The transmit bits start with 4 preamble bits "0000", a start bit "1" # followed by the single/differential bit (M) which is 1 for # single-ended read, and 0 for differential read, followed by 3-bits # for the channel (C). The remainder of the transmission are "don't # care" bits (x). # # The first byte received and the top 2 bits of the second byte are # don't care bits (x). These are followed by a null bit (0), then the # sign bit (S), and then the 12 result bits (R). # # In single read mode (the default) the sign bit is always zero and the # result is effectively 12-bits. In differential mode, the sign bit is # significant and the result is a two's-complement 13-bit value. # # The MCP3301 variant operates similarly to the other MCP3x01 variants; # no input, just output and always differential. return self._int_to_words( (0b10000 | (not self.differential) << 3 | self.channel) << (self.bits + 3) ) @property def differential(self): """ If ``True``, the device is operated in differential mode. In this mode one channel (specified by the channel attribute) is read relative to the value of a second channel (implied by the chip's design). Please refer to the device data-sheet to determine which channel is used as the relative base value (for example, when using an :class:`MCP3304` in differential mode, channel 0 is read relative to channel 1). """ return super(MCP33xx, self).differential @property def value(self): """ The current value read from the device, scaled to a value between 0 and 1 (or -1 to +1 for devices operating in differential mode). """ return super(MCP33xx, self).value class MCP3001(MCP30xx): """ The `MCP3001`_ is a 10-bit analog to digital converter with 1 channel. Please note that the MCP3001 always operates in differential mode, measuring the value of IN+ relative to IN-. .. _MCP3001: http://www.farnell.com/datasheets/630400.pdf """ def __init__(self, max_voltage=3.3, **spi_args): super(MCP3001, self).__init__(0, True, max_voltage, **spi_args) def _read(self): # MCP3001 protocol looks like the following: # # Byte 0 1 # ==== ======== ======== # Rx xx0RRRRR RRRRRxxx return self._words_to_int(self._spi.read(2), 13) >> 3 class MCP3002(MCP30xx, MCP3xx2): """ The `MCP3002`_ is a 10-bit analog to digital converter with 2 channels (0-1). .. _MCP3002: http://www.farnell.com/datasheets/1599363.pdf """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): if not 0 <= channel < 2: raise SPIBadChannel('channel must be 0 or 1') super(MCP3002, self).__init__(channel, differential, max_voltage, **spi_args) class MCP3004(MCP30xx): """ The `MCP3004`_ is a 10-bit analog to digital converter with 4 channels (0-3). .. _MCP3004: http://www.farnell.com/datasheets/808965.pdf """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): if not 0 <= channel < 4: raise SPIBadChannel('channel must be between 0 and 3') super(MCP3004, self).__init__(channel, differential, max_voltage, **spi_args) class MCP3008(MCP30xx): """ The `MCP3008`_ is a 10-bit analog to digital converter with 8 channels (0-7). .. _MCP3008: http://www.farnell.com/datasheets/808965.pdf """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): if not 0 <= channel < 8: raise SPIBadChannel('channel must be between 0 and 7') super(MCP3008, self).__init__(channel, differential, max_voltage, **spi_args) class MCP3201(MCP32xx): """ The `MCP3201`_ is a 12-bit analog to digital converter with 1 channel. Please note that the MCP3201 always operates in differential mode, measuring the value of IN+ relative to IN-. .. _MCP3201: http://www.farnell.com/datasheets/1669366.pdf """ def __init__(self, max_voltage=3.3, **spi_args): super(MCP3201, self).__init__(0, True, max_voltage, **spi_args) def _read(self): # MCP3201 protocol looks like the following: # # Byte 0 1 # ==== ======== ======== # Rx xx0RRRRR RRRRRRRx return self._words_to_int(self._spi.read(2), 13) >> 1 class MCP3202(MCP32xx, MCP3xx2): """ The `MCP3202`_ is a 12-bit analog to digital converter with 2 channels (0-1). .. _MCP3202: http://www.farnell.com/datasheets/1669376.pdf """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): if not 0 <= channel < 2: raise SPIBadChannel('channel must be 0 or 1') super(MCP3202, self).__init__(channel, differential, max_voltage, **spi_args) class MCP3204(MCP32xx): """ The `MCP3204`_ is a 12-bit analog to digital converter with 4 channels (0-3). .. _MCP3204: http://www.farnell.com/datasheets/808967.pdf """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): if not 0 <= channel < 4: raise SPIBadChannel('channel must be between 0 and 3') super(MCP3204, self).__init__(channel, differential, max_voltage, **spi_args) class MCP3208(MCP32xx): """ The `MCP3208`_ is a 12-bit analog to digital converter with 8 channels (0-7). .. _MCP3208: http://www.farnell.com/datasheets/808967.pdf """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): if not 0 <= channel < 8: raise SPIBadChannel('channel must be between 0 and 7') super(MCP3208, self).__init__(channel, differential, max_voltage, **spi_args) class MCP3301(MCP33xx): """ The `MCP3301`_ is a signed 13-bit analog to digital converter. Please note that the MCP3301 always operates in differential mode measuring the difference between IN+ and IN-. Its output value is scaled from -1 to +1. .. _MCP3301: http://www.farnell.com/datasheets/1669397.pdf """ def __init__(self, max_voltage=3.3, **spi_args): super(MCP3301, self).__init__(0, True, max_voltage, **spi_args) def _read(self): # MCP3301 protocol looks like the following: # # Byte 0 1 # ==== ======== ======== # Rx xx0SRRRR RRRRRRRR result = self._words_to_int(self._spi.read(2), 13) # Account for the sign bit if result > 4095: return -(8192 - result) else: return result class MCP3302(MCP33xx): """ The `MCP3302`_ is a 12/13-bit analog to digital converter with 4 channels (0-3). When operated in differential mode, the device outputs a signed 13-bit value which is scaled from -1 to +1. When operated in single-ended mode (the default), the device outputs an unsigned 12-bit value scaled from 0 to 1. .. _MCP3302: http://www.farnell.com/datasheets/1486116.pdf """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): if not 0 <= channel < 4: raise SPIBadChannel('channel must be between 0 and 4') super(MCP3302, self).__init__(channel, differential, max_voltage, **spi_args) class MCP3304(MCP33xx): """ The `MCP3304`_ is a 12/13-bit analog to digital converter with 8 channels (0-7). When operated in differential mode, the device outputs a signed 13-bit value which is scaled from -1 to +1. When operated in single-ended mode (the default), the device outputs an unsigned 12-bit value scaled from 0 to 1. .. _MCP3304: http://www.farnell.com/datasheets/1486116.pdf """ def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args): if not 0 <= channel < 8: raise SPIBadChannel('channel must be between 0 and 7') super(MCP3304, self).__init__(channel, differential, max_voltage, **spi_args) gpiozero-1.4.1/gpiozero/mixins.py0000644000175000017500000004173713243115414016764 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) nstr = str str = type('') import inspect import weakref from functools import wraps, partial from threading import Event from collections import deque from time import time try: from statistics import median except ImportError: from .compat import median from .threads import GPIOThread from .exc import ( BadEventHandler, BadWaitTime, BadQueueLen, DeviceClosed, ) class ValuesMixin(object): """ Adds a :attr:`values` property to the class which returns an infinite generator of readings from the :attr:`value` property. There is rarely a need to use this mixin directly as all base classes in GPIO Zero include it. .. note:: Use this mixin *first* in the parent class list. """ @property def values(self): """ An infinite iterator of values read from `value`. """ while True: try: yield self.value except DeviceClosed: break class SourceMixin(object): """ Adds a :attr:`source` property to the class which, given an iterable, sets :attr:`value` to each member of that iterable until it is exhausted. This mixin is generally included in novel output devices to allow their state to be driven from another device. .. note:: Use this mixin *first* in the parent class list. """ def __init__(self, *args, **kwargs): self._source = None self._source_thread = None self._source_delay = 0.01 super(SourceMixin, self).__init__(*args, **kwargs) def close(self): try: self.source = None except AttributeError: pass try: super(SourceMixin, self).close() except AttributeError: pass def _copy_values(self, source): for v in source: self.value = v if self._source_thread.stopping.wait(self._source_delay): break @property def source_delay(self): """ The delay (measured in seconds) in the loop used to read values from :attr:`source`. Defaults to 0.01 seconds which is generally sufficient to keep CPU usage to a minimum while providing adequate responsiveness. """ return self._source_delay @source_delay.setter def source_delay(self, value): if value < 0: raise BadWaitTime('source_delay must be 0 or greater') self._source_delay = float(value) @property def source(self): """ The iterable to use as a source of values for :attr:`value`. """ return self._source @source.setter def source(self, value): if getattr(self, '_source_thread', None): self._source_thread.stop() self._source_thread = None self._source = value if value is not None: self._source_thread = GPIOThread(target=self._copy_values, args=(value,)) self._source_thread.start() class SharedMixin(object): """ This mixin marks a class as "shared". In this case, the meta-class (GPIOMeta) will use :meth:`_shared_key` to convert the constructor arguments to an immutable key, and will check whether any existing instances match that key. If they do, they will be returned by the constructor instead of a new instance. An internal reference counter is used to determine how many times an instance has been "constructed" in this way. When :meth:`close` is called, an internal reference counter will be decremented and the instance will only close when it reaches zero. """ _instances = {} def __del__(self): self._refs = 0 super(SharedMixin, self).__del__() @classmethod def _shared_key(cls, *args, **kwargs): """ Given the constructor arguments, returns an immutable key representing the instance. The default simply assumes all positional arguments are immutable. """ return args class EventsMixin(object): """ Adds edge-detected :meth:`when_activated` and :meth:`when_deactivated` events to a device based on changes to the :attr:`~Device.is_active` property common to all devices. Also adds :meth:`wait_for_active` and :meth:`wait_for_inactive` methods for level-waiting. .. note:: Note that this mixin provides no means of actually firing its events; call :meth:`_fire_events` in sub-classes when device state changes to trigger the events. This should also be called once at the end of initialization to set initial states. """ def __init__(self, *args, **kwargs): super(EventsMixin, self).__init__(*args, **kwargs) self._active_event = Event() self._inactive_event = Event() self._when_activated = None self._when_deactivated = None self._last_state = None self._last_changed = time() def wait_for_active(self, timeout=None): """ Pause the script until the device is activated, or the timeout is reached. :param float timeout: Number of seconds to wait before proceeding. If this is ``None`` (the default), then wait indefinitely until the device is active. """ return self._active_event.wait(timeout) def wait_for_inactive(self, timeout=None): """ Pause the script until the device is deactivated, or the timeout is reached. :param float timeout: Number of seconds to wait before proceeding. If this is ``None`` (the default), then wait indefinitely until the device is inactive. """ return self._inactive_event.wait(timeout) @property def when_activated(self): """ The function to run when the device changes state from inactive to active. This can be set to a function which accepts no (mandatory) parameters, or a Python function which accepts a single mandatory parameter (with as many optional parameters as you like). If the function accepts a single mandatory parameter, the device that activated will be passed as that parameter. Set this property to ``None`` (the default) to disable the event. """ return self._when_activated @when_activated.setter def when_activated(self, value): self._when_activated = self._wrap_callback(value) @property def when_deactivated(self): """ The function to run when the device changes state from active to inactive. This can be set to a function which accepts no (mandatory) parameters, or a Python function which accepts a single mandatory parameter (with as many optional parameters as you like). If the function accepts a single mandatory parameter, the device that deactivated will be passed as that parameter. Set this property to ``None`` (the default) to disable the event. """ return self._when_deactivated @when_deactivated.setter def when_deactivated(self, value): self._when_deactivated = self._wrap_callback(value) @property def active_time(self): """ The length of time (in seconds) that the device has been active for. When the device is inactive, this is ``None``. """ if self._active_event.is_set(): return time() - self._last_changed else: return None @property def inactive_time(self): """ The length of time (in seconds) that the device has been inactive for. When the device is active, this is ``None``. """ if self._inactive_event.is_set(): return time() - self._last_changed else: return None def _wrap_callback(self, fn): if fn is None: return None elif not callable(fn): raise BadEventHandler('value must be None or a callable') # If fn is wrapped with partial (i.e. partial, partialmethod, or wraps # has been used to produce it) we need to dig out the "real" function # that's been wrapped along with all the mandatory positional args # used in the wrapper so we can test the binding args = () wrapped_fn = fn while isinstance(wrapped_fn, partial): args = wrapped_fn.args + args wrapped_fn = wrapped_fn.func if inspect.isbuiltin(wrapped_fn): # We can't introspect the prototype of builtins. In this case we # assume that the builtin has no (mandatory) parameters; this is # the most reasonable assumption on the basis that pre-existing # builtins have no knowledge of gpiozero, and the sole parameter # we would pass is a gpiozero object return fn else: # Try binding ourselves to the argspec of the provided callable. # If this works, assume the function is capable of accepting no # parameters try: inspect.getcallargs(wrapped_fn, *args) return fn except TypeError: try: # If the above fails, try binding with a single parameter # (ourselves). If this works, wrap the specified callback inspect.getcallargs(wrapped_fn, *(args + (self,))) @wraps(fn) def wrapper(): return fn(self) return wrapper except TypeError: raise BadEventHandler( 'value must be a callable which accepts up to one ' 'mandatory parameter') def _fire_activated(self): # These methods are largely here to be overridden by descendents if self.when_activated: self.when_activated() def _fire_deactivated(self): # These methods are largely here to be overridden by descendents if self.when_deactivated: self.when_deactivated() def _fire_events(self): old_state = self._last_state new_state = self._last_state = self.is_active if old_state is None: # Initial "indeterminate" state; set events but don't fire # callbacks as there's not necessarily an edge if new_state: self._active_event.set() else: self._inactive_event.set() elif old_state != new_state: self._last_changed = time() if new_state: self._inactive_event.clear() self._active_event.set() self._fire_activated() else: self._active_event.clear() self._inactive_event.set() self._fire_deactivated() class HoldMixin(EventsMixin): """ Extends :class:`EventsMixin` to add the :attr:`when_held` event and the machinery to fire that event repeatedly (when :attr:`hold_repeat` is ``True``) at internals defined by :attr:`hold_time`. """ def __init__(self, *args, **kwargs): self._hold_thread = None super(HoldMixin, self).__init__(*args, **kwargs) self._when_held = None self._held_from = None self._hold_time = 1 self._hold_repeat = False self._hold_thread = HoldThread(self) def close(self): if getattr(self, '_hold_thread', None): self._hold_thread.stop() self._hold_thread = None try: super(HoldMixin, self).close() except AttributeError: pass def _fire_activated(self): super(HoldMixin, self)._fire_activated() self._hold_thread.holding.set() def _fire_deactivated(self): self._held_from = None super(HoldMixin, self)._fire_deactivated() def _fire_held(self): if self.when_held: self.when_held() @property def when_held(self): """ The function to run when the device has remained active for :attr:`hold_time` seconds. This can be set to a function which accepts no (mandatory) parameters, or a Python function which accepts a single mandatory parameter (with as many optional parameters as you like). If the function accepts a single mandatory parameter, the device that activated will be passed as that parameter. Set this property to ``None`` (the default) to disable the event. """ return self._when_held @when_held.setter def when_held(self, value): self._when_held = self._wrap_callback(value) @property def hold_time(self): """ The length of time (in seconds) to wait after the device is activated, until executing the :attr:`when_held` handler. If :attr:`hold_repeat` is True, this is also the length of time between invocations of :attr:`when_held`. """ return self._hold_time @hold_time.setter def hold_time(self, value): if value < 0: raise BadWaitTime('hold_time must be 0 or greater') self._hold_time = float(value) @property def hold_repeat(self): """ If ``True``, :attr:`when_held` will be executed repeatedly with :attr:`hold_time` seconds between each invocation. """ return self._hold_repeat @hold_repeat.setter def hold_repeat(self, value): self._hold_repeat = bool(value) @property def is_held(self): """ When ``True``, the device has been active for at least :attr:`hold_time` seconds. """ return self._held_from is not None @property def held_time(self): """ The length of time (in seconds) that the device has been held for. This is counted from the first execution of the :attr:`when_held` event rather than when the device activated, in contrast to :attr:`~EventsMixin.active_time`. If the device is not currently held, this is ``None``. """ if self._held_from is not None: return time() - self._held_from else: return None class HoldThread(GPIOThread): """ Extends :class:`GPIOThread`. Provides a background thread that repeatedly fires the :attr:`HoldMixin.when_held` event as long as the owning device is active. """ def __init__(self, parent): super(HoldThread, self).__init__( target=self.held, args=(weakref.proxy(parent),)) self.holding = Event() self.start() def held(self, parent): try: while not self.stopping.is_set(): if self.holding.wait(0.1): self.holding.clear() while not ( self.stopping.is_set() or parent._inactive_event.wait(parent.hold_time) ): if parent._held_from is None: parent._held_from = time() parent._fire_held() if not parent.hold_repeat: break except ReferenceError: # Parent is dead; time to die! pass class GPIOQueue(GPIOThread): """ Extends :class:`GPIOThread`. Provides a background thread that monitors a device's values and provides a running *average* (defaults to median) of those values. If the *parent* device includes the :class:`EventsMixin` in its ancestry, the thread automatically calls :meth:`~EventsMixin._fire_events`. """ def __init__( self, parent, queue_len=5, sample_wait=0.0, partial=False, average=median): assert callable(average) super(GPIOQueue, self).__init__(target=self.fill) if queue_len < 1: raise BadQueueLen('queue_len must be at least one') if sample_wait < 0: raise BadWaitTime('sample_wait must be 0 or greater') self.queue = deque(maxlen=queue_len) self.partial = bool(partial) self.sample_wait = float(sample_wait) self.full = Event() self.parent = weakref.proxy(parent) self.average = average @property def value(self): if not self.partial: self.full.wait() try: return self.average(self.queue) except ZeroDivisionError: # No data == inactive value return 0.0 def fill(self): try: while not self.stopping.wait(self.sample_wait): self.queue.append(self.parent._read()) if not self.full.is_set() and len(self.queue) >= self.queue.maxlen: self.full.set() if (self.partial or self.full.is_set()) and isinstance(self.parent, EventsMixin): self.parent._fire_events() except ReferenceError: # Parent is dead; time to die! pass gpiozero-1.4.1/gpiozero/exc.py0000644000175000017500000001433113243115414016222 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') class GPIOZeroError(Exception): "Base class for all exceptions in GPIO Zero" class DeviceClosed(GPIOZeroError): "Error raised when an operation is attempted on a closed device" class BadEventHandler(GPIOZeroError, ValueError): "Error raised when an event handler with an incompatible prototype is specified" class BadWaitTime(GPIOZeroError, ValueError): "Error raised when an invalid wait time is specified" class BadQueueLen(GPIOZeroError, ValueError): "Error raised when non-positive queue length is specified" class BadPinFactory(GPIOZeroError, ImportError): "Error raised when an unknown pin factory name is specified" class CompositeDeviceError(GPIOZeroError): "Base class for errors specific to the CompositeDevice hierarchy" class CompositeDeviceBadName(CompositeDeviceError, ValueError): "Error raised when a composite device is constructed with a reserved name" class CompositeDeviceBadOrder(CompositeDeviceError, ValueError): "Error raised when a composite device is constructed with an incomplete order" class CompositeDeviceBadDevice(CompositeDeviceError, ValueError): "Error raised when a composite device is constructed with an object that doesn't inherit from :class:`Device`" class EnergenieSocketMissing(CompositeDeviceError, ValueError): "Error raised when socket number is not specified" class EnergenieBadSocket(CompositeDeviceError, ValueError): "Error raised when an invalid socket number is passed to :class:`Energenie`" class SPIError(GPIOZeroError): "Base class for errors related to the SPI implementation" class SPIBadArgs(SPIError, ValueError): "Error raised when invalid arguments are given while constructing :class:`SPIDevice`" class SPIBadChannel(SPIError, ValueError): "Error raised when an invalid channel is given to an :class:`AnalogInputDevice`" class SPIFixedClockMode(SPIError, AttributeError): "Error raised when the SPI clock mode cannot be changed" class SPIInvalidClockMode(SPIError, ValueError): "Error raised when an invalid clock mode is given to an SPI implementation" class SPIFixedBitOrder(SPIError, AttributeError): "Error raised when the SPI bit-endianness cannot be changed" class SPIFixedSelect(SPIError, AttributeError): "Error raised when the SPI select polarity cannot be changed" class SPIFixedWordSize(SPIError, AttributeError): "Error raised when the number of bits per word cannot be changed" class SPIInvalidWordSize(SPIError, ValueError): "Error raised when an invalid (out of range) number of bits per word is specified" class GPIODeviceError(GPIOZeroError): "Base class for errors specific to the GPIODevice hierarchy" class GPIODeviceClosed(GPIODeviceError, DeviceClosed): "Deprecated descendent of :exc:`DeviceClosed`" 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 specification is not given" class InputDeviceError(GPIODeviceError): "Base class for errors specific to the InputDevice hierarchy" class OutputDeviceError(GPIODeviceError): "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 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 PinInvalidBounce(PinError, ValueError): "Error raised when attempting to assign an invalid bounce time 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 PinUnsupported(PinError, NotImplementedError): "Error raised when attempting to obtain a pin interface on unsupported pins" class PinSPIUnsupported(PinError, NotImplementedError): "Error raised when attempting to obtain an SPI interface 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" class PinUnknownPi(PinError, RuntimeError): "Error raised when gpiozero doesn't recognize a revision of the Pi" class PinMultiplePins(PinError, RuntimeError): "Error raised when multiple pins support the requested function" class PinNoPins(PinError, RuntimeError): "Error raised when no pins support the requested function" class PinInvalidPin(PinError, ValueError): "Error raised when an invalid pin specification is provided" class GPIOZeroWarning(Warning): "Base class for all warnings in GPIO Zero" class DistanceSensorNoEcho(GPIOZeroWarning): "Warning raised when the distance sensor sees no echo at all" class SPIWarning(GPIOZeroWarning): "Base class for warnings related to the SPI implementation" class SPISoftwareFallback(SPIWarning): "Warning raised when falling back to the software implementation" class PinWarning(GPIOZeroWarning): "Base class for warnings related to pin implementations" class PinFactoryFallback(PinWarning): "Warning raised when a default pin factory fails to load and a fallback is tried" class PinNonPhysical(PinWarning): "Warning raised when a non-physical pin is specified in a constructor" gpiozero-1.4.1/LICENCE.txt0000644000175000017500000000274413165454031015047 0ustar davedave00000000000000Copyright 2015- Raspberry Pi Foundation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. gpiozero-1.4.1/tests/0000755000175000017500000000000013243117413014374 5ustar davedave00000000000000gpiozero-1.4.1/tests/test_pins_data.py0000644000175000017500000002071313241530546017756 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import re import pytest from mock import patch, MagicMock import gpiozero.pins.data import gpiozero.pins.local from gpiozero.pins.local import LocalPiFactory from gpiozero.pins.data import Style, HeaderInfo, PinInfo from gpiozero import * def test_pi_revision(): with patch('gpiozero.devices.Device.pin_factory', LocalPiFactory()): # Can't use MockPin for this as we want something that'll actually try # and read /proc/cpuinfo (MockPin simply parrots the 2B's data); # LocalPiFactory is used as we can definitely instantiate it (strictly # speaking it's abstract but we're only interested in the pi_info # stuff) with patch('io.open') as m: m.return_value.__enter__.return_value = ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial: xxxxxxxxxxx'] assert pi_info().revision == '0002' # LocalPiFactory caches the revision (because realistically it # isn't going to change at runtime); we need to wipe it here though Device.pin_factory._info = None m.return_value.__enter__.return_value = ['Revision: a21042'] assert pi_info().revision == 'a21042' # Check over-volting result (some argument over whether this is 7 or # 8 character result; make sure both work) Device.pin_factory._info = None m.return_value.__enter__.return_value = ['Revision: 1000003'] assert pi_info().revision == '0003' Device.pin_factory._info = None m.return_value.__enter__.return_value = ['Revision: 100003'] assert pi_info().revision == '0003' with pytest.raises(PinUnknownPi): m.return_value.__enter__.return_value = ['nothing', 'relevant', 'at all'] Device.pin_factory._info = None pi_info() with pytest.raises(PinUnknownPi): pi_info('0fff') def test_pi_info(): r = pi_info('900011') assert r.model == 'B' assert r.pcb_revision == '1.0' assert r.memory == 512 assert r.manufacturer == 'Sony' assert r.storage == 'SD' assert r.usb == 2 assert r.ethernet == 1 assert not r.wifi assert not r.bluetooth assert r.csi == 1 assert r.dsi == 1 r = pi_info('9000f1') assert r.model == '???' assert r.pcb_revision == '1.1' assert r.memory == 512 assert r.manufacturer == 'Sony' assert r.storage == 'MicroSD' assert r.usb == 4 assert r.ethernet == 1 assert not r.wifi assert not r.bluetooth assert r.csi == 1 assert r.dsi == 1 def test_pi_info_other_types(): assert pi_info(b'9000f1') == pi_info(0x9000f1) def test_physical_pins(): # Assert physical pins for some well-known Pi's; a21041 is a Pi2B assert pi_info('a21041').physical_pins('3V3') == {('J8', 1), ('J8', 17)} assert pi_info('a21041').physical_pins('GPIO2') == {('J8', 3)} assert pi_info('a21041').physical_pins('GPIO47') == set() def test_physical_pin(): with pytest.raises(PinMultiplePins): assert pi_info('a21041').physical_pin('GND') assert pi_info('a21041').physical_pin('GPIO3') == ('J8', 5) with pytest.raises(PinNoPins): assert pi_info('a21041').physical_pin('GPIO47') def test_pulled_up(): assert pi_info('a21041').pulled_up('GPIO2') assert not pi_info('a21041').pulled_up('GPIO4') assert not pi_info('a21041').pulled_up('GPIO47') def test_pprint_content(): with patch('sys.stdout') as stdout: stdout.output = [] stdout.write = lambda buf: stdout.output.append(buf) pi_info('900092').pprint(color=False) s = ''.join(stdout.output) assert ('o' * 20 + ' ') in s # first header row assert ('1' + 'o' * 19 + ' ') in s # second header row assert 'PiZero' in s assert 'V1.2' in s # PCB revision assert '900092' in s # Pi revision assert 'BCM2835' in s # SOC name stdout.output = [] pi_info('0002').pprint(color=False) s = ''.join(stdout.output) assert ('o' * 13 + ' ') in s # first header row assert ('1' + 'o' * 12 + ' ') in s # second header row assert 'Pi Model' in s assert 'B V1.0' in s # PCB revision assert '0002' in s # Pi revision assert 'BCM2835' in s # SOC name stdout.output = [] pi_info('0014').headers['SODIMM'].pprint(color=False) assert len(''.join(stdout.output).splitlines()) == 100 def test_format_content(): with patch('sys.stdout') as stdout: stdout.output = [] stdout.write = lambda buf: stdout.output.append(buf) pi_info('900092').pprint(color=False) s = ''.join(stdout.output) assert '{0:mono}\n'.format(pi_info('900092')) == s stdout.output = [] pi_info('900092').pprint(color=True) s = ''.join(stdout.output) assert '{0:color full}\n'.format(pi_info('900092')) == s def test_pprint_headers(): assert len(pi_info('0002').headers) == 1 assert len(pi_info('000e').headers) == 2 assert len(pi_info('900092').headers) == 1 with patch('sys.stdout') as stdout: stdout.output = [] stdout.write = lambda buf: stdout.output.append(buf) pi_info('0002').pprint() s = ''.join(stdout.output) assert 'P1:\n' in s assert 'P5:\n' not in s stdout.output = [] pi_info('000e').pprint() s = ''.join(stdout.output) assert 'P1:\n' in s assert 'P5:\n' in s stdout.output = [] pi_info('900092').pprint() s = ''.join(stdout.output) assert 'J8:\n' in s assert 'P1:\n' not in s assert 'P5:\n' not in s def test_pprint_color(): with patch('sys.stdout') as stdout: stdout.output = [] stdout.write = lambda buf: stdout.output.append(buf) pi_info('900092').pprint(color=False) s = ''.join(stdout.output) assert '\x1b[0m' not in s # make sure ANSI reset code isn't in there stdout.output = [] pi_info('900092').pprint(color=True) s = ''.join(stdout.output) assert '\x1b[0m' in s # check the ANSI reset code *is* in there (can't guarantee much else!) stdout.output = [] stdout.fileno.side_effect = IOError('not a real file') pi_info('900092').pprint() s = ''.join(stdout.output) assert '\x1b[0m' not in s # default should output mono with patch('os.isatty') as isatty: isatty.return_value = True stdout.fileno.side_effect = None stdout.output = [] pi_info('900092').pprint() s = ''.join(stdout.output) assert '\x1b[0m' in s # default should now output color def test_pprint_styles(): with pytest.raises(ValueError): Style.from_style_content('mono color full') with pytest.raises(ValueError): Style.from_style_content('full specs') with patch('sys.stdout') as stdout: s = '{0:full}'.format(pi_info('900092')) assert '\x1b[0m' not in s # ensure default is mono when stdout is not a tty with pytest.raises(ValueError): '{0:foo on bar}'.format(Style()) def test_pprint_missing_pin(): header = HeaderInfo('FOO', 4, 2, { 1: PinInfo(1, '5V', False, 1, 1), 2: PinInfo(2, 'GND', False, 1, 2), # Pin 3 is deliberately missing 4: PinInfo(4, 'GPIO1', False, 2, 2), 5: PinInfo(5, 'GPIO2', False, 3, 1), 6: PinInfo(6, 'GPIO3', False, 3, 2), 7: PinInfo(7, '3V3', False, 4, 1), 8: PinInfo(8, 'GND', False, 4, 2), }) with patch('sys.stdout') as stdout: stdout.output = [] stdout.write = lambda buf: stdout.output.append(buf) s = ''.join(stdout.output) header.pprint() for i in range(1, 9): if i == 3: assert '(3)' not in s else: assert ('(%d)' % i) def test_pprint_rows_cols(): assert '{0:row1}'.format(pi_info('900092').headers['J8']) == '1o' assert '{0:row2}'.format(pi_info('900092').headers['J8']) == 'oo' assert '{0:col1}'.format(pi_info('0002').headers['P1']) == '1oooooooooooo' assert '{0:col2}'.format(pi_info('0002').headers['P1']) == 'ooooooooooooo' with pytest.raises(ValueError): '{0:row16}'.format(pi_info('0002').headers['P1']) with pytest.raises(ValueError): '{0:col3}'.format(pi_info('0002').headers['P1']) gpiozero-1.4.1/tests/test_boards.py0000644000175000017500000011557013243115414017267 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import sys import pytest from time import sleep from gpiozero import * from gpiozero.pins.mock import MockPWMPin, MockPin def setup_function(function): # dirty, but it does the job Device.pin_factory.pin_class = MockPWMPin if function.__name__ in ( 'test_robot', 'test_phaseenable_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot', 'test_pololudrv8835_robot', 'test_led_borg', 'test_led_board_pwm_value', 'test_led_board_pwm_bad_value', 'test_snow_pi_initial_value_pwm', 'test_led_board_pwm_initial_value', 'test_led_board_pwm_bad_initial_value', 'test_led_board_fade_background', 'test_led_bar_graph_pwm_value', 'test_led_bar_graph_pwm_initial_value', 'test_statusboard_kwargs', 'test_statuszero_kwargs', ) else MockPin def teardown_function(function): Device.pin_factory.reset() def teardown_module(module): # make sure we reset the default Device.pin_factory.pwm = False def test_composite_output_on_off(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with CompositeOutputDevice(OutputDevice(2), OutputDevice(3), foo=OutputDevice(4)) as device: device.on() assert all((pin1.state, pin2.state, pin3.state)) device.off() assert not any((pin1.state, pin2.state, pin3.state)) def test_composite_output_toggle(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with CompositeOutputDevice(OutputDevice(2), OutputDevice(3), foo=OutputDevice(4)) as device: device.toggle() assert all((pin1.state, pin2.state, pin3.state)) device[0].off() device.toggle() assert pin1.state assert not pin2.state assert not pin3.state def test_composite_output_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with CompositeOutputDevice(OutputDevice(2), OutputDevice(3), foo=OutputDevice(4)) as device: assert device.value == (0, 0, 0) device.toggle() assert device.value == (1, 1, 1) device.value = (1, 0, 1) assert device[0].is_active assert not device[1].is_active assert device[2].is_active def test_led_board_on_off(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBoard(2, 3, foo=4) as board: assert isinstance(board[0], LED) assert isinstance(board[1], LED) assert isinstance(board[2], LED) assert board.active_high assert board[0].active_high assert board[1].active_high assert board[2].active_high board.on() assert all((pin1.state, pin2.state, pin3.state)) board.off() assert not any((pin1.state, pin2.state, pin3.state)) board[0].on() assert board.value == (1, 0, 0) assert pin1.state assert not pin2.state assert not pin3.state board.toggle() assert board.value == (0, 1, 1) assert not pin1.state assert pin2.state assert pin3.state board.toggle(0,1) assert board.value == (1, 0, 1) assert pin1.state assert not pin2.state assert pin3.state board.off(2) assert board.value == (1, 0, 0) assert pin1.state assert not pin2.state assert not pin3.state board.on(1) assert board.value == (1, 1, 0) assert pin1.state assert pin2.state assert not pin3.state board.off(0,1) assert board.value == (0, 0, 0) assert not pin1.state assert not pin2.state assert not pin3.state board.on(1,2) assert board.value == (0, 1, 1) assert not pin1.state assert pin2.state assert pin3.state board.toggle(0) assert board.value == (1, 1, 1) assert pin1.state assert pin2.state assert pin3.state def test_led_board_active_low(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBoard(2, 3, foo=4, active_high=False) as board: assert not board.active_high assert not board[0].active_high assert not board[1].active_high assert not board[2].active_high board.on() assert not any ((pin1.state, pin2.state, pin3.state)) board.off() assert all((pin1.state, pin2.state, pin3.state)) board[0].on() assert board.value == (1, 0, 0) assert not pin1.state assert pin2.state assert pin3.state board.toggle() assert board.value == (0, 1, 1) assert pin1.state assert not pin2.state assert not pin3.state def test_led_board_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBoard(2, 3, foo=4) as board: assert board.value == (0, 0, 0) board.value = (0, 1, 0) assert board.value == (0, 1, 0) board.value = (1, 0, 1) assert board.value == (1, 0, 1) def test_led_board_pwm_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBoard(2, 3, foo=4, pwm=True) as board: assert board.value == (0, 0, 0) board.value = (0, 1, 0) assert board.value == (0, 1, 0) board.value = (0.5, 0, 0.75) assert board.value == (0.5, 0, 0.75) def test_led_board_pwm_bad_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBoard(2, 3, foo=4, pwm=True) as board: with pytest.raises(ValueError): board.value = (-1, 0, 0) with pytest.raises(ValueError): board.value = (0, 2, 0) def test_led_board_initial_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBoard(2, 3, foo=4, initial_value=0) as board: assert board.value == (0, 0, 0) with LEDBoard(2, 3, foo=4, initial_value=1) as board: assert board.value == (1, 1, 1) def test_led_board_pwm_initial_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBoard(2, 3, foo=4, pwm=True, initial_value=0) as board: assert board.value == (0, 0, 0) with LEDBoard(2, 3, foo=4, pwm=True, initial_value=1) as board: assert board.value == (1, 1, 1) with LEDBoard(2, 3, foo=4, pwm=True, initial_value=0.5) as board: assert board.value == (0.5, 0.5, 0.5) def test_led_board_pwm_bad_initial_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with pytest.raises(ValueError): LEDBoard(2, 3, foo=4, pwm=True, initial_value=-1) with pytest.raises(ValueError): LEDBoard(2, 3, foo=4, pwm=True, initial_value=2) def test_led_board_nested(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBoard(2, LEDBoard(3, 4)) as board: assert list(led.pin for led in board.leds) == [pin1, pin2, pin3] assert board.value == (0, (0, 0)) board.value = (1, (0, 1)) assert pin1.state assert not pin2.state assert pin3.state def test_led_board_bad_blink(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBoard(2, LEDBoard(3, 4)) as board: with pytest.raises(ValueError): board.blink(fade_in_time=1, fade_out_time=1) with pytest.raises(ValueError): board.blink(fade_out_time=1) with pytest.raises(ValueError): board.pulse() @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_led_board_blink_background(): pin1 = Device.pin_factory.pin(4) pin2 = Device.pin_factory.pin(5) pin3 = Device.pin_factory.pin(6) with LEDBoard(4, LEDBoard(5, 6)) as board: # Instantiation takes a long enough time that it throws off our timing # here! pin1.clear_states() pin2.clear_states() pin3.clear_states() board.blink(0.1, 0.1, n=2) board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test test = [ (0.0, False), (0.0, True), (0.1, False), (0.1, True), (0.1, False) ] pin1.assert_states_and_times(test) pin2.assert_states_and_times(test) pin3.assert_states_and_times(test) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_led_board_blink_foreground(): pin1 = Device.pin_factory.pin(4) pin2 = Device.pin_factory.pin(5) pin3 = Device.pin_factory.pin(6) with LEDBoard(4, LEDBoard(5, 6)) as board: pin1.clear_states() pin2.clear_states() pin3.clear_states() board.blink(0.1, 0.1, n=2, background=False) test = [ (0.0, False), (0.0, True), (0.1, False), (0.1, True), (0.1, False) ] pin1.assert_states_and_times(test) pin2.assert_states_and_times(test) pin3.assert_states_and_times(test) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_led_board_blink_control(): pin1 = Device.pin_factory.pin(4) pin2 = Device.pin_factory.pin(5) pin3 = Device.pin_factory.pin(6) with LEDBoard(4, LEDBoard(5, 6)) as board: pin1.clear_states() pin2.clear_states() pin3.clear_states() board.blink(0.1, 0.1, n=2) # make sure the blink thread's started while not board._blink_leds: sleep(0.00001) # pragma: no cover board[1][0].off() # immediately take over the second LED board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test test = [ (0.0, False), (0.0, True), (0.1, False), (0.1, True), (0.1, False) ] pin1.assert_states_and_times(test) pin3.assert_states_and_times(test) print(pin2.states) pin2.assert_states_and_times([(0.0, False), (0.0, True), (0.0, False)]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_led_board_blink_take_over(): pin1 = Device.pin_factory.pin(4) pin2 = Device.pin_factory.pin(5) pin3 = Device.pin_factory.pin(6) with LEDBoard(4, LEDBoard(5, 6)) as board: pin1.clear_states() pin2.clear_states() pin3.clear_states() board[1].blink(0.1, 0.1, n=2) board.blink(0.1, 0.1, n=2) # immediately take over blinking board[1]._blink_thread.join() board._blink_thread.join() test = [ (0.0, False), (0.0, True), (0.1, False), (0.1, True), (0.1, False) ] pin1.assert_states_and_times(test) pin2.assert_states_and_times(test) pin3.assert_states_and_times(test) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_led_board_blink_control_all(): pin1 = Device.pin_factory.pin(4) pin2 = Device.pin_factory.pin(5) pin3 = Device.pin_factory.pin(6) with LEDBoard(4, LEDBoard(5, 6)) as board: pin1.clear_states() pin2.clear_states() pin3.clear_states() board.blink(0.1, 0.1, n=2) # make sure the blink thread's started while not board._blink_leds: sleep(0.00001) # pragma: no cover board[0].off() # immediately take over all LEDs board[1][0].off() board[1][1].off() board._blink_thread.join() # blink should terminate here anyway test = [ (0.0, False), (0.0, True), (0.0, False), ] pin1.assert_states_and_times(test) pin2.assert_states_and_times(test) pin3.assert_states_and_times(test) def test_led_board_blink_interrupt_on(): pin1 = Device.pin_factory.pin(4) pin2 = Device.pin_factory.pin(5) pin3 = Device.pin_factory.pin(6) with LEDBoard(4, LEDBoard(5, 6)) as board: board.blink(1, 0.1) sleep(0.2) board.off() # should interrupt while on pin1.assert_states([False, True, False]) pin2.assert_states([False, True, False]) pin3.assert_states([False, True, False]) def test_led_board_blink_interrupt_off(): pin1 = Device.pin_factory.pin(4) pin2 = Device.pin_factory.pin(5) pin3 = Device.pin_factory.pin(6) with LEDBoard(4, LEDBoard(5, 6)) as board: pin1.clear_states() pin2.clear_states() pin3.clear_states() board.blink(0.1, 1) sleep(0.2) board.off() # should interrupt while off pin1.assert_states([False, True, False]) pin2.assert_states([False, True, False]) pin3.assert_states([False, True, False]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_led_board_fade_background(): pin1 = Device.pin_factory.pin(4) pin2 = Device.pin_factory.pin(5) pin3 = Device.pin_factory.pin(6) with LEDBoard(4, LEDBoard(5, 6, pwm=True), pwm=True) as board: pin1.clear_states() pin2.clear_states() pin3.clear_states() board.blink(0, 0, 0.2, 0.2, n=2) board._blink_thread.join() test = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ] pin1.assert_states_and_times(test) pin2.assert_states_and_times(test) pin3.assert_states_and_times(test) def test_led_bar_graph_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBarGraph(2, 3, 4) as graph: assert isinstance(graph[0], LED) assert isinstance(graph[1], LED) assert isinstance(graph[2], LED) assert graph.active_high assert graph[0].active_high assert graph[1].active_high assert graph[2].active_high graph.value = 0 assert graph.value == 0 assert graph.lit_count == 0 assert not any((pin1.state, pin2.state, pin3.state)) graph.value = 1 assert graph.value == 1 assert graph.lit_count == 3 assert all((pin1.state, pin2.state, pin3.state)) graph.value = 1/3 assert graph.value == 1/3 assert graph.lit_count == 1 assert pin1.state and not (pin2.state or pin3.state) graph.value = -1/3 assert graph.value == -1/3 assert graph.lit_count == -1 assert pin3.state and not (pin1.state or pin2.state) pin1.state = True pin2.state = True assert graph.value == 1 assert graph.lit_count == 3 pin3.state = False assert graph.value == 2/3 assert graph.lit_count == 2 pin3.state = True pin1.state = False assert graph.value == -2/3 graph.lit_count = 2 assert graph.value == 2/3 graph.lit_count = -1 assert graph.value == -1/3 graph.lit_count = -3 assert graph.value == 1 def test_led_bar_graph_active_low(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBarGraph(2, 3, 4, active_high=False) as graph: assert not graph.active_high assert not graph[0].active_high assert not graph[1].active_high assert not graph[2].active_high graph.value = 0 assert graph.value == 0 assert all((pin1.state, pin2.state, pin3.state)) graph.value = 1 assert graph.value == 1 assert not any((pin1.state, pin2.state, pin3.state)) graph.value = 1/3 assert graph.value == 1/3 assert not pin1.state and pin2.state and pin3.state graph.value = -1/3 assert graph.value == -1/3 assert not pin3.state and pin1.state and pin2.state def test_led_bar_graph_pwm_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBarGraph(2, 3, 4, pwm=True) as graph: assert isinstance(graph[0], PWMLED) assert isinstance(graph[1], PWMLED) assert isinstance(graph[2], PWMLED) graph.value = 0 assert graph.value == 0 assert graph.lit_count == 0 assert not any((pin1.state, pin2.state, pin3.state)) graph.value = 1 assert graph.value == 1 assert graph.lit_count == 3 assert all((pin1.state, pin2.state, pin3.state)) graph.value = 1/3 assert graph.value == 1/3 assert graph.lit_count == 1 assert pin1.state and not (pin2.state or pin3.state) graph.value = -1/3 assert graph.value == -1/3 assert graph.lit_count == -1 assert pin3.state and not (pin1.state or pin2.state) graph.value = 1/2 assert graph.value == 1/2 assert graph.lit_count == 1.5 assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0) pin1.state = 0 pin3.state = 1 assert graph.value == -1/2 assert graph.lit_count == -1.5 graph.lit_count = 1.5 assert graph.value == 0.5 def test_led_bar_graph_bad_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBarGraph(2, 3, 4) as graph: with pytest.raises(ValueError): graph.value = -2 with pytest.raises(ValueError): graph.value = 2 with pytest.raises(ValueError): graph.lit_count = -4 with pytest.raises(ValueError): graph.lit_count = 4 def test_led_bar_graph_bad_init(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with pytest.raises(TypeError): LEDBarGraph(2, 3, foo=4) with pytest.raises(ValueError): LEDBarGraph(2, 3, 4, initial_value=-2) with pytest.raises(ValueError): LEDBarGraph(2, 3, 4, initial_value=2) def test_led_bar_graph_initial_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBarGraph(2, 3, 4, initial_value=1/3) as graph: assert graph.value == 1/3 assert pin1.state and not (pin2.state or pin3.state) with LEDBarGraph(2, 3, 4, initial_value=-1/3) as graph: assert graph.value == -1/3 assert pin3.state and not (pin1.state or pin2.state) def test_led_bar_graph_pwm_initial_value(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(3) pin3 = Device.pin_factory.pin(4) with LEDBarGraph(2, 3, 4, pwm=True, initial_value=0.5) as graph: assert graph.value == 0.5 assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0) with LEDBarGraph(2, 3, 4, pwm=True, initial_value=-0.5) as graph: assert graph.value == -0.5 assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1) def test_led_borg(): pins = [Device.pin_factory.pin(n) for n in (17, 27, 22)] with LedBorg() as board: assert [device.pin for device in board._leds] == pins def test_pi_liter(): pins = [Device.pin_factory.pin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] with PiLiter() as board: assert [device.pin for device in board] == pins def test_pi_liter_graph(): pins = [Device.pin_factory.pin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] with PiLiterBarGraph() as board: board.value = 0.5 assert [pin.state for pin in pins] == [1, 1, 1, 1, 0, 0, 0, 0] pins[4].state = 1 assert board.value == 5/8 def test_traffic_lights(): red_pin = Device.pin_factory.pin(2) amber_pin = Device.pin_factory.pin(3) green_pin = Device.pin_factory.pin(4) with TrafficLights(2, 3, 4) as board: board.red.on() assert board.red.value assert not board.amber.value assert not board.yellow.value assert not board.green.value assert red_pin.state assert not amber_pin.state assert not green_pin.state board.amber.on() assert amber_pin.state board.yellow.off() assert not amber_pin.state with TrafficLights(red=2, yellow=3, green=4) as board: board.yellow.on() assert not board.red.value assert board.amber.value assert board.yellow.value assert not board.green.value assert not red_pin.state assert amber_pin.state assert not green_pin.state board.amber.off() assert not amber_pin.state def test_traffic_lights_bad_init(): with pytest.raises(ValueError): TrafficLights() red_pin = Device.pin_factory.pin(2) amber_pin = Device.pin_factory.pin(3) green_pin = Device.pin_factory.pin(4) yellow_pin = Device.pin_factory.pin(5) with pytest.raises(ValueError): TrafficLights(red=2, amber=3, yellow=5, green=4) def test_pi_traffic(): pins = [Device.pin_factory.pin(n) for n in (9, 10, 11)] with PiTraffic() as board: assert [device.pin for device in board] == pins def test_pi_stop(): with pytest.raises(ValueError): PiStop() with pytest.raises(ValueError): PiStop('E') pins_a = [Device.pin_factory.pin(n) for n in (7, 8, 25)] with PiStop('A') as board: assert [device.pin for device in board] == pins_a pins_aplus = [Device.pin_factory.pin(n) for n in (21, 20, 16)] with PiStop('A+') as board: assert [device.pin for device in board] == pins_aplus pins_b = [Device.pin_factory.pin(n) for n in (10, 9, 11)] with PiStop('B') as board: assert [device.pin for device in board] == pins_b pins_bplus = [Device.pin_factory.pin(n) for n in (13, 19, 26)] with PiStop('B+') as board: assert [device.pin for device in board] == pins_bplus pins_c = [Device.pin_factory.pin(n) for n in (18, 15, 14)] with PiStop('C') as board: assert [device.pin for device in board] == pins_c pins_d = [Device.pin_factory.pin(n) for n in (2, 3, 4)] with PiStop('D') as board: assert [device.pin for device in board] == pins_d def test_snow_pi(): pins = [Device.pin_factory.pin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] with SnowPi() as board: assert [device.pin for device in board.leds] == pins def test_snow_pi_initial_value(): with SnowPi() as board: assert all(device.pin.state == False for device in board.leds) with SnowPi(initial_value=False) as board: assert all(device.pin.state == False for device in board.leds) with SnowPi(initial_value=True) as board: assert all(device.pin.state == True for device in board.leds) with SnowPi(initial_value=0.5) as board: assert all(device.pin.state == True for device in board.leds) def test_snow_pi_initial_value_pwm(): pins = [Device.pin_factory.pin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] with SnowPi(pwm=True, initial_value=0.5) as board: assert [device.pin for device in board.leds] == pins assert all(device.pin.state == 0.5 for device in board.leds) def test_traffic_lights_buzzer(): red_pin = Device.pin_factory.pin(2) amber_pin = Device.pin_factory.pin(3) green_pin = Device.pin_factory.pin(4) buzzer_pin = Device.pin_factory.pin(5) button_pin = Device.pin_factory.pin(6) with TrafficLightsBuzzer( TrafficLights(2, 3, 4), Buzzer(5), Button(6)) as board: board.lights.red.on() board.buzzer.on() assert red_pin.state assert not amber_pin.state assert not green_pin.state assert buzzer_pin.state button_pin.drive_low() assert board.button.is_active def test_fish_dish(): pins = [Device.pin_factory.pin(n) for n in (9, 22, 4, 8, 7)] with FishDish() as board: assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins def test_traffic_hat(): pins = [Device.pin_factory.pin(n) for n in (24, 23, 22, 5, 25)] with TrafficHat() as board: assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins def test_robot(): pins = [Device.pin_factory.pin(n) for n in (2, 3, 4, 5)] def check_pins_and_value(robot, expected_value): # Ensure both forward and back pins aren't both driven simultaneously assert pins[0].state == 0 or pins[1].state == 0 assert pins[2].state == 0 or pins[3].state == 0 assert robot.value == (pins[0].state - pins[1].state, pins[2].state - pins[3].state) == expected_value with Robot((2, 3), (4, 5)) as robot: assert ( [device.pin for device in robot.left_motor] + [device.pin for device in robot.right_motor]) == pins check_pins_and_value(robot, (0, 0)) robot.forward() check_pins_and_value(robot, (1, 1)) robot.backward() check_pins_and_value(robot, (-1, -1)) robot.forward(0) check_pins_and_value(robot, (0, 0)) robot.forward(0.5) check_pins_and_value(robot, (0.5, 0.5)) robot.forward(1) check_pins_and_value(robot, (1, 1)) robot.forward(curve_right=0) check_pins_and_value(robot, (1, 1)) robot.forward(curve_left=0) check_pins_and_value(robot, (1, 1)) robot.forward(curve_left=0, curve_right=0) check_pins_and_value(robot, (1, 1)) robot.forward(curve_right=1) check_pins_and_value(robot, (1, 0)) robot.forward(curve_left=1) check_pins_and_value(robot, (0, 1)) robot.forward(0.5, curve_right=1) check_pins_and_value(robot, (0.5, 0)) robot.forward(0.5, curve_left=1) check_pins_and_value(robot, (0, 0.5)) robot.forward(curve_right=0.5) check_pins_and_value(robot, (1, 0.5)) robot.forward(curve_left=0.5) check_pins_and_value(robot, (0.5, 1)) robot.forward(0.5, curve_right=0.5) check_pins_and_value(robot, (0.5, 0.25)) robot.forward(0.5, curve_left=0.5) check_pins_and_value(robot, (0.25, 0.5)) with pytest.raises(ValueError): robot.forward(-1) with pytest.raises(ValueError): robot.forward(2) with pytest.raises(ValueError): robot.forward(curve_left=-1) with pytest.raises(ValueError): robot.forward(curve_left=2) with pytest.raises(ValueError): robot.forward(curve_right=-1) with pytest.raises(ValueError): robot.forward(curve_right=2) with pytest.raises(ValueError): robot.forward(curve_left=1, curve_right=1) robot.backward() check_pins_and_value(robot, (-1, -1)) robot.reverse() check_pins_and_value(robot, (1, 1)) robot.backward(0) check_pins_and_value(robot, (0, 0)) robot.backward(0.5) check_pins_and_value(robot, (-0.5, -0.5)) robot.backward(1) check_pins_and_value(robot, (-1, -1)) robot.backward(curve_right=0) check_pins_and_value(robot, (-1, -1)) robot.backward(curve_left=0) check_pins_and_value(robot, (-1, -1)) robot.backward(curve_left=0, curve_right=0) check_pins_and_value(robot, (-1, -1)) robot.backward(curve_right=1) check_pins_and_value(robot, (-1, 0)) robot.backward(curve_left=1) check_pins_and_value(robot, (0, -1)) robot.backward(0.5, curve_right=1) check_pins_and_value(robot, (-0.5, 0)) robot.backward(0.5, curve_left=1) check_pins_and_value(robot, (0, -0.5)) robot.backward(curve_right=0.5) check_pins_and_value(robot, (-1, -0.5)) robot.backward(curve_left=0.5) check_pins_and_value(robot, (-0.5, -1)) robot.backward(0.5, curve_right=0.5) check_pins_and_value(robot, (-0.5, -0.25)) robot.backward(0.5, curve_left=0.5) check_pins_and_value(robot, (-0.25, -0.5)) with pytest.raises(ValueError): robot.backward(-1) with pytest.raises(ValueError): robot.backward(2) with pytest.raises(ValueError): robot.backward(curve_left=-1) with pytest.raises(ValueError): robot.backward(curve_left=2) with pytest.raises(ValueError): robot.backward(curve_right=-1) with pytest.raises(ValueError): robot.backward(curve_right=2) with pytest.raises(ValueError): robot.backward(curve_left=1, curve_right=1) with pytest.raises(TypeError): robot.forward(curveleft=1) with pytest.raises(TypeError): robot.forward(curveright=1) robot.left() check_pins_and_value(robot, (-1, 1)) robot.left(0) check_pins_and_value(robot, (0, 0)) robot.left(0.5) check_pins_and_value(robot, (-0.5, 0.5)) robot.left(1) check_pins_and_value(robot, (-1, 1)) with pytest.raises(ValueError): robot.left(-1) with pytest.raises(ValueError): robot.left(2) robot.right() check_pins_and_value(robot, (1, -1)) robot.right(0) check_pins_and_value(robot, (0, 0)) robot.right(0.5) check_pins_and_value(robot, (0.5, -0.5)) robot.right(1) check_pins_and_value(robot, (1, -1)) with pytest.raises(ValueError): robot.right(-1) with pytest.raises(ValueError): robot.right(2) robot.reverse() check_pins_and_value(robot, (-1, 1)) robot.stop() check_pins_and_value(robot, (0, 0)) robot.stop() check_pins_and_value(robot, (0, 0)) robot.value = (-1, -1) check_pins_and_value(robot, (-1, -1)) robot.value = (0.5, 1) check_pins_and_value(robot, (0.5, 1)) robot.value = (0, -0.5) check_pins_and_value(robot, (0, -0.5)) def test_phaseenable_robot(): pins = [Device.pin_factory.pin(n) for n in (5, 12, 6, 13)] with PhaseEnableRobot((5, 12), (6, 13)) as robot: assert ( [device.pin for device in robot.left_motor] + [device.pin for device in robot.right_motor]) == pins assert robot.value == (0, 0) robot.forward() assert [pin.state for pin in pins] == [0, 1, 0, 1] assert robot.value == (1, 1) robot.backward() assert [pin.state for pin in pins] == [1, 1, 1, 1] assert robot.value == (-1, -1) robot.forward(0.5) assert [pin.state for pin in pins] == [0, 0.5, 0, 0.5] assert robot.value == (0.5, 0.5) robot.left() assert [pin.state for pin in pins] == [1, 1, 0, 1] assert robot.value == (-1, 1) robot.right() assert [pin.state for pin in pins] == [0, 1, 1, 1] assert robot.value == (1, -1) robot.reverse() assert [pin.state for pin in pins] == [1, 1, 0, 1] assert robot.value == (-1, 1) robot.stop() assert [pin.state for pin in pins][1::2] == [0, 0] assert robot.value == (0, 0) robot.value = (-1, -1) assert robot.value == (-1, -1) robot.value = (0.5, 1) assert robot.value == (0.5, 1) robot.value = (0, -0.5) assert robot.value == (0, -0.5) def test_ryanteck_robot(): pins = [Device.pin_factory.pin(n) for n in (17, 18, 22, 23)] with RyanteckRobot() as board: assert [device.pin for motor in board for device in motor] == pins def test_camjam_kit_robot(): pins = [Device.pin_factory.pin(n) for n in (9, 10, 7, 8)] with CamJamKitRobot() as board: assert [device.pin for motor in board for device in motor] == pins def test_pololudrv8835_robot(): pins = [Device.pin_factory.pin(n) for n in (5, 12, 6, 13)] with PololuDRV8835Robot() as board: assert [device.pin for motor in board for device in motor] == pins def test_energenie_bad_init(): with pytest.raises(ValueError): Energenie() with pytest.raises(ValueError): Energenie(0) with pytest.raises(ValueError): Energenie(5) def test_energenie(): pins = [Device.pin_factory.pin(n) for n in (17, 22, 23, 27, 24, 25)] with Energenie(1, initial_value=True) as device1, \ Energenie(2, initial_value=False) as device2: assert repr(device1) == '' assert repr(device2) == '' assert device1.value assert not device2.value [pin.clear_states() for pin in pins] device1.on() assert device1.value pins[0].assert_states_and_times([(0.0, False), (0.0, True)]) pins[1].assert_states_and_times([(0.0, True), (0.0, True)]) pins[2].assert_states_and_times([(0.0, True), (0.0, True)]) pins[3].assert_states_and_times([(0.0, False), (0.0, True)]) pins[4].assert_states_and_times([(0.0, False)]) pins[5].assert_states_and_times([(0.0, False), (0.1, True), (0.25, False)]) [pin.clear_states() for pin in pins] device2.on() assert device2.value pins[0].assert_states_and_times([(0.0, True), (0.0, False)]) pins[1].assert_states_and_times([(0.0, True), (0.0, True)]) pins[2].assert_states_and_times([(0.0, True), (0.0, True)]) pins[3].assert_states_and_times([(0.0, True), (0.0, True)]) pins[4].assert_states_and_times([(0.0, False)]) pins[5].assert_states_and_times([(0.0, False), (0.1, True), (0.25, False)]) device1.close() assert repr(device1) == '' def test_statuszero_init(): with StatusZero() as sz: assert sz.namedtuple._fields == ('one', 'two', 'three') with StatusZero('a') as sz: assert sz.namedtuple._fields == ('a',) with StatusZero('a', 'b') as sz: assert sz.namedtuple._fields == ('a', 'b') with StatusZero('a', 'b', 'c') as sz: assert sz.namedtuple._fields == ('a', 'b', 'c') with pytest.raises(ValueError): StatusZero('a', 'b', 'c', 'd') with pytest.raises(ValueError): StatusZero('0') with pytest.raises(ValueError): StatusZero('foo', 'hello world') with pytest.raises(ValueError): StatusZero('foo', 'foo') def test_statuszero(): with StatusZero() as sz: assert isinstance(sz.one, LEDBoard) assert isinstance(sz.two, LEDBoard) assert isinstance(sz.three, LEDBoard) assert isinstance(sz.one.red, LED) assert isinstance(sz.one.green, LED) assert sz.value == ((False, False), (False, False), (False, False)) sz.on() assert sz.value == ((True, True), (True, True), (True, True)) sz.one.green.off() assert sz.one.value == (True, False) def test_statuszero_kwargs(): with StatusZero(pwm=True, initial_value=True) as sz: assert isinstance(sz.one, LEDBoard) assert isinstance(sz.two, LEDBoard) assert isinstance(sz.three, LEDBoard) assert isinstance(sz.one.red, PWMLED) assert isinstance(sz.one.green, PWMLED) assert sz.value == ((1, 1), (1, 1), (1, 1)) sz.off() assert sz.value == ((0, 0), (0, 0), (0, 0)) def test_statuszero_named(): with StatusZero('a') as sz: assert isinstance(sz.a, LEDBoard) assert isinstance(sz.a.red, LED) with pytest.raises(AttributeError): sz.one def test_statusboard_init(): with StatusBoard() as sb: assert sb.namedtuple._fields == ('one', 'two', 'three', 'four', 'five') with StatusBoard('a') as sb: assert sb.namedtuple._fields == ('a',) with StatusBoard('a', 'b') as sb: assert sb.namedtuple._fields == ('a', 'b',) with StatusBoard('a', 'b', 'c', 'd', 'e') as sb: assert sb.namedtuple._fields == ('a', 'b', 'c', 'd', 'e') with pytest.raises(ValueError): StatusBoard('a', 'b', 'c', 'd', 'e', 'f') with pytest.raises(ValueError): StatusBoard('0') with pytest.raises(ValueError): StatusBoard('foo', 'hello world') with pytest.raises(ValueError): StatusBoard('foo', 'foo') def test_statusboard(): with StatusBoard() as sb: assert isinstance(sb.one, CompositeOutputDevice) assert isinstance(sb.two, CompositeOutputDevice) assert isinstance(sb.five, CompositeOutputDevice) assert isinstance(sb.one.button, Button) assert isinstance(sb.one.lights, LEDBoard) assert isinstance(sb.one.lights.red, LED) assert isinstance(sb.one.lights.green, LED) assert sb.value == ((False, (False, False)), (False, (False, False)), (False, (False, False)), (False, (False, False)), (False, (False, False))) sb.on() assert sb.value == ((False, (True, True)), (False, (True, True)), (False, (True, True)), (False, (True, True)), (False, (True, True))) sb.one.lights.green.off() assert sb.one.value == (False, (True, False)) def test_statusboard_kwargs(): with StatusBoard(pwm=True, initial_value=True) as sb: assert isinstance(sb.one, CompositeOutputDevice) assert isinstance(sb.two, CompositeOutputDevice) assert isinstance(sb.five, CompositeOutputDevice) assert isinstance(sb.one.button, Button) assert isinstance(sb.one.lights, LEDBoard) assert isinstance(sb.one.lights.red, PWMLED) assert isinstance(sb.one.lights.green, PWMLED) assert sb.value == ((False, (1, 1)), (False, (1, 1)), (False, (1, 1)), (False, (1, 1)), (False, (1, 1))) sb.off() assert sb.value == ((False, (0, 0)), (False, (0, 0)), (False, (0, 0)), (False, (0, 0)), (False, (0, 0))) def test_statusboard_named(): with StatusBoard('a') as sb: assert isinstance(sb.a, CompositeOutputDevice) assert isinstance(sb.a.button, Button) assert isinstance(sb.a.lights, LEDBoard) assert isinstance(sb.a.lights.red, LED) with pytest.raises(AttributeError): sb.one gpiozero-1.4.1/tests/conftest.py0000644000175000017500000000026213165454031016576 0ustar davedave00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') import os os.environ['GPIOZERO_PIN_FACTORY'] = 'mock' gpiozero-1.4.1/tests/test_spi.py0000644000175000017500000002007313243115414016601 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) nstr = str str = type('') import sys import pytest from array import array from mock import patch from collections import namedtuple from gpiozero.pins.native import NativeFactory from gpiozero.pins.local import ( LocalPiHardwareSPI, LocalPiSoftwareSPI, LocalPiHardwareSPIShared, LocalPiSoftwareSPIShared, ) from gpiozero.pins.mock import MockSPIDevice from gpiozero import * def teardown_function(function): Device.pin_factory.reset() def test_spi_hardware_params(): with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open: mmap_mmap.return_value = array(nstr('B'), (0,) * 4096) io_open.return_value.__enter__.return_value = ['Revision: a21042'] factory = NativeFactory() with patch('gpiozero.pins.local.SpiDev'): with factory.spi() as device: assert isinstance(device, LocalPiHardwareSPI) device.close() assert device.closed with factory.spi(port=0, device=0) as device: assert isinstance(device, LocalPiHardwareSPI) with factory.spi(port=0, device=1) as device: assert isinstance(device, LocalPiHardwareSPI) with factory.spi(clock_pin=11) as device: assert isinstance(device, LocalPiHardwareSPI) with factory.spi(clock_pin=11, mosi_pin=10, select_pin=8) as device: assert isinstance(device, LocalPiHardwareSPI) with factory.spi(clock_pin=11, mosi_pin=10, select_pin=7) as device: assert isinstance(device, LocalPiHardwareSPI) with factory.spi(shared=True) as device: assert isinstance(device, LocalPiHardwareSPIShared) with pytest.raises(ValueError): factory.spi(port=1) with pytest.raises(ValueError): factory.spi(device=2) with pytest.raises(ValueError): factory.spi(port=0, clock_pin=12) with pytest.raises(ValueError): factory.spi(foo='bar') def test_spi_software_params(): with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open: mmap_mmap.return_value = array(nstr('B'), (0,) * 4096) io_open.return_value.__enter__.return_value = ['Revision: a21042'] factory = NativeFactory() with patch('gpiozero.pins.local.SpiDev'): with factory.spi(select_pin=6) as device: assert isinstance(device, LocalPiSoftwareSPI) device.close() assert device.closed with factory.spi(clock_pin=11, mosi_pin=9, miso_pin=10) as device: assert isinstance(device, LocalPiSoftwareSPI) device._bus.close() assert device._bus.closed device.close() assert device.closed with factory.spi(select_pin=6, shared=True) as device: assert isinstance(device, LocalPiSoftwareSPIShared) with patch('gpiozero.pins.local.SpiDev', None): # Clear out the old factory's caches (this is only necessary because # we're being naughty switching out patches) factory.pins.clear() factory._reservations.clear() # Ensure software fallback works when SpiDev isn't present with factory.spi() as device: assert isinstance(device, LocalPiSoftwareSPI) def test_spi_hardware_conflict(): with patch('gpiozero.pins.local.SpiDev') as spidev: with LED(11) as led: with pytest.raises(GPIOPinInUse): Device.pin_factory.spi(port=0, device=0) with patch('gpiozero.pins.local.SpiDev') as spidev: with Device.pin_factory.spi(port=0, device=0) as spi: with pytest.raises(GPIOPinInUse): LED(11) def test_spi_hardware_read(): with patch('gpiozero.pins.local.SpiDev') as spidev: spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] with Device.pin_factory.spi() as device: assert device.read(3) == [0, 1, 2] assert device.read(6) == list(range(6)) def test_spi_hardware_write(): with patch('gpiozero.pins.local.SpiDev') as spidev: spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] with Device.pin_factory.spi() as device: assert device.write([0, 1, 2]) == 3 assert spidev.return_value.xfer2.called_with([0, 1, 2]) assert device.write(list(range(6))) == 6 assert spidev.return_value.xfer2.called_with(list(range(6))) def test_spi_hardware_modes(): with patch('gpiozero.pins.local.SpiDev') as spidev: spidev.return_value.mode = 0 spidev.return_value.lsbfirst = False spidev.return_value.cshigh = True spidev.return_value.bits_per_word = 8 with Device.pin_factory.spi() as device: assert device.clock_mode == 0 assert not device.clock_polarity assert not device.clock_phase device.clock_polarity = False assert device.clock_mode == 0 device.clock_polarity = True assert device.clock_mode == 2 device.clock_phase = True assert device.clock_mode == 3 assert not device.lsb_first assert device.select_high assert device.bits_per_word == 8 device.select_high = False device.lsb_first = True device.bits_per_word = 12 assert not spidev.return_value.cshigh assert spidev.return_value.lsbfirst assert spidev.return_value.bits_per_word == 12 def test_spi_software_read(): class SPISlave(MockSPIDevice): def on_start(self): super(SPISlave, self).on_start() for i in range(10): self.tx_word(i) with patch('gpiozero.pins.local.SpiDev', None), \ SPISlave(11, 10, 9, 8) as slave, \ Device.pin_factory.spi() as master: assert master.read(3) == [0, 1, 2] assert master.read(6) == [0, 1, 2, 3, 4, 5] slave.clock_phase = True master.clock_phase = True assert master.read(3) == [0, 1, 2] assert master.read(6) == [0, 1, 2, 3, 4, 5] def test_spi_software_write(): with patch('gpiozero.pins.local.SpiDev', None), \ MockSPIDevice(11, 10, 9, 8) as test_device, \ Device.pin_factory.spi() as master: master.write([0]) assert test_device.rx_word() == 0 master.write([2, 0]) assert test_device.rx_word() == 512 master.write([0, 1, 1]) assert test_device.rx_word() == 257 def test_spi_software_clock_mode(): with patch('gpiozero.pins.local.SpiDev', None), \ Device.pin_factory.spi() as master: assert master.clock_mode == 0 assert not master.clock_polarity assert not master.clock_phase master.clock_polarity = False assert master.clock_mode == 0 master.clock_polarity = True assert master.clock_mode == 2 master.clock_phase = True assert master.clock_mode == 3 master.clock_mode = 0 assert not master.clock_polarity assert not master.clock_phase with pytest.raises(ValueError): master.clock_mode = 5 def test_spi_software_attr(): with patch('gpiozero.pins.local.SpiDev', None), \ Device.pin_factory.spi() as master: assert not master.lsb_first assert not master.select_high assert master.bits_per_word == 8 master.bits_per_word = 12 assert master.bits_per_word == 12 master.lsb_first = True assert master.lsb_first master.select_high = True assert master.select_high with pytest.raises(ValueError): master.bits_per_word = 0 # XXX Test two simultaneous SPI devices sharing clock, MOSI, and MISO, with # separate select pins (including threaded tests which attempt simultaneous # reading/writing) gpiozero-1.4.1/tests/test_spi_devices.py0000644000175000017500000003324713243115414020312 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import sys import pytest from mock import patch from collections import namedtuple try: from math import isclose except ImportError: from gpiozero.compat import isclose from gpiozero.pins.mock import MockSPIDevice, MockPin from gpiozero import * def teardown_function(function): Device.pin_factory.reset() def clamp(v, min_value, max_value): return min(max_value, max(min_value, v)) def scale(v, ref, bits): v /= ref vmin = -(2 ** bits) vmax = -vmin - 1 vrange = vmax - vmin return int(((v + 1) / 2.0) * vrange + vmin) class MockMCP3xxx(MockSPIDevice): def __init__( self, clock_pin, mosi_pin, miso_pin, select_pin=None, channels=8, bits=10): super(MockMCP3xxx, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin) self.vref = 3.3 self.channels = [0.0] * channels self.channel_bits = 3 self.bits = bits self.state = 'idle' def on_start(self): super(MockMCP3xxx, self).on_start() self.state = 'idle' def on_bit(self): if self.state == 'idle': if self.rx_buf[-1]: self.state = 'mode' self.rx_buf = [] elif self.state == 'mode': if self.rx_buf[-1]: self.state = 'single' else: self.state = 'diff' self.rx_buf = [] elif self.state in ('single', 'diff'): if len(self.rx_buf) == self.channel_bits: self.on_result(self.state == 'diff', self.rx_word()) self.state = 'result' elif self.state == 'result': if not self.tx_buf: self.state = 'idle' self.rx_buf = [] else: assert False def on_result(self, differential, channel): if differential: pos_channel = channel neg_channel = pos_channel ^ 1 result = self.channels[pos_channel] - self.channels[neg_channel] result = clamp(result, 0, self.vref) else: result = clamp(self.channels[channel], 0, self.vref) result = scale(result, self.vref, self.bits) self.tx_word(result, self.bits + 2) class MockMCP3xx1(MockMCP3xxx): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, bits=10): super(MockMCP3xx1, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=bits) def on_start(self): super(MockMCP3xx1, self).on_start() result = self.channels[0] - self.channels[1] result = clamp(result, 0, self.vref) result = scale(result, self.vref, self.bits) self.tx_word(result, self.bits + 3) def on_bit(self): pass class MockMCP3xx2(MockMCP3xxx): def __init__( self, clock_pin, mosi_pin, miso_pin, select_pin=None, bits=10): super(MockMCP3xx2, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=bits) self.channel_bits = 1 class MockMCP33xx(MockMCP3xxx): def __init__( self, clock_pin, mosi_pin, miso_pin, select_pin=None, channels=8): super(MockMCP33xx, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels, 12) def on_result(self, differential, channel): if differential: pos_channel = channel neg_channel = pos_channel ^ 1 result = self.channels[pos_channel] - self.channels[neg_channel] result = clamp(result, -self.vref, self.vref) else: result = clamp(self.channels[channel], 0, self.vref) result = scale(result, self.vref, self.bits) if result < 0: result += 8192 self.tx_word(result, self.bits + 3) class MockMCP3001(MockMCP3xx1): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3001, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, bits=10) class MockMCP3002(MockMCP3xx2): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3002, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, bits=10) class MockMCP3004(MockMCP3xxx): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3004, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels=4, bits=10) class MockMCP3008(MockMCP3xxx): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3008, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels=8, bits=10) class MockMCP3201(MockMCP3xx1): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3201, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, bits=12) class MockMCP3202(MockMCP3xx2): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3202, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, bits=12) class MockMCP3204(MockMCP3xxx): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3204, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels=4, bits=12) class MockMCP3208(MockMCP3xxx): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3208, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels=8, bits=12) class MockMCP3301(MockMCP3xxx): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3301, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=12) def on_start(self): super(MockMCP3301, self).on_start() result = self.channels[0] - self.channels[1] result = clamp(result, -self.vref, self.vref) result = scale(result, self.vref, self.bits) if result < 0: result += 8192 self.tx_word(result, self.bits + 4) class MockMCP3302(MockMCP33xx): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3302, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels=4) class MockMCP3304(MockMCP33xx): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None): super(MockMCP3304, self).__init__( clock_pin, mosi_pin, miso_pin, select_pin, channels=8) def single_mcp_test(mock, pot, channel, bits): scale = 2**bits tolerance = 1 / scale voltage_tolerance = pot.max_voltage / scale mock.channels[channel] = 0.0 assert pot.raw_value == 0 assert isclose(pot.value, 0.0, abs_tol=tolerance) assert isclose(pot.voltage, 0.0, abs_tol=voltage_tolerance) mock.channels[channel] = mock.vref / 2 assert pot.raw_value == (scale / 2) - 1 assert isclose(pot.value, 0.5, abs_tol=tolerance) assert isclose(pot.voltage, pot.max_voltage / 2, abs_tol=voltage_tolerance) mock.channels[channel] = mock.vref assert pot.raw_value == scale - 1 assert isclose(pot.value, 1.0, abs_tol=tolerance) assert isclose(pot.voltage, pot.max_voltage, abs_tol=voltage_tolerance) def differential_mcp_test(mock, pot, pos_channel, neg_channel, bits, full=False): scale = 2**bits tolerance = 1 / scale voltage_tolerance = pot.max_voltage / scale mock.channels[pos_channel] = 0.0 mock.channels[neg_channel] = 0.0 assert pot.raw_value == 0 assert isclose(pot.value, 0.0, abs_tol=tolerance) assert isclose(pot.voltage, 0.0, abs_tol=voltage_tolerance) mock.channels[pos_channel] = mock.vref / 2 assert pot.raw_value == (scale / 2) - 1 assert isclose(pot.value, 0.5, abs_tol=tolerance) assert isclose(pot.voltage, pot.max_voltage / 2, abs_tol=voltage_tolerance) mock.channels[pos_channel] = mock.vref assert pot.raw_value == scale - 1 assert isclose(pot.value, 1.0, abs_tol=tolerance) assert isclose(pot.voltage, pot.max_voltage, abs_tol=voltage_tolerance) mock.channels[neg_channel] = mock.vref / 2 assert pot.raw_value == (scale / 2) - 1 assert isclose(pot.value, 0.5, abs_tol=tolerance) assert isclose(pot.voltage, pot.max_voltage / 2, abs_tol=voltage_tolerance) mock.channels[pos_channel] = mock.vref / 2 assert pot.raw_value == 0 assert isclose(pot.value, 0.0, abs_tol=tolerance) assert isclose(pot.voltage, 0.0, abs_tol=voltage_tolerance) mock.channels[pos_channel] = 0.0 mock.channels[neg_channel] = mock.vref if full: assert pot.raw_value == -scale assert isclose(pot.value, -1.0, abs_tol=tolerance) assert isclose(pot.voltage, -pot.max_voltage, abs_tol=voltage_tolerance) else: assert pot.raw_value == 0 assert isclose(pot.value, 0.0, abs_tol=tolerance) assert isclose(pot.voltage, 0.0, abs_tol=voltage_tolerance) def test_MCP3001(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3001(11, 10, 9, 8) with MCP3001() as pot: differential_mcp_test(mock, pot, 0, 1, 10) with MCP3001(max_voltage=5.0) as pot: differential_mcp_test(mock, pot, 0, 1, 10) def test_MCP3002(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3002(11, 10, 9, 8) with pytest.raises(ValueError): MCP3002(channel=5) with MCP3002(channel=1) as pot: single_mcp_test(mock, pot, 1, 10) with MCP3002(channel=1, max_voltage=5.0) as pot: single_mcp_test(mock, pot, 1, 10) with MCP3002(channel=1, differential=True) as pot: differential_mcp_test(mock, pot, 1, 0, 10) def test_MCP3004(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3004(11, 10, 9, 8) with pytest.raises(ValueError): MCP3004(channel=5) with MCP3004(channel=3) as pot: single_mcp_test(mock, pot, 3, 10) with MCP3004(channel=3, max_voltage=5.0) as pot: single_mcp_test(mock, pot, 3, 10) with MCP3004(channel=3, differential=True) as pot: differential_mcp_test(mock, pot, 3, 2, 10) def test_MCP3008(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3008(11, 10, 9, 8) with pytest.raises(ValueError): MCP3008(channel=9) with MCP3008(channel=0) as pot: single_mcp_test(mock, pot, 0, 10) with MCP3008(channel=1, max_voltage=5.0) as pot: single_mcp_test(mock, pot, 1, 10) with MCP3008(channel=0, differential=True) as pot: differential_mcp_test(mock, pot, 0, 1, 10) def test_MCP3201(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3201(11, 10, 9, 8) with MCP3201() as pot: differential_mcp_test(mock, pot, 0, 1, 12) with MCP3201(max_voltage=5.0) as pot: differential_mcp_test(mock, pot, 0, 1, 12) def test_MCP3202(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3202(11, 10, 9, 8) with pytest.raises(ValueError): MCP3202(channel=5) with MCP3202(channel=1) as pot: single_mcp_test(mock, pot, 1, 12) with MCP3202(channel=1, max_voltage=5.0) as pot: single_mcp_test(mock, pot, 1, 12) with MCP3202(channel=1, differential=True) as pot: differential_mcp_test(mock, pot, 1, 0, 12) def test_MCP3204(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3204(11, 10, 9, 8) with pytest.raises(ValueError): MCP3204(channel=5) with MCP3204(channel=1) as pot: single_mcp_test(mock, pot, 1, 12) with MCP3204(channel=1, max_voltage=5.0) as pot: single_mcp_test(mock, pot, 1, 12) with MCP3204(channel=1, differential=True) as pot: differential_mcp_test(mock, pot, 1, 0, 12) def test_MCP3208(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3208(11, 10, 9, 8) with pytest.raises(ValueError): MCP3208(channel=9) with MCP3208(channel=7) as pot: single_mcp_test(mock, pot, 7, 12) with MCP3208(channel=7, max_voltage=5.0) as pot: single_mcp_test(mock, pot, 7, 12) with MCP3208(channel=7, differential=True) as pot: differential_mcp_test(mock, pot, 7, 6, 12) def test_MCP3301(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3301(11, 10, 9, 8) with MCP3301() as pot: differential_mcp_test(mock, pot, 0, 1, 12, full=True) with MCP3301(max_voltage=5.0) as pot: differential_mcp_test(mock, pot, 0, 1, 12, full=True) def test_MCP3302(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3302(11, 10, 9, 8) with pytest.raises(ValueError): MCP3302(channel=4) with MCP3302(channel=0) as pot: single_mcp_test(mock, pot, 0, 12) with MCP3302(channel=0, max_voltage=5.0) as pot: single_mcp_test(mock, pot, 0, 12) with MCP3302(channel=0, differential=True) as pot: differential_mcp_test(mock, pot, 0, 1, 12, full=True) def test_MCP3304(): with patch('gpiozero.pins.local.SpiDev', None): mock = MockMCP3304(11, 10, 9, 8) with pytest.raises(ValueError): MCP3304(channel=9) with MCP3304(channel=5) as pot: single_mcp_test(mock, pot, 5, 12) with MCP3304(channel=5, max_voltage=5.0) as pot: single_mcp_test(mock, pot, 5, 12) with MCP3304(channel=5, differential=True) as pot: differential_mcp_test(mock, pot, 5, 4, 12, full=True) gpiozero-1.4.1/tests/test_inputs.py0000644000175000017500000001516213165454031017337 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import sys import pytest from threading import Event from functools import partial from gpiozero.pins.mock import MockChargingPin, MockTriggerPin from gpiozero import * def teardown_function(function): Device.pin_factory.reset() def test_input_initial_values(): pin = Device.pin_factory.pin(4) with InputDevice(4, pull_up=True) as device: assert pin.function == 'input' assert pin.pull == 'up' assert device.pull_up with InputDevice(4, pull_up=False) as device: assert pin.pull == 'down' assert not device.pull_up def test_input_is_active_low(): pin = Device.pin_factory.pin(2) with InputDevice(2, pull_up=True) as device: pin.drive_high() assert not device.is_active assert repr(device) == '' pin.drive_low() assert device.is_active assert repr(device) == '' def test_input_is_active_high(): pin = Device.pin_factory.pin(4) with InputDevice(4, pull_up=False) as device: pin.drive_high() assert device.is_active assert repr(device) == '' pin.drive_low() assert not device.is_active assert repr(device) == '' def test_input_pulled_up(): pin = Device.pin_factory.pin(2) with pytest.raises(PinFixedPull): InputDevice(2, pull_up=False) def test_input_event_activated(): event = Event() pin = Device.pin_factory.pin(4) with DigitalInputDevice(4) as device: device.when_activated = lambda: event.set() assert not event.is_set() pin.drive_high() assert event.is_set() def test_input_event_deactivated(): event = Event() pin = Device.pin_factory.pin(4) with DigitalInputDevice(4) as device: device.when_deactivated = lambda: event.set() assert not event.is_set() pin.drive_high() assert not event.is_set() pin.drive_low() assert event.is_set() def test_input_partial_callback(): event = Event() pin = Device.pin_factory.pin(4) def foo(a, b): event.set() return a + b bar = partial(foo, 1) baz = partial(bar, 2) with DigitalInputDevice(4) as device: device.when_activated = baz assert not event.is_set() pin.drive_high() assert event.is_set() def test_input_wait_active(): pin = Device.pin_factory.pin(4) with DigitalInputDevice(4) as device: pin.drive_high() assert device.wait_for_active(1) assert not device.wait_for_inactive(0) def test_input_wait_inactive(): pin = Device.pin_factory.pin(4) with DigitalInputDevice(4) as device: assert device.wait_for_inactive(1) assert not device.wait_for_active(0) def test_input_smoothed_attrib(): pin = Device.pin_factory.pin(4) with SmoothedInputDevice(4, threshold=0.5, queue_len=5, partial=False) as device: assert repr(device) == '' assert device.threshold == 0.5 assert device.queue_len == 5 assert not device.partial device._queue.start() assert not device.is_active with pytest.raises(InputDeviceError): device.threshold = 1 def test_input_smoothed_values(): pin = Device.pin_factory.pin(4) with SmoothedInputDevice(4) as device: device._queue.start() assert not device.is_active pin.drive_high() assert device.wait_for_active(1) pin.drive_low() assert device.wait_for_inactive(1) def test_input_button(): pin = Device.pin_factory.pin(2) with Button(2) as button: assert pin.pull == 'up' assert not button.is_pressed pin.drive_low() assert button.is_pressed assert button.wait_for_press(1) pin.drive_high() assert not button.is_pressed assert button.wait_for_release(1) def test_input_line_sensor(): pin = Device.pin_factory.pin(4) with LineSensor(4) as sensor: pin.drive_low() # logic is inverted for line sensor assert sensor.wait_for_line(1) assert sensor.line_detected pin.drive_high() assert sensor.wait_for_no_line(1) assert not sensor.line_detected def test_input_motion_sensor(): pin = Device.pin_factory.pin(4) with MotionSensor(4) as sensor: pin.drive_high() assert sensor.wait_for_motion(1) assert sensor.motion_detected pin.drive_low() assert sensor.wait_for_no_motion(1) assert not sensor.motion_detected @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_input_light_sensor(): pin = Device.pin_factory.pin(4, pin_class=MockChargingPin) with LightSensor(4) as sensor: pin.charge_time = 0.1 assert sensor.wait_for_dark(1) pin.charge_time = 0.0 assert sensor.wait_for_light(1) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_input_distance_sensor(): echo_pin = Device.pin_factory.pin(4) trig_pin = Device.pin_factory.pin(5, pin_class=MockTriggerPin, echo_pin=echo_pin, echo_time=0.02) with pytest.raises(ValueError): DistanceSensor(4, 5, max_distance=-1) # normal queue len is large (because the sensor is *really* jittery) but # we want quick tests and we've got precisely controlled pins :) with DistanceSensor(4, 5, queue_len=5, max_distance=1) as sensor: assert sensor.max_distance == 1 assert sensor.trigger is trig_pin assert sensor.echo is echo_pin assert sensor.wait_for_out_of_range(1) assert not sensor.in_range assert sensor.distance == 1.0 # should be waay before max-distance so this should work trig_pin.echo_time = 0.0 assert sensor.wait_for_in_range(1) assert sensor.in_range assert sensor.distance < sensor.threshold_distance # depending on speed of machine, may not reach 0 here sensor.threshold_distance = 0.1 assert sensor.threshold_distance == 0.1 with pytest.raises(ValueError): sensor.max_distance = -1 sensor.max_distance = 20 assert sensor.max_distance == 20 assert sensor.threshold_distance == 0.1 gpiozero-1.4.1/tests/cli/0000755000175000017500000000000013243117413015143 5ustar davedave00000000000000gpiozero-1.4.1/tests/cli/test_pinout.py0000644000175000017500000000150113165454031020072 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import pytest from gpiozerocli.pinout import main def test_args_incorrect(): with pytest.raises(SystemExit) as ex: main(['pinout', '--nonexistentarg']) def test_args_color(): args = main.parser.parse_args([]) assert args.color is None args = main.parser.parse_args(['--color']) assert args.color is True args = main.parser.parse_args(['--monochrome']) assert args.color is False def test_args_revision(): args = main.parser.parse_args(['--revision', '000d']) assert args.revision == '000d' def test_help(capsys): with pytest.raises(SystemExit) as ex: main(['pinout', '--help']) out, err = capsys.readouterr() assert 'GPIO pin-out' in out gpiozero-1.4.1/tests/test_devices.py0000644000175000017500000000710713165454031017437 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import warnings import pytest from gpiozero import * def teardown_function(function): Device.pin_factory.reset() # TODO add more devices tests! def test_device_bad_pin(): with pytest.raises(GPIOPinMissing): device = GPIODevice() with pytest.raises(PinInvalidPin): device = GPIODevice(60) def test_device_non_physical(): with warnings.catch_warnings(record=True) as w: device = GPIODevice(37) assert len(w) == 1 assert w[0].category == PinNonPhysical def test_device_init(): pin = Device.pin_factory.pin(2) with GPIODevice(2) as device: assert not device.closed assert device.pin == pin def test_device_init_twice_same_pin(): with GPIODevice(2) as device: with pytest.raises(GPIOPinInUse): GPIODevice(2) def test_device_init_twice_different_pin(): with GPIODevice(2) as device: with GPIODevice(3) as device2: pass def test_device_close(): device = GPIODevice(2) # Don't use "with" here; we're testing close explicitly device.close() assert device.closed assert device.pin is None def test_device_reopen_same_pin(): pin = Device.pin_factory.pin(2) with GPIODevice(2) as device: pass with GPIODevice(2) as device2: assert not device2.closed assert device2.pin is pin assert device.closed assert device.pin is None def test_device_repr(): with GPIODevice(4) as device: assert repr(device) == '' % device.pin def test_device_repr_after_close(): with GPIODevice(2) as device: pass assert repr(device) == '' def test_device_unknown_attr(): with GPIODevice(2) as device: with pytest.raises(AttributeError): device.foo = 1 def test_device_context_manager(): with GPIODevice(2) as device: assert not device.closed assert device.closed def test_composite_device_sequence(): with CompositeDevice( InputDevice(4), InputDevice(5) ) as device: assert len(device) == 2 assert device[0].pin.number == 4 assert device[1].pin.number == 5 assert device.namedtuple._fields == ('device_0', 'device_1') def test_composite_device_values(): with CompositeDevice( InputDevice(4), InputDevice(5) ) as device: assert device.value == (0, 0) assert not device.is_active device[0].pin.drive_high() assert device.value == (1, 0) assert device.is_active def test_composite_device_named(): with CompositeDevice( foo=InputDevice(4), bar=InputDevice(5), _order=('foo', 'bar') ) as device: assert device.namedtuple._fields == ('foo', 'bar') assert device.value == (0, 0) assert not device.is_active def test_composite_device_bad_init(): with pytest.raises(ValueError): CompositeDevice(foo=1, bar=2, _order=('foo',)) with pytest.raises(ValueError): CompositeDevice(close=1) with pytest.raises(ValueError): CompositeDevice(2) with pytest.raises(ValueError): CompositeDevice(Device.pin_factory.pin(2)) def test_composite_device_read_only(): with CompositeDevice( foo=InputDevice(4), bar=InputDevice(5) ) as device: with pytest.raises(AttributeError): device.foo = 1 gpiozero-1.4.1/tests/test_mock_pin.py0000644000175000017500000001142413165454031017611 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') from threading import Event import pytest from gpiozero.pins.mock import MockPWMPin, MockPin from gpiozero import * def teardown_function(function): Device.pin_factory.reset() # Some rough tests to make sure our MockPin is up to snuff. This is just # enough to get reasonable coverage but it's by no means comprehensive... def test_mock_pin_init(): with pytest.raises(ValueError): Device.pin_factory.pin(60) assert Device.pin_factory.pin(2).number == 2 def test_mock_pin_defaults(): pin = Device.pin_factory.pin(4) assert pin.bounce == None assert pin.edges == 'both' assert pin.frequency == None assert pin.function == 'input' assert pin.pull == 'floating' assert pin.state == 0 assert pin.when_changed == None pin.close() pin = Device.pin_factory.pin(2) assert pin.pull == 'up' def test_mock_pin_open_close(): pin = Device.pin_factory.pin(2) pin.close() def test_mock_pin_init_twice_same_pin(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(pin1.number) assert pin1 is pin2 def test_mock_pin_init_twice_different_pin(): pin1 = Device.pin_factory.pin(2) pin2 = Device.pin_factory.pin(pin1.number+1) assert pin1 != pin2 assert pin1.number == 2 assert pin2.number == pin1.number+1 def test_mock_pwm_pin_defaults(): pin = Device.pin_factory.pin(4, pin_class=MockPWMPin) assert pin.bounce == None assert pin.edges == 'both' assert pin.frequency == None assert pin.function == 'input' assert pin.pull == 'floating' assert pin.state == 0 assert pin.when_changed == None pin.close() pin = Device.pin_factory.pin(2, pin_class=MockPWMPin) assert pin.pull == 'up' def test_mock_pwm_pin_open_close(): pin = Device.pin_factory.pin(2, pin_class=MockPWMPin) pin.close() def test_mock_pwm_pin_init_twice_same_pin(): pin1 = Device.pin_factory.pin(2, pin_class=MockPWMPin) pin2 = Device.pin_factory.pin(pin1.number, pin_class=MockPWMPin) assert pin1 is pin2 def test_mock_pwm_pin_init_twice_different_pin(): pin1 = Device.pin_factory.pin(2, pin_class=MockPWMPin) pin2 = Device.pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin) assert pin1 != pin2 assert pin1.number == 2 assert pin2.number == pin1.number+1 def test_mock_pin_init_twice_different_modes(): pin1 = Device.pin_factory.pin(2, pin_class=MockPin) pin2 = Device.pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin) assert pin1 != pin2 with pytest.raises(ValueError): Device.pin_factory.pin(pin1.number, pin_class=MockPWMPin) with pytest.raises(ValueError): Device.pin_factory.pin(pin2.number, pin_class=MockPin) def test_mock_pin_frequency_unsupported(): pin = Device.pin_factory.pin(2) pin.frequency = None with pytest.raises(PinPWMUnsupported): pin.frequency = 100 def test_mock_pin_frequency_supported(): pin = Device.pin_factory.pin(2, pin_class=MockPWMPin) pin.function = 'output' assert pin.frequency is None pin.frequency = 100 pin.state = 0.5 pin.frequency = None assert not pin.state def test_mock_pin_pull(): pin = Device.pin_factory.pin(4) pin.function = 'input' assert pin.pull == 'floating' pin.pull = 'up' assert pin.state pin.pull = 'down' assert not pin.state pin.close() pin = Device.pin_factory.pin(2) pin.function = 'input' assert pin.pull == 'up' with pytest.raises(PinFixedPull): pin.pull = 'floating' def test_mock_pin_state(): pin = Device.pin_factory.pin(2) with pytest.raises(PinSetInput): pin.state = 1 pin.function = 'output' pin.state = 1 assert pin.state == 1 pin.state = 0 assert pin.state == 0 pin.state = 0.5 assert pin.state == 1 def test_mock_pwm_pin_state(): pin = Device.pin_factory.pin(2, pin_class=MockPWMPin) with pytest.raises(PinSetInput): pin.state = 1 pin.function = 'output' pin.state = 1 assert pin.state == 1 pin.state = 0 assert pin.state == 0 pin.state = 0.5 assert pin.state == 0.5 def test_mock_pin_edges(): pin = Device.pin_factory.pin(2) assert pin.when_changed is None fired = Event() pin.function = 'input' pin.edges = 'both' assert pin.edges == 'both' pin.drive_low() assert not pin.state def changed(): fired.set() pin.when_changed = changed pin.drive_high() assert pin.state assert fired.is_set() fired.clear() pin.edges = 'falling' pin.drive_low() assert not pin.state assert fired.is_set() fired.clear() pin.drive_high() assert pin.state assert not fired.is_set() assert pin.edges == 'falling' gpiozero-1.4.1/tests/test_tools.py0000644000175000017500000003050613243115414017150 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import pytest from math import sin, cos, radians from time import time from itertools import islice from gpiozero.tools import * try: from math import isclose except ImportError: from gpiozero.compat import isclose try: from statistics import mean except ImportError: from gpiozero.compat import mean try: from statistics import median except ImportError: from gpiozero.compat import median def test_negated(): assert list(negated(())) == [] assert list(negated((True, True, False, False))) == [False, False, True, True] def test_inverted(): with pytest.raises(ValueError): list(inverted((), 0, 0)) with pytest.raises(ValueError): list(inverted((), 1, 1)) with pytest.raises(ValueError): list(inverted((), 1, 0)) assert list(inverted(())) == [] assert list(inverted((1, 0, 0.1, 0.5))) == [0, 1, 0.9, 0.5] assert list(inverted((1, 0, 0.1, 0.5), 0, 1)) == [0, 1, 0.9, 0.5] assert list(inverted((-1, 0, -0.1, -0.5), -1, 0)) == [0, -1, -0.9, -0.5] assert list(inverted((1, 0, 0.1, 0.5, -1, -0.1, -0.5), -1, 1)) == [-1, 0, -0.1, -0.5, 1, 0.1, 0.5] assert list(inverted((2, 1, 1.1, 1.5), 1, 2)) == [1, 2, 1.9, 1.5] def test_scaled(): with pytest.raises(ValueError): list(scaled((), 0, 1, 0, 0)) with pytest.raises(ValueError): list(scaled((), 0, 1, 1, 1)) with pytest.raises(ValueError): list(scaled((), 0, 1, 1, 0)) assert list(scaled((), 0, 1)) == [] # no scale assert list(scaled((0, 1, 0.5, 0.1), 0, 1)) == [0, 1, 0.5, 0.1] assert list(scaled((0, 1, 0.5, 0.1), 0, 1, 0, 1)) == [0, 1, 0.5, 0.1] # multiply by 2 assert list(scaled((0, 1, 0.5, 0.1), 0, 2, 0, 1)) == [0, 2, 1, 0.2] # add 1 assert list(scaled((0, 1, 0.5, 0.1), 1, 2, 0, 1)) == [1, 2, 1.5, 1.1] # multiply by 2 then add 1 assert list(scaled((0, 1, 0.5, 0.1), 1, 3, 0, 1)) == [1, 3, 2, 1.2] # add 1 then multiply by 2 assert list(scaled((0, 1, 0.5, 0.1), 2, 4, 0, 1)) == [2, 4, 3, 2.2] # invert assert list(scaled((0, 1, 0.5, 0.1), 1, 0, 0, 1)) == [1, 0, 0.5, 0.9] # multiply by -1 then subtract 1 assert list(scaled((0, 1, 0.5, 0.1), -1, -2, 0, 1)) == [-1, -2, -1.5, -1.1] # scale 0->1 to -1->+1 assert list(scaled((0, 1, 0.5, 0.1), -1, 1)) == [-1, 1, 0.0, -0.8] assert list(scaled((0, 1, 0.5, 0.1), -1, 1, 0, 1)) == [-1, 1, 0.0, -0.8] # scale -1->+1 to 0->1 assert list(scaled((-1, 1, 0.0, -0.5), 0, 1, -1, 1)) == [0, 1, 0.5, 0.25] def test_clamped(): with pytest.raises(ValueError): list(clamped((), 0, 0)) with pytest.raises(ValueError): list(clamped((), 1, 1)) with pytest.raises(ValueError): list(clamped((), 1, 0)) assert list(clamped(())) == [] assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2))) == [0, 0, 0, 0, 0.5, 1, 1] assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), 0, 1)) == [0, 0, 0, 0, 0.5, 1, 1] assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), -1, 1)) == [-1, -1, -0.5, 0, 0.5, 1, 1] assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), -2, 2)) == [-2, -1, -0.5, 0, 0.5, 1, 2] def test_absoluted(): assert list(absoluted(())) == [] assert list(absoluted((-2, -1, 0, 1, 2))) == [2, 1, 0, 1, 2] def test_quantized(): with pytest.raises(ValueError): list(quantized((), 0)) with pytest.raises(ValueError): list(quantized((), 4, 0, 0)) with pytest.raises(ValueError): list(quantized((), 4, 1, 1)) with pytest.raises(ValueError): list(quantized((), 4, 1, 0)) assert list(quantized((), 4)) == [] assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 4)) == [ 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 1.0] assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 4, 0, 1)) == [ 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 1.0] assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 5)) == [ 0.0, 0.0, 0.2, 0.2, 0.4, 0.4, 0.6, 0.6, 0.8, 0.8, 1.0] assert list(quantized((0, 0.25, 0.5, 0.75, 1.0, 1.5, 1.75, 2.0), 2, 0, 2)) == [ 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0] assert list(quantized((1, 1.25, 1.5, 1.75, 2.0, 2.5, 2.75, 3.0), 2, 1, 3)) == [ 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0] def test_booleanized(): with pytest.raises(ValueError): list(booleanized((), 0, 0)) with pytest.raises(ValueError): list(booleanized((), 1, 1)) with pytest.raises(ValueError): list(booleanized((), 1, 0)) with pytest.raises(ValueError): list(booleanized((), 0, 0.5, -0.2)) with pytest.raises(ValueError): list(booleanized((), 0, 0.5, 0.5)) assert list(booleanized((), 0, 0.5)) == [] assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 0, 0.5)) == [ True, True, True, True, True, True, False, False, False, False, False] assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0), 0.25, 0.75)) == [ False, False, False, True, True, True, True, True, False, False, False, False] assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0), 0.25, 0.75, 0.2)) == [ False, False, False, False, False, True, True, True, True, True, False, False] assert list(booleanized((1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 1), 0.25, 0.75)) == [ False, False, False, True, True, True, True, True, False, False, False, False] assert list(booleanized((1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 1), 0.25, 0.75, 0.2)) == [ False, False, False, False, False, True, True, True, True, True, False, False] def test_all_values(): assert list(all_values(())) == [] assert list(all_values((False, True))) == [False, True] assert list(all_values((0, 1, 0, 1), (0, 0, 0, 1))) == [0, 0, 0, 1] def test_any_values(): assert list(any_values(())) == [] assert list(any_values((False, True))) == [False, True] assert list(any_values((0, 1, 0, 1), (0, 0, 0, 1))) == [0, 1, 0, 1] def test_averaged(): assert list(averaged(())) == [] assert list(averaged((0, 0.5, 1))) == [0, 0.5, 1] assert list(averaged((0, 0.5, 1), (1, 1, 1))) == [0.5, 0.75, 1] def test_summed(): assert list(summed(())) == [] assert list(summed((0, 0.5, 0.5, 1))) == [0, 0.5, 0.5, 1] assert list(summed((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [1, 1, 1.5, 2] def test_multiplied(): assert list(multiplied(())) == [] assert list(multiplied((0, 0.5, 0.5, 1))) == [0, 0.5, 0.5, 1] assert list(multiplied((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [0, 0.25, 0.5, 1] def test_queued(): with pytest.raises(ValueError): list(queued((), 0)) assert list(queued((), 5)) == [] assert list(queued((1, 2, 3, 4, 5), 5)) == [1] assert list(queued((1, 2, 3, 4, 5, 6), 5)) == [1, 2] def test_smoothed(): with pytest.raises(ValueError): list(smoothed((), 0)) assert list(smoothed((), 5)) == [] assert list(smoothed((1, 2, 3, 4, 5), 5)) == [3.0] assert list(smoothed((1, 2, 3, 4, 5, 6), 5)) == [3.0, 4.0] assert list(smoothed((1, 2, 3, 4, 5, 6), 5, average=mean)) == [3.0, 4.0] assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=mean)) == [2.4, 3.2] assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=median)) == [1, 4] def test_pre_delayed(): with pytest.raises(ValueError): list(pre_delayed((), -1)) assert list(pre_delayed((), 0.01)) == [] count = 0 start = time() for v in pre_delayed((0, 0, 0), 0.01): count += 1 assert v == 0 assert time() - start >= 0.01 start = time() assert count == 3 def test_post_delayed(): with pytest.raises(ValueError): list(post_delayed((), -1)) assert list(post_delayed((), 0.01)) == [] count = 0 start = time() for v in post_delayed((1, 2, 2), 0.01): count += 1 if v == 1: assert time() - start < 0.01 else: assert v == 2 assert time() - start >= 0.01 start = time() assert time() - start >= 0.01 assert count == 3 def test_pre_periodic_filtered(): with pytest.raises(ValueError): list(pre_periodic_filtered((), 2, -1)) with pytest.raises(ValueError): list(pre_periodic_filtered((), 0, 0)) assert list(pre_periodic_filtered((), 2, 0)) == [] assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 0)) == [3, 4, 5, 6, 7, 8, 9, 10] assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 1)) == [2, 4, 6, 8, 10] assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 2)) == [2, 3, 5, 6, 8, 9] assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 1)) == [3, 6, 9] def test_post_periodic_filtered(): with pytest.raises(ValueError): list(post_periodic_filtered((), 1, 0)) with pytest.raises(ValueError): list(post_periodic_filtered((), 0, 1)) assert list(pre_periodic_filtered((), 1, 1)) == [] assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 1)) == [1, 3, 5, 7, 9] assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 2)) == [1, 4, 7, 10] assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 1)) == [1, 2, 4, 5, 7, 8, 10] def test_alternating_values(): assert list(islice(alternating_values(), 5)) == [False, True, False, True, False] assert list(islice(alternating_values(True), 5)) == [True, False, True, False, True] def test_random_values(): for _, v in zip(range(1000), random_values()): assert 0 <= v <= 1 def test_sin_values(): for e, v in zip([0, 0], sin_values(2)): assert -1 <= v <= 1 assert isclose(e, v, abs_tol=1e-9) for e, v in zip([0, 1, 0, -1], sin_values(4)): assert -1 <= v <= 1 assert isclose(e, v, abs_tol=1e-9) for e, v in zip([0, 2**0.5/2, 1, 2**0.5/2, 0, -2**0.5/2, -1, -2**0.5/2], sin_values(8)): assert -1 <= v <= 1 assert isclose(e, v, abs_tol=1e-9) firstval = None for i, v in zip(range(1000), sin_values()): assert -1 <= v <= 1 assert isclose(v, sin(radians(i)), abs_tol=1e-9) if i == 0: firstval = v else: if i % 360 == 0: assert v == firstval for period in (360, 100): firstval = None for i, v in zip(range(1000), sin_values(period)): assert -1 <= v <= 1 if i == 0: firstval = v else: if i % period == 0: assert v == firstval def test_cos_values(): for e, v in zip([1, -1], cos_values(2)): assert -1 <= v <= 1 assert isclose(e, v, abs_tol=1e-9) for e, v in zip([1, 0, -1, 0], cos_values(4)): assert -1 <= v <= 1 assert isclose(e, v, abs_tol=1e-9) for e, v in zip([1, 2**0.5/2, 0, -2**0.5/2, -1, -2**0.5/2, 0, 2**0.5/2], cos_values(8)): assert -1 <= v <= 1 assert isclose(e, v, abs_tol=1e-9) firstval = None for i, v in zip(range(1000), cos_values()): assert -1 <= v <= 1 assert isclose(v, cos(radians(i)), abs_tol=1e-9) if i == 0: firstval = v else: if i % 360 == 0: assert v == firstval for period in (360, 100): firstval = None for i, v in zip(range(1000), cos_values(period)): assert -1 <= v <= 1 if i == 0: firstval = v else: if i % period == 0: assert v == firstval def test_ramping_values(): assert list(islice(ramping_values(2), 2)) == [0, 1] assert list(islice(ramping_values(4), 4)) == [0, 0.5, 1, 0.5] assert list(islice(ramping_values(8), 8)) == [0, 0.25, 0.5, 0.75, 1, 0.75, 0.5, 0.25] firstval = None for i, v in zip(range(1000), ramping_values()): assert 0 <= v <= 1 if i == 0: firstval = v else: if i % 360 == 0: v == firstval for period in (360, 100): firstval = None for i, v in zip(range(1000), ramping_values(period)): assert 0 <= v <= 1 if i == 0: firstval = v else: if i % period == 0: assert v == firstval gpiozero-1.4.1/tests/test_real_pins.py0000644000175000017500000001255513165454031017774 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') try: range = xrange except NameError: pass import io import os from time import sleep import pytest import pkg_resources from gpiozero import ( PinFixedPull, PinInvalidPull, PinInvalidFunction, PinPWMUnsupported, Device, ) from gpiozero.pins.mock import MockConnectedPin, MockFactory try: from math import isclose except ImportError: from gpiozero.compat import isclose # This module assumes you've wired the following GPIO pins together. The pins # can be re-configured via the listed environment variables (useful for when # your testing rig requires different pins because the defaults interfere with # attached hardware). TEST_PIN = int(os.getenv('GPIOZERO_TEST_PIN', '22')) INPUT_PIN = int(os.getenv('GPIOZERO_TEST_INPUT_PIN', '27')) @pytest.fixture( scope='module', params=[ name for name in pkg_resources.get_distribution('gpiozero').get_entry_map('gpiozero_pin_factories').keys() if not name.endswith('Pin') # leave out compatibility names ]) def pin_factory(request): try: factory = pkg_resources.load_entry_point('gpiozero', 'gpiozero_pin_factories', request.param)() except Exception as e: pytest.skip("skipped factory %s: %s" % (request.param, str(e))) else: Device.pin_factory = factory def fin(): Device.pin_factory = MockFactory() request.addfinalizer(fin) return factory @pytest.fixture(scope='function') def pins(request, pin_factory): # Why return both pins in a single fixture? If we defined one fixture for # each pin then pytest will (correctly) test RPiGPIOPin(22) against # NativePin(27) and so on. This isn't supported, so we don't test it input_pin = pin_factory.pin(INPUT_PIN) input_pin.function = 'input' input_pin.pull = 'down' if pin_factory.__class__.__name__ == 'MockFactory': test_pin = pin_factory.pin(TEST_PIN, pin_class=MockConnectedPin, input_pin=input_pin) else: test_pin = pin_factory.pin(TEST_PIN) def fin(): test_pin.close() input_pin.close() request.addfinalizer(fin) return test_pin, input_pin def test_pin_numbers(pins): test_pin, input_pin = pins assert test_pin.number == TEST_PIN assert input_pin.number == INPUT_PIN def test_function_bad(pins): test_pin, input_pin = pins with pytest.raises(PinInvalidFunction): test_pin.function = 'foo' def test_output(pins): test_pin, input_pin = pins test_pin.function = 'output' test_pin.state = 0 assert input_pin.state == 0 test_pin.state = 1 assert input_pin.state == 1 def test_output_with_state(pins): test_pin, input_pin = pins test_pin.output_with_state(0) assert input_pin.state == 0 test_pin.output_with_state(1) assert input_pin.state == 1 def test_pull(pins): test_pin, input_pin = pins test_pin.function = 'input' test_pin.pull = 'up' assert input_pin.state == 1 test_pin.pull = 'down' test_pin, input_pin = pins assert input_pin.state == 0 def test_pull_bad(pins): test_pin, input_pin = pins test_pin.function = 'input' with pytest.raises(PinInvalidPull): test_pin.pull = 'foo' with pytest.raises(PinInvalidPull): test_pin.input_with_pull('foo') def test_pull_down_warning(pin_factory): if pin_factory.pi_info.pulled_up('GPIO2'): pin = pin_factory.pin(2) try: with pytest.raises(PinFixedPull): pin.pull = 'down' with pytest.raises(PinFixedPull): pin.input_with_pull('down') finally: pin.close() else: pytest.skip("GPIO2 isn't pulled up on this pi") def test_input_with_pull(pins): test_pin, input_pin = pins test_pin.input_with_pull('up') assert input_pin.state == 1 test_pin.input_with_pull('down') assert input_pin.state == 0 def test_bad_duty_cycle(pins): test_pin, input_pin = pins test_pin.function = 'output' try: # NOTE: There's some race in RPi.GPIO that causes a segfault if we # don't pause before starting PWM; only seems to happen when stopping # and restarting PWM very rapidly (i.e. between test cases). if Device.pin_factory.__class__.__name__ == 'RPiGPIOFactory': sleep(0.1) test_pin.frequency = 100 except PinPWMUnsupported: pytest.skip("%r doesn't support PWM" % test_pin.factory) else: try: with pytest.raises(ValueError): test_pin.state = 1.1 finally: test_pin.frequency = None def test_duty_cycles(pins): test_pin, input_pin = pins test_pin.function = 'output' try: # NOTE: see above if Device.pin_factory.__class__.__name__ == 'RPiGPIOFactory': sleep(0.1) test_pin.frequency = 100 except PinPWMUnsupported: pytest.skip("%r doesn't support PWM" % test_pin.factory) else: try: for duty_cycle in (0.0, 0.1, 0.5, 1.0): test_pin.state = duty_cycle assert test_pin.state == duty_cycle total = sum(input_pin.state for i in range(20000)) assert isclose(total / 20000, duty_cycle, rel_tol=0.1, abs_tol=0.1) finally: test_pin.frequency = None gpiozero-1.4.1/tests/test_outputs.py0000644000175000017500000012177513243115414017544 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import sys from time import sleep, time try: from math import isclose except ImportError: from gpiozero.compat import isclose import pytest from gpiozero.pins.mock import MockPin, MockPWMPin from gpiozero import * def setup_function(function): # dirty, but it does the job Device.pin_factory.pin_class = MockPWMPin if function.__name__ in ( 'test_output_pwm_states', 'test_output_pwm_read', 'test_output_pwm_write', 'test_output_pwm_toggle', 'test_output_pwm_active_high_read', 'test_output_pwm_bad_value', 'test_output_pwm_write_closed', 'test_output_pwm_write_silly', 'test_output_pwm_blink_background', 'test_output_pwm_blink_foreground', 'test_output_pwm_fade_background', 'test_output_pwm_fade_foreground', 'test_output_pwm_pulse_background', 'test_output_pwm_pulse_foreground', 'test_output_pwm_blink_interrupt', 'test_rgbled_initial_value', 'test_rgbled_initial_bad_value', 'test_rgbled_value', 'test_rgbled_bad_value', 'test_rgbled_toggle', 'test_rgbled_blink_background', 'test_rgbled_blink_foreground', 'test_rgbled_fade_background', 'test_rgbled_fade_foreground', 'test_rgbled_pulse_background', 'test_rgbled_pulse_foreground', 'test_rgbled_blink_interrupt', 'test_rgbled_close', 'test_motor_pins', 'test_motor_close', 'test_motor_value', 'test_motor_bad_value', 'test_motor_reverse', 'test_phaseenable_motor_pins', 'test_phaseenable_motor_close', 'test_phaseenable_motor_value', 'test_phaseenable_motor_bad_value', 'test_phaseenable_motor_reverse', 'test_servo_pins', 'test_servo_bad_value', 'test_servo_close', 'test_servo_pulse_width', 'test_servo_values', 'test_servo_initial_values', 'test_angular_servo_range', 'test_angular_servo_angles', 'test_angular_servo_initial_angles', ) else MockPin def teardown_function(function): Device.pin_factory.reset() def test_output_initial_values(): pin = Device.pin_factory.pin(2) with OutputDevice(2, initial_value=False) as device: assert pin.function == 'output' assert not pin.state with OutputDevice(2, initial_value=True) as device: assert pin.state state = pin.state with OutputDevice(2, initial_value=None) as device: assert state == pin.state def test_output_write_active_high(): pin = Device.pin_factory.pin(2) with OutputDevice(2) as device: device.on() assert pin.state device.off() assert not pin.state def test_output_write_active_low(): pin = Device.pin_factory.pin(2) with OutputDevice(2, active_high=False) as device: device.on() assert not pin.state device.off() assert pin.state def test_output_write_closed(): with OutputDevice(2) as device: device.close() assert device.closed device.close() assert device.closed with pytest.raises(GPIODeviceClosed): device.on() def test_output_write_silly(): pin = Device.pin_factory.pin(2) with OutputDevice(2) as device: pin.function = 'input' with pytest.raises(AttributeError): device.on() def test_output_value(): pin = Device.pin_factory.pin(2) with OutputDevice(2) as device: assert not device.value assert not pin.state device.on() assert device.value assert pin.state device.value = False assert not device.value assert not pin.state def test_output_digital_toggle(): pin = Device.pin_factory.pin(2) with DigitalOutputDevice(2) as device: assert not device.value assert not pin.state device.toggle() assert device.value assert pin.state device.toggle() assert not device.value assert not pin.state @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_blink_background(): pin = Device.pin_factory.pin(4) with DigitalOutputDevice(4) as device: start = time() device.blink(0.1, 0.1, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() # naughty, but ensures no arbitrary waits in the test assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, False), (0.0, True), (0.1, False), (0.1, True), (0.1, False) ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_blink_foreground(): pin = Device.pin_factory.pin(4) with DigitalOutputDevice(4) as device: start = time() device.blink(0.1, 0.1, n=2, background=False) assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, False), (0.0, True), (0.1, False), (0.1, True), (0.1, False) ]) def test_output_blink_interrupt_on(): pin = Device.pin_factory.pin(4) with DigitalOutputDevice(4) as device: device.blink(1, 0.1) sleep(0.2) device.off() # should interrupt while on pin.assert_states([False, True, False]) def test_output_blink_interrupt_off(): pin = Device.pin_factory.pin(4) with DigitalOutputDevice(4) as device: device.blink(0.1, 1) sleep(0.2) device.off() # should interrupt while off pin.assert_states([False, True, False]) def test_output_pwm_bad_initial_value(): with pytest.raises(ValueError): PWMOutputDevice(2, initial_value=2) def test_output_pwm_not_supported(): with pytest.raises(AttributeError): PWMOutputDevice(2) def test_output_pwm_states(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: device.value = 0.1 device.value = 0.2 device.value = 0.0 pin.assert_states([0.0, 0.1, 0.2, 0.0]) def test_output_pwm_read(): pin = Device.pin_factory.pin(2) with PWMOutputDevice(2, frequency=100) as device: assert device.frequency == 100 device.value = 0.1 assert isclose(device.value, 0.1) assert isclose(pin.state, 0.1) assert device.is_active device.frequency = None assert not device.value assert not device.is_active assert device.frequency is None def test_output_pwm_write(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: device.on() device.off() pin.assert_states([False, True, False]) def test_output_pwm_toggle(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: device.toggle() device.value = 0.5 device.value = 0.1 device.toggle() device.off() pin.assert_states([False, True, 0.5, 0.1, 0.9, False]) def test_output_pwm_active_high_read(): pin = Device.pin_factory.pin(2) with PWMOutputDevice(2, active_high=False) as device: device.value = 0.1 assert isclose(device.value, 0.1) assert isclose(pin.state, 0.9) device.frequency = None assert device.value def test_output_pwm_bad_value(): pin = Device.pin_factory.pin(2) with PWMOutputDevice(2) as device: with pytest.raises(ValueError): device.value = 2 def test_output_pwm_write_closed(): pin = Device.pin_factory.pin(2) with PWMOutputDevice(2) as device: device.close() with pytest.raises(GPIODeviceClosed): device.on() def test_output_pwm_write_silly(): pin = Device.pin_factory.pin(2) with PWMOutputDevice(2) as device: pin.function = 'input' with pytest.raises(AttributeError): device.off() @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_blink_background(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: start = time() device.blink(0.1, 0.1, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_blink_foreground(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: start = time() device.blink(0.1, 0.1, n=2, background=False) assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_fade_background(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: start = time() device.blink(0, 0, 0.2, 0.2, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_fade_foreground(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: start = time() device.blink(0, 0, 0.2, 0.2, n=2, background=False) assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_pulse_background(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: start = time() device.pulse(0.2, 0.2, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_pulse_foreground(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: start = time() device.pulse(0.2, 0.2, n=2, background=False) assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ]) def test_output_pwm_blink_interrupt(): pin = Device.pin_factory.pin(4) with PWMOutputDevice(4) as device: device.blink(1, 0.1) sleep(0.2) device.off() # should interrupt while on pin.assert_states([0, 1, 0]) def test_rgbled_missing_pins(): with pytest.raises(ValueError): RGBLED() def test_rgbled_initial_value(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, initial_value=(0.1, 0.2, 0)) as device: assert r.frequency assert g.frequency assert b.frequency assert isclose(r.state, 0.1) assert isclose(g.state, 0.2) assert isclose(b.state, 0.0) def test_rgbled_initial_value_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False, initial_value=(0, 1, 1)) as device: assert r.state == 0 assert g.state == 1 assert b.state == 1 def test_rgbled_initial_bad_value(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with pytest.raises(ValueError): RGBLED(1, 2, 3, initial_value=(0.1, 0.2, 1.2)) def test_rgbled_initial_bad_value_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with pytest.raises(ValueError): RGBLED(1, 2, 3, pwm=False, initial_value=(0.1, 0.2, 0)) def test_rgbled_value(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3) as device: assert isinstance(device._leds[0], PWMLED) assert isinstance(device._leds[1], PWMLED) assert isinstance(device._leds[2], PWMLED) assert not device.is_active assert device.value == (0, 0, 0) device.on() assert device.is_active assert device.value == (1, 1, 1) device.off() assert not device.is_active assert device.value == (0, 0, 0) device.value = (0.5, 0.5, 0.5) assert device.is_active assert device.value == (0.5, 0.5, 0.5) def test_rgbled_value_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False) as device: assert isinstance(device._leds[0], LED) assert isinstance(device._leds[1], LED) assert isinstance(device._leds[2], LED) assert not device.is_active assert device.value == (0, 0, 0) device.on() assert device.is_active assert device.value == (1, 1, 1) device.off() assert not device.is_active assert device.value == (0, 0, 0) def test_rgbled_bad_value(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3) as device: with pytest.raises(ValueError): device.value = (2, 0, 0) with RGBLED(1, 2, 3) as device: with pytest.raises(ValueError): device.value = (0, -1, 0) def test_rgbled_bad_value_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.value = (2, 0, 0) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.value = (0, -1, 0) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.value = (0.5, 0, 0) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.value = (0, 0.5, 0) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.value = (0, 0, 0.5) def test_rgbled_toggle(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3) as device: assert not device.is_active assert device.value == (0, 0, 0) device.toggle() assert device.is_active assert device.value == (1, 1, 1) device.toggle() assert not device.is_active assert device.value == (0, 0, 0) def test_rgbled_toggle_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False) as device: assert not device.is_active assert device.value == (0, 0, 0) device.toggle() assert device.is_active assert device.value == (1, 1, 1) device.toggle() assert not device.is_active assert device.value == (0, 0, 0) def test_rgbled_blink_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.blink(fade_in_time=1) with pytest.raises(ValueError): device.blink(fade_out_time=1) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_background(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3) as device: start = time() device.blink(0.1, 0.1, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_background_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3, pwm=False) as device: start = time() device.blink(0.1, 0.1, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_foreground(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3) as device: start = time() device.blink(0.1, 0.1, n=2, background=False) assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_foreground_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3, pwm=False) as device: start = time() device.blink(0.1, 0.1, n=2, background=False) assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_fade_background(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3) as device: start = time() device.blink(0, 0, 0.2, 0.2, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.8, abs_tol=0.05) expected = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) def test_rgbled_fade_background_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.blink(0, 0, 0.2, 0, n=2) with pytest.raises(ValueError): device.blink(0, 0, 0, 0.2, n=2) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_fade_foreground(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3) as device: start = time() device.blink(0, 0, 0.2, 0.2, n=2, background=False) assert isclose(time() - start, 0.8, abs_tol=0.05) expected = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) def test_rgbled_fade_foreground_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.blink(0, 0, 0.2, 0.2, n=2, background=False) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_pulse_background(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3) as device: start = time() device.pulse(0.2, 0.2, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.8, abs_tol=0.05) expected = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) def test_rgbled_pulse_background_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.pulse(0.2, 0.2, n=2) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_pulse_foreground(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3) as device: start = time() device.pulse(0.2, 0.2, n=2, background=False) assert isclose(time() - start, 0.8, abs_tol=0.05) expected = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) def test_rgbled_pulse_foreground_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False) as device: with pytest.raises(ValueError): device.pulse(0.2, 0.2, n=2, background=False) def test_rgbled_blink_interrupt(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3) as device: device.blink(1, 0.1) sleep(0.2) device.off() # should interrupt while on r.assert_states([0, 1, 0]) g.assert_states([0, 1, 0]) b.assert_states([0, 1, 0]) def test_rgbled_blink_interrupt_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6)) with RGBLED(1, 2, 3, pwm=False) as device: device.blink(1, 0.1) sleep(0.2) device.off() # should interrupt while on r.assert_states([0, 1, 0]) g.assert_states([0, 1, 0]) b.assert_states([0, 1, 0]) def test_rgbled_close(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3) as device: assert not device.closed device.close() assert device.closed device.close() assert device.closed def test_rgbled_close_nonpwm(): r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3)) with RGBLED(1, 2, 3, pwm=False) as device: assert not device.closed device.close() assert device.closed device.close() assert device.closed def test_motor_missing_pins(): with pytest.raises(ValueError): Motor() def test_motor_pins(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2) as device: assert device.forward_device.pin is f assert isinstance(device.forward_device, PWMOutputDevice) assert device.backward_device.pin is b assert isinstance(device.backward_device, PWMOutputDevice) def test_motor_pins_nonpwm(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2, pwm=False) as device: assert device.forward_device.pin is f assert isinstance(device.forward_device, DigitalOutputDevice) assert device.backward_device.pin is b assert isinstance(device.backward_device, DigitalOutputDevice) def test_motor_close(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2) as device: device.close() assert device.closed assert device.forward_device.pin is None assert device.backward_device.pin is None device.close() assert device.closed def test_motor_close_nonpwm(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2, pwm=False) as device: device.close() assert device.closed assert device.forward_device.pin is None assert device.backward_device.pin is None def test_motor_value(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2) as device: device.value = -1 assert device.is_active assert device.value == -1 assert b.state == 1 and f.state == 0 device.value = 1 assert device.is_active assert device.value == 1 assert b.state == 0 and f.state == 1 device.value = 0.5 assert device.is_active assert device.value == 0.5 assert b.state == 0 and f.state == 0.5 device.value = -0.5 assert device.is_active assert device.value == -0.5 assert b.state == 0.5 and f.state == 0 device.value = 0 assert not device.is_active assert not device.value assert b.state == 0 and f.state == 0 def test_motor_value_nonpwm(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2, pwm=False) as device: device.value = -1 assert device.is_active assert device.value == -1 assert b.state == 1 and f.state == 0 device.value = 1 assert device.is_active assert device.value == 1 assert b.state == 0 and f.state == 1 device.value = 0 assert not device.is_active assert not device.value assert b.state == 0 and f.state == 0 def test_motor_bad_value(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2) as device: with pytest.raises(ValueError): device.value = -2 with pytest.raises(ValueError): device.value = 2 with pytest.raises(ValueError): device.forward(2) with pytest.raises(ValueError): device.backward(2) with pytest.raises(ValueError): device.forward(-1) with pytest.raises(ValueError): device.backward(-1) def test_motor_bad_value_nonpwm(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2, pwm=False) as device: with pytest.raises(ValueError): device.value = -2 with pytest.raises(ValueError): device.value = 2 with pytest.raises(ValueError): device.value = 0.5 with pytest.raises(ValueError): device.value = -0.5 with pytest.raises(ValueError): device.forward(0.5) with pytest.raises(ValueError): device.backward(0.5) def test_motor_reverse(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2) as device: device.forward() assert device.value == 1 assert b.state == 0 and f.state == 1 device.reverse() assert device.value == -1 assert b.state == 1 and f.state == 0 device.backward(0.5) assert device.value == -0.5 assert b.state == 0.5 and f.state == 0 device.reverse() assert device.value == 0.5 assert b.state == 0 and f.state == 0.5 def test_motor_reverse_nonpwm(): f = Device.pin_factory.pin(1) b = Device.pin_factory.pin(2) with Motor(1, 2, pwm=False) as device: device.forward() assert device.value == 1 assert b.state == 0 and f.state == 1 device.reverse() assert device.value == -1 assert b.state == 1 and f.state == 0 def test_phaseenable_motor_pins(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2) as device: assert device.phase_device.pin is p assert isinstance(device.phase_device, OutputDevice) assert device.enable_device.pin is e assert isinstance(device.enable_device, PWMOutputDevice) def test_phaseenable_motor_pins_nonpwm(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2, pwm=False) as device: assert device.phase_device.pin is p assert isinstance(device.phase_device, OutputDevice) assert device.enable_device.pin is e assert isinstance(device.enable_device, DigitalOutputDevice) def test_phaseenable_motor_close(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2) as device: device.close() assert device.closed assert device.phase_device.pin is None assert device.enable_device.pin is None device.close() assert device.closed def test_phaseenable_motor_close_nonpwm(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2, pwm=False) as device: device.close() assert device.closed assert device.phase_device.pin is None assert device.enable_device.pin is None def test_phaseenable_motor_value(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2) as device: device.value = -1 assert device.is_active assert device.value == -1 assert p.state == 1 and e.state == 1 device.value = 1 assert device.is_active assert device.value == 1 assert p.state == 0 and e.state == 1 device.value = 0.5 assert device.is_active assert device.value == 0.5 assert p.state == 0 and e.state == 0.5 device.value = -0.5 assert device.is_active assert device.value == -0.5 assert p.state == 1 and e.state == 0.5 device.value = 0 assert not device.is_active assert not device.value assert e.state == 0 def test_phaseenable_motor_value_nonpwm(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2, pwm=False) as device: device.value = -1 assert device.is_active assert device.value == -1 assert p.state == 1 and e.state == 1 device.value = 1 assert device.is_active assert device.value == 1 assert p.state == 0 and e.state == 1 device.value = 0 assert not device.is_active assert not device.value assert e.state == 0 def test_phaseenable_motor_bad_value(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2) as device: with pytest.raises(ValueError): device.value = -2 with pytest.raises(ValueError): device.value = 2 with pytest.raises(ValueError): device.forward(2) with pytest.raises(ValueError): device.backward(2) def test_phaseenable_motor_bad_value_nonpwm(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2, pwm=False) as device: with pytest.raises(ValueError): device.value = -2 with pytest.raises(ValueError): device.value = 2 with pytest.raises(ValueError): device.value = 0.5 with pytest.raises(ValueError): device.value = -0.5 def test_phaseenable_motor_reverse(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2) as device: device.forward() assert device.value == 1 assert p.state == 0 and e.state == 1 device.reverse() assert device.value == -1 assert p.state == 1 and e.state == 1 device.backward(0.5) assert device.value == -0.5 assert p.state == 1 and e.state == 0.5 device.reverse() assert device.value == 0.5 assert p.state == 0 and e.state == 0.5 def test_phaseenable_motor_reverse_nonpwm(): p = Device.pin_factory.pin(1) e = Device.pin_factory.pin(2) with PhaseEnableMotor(1, 2, pwm=False) as device: device.forward() assert device.value == 1 assert p.state == 0 and e.state == 1 device.reverse() assert device.value == -1 assert p.state == 1 and e.state == 1 def test_servo_pins(): p = Device.pin_factory.pin(1) with Servo(1) as device: assert device.pwm_device.pin is p assert isinstance(device.pwm_device, PWMOutputDevice) def test_servo_bad_value(): p = Device.pin_factory.pin(1) with pytest.raises(ValueError): Servo(1, initial_value=2) with pytest.raises(ValueError): Servo(1, min_pulse_width=30/1000) with pytest.raises(ValueError): Servo(1, max_pulse_width=30/1000) def test_servo_pins_nonpwm(): p = Device.pin_factory.pin(2) with pytest.raises(PinPWMUnsupported): Servo(1) def test_servo_close(): p = Device.pin_factory.pin(2) with Servo(2) as device: device.close() assert device.closed assert device.pwm_device.pin is None device.close() assert device.closed def test_servo_pulse_width(): p = Device.pin_factory.pin(2) with Servo(2, min_pulse_width=5/10000, max_pulse_width=25/10000) as device: assert isclose(device.min_pulse_width, 5/10000) assert isclose(device.max_pulse_width, 25/10000) assert isclose(device.frame_width, 20/1000) assert isclose(device.pulse_width, 15/10000) device.value = -1 assert isclose(device.pulse_width, 5/10000) device.value = 1 assert isclose(device.pulse_width, 25/10000) device.value = None assert device.pulse_width is None def test_servo_initial_values(): p = Device.pin_factory.pin(2) with Servo(2) as device: assert device.value == 0 with Servo(2, initial_value=-1) as device: assert device.is_active assert device.value == -1 assert isclose(p.state, 0.05) with Servo(2, initial_value=0) as device: assert device.is_active assert device.value == 0 assert isclose(p.state, 0.075) with Servo(2, initial_value=1) as device: assert device.is_active assert device.value == 1 assert isclose(p.state, 0.1) with Servo(2, initial_value=None) as device: assert not device.is_active assert device.value is None def test_servo_values(): p = Device.pin_factory.pin(1) with Servo(1) as device: device.min() assert device.is_active assert device.value == -1 assert isclose(p.state, 0.05) device.max() assert device.is_active assert device.value == 1 assert isclose(p.state, 0.1) device.mid() assert device.is_active assert device.value == 0.0 assert isclose(p.state, 0.075) device.value = 0.5 assert device.is_active assert device.value == 0.5 assert isclose(p.state, 0.0875) device.detach() assert not device.is_active assert device.value is None device.value = 0 assert device.value == 0 device.value = None assert device.value is None def test_angular_servo_range(): p = Device.pin_factory.pin(1) with AngularServo(1, initial_angle=15, min_angle=0, max_angle=90) as device: assert device.min_angle == 0 assert device.max_angle == 90 def test_angular_servo_initial_angles(): p = Device.pin_factory.pin(1) with AngularServo(1) as device: assert device.angle == 0 with AngularServo(1, initial_angle=-90) as device: assert device.angle == -90 assert isclose(device.value, -1) with AngularServo(1, initial_angle=0) as device: assert device.angle == 0 assert isclose(device.value, 0) with AngularServo(1, initial_angle=90) as device: assert device.angle == 90 assert isclose(device.value, 1) with AngularServo(1, initial_angle=None) as device: assert device.angle is None def test_angular_servo_angles(): p = Device.pin_factory.pin(1) with AngularServo(1) as device: device.angle = 0 assert device.angle == 0 assert isclose(device.value, 0) device.max() assert device.angle == 90 assert isclose(device.value, 1) device.min() assert device.angle == -90 assert isclose(device.value, -1) device.detach() assert device.angle is None with AngularServo(1, initial_angle=15, min_angle=0, max_angle=90) as device: assert device.angle == 15 assert isclose(device.value, -2/3) device.angle = 0 assert device.angle == 0 assert isclose(device.value, -1) device.angle = 90 assert device.angle == 90 assert isclose(device.value, 1) device.angle = None assert device.angle is None with AngularServo(1, min_angle=45, max_angle=-45) as device: assert device.angle == 0 assert isclose(device.value, 0) device.angle = -45 assert device.angle == -45 assert isclose(device.value, 1) device.angle = -15 assert device.angle == -15 assert isclose(device.value, 1/3) gpiozero-1.4.1/tests/test_compat.py0000644000175000017500000000740013165454031017274 0ustar davedave00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import pytest import random from gpiozero.compat import * # ported from the official test cases; see # https://github.com/python/cpython/blob/master/Lib/test/test_math.py for original NAN = float('nan') INF = float('inf') NINF = float('-inf') def test_isclose_negative_tolerances(): with pytest.raises(ValueError): isclose(1, 1, rel_tol=-1e-100) with pytest.raises(ValueError): isclose(1, 1, rel_tol=1e-100, abs_tol=-1e10) def test_isclose_identical(): examples = [ (2.0, 2.0), (0.1e200, 0.1e200), (1.123e-300, 1.123e-300), (12345, 12345.0), (0.0, -0.0), (345678, 345678), ] for a, b in examples: assert isclose(a, b, rel_tol=0.0, abs_tol=0.0) def test_isclose_eight_decimals(): examples = [ (1e8, 1e8 + 1), (-1e-8, -1.000000009e-8), (1.12345678, 1.12345679), ] for a, b in examples: assert isclose(a, b, rel_tol=1e-8) assert not isclose(a, b, rel_tol=1e-9) def test_isclose_near_zero(): examples = [1e-9, -1e-9, -1e-150] for a in examples: assert not isclose(a, 0.0, rel_tol=0.9) assert isclose(a, 0.0, abs_tol=1e-8) def test_isclose_inf(): assert isclose(INF, INF) assert isclose(INF, INF, abs_tol=0.0) assert isclose(NINF, NINF) assert isclose(NINF, NINF, abs_tol=0.0) def test_isclose_inf_ninf_nan(): examples = [ (NAN, NAN), (NAN, 1e-100), (1e-100, NAN), (INF, NAN), (NAN, INF), (INF, NINF), (INF, 1.0), (1.0, INF), (INF, 1e308), (1e308, INF), ] for a, b in examples: assert not isclose(a, b, abs_tol=0.999999999999999) def test_isclose_zero_tolerance(): examples = [ (1.0, 1.0), (-3.4, -3.4), (-1e-300, -1e-300), ] for a, b in examples: assert isclose(a, b, rel_tol=0.0) examples = [ (1.0, 1.000000000000001), (0.99999999999999, 1.0), (1.0e200, .999999999999999e200), ] for a, b in examples: assert not isclose(a, b, rel_tol=0.0) def test_isclose_assymetry(): assert isclose(9, 10, rel_tol=0.1) assert isclose(10, 9, rel_tol=0.1) def test_isclose_integers(): examples = [ (100000001, 100000000), (123456789, 123456788), ] for a, b in examples: assert isclose(a, b, rel_tol=1e-8) assert not isclose(a, b, rel_tol=1e-9) # ported from the official test cases; see # https://github.com/python/cpython/blob/master/Lib/test/test_statistics.py for # original def test_mean(): examples = [ (4.8125, (0, 1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 7, 7, 7, 8, 9)), (22.015625, (17.25, 19.75, 20.0, 21.5, 21.75, 23.25, 25.125, 27.5)), (INF, (1, 3, 5, 7, 9, INF)), (NINF, (1, 3, 5, 7, 9, NINF)), ] for result, values in examples: values = list(values) random.shuffle(values) assert mean(values) == result assert mean(iter(values)) == result def test_mean_big_data(): c = 1e9 data = [3.4, 4.5, 4.9, 6.7, 6.8, 7.2, 8.0, 8.1, 9.4] expected = mean(data) + c assert expected != c assert mean([x + c for x in data]) == expected def test_mean_doubled_data(): data = [random.uniform(-3, 5) for _ in range(1000)] expected = mean(data) actual = mean(data * 2) assert isclose(expected, actual) def test_mean_empty(): with pytest.raises(ValueError): mean(()) def test_median(): assert median([1, 2, 3, 4, 5, 6]) == 3.5 assert median([1, 2, 3, 4, 5, 6, 9]) == 4 def test_median_empty(): with pytest.raises(ValueError): median(()) gpiozero-1.4.1/gpiozero.egg-info/0000755000175000017500000000000013243117413016562 5ustar davedave00000000000000gpiozero-1.4.1/gpiozero.egg-info/requires.txt0000644000175000017500000000005313243117412021157 0ustar davedave00000000000000 [doc] sphinx [test] pytest coverage mock gpiozero-1.4.1/gpiozero.egg-info/PKG-INFO0000644000175000017500000001301713243117412017660 0ustar davedave00000000000000Metadata-Version: 1.1 Name: gpiozero Version: 1.4.1 Summary: UNKNOWN Home-page: https://github.com/RPi-Distro/python-gpiozero Author: Ben Nuttall Author-email: ben@raspberrypi.org License: BSD License Description-Content-Type: UNKNOWN Description: ======== gpiozero ======== .. image:: https://badge.fury.io/py/gpiozero.svg :target: https://badge.fury.io/py/gpiozero :alt: Latest Version .. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master :target: https://travis-ci.org/RPi-Distro/python-gpiozero :alt: Build Tests .. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000 :target: https://codecov.io/github/RPi-Distro/python-gpiozero :alt: Code Coverage A simple interface to GPIO devices with Raspberry Pi. Created by `Ben Nuttall`_ of the `Raspberry Pi Foundation`_, `Dave Jones`_, and other contributors. About ===== Component interfaces are provided to allow a frictionless way to get started with physical computing: .. code:: python from gpiozero import LED from time import sleep led = LED(17) while True: led.on() sleep(1) led.off() sleep(1) With very little code, you can quickly get going connecting your components together: .. code:: python from gpiozero import LED, Button from signal import pause led = LED(17) button = Button(3) button.when_pressed = led.on button.when_released = led.off pause() The library includes interfaces to many simple everyday components, as well as some more complex things like sensors, analogue-to-digital converters, full colour LEDs, robotics kits and more. See the `Recipes`_ chapter of the documentation for ideas on how to get started. Installation ============ GPIO Zero is installed by default in the Raspbian desktop image, available from `raspberrypi.org`_. To install on Raspbian Lite or other operating systems, including for PCs using remote GPIO, see the `Installing`_ chapter. Documentation ============= Comprehensive documentation is available at https://gpiozero.readthedocs.io/. Please refer to the `Contributing`_ and `Development`_ chapters in the documentation for information on contributing to the project. Contributors ============ Core developers: - `Ben Nuttall`_ - `Dave Jones`_ - `Andrew Scheller`_ Other contributors: - `Martin O'Hanlon`_ - `Steve Amor`_ - `David Glaude`_ - `Edward Betts`_ - `Alex Chan`_ - `Thijs Triemstra`_ - `Schelto vanDoorn`_ - `Alex Eames`_ - `Barry Byford`_ - `Clare Macrae`_ - `Tim Golden`_ - `Phil Howard`_ - `Stewart Adcock`_ - `Ian Harcombe`_ .. _Raspberry Pi Foundation: https://www.raspberrypi.org/ .. _raspberrypi.org: https://www.raspberrypi.org/downloads/ .. _Recipes: https://gpiozero.readthedocs.io/en/stable/recipes.html .. _Contributing: https://gpiozero.readthedocs.io/en/stable/contributing.html .. _Development: https://gpiozero.readthedocs.io/en/stable/development.html .. _Installing: https://gpiozero.readthedocs.io/en/stable/installing.html .. _Ben Nuttall: https://github.com/bennuttall .. _Dave Jones: https://github.com/waveform80 .. _Andrew Scheller: https://github.com/lurch .. _Martin O'Hanlon: https://github.com/martinohanlon .. _Steve Amor: https://github.com/SteveAmor .. _David Glaude: https://github.com/dglaude .. _Edward Betts: https://github.com/edwardbetts .. _Alex Chan: https://github.com/alexwlchan .. _Thijs Triemstra: https://github.com/thijstriemstra .. _Schelto vanDoorn: https://github.com/goloplo .. _Alex Eames: https://github.com/raspitv .. _Barry Byford: https://github.com/ukBaz .. _Clare Macrae: https://github.com/claremacrae .. _Tim Golden: https://github.com/tjguk .. _Phil Howard: https://github.com/Gadgetoid .. _Stewart Adcock: https://github.com/stewartadcock .. _Ian Harcombe: https://github.com/MrHarcombe Keywords: raspberrypi,gpio Platform: ALL Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Education Classifier: Intended Audience :: Developers Classifier: Topic :: Education Classifier: Topic :: System :: Hardware Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: PyPy gpiozero-1.4.1/gpiozero.egg-info/SOURCES.txt0000644000175000017500000000203413243117413020445 0ustar davedave00000000000000LICENCE.txt MANIFEST.in README.rst setup.py gpiozero/__init__.py gpiozero/boards.py gpiozero/compat.py gpiozero/devices.py gpiozero/exc.py gpiozero/input_devices.py gpiozero/mixins.py gpiozero/other_devices.py gpiozero/output_devices.py gpiozero/spi_devices.py gpiozero/threads.py gpiozero/tools.py gpiozero.egg-info/PKG-INFO gpiozero.egg-info/SOURCES.txt gpiozero.egg-info/dependency_links.txt gpiozero.egg-info/entry_points.txt gpiozero.egg-info/requires.txt gpiozero.egg-info/top_level.txt gpiozero/pins/__init__.py gpiozero/pins/data.py gpiozero/pins/local.py gpiozero/pins/mock.py gpiozero/pins/native.py gpiozero/pins/pi.py gpiozero/pins/pigpio.py gpiozero/pins/rpigpio.py gpiozero/pins/rpio.py gpiozero/pins/spi.py gpiozerocli/__init__.py gpiozerocli/pinout.py tests/conftest.py tests/test_boards.py tests/test_compat.py tests/test_devices.py tests/test_inputs.py tests/test_mock_pin.py tests/test_outputs.py tests/test_pins_data.py tests/test_real_pins.py tests/test_spi.py tests/test_spi_devices.py tests/test_tools.py tests/cli/test_pinout.pygpiozero-1.4.1/gpiozero.egg-info/top_level.txt0000644000175000017500000000002513243117412021310 0ustar davedave00000000000000gpiozero gpiozerocli gpiozero-1.4.1/gpiozero.egg-info/entry_points.txt0000644000175000017500000000125713243117412022064 0ustar davedave00000000000000[console_scripts] pinout = gpiozerocli.pinout:main [gpiozero_mock_pin_classes] mockchargingpin = gpiozero.pins.mock:MockChargingPin mockpin = gpiozero.pins.mock:MockPin mockpwmpin = gpiozero.pins.mock:MockPWMPin mocktriggerpin = gpiozero.pins.mock:MockTriggerPin [gpiozero_pin_factories] NativePin = gpiozero.pins.native:NativeFactory PiGPIOPin = gpiozero.pins.pigpio:PiGPIOFactory RPIOPin = gpiozero.pins.rpio:RPIOFactory RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOFactory mock = gpiozero.pins.mock:MockFactory native = gpiozero.pins.native:NativeFactory pigpio = gpiozero.pins.pigpio:PiGPIOFactory rpigpio = gpiozero.pins.rpigpio:RPiGPIOFactory rpio = gpiozero.pins.rpio:RPIOFactory gpiozero-1.4.1/gpiozero.egg-info/dependency_links.txt0000644000175000017500000000000113243117412022627 0ustar davedave00000000000000 gpiozero-1.4.1/MANIFEST.in0000644000175000017500000000010413165454031014766 0ustar davedave00000000000000include README.rst recursive-include tests *.py include LICENCE.txt gpiozero-1.4.1/setup.py0000644000175000017500000000770413243115421014751 0ustar davedave00000000000000import os import sys from setuptools import setup, find_packages "A simple interface to GPIO devices with Raspberry Pi." if sys.version_info[0] == 2: if not sys.version_info >= (2, 7): raise ValueError('This package requires Python 2.7 or above') elif sys.version_info[0] == 3: if not sys.version_info >= (3, 2): raise ValueError('This package requires Python 3.2 or above') else: raise ValueError('Unrecognized major version of Python') HERE = os.path.abspath(os.path.dirname(__file__)) # Workaround try: import multiprocessing except ImportError: pass __project__ = 'gpiozero' __version__ = '1.4.1' __author__ = 'Ben Nuttall' __author_email__ = 'ben@raspberrypi.org' __url__ = 'https://github.com/RPi-Distro/python-gpiozero' __platforms__ = 'ALL' __classifiers__ = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Education", "Intended Audience :: Developers", "Topic :: Education", "Topic :: System :: Hardware", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: PyPy", ] __keywords__ = [ 'raspberrypi', 'gpio', ] __requires__ = [ ] __extra_requires__ = { 'doc': ['sphinx'], 'test': ['pytest', 'coverage', 'mock'], } if sys.version_info[:2] == (3, 2): # Particular versions are required for Python 3.2 compatibility __extra_requires__['doc'].extend([ 'Jinja2<2.7', 'MarkupSafe<0.16', ]) __extra_requires__['test'][1] = 'coverage<4.0dev' __entry_points__ = { 'gpiozero_pin_factories': [ 'pigpio = gpiozero.pins.pigpio:PiGPIOFactory', 'rpigpio = gpiozero.pins.rpigpio:RPiGPIOFactory', 'rpio = gpiozero.pins.rpio:RPIOFactory', 'native = gpiozero.pins.native:NativeFactory', 'mock = gpiozero.pins.mock:MockFactory', # Backwards compatible names 'PiGPIOPin = gpiozero.pins.pigpio:PiGPIOFactory', 'RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOFactory', 'RPIOPin = gpiozero.pins.rpio:RPIOFactory', 'NativePin = gpiozero.pins.native:NativeFactory', ], 'gpiozero_mock_pin_classes': [ 'mockpin = gpiozero.pins.mock:MockPin', 'mockpwmpin = gpiozero.pins.mock:MockPWMPin', 'mockchargingpin = gpiozero.pins.mock:MockChargingPin', 'mocktriggerpin = gpiozero.pins.mock:MockTriggerPin', ], 'console_scripts': [ 'pinout = gpiozerocli.pinout:main', ] } def main(): import io with io.open(os.path.join(HERE, 'README.rst'), 'r') as readme: setup( name = __project__, version = __version__, description = __doc__, long_description = readme.read(), classifiers = __classifiers__, author = __author__, author_email = __author_email__, url = __url__, license = [ c.rsplit('::', 1)[1].strip() for c in __classifiers__ if c.startswith('License ::') ][0], keywords = __keywords__, packages = find_packages(), include_package_data = True, platforms = __platforms__, install_requires = __requires__, extras_require = __extra_requires__, entry_points = __entry_points__, ) if __name__ == '__main__': main()