Merge branch 'master' into docs-updates

This commit is contained in:
Ben Nuttall
2017-06-22 22:55:17 +01:00
committed by GitHub
80 changed files with 4434 additions and 2564 deletions

View File

@@ -1,2 +1,3 @@
include README.rst include README.rst
recursive-include tests *.py recursive-include tests *.py
include LICENCE.txt

View File

@@ -101,7 +101,7 @@ develop: tags
$(PIP) install -e .[doc,test] $(PIP) install -e .[doc,test]
test: test:
$(COVERAGE) run -m $(PYTEST) tests -v $(COVERAGE) run -m $(PYTEST) tests -v -r sx
$(COVERAGE) report --rcfile coverage.cfg $(COVERAGE) report --rcfile coverage.cfg
clean: clean:

View File

@@ -2,6 +2,8 @@
gpiozero gpiozero
======== ========
.. only:: builder_html
.. image:: https://badge.fury.io/py/gpiozero.svg .. image:: https://badge.fury.io/py/gpiozero.svg
:target: https://badge.fury.io/py/gpiozero :target: https://badge.fury.io/py/gpiozero
:alt: Latest Version :alt: Latest Version

6
debian/changelog vendored
View File

@@ -1,3 +1,9 @@
gpiozero (1.3.2) stable; urgency=low
* Added new Pi models to stop "pi_info" breaking
-- Ben Nuttall <ben@raspberrypi.org> Fri, 03 Mar 2017 13:18:00 +0100
gpiozero (1.3.1) stable; urgency=low gpiozero (1.3.1) stable; urgency=low
* Fixed hardware SPI support which Dave broke in 1.3.0. Sorry! * Fixed hardware SPI support which Dave broke in 1.3.0. Sorry!

37
debian/control vendored
View File

@@ -11,28 +11,30 @@ X-Python3-Version: >= 3.2
Package: python-gpiozero Package: python-gpiozero
Architecture: all Architecture: all
Section: python Section: python
Depends: ${misc:Depends}, ${python:Depends} Depends: ${misc:Depends}, ${python:Depends}, python-pkg-resources
Recommends: python-rpi.gpio, python-spidev Recommends: python-rpi.gpio, python-spidev
Suggests: python-gpiozero-docs Suggests: python-gpiozero-docs
Description: Simple API for controlling devices attached to the GPIO pins. Description: Simple API for controlling devices attached to a Pi's GPIO pins.
gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify gpiozero builds on various pin libraries to provide a set of classes designed
interaction with devices connected to the GPIO pins, from simple buttons and to simplify interaction with devices connected to the GPIO pins, from simple
LEDs, up to various add-on boards. The API tries to adhere closely to Python's buttons and LEDs, up to various add-on boards. The API tries to adhere closely
idioms and naming conventions. to Python's idioms and naming conventions, and features alternative
programming paradigm approaches.
. .
This is the Python 2 version of the package. This is the Python 2 version of the package.
Package: python3-gpiozero Package: python3-gpiozero
Architecture: all Architecture: all
Section: python Section: python
Depends: ${misc:Depends}, ${python3:Depends} Depends: ${misc:Depends}, ${python3:Depends}, python3-pkg-resources
Recommends: python3-rpi.gpio, python3-spidev Recommends: python3-rpi.gpio, python3-spidev
Suggests: python-gpiozero-docs Suggests: python-gpiozero-docs
Description: Simple API for controlling devices attached to the GPIO pins. Description: Simple API for controlling devices attached to a Pi's GPIO pins.
gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify gpiozero builds on various pin libraries to provide a set of classes designed
interaction with devices connected to the GPIO pins, from simple buttons and to simplify interaction with devices connected to the GPIO pins, from simple
LEDs, up to various add-on boards. The API tries to adhere closely to Python's buttons and LEDs, up to various add-on boards. The API tries to adhere closely
idioms and naming conventions. to Python's idioms and naming conventions, and features alternative
programming paradigm approaches.
. .
This is the Python 3 version of the package. This is the Python 3 version of the package.
@@ -40,10 +42,11 @@ Package: python-gpiozero-doc
Architecture: all Architecture: all
Section: doc Section: doc
Depends: ${sphinxdoc:Depends}, ${misc:Depends} Depends: ${sphinxdoc:Depends}, ${misc:Depends}
Description: Documentation for the gpiozero API Description: Simple API for controlling devices attached to a Pi's GPIO pins.
gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify gpiozero builds on various pin libraries to provide a set of classes designed
interaction with devices connected to the GPIO pins, from simple buttons and to simplify interaction with devices connected to the GPIO pins, from simple
LEDs, up to various add-on boards. The API tries to adhere closely to Python's buttons and LEDs, up to various add-on boards. The API tries to adhere closely
idioms and naming conventions. to Python's idioms and naming conventions, and features alternative
programming paradigm approaches.
. .
This is the version independent documentation for the package. This is the version independent documentation for the package.

View File

@@ -59,6 +59,20 @@ Errors
.. autoexception:: SPIBadArgs .. autoexception:: SPIBadArgs
.. autoexception:: SPIBadChannel
.. autoexception:: SPIFixedClockMode
.. autoexception:: SPIInvalidClockMode
.. autoexception:: SPIFixedBitOrder
.. autoexception:: SPIFixedSelect
.. autoexception:: SPIFixedWordSize
.. autoexception:: SPIInvalidWordSize
.. autoexception:: GPIODeviceError .. autoexception:: GPIODeviceError
.. autoexception:: GPIODeviceClosed .. autoexception:: GPIODeviceClosed
@@ -83,23 +97,31 @@ Errors
.. autoexception:: PinInvalidEdges .. autoexception:: PinInvalidEdges
.. autoexception:: PinInvalidBounce
.. autoexception:: PinSetInput .. autoexception:: PinSetInput
.. autoexception:: PinFixedPull .. autoexception:: PinFixedPull
.. autoexception:: PinEdgeDetectUnsupported .. autoexception:: PinEdgeDetectUnsupported
.. autoexception:: PinUnsupported
.. autoexception:: PinSPIUnsupported
.. autoexception:: PinPWMError .. autoexception:: PinPWMError
.. autoexception:: PinPWMUnsupported .. autoexception:: PinPWMUnsupported
.. autoexception:: PinPWMFixedValue .. autoexception:: PinPWMFixedValue
.. autoexception:: PinUnknownPi
.. autoexception:: PinMultiplePins .. autoexception:: PinMultiplePins
.. autoexception:: PinNoPins .. autoexception:: PinNoPins
.. autoexception:: PinUnknownPi .. autoexception:: PinInvalidPin
Warnings Warnings
======== ========
@@ -110,3 +132,7 @@ Warnings
.. autoexception:: SPISoftwareFallback .. autoexception:: SPISoftwareFallback
.. autoexception:: PinFactoryFallback
.. autoexception:: PinNonPhysical

View File

@@ -23,7 +23,8 @@ classes (most of which are documented in their corresponding chapters):
devices like HATs devices like HATs
There are also several `mixin classes`_ for adding important functionality There are also several `mixin classes`_ for adding important functionality
at numerous points in the hierarchy, which is illustrated below: at numerous points in the hierarchy, which is illustrated below (mixin classes
are represented in purple, while abstract classes are shaded lighter):
.. image:: images/device_hierarchy.* .. image:: images/device_hierarchy.*

View File

@@ -52,7 +52,8 @@ Base Classes
The classes in the sections above are derived from a series of base classes, The classes in the sections above are derived from a series of base classes,
some of which are effectively abstract. The classes form the (partial) some of which are effectively abstract. The classes form the (partial)
hierarchy displayed in the graph below: hierarchy displayed in the graph below (abstract classes are shaded lighter
than concrete classes):
.. image:: images/input_device_hierarchy.* .. image:: images/input_device_hierarchy.*

View File

@@ -35,7 +35,8 @@ Base Classes
The classes in the sections above are derived from a series of base classes, The classes in the sections above are derived from a series of base classes,
some of which are effectively abstract. The classes form the (partial) some of which are effectively abstract. The classes form the (partial)
hierarchy displayed in the graph below: hierarchy displayed in the graph below (abstract classes are shaded lighter
than concrete classes):
.. image:: images/other_device_hierarchy.* .. image:: images/other_device_hierarchy.*

View File

@@ -62,7 +62,8 @@ Base Classes
The classes in the sections above are derived from a series of base classes, The classes in the sections above are derived from a series of base classes,
some of which are effectively abstract. The classes form the (partial) some of which are effectively abstract. The classes form the (partial)
hierarchy displayed in the graph below: hierarchy displayed in the graph below (abstract classes are shaded lighter
than concrete classes):
.. image:: images/output_device_hierarchy.* .. image:: images/output_device_hierarchy.*

View File

@@ -11,68 +11,92 @@ are concerned with. However, some users may wish to take advantage of the
capabilities of alternative GPIO implementations or (in future) use GPIO capabilities of alternative GPIO implementations or (in future) use GPIO
extender chips. This is the purpose of the pins portion of the library. extender chips. This is the purpose of the pins portion of the library.
When you construct a device, you pass in a GPIO pin number. However, what the When you construct a device, you pass in a pin specification. However, what the
library actually expects is a :class:`Pin` implementation. If it finds a simple library actually expects is a :class:`Pin` implementation. If it finds anything
integer number instead, it uses one of the following classes to provide the else, it uses the existing ``Device.pin_factory`` to construct a :class:`Pin`
:class:`Pin` implementation (classes are listed in favoured order): implementation based on the specification.
1. :class:`gpiozero.pins.rpigpio.RPiGPIOPin` Changing the pin factory
========================
2. :class:`gpiozero.pins.rpio.RPIOPin` The default pin factory can be replaced by specifying a value for the
``GPIOZERO_PIN_FACTORY`` environment variable. For example:
3. :class:`gpiozero.pins.pigpiod.PiGPIOPin` .. code-block:: console
4. :class:`gpiozero.pins.native.NativePin` pi@raspberrypi $ GPIOZERO_PIN_FACTORY=native python
Python 3.4.2 (default, Oct 19 2014, 13:31:11)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import gpiozero
>>> gpiozero.Device.pin_factory
<gpiozero.pins.native.NativeFactory object at 0x762c26b0>
You can change the default pin implementation by over-writing the To set the ``GPIOZERO_PIN_FACTORY`` for the rest of your session you can
``pin_factory`` global in the ``devices`` module like so:: export this value:
from gpiozero.pins.native import NativePin .. code-block:: console
import gpiozero.devices
# Force the default pin implementation to be NativePin pi@raspberrypi $ export GPIOZERO_PIN_FACTORY=native
gpiozero.devices.pin_factory = NativePin pi@raspberrypi $ python
Python 3.4.2 (default, Oct 19 2014, 13:31:11)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import gpiozero
>>> gpiozero.Device.pin_factory
<gpiozero.pins.native.NativeFactory object at 0x762c26b0>
>>> quit()
pi@raspberrypi $ python
Python 3.4.2 (default, Oct 19 2014, 13:31:11)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import gpiozero
>>> gpiozero.Device.pin_factory
<gpiozero.pins.native.NativeFactory object at 0x76401330>
If you add the ``export`` command to your :file:`~/.bashrc` file, you'll set
the default pin factory for all future sessions too.
The following values, and the corresponding :class:`Factory` and :class:`Pin`
classes are listed in the table below. Factories are listed in the order that
they are tried by default.
+---------+-----------------------------------------------+-------------------------------------------+
| Name | Factory class | Pin class |
+=========+===============================================+===========================================+
| rpigpio | :class:`gpiozero.pins.rpigpio.RPiGPIOFactory` | :class:`gpiozero.pins.rpigpio.RPiGPIOPin` |
+---------+-----------------------------------------------+-------------------------------------------+
| rpio | :class:`gpiozero.pins.rpio.RPIOFactory` | :class:`gpiozero.pins.rpio.RPIOPin` |
+---------+-----------------------------------------------+-------------------------------------------+
| pigpio | :class:`gpiozero.pins.pigpio.PiGPIOFactory` | :class:`gpiozero.pins.pigpio.PiGPIOPin` |
+---------+-----------------------------------------------+-------------------------------------------+
| native | :class:`gpiozero.pins.native.NativeFactory` | :class:`gpiozero.pins.native.NativePin` |
+---------+-----------------------------------------------+-------------------------------------------+
If you need to change the default pin factory from within a script, set
``Device.pin_factory`` to the new factory instance to use::
from gpiozero.pins.native import NativeFactory
from gpiozero import *
Device.pin_factory = NativeFactory()
from gpiozero import LED from gpiozero import LED
# This will now use NativePin instead of RPiGPIOPin # This will now use NativePin instead of RPiGPIOPin
led = LED(16) led = LED(16)
``pin_factory`` is a concrete descendent of the abstract :class:`Pin` class. Certain factories may take default information from additional sources.
The descendent may take additional parameters in its constructor provided they
are optional; GPIO Zero will expect to be able to construct instances with
nothing more than an integer pin number.
However, the descendent may take default information from additional sources.
For example, to default to creating pins with For example, to default to creating pins with
:class:`gpiozero.pins.pigpiod.PiGPIOPin` on a remote pi called ``remote-pi`` :class:`gpiozero.pins.pigpio.PiGPIOPin` on a remote pi called ``remote-pi``
you can set the :envvar:`PIGPIO_ADDR` environment variable when running your you can set the :envvar:`PIGPIO_ADDR` environment variable when running your
script:: script:
$ PIGPIO_ADDR=remote-pi python my_script.py .. code-block:: console
It is worth noting that instead of passing an integer to device constructors, $ GPIOZERO_PIN_FACTORY=pigpio PIGPIO_ADDR=remote-pi python3 my_script.py
you can pass an object derived from :class:`Pin` itself::
from gpiozero.pins.native import NativePin Like the ``GPIOZERO_PIN_FACTORY`` value, these can be exported from your
from gpiozero import LED :file:`~/.bashrc` script too.
led = LED(NativePin(16))
In future, this separation of pins and devices should also permit the library
to utilize pins that are part of IO extender chips. For example::
from gpiozero import IOExtender, LED
ext = IOExtender()
led = LED(ext.pins[0])
led.on()
.. warning::
While the devices API is now considered stable and won't change in
backwards incompatible ways, the pins API is *not* yet considered stable.
It is potentially subject to change in future versions. We welcome any
comments from testers!
.. warning:: .. warning::
@@ -83,41 +107,64 @@ to utilize pins that are part of IO extender chips. For example::
actual raspberry pie, you have only yourself to blame. actual raspberry pie, you have only yourself to blame.
RPiGPIOPin RPi.GPIO
========== ========
.. autoclass:: gpiozero.pins.rpigpio.RPiGPIOFactory
.. autoclass:: gpiozero.pins.rpigpio.RPiGPIOPin .. autoclass:: gpiozero.pins.rpigpio.RPiGPIOPin
RPIOPin RPIO
======= ====
.. autoclass:: gpiozero.pins.rpio.RPIOFactory
.. autoclass:: gpiozero.pins.rpio.RPIOPin .. autoclass:: gpiozero.pins.rpio.RPIOPin
PiGPIOPin PiGPIO
========= ======
.. autoclass:: gpiozero.pins.pigpiod.PiGPIOPin .. autoclass:: gpiozero.pins.pigpio.PiGPIOFactory
.. autoclass:: gpiozero.pins.pigpio.PiGPIOPin
NativePin Native
========= ======
.. autoclass:: gpiozero.pins.native.NativeFactory
.. autoclass:: gpiozero.pins.native.NativePin .. autoclass:: gpiozero.pins.native.NativePin
Abstract Pin Base classes
============ ============
.. autoclass:: Factory
:members:
.. autoclass:: Pin .. autoclass:: Pin
:members: :members:
.. autoclass:: SPI
:members:
Local Pin .. currentmodule:: gpiozero.pins.pi
=========
.. autoclass:: LocalPin .. autoclass:: PiFactory
:members:
.. autoclass:: PiPin
:members:
.. currentmodule:: gpiozero.pins.local
.. autoclass:: LocalPiFactory
:members:
.. autoclass:: LocalPiPin
:members: :members:
@@ -130,6 +177,8 @@ non-physical pins are used, or to raise exceptions when pull-downs are
requested on pins with physical pull-up resistors attached. The following requested on pins with physical pull-up resistors attached. The following
functions and classes can be used to query this database: functions and classes can be used to query this database:
.. currentmodule:: gpiozero
.. autofunction:: pi_info .. autofunction:: pi_info
.. autoclass:: PiBoardInfo .. autoclass:: PiBoardInfo

View File

@@ -121,7 +121,8 @@ Base Classes
The classes in the sections above are derived from a series of base classes, The classes in the sections above are derived from a series of base classes,
some of which are effectively abstract. The classes form the (partial) some of which are effectively abstract. The classes form the (partial)
hierarchy displayed in the graph below: hierarchy displayed in the graph below (abstract classes are shaded lighter
than concrete classes):
.. image:: images/spi_device_hierarchy.* .. image:: images/spi_device_hierarchy.*

View File

@@ -5,6 +5,12 @@ Changelog
.. currentmodule:: gpiozero .. currentmodule:: gpiozero
Release 1.3.2 (2017-03-03)
==========================
* Added new Pi models to stop :func:`pi_info` breaking
* Fix issue with :func:`pi_info` breaking on unknown Pi models
Release 1.3.1 (2016-08-31 ... later) Release 1.3.1 (2016-08-31 ... later)
==================================== ====================================
@@ -55,7 +61,7 @@ Release 1.2.0 (2016-04-10)
* Added support for lots of ADC chips (MCP3xxx family) (`#162`_) - many thanks * Added support for lots of ADC chips (MCP3xxx family) (`#162`_) - many thanks
to pcopa and lurch! to pcopa and lurch!
* Added support for pigpiod as a pin implementation with * Added support for pigpiod as a pin implementation with
:class:`~gpiozero.pins.pigpiod.PiGPIOPin` (`#180`_) :class:`~gpiozero.pins.pigpio.PiGPIOPin` (`#180`_)
* Many refinements to the base classes mean more consistency in composite * Many refinements to the base classes mean more consistency in composite
devices and several bugs squashed (`#164`_, `#175`_, `#182`_, `#189`_, devices and several bugs squashed (`#164`_, `#175`_, `#182`_, `#189`_,
`#193`_, `#229`_) `#193`_, `#229`_)

26
docs/cli_tools.rst Normal file
View File

@@ -0,0 +1,26 @@
==================
Command-line Tools
==================
Pinout
======
The gpiozero package contains a database of information about the various
revisions of Raspberry Pi. This is queried by the ``pinout`` command-line
tool to output details of the GPIO pins available.
Unless specified, the revision of the current device will be detected. A
particular revision may be selected with the --revision command-line
option. For example::
pinout --revision 000d
By default, the output will include ANSI color codes if run in a color-capable
terminal. This behaviour may be overridden by the --color or --monochrome
options to force colored or non-colored output, respectively. For example::
pinout --monochrome
Full usage details are available with::
pinout --help

130
docs/development.rst Normal file
View File

@@ -0,0 +1,130 @@
===========
Development
===========
.. currentmodule:: gpiozero
The main GitHub repository for the project can be found at:
https://github.com/RPi-Distro/python-gpiozero
For anybody wishing to hack on the project, we recommend starting off by
getting to grips with some simple device classes. Pick something like
:class:`LED` and follow its heritage backward to :class:`DigitalOutputDevice`.
Follow that back to :class:`OutputDevice` and you should have a good
understanding of simple output devices along with a grasp of how GPIO Zero
relies fairly heavily upon inheritance to refine the functionality of devices.
The same can be done for input devices, and eventually more complex devices
(composites and SPI based).
.. _dev_install:
Development installation
========================
If you wish to develop GPIO Zero itself, we recommend obtaining the source by
cloning the GitHub repository and then use the "develop" target of the Makefile
which will install the package as a link to the cloned repository allowing
in-place development (it also builds a tags file for use with vim/emacs with
Exuberants ctags utility). The following example demonstrates this method
within a virtual Python environment:
.. code-block:: console
$ sudo apt-get install lsb-release build-essential git git-core \
> exuberant-ctags virtualenvwrapper python-virtualenv python3-virtualenv \
> python-dev python3-dev
$ cd
$ mkvirtualenv -p /usr/bin/python3 python-gpiozero
$ workon python-gpiozero
(python-gpiozero) $ git clone https://github.com/RPi-Distro/python-gpiozero.git
(python-gpiozero) $ cd python-gpiozero
(python-gpiozero) $ make develop
You will likely wish to install one or more pin implementations within the
virtual environment (if you don't, GPIO Zero will use the "native" pin
implementation which is largely experimental at this stage and not very
useful):
.. code-block:: console
(python-gpiozero) $ pip install rpi.gpio pigpio
If you are working on SPI devices you may also wish to install the ``spidev``
package to provide hardware SPI capabilities (again, GPIO Zero will work
without this, but a big-banging software SPI implementation will be used
instead):
.. code-block:: console
(python-gpiozero) $ pip install spidev
To pull the latest changes from git into your clone and update your
installation:
.. code-block:: console
$ workon python-gpiozero
(python-gpiozero) $ cd ~/python-gpiozero
(python-gpiozero) $ git pull
(python-gpiozero) $ make develop
To remove your installation, destroy the sandbox and the clone:
.. code-block:: console
(python-gpiozero) $ deactivate
$ rmvirtualenv python-gpiozero
$ rm -fr ~/python-gpiozero
Building the docs
=================
If you wish to build the docs, you'll need a few more dependencies. Inkscape
is used for conversion of SVGs to other formats, Graphviz is used for rendering
certain charts, and TeX Live is required for building PDF output. The following
command should install all required dependencies:
.. code-block:: console
$ sudo apt-get install texlive-latex-recommended texlive-latex-extra \
texlive-fonts-recommended graphviz inkscape
Once these are installed, you can use the "doc" target to build the
documentation:
.. code-block:: console
$ workon python-gpiozero
(python-gpiozero) $ cd ~/python-gpiozero
(python-gpiozero) $ make doc
The HTML output is written to :file:`docs/_build/html` while the PDF output
goes to :file:`docs/_build/latex`.
Test suite
==========
If you wish to run the GPIO Zero test suite, follow the instructions in
:ref:`dev_install` above and then make the "test" target within the sandbox:
.. code-block:: console
$ workon python-gpiozero
(python-gpiozero) $ cd ~/python-gpiozero
(python-gpiozero) $ make test
The test suite expects pins 22 and 27 (by default) to be wired together in
order to run the "real" pin tests. The pins used by the test suite can be
overridden with the environment variables ``GPIOZERO_TEST_PIN`` (defaults to
22) and ``GPIOZERO_TEST_INPUT_PIN`` (defaults to 27).
.. warning::
When wiring GPIOs together, ensure a load (like a 330Ω resistor) is placed
between them. Failure to do so may lead to blown GPIO pins (your humble
author has a fried GPIO27 as a result of such laziness, although it did
take *many* runs of the test suite before this occurred!).

173
docs/images/class_graph Executable file
View File

@@ -0,0 +1,173 @@
#!/usr/bin/python3
import re
import sys
import argparse
from pathlib import Path
ABSTRACT_CLASSES = {
'Device',
'GPIODevice',
'SmoothedInputDevice',
'AnalogInputDevice',
'MCP3xxx',
'MCP33xx',
'CompositeDevice',
'CompositeOutputDevice',
'LEDCollection',
'InternalDevice',
}
OMIT_CLASSES = {
'object',
'GPIOBase',
'GPIOMeta',
'frozendict',
'WeakMethod',
'_EnergenieMaster',
}
def main(args=None):
"""
A simple application for generating GPIO Zero's charts. Specify the root
class to generate with -i (multiple roots can be specified). Specify parts
of the hierarchy to exclude with -x. Output is in a format suitable for
feeding to graphviz's dot application.
"""
if args is None:
args = sys.argv[1:]
my_path = Path(__file__).parent
# XXX make this relative to repo root rather than this script
default_path = my_path / '..' / '..' / 'gpiozero'
default_path = default_path.resolve()
parser = argparse.ArgumentParser(description=main.__doc__)
parser.add_argument('-p', '--path', action='append', metavar='PATH',
default=[], help=
"search under PATH for Python source files; can be "
"specified multiple times, defaults to %s" % default_path)
parser.add_argument('-i', '--include', action='append', metavar='BASE',
default=[], help=
"only include classes which have BASE somewhere in "
"their ancestry; can be specified multiple times")
parser.add_argument('-x', '--exclude', action='append', metavar='BASE',
default=[], help=
"exclude any classes which have BASE somewhere in "
"their ancestry; can be specified multiple times")
parser.add_argument('output', nargs='?', type=argparse.FileType('w'),
default=sys.stdout, help=
"the file to write the output to; defaults to stdout")
args = parser.parse_args(args)
if not args.path:
args.path = [str(default_path)]
m = make_class_map(args.path, OMIT_CLASSES)
if args.include or args.exclude:
m = filter_map(m, include_roots=set(args.include), exclude_roots=set(args.exclude))
args.output.write(render_map(m, ABSTRACT_CLASSES))
def make_class_map(search_paths, omit):
"""
Find all Python source files under *search_paths*, extract (via a crude
regex) all class definitions and return a mapping of class-name to the list
of base classes.
All classes listed in *omit* will be excluded from the result, but not
their descendents (useful for excluding "object" etc.)
"""
def find_classes():
class_re = re.compile(r'^class (?P<name>\w+)(?:\((?P<bases>.*)\))?:', re.MULTILINE)
for path in search_paths:
for py_file in Path(path).rglob('*.py'):
with py_file.open() as f:
for match in class_re.finditer(f.read()):
if match.group('name') not in omit:
yield match.group('name'), [
base.strip()
for base in (match.group('bases') or '').split(',')
if base.strip() not in omit
]
return {
name: bases
for name, bases in find_classes()
}
def filter_map(class_map, include_roots, exclude_roots):
"""
Returns *class_map* (which is a mapping such as that returned by
:func:`make_class_map`), with only those classes which have at least one
of the *include_roots* in their ancestry, and none of the *exclude_roots*.
"""
def has_parent(cls, parent):
return cls == parent or any(
has_parent(base, parent) for base in class_map.get(cls, ()))
return {
name: bases
for name, bases in class_map.items()
if (not include_roots or any(has_parent(name, root) for root in include_roots))
and not any(has_parent(name, root) for root in exclude_roots)
}
def render_map(class_map, abstract):
"""
Renders *class_map* (which is a mapping such as that returned by
:func:`make_class_map`) to graphviz's dot language.
The *abstract* sequence determines which classes will be rendered lighter
to indicate their abstract nature. All classes with names ending "Mixin"
will be implicitly rendered in a different style.
"""
def all_names(class_map):
for name, bases in class_map.items():
yield name
for base in bases:
yield base
template = """\
digraph classes {{
graph [rankdir=RL];
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
edge [];
/* Mixin classes */
node [color="#c69ee0", fontcolor="#000000"]
{mixin_nodes}
/* Abstract classes */
node [color="#9ec6e0", fontcolor="#000000"]
{abstract_nodes}
/* Concrete classes */
node [color="#2980b9", fontcolor="#ffffff"];
{edges}
}}
"""
return template.format(
mixin_nodes='\n '.join(
'{name};'.format(name=name)
for name in set(all_names(class_map))
if name.endswith('Mixin')
),
abstract_nodes='\n '.join(
'{name};'.format(name=name)
for name in abstract & set(all_names(class_map))
),
edges='\n '.join(
'{name}->{base};'.format(name=name, base=base)
for name, bases in class_map.items()
for base in bases
),
)
if __name__ == '__main__':
main()

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,7 +1,7 @@
/* vim: set et sw=4 sts=4: */ /* vim: set et sw=4 sts=4: */
digraph classes { digraph classes {
graph [rankdir=BT]; graph [rankdir=RL];
node [shape=rect, style=filled, fontname=Sans, fontsize=10]; node [shape=rect, style=filled, fontname=Sans, fontsize=10];
edge []; edge [];
@@ -24,6 +24,7 @@ digraph classes {
PiLiter->LEDBoard; PiLiter->LEDBoard;
PiLiterBarGraph->LEDBarGraph; PiLiterBarGraph->LEDBarGraph;
TrafficLights->LEDBoard; TrafficLights->LEDBoard;
SnowPi->LEDBoard;
PiTraffic->TrafficLights; PiTraffic->TrafficLights;
PiStop->TrafficLights; PiStop->TrafficLights;
TrafficLightsBuzzer->CompositeOutputDevice; TrafficLightsBuzzer->CompositeOutputDevice;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -1,228 +1,238 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.36.0 (20140111.2315) <!-- Generated by graphviz version 2.38.0 (20140413.2041)
--> -->
<!-- Title: classes Pages: 1 --> <!-- Title: classes Pages: 1 -->
<svg width="733pt" height="476pt" <svg width="845pt" height="462pt"
viewBox="0.00 0.00 733.00 476.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> viewBox="0.00 0.00 845.00 462.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 472)"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 458)">
<title>classes</title> <title>classes</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-472 729,-472 729,4 -4,4"/> <polygon fill="white" stroke="none" points="-4,4 -4,-458 841,-458 841,4 -4,4"/>
<!-- Device --> <!-- Device -->
<g id="node1" class="node"><title>Device</title> <g id="node1" class="node"><title>Device</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="607,-468 553,-468 553,-432 607,-432 607,-468"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-117 0,-117 0,-81 54,-81 54,-117"/>
<text text-anchor="middle" x="580" y="-447.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> <text text-anchor="middle" x="27" y="-96.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
</g> </g>
<!-- CompositeDevice --> <!-- CompositeDevice -->
<g id="node2" class="node"><title>CompositeDevice</title> <g id="node2" class="node"><title>CompositeDevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="580.5,-396 479.5,-396 479.5,-360 580.5,-360 580.5,-396"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="191,-144 90,-144 90,-108 191,-108 191,-144"/>
<text text-anchor="middle" x="530" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeDevice</text> <text text-anchor="middle" x="140.5" y="-123.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeDevice</text>
</g> </g>
<!-- CompositeDevice&#45;&gt;Device --> <!-- CompositeDevice&#45;&gt;Device -->
<g id="edge1" class="edge"><title>CompositeDevice&#45;&gt;Device</title> <g id="edge1" class="edge"><title>CompositeDevice&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M542.36,-396.303C548.233,-404.526 555.369,-414.517 561.842,-423.579"/> <path fill="none" stroke="black" d="M89.9451,-114C81.1791,-111.877 72.2061,-109.705 63.9077,-107.695"/>
<polygon fill="black" stroke="black" points="559.122,-425.793 567.783,-431.896 564.818,-421.724 559.122,-425.793"/> <polygon fill="black" stroke="black" points="64.7221,-104.291 54.1792,-105.339 63.0746,-111.095 64.7221,-104.291"/>
</g> </g>
<!-- CompositeOutputDevice --> <!-- CompositeOutputDevice -->
<g id="node3" class="node"><title>CompositeOutputDevice</title> <g id="node3" class="node"><title>CompositeOutputDevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="375,-324 241,-324 241,-288 375,-288 375,-324"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="361,-252 227,-252 227,-216 361,-216 361,-252"/>
<text text-anchor="middle" x="308" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeOutputDevice</text> <text text-anchor="middle" x="294" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeOutputDevice</text>
</g> </g>
<!-- CompositeOutputDevice&#45;&gt;CompositeDevice --> <!-- CompositeOutputDevice&#45;&gt;CompositeDevice -->
<g id="edge2" class="edge"><title>CompositeOutputDevice&#45;&gt;CompositeDevice</title> <g id="edge2" class="edge"><title>CompositeOutputDevice&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M362.023,-324.034C394.615,-334.311 436.197,-347.422 469.834,-358.029"/> <path fill="none" stroke="black" d="M242.978,-215.876C237.433,-213.187 231.992,-210.223 227,-207 203.166,-191.61 180.052,-169.109 163.878,-151.725"/>
<polygon fill="black" stroke="black" points="468.958,-361.422 479.547,-361.091 471.063,-354.746 468.958,-361.422"/> <polygon fill="black" stroke="black" points="166.284,-149.169 156.958,-144.142 161.114,-153.888 166.284,-149.169"/>
</g> </g>
<!-- LEDCollection --> <!-- LEDCollection -->
<g id="node4" class="node"><title>LEDCollection</title> <g id="node4" class="node"><title>LEDCollection</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="237,-252 155,-252 155,-216 237,-216 237,-252"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="490.5,-306 408.5,-306 408.5,-270 490.5,-270 490.5,-306"/>
<text text-anchor="middle" x="196" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">LEDCollection</text> <text text-anchor="middle" x="449.5" y="-285.5" font-family="Sans" font-size="10.00" fill="#000000">LEDCollection</text>
</g> </g>
<!-- LEDCollection&#45;&gt;CompositeOutputDevice --> <!-- LEDCollection&#45;&gt;CompositeOutputDevice -->
<g id="edge3" class="edge"><title>LEDCollection&#45;&gt;CompositeOutputDevice</title> <g id="edge3" class="edge"><title>LEDCollection&#45;&gt;CompositeOutputDevice</title>
<path fill="none" stroke="black" d="M223.398,-252.124C237.907,-261.192 255.916,-272.448 271.585,-282.241"/> <path fill="none" stroke="black" d="M408.17,-273.812C392.206,-268.196 373.505,-261.618 355.956,-255.444"/>
<polygon fill="black" stroke="black" points="270.161,-285.478 280.496,-287.81 273.871,-279.542 270.161,-285.478"/> <polygon fill="black" stroke="black" points="356.945,-252.081 346.35,-252.065 354.622,-258.685 356.945,-252.081"/>
</g> </g>
<!-- LEDBoard --> <!-- LEDBoard -->
<g id="node5" class="node"><title>LEDBoard</title> <g id="node5" class="node"><title>LEDBoard</title>
<polygon fill="#2980b9" stroke="#2980b9" points="140,-180 76,-180 76,-144 140,-144 140,-180"/> <polygon fill="#2980b9" stroke="#2980b9" points="610.5,-387 546.5,-387 546.5,-351 610.5,-351 610.5,-387"/>
<text text-anchor="middle" x="108" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBoard</text> <text text-anchor="middle" x="578.5" y="-366.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBoard</text>
</g> </g>
<!-- LEDBoard&#45;&gt;LEDCollection --> <!-- LEDBoard&#45;&gt;LEDCollection -->
<g id="edge4" class="edge"><title>LEDBoard&#45;&gt;LEDCollection</title> <g id="edge4" class="edge"><title>LEDBoard&#45;&gt;LEDCollection</title>
<path fill="none" stroke="black" d="M129.753,-180.303C140.836,-189.119 154.474,-199.968 166.49,-209.526"/> <path fill="none" stroke="black" d="M548.999,-350.822C530.861,-339.254 507.182,-324.152 487.587,-311.654"/>
<polygon fill="black" stroke="black" points="164.493,-212.41 174.497,-215.896 168.85,-206.931 164.493,-212.41"/> <polygon fill="black" stroke="black" points="489.219,-308.544 478.906,-306.117 485.455,-314.446 489.219,-308.544"/>
</g> </g>
<!-- LEDBarGraph --> <!-- LEDBarGraph -->
<g id="node6" class="node"><title>LEDBarGraph</title> <g id="node6" class="node"><title>LEDBarGraph</title>
<polygon fill="#2980b9" stroke="#2980b9" points="243.5,-180 162.5,-180 162.5,-144 243.5,-144 243.5,-180"/> <polygon fill="#2980b9" stroke="#2980b9" points="619,-306 538,-306 538,-270 619,-270 619,-306"/>
<text text-anchor="middle" x="203" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBarGraph</text> <text text-anchor="middle" x="578.5" y="-285.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBarGraph</text>
</g> </g>
<!-- LEDBarGraph&#45;&gt;LEDCollection --> <!-- LEDBarGraph&#45;&gt;LEDCollection -->
<g id="edge5" class="edge"><title>LEDBarGraph&#45;&gt;LEDCollection</title> <g id="edge5" class="edge"><title>LEDBarGraph&#45;&gt;LEDCollection</title>
<path fill="none" stroke="black" d="M201.27,-180.303C200.498,-188.017 199.571,-197.288 198.711,-205.888"/> <path fill="none" stroke="black" d="M537.665,-288C525.958,-288 512.998,-288 500.73,-288"/>
<polygon fill="black" stroke="black" points="195.223,-205.597 197.71,-215.896 202.188,-206.294 195.223,-205.597"/> <polygon fill="black" stroke="black" points="500.63,-284.5 490.63,-288 500.63,-291.5 500.63,-284.5"/>
</g> </g>
<!-- PiLiter --> <!-- PiLiter -->
<g id="node7" class="node"><title>PiLiter</title> <g id="node7" class="node"><title>PiLiter</title>
<polygon fill="#2980b9" stroke="#2980b9" points="54,-108 0,-108 0,-72 54,-72 54,-108"/> <polygon fill="#2980b9" stroke="#2980b9" points="728,-454 674,-454 674,-418 728,-418 728,-454"/>
<text text-anchor="middle" x="27" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiLiter</text> <text text-anchor="middle" x="701" y="-433.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiLiter</text>
</g> </g>
<!-- PiLiter&#45;&gt;LEDBoard --> <!-- PiLiter&#45;&gt;LEDBoard -->
<g id="edge6" class="edge"><title>PiLiter&#45;&gt;LEDBoard</title> <g id="edge6" class="edge"><title>PiLiter&#45;&gt;LEDBoard</title>
<path fill="none" stroke="black" d="M47.0225,-108.303C57.1256,-117.035 69.536,-127.76 80.5175,-137.25"/> <path fill="none" stroke="black" d="M673.853,-421.462C658.056,-412.679 637.595,-401.302 619.767,-391.389"/>
<polygon fill="black" stroke="black" points="78.3532,-140.005 88.2078,-143.896 82.9302,-134.709 78.3532,-140.005"/> <polygon fill="black" stroke="black" points="621.163,-388.161 610.723,-386.36 617.762,-394.279 621.163,-388.161"/>
</g> </g>
<!-- PiLiterBarGraph --> <!-- PiLiterBarGraph -->
<g id="node8" class="node"><title>PiLiterBarGraph</title> <g id="node8" class="node"><title>PiLiterBarGraph</title>
<polygon fill="#2980b9" stroke="#2980b9" points="254,-108 162,-108 162,-72 254,-72 254,-108"/> <polygon fill="#2980b9" stroke="#2980b9" points="747,-292 655,-292 655,-256 747,-256 747,-292"/>
<text text-anchor="middle" x="208" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiLiterBarGraph</text> <text text-anchor="middle" x="701" y="-271.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiLiterBarGraph</text>
</g> </g>
<!-- PiLiterBarGraph&#45;&gt;LEDBarGraph --> <!-- PiLiterBarGraph&#45;&gt;LEDBarGraph -->
<g id="edge7" class="edge"><title>PiLiterBarGraph&#45;&gt;LEDBarGraph</title> <g id="edge7" class="edge"><title>PiLiterBarGraph&#45;&gt;LEDBarGraph</title>
<path fill="none" stroke="black" d="M206.764,-108.303C206.213,-116.017 205.551,-125.288 204.937,-133.888"/> <path fill="none" stroke="black" d="M654.787,-279.253C646.431,-280.224 637.663,-281.242 629.183,-282.228"/>
<polygon fill="black" stroke="black" points="201.443,-133.672 204.222,-143.896 208.425,-134.17 201.443,-133.672"/> <polygon fill="black" stroke="black" points="628.558,-278.777 619.029,-283.407 629.366,-285.73 628.558,-278.777"/>
</g> </g>
<!-- TrafficLights --> <!-- TrafficLights -->
<g id="node9" class="node"><title>TrafficLights</title> <g id="node9" class="node"><title>TrafficLights</title>
<polygon fill="#2980b9" stroke="#2980b9" points="144,-108 72,-108 72,-72 144,-72 144,-108"/> <polygon fill="#2980b9" stroke="#2980b9" points="737,-400 665,-400 665,-364 737,-364 737,-400"/>
<text text-anchor="middle" x="108" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLights</text> <text text-anchor="middle" x="701" y="-379.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLights</text>
</g> </g>
<!-- TrafficLights&#45;&gt;LEDBoard --> <!-- TrafficLights&#45;&gt;LEDBoard -->
<g id="edge8" class="edge"><title>TrafficLights&#45;&gt;LEDBoard</title> <g id="edge8" class="edge"><title>TrafficLights&#45;&gt;LEDBoard</title>
<path fill="none" stroke="black" d="M108,-108.303C108,-116.017 108,-125.288 108,-133.888"/> <path fill="none" stroke="black" d="M664.822,-378.205C651.071,-376.721 635.199,-375.009 620.902,-373.467"/>
<polygon fill="black" stroke="black" points="104.5,-133.896 108,-143.896 111.5,-133.896 104.5,-133.896"/> <polygon fill="black" stroke="black" points="621.14,-369.972 610.822,-372.379 620.389,-376.932 621.14,-369.972"/>
</g>
<!-- SnowPi -->
<g id="node10" class="node"><title>SnowPi</title>
<polygon fill="#2980b9" stroke="#2980b9" points="728,-346 674,-346 674,-310 728,-310 728,-346"/>
<text text-anchor="middle" x="701" y="-325.5" font-family="Sans" font-size="10.00" fill="#ffffff">SnowPi</text>
</g>
<!-- SnowPi&#45;&gt;LEDBoard -->
<g id="edge9" class="edge"><title>SnowPi&#45;&gt;LEDBoard</title>
<path fill="none" stroke="black" d="M673.853,-336.896C658.201,-342.222 637.971,-349.105 620.259,-355.132"/>
<polygon fill="black" stroke="black" points="619.062,-351.842 610.723,-358.376 621.317,-358.469 619.062,-351.842"/>
</g> </g>
<!-- PiTraffic --> <!-- PiTraffic -->
<g id="node10" class="node"><title>PiTraffic</title> <g id="node11" class="node"><title>PiTraffic</title>
<polygon fill="#2980b9" stroke="#2980b9" points="99,-36 45,-36 45,-0 99,-0 99,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="837,-427 783,-427 783,-391 837,-391 837,-427"/>
<text text-anchor="middle" x="72" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiTraffic</text> <text text-anchor="middle" x="810" y="-406.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiTraffic</text>
</g> </g>
<!-- PiTraffic&#45;&gt;TrafficLights --> <!-- PiTraffic&#45;&gt;TrafficLights -->
<g id="edge9" class="edge"><title>PiTraffic&#45;&gt;TrafficLights</title> <g id="edge10" class="edge"><title>PiTraffic&#45;&gt;TrafficLights</title>
<path fill="none" stroke="black" d="M80.8989,-36.3034C84.9968,-44.2716 89.9488,-53.9005 94.4927,-62.7359"/> <path fill="none" stroke="black" d="M782.825,-402.395C772.094,-399.687 759.359,-396.474 747.209,-393.408"/>
<polygon fill="black" stroke="black" points="91.5174,-64.6035 99.2035,-71.8957 97.7425,-61.402 91.5174,-64.6035"/> <polygon fill="black" stroke="black" points="747.749,-389.935 737.197,-390.881 746.036,-396.722 747.749,-389.935"/>
</g> </g>
<!-- PiStop --> <!-- PiStop -->
<g id="node11" class="node"><title>PiStop</title> <g id="node12" class="node"><title>PiStop</title>
<polygon fill="#2980b9" stroke="#2980b9" points="171,-36 117,-36 117,-0 171,-0 171,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="837,-373 783,-373 783,-337 837,-337 837,-373"/>
<text text-anchor="middle" x="144" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiStop</text> <text text-anchor="middle" x="810" y="-352.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiStop</text>
</g> </g>
<!-- PiStop&#45;&gt;TrafficLights --> <!-- PiStop&#45;&gt;TrafficLights -->
<g id="edge10" class="edge"><title>PiStop&#45;&gt;TrafficLights</title> <g id="edge11" class="edge"><title>PiStop&#45;&gt;TrafficLights</title>
<path fill="none" stroke="black" d="M135.101,-36.3034C131.003,-44.2716 126.051,-53.9005 121.507,-62.7359"/> <path fill="none" stroke="black" d="M782.825,-361.605C772.094,-364.313 759.359,-367.526 747.209,-370.592"/>
<polygon fill="black" stroke="black" points="118.258,-61.402 116.797,-71.8957 124.483,-64.6035 118.258,-61.402"/> <polygon fill="black" stroke="black" points="746.036,-367.278 737.197,-373.119 747.749,-374.065 746.036,-367.278"/>
</g> </g>
<!-- TrafficLightsBuzzer --> <!-- TrafficLightsBuzzer -->
<g id="node12" class="node"><title>TrafficLightsBuzzer</title> <g id="node13" class="node"><title>TrafficLightsBuzzer</title>
<polygon fill="#2980b9" stroke="#2980b9" points="360.5,-252 255.5,-252 255.5,-216 360.5,-216 360.5,-252"/> <polygon fill="#2980b9" stroke="#2980b9" points="502,-252 397,-252 397,-216 502,-216 502,-252"/>
<text text-anchor="middle" x="308" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLightsBuzzer</text> <text text-anchor="middle" x="449.5" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLightsBuzzer</text>
</g> </g>
<!-- TrafficLightsBuzzer&#45;&gt;CompositeOutputDevice --> <!-- TrafficLightsBuzzer&#45;&gt;CompositeOutputDevice -->
<g id="edge11" class="edge"><title>TrafficLightsBuzzer&#45;&gt;CompositeOutputDevice</title> <g id="edge12" class="edge"><title>TrafficLightsBuzzer&#45;&gt;CompositeOutputDevice</title>
<path fill="none" stroke="black" d="M308,-252.303C308,-260.017 308,-269.288 308,-277.888"/> <path fill="none" stroke="black" d="M396.96,-234C388.696,-234 379.995,-234 371.3,-234"/>
<polygon fill="black" stroke="black" points="304.5,-277.896 308,-287.896 311.5,-277.896 304.5,-277.896"/> <polygon fill="black" stroke="black" points="371.125,-230.5 361.124,-234 371.124,-237.5 371.125,-230.5"/>
</g> </g>
<!-- FishDish --> <!-- FishDish -->
<g id="node13" class="node"><title>FishDish</title> <g id="node14" class="node"><title>FishDish</title>
<polygon fill="#2980b9" stroke="#2980b9" points="324,-180 268,-180 268,-144 324,-144 324,-180"/> <polygon fill="#2980b9" stroke="#2980b9" points="606.5,-252 550.5,-252 550.5,-216 606.5,-216 606.5,-252"/>
<text text-anchor="middle" x="296" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">FishDish</text> <text text-anchor="middle" x="578.5" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">FishDish</text>
</g> </g>
<!-- FishDish&#45;&gt;TrafficLightsBuzzer --> <!-- FishDish&#45;&gt;TrafficLightsBuzzer -->
<g id="edge12" class="edge"><title>FishDish&#45;&gt;TrafficLightsBuzzer</title> <g id="edge13" class="edge"><title>FishDish&#45;&gt;TrafficLightsBuzzer</title>
<path fill="none" stroke="black" d="M298.966,-180.303C300.289,-188.017 301.878,-197.288 303.352,-205.888"/> <path fill="none" stroke="black" d="M550.255,-234C539.046,-234 525.57,-234 512.2,-234"/>
<polygon fill="black" stroke="black" points="299.928,-206.631 305.068,-215.896 306.828,-205.448 299.928,-206.631"/> <polygon fill="black" stroke="black" points="512.036,-230.5 502.036,-234 512.036,-237.5 512.036,-230.5"/>
</g> </g>
<!-- TrafficHat --> <!-- TrafficHat -->
<g id="node14" class="node"><title>TrafficHat</title> <g id="node15" class="node"><title>TrafficHat</title>
<polygon fill="#2980b9" stroke="#2980b9" points="403.5,-180 342.5,-180 342.5,-144 403.5,-144 403.5,-180"/> <polygon fill="#2980b9" stroke="#2980b9" points="609,-198 548,-198 548,-162 609,-162 609,-198"/>
<text text-anchor="middle" x="373" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficHat</text> <text text-anchor="middle" x="578.5" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficHat</text>
</g> </g>
<!-- TrafficHat&#45;&gt;TrafficLightsBuzzer --> <!-- TrafficHat&#45;&gt;TrafficLightsBuzzer -->
<g id="edge13" class="edge"><title>TrafficHat&#45;&gt;TrafficLightsBuzzer</title> <g id="edge14" class="edge"><title>TrafficHat&#45;&gt;TrafficLightsBuzzer</title>
<path fill="none" stroke="black" d="M356.933,-180.303C349.061,-188.78 339.445,-199.136 330.827,-208.417"/> <path fill="none" stroke="black" d="M547.724,-192.661C534.246,-198.392 517.911,-205.337 502.51,-211.886"/>
<polygon fill="black" stroke="black" points="328.122,-206.186 323.883,-215.896 333.252,-210.949 328.122,-206.186"/> <polygon fill="black" stroke="black" points="500.908,-208.763 493.075,-215.897 503.647,-215.205 500.908,-208.763"/>
</g> </g>
<!-- Robot --> <!-- Robot -->
<g id="node15" class="node"><title>Robot</title> <g id="node16" class="node"><title>Robot</title>
<polygon fill="#2980b9" stroke="#2980b9" points="485,-324 431,-324 431,-288 485,-288 485,-324"/> <polygon fill="#2980b9" stroke="#2980b9" points="321,-198 267,-198 267,-162 321,-162 321,-198"/>
<text text-anchor="middle" x="458" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text> <text text-anchor="middle" x="294" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text>
</g> </g>
<!-- Robot&#45;&gt;CompositeDevice --> <!-- Robot&#45;&gt;CompositeDevice -->
<g id="edge14" class="edge"><title>Robot&#45;&gt;CompositeDevice</title> <g id="edge15" class="edge"><title>Robot&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M475.798,-324.303C484.604,-332.865 495.382,-343.344 505.001,-352.696"/> <path fill="none" stroke="black" d="M266.956,-170.717C248.746,-164.226 223.673,-155.289 200.727,-147.111"/>
<polygon fill="black" stroke="black" points="502.797,-355.434 512.407,-359.896 507.677,-350.415 502.797,-355.434"/> <polygon fill="black" stroke="black" points="201.644,-143.722 191.049,-143.661 199.294,-150.316 201.644,-143.722"/>
</g> </g>
<!-- RyanteckRobot --> <!-- RyanteckRobot -->
<g id="node16" class="node"><title>RyanteckRobot</title> <g id="node17" class="node"><title>RyanteckRobot</title>
<polygon fill="#2980b9" stroke="#2980b9" points="465,-252 379,-252 379,-216 465,-216 465,-252"/> <polygon fill="#2980b9" stroke="#2980b9" points="492.5,-198 406.5,-198 406.5,-162 492.5,-162 492.5,-198"/>
<text text-anchor="middle" x="422" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">RyanteckRobot</text> <text text-anchor="middle" x="449.5" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">RyanteckRobot</text>
</g> </g>
<!-- RyanteckRobot&#45;&gt;Robot --> <!-- RyanteckRobot&#45;&gt;Robot -->
<g id="edge15" class="edge"><title>RyanteckRobot&#45;&gt;Robot</title> <g id="edge16" class="edge"><title>RyanteckRobot&#45;&gt;Robot</title>
<path fill="none" stroke="black" d="M430.899,-252.303C434.997,-260.272 439.949,-269.9 444.493,-278.736"/> <path fill="none" stroke="black" d="M406.149,-180C382.707,-180 353.867,-180 331.439,-180"/>
<polygon fill="black" stroke="black" points="441.517,-280.604 449.203,-287.896 447.742,-277.402 441.517,-280.604"/> <polygon fill="black" stroke="black" points="331.313,-176.5 321.313,-180 331.313,-183.5 331.313,-176.5"/>
</g> </g>
<!-- CamJamKitRobot --> <!-- CamJamKitRobot -->
<g id="node17" class="node"><title>CamJamKitRobot</title> <g id="node18" class="node"><title>CamJamKitRobot</title>
<polygon fill="#2980b9" stroke="#2980b9" points="579,-252 483,-252 483,-216 579,-216 579,-252"/> <polygon fill="#2980b9" stroke="#2980b9" points="497.5,-144 401.5,-144 401.5,-108 497.5,-108 497.5,-144"/>
<text text-anchor="middle" x="531" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">CamJamKitRobot</text> <text text-anchor="middle" x="449.5" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">CamJamKitRobot</text>
</g> </g>
<!-- CamJamKitRobot&#45;&gt;Robot --> <!-- CamJamKitRobot&#45;&gt;Robot -->
<g id="edge16" class="edge"><title>CamJamKitRobot&#45;&gt;Robot</title> <g id="edge17" class="edge"><title>CamJamKitRobot&#45;&gt;Robot</title>
<path fill="none" stroke="black" d="M512.955,-252.303C503.938,-260.95 492.882,-271.551 483.057,-280.973"/> <path fill="none" stroke="black" d="M401.192,-142.642C378.531,-150.615 351.832,-160.007 330.875,-167.379"/>
<polygon fill="black" stroke="black" points="480.633,-278.448 475.837,-287.896 485.478,-283.501 480.633,-278.448"/> <polygon fill="black" stroke="black" points="329.661,-164.096 321.389,-170.717 331.984,-170.7 329.661,-164.096"/>
</g> </g>
<!-- Motor --> <!-- Motor -->
<g id="node18" class="node"><title>Motor</title> <g id="node19" class="node"><title>Motor</title>
<polygon fill="#2980b9" stroke="#2980b9" points="557,-324 503,-324 503,-288 557,-288 557,-324"/> <polygon fill="#2980b9" stroke="#2980b9" points="321,-144 267,-144 267,-108 321,-108 321,-144"/>
<text text-anchor="middle" x="530" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Motor</text> <text text-anchor="middle" x="294" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">Motor</text>
</g> </g>
<!-- Motor&#45;&gt;CompositeDevice --> <!-- Motor&#45;&gt;CompositeDevice -->
<g id="edge17" class="edge"><title>Motor&#45;&gt;CompositeDevice</title> <g id="edge18" class="edge"><title>Motor&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M530,-324.303C530,-332.017 530,-341.288 530,-349.888"/> <path fill="none" stroke="black" d="M266.956,-126C248.908,-126 224.12,-126 201.342,-126"/>
<polygon fill="black" stroke="black" points="526.5,-349.896 530,-359.896 533.5,-349.896 526.5,-349.896"/> <polygon fill="black" stroke="black" points="201.049,-122.5 191.049,-126 201.049,-129.5 201.049,-122.5"/>
</g> </g>
<!-- Servo --> <!-- Servo -->
<g id="node19" class="node"><title>Servo</title> <g id="node20" class="node"><title>Servo</title>
<polygon fill="#2980b9" stroke="#2980b9" points="629,-324 575,-324 575,-288 629,-288 629,-324"/> <polygon fill="#2980b9" stroke="#2980b9" points="321,-90 267,-90 267,-54 321,-54 321,-90"/>
<text text-anchor="middle" x="602" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Servo</text> <text text-anchor="middle" x="294" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">Servo</text>
</g> </g>
<!-- Servo&#45;&gt;CompositeDevice --> <!-- Servo&#45;&gt;CompositeDevice -->
<g id="edge18" class="edge"><title>Servo&#45;&gt;CompositeDevice</title> <g id="edge19" class="edge"><title>Servo&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M584.202,-324.303C575.396,-332.865 564.618,-343.344 554.999,-352.696"/> <path fill="none" stroke="black" d="M266.956,-81.2829C248.746,-87.7738 223.673,-96.7105 200.727,-104.889"/>
<polygon fill="black" stroke="black" points="552.323,-350.415 547.593,-359.896 557.203,-355.434 552.323,-350.415"/> <polygon fill="black" stroke="black" points="199.294,-101.684 191.049,-108.339 201.644,-108.278 199.294,-101.684"/>
</g> </g>
<!-- AngularServo --> <!-- AngularServo -->
<g id="node20" class="node"><title>AngularServo</title> <g id="node21" class="node"><title>AngularServo</title>
<polygon fill="#2980b9" stroke="#2980b9" points="678.5,-252 597.5,-252 597.5,-216 678.5,-216 678.5,-252"/> <polygon fill="#2980b9" stroke="#2980b9" points="490,-90 409,-90 409,-54 490,-54 490,-90"/>
<text text-anchor="middle" x="638" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">AngularServo</text> <text text-anchor="middle" x="449.5" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">AngularServo</text>
</g> </g>
<!-- AngularServo&#45;&gt;Servo --> <!-- AngularServo&#45;&gt;Servo -->
<g id="edge19" class="edge"><title>AngularServo&#45;&gt;Servo</title> <g id="edge20" class="edge"><title>AngularServo&#45;&gt;Servo</title>
<path fill="none" stroke="black" d="M629.101,-252.303C625.003,-260.272 620.051,-269.9 615.507,-278.736"/> <path fill="none" stroke="black" d="M408.97,-72C385.09,-72 354.867,-72 331.545,-72"/>
<polygon fill="black" stroke="black" points="612.258,-277.402 610.797,-287.896 618.483,-280.604 612.258,-277.402"/> <polygon fill="black" stroke="black" points="331.363,-68.5001 321.363,-72 331.363,-75.5001 331.363,-68.5001"/>
</g> </g>
<!-- Energenie --> <!-- Energenie -->
<g id="node21" class="node"><title>Energenie</title> <g id="node22" class="node"><title>Energenie</title>
<polygon fill="#2980b9" stroke="#2980b9" points="663.5,-396 598.5,-396 598.5,-360 663.5,-360 663.5,-396"/> <polygon fill="#2980b9" stroke="#2980b9" points="173,-90 108,-90 108,-54 173,-54 173,-90"/>
<text text-anchor="middle" x="631" y="-375.5" font-family="Sans" font-size="10.00" fill="#ffffff">Energenie</text> <text text-anchor="middle" x="140.5" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">Energenie</text>
</g> </g>
<!-- Energenie&#45;&gt;Device --> <!-- Energenie&#45;&gt;Device -->
<g id="edge20" class="edge"><title>Energenie&#45;&gt;Device</title> <g id="edge21" class="edge"><title>Energenie&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M618.393,-396.303C612.403,-404.526 605.124,-414.517 598.521,-423.579"/> <path fill="none" stroke="black" d="M107.847,-79.6649C94.1694,-82.9769 78.0958,-86.8692 63.9692,-90.29"/>
<polygon fill="black" stroke="black" points="595.522,-421.752 592.462,-431.896 601.179,-425.874 595.522,-421.752"/> <polygon fill="black" stroke="black" points="62.9844,-86.9272 54.089,-92.6825 64.6319,-93.7306 62.9844,-86.9272"/>
</g> </g>
<!-- ButtonBoard --> <!-- ButtonBoard -->
<g id="node22" class="node"><title>ButtonBoard</title> <g id="node23" class="node"><title>ButtonBoard</title>
<polygon fill="#2980b9" stroke="#2980b9" points="724.5,-324 647.5,-324 647.5,-288 724.5,-288 724.5,-324"/> <polygon fill="#2980b9" stroke="#2980b9" points="332.5,-36 255.5,-36 255.5,-0 332.5,-0 332.5,-36"/>
<text text-anchor="middle" x="686" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">ButtonBoard</text> <text text-anchor="middle" x="294" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">ButtonBoard</text>
</g> </g>
<!-- ButtonBoard&#45;&gt;CompositeDevice --> <!-- ButtonBoard&#45;&gt;CompositeDevice -->
<g id="edge21" class="edge"><title>ButtonBoard&#45;&gt;CompositeDevice</title> <g id="edge22" class="edge"><title>ButtonBoard&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M647.838,-324.124C626.607,-333.651 599.992,-345.593 577.431,-355.717"/> <path fill="none" stroke="black" d="M255.244,-28.9887C245.394,-33.0006 235.241,-38.2594 227,-45 204.672,-63.2615 212.28,-79.5278 191,-99 189.894,-100.012 188.744,-100.999 187.557,-101.962"/>
<polygon fill="black" stroke="black" points="575.729,-352.644 568.038,-359.932 578.594,-359.031 575.729,-352.644"/> <polygon fill="black" stroke="black" points="185.354,-99.2363 179.313,-107.94 189.463,-104.903 185.354,-99.2363"/>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
/* vim: set et sw=4 sts=4: */ /* vim: set et sw=4 sts=4: */
digraph classes { digraph classes {
graph [rankdir=BT]; graph [rankdir=RL];
node [shape=rect, style=filled, fontname=Sans, fontsize=10]; node [shape=rect, style=filled, fontname=Sans, fontsize=10];
edge []; edge [];
@@ -60,11 +60,21 @@ digraph classes {
SPIDevice->Device; SPIDevice->Device;
AnalogInputDevice->SPIDevice; AnalogInputDevice->SPIDevice;
MCP3xxx->AnalogInputDevice; MCP3xxx->AnalogInputDevice;
MCP30xx->MCP3xxx;
MCP32xx->MCP3xxx;
MCP33xx->MCP3xxx; MCP33xx->MCP3xxx;
MCP3004->MCP3xxx; MCP3xx2->MCP3xxx;
MCP3008->MCP3xxx;
MCP3204->MCP3xxx; MCP3001->MCP30xx;
MCP3208->MCP3xxx; MCP3002->MCP30xx;
MCP3004->MCP30xx;
MCP3008->MCP30xx;
MCP3201->MCP32xx;
MCP3202->MCP32xx;
MCP3204->MCP32xx;
MCP3208->MCP32xx;
MCP3002->MCP3xx2;
MCP3202->MCP3xx2;
MCP3301->MCP33xx; MCP3301->MCP33xx;
MCP3302->MCP33xx; MCP3302->MCP33xx;
MCP3304->MCP33xx; MCP3304->MCP33xx;
@@ -75,10 +85,15 @@ digraph classes {
LEDCollection->CompositeOutputDevice; LEDCollection->CompositeOutputDevice;
LEDBoard->LEDCollection; LEDBoard->LEDCollection;
LEDBarGraph->LEDCollection; LEDBarGraph->LEDCollection;
LedBorg->RGBLED;
ButtonBoard->CompositeDevice;
ButtonBoard->HoldMixin;
PiLiter->LEDBoard; PiLiter->LEDBoard;
PiLiterBarGraph->LEDBarGraph; PiLiterBarGraph->LEDBarGraph;
TrafficLights->LEDBoard; TrafficLights->LEDBoard;
SnowPi->LEDBoard;
PiTraffic->TrafficLights; PiTraffic->TrafficLights;
PiStop->TrafficLights;
TrafficLightsBuzzer->CompositeOutputDevice; TrafficLightsBuzzer->CompositeOutputDevice;
FishDish->TrafficLightsBuzzer; FishDish->TrafficLightsBuzzer;
TrafficHat->TrafficLightsBuzzer; TrafficHat->TrafficLightsBuzzer;
@@ -90,10 +105,14 @@ digraph classes {
CamJamKitRobot->Robot; CamJamKitRobot->Robot;
Motor->CompositeDevice; Motor->CompositeDevice;
Motor->SourceMixin; Motor->SourceMixin;
Servo->CompositeDevice;
Servo->SourceMixin;
AngularServo->Servo;
InternalDevice->Device; InternalDevice->Device;
InternalDevice->EventsMixin; InternalDevice->EventsMixin;
TimeOfDay->InternalDevice; TimeOfDay->InternalDevice;
PingServer->InternalDevice; PingServer->InternalDevice;
CPUTemperature->InternalDevice;
} }

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 247 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,7 +1,7 @@
/* vim: set et sw=4 sts=4: */ /* vim: set et sw=4 sts=4: */
digraph classes { digraph classes {
graph [rankdir=BT]; graph [rankdir=RL];
node [shape=rect, style=filled, fontname=Sans, fontsize=10]; node [shape=rect, style=filled, fontname=Sans, fontsize=10];
edge []; edge [];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,108 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.36.0 (20140111.2315) <!-- Generated by graphviz version 2.38.0 (20140413.2041)
--> -->
<!-- Title: classes Pages: 1 --> <!-- Title: classes Pages: 1 -->
<svg width="453pt" height="332pt" <svg width="566pt" height="260pt"
viewBox="0.00 0.00 453.00 332.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> viewBox="0.00 0.00 566.00 260.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 328)"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)">
<title>classes</title> <title>classes</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-328 449,-328 449,4 -4,4"/> <polygon fill="white" stroke="none" points="-4,4 -4,-256 562,-256 562,4 -4,4"/>
<!-- Device --> <!-- Device -->
<g id="node1" class="node"><title>Device</title> <g id="node1" class="node"><title>Device</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="335,-324 281,-324 281,-288 335,-288 335,-324"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-103 0,-103 0,-67 54,-67 54,-103"/>
<text text-anchor="middle" x="308" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> <text text-anchor="middle" x="27" y="-82.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
</g> </g>
<!-- GPIODevice --> <!-- GPIODevice -->
<g id="node2" class="node"><title>GPIODevice</title> <g id="node2" class="node"><title>GPIODevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="344,-252 272,-252 272,-216 344,-216 344,-252"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="162,-103 90,-103 90,-67 162,-67 162,-103"/>
<text text-anchor="middle" x="308" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text> <text text-anchor="middle" x="126" y="-82.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text>
</g> </g>
<!-- GPIODevice&#45;&gt;Device --> <!-- GPIODevice&#45;&gt;Device -->
<g id="edge1" class="edge"><title>GPIODevice&#45;&gt;Device</title> <g id="edge1" class="edge"><title>GPIODevice&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M308,-252.303C308,-260.017 308,-269.288 308,-277.888"/> <path fill="none" stroke="black" d="M89.9808,-85C81.6627,-85 72.77,-85 64.3922,-85"/>
<polygon fill="black" stroke="black" points="304.5,-277.896 308,-287.896 311.5,-277.896 304.5,-277.896"/> <polygon fill="black" stroke="black" points="64.1378,-81.5001 54.1378,-85 64.1377,-88.5001 64.1378,-81.5001"/>
</g> </g>
<!-- SmoothedInputDevice --> <!-- SmoothedInputDevice -->
<g id="node3" class="node"><title>SmoothedInputDevice</title> <g id="node3" class="node"><title>SmoothedInputDevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="289.5,-108 166.5,-108 166.5,-72 289.5,-72 289.5,-108"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="430,-144 307,-144 307,-108 430,-108 430,-144"/>
<text text-anchor="middle" x="228" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">SmoothedInputDevice</text> <text text-anchor="middle" x="368.5" y="-123.5" font-family="Sans" font-size="10.00" fill="#000000">SmoothedInputDevice</text>
</g> </g>
<!-- InputDevice --> <!-- InputDevice -->
<g id="node4" class="node"><title>InputDevice</title> <g id="node4" class="node"><title>InputDevice</title>
<polygon fill="#2980b9" stroke="#2980b9" points="344.5,-180 271.5,-180 271.5,-144 344.5,-144 344.5,-180"/> <polygon fill="#2980b9" stroke="#2980b9" points="271,-103 198,-103 198,-67 271,-67 271,-103"/>
<text text-anchor="middle" x="308" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">InputDevice</text> <text text-anchor="middle" x="234.5" y="-82.5" font-family="Sans" font-size="10.00" fill="#ffffff">InputDevice</text>
</g> </g>
<!-- SmoothedInputDevice&#45;&gt;InputDevice --> <!-- SmoothedInputDevice&#45;&gt;InputDevice -->
<g id="edge4" class="edge"><title>SmoothedInputDevice&#45;&gt;InputDevice</title> <g id="edge4" class="edge"><title>SmoothedInputDevice&#45;&gt;InputDevice</title>
<path fill="none" stroke="black" d="M247.775,-108.303C257.754,-117.035 270.011,-127.76 280.857,-137.25"/> <path fill="none" stroke="black" d="M309.219,-107.897C299.683,-104.936 289.93,-101.906 280.77,-99.0612"/>
<polygon fill="black" stroke="black" points="278.622,-139.945 288.452,-143.896 283.231,-134.677 278.622,-139.945"/> <polygon fill="black" stroke="black" points="281.69,-95.6819 271.102,-96.0581 279.613,-102.367 281.69,-95.6819"/>
</g> </g>
<!-- InputDevice&#45;&gt;GPIODevice --> <!-- InputDevice&#45;&gt;GPIODevice -->
<g id="edge2" class="edge"><title>InputDevice&#45;&gt;GPIODevice</title> <g id="edge2" class="edge"><title>InputDevice&#45;&gt;GPIODevice</title>
<path fill="none" stroke="black" d="M308,-180.303C308,-188.017 308,-197.288 308,-205.888"/> <path fill="none" stroke="black" d="M197.741,-85C189.578,-85 180.799,-85 172.296,-85"/>
<polygon fill="black" stroke="black" points="304.5,-205.896 308,-215.896 311.5,-205.896 304.5,-205.896"/> <polygon fill="black" stroke="black" points="172.122,-81.5001 162.122,-85 172.122,-88.5001 172.122,-81.5001"/>
</g> </g>
<!-- DigitalInputDevice --> <!-- DigitalInputDevice -->
<g id="node5" class="node"><title>DigitalInputDevice</title> <g id="node5" class="node"><title>DigitalInputDevice</title>
<polygon fill="#2980b9" stroke="#2980b9" points="439.5,-108 336.5,-108 336.5,-72 439.5,-72 439.5,-108"/> <polygon fill="#2980b9" stroke="#2980b9" points="420,-63 317,-63 317,-27 420,-27 420,-63"/>
<text text-anchor="middle" x="388" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text> <text text-anchor="middle" x="368.5" y="-42.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text>
</g> </g>
<!-- DigitalInputDevice&#45;&gt;InputDevice --> <!-- DigitalInputDevice&#45;&gt;InputDevice -->
<g id="edge3" class="edge"><title>DigitalInputDevice&#45;&gt;InputDevice</title> <g id="edge3" class="edge"><title>DigitalInputDevice&#45;&gt;InputDevice</title>
<path fill="none" stroke="black" d="M368.225,-108.303C358.246,-117.035 345.989,-127.76 335.143,-137.25"/> <path fill="none" stroke="black" d="M316.843,-60.3506C305.034,-63.9291 292.532,-67.7177 280.964,-71.2231"/>
<polygon fill="black" stroke="black" points="332.769,-134.677 327.548,-143.896 337.378,-139.945 332.769,-134.677"/> <polygon fill="black" stroke="black" points="279.635,-67.9685 271.08,-74.2182 281.665,-74.6677 279.635,-67.9685"/>
</g> </g>
<!-- Button --> <!-- Button -->
<g id="node6" class="node"><title>Button</title> <g id="node6" class="node"><title>Button</title>
<polygon fill="#2980b9" stroke="#2980b9" points="445,-36 391,-36 391,-0 445,-0 445,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="539,-36 485,-36 485,-0 539,-0 539,-36"/>
<text text-anchor="middle" x="418" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text> <text text-anchor="middle" x="512" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text>
</g> </g>
<!-- Button&#45;&gt;DigitalInputDevice --> <!-- Button&#45;&gt;DigitalInputDevice -->
<g id="edge5" class="edge"><title>Button&#45;&gt;DigitalInputDevice</title> <g id="edge5" class="edge"><title>Button&#45;&gt;DigitalInputDevice</title>
<path fill="none" stroke="black" d="M410.584,-36.3034C407.206,-44.1868 403.13,-53.6958 399.377,-62.4536"/> <path fill="none" stroke="black" d="M484.717,-23.0152C469.335,-25.9502 449.238,-29.785 430.144,-33.4284"/>
<polygon fill="black" stroke="black" points="396.053,-61.3255 395.33,-71.8957 402.487,-64.0829 396.053,-61.3255"/> <polygon fill="black" stroke="black" points="429.447,-29.9981 420.28,-35.3105 430.759,-36.8741 429.447,-29.9981"/>
</g> </g>
<!-- MotionSensor --> <!-- MotionSensor -->
<g id="node7" class="node"><title>MotionSensor</title> <g id="node7" class="node"><title>MotionSensor</title>
<polygon fill="#2980b9" stroke="#2980b9" points="82.5,-36 -0.5,-36 -0.5,-0 82.5,-0 82.5,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="553.5,-252 470.5,-252 470.5,-216 553.5,-216 553.5,-252"/>
<text text-anchor="middle" x="41" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MotionSensor</text> <text text-anchor="middle" x="512" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">MotionSensor</text>
</g> </g>
<!-- MotionSensor&#45;&gt;SmoothedInputDevice --> <!-- MotionSensor&#45;&gt;SmoothedInputDevice -->
<g id="edge6" class="edge"><title>MotionSensor&#45;&gt;SmoothedInputDevice</title> <g id="edge6" class="edge"><title>MotionSensor&#45;&gt;SmoothedInputDevice</title>
<path fill="none" stroke="black" d="M82.5014,-34.5353C109.086,-44.4869 143.895,-57.5168 172.8,-68.3368"/> <path fill="none" stroke="black" d="M479.125,-215.734C474.645,-212.92 470.154,-209.962 466,-207 441.533,-189.557 415.537,-167.509 396.698,-150.822"/>
<polygon fill="black" stroke="black" points="171.763,-71.6859 182.356,-71.9139 174.217,-65.1302 171.763,-71.6859"/> <polygon fill="black" stroke="black" points="398.878,-148.077 389.088,-144.028 394.216,-153.299 398.878,-148.077"/>
</g> </g>
<!-- LightSensor --> <!-- LightSensor -->
<g id="node8" class="node"><title>LightSensor</title> <g id="node8" class="node"><title>LightSensor</title>
<polygon fill="#2980b9" stroke="#2980b9" points="175,-36 101,-36 101,-0 175,-0 175,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="549,-198 475,-198 475,-162 549,-162 549,-198"/>
<text text-anchor="middle" x="138" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LightSensor</text> <text text-anchor="middle" x="512" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">LightSensor</text>
</g> </g>
<!-- LightSensor&#45;&gt;SmoothedInputDevice --> <!-- LightSensor&#45;&gt;SmoothedInputDevice -->
<g id="edge7" class="edge"><title>LightSensor&#45;&gt;SmoothedInputDevice</title> <g id="edge7" class="edge"><title>LightSensor&#45;&gt;SmoothedInputDevice</title>
<path fill="none" stroke="black" d="M160.247,-36.3034C171.582,-45.1193 185.53,-55.9679 197.819,-65.5258"/> <path fill="none" stroke="black" d="M474.927,-166.234C460.143,-160.592 442.674,-153.925 426.253,-147.659"/>
<polygon fill="black" stroke="black" points="195.966,-68.519 206.009,-71.8957 200.264,-62.9935 195.966,-68.519"/> <polygon fill="black" stroke="black" points="427.325,-144.321 416.734,-144.026 424.829,-150.861 427.325,-144.321"/>
</g> </g>
<!-- LineSensor --> <!-- LineSensor -->
<g id="node9" class="node"><title>LineSensor</title> <g id="node9" class="node"><title>LineSensor</title>
<polygon fill="#2980b9" stroke="#2980b9" points="263,-36 193,-36 193,-0 263,-0 263,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="547,-144 477,-144 477,-108 547,-108 547,-144"/>
<text text-anchor="middle" x="228" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LineSensor</text> <text text-anchor="middle" x="512" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">LineSensor</text>
</g> </g>
<!-- LineSensor&#45;&gt;SmoothedInputDevice --> <!-- LineSensor&#45;&gt;SmoothedInputDevice -->
<g id="edge8" class="edge"><title>LineSensor&#45;&gt;SmoothedInputDevice</title> <g id="edge8" class="edge"><title>LineSensor&#45;&gt;SmoothedInputDevice</title>
<path fill="none" stroke="black" d="M228,-36.3034C228,-44.0173 228,-53.2875 228,-61.8876"/> <path fill="none" stroke="black" d="M476.747,-126C465.711,-126 453.043,-126 440.37,-126"/>
<polygon fill="black" stroke="black" points="224.5,-61.8956 228,-71.8957 231.5,-61.8957 224.5,-61.8956"/> <polygon fill="black" stroke="black" points="440.237,-122.5 430.237,-126 440.237,-129.5 440.237,-122.5"/>
</g> </g>
<!-- DistanceSensor --> <!-- DistanceSensor -->
<g id="node10" class="node"><title>DistanceSensor</title> <g id="node10" class="node"><title>DistanceSensor</title>
<polygon fill="#2980b9" stroke="#2980b9" points="373,-36 281,-36 281,-0 373,-0 373,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="558,-90 466,-90 466,-54 558,-54 558,-90"/>
<text text-anchor="middle" x="327" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">DistanceSensor</text> <text text-anchor="middle" x="512" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">DistanceSensor</text>
</g> </g>
<!-- DistanceSensor&#45;&gt;SmoothedInputDevice --> <!-- DistanceSensor&#45;&gt;SmoothedInputDevice -->
<g id="edge9" class="edge"><title>DistanceSensor&#45;&gt;SmoothedInputDevice</title> <g id="edge9" class="edge"><title>DistanceSensor&#45;&gt;SmoothedInputDevice</title>
<path fill="none" stroke="black" d="M302.782,-36.1239C290.077,-45.1069 274.336,-56.2375 260.577,-65.9659"/> <path fill="none" stroke="black" d="M465.839,-89.2344C453.213,-94.0529 439.33,-99.351 426.131,-104.388"/>
<polygon fill="black" stroke="black" points="258.456,-63.1791 252.311,-71.8102 262.497,-68.8947 258.456,-63.1791"/> <polygon fill="black" stroke="black" points="424.763,-101.164 416.668,-107.999 427.259,-107.704 424.763,-101.164"/>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,7 +1,7 @@
/* vim: set et sw=4 sts=4: */ /* vim: set et sw=4 sts=4: */
digraph classes { digraph classes {
graph [rankdir=BT]; graph [rankdir=RL];
node [shape=rect, style=filled, fontname=Sans, fontsize=10]; node [shape=rect, style=filled, fontname=Sans, fontsize=10];
edge []; edge [];
@@ -16,4 +16,5 @@ digraph classes {
InternalDevice->Device; InternalDevice->Device;
TimeOfDay->InternalDevice; TimeOfDay->InternalDevice;
PingServer->InternalDevice; PingServer->InternalDevice;
CPUTemperature->InternalDevice;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,48 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.36.0 (20140111.2315) <!-- Generated by graphviz version 2.38.0 (20140413.2041)
--> -->
<!-- Title: classes Pages: 1 --> <!-- Title: classes Pages: 1 -->
<svg width="163pt" height="188pt" <svg width="317pt" height="152pt"
viewBox="0.00 0.00 163.00 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> viewBox="0.00 0.00 317.00 152.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 148)">
<title>classes</title> <title>classes</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-184 159,-184 159,4 -4,4"/> <polygon fill="white" stroke="none" points="-4,4 -4,-148 313,-148 313,4 -4,4"/>
<!-- Device --> <!-- Device -->
<g id="node1" class="node"><title>Device</title> <g id="node1" class="node"><title>Device</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="104,-180 50,-180 50,-144 104,-144 104,-180"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-90 0,-90 0,-54 54,-54 54,-90"/>
<text text-anchor="middle" x="77" y="-159.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> <text text-anchor="middle" x="27" y="-69.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
</g> </g>
<!-- InternalDevice --> <!-- InternalDevice -->
<g id="node2" class="node"><title>InternalDevice</title> <g id="node2" class="node"><title>InternalDevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="119.5,-108 34.5,-108 34.5,-72 119.5,-72 119.5,-108"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="175,-90 90,-90 90,-54 175,-54 175,-90"/>
<text text-anchor="middle" x="77" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">InternalDevice</text> <text text-anchor="middle" x="132.5" y="-69.5" font-family="Sans" font-size="10.00" fill="#000000">InternalDevice</text>
</g> </g>
<!-- InternalDevice&#45;&gt;Device --> <!-- InternalDevice&#45;&gt;Device -->
<g id="edge1" class="edge"><title>InternalDevice&#45;&gt;Device</title> <g id="edge1" class="edge"><title>InternalDevice&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M77,-108.303C77,-116.017 77,-125.288 77,-133.888"/> <path fill="none" stroke="black" d="M89.999,-72C81.4576,-72 72.5283,-72 64.1926,-72"/>
<polygon fill="black" stroke="black" points="73.5001,-133.896 77,-143.896 80.5001,-133.896 73.5001,-133.896"/> <polygon fill="black" stroke="black" points="64.0256,-68.5001 54.0256,-72 64.0255,-75.5001 64.0256,-68.5001"/>
</g> </g>
<!-- TimeOfDay --> <!-- TimeOfDay -->
<g id="node3" class="node"><title>TimeOfDay</title> <g id="node3" class="node"><title>TimeOfDay</title>
<polygon fill="#2980b9" stroke="#2980b9" points="68.5,-36 -0.5,-36 -0.5,-0 68.5,-0 68.5,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="294.5,-144 225.5,-144 225.5,-108 294.5,-108 294.5,-144"/>
<text text-anchor="middle" x="34" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">TimeOfDay</text> <text text-anchor="middle" x="260" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">TimeOfDay</text>
</g> </g>
<!-- TimeOfDay&#45;&gt;InternalDevice --> <!-- TimeOfDay&#45;&gt;InternalDevice -->
<g id="edge2" class="edge"><title>TimeOfDay&#45;&gt;InternalDevice</title> <g id="edge2" class="edge"><title>TimeOfDay&#45;&gt;InternalDevice</title>
<path fill="none" stroke="black" d="M44.6292,-36.3034C49.6281,-44.4411 55.691,-54.311 61.2121,-63.2987"/> <path fill="none" stroke="black" d="M225.368,-111.529C212.862,-106.148 198.372,-99.9132 184.65,-94.0087"/>
<polygon fill="black" stroke="black" points="58.2766,-65.2069 66.4931,-71.8957 64.2411,-61.5429 58.2766,-65.2069"/> <polygon fill="black" stroke="black" points="185.875,-90.7257 175.306,-89.9883 183.108,-97.1558 185.875,-90.7257"/>
</g> </g>
<!-- PingServer --> <!-- PingServer -->
<g id="node4" class="node"><title>PingServer</title> <g id="node4" class="node"><title>PingServer</title>
<polygon fill="#2980b9" stroke="#2980b9" points="155,-36 87,-36 87,-0 155,-0 155,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="294,-90 226,-90 226,-54 294,-54 294,-90"/>
<text text-anchor="middle" x="121" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PingServer</text> <text text-anchor="middle" x="260" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">PingServer</text>
</g> </g>
<!-- PingServer&#45;&gt;InternalDevice --> <!-- PingServer&#45;&gt;InternalDevice -->
<g id="edge3" class="edge"><title>PingServer&#45;&gt;InternalDevice</title> <g id="edge3" class="edge"><title>PingServer&#45;&gt;InternalDevice</title>
<path fill="none" stroke="black" d="M110.124,-36.3034C105.008,-44.4411 98.8045,-54.311 93.1551,-63.2987"/> <path fill="none" stroke="black" d="M225.698,-72C213.314,-72 198.952,-72 185.303,-72"/>
<polygon fill="black" stroke="black" points="90.1098,-61.5667 87.7513,-71.8957 96.0363,-65.2919 90.1098,-61.5667"/> <polygon fill="black" stroke="black" points="185.049,-68.5001 175.049,-72 185.049,-75.5001 185.049,-68.5001"/>
</g>
<!-- CPUTemperature -->
<g id="node5" class="node"><title>CPUTemperature</title>
<polygon fill="#2980b9" stroke="#2980b9" points="309,-36 211,-36 211,-0 309,-0 309,-36"/>
<text text-anchor="middle" x="260" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">CPUTemperature</text>
</g>
<!-- CPUTemperature&#45;&gt;InternalDevice -->
<g id="edge4" class="edge"><title>CPUTemperature&#45;&gt;InternalDevice</title>
<path fill="none" stroke="black" d="M216.861,-36.1314C206.507,-40.5867 195.31,-45.4043 184.585,-50.0191"/>
<polygon fill="black" stroke="black" points="183.117,-46.8407 175.314,-54.0082 185.883,-53.2707 183.117,-46.8407"/>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,7 +1,7 @@
/* vim: set et sw=4 sts=4: */ /* vim: set et sw=4 sts=4: */
digraph classes { digraph classes {
graph [rankdir=BT]; graph [rankdir=RL];
node [shape=rect, style=filled, fontname=Sans, fontsize=10]; node [shape=rect, style=filled, fontname=Sans, fontsize=10];
edge []; edge [];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,108 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.36.0 (20140111.2315) <!-- Generated by graphviz version 2.38.0 (20140413.2041)
--> -->
<!-- Title: classes Pages: 1 --> <!-- Title: classes Pages: 1 -->
<svg width="259pt" height="332pt" <svg width="530pt" height="179pt"
viewBox="0.00 0.00 259.00 332.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> viewBox="0.00 0.00 530.00 179.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 328)"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 175)">
<title>classes</title> <title>classes</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-328 255,-328 255,4 -4,4"/> <polygon fill="white" stroke="none" points="-4,4 -4,-175 526,-175 526,4 -4,4"/>
<!-- Device --> <!-- Device -->
<g id="node1" class="node"><title>Device</title> <g id="node1" class="node"><title>Device</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="206,-324 152,-324 152,-288 206,-288 206,-324"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-63 0,-63 0,-27 54,-27 54,-63"/>
<text text-anchor="middle" x="179" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> <text text-anchor="middle" x="27" y="-42.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
</g> </g>
<!-- GPIODevice --> <!-- GPIODevice -->
<g id="node2" class="node"><title>GPIODevice</title> <g id="node2" class="node"><title>GPIODevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="173,-252 101,-252 101,-216 173,-216 173,-252"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="162,-90 90,-90 90,-54 162,-54 162,-90"/>
<text text-anchor="middle" x="137" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text> <text text-anchor="middle" x="126" y="-69.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text>
</g> </g>
<!-- GPIODevice&#45;&gt;Device --> <!-- GPIODevice&#45;&gt;Device -->
<g id="edge1" class="edge"><title>GPIODevice&#45;&gt;Device</title> <g id="edge1" class="edge"><title>GPIODevice&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M147.382,-252.303C152.265,-260.441 158.187,-270.311 163.579,-279.299"/> <path fill="none" stroke="black" d="M89.9808,-62.2524C81.4779,-59.8856 72.3745,-57.3517 63.8345,-54.9745"/>
<polygon fill="black" stroke="black" points="160.591,-281.121 168.737,-287.896 166.594,-277.52 160.591,-281.121"/> <polygon fill="black" stroke="black" points="64.7101,-51.5853 54.1378,-52.2755 62.833,-58.3289 64.7101,-51.5853"/>
</g> </g>
<!-- OutputDevice --> <!-- OutputDevice -->
<g id="node3" class="node"><title>OutputDevice</title> <g id="node3" class="node"><title>OutputDevice</title>
<polygon fill="#2980b9" stroke="#2980b9" points="177,-180 95,-180 95,-144 177,-144 177,-180"/> <polygon fill="#2980b9" stroke="#2980b9" points="280,-90 198,-90 198,-54 280,-54 280,-90"/>
<text text-anchor="middle" x="136" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text> <text text-anchor="middle" x="239" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text>
</g> </g>
<!-- OutputDevice&#45;&gt;GPIODevice --> <!-- OutputDevice&#45;&gt;GPIODevice -->
<g id="edge2" class="edge"><title>OutputDevice&#45;&gt;GPIODevice</title> <g id="edge2" class="edge"><title>OutputDevice&#45;&gt;GPIODevice</title>
<path fill="none" stroke="black" d="M136.247,-180.303C136.357,-188.017 136.49,-197.288 136.613,-205.888"/> <path fill="none" stroke="black" d="M197.926,-72C189.627,-72 180.827,-72 172.353,-72"/>
<polygon fill="black" stroke="black" points="133.113,-205.947 136.756,-215.896 140.112,-205.847 133.113,-205.947"/> <polygon fill="black" stroke="black" points="172.235,-68.5001 162.235,-72 172.235,-75.5001 172.235,-68.5001"/>
</g> </g>
<!-- DigitalOutputDevice --> <!-- DigitalOutputDevice -->
<g id="node4" class="node"><title>DigitalOutputDevice</title> <g id="node4" class="node"><title>DigitalOutputDevice</title>
<polygon fill="#2980b9" stroke="#2980b9" points="129,-108 17,-108 17,-72 129,-72 129,-108"/> <polygon fill="#2980b9" stroke="#2980b9" points="428,-117 316,-117 316,-81 428,-81 428,-117"/>
<text text-anchor="middle" x="73" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text> <text text-anchor="middle" x="372" y="-96.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text>
</g> </g>
<!-- DigitalOutputDevice&#45;&gt;OutputDevice --> <!-- DigitalOutputDevice&#45;&gt;OutputDevice -->
<g id="edge3" class="edge"><title>DigitalOutputDevice&#45;&gt;OutputDevice</title> <g id="edge3" class="edge"><title>DigitalOutputDevice&#45;&gt;OutputDevice</title>
<path fill="none" stroke="black" d="M88.573,-108.303C96.2022,-116.78 105.523,-127.136 113.876,-136.417"/> <path fill="none" stroke="black" d="M315.824,-87.6278C307.373,-85.886 298.687,-84.0957 290.347,-82.3769"/>
<polygon fill="black" stroke="black" points="111.315,-138.804 120.606,-143.896 116.518,-134.121 111.315,-138.804"/> <polygon fill="black" stroke="black" points="290.886,-78.9146 280.386,-80.3238 289.473,-85.7705 290.886,-78.9146"/>
</g> </g>
<!-- LED --> <!-- LED -->
<g id="node5" class="node"><title>LED</title> <g id="node5" class="node"><title>LED</title>
<polygon fill="#2980b9" stroke="#2980b9" points="54,-36 0,-36 0,-0 54,-0 54,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="520,-171 466,-171 466,-135 520,-135 520,-171"/>
<text text-anchor="middle" x="27" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text> <text text-anchor="middle" x="493" y="-150.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text>
</g> </g>
<!-- LED&#45;&gt;DigitalOutputDevice --> <!-- LED&#45;&gt;DigitalOutputDevice -->
<g id="edge4" class="edge"><title>LED&#45;&gt;DigitalOutputDevice</title> <g id="edge4" class="edge"><title>LED&#45;&gt;DigitalOutputDevice</title>
<path fill="none" stroke="black" d="M38.3708,-36.3034C43.7185,-44.4411 50.2043,-54.311 56.1106,-63.2987"/> <path fill="none" stroke="black" d="M465.885,-141.149C453.08,-135.339 437.226,-128.144 422.262,-121.354"/>
<polygon fill="black" stroke="black" points="53.3432,-65.4607 61.76,-71.8957 59.1932,-61.6165 53.3432,-65.4607"/> <polygon fill="black" stroke="black" points="423.647,-118.139 413.095,-117.194 420.755,-124.514 423.647,-118.139"/>
</g> </g>
<!-- Buzzer --> <!-- Buzzer -->
<g id="node6" class="node"><title>Buzzer</title> <g id="node6" class="node"><title>Buzzer</title>
<polygon fill="#2980b9" stroke="#2980b9" points="126,-36 72,-36 72,-0 126,-0 126,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="520,-117 466,-117 466,-81 520,-81 520,-117"/>
<text text-anchor="middle" x="99" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text> <text text-anchor="middle" x="493" y="-96.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text>
</g> </g>
<!-- Buzzer&#45;&gt;DigitalOutputDevice --> <!-- Buzzer&#45;&gt;DigitalOutputDevice -->
<g id="edge5" class="edge"><title>Buzzer&#45;&gt;DigitalOutputDevice</title> <g id="edge5" class="edge"><title>Buzzer&#45;&gt;DigitalOutputDevice</title>
<path fill="none" stroke="black" d="M92.573,-36.3034C89.6449,-44.1868 86.113,-53.6958 82.8601,-62.4536"/> <path fill="none" stroke="black" d="M465.885,-99C457.659,-99 448.174,-99 438.475,-99"/>
<polygon fill="black" stroke="black" points="79.554,-61.3027 79.353,-71.8957 86.116,-63.7401 79.554,-61.3027"/> <polygon fill="black" stroke="black" points="438.334,-95.5001 428.334,-99 438.334,-102.5 438.334,-95.5001"/>
</g> </g>
<!-- PWMOutputDevice --> <!-- PWMOutputDevice -->
<g id="node7" class="node"><title>PWMOutputDevice</title> <g id="node7" class="node"><title>PWMOutputDevice</title>
<polygon fill="#2980b9" stroke="#2980b9" points="251,-108 147,-108 147,-72 251,-72 251,-108"/> <polygon fill="#2980b9" stroke="#2980b9" points="424,-63 320,-63 320,-27 424,-27 424,-63"/>
<text text-anchor="middle" x="199" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text> <text text-anchor="middle" x="372" y="-42.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text>
</g> </g>
<!-- PWMOutputDevice&#45;&gt;OutputDevice --> <!-- PWMOutputDevice&#45;&gt;OutputDevice -->
<g id="edge6" class="edge"><title>PWMOutputDevice&#45;&gt;OutputDevice</title> <g id="edge6" class="edge"><title>PWMOutputDevice&#45;&gt;OutputDevice</title>
<path fill="none" stroke="black" d="M183.427,-108.303C175.798,-116.78 166.477,-127.136 158.124,-136.417"/> <path fill="none" stroke="black" d="M319.977,-55.5162C310.166,-57.5383 299.899,-59.6544 290.117,-61.6705"/>
<polygon fill="black" stroke="black" points="155.482,-134.121 151.394,-143.896 160.685,-138.804 155.482,-134.121"/> <polygon fill="black" stroke="black" points="289.226,-58.2805 280.139,-63.7271 290.639,-65.1363 289.226,-58.2805"/>
</g> </g>
<!-- PWMLED --> <!-- PWMLED -->
<g id="node8" class="node"><title>PWMLED</title> <g id="node8" class="node"><title>PWMLED</title>
<polygon fill="#2980b9" stroke="#2980b9" points="228,-36 170,-36 170,-0 228,-0 228,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="522,-63 464,-63 464,-27 522,-27 522,-63"/>
<text text-anchor="middle" x="199" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text> <text text-anchor="middle" x="493" y="-42.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text>
</g> </g>
<!-- PWMLED&#45;&gt;PWMOutputDevice --> <!-- PWMLED&#45;&gt;PWMOutputDevice -->
<g id="edge7" class="edge"><title>PWMLED&#45;&gt;PWMOutputDevice</title> <g id="edge7" class="edge"><title>PWMLED&#45;&gt;PWMOutputDevice</title>
<path fill="none" stroke="black" d="M199,-36.3034C199,-44.0173 199,-53.2875 199,-61.8876"/> <path fill="none" stroke="black" d="M463.799,-45C454.932,-45 444.77,-45 434.527,-45"/>
<polygon fill="black" stroke="black" points="195.5,-61.8956 199,-71.8957 202.5,-61.8957 195.5,-61.8956"/> <polygon fill="black" stroke="black" points="434.303,-41.5001 424.303,-45 434.303,-48.5001 434.303,-41.5001"/>
</g> </g>
<!-- RGBLED --> <!-- RGBLED -->
<g id="node9" class="node"><title>RGBLED</title> <g id="node9" class="node"><title>RGBLED</title>
<polygon fill="#2980b9" stroke="#2980b9" points="249,-252 193,-252 193,-216 249,-216 249,-252"/> <polygon fill="#2980b9" stroke="#2980b9" points="154,-36 98,-36 98,-0 154,-0 154,-36"/>
<text text-anchor="middle" x="221" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text> <text text-anchor="middle" x="126" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text>
</g> </g>
<!-- RGBLED&#45;&gt;Device --> <!-- RGBLED&#45;&gt;Device -->
<g id="edge8" class="edge"><title>RGBLED&#45;&gt;Device</title> <g id="edge8" class="edge"><title>RGBLED&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M210.618,-252.303C205.735,-260.441 199.813,-270.311 194.421,-279.299"/> <path fill="none" stroke="black" d="M97.9801,-25.521C87.5078,-28.436 75.3612,-31.817 64.1562,-34.9359"/>
<polygon fill="black" stroke="black" points="191.406,-277.52 189.263,-287.896 197.409,-281.121 191.406,-277.52"/> <polygon fill="black" stroke="black" points="62.9008,-31.6522 54.2057,-37.7056 64.778,-38.3958 62.9008,-31.6522"/>
</g> </g>
<!-- LedBorg --> <!-- LedBorg -->
<g id="node10" class="node"><title>LedBorg</title> <g id="node10" class="node"><title>LedBorg</title>
<polygon fill="#2980b9" stroke="#2980b9" points="251,-180 195,-180 195,-144 251,-144 251,-180"/> <polygon fill="#2980b9" stroke="#2980b9" points="267,-36 211,-36 211,-0 267,-0 267,-36"/>
<text text-anchor="middle" x="223" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">LedBorg</text> <text text-anchor="middle" x="239" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LedBorg</text>
</g> </g>
<!-- LedBorg&#45;&gt;RGBLED --> <!-- LedBorg&#45;&gt;RGBLED -->
<g id="edge9" class="edge"><title>LedBorg&#45;&gt;RGBLED</title> <g id="edge9" class="edge"><title>LedBorg&#45;&gt;RGBLED</title>
<path fill="none" stroke="black" d="M222.506,-180.303C222.285,-188.017 222.02,-197.288 221.775,-205.888"/> <path fill="none" stroke="black" d="M210.846,-18C196.871,-18 179.617,-18 164.401,-18"/>
<polygon fill="black" stroke="black" points="218.276,-205.8 221.489,-215.896 225.273,-206 218.276,-205.8"/> <polygon fill="black" stroke="black" points="164.258,-14.5001 154.258,-18 164.258,-21.5001 164.258,-14.5001"/>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -1,5 +1,5 @@
digraph classes { digraph classes {
graph [rankdir=BT]; graph [rankdir=RL];
node [shape=rect, style=filled, fontname=Sans, fontsize=10]; node [shape=rect, style=filled, fontname=Sans, fontsize=10];
edge []; edge [];

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -4,205 +4,205 @@
<!-- Generated by graphviz version 2.38.0 (20140413.2041) <!-- Generated by graphviz version 2.38.0 (20140413.2041)
--> -->
<!-- Title: classes Pages: 1 --> <!-- Title: classes Pages: 1 -->
<svg width="870pt" height="404pt" <svg width="593pt" height="584pt"
viewBox="0.00 0.00 870.00 404.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> viewBox="0.00 0.00 593.00 584.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 400)"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 580)">
<title>classes</title> <title>classes</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-400 866,-400 866,4 -4,4"/> <polygon fill="white" stroke="none" points="-4,4 -4,-580 589,-580 589,4 -4,4"/>
<!-- Device --> <!-- Device -->
<g id="node1" class="node"><title>Device</title> <g id="node1" class="node"><title>Device</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="418,-396 364,-396 364,-360 418,-360 418,-396"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-333 0,-333 0,-297 54,-297 54,-333"/>
<text text-anchor="middle" x="391" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> <text text-anchor="middle" x="27" y="-312.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
</g> </g>
<!-- SPIDevice --> <!-- SPIDevice -->
<g id="node2" class="node"><title>SPIDevice</title> <g id="node2" class="node"><title>SPIDevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="423,-324 359,-324 359,-288 423,-288 423,-324"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="154,-333 90,-333 90,-297 154,-297 154,-333"/>
<text text-anchor="middle" x="391" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">SPIDevice</text> <text text-anchor="middle" x="122" y="-312.5" font-family="Sans" font-size="10.00" fill="#000000">SPIDevice</text>
</g> </g>
<!-- SPIDevice&#45;&gt;Device --> <!-- SPIDevice&#45;&gt;Device -->
<g id="edge1" class="edge"><title>SPIDevice&#45;&gt;Device</title> <g id="edge1" class="edge"><title>SPIDevice&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M391,-324.303C391,-332.017 391,-341.288 391,-349.888"/> <path fill="none" stroke="black" d="M89.7736,-315C81.5203,-315 72.5448,-315 64.0586,-315"/>
<polygon fill="black" stroke="black" points="387.5,-349.896 391,-359.896 394.5,-349.896 387.5,-349.896"/> <polygon fill="black" stroke="black" points="64.0313,-311.5 54.0313,-315 64.0312,-318.5 64.0313,-311.5"/>
</g> </g>
<!-- AnalogInputDevice --> <!-- AnalogInputDevice -->
<g id="node3" class="node"><title>AnalogInputDevice</title> <g id="node3" class="node"><title>AnalogInputDevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="444,-252 338,-252 338,-216 444,-216 444,-252"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="296,-333 190,-333 190,-297 296,-297 296,-333"/>
<text text-anchor="middle" x="391" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">AnalogInputDevice</text> <text text-anchor="middle" x="243" y="-312.5" font-family="Sans" font-size="10.00" fill="#000000">AnalogInputDevice</text>
</g> </g>
<!-- AnalogInputDevice&#45;&gt;SPIDevice --> <!-- AnalogInputDevice&#45;&gt;SPIDevice -->
<g id="edge2" class="edge"><title>AnalogInputDevice&#45;&gt;SPIDevice</title> <g id="edge2" class="edge"><title>AnalogInputDevice&#45;&gt;SPIDevice</title>
<path fill="none" stroke="black" d="M391,-252.303C391,-260.017 391,-269.288 391,-277.888"/> <path fill="none" stroke="black" d="M189.805,-315C181.286,-315 172.565,-315 164.354,-315"/>
<polygon fill="black" stroke="black" points="387.5,-277.896 391,-287.896 394.5,-277.896 387.5,-277.896"/> <polygon fill="black" stroke="black" points="164.279,-311.5 154.279,-315 164.279,-318.5 164.279,-311.5"/>
</g> </g>
<!-- MCP3xxx --> <!-- MCP3xxx -->
<g id="node4" class="node"><title>MCP3xxx</title> <g id="node4" class="node"><title>MCP3xxx</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="420.5,-180 361.5,-180 361.5,-144 420.5,-144 420.5,-180"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="391,-333 332,-333 332,-297 391,-297 391,-333"/>
<text text-anchor="middle" x="391" y="-159.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xxx</text> <text text-anchor="middle" x="361.5" y="-312.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xxx</text>
</g> </g>
<!-- MCP3xxx&#45;&gt;AnalogInputDevice --> <!-- MCP3xxx&#45;&gt;AnalogInputDevice -->
<g id="edge3" class="edge"><title>MCP3xxx&#45;&gt;AnalogInputDevice</title> <g id="edge3" class="edge"><title>MCP3xxx&#45;&gt;AnalogInputDevice</title>
<path fill="none" stroke="black" d="M391,-180.303C391,-188.017 391,-197.288 391,-205.888"/> <path fill="none" stroke="black" d="M331.702,-315C323.869,-315 315.072,-315 306.152,-315"/>
<polygon fill="black" stroke="black" points="387.5,-205.896 391,-215.896 394.5,-205.896 387.5,-205.896"/> <polygon fill="black" stroke="black" points="306.074,-311.5 296.074,-315 306.074,-318.5 306.074,-311.5"/>
</g> </g>
<!-- MCP30xx --> <!-- MCP30xx -->
<g id="node5" class="node"><title>MCP30xx</title> <g id="node5" class="node"><title>MCP30xx</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="221,-108 161,-108 161,-72 221,-72 221,-108"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="487,-468 427,-468 427,-432 487,-432 487,-468"/>
<text text-anchor="middle" x="191" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP30xx</text> <text text-anchor="middle" x="457" y="-447.5" font-family="Sans" font-size="10.00" fill="#000000">MCP30xx</text>
</g> </g>
<!-- MCP30xx&#45;&gt;MCP3xxx --> <!-- MCP30xx&#45;&gt;MCP3xxx -->
<g id="edge4" class="edge"><title>MCP30xx&#45;&gt;MCP3xxx</title> <g id="edge4" class="edge"><title>MCP30xx&#45;&gt;MCP3xxx</title>
<path fill="none" stroke="black" d="M221.376,-101.631C256.184,-113.814 313.335,-133.817 351.556,-147.195"/> <path fill="none" stroke="black" d="M443.39,-431.793C427.357,-408.644 399.546,-368.489 381.082,-341.829"/>
<polygon fill="black" stroke="black" points="350.725,-150.612 361.319,-150.612 353.037,-144.005 350.725,-150.612"/> <polygon fill="black" stroke="black" points="383.78,-339.577 375.209,-333.349 378.025,-343.563 383.78,-339.577"/>
</g> </g>
<!-- MCP32xx --> <!-- MCP32xx -->
<g id="node6" class="node"><title>MCP32xx</title> <g id="node6" class="node"><title>MCP32xx</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="461,-108 401,-108 401,-72 461,-72 461,-108"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="487,-306 427,-306 427,-270 487,-270 487,-306"/>
<text text-anchor="middle" x="431" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP32xx</text> <text text-anchor="middle" x="457" y="-285.5" font-family="Sans" font-size="10.00" fill="#000000">MCP32xx</text>
</g> </g>
<!-- MCP32xx&#45;&gt;MCP3xxx --> <!-- MCP32xx&#45;&gt;MCP3xxx -->
<g id="edge5" class="edge"><title>MCP32xx&#45;&gt;MCP3xxx</title> <g id="edge5" class="edge"><title>MCP32xx&#45;&gt;MCP3xxx</title>
<path fill="none" stroke="black" d="M421.112,-108.303C416.511,-116.356 410.94,-126.106 405.847,-135.018"/> <path fill="none" stroke="black" d="M426.929,-296.395C418.76,-298.754 409.747,-301.356 401.116,-303.849"/>
<polygon fill="black" stroke="black" points="402.696,-133.477 400.774,-143.896 408.774,-136.95 402.696,-133.477"/> <polygon fill="black" stroke="black" points="399.869,-300.566 391.232,-306.703 401.811,-307.291 399.869,-300.566"/>
</g> </g>
<!-- MCP3xx2 --> <!-- MCP3xx2 -->
<g id="node7" class="node"><title>MCP3xx2</title> <g id="node7" class="node"><title>MCP3xx2</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="381,-108 321,-108 321,-72 381,-72 381,-108"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="487,-360 427,-360 427,-324 487,-324 487,-360"/>
<text text-anchor="middle" x="351" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xx2</text> <text text-anchor="middle" x="457" y="-339.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xx2</text>
</g> </g>
<!-- MCP3xx2&#45;&gt;MCP3xxx --> <!-- MCP3xx2&#45;&gt;MCP3xxx -->
<g id="edge7" class="edge"><title>MCP3xx2&#45;&gt;MCP3xxx</title> <g id="edge7" class="edge"><title>MCP3xx2&#45;&gt;MCP3xxx</title>
<path fill="none" stroke="black" d="M360.888,-108.303C365.489,-116.356 371.06,-126.106 376.153,-135.018"/> <path fill="none" stroke="black" d="M426.929,-333.605C418.76,-331.246 409.747,-328.644 401.116,-326.151"/>
<polygon fill="black" stroke="black" points="373.226,-136.95 381.226,-143.896 379.304,-133.477 373.226,-136.95"/> <polygon fill="black" stroke="black" points="401.811,-322.709 391.232,-323.297 399.869,-329.434 401.811,-322.709"/>
</g> </g>
<!-- MCP33xx --> <!-- MCP33xx -->
<g id="node8" class="node"><title>MCP33xx</title> <g id="node8" class="node"><title>MCP33xx</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="741,-108 681,-108 681,-72 741,-72 741,-108"/> <polygon fill="#9ec6e0" stroke="#9ec6e0" points="487,-117 427,-117 427,-81 487,-81 487,-117"/>
<text text-anchor="middle" x="711" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP33xx</text> <text text-anchor="middle" x="457" y="-96.5" font-family="Sans" font-size="10.00" fill="#000000">MCP33xx</text>
</g> </g>
<!-- MCP33xx&#45;&gt;MCP3xxx --> <!-- MCP33xx&#45;&gt;MCP3xxx -->
<g id="edge6" class="edge"><title>MCP33xx&#45;&gt;MCP3xxx</title> <g id="edge6" class="edge"><title>MCP33xx&#45;&gt;MCP3xxx</title>
<path fill="none" stroke="black" d="M680.895,-97.5854C622.855,-110.282 495.498,-138.141 430.644,-152.328"/> <path fill="none" stroke="black" d="M448.189,-117.045C431.827,-154.844 393.862,-242.549 374.428,-287.444"/>
<polygon fill="black" stroke="black" points="429.813,-148.927 420.792,-154.483 431.309,-155.765 429.813,-148.927"/> <polygon fill="black" stroke="black" points="371.115,-286.287 370.354,-296.855 377.539,-289.068 371.115,-286.287"/>
</g> </g>
<!-- MCP3001 --> <!-- MCP3001 -->
<g id="node9" class="node"><title>MCP3001</title> <g id="node9" class="node"><title>MCP3001</title>
<polygon fill="#2980b9" stroke="#2980b9" points="142,-36 80,-36 80,-0 142,-0 142,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-576 523,-576 523,-540 585,-540 585,-576"/>
<text text-anchor="middle" x="111" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3001</text> <text text-anchor="middle" x="554" y="-555.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3001</text>
</g> </g>
<!-- MCP3001&#45;&gt;MCP30xx --> <!-- MCP3001&#45;&gt;MCP30xx -->
<g id="edge8" class="edge"><title>MCP3001&#45;&gt;MCP30xx</title> <g id="edge8" class="edge"><title>MCP3001&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M130.775,-36.3034C140.754,-45.0345 153.011,-55.7595 163.857,-65.2497"/> <path fill="none" stroke="black" d="M531.931,-539.831C528.858,-536.955 525.791,-533.954 523,-531 506.549,-513.591 489.668,-492.438 477.234,-476.115"/>
<polygon fill="black" stroke="black" points="161.622,-67.9446 171.452,-71.8957 166.231,-62.6766 161.622,-67.9446"/> <polygon fill="black" stroke="black" points="479.978,-473.941 471.163,-468.065 474.389,-478.156 479.978,-473.941"/>
</g> </g>
<!-- MCP3002 --> <!-- MCP3002 -->
<g id="node10" class="node"><title>MCP3002</title> <g id="node10" class="node"><title>MCP3002</title>
<polygon fill="#2980b9" stroke="#2980b9" points="302,-36 240,-36 240,-0 302,-0 302,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-414 523,-414 523,-378 585,-378 585,-414"/>
<text text-anchor="middle" x="271" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3002</text> <text text-anchor="middle" x="554" y="-393.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3002</text>
</g> </g>
<!-- MCP3002&#45;&gt;MCP30xx --> <!-- MCP3002&#45;&gt;MCP30xx -->
<g id="edge9" class="edge"><title>MCP3002&#45;&gt;MCP30xx</title> <g id="edge9" class="edge"><title>MCP3002&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M251.225,-36.3034C241.246,-45.0345 228.989,-55.7595 218.143,-65.2497"/> <path fill="none" stroke="black" d="M522.941,-413.086C514.38,-417.953 504.94,-423.318 495.973,-428.415"/>
<polygon fill="black" stroke="black" points="215.769,-62.6766 210.548,-71.8957 220.378,-67.9446 215.769,-62.6766"/> <polygon fill="black" stroke="black" points="494.178,-425.41 487.214,-433.394 497.637,-431.496 494.178,-425.41"/>
</g> </g>
<!-- MCP3002&#45;&gt;MCP3xx2 --> <!-- MCP3002&#45;&gt;MCP3xx2 -->
<g id="edge16" class="edge"><title>MCP3002&#45;&gt;MCP3xx2</title> <g id="edge16" class="edge"><title>MCP3002&#45;&gt;MCP3xx2</title>
<path fill="none" stroke="black" d="M290.775,-36.3034C300.754,-45.0345 313.011,-55.7595 323.857,-65.2497"/> <path fill="none" stroke="black" d="M522.941,-378.914C514.38,-374.047 504.94,-368.682 495.973,-363.585"/>
<polygon fill="black" stroke="black" points="321.622,-67.9446 331.452,-71.8957 326.231,-62.6766 321.622,-67.9446"/> <polygon fill="black" stroke="black" points="497.637,-360.504 487.214,-358.606 494.178,-366.59 497.637,-360.504"/>
</g> </g>
<!-- MCP3004 --> <!-- MCP3004 -->
<g id="node11" class="node"><title>MCP3004</title> <g id="node11" class="node"><title>MCP3004</title>
<polygon fill="#2980b9" stroke="#2980b9" points="222,-36 160,-36 160,-0 222,-0 222,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-522 523,-522 523,-486 585,-486 585,-522"/>
<text text-anchor="middle" x="191" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3004</text> <text text-anchor="middle" x="554" y="-501.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3004</text>
</g> </g>
<!-- MCP3004&#45;&gt;MCP30xx --> <!-- MCP3004&#45;&gt;MCP30xx -->
<g id="edge10" class="edge"><title>MCP3004&#45;&gt;MCP30xx</title> <g id="edge10" class="edge"><title>MCP3004&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M191,-36.3034C191,-44.0173 191,-53.2875 191,-61.8876"/> <path fill="none" stroke="black" d="M522.941,-486.914C514.38,-482.047 504.94,-476.682 495.973,-471.585"/>
<polygon fill="black" stroke="black" points="187.5,-61.8956 191,-71.8957 194.5,-61.8957 187.5,-61.8956"/> <polygon fill="black" stroke="black" points="497.637,-468.504 487.214,-466.606 494.178,-474.59 497.637,-468.504"/>
</g> </g>
<!-- MCP3008 --> <!-- MCP3008 -->
<g id="node12" class="node"><title>MCP3008</title> <g id="node12" class="node"><title>MCP3008</title>
<polygon fill="#2980b9" stroke="#2980b9" points="62,-36 3.55271e-15,-36 3.55271e-15,-0 62,-0 62,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-468 523,-468 523,-432 585,-432 585,-468"/>
<text text-anchor="middle" x="31" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3008</text> <text text-anchor="middle" x="554" y="-447.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3008</text>
</g> </g>
<!-- MCP3008&#45;&gt;MCP30xx --> <!-- MCP3008&#45;&gt;MCP30xx -->
<g id="edge11" class="edge"><title>MCP3008&#45;&gt;MCP30xx</title> <g id="edge11" class="edge"><title>MCP3008&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M62.2294,-32.6629C87.7738,-43.8385 124.046,-59.7076 151.587,-71.757"/> <path fill="none" stroke="black" d="M522.941,-450C514.826,-450 505.921,-450 497.378,-450"/>
<polygon fill="black" stroke="black" points="150.369,-75.044 160.933,-75.8457 153.174,-68.6309 150.369,-75.044"/> <polygon fill="black" stroke="black" points="497.214,-446.5 487.214,-450 497.213,-453.5 497.214,-446.5"/>
</g> </g>
<!-- MCP3201 --> <!-- MCP3201 -->
<g id="node13" class="node"><title>MCP3201</title> <g id="node13" class="node"><title>MCP3201</title>
<polygon fill="#2980b9" stroke="#2980b9" points="542,-36 480,-36 480,-0 542,-0 542,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-306 523,-306 523,-270 585,-270 585,-306"/>
<text text-anchor="middle" x="511" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3201</text> <text text-anchor="middle" x="554" y="-285.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3201</text>
</g> </g>
<!-- MCP3201&#45;&gt;MCP32xx --> <!-- MCP3201&#45;&gt;MCP32xx -->
<g id="edge12" class="edge"><title>MCP3201&#45;&gt;MCP32xx</title> <g id="edge12" class="edge"><title>MCP3201&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M491.225,-36.3034C481.246,-45.0345 468.989,-55.7595 458.143,-65.2497"/> <path fill="none" stroke="black" d="M522.941,-288C514.826,-288 505.921,-288 497.378,-288"/>
<polygon fill="black" stroke="black" points="455.769,-62.6766 450.548,-71.8957 460.378,-67.9446 455.769,-62.6766"/> <polygon fill="black" stroke="black" points="497.214,-284.5 487.214,-288 497.213,-291.5 497.214,-284.5"/>
</g> </g>
<!-- MCP3202 --> <!-- MCP3202 -->
<g id="node14" class="node"><title>MCP3202</title> <g id="node14" class="node"><title>MCP3202</title>
<polygon fill="#2980b9" stroke="#2980b9" points="382,-36 320,-36 320,-0 382,-0 382,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-360 523,-360 523,-324 585,-324 585,-360"/>
<text text-anchor="middle" x="351" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3202</text> <text text-anchor="middle" x="554" y="-339.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3202</text>
</g> </g>
<!-- MCP3202&#45;&gt;MCP32xx --> <!-- MCP3202&#45;&gt;MCP32xx -->
<g id="edge13" class="edge"><title>MCP3202&#45;&gt;MCP32xx</title> <g id="edge13" class="edge"><title>MCP3202&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M370.775,-36.3034C380.754,-45.0345 393.011,-55.7595 403.857,-65.2497"/> <path fill="none" stroke="black" d="M522.941,-324.914C514.38,-320.047 504.94,-314.682 495.973,-309.585"/>
<polygon fill="black" stroke="black" points="401.622,-67.9446 411.452,-71.8957 406.231,-62.6766 401.622,-67.9446"/> <polygon fill="black" stroke="black" points="497.637,-306.504 487.214,-304.606 494.178,-312.59 497.637,-306.504"/>
</g> </g>
<!-- MCP3202&#45;&gt;MCP3xx2 --> <!-- MCP3202&#45;&gt;MCP3xx2 -->
<g id="edge17" class="edge"><title>MCP3202&#45;&gt;MCP3xx2</title> <g id="edge17" class="edge"><title>MCP3202&#45;&gt;MCP3xx2</title>
<path fill="none" stroke="black" d="M351,-36.3034C351,-44.0173 351,-53.2875 351,-61.8876"/> <path fill="none" stroke="black" d="M522.941,-342C514.826,-342 505.921,-342 497.378,-342"/>
<polygon fill="black" stroke="black" points="347.5,-61.8956 351,-71.8957 354.5,-61.8957 347.5,-61.8956"/> <polygon fill="black" stroke="black" points="497.214,-338.5 487.214,-342 497.213,-345.5 497.214,-338.5"/>
</g> </g>
<!-- MCP3204 --> <!-- MCP3204 -->
<g id="node15" class="node"><title>MCP3204</title> <g id="node15" class="node"><title>MCP3204</title>
<polygon fill="#2980b9" stroke="#2980b9" points="622,-36 560,-36 560,-0 622,-0 622,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-252 523,-252 523,-216 585,-216 585,-252"/>
<text text-anchor="middle" x="591" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3204</text> <text text-anchor="middle" x="554" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3204</text>
</g> </g>
<!-- MCP3204&#45;&gt;MCP32xx --> <!-- MCP3204&#45;&gt;MCP32xx -->
<g id="edge14" class="edge"><title>MCP3204&#45;&gt;MCP32xx</title> <g id="edge14" class="edge"><title>MCP3204&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M559.771,-32.6629C534.226,-43.8385 497.954,-59.7076 470.413,-71.757"/> <path fill="none" stroke="black" d="M522.941,-251.086C514.38,-255.953 504.94,-261.318 495.973,-266.415"/>
<polygon fill="black" stroke="black" points="468.826,-68.6309 461.067,-75.8457 471.631,-75.044 468.826,-68.6309"/> <polygon fill="black" stroke="black" points="494.178,-263.41 487.214,-271.394 497.637,-269.496 494.178,-263.41"/>
</g> </g>
<!-- MCP3208 --> <!-- MCP3208 -->
<g id="node16" class="node"><title>MCP3208</title> <g id="node16" class="node"><title>MCP3208</title>
<polygon fill="#2980b9" stroke="#2980b9" points="462,-36 400,-36 400,-0 462,-0 462,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-198 523,-198 523,-162 585,-162 585,-198"/>
<text text-anchor="middle" x="431" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3208</text> <text text-anchor="middle" x="554" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3208</text>
</g> </g>
<!-- MCP3208&#45;&gt;MCP32xx --> <!-- MCP3208&#45;&gt;MCP32xx -->
<g id="edge15" class="edge"><title>MCP3208&#45;&gt;MCP32xx</title> <g id="edge15" class="edge"><title>MCP3208&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M431,-36.3034C431,-44.0173 431,-53.2875 431,-61.8876"/> <path fill="none" stroke="black" d="M531.931,-198.169C528.858,-201.045 525.791,-204.046 523,-207 506.549,-224.409 489.668,-245.562 477.234,-261.885"/>
<polygon fill="black" stroke="black" points="427.5,-61.8956 431,-71.8957 434.5,-61.8957 427.5,-61.8956"/> <polygon fill="black" stroke="black" points="474.389,-259.844 471.163,-269.935 479.978,-264.059 474.389,-259.844"/>
</g> </g>
<!-- MCP3301 --> <!-- MCP3301 -->
<g id="node17" class="node"><title>MCP3301</title> <g id="node17" class="node"><title>MCP3301</title>
<polygon fill="#2980b9" stroke="#2980b9" points="782,-36 720,-36 720,-0 782,-0 782,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-144 523,-144 523,-108 585,-108 585,-144"/>
<text text-anchor="middle" x="751" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3301</text> <text text-anchor="middle" x="554" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3301</text>
</g> </g>
<!-- MCP3301&#45;&gt;MCP33xx --> <!-- MCP3301&#45;&gt;MCP33xx -->
<g id="edge18" class="edge"><title>MCP3301&#45;&gt;MCP33xx</title> <g id="edge18" class="edge"><title>MCP3301&#45;&gt;MCP33xx</title>
<path fill="none" stroke="black" d="M741.112,-36.3034C736.511,-44.3564 730.94,-54.1055 725.847,-63.0176"/> <path fill="none" stroke="black" d="M522.941,-117.457C514.737,-115.125 505.725,-112.564 497.096,-110.112"/>
<polygon fill="black" stroke="black" points="722.696,-61.4767 720.774,-71.8957 728.774,-64.9497 722.696,-61.4767"/> <polygon fill="black" stroke="black" points="497.789,-106.67 487.214,-107.303 495.876,-113.403 497.789,-106.67"/>
</g> </g>
<!-- MCP3302 --> <!-- MCP3302 -->
<g id="node18" class="node"><title>MCP3302</title> <g id="node18" class="node"><title>MCP3302</title>
<polygon fill="#2980b9" stroke="#2980b9" points="862,-36 800,-36 800,-0 862,-0 862,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-90 523,-90 523,-54 585,-54 585,-90"/>
<text text-anchor="middle" x="831" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3302</text> <text text-anchor="middle" x="554" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3302</text>
</g> </g>
<!-- MCP3302&#45;&gt;MCP33xx --> <!-- MCP3302&#45;&gt;MCP33xx -->
<g id="edge19" class="edge"><title>MCP3302&#45;&gt;MCP33xx</title> <g id="edge19" class="edge"><title>MCP3302&#45;&gt;MCP33xx</title>
<path fill="none" stroke="black" d="M801.645,-36.1239C785.955,-45.2764 766.443,-56.6583 749.546,-66.515"/> <path fill="none" stroke="black" d="M522.941,-80.543C514.737,-82.8749 505.725,-85.4359 497.096,-87.8884"/>
<polygon fill="black" stroke="black" points="747.342,-63.7483 740.468,-71.8102 750.87,-69.7947 747.342,-63.7483"/> <polygon fill="black" stroke="black" points="495.876,-84.5966 487.214,-90.6972 497.789,-91.33 495.876,-84.5966"/>
</g> </g>
<!-- MCP3304 --> <!-- MCP3304 -->
<g id="node19" class="node"><title>MCP3304</title> <g id="node19" class="node"><title>MCP3304</title>
<polygon fill="#2980b9" stroke="#2980b9" points="702,-36 640,-36 640,-0 702,-0 702,-36"/> <polygon fill="#2980b9" stroke="#2980b9" points="585,-36 523,-36 523,-0 585,-0 585,-36"/>
<text text-anchor="middle" x="671" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3304</text> <text text-anchor="middle" x="554" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3304</text>
</g> </g>
<!-- MCP3304&#45;&gt;MCP33xx --> <!-- MCP3304&#45;&gt;MCP33xx -->
<g id="edge20" class="edge"><title>MCP3304&#45;&gt;MCP33xx</title> <g id="edge20" class="edge"><title>MCP3304&#45;&gt;MCP33xx</title>
<path fill="none" stroke="black" d="M680.888,-36.3034C685.489,-44.3564 691.06,-54.1055 696.153,-63.0176"/> <path fill="none" stroke="black" d="M531.443,-36.3802C518.233,-47.6438 501.16,-62.2002 486.801,-74.4434"/>
<polygon fill="black" stroke="black" points="693.226,-64.9497 701.226,-71.8957 699.304,-61.4767 693.226,-64.9497"/> <polygon fill="black" stroke="black" points="484.483,-71.8201 479.144,-80.9716 489.025,-77.1468 484.483,-71.8201"/>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -18,10 +18,12 @@ Table of Contents
api_tools api_tools
api_pins api_pins
api_exc api_exc
cli_tools
source_values source_values
remote_gpio remote_gpio
recipes_advanced recipes_advanced
recipes_remote_gpio recipes_remote_gpio
contributing contributing
development
changelog changelog
license license

View File

@@ -4,6 +4,7 @@ Notes
.. currentmodule:: gpiozero .. currentmodule:: gpiozero
.. _keep-your-script-running: .. _keep-your-script-running:
Keep your script running Keep your script running
@@ -45,6 +46,7 @@ events to be detected::
button.when_pressed = hello button.when_pressed = hello
pause() pause()
Importing from GPIO Zero Importing from GPIO Zero
======================== ========================
@@ -69,12 +71,15 @@ In this case, all references to items within GPIO Zero must be prefixed::
button = gpiozero.Button(2) button = gpiozero.Button(2)
How can I tell what version of gpiozero I have installed? How can I tell what version of gpiozero I have installed?
========================================================= =========================================================
The gpiozero library relies on the setuptools package for installation The gpiozero library relies on the setuptools package for installation
services. You can use the setuptools ``pkg_resources`` API to query which services. You can use the setuptools ``pkg_resources`` API to query which
version of gpiozero is available in your Python environment like so:: version of gpiozero is available in your Python environment like so:
.. code-block:: pycon
>>> from pkg_resources import require >>> from pkg_resources import require
>>> require('gpiozero') >>> require('gpiozero')
@@ -88,7 +93,9 @@ first entry in the list will be the version that ``import gpiozero`` will
import. import.
If you receive the error "No module named pkg_resources", you need to install If you receive the error "No module named pkg_resources", you need to install
the ``pip`` utility. This can be done with the following command in Raspbian:: the ``pip`` utility. This can be done with the following command in Raspbian:
.. code-block:: console
sudo apt install python-pip sudo apt install python-pip

View File

@@ -8,7 +8,7 @@ The following recipes demonstrate some of the capabilities of the GPIO Zero
library. Please note that all recipes are written assuming Python 3. Recipes library. Please note that all recipes are written assuming Python 3. Recipes
*may* work under Python 2, but no guarantees! *may* work under Python 2, but no guarantees!
.. _pin_numbering: .. _pin-numbering:
Pin Numbering Pin Numbering
============= =============

View File

@@ -6,8 +6,9 @@ from __future__ import (
) )
from .pins import ( from .pins import (
Factory,
Pin, Pin,
LocalPin, SPI,
) )
from .pins.data import ( from .pins.data import (
PiBoardInfo, PiBoardInfo,
@@ -15,47 +16,9 @@ from .pins.data import (
PinInfo, PinInfo,
pi_info, pi_info,
) )
from .exc import ( # Yes, import * is naughty, but exc imports nothing else so there's no cross
GPIOZeroError, # contamination here ... and besides, have you *seen* the list lately?!
DeviceClosed, from .exc import *
BadEventHandler,
BadWaitTime,
BadQueueLen,
CompositeDeviceError,
CompositeDeviceBadName,
CompositeDeviceBadOrder,
CompositeDeviceBadDevice,
SPIError,
SPIBadArgs,
EnergenieSocketMissing,
EnergenieBadSocket,
GPIODeviceError,
GPIODeviceClosed,
GPIOPinInUse,
GPIOPinMissing,
InputDeviceError,
OutputDeviceError,
OutputDeviceBadValue,
PinError,
PinInvalidFunction,
PinInvalidState,
PinInvalidPull,
PinInvalidEdges,
PinSetInput,
PinFixedPull,
PinEdgeDetectUnsupported,
PinPWMError,
PinPWMUnsupported,
PinPWMFixedValue,
PinUnknownPi,
PinMultiplePins,
PinNoPins,
GPIOZeroWarning,
SPIWarning,
SPISoftwareFallback,
PinWarning,
PinNonPhysical,
)
from .devices import ( from .devices import (
Device, Device,
GPIODevice, GPIODevice,

0
gpiozero/cli/__init__.py Normal file
View File

67
gpiozero/cli/pinout.py Executable file
View File

@@ -0,0 +1,67 @@
#!/usr/bin/env python
"""
pinout - gpiozero command-line pinout tool.
Output Raspberry Pi GPIO pinout information.
"""
from __future__ import unicode_literals, absolute_import, print_function, division
import argparse
import sys
from gpiozero import pi_info
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:
# 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):
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)
print('')
print('For further information, please refer to https://pinout.xyz/')
main = PinoutTool()

View File

@@ -9,6 +9,7 @@ from __future__ import (
str = type('') str = type('')
import cmath import cmath
import weakref
import collections import collections
import operator import operator
import functools import functools
@@ -81,3 +82,59 @@ class frozendict(collections.Mapping):
hashes = map(hash, self.items()) hashes = map(hash, self.items())
self.__hash = functools.reduce(operator.xor, hashes, 0) self.__hash = functools.reduce(operator.xor, hashes, 0)
return self.__hash 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__

View File

@@ -10,15 +10,16 @@ str = type('')
import os import os
import atexit import atexit
import weakref import weakref
from collections import namedtuple import warnings
from collections import namedtuple, defaultdict
from itertools import chain from itertools import chain
from types import FunctionType from types import FunctionType
from threading import RLock from threading import Lock
import pkg_resources import pkg_resources
from .pins import Pin
from .threads import _threads_shutdown from .threads import _threads_shutdown
from .pins import _pins_shutdown
from .mixins import ( from .mixins import (
ValuesMixin, ValuesMixin,
SharedMixin, SharedMixin,
@@ -32,52 +33,11 @@ from .exc import (
GPIOPinMissing, GPIOPinMissing,
GPIOPinInUse, GPIOPinInUse,
GPIODeviceClosed, GPIODeviceClosed,
PinFactoryFallback,
) )
from .compat import frozendict from .compat import frozendict
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 ('RPiGPIOPin', 'RPIOPin', 'PiGPIOPin', 'NativePin'):
try:
return pkg_resources.load_entry_point(dist, group, name)
except ImportError:
pass
raise BadPinFactory('Unable to locate any default pin factory!')
else:
for factory in pkg_resources.iter_entry_points(group, name):
return factory.load()
raise BadPinFactory('Unable to locate pin factory "%s"' % name)
pin_factory = _default_pin_factory()
_PINS = set()
_PINS_LOCK = RLock() # Yes, this needs to be re-entrant
def _shutdown():
_threads_shutdown()
with _PINS_LOCK:
while _PINS:
_PINS.pop().close()
# Any cleanup routines registered by pins libraries must be called *after*
# cleanup of pin objects used by devices
_pins_shutdown()
atexit.register(_shutdown)
class GPIOMeta(type): class GPIOMeta(type):
# NOTE Yes, this is a metaclass. Don't be scared - it's a simple one. # NOTE Yes, this is a metaclass. Don't be scared - it's a simple one.
@@ -106,7 +66,7 @@ class GPIOMeta(type):
# already exists. Only construct the instance if the key's new. # already exists. Only construct the instance if the key's new.
key = cls._shared_key(*args, **kwargs) key = cls._shared_key(*args, **kwargs)
try: try:
self = cls._INSTANCES[key] self = cls._instances[key]
self._refs += 1 self._refs += 1
except (KeyError, ReferenceError) as e: except (KeyError, ReferenceError) as e:
self = super(GPIOMeta, cls).__call__(*args, **kwargs) self = super(GPIOMeta, cls).__call__(*args, **kwargs)
@@ -122,14 +82,14 @@ class GPIOMeta(type):
old_close() old_close()
finally: finally:
try: try:
del cls._INSTANCES[key] del cls._instances[key]
except KeyError: except KeyError:
# If the _refs go negative (too many closes) # If the _refs go negative (too many closes)
# just ignore the resulting KeyError here - # just ignore the resulting KeyError here -
# it's already gone # it's already gone
pass pass
self.close = close self.close = close
cls._INSTANCES[key] = weakref.proxy(self) cls._instances[key] = weakref.proxy(self)
else: else:
# Construct the instance as normal # Construct the instance as normal
self = super(GPIOMeta, cls).__call__(*args, **kwargs) self = super(GPIOMeta, cls).__call__(*args, **kwargs)
@@ -229,13 +189,89 @@ class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})):
class Device(ValuesMixin, GPIOBase): class Device(ValuesMixin, GPIOBase):
""" """
Represents a single device of any type; GPIO-based, SPI-based, I2C-based, 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 etc. This is the base class of the device hierarchy. It defines the basic
basic services applicable to all devices (specifically the :attr:`is_active` services applicable to all devices (specifically the :attr:`is_active`
property, the :attr:`value` property, and the :meth:`close` method). property, the :attr:`value` property, and the :meth:`close` method).
""" """
pin_factory = None # instance of a Factory sub-class
_reservations = defaultdict(list) # maps pin addresses to lists of devices
_res_lock = Lock()
def __repr__(self): def __repr__(self):
return "<gpiozero.%s object>" % (self.__class__.__name__) return "<gpiozero.%s object>" % (self.__class__.__name__)
def _reserve_pins(self, *pins_or_addresses):
"""
Called to indicate that the device reserves the right to use the
specified *pins_or_addresses*. 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`.
The *pins_or_addresses* can be actual :class:`Pin` instances or the
addresses of pin instances (each address is a tuple of strings). The
latter form is permitted to ensure that devices do not have to
construct :class:`Pin` objects to reserve pins. This is important as
constructing a pin often configures it (e.g. as an input) which
conflicts with alternate pin functions like SPI.
"""
addresses = (
p.address if isinstance(p, Pin) else p
for p in pins_or_addresses
)
with Device._res_lock:
for address in addresses:
for device_ref in Device._reservations[address]:
device = device_ref()
if device is not None and self._conflicts_with(device):
raise GPIOPinInUse(
'pin %s is already in use by %r' % (
'/'.join(address), device)
)
Device._reservations[address].append(weakref.ref(self))
def _release_pins(self, *pins_or_addresses):
"""
Releases the reservation of this device against *pins_or_addresses*.
This is typically called during :meth:`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).
"""
addresses = (
p.address if isinstance(p, Pin) else p
for p in pins_or_addresses
)
with Device._res_lock:
for address in addresses:
Device._reservations[address] = [
ref for ref in Device._reservations[address]
if ref() not in (self, None) # may as well clean up dead refs
]
def _release_all(self):
"""
Releases all pin reservations taken out by this device. See
:meth:`_release_pins` for further information).
"""
with Device._res_lock:
Device._reservations = defaultdict(list, {
address: [
ref for ref in conflictors
if ref() not in (self, None)
]
for address, conflictors in Device._reservations.items()
})
def _conflicts_with(self, other):
"""
Called by :meth:`_reserve_pin` 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 @property
def value(self): def value(self):
""" """
@@ -378,14 +414,12 @@ class GPIODevice(Device):
self._pin = None self._pin = None
if pin is None: if pin is None:
raise GPIOPinMissing('No pin given') raise GPIOPinMissing('No pin given')
if isinstance(pin, int): if isinstance(pin, Pin):
pin = pin_factory(pin) self._reserve_pins(pin)
with _PINS_LOCK: else:
if pin in _PINS: # Check you can reserve *before* constructing the pin
raise GPIOPinInUse( self._reserve_pins(Device.pin_factory.pin_address(pin))
'pin %r is already in use by another gpiozero object' % pin pin = Device.pin_factory.pin(pin)
)
_PINS.add(pin)
self._pin = pin self._pin = pin
self._active_state = True self._active_state = True
self._inactive_state = False self._inactive_state = False
@@ -402,12 +436,10 @@ class GPIODevice(Device):
def close(self): def close(self):
super(GPIODevice, self).close() super(GPIODevice, self).close()
with _PINS_LOCK: if self._pin is not None:
pin = self._pin self._release_pins(self._pin)
self._pin.close()
self._pin = None self._pin = None
if pin in _PINS:
_PINS.remove(pin)
pin.close()
@property @property
def closed(self): def closed(self):
@@ -441,3 +473,57 @@ class GPIODevice(Device):
except DeviceClosed: except DeviceClosed:
return "<gpiozero.%s object closed>" % self.__class__.__name__ return "<gpiozero.%s object closed>" % 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(
'Failed to load factory %s: %s' % (name, str(e))))
raise BadPinFactory('Unable to load any default pin factory!')
else:
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._res_lock:
reserved_devices = {
dev
for ref_list in Device._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)

View File

@@ -52,6 +52,24 @@ class SPIBadArgs(SPIError, ValueError):
class SPIBadChannel(SPIError, ValueError): class SPIBadChannel(SPIError, ValueError):
"Error raised when an invalid channel is given to an :class:`AnalogInputDevice`" "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): class GPIODeviceError(GPIOZeroError):
"Base class for errors specific to the GPIODevice hierarchy" "Base class for errors specific to the GPIODevice hierarchy"
@@ -62,7 +80,7 @@ class GPIOPinInUse(GPIODeviceError):
"Error raised when attempting to use a pin already in use by another device" "Error raised when attempting to use a pin already in use by another device"
class GPIOPinMissing(GPIODeviceError, ValueError): class GPIOPinMissing(GPIODeviceError, ValueError):
"Error raised when a pin number is not specified" "Error raised when a pin specification is not given"
class InputDeviceError(GPIODeviceError): class InputDeviceError(GPIODeviceError):
"Base class for errors specific to the InputDevice hierarchy" "Base class for errors specific to the InputDevice hierarchy"
@@ -100,6 +118,12 @@ class PinFixedPull(PinError, AttributeError):
class PinEdgeDetectUnsupported(PinError, AttributeError): class PinEdgeDetectUnsupported(PinError, AttributeError):
"Error raised when attempting to use edge detection on unsupported pins" "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): class PinPWMError(PinError):
"Base class for errors related to PWM implementations" "Base class for errors related to PWM implementations"
@@ -118,6 +142,9 @@ class PinMultiplePins(PinError, RuntimeError):
class PinNoPins(PinError, RuntimeError): class PinNoPins(PinError, RuntimeError):
"Error raised when no pins support the requested function" "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): class GPIOZeroWarning(Warning):
"Base class for all warnings in GPIO Zero" "Base class for all warnings in GPIO Zero"
@@ -130,6 +157,9 @@ class SPISoftwareFallback(SPIWarning):
class PinWarning(GPIOZeroWarning): class PinWarning(GPIOZeroWarning):
"Base class for warnings related to pin implementations" "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): class PinNonPhysical(PinWarning):
"Warning raised when a non-physical pin is specified in a constructor" "Warning raised when a non-physical pin is specified in a constructor"

View File

@@ -165,7 +165,7 @@ class SmoothedInputDevice(EventsMixin, InputDevice):
if self.partial or self._queue.full.is_set(): if self.partial or self._queue.full.is_set():
return super(SmoothedInputDevice, self).__repr__() return super(SmoothedInputDevice, self).__repr__()
else: else:
return "<gpiozero.%s object on pin=%r, pull_up=%s>" % ( return "<gpiozero.%s object on pin %r, pull_up=%s>" % (
self.__class__.__name__, self.pin, self.pull_up) self.__class__.__name__, self.pin, self.pull_up)
@property @property
@@ -240,7 +240,7 @@ class Button(HoldMixin, DigitalInputDevice):
print("The button was pressed!") print("The button was pressed!")
:param int pin: :param int pin:
The GPIO pin which the button is attached to. See :ref:`pin_numbering` The GPIO pin which the button is attached to. See :ref:`pin-numbering`
for valid pin numbers. for valid pin numbers.
:param bool pull_up: :param bool pull_up:
@@ -302,7 +302,7 @@ class LineSensor(SmoothedInputDevice):
pause() pause()
:param int pin: :param int pin:
The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` The GPIO pin which the sensor is attached to. See :ref:`pin-numbering`
for valid pin numbers. for valid pin numbers.
:param int queue_len: :param int queue_len:
@@ -371,7 +371,7 @@ class MotionSensor(SmoothedInputDevice):
print("Motion detected!") print("Motion detected!")
:param int pin: :param int pin:
The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` The GPIO pin which the sensor is attached to. See :ref:`pin-numbering`
for valid pin numbers. for valid pin numbers.
:param int queue_len: :param int queue_len:
@@ -435,7 +435,7 @@ class LightSensor(SmoothedInputDevice):
print("Light detected!") print("Light detected!")
:param int pin: :param int pin:
The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` The GPIO pin which the sensor is attached to. See :ref:`pin-numbering`
for valid pin numbers. for valid pin numbers.
:param int queue_len: :param int queue_len:
@@ -543,11 +543,11 @@ class DistanceSensor(SmoothedInputDevice):
:param int echo: :param int echo:
The GPIO pin which the ECHO pin is attached to. See The GPIO pin which the ECHO pin is attached to. See
:ref:`pin_numbering` for valid pin numbers. :ref:`pin-numbering` for valid pin numbers.
:param int trigger: :param int trigger:
The GPIO pin which the TRIG pin is attached to. See The GPIO pin which the TRIG pin is attached to. See
:ref:`pin_numbering` for valid pin numbers. :ref:`pin-numbering` for valid pin numbers.
:param int queue_len: :param int queue_len:
The length of the queue used to store values read from the sensor. The length of the queue used to store values read from the sensor.

View File

@@ -69,11 +69,14 @@ class SourceMixin(object):
super(SourceMixin, self).__init__(*args, **kwargs) super(SourceMixin, self).__init__(*args, **kwargs)
def close(self): def close(self):
try:
self.source = None
except AttributeError:
pass
try: try:
super(SourceMixin, self).close() super(SourceMixin, self).close()
except AttributeError: except AttributeError:
pass pass
self.source = None
def _copy_values(self, source): def _copy_values(self, source):
for v in source: for v in source:
@@ -127,7 +130,7 @@ class SharedMixin(object):
When :meth:`close` is called, an internal reference counter will be When :meth:`close` is called, an internal reference counter will be
decremented and the instance will only close when it reaches zero. decremented and the instance will only close when it reaches zero.
""" """
_INSTANCES = {} _instances = {}
def __del__(self): def __del__(self):
self._refs = 0 self._refs = 0
@@ -438,11 +441,13 @@ class HoldThread(GPIOThread):
device is active. device is active.
""" """
def __init__(self, parent): def __init__(self, parent):
super(HoldThread, self).__init__(target=self.held, args=(parent,)) super(HoldThread, self).__init__(
target=self.held, args=(weakref.proxy(parent),))
self.holding = Event() self.holding = Event()
self.start() self.start()
def held(self, parent): def held(self, parent):
try:
while not self.stopping.is_set(): while not self.stopping.is_set():
if self.holding.wait(0.1): if self.holding.wait(0.1):
self.holding.clear() self.holding.clear()
@@ -455,6 +460,9 @@ class HoldThread(GPIOThread):
parent._fire_held() parent._fire_held()
if not parent.hold_repeat: if not parent.hold_repeat:
break break
except ReferenceError:
# Parent is dead; time to die!
pass
class GPIOQueue(GPIOThread): class GPIOQueue(GPIOThread):

View File

@@ -56,7 +56,7 @@ class PingServer(InternalDevice):
self._fire_events() self._fire_events()
def __repr__(self): def __repr__(self):
return '<gpiozero.PingDevice host="%s">' % self.host return '<gpiozero.PingServer host="%s">' % self.host
@property @property
def value(self): def value(self):

View File

@@ -128,8 +128,8 @@ class DigitalOutputDevice(OutputDevice):
""" """
def __init__(self, pin=None, active_high=True, initial_value=False): def __init__(self, pin=None, active_high=True, initial_value=False):
self._blink_thread = None self._blink_thread = None
super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value)
self._controller = None self._controller = None
super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value)
@property @property
def value(self): def value(self):
@@ -217,7 +217,7 @@ class LED(DigitalOutputDevice):
led.on() led.on()
:param int pin: :param int pin:
The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for The GPIO pin which the LED is attached to. See :ref:`pin-numbering` for
valid pin numbers. valid pin numbers.
:param bool active_high: :param bool active_high:
@@ -252,7 +252,7 @@ class Buzzer(DigitalOutputDevice):
bz.on() bz.on()
:param int pin: :param int pin:
The GPIO pin which the buzzer is attached to. See :ref:`pin_numbering` The GPIO pin which the buzzer is attached to. See :ref:`pin-numbering`
for valid pin numbers. for valid pin numbers.
:param bool active_high: :param bool active_high:
@@ -276,7 +276,7 @@ class PWMOutputDevice(OutputDevice):
Generic output device configured for pulse-width modulation (PWM). Generic output device configured for pulse-width modulation (PWM).
:param int pin: :param int pin:
The GPIO pin which the device is attached to. See :ref:`pin_numbering` The GPIO pin which the device is attached to. See :ref:`pin-numbering`
for valid pin numbers. for valid pin numbers.
:param bool active_high: :param bool active_high:
@@ -483,7 +483,7 @@ class PWMLED(PWMOutputDevice):
an optional resistor to prevent the LED from burning out. an optional resistor to prevent the LED from burning out.
:param int pin: :param int pin:
The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for The GPIO pin which the LED is attached to. See :ref:`pin-numbering` for
valid pin numbers. valid pin numbers.
:param bool active_high: :param bool active_high:
@@ -926,7 +926,7 @@ class Servo(SourceMixin, CompositeDevice):
sleep(1) sleep(1)
:param int pin: :param int pin:
The GPIO pin which the device is attached to. See :ref:`pin_numbering` The GPIO pin which the device is attached to. See :ref:`pin-numbering`
for valid pin numbers. for valid pin numbers.
:param float initial_value: :param float initial_value:
@@ -1116,7 +1116,7 @@ class AngularServo(Servo):
expectations of minimum and maximum. expectations of minimum and maximum.
:param int pin: :param int pin:
The GPIO pin which the device is attached to. See :ref:`pin_numbering` The GPIO pin which the device is attached to. See :ref:`pin-numbering`
for valid pin numbers. for valid pin numbers.
:param float initial_angle: :param float initial_angle:

View File

@@ -1,3 +1,5 @@
# vim: set fileencoding=utf-8:
from __future__ import ( from __future__ import (
unicode_literals, unicode_literals,
absolute_import, absolute_import,
@@ -6,39 +8,137 @@ from __future__ import (
) )
str = type('') str = type('')
import io
from .data import pi_info
from ..exc import ( from ..exc import (
PinInvalidFunction, PinInvalidFunction,
PinSetInput, PinSetInput,
PinFixedPull, PinFixedPull,
PinUnsupported,
PinSPIUnsupported,
PinPWMUnsupported, PinPWMUnsupported,
PinEdgeDetectUnsupported, PinEdgeDetectUnsupported,
SPIFixedClockMode,
SPIFixedBitOrder,
SPIFixedSelect,
SPIFixedWordSize,
) )
PINS_CLEANUP = [] class Factory(object):
def _pins_shutdown(): """
for routine in PINS_CLEANUP: Generates pins, SPI, and I2C interfaces for devices. This is an abstract
routine() base class for pin factories. Descendents *must* override the following
methods:
* :meth:`_get_address`
* :meth:`pin_address`
Descendents *may* additionally override the following methods, if
applicable:
* :meth:`close`
* :meth:`pin`
* :meth:`spi`
* :meth:`_get_pi_info`
"""
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 pin_address(self, spec):
"""
Returns the address that a pin *would* have if constructed from the
given *spec*.
This unusual method is used by the pin reservation system to check
for conflicts *prior* to pin construction; with most implementations,
pin construction implicitly alters the state of the pin (e.g. setting
it to an input). This allows pin reservation to take place without
affecting the state of other components.
"""
raise NotImplementedError
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_address(self):
raise NotImplementedError
address = property(
lambda self: self._get_address(),
doc="""\
Returns a tuple of strings representing the address of the factory.
For the Pi itself this is a tuple of one string representing the Pi's
address (e.g. "localhost"). Expander chips can return a tuple appending
whatever string they require to uniquely identify the expander chip
amongst all factories in the system.
.. note::
This property *must* return an immutable object capable of being
used as a dictionary key.
""")
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): class Pin(object):
""" """
Abstract base class representing a GPIO pin or a pin from an IO extender. 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 Descendents should override property getters and setters to accurately
represent the capabilities of pins. The following functions *must* be represent the capabilities of pins. Descendents *must* override the
overridden: following methods:
* :meth:`_get_address`
* :meth:`_get_function` * :meth:`_get_function`
* :meth:`_set_function` * :meth:`_set_function`
* :meth:`_get_state` * :meth:`_get_state`
The following functions *may* be overridden if applicable: Descendents *may* additionally override the following methods, if
applicable:
* :meth:`close` * :meth:`close`
* :meth:`output_with_state`
* :meth:`input_with_pull`
* :meth:`_set_state` * :meth:`_set_state`
* :meth:`_get_frequency` * :meth:`_get_frequency`
* :meth:`_set_frequency` * :meth:`_set_frequency`
@@ -50,20 +150,10 @@ class Pin(object):
* :meth:`_set_edges` * :meth:`_set_edges`
* :meth:`_get_when_changed` * :meth:`_get_when_changed`
* :meth:`_set_when_changed` * :meth:`_set_when_changed`
* :meth:`pi_info`
* :meth:`output_with_state`
* :meth:`input_with_pull`
.. warning::
Descendents must ensure that pin instances representing the same
physical hardware are identical, right down to object identity. The
framework relies on this to correctly clean up resources at interpreter
shutdown.
""" """
def __repr__(self): def __repr__(self):
return "Abstract pin" return self.address[-1]
def close(self): def close(self):
""" """
@@ -105,6 +195,18 @@ class Pin(object):
self.function = 'input' self.function = 'input'
self.pull = pull self.pull = pull
def _get_address(self):
raise NotImplementedError
address = property(
lambda self: self._get_address(),
doc="""\
The address of the pin. This property is a tuple of strings constructed
from the owning factory's address with the unique address of the pin
appended to it. The tuple as a whole uniquely identifies the pin
amongst all pins attached to the system.
""")
def _get_function(self): def _get_function(self):
return "input" return "input"
@@ -140,10 +242,19 @@ class Pin(object):
doc="""\ doc="""\
The state of the pin. This is 0 for low, and 1 for high. As a low level 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 view of the pin, no swapping is performed in the case of pull ups (see
:attr:`pull` for more information). :attr:`pull` for more information):
If PWM is currently active (when :attr:`frequency` is not ``None``), .. code-block:: text
this represents the PWM duty cycle as a value between 0.0 and 1.0.
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 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 set this attribute, :exc:`PinSetInput` will be raised. If an invalid
@@ -205,6 +316,26 @@ class Pin(object):
detection, measured in seconds. If bounce detection is not currently in detection, measured in seconds. If bounce detection is not currently in
use, this is ``None``. use, this is ``None``.
For example, if :attr:`edge` 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 If the pin does not support edge detection, attempts to set this
property will raise :exc:`PinEdgeDetectUnsupported`. If the pin property will raise :exc:`PinEdgeDetectUnsupported`. If the pin
supports edge detection, the class must implement bounce detection, supports edge detection, the class must implement bounce detection,
@@ -223,7 +354,18 @@ class Pin(object):
doc="""\ doc="""\
The edge that will trigger execution of the function or bound method The edge that will trigger execution of the function or bound method
assigned to :attr:`when_changed`. This can be one of the strings assigned to :attr:`when_changed`. This can be one of the strings
"both" (the default), "rising", "falling", or "none". "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 If the pin does not support edge detection, attempts to set this
property will raise :exc:`PinEdgeDetectUnsupported`. property will raise :exc:`PinEdgeDetectUnsupported`.
@@ -247,48 +389,303 @@ class Pin(object):
property will raise :exc:`PinEdgeDetectUnsupported`. property will raise :exc:`PinEdgeDetectUnsupported`.
""") """)
@classmethod
def pi_info(cls):
"""
Returns a :class:`PiBoardInfo` instance representing the Pi that
instances of this pin class will be attached to.
If the pins represented by this class are not *directly* attached to a class SPI(object):
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``.
""" """
return None 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.
""")
class LocalPin(Pin):
"""
Abstract base class representing pins attached locally to a Pi. This forms
the base class for local-only pin interfaces (:class:`RPiGPIOPin`,
:class:`RPIOPin`, and :class:`NativePin`).
"""
_PI_REVISION = None
@classmethod
def pi_info(cls):
"""
Returns a :class:`PiBoardInfo` instance representing the local Pi.
The Pi's revision is determined by reading :file:`/proc/cpuinfo`. If
no valid revision is found, returns ``None``.
"""
# 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)
if cls._PI_REVISION is None:
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:]
cls._PI_REVISION = revision
break
if cls._PI_REVISION is None:
return None # something weird going on
return pi_info(cls._PI_REVISION)

View File

@@ -6,7 +6,6 @@ from __future__ import (
) )
str = type('') str = type('')
import io
import os import os
import sys import sys
from textwrap import dedent from textwrap import dedent
@@ -14,7 +13,7 @@ from itertools import cycle
from operator import attrgetter from operator import attrgetter
from collections import namedtuple from collections import namedtuple
from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins, PinInvalidPin
# Some useful constants for describing pins # Some useful constants for describing pins
@@ -120,8 +119,8 @@ A_BOARD = """\
BPLUS_BOARD = """\ BPLUS_BOARD = """\
{style:white on green},--------------------------------.{style:reset} {style:white on green},--------------------------------.{style:reset}
{style:white on green}| {P1:{style} col2}{style:white on green} P1 {style:black on white}+===={style:reset} {style:white on green}| {J8:{style} col2}{style:white on green} J8 {style:black on white}+===={style:reset}
{style:white on green}| {P1:{style} col1}{style:white on green} {style:black on white}| USB{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:black on white}+===={style:reset}
{style:white on green}| {style:bold}Pi Model {model:2s} V{pcb_revision:3s}{style:normal} |{style:reset} {style:white on green}| {style:bold}Pi Model {model:2s} 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}+----+{style:on green} {style:black on white}+===={style:reset}
@@ -135,8 +134,8 @@ BPLUS_BOARD = """\
APLUS_BOARD = """\ APLUS_BOARD = """\
{style:white on green},--------------------------.{style:reset} {style:white on green},--------------------------.{style:reset}
{style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset} {style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset}
{style:white on green}| {P1:{style} col1}{style:white on green} |{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:reset}
{style:white on green}| {style:bold}Pi Model {model:2s} V{pcb_revision:3s}{style:normal} |{style:reset} {style:white on green}| {style:bold}Pi Model {model:2s} 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}+----+{style:on green} {style:black on white}+===={style:reset}
@@ -150,20 +149,20 @@ APLUS_BOARD = """\
ZERO12_BOARD = """\ ZERO12_BOARD = """\
{style:white on green},-------------------------.{style:reset} {style:white on green},-------------------------.{style:reset}
{style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset} {style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset}
{style:white on green}| {P1:{style} col1}{style:white on green} |{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}---+{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} 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: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}""" {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 = """\ ZERO13_BOARD = """\
{style:white on green}.-------------------------.{style:reset} {style:white on green}.-------------------------.{style:reset}
{style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset} {style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset}
{style:white on green}| {P1:{style} col1}{style:white on green} {style:black on white}|c{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}PiZero{style:normal} {style:black on white}|s{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} 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: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}""" {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 = """\ CM_BOARD = """\
@@ -217,7 +216,7 @@ REV2_P5 = {
7: (GND, False), 8: (GND, False), 7: (GND, False), 8: (GND, False),
} }
PLUS_P1 = { PLUS_J8 = {
1: (V3_3, False), 2: (V5, False), 1: (V3_3, False), 2: (V5, False),
3: (GPIO2, True), 4: (V5, False), 3: (GPIO2, True), 4: (V5, False),
5: (GPIO3, True), 6: (GND, False), 5: (GPIO3, True), 6: (GND, False),
@@ -344,6 +343,23 @@ CM_SODIMM = {
199: ('VBAT', False), 200: ('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: # The following data is sourced from a combination of the following locations:
# #
# http://elinux.org/RPi_HardwareHistory # http://elinux.org/RPi_HardwareHistory
@@ -363,12 +379,12 @@ PI_REVISIONS = {
0xd: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, REV2_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, ), 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, ), 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, {'P1': PLUS_P1}, BPLUS_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, ), 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, {'P1': PLUS_P1}, APLUS_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, {'P1': PLUS_P1}, BPLUS_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, ), 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, {'P1': PLUS_P1}, APLUS_BOARD, ), 0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Embest', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'J8': PLUS_J8}, APLUS_BOARD, ),
} }
@@ -513,7 +529,8 @@ class HeaderInfo(namedtuple('HeaderInfo', (
from gpiozero import * from gpiozero import *
print('{0:full}'.format(pi_info().headers['P1'])) 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:col2}'.format(pi_info().headers['P1']))
print('{0:row1}'.format(pi_info().headers['P1'])) print('{0:row1}'.format(pi_info().headers['P1']))
@@ -521,10 +538,9 @@ class HeaderInfo(namedtuple('HeaderInfo', (
the use of `ANSI color codes`_. If neither is specified, ANSI codes will the use of `ANSI color codes`_. If neither is specified, ANSI codes will
only be used if stdout is detected to be a tty:: only be used if stdout is detected to be a tty::
print('{0:color row2}'.format(pi_info().headers['P1'])) # force use of ANSI codes 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 print('{0:mono row2}'.format(pi_info().headers['P1'])) # force plain ASCII
.. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code
The following attributes are defined: The following attributes are defined:
.. automethod:: pprint .. automethod:: pprint
@@ -532,7 +548,7 @@ class HeaderInfo(namedtuple('HeaderInfo', (
.. attribute:: name .. attribute:: name
The name of the header, typically as it appears silk-screened on the The name of the header, typically as it appears silk-screened on the
board (e.g. "P1"). board (e.g. "P1" or "J8").
.. attribute:: rows .. attribute:: rows
@@ -545,6 +561,8 @@ class HeaderInfo(namedtuple('HeaderInfo', (
.. attribute:: pins .. attribute:: pins
A dictionary mapping physical pin numbers to :class:`PinInfo` tuples. 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 __slots__ = () # workaround python issue #24931
@@ -563,7 +581,6 @@ class HeaderInfo(namedtuple('HeaderInfo', (
def _format_full(self, style): def _format_full(self, style):
Cell = namedtuple('Cell', ('content', 'align', 'style')) Cell = namedtuple('Cell', ('content', 'align', 'style'))
pin_digits = len(str(self.rows * self.columns))
lines = [] lines = []
for row in range(self.rows): for row in range(self.rows):
line = [] line = []
@@ -643,7 +660,6 @@ class HeaderInfo(namedtuple('HeaderInfo', (
print('{0:{style} full}'.format(self, style=Style(color))) print('{0:{style} full}'.format(self, style=Style(color)))
class PiBoardInfo(namedtuple('PiBoardInfo', ( class PiBoardInfo(namedtuple('PiBoardInfo', (
'revision', 'revision',
'model', 'model',
@@ -671,6 +687,7 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
from gpiozero import * from gpiozero import *
print('{0}'.format(pi_info()))
print('{0:full}'.format(pi_info())) print('{0:full}'.format(pi_info()))
print('{0:board}'.format(pi_info())) print('{0:board}'.format(pi_info()))
print('{0:specs}'.format(pi_info())) print('{0:specs}'.format(pi_info()))
@@ -787,8 +804,8 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
A dictionary which maps header labels to :class:`HeaderInfo` tuples. A dictionary which maps header labels to :class:`HeaderInfo` tuples.
For example, to obtain information about header P1 you would query For example, to obtain information about header P1 you would query
``headers['P1']``. To obtain information about pin 12 on header P1 you ``headers['P1']``. To obtain information about pin 12 on header J8 you
would query ``headers['P1'].pins[12]``. would query ``headers['J8'].pins[12]``.
A rendered version of this data can be obtained by using the A rendered version of this data can be obtained by using the
:class:`PiBoardInfo` object in a format string:: :class:`PiBoardInfo` object in a format string::
@@ -821,10 +838,16 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
# uuuuuuuu - Unused # uuuuuuuu - Unused
# F - New flag (1=valid new-style revision, 0=old-style) # F - New flag (1=valid new-style revision, 0=old-style)
# MMM - Memory size (0=256, 1=512, 2=1024) # MMM - Memory size (0=256, 1=512, 2=1024)
# CCCC - Manufacturer (0=Sony, 1=Egoman, 2=Embest) # CCCC - Manufacturer (0=Sony, 1=Egoman, 2=Embest, 3=Sony Japan)
# PPPP - Processor (0=2835, 1=2836, 2=2837) # 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) # 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.) # 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: try:
model = { model = {
0: 'A', 0: 'A',
@@ -835,82 +858,100 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
6: 'CM', 6: 'CM',
8: '3B', 8: '3B',
9: 'Zero', 9: 'Zero',
}[(revision & 0xff0) >> 4] 10: 'CM3',
12: 'Zero W',
}[revcode_type]
if model in ('A', 'B'): if model in ('A', 'B'):
pcb_revision = { pcb_revision = {
0: '1.0', # is this right? 0: '1.0', # is this right?
1: '1.0', 1: '1.0',
2: '2.0', 2: '2.0',
}[revision & 0x0f] }.get(revcode_revision, 'Unknown')
else: else:
pcb_revision = '1.%d' % (revision & 0x0f) pcb_revision = '1.%d' % revcode_revision
released = {
'A': '2013Q1',
'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4',
'A+': '2014Q4',
'B+': '2014Q3',
'2B': '2015Q1' if pcb_revision == '1.0' or pcb_revision == '1.1' else '2016Q3',
'CM': '2014Q2',
'3B': '2016Q1',
'Zero': '2015Q4' if pcb_revision == '1.2' else '2016Q2',
}[model]
soc = { soc = {
0: 'BCM2835', 0: 'BCM2835',
1: 'BCM2836', 1: 'BCM2836',
2: 'BCM2837', 2: 'BCM2837',
}[(revision & 0xf000) >> 12] }.get(revcode_processor, 'Unknown')
manufacturer = { manufacturer = {
0: 'Sony', 0: 'Sony',
1: 'Egoman', 1: 'Egoman',
2: 'Embest', 2: 'Embest',
}[(revision & 0xf0000) >> 16] 3: 'Sony Japan',
}.get(revcode_manufacturer, 'Unknown')
memory = { memory = {
0: 256, 0: 256,
1: 512, 1: 512,
2: 1024, 2: 1024,
}[(revision & 0x700000) >> 20] }.get(revcode_memory, 0)
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 = { storage = {
'A': 'SD', 'A': 'SD',
'B': 'SD', 'B': 'SD',
'CM': 'eMMC', 'CM': 'eMMC',
'CM3': 'eMMC / off-board',
}.get(model, 'MicroSD') }.get(model, 'MicroSD')
usb = { usb = {
'A': 1, 'A': 1,
'A+': 1, 'A+': 1,
'Zero': 1, 'Zero': 1,
'Zero W': 1,
'B': 2, 'B': 2,
'CM': 1, 'CM': 0,
'CM3': 1,
}.get(model, 4) }.get(model, 4)
ethernet = { ethernet = {
'A': 0, 'A': 0,
'A+': 0, 'A+': 0,
'Zero': 0, 'Zero': 0,
'Zero W': 0,
'CM': 0, 'CM': 0,
'CM3': 0,
}.get(model, 1) }.get(model, 1)
wifi = { wifi = {
'3B': True, '3B': True,
'Zero W': True,
}.get(model, False) }.get(model, False)
bluetooth = { bluetooth = {
'3B': True, '3B': True,
'Zero W': True,
}.get(model, False) }.get(model, False)
csi = { csi = {
'Zero': 0 if pcb_revision == '1.2' else 1, 'Zero': 0 if pcb_revision == '1.0' else 1,
'Zero W': 1,
'CM': 2, 'CM': 2,
'CM3': 2,
}.get(model, 1) }.get(model, 1)
dsi = { dsi = {
'Zero': 0, 'Zero': 0,
'Zero W': 0,
}.get(model, csi) }.get(model, csi)
headers = { headers = {
'A': {'P1': REV2_P1, 'P5': REV2_P5}, 'A': {'P1': REV2_P1, 'P5': REV2_P5},
'B': {'P1': REV1_P1} if pcb_revision == '1.0' else {'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}, 'CM': {'SODIMM': CM_SODIMM},
}.get(model, {'P1': PLUS_P1}) 'CM3': {'SODIMM': CM3_SODIMM},
}.get(model, {'J8': PLUS_J8})
board = { board = {
'A': A_BOARD, 'A': A_BOARD,
'B': REV1_BOARD if pcb_revision == '1.0' else REV2_BOARD, 'B': REV1_BOARD if pcb_revision == '1.0' else REV2_BOARD,
'A+': APLUS_BOARD, 'A+': APLUS_BOARD,
'CM': CM_BOARD, 'CM': CM_BOARD,
'CM3': CM_BOARD,
'Zero': ZERO12_BOARD if pcb_revision == '1.2' else ZERO13_BOARD, 'Zero': ZERO12_BOARD if pcb_revision == '1.2' else ZERO13_BOARD,
'Zero W': ZERO13_BOARD,
}.get(model, BPLUS_BOARD) }.get(model, BPLUS_BOARD)
except KeyError: except KeyError:
raise PinUnknownPi('unable to parse new-style revision "%x"' % revision) raise PinUnknownPi('unable to parse new-style revision "%x"' % revision)
@@ -1077,8 +1118,8 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
""" """
Pretty-print a representation of the board along with header diagrams. Pretty-print a representation of the board along with header diagrams.
If *color* is ``None`` (the default, the diagram will include ANSI If *color* is ``None`` (the default), the diagram will include ANSI
color codes if stdout is a color-capable terminal). Otherwise *color* color codes if stdout is a color-capable terminal. Otherwise *color*
can be set to ``True`` or ``False`` to force color or monochrome can be set to ``True`` or ``False`` to force color or monochrome
output. output.
""" """
@@ -1096,13 +1137,10 @@ def pi_info(revision=None):
the model of Pi it is running on and return information about that. the model of Pi it is running on and return information about that.
""" """
if revision is None: if revision is None:
# NOTE: This import is declared locally for two reasons. Firstly it # The reason this import is located here is to avoid a circular
# avoids a circular dependency (devices->pins->pins.data->devices). # dependency; devices->pins.local->pins.data->devices
# Secondly, pin_factory is one global which might potentially be from ..devices import Device
# re-written by a user's script at runtime hence we should re-import result = Device.pin_factory.pi_info
# here in case it's changed since initialization
from ..devices import pin_factory
result = pin_factory.pi_info()
if result is None: if result is None:
raise PinUnknownPi('The default pin_factory is not attached to a Pi') raise PinUnknownPi('The default pin_factory is not attached to a Pi')
else: else:

245
gpiozero/pins/local.py Normal file
View File

@@ -0,0 +1,245 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import io
import warnings
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 = {}
def __init__(self):
super(LocalPiFactory, self).__init__()
self.spi_classes = {
('hardware', 'exclusive'): LocalPiHardwareSPI,
('hardware', 'shared'): LocalPiHardwareSPIShared,
('software', 'exclusive'): LocalPiSoftwareSPI,
('software', 'shared'): LocalPiSoftwareSPIShared,
}
# Override the pins dict to be this class' pins dict. 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
def _get_address(self):
return ('localhost',)
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):
if SpiDev is None:
raise ImportError('failed to import spidev')
self._port = port
self._device = device
self._interface = None
self._address = factory.address + ('SPI(port=%d, device=%d)' % (port, device),)
super(LocalPiHardwareSPI, self).__init__()
pins = SPI_HARDWARE_PINS[port]
self._reserve_pins(
factory.pin_address(pins['clock']),
factory.pin_address(pins['mosi']),
factory.pin_address(pins['miso']),
factory.pin_address(pins['select'][device])
)
self._interface = SpiDev()
self._interface.open(port, device)
self._interface.max_speed_hz = 500000
def close(self):
if self._interface:
try:
self._interface.close()
finally:
self._interface = None
self._release_all()
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
self._address = factory.address + (
'SPI(clock_pin=%d, mosi_pin=%d, miso_pin=%d, select_pin=%d)' % (
clock_pin, mosi_pin, miso_pin, select_pin),
)
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):
return not (
isinstance(other, LocalPiSoftwareSPI) and
(self.pin.number != other.pin.number)
)
def close(self):
if self._bus:
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,)

View File

@@ -7,6 +7,7 @@ from __future__ import (
str = type('') str = type('')
import os
from collections import namedtuple from collections import namedtuple
from time import time, sleep from time import time, sleep
from threading import Thread, Event from threading import Thread, Event
@@ -15,56 +16,36 @@ try:
except ImportError: except ImportError:
from ..compat import isclose from ..compat import isclose
from . import Pin import pkg_resources
from .data import pi_info
from ..exc import PinSetInput, PinPWMUnsupported, PinFixedPull 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')) PinState = namedtuple('PinState', ('timestamp', 'state'))
class MockPin(Pin): class MockPin(PiPin):
""" """
A mock pin used primarily for testing. This class does *not* support PWM. A mock pin used primarily for testing. This class does *not* support PWM.
""" """
_PINS = {} def __init__(self, factory, number):
super(MockPin, self).__init__(factory, number)
@classmethod
def clear_pins(cls):
cls._PINS.clear()
@classmethod
def pi_info(cls):
return pi_info('a21041') # Pretend we're a Pi 2B
def __new__(cls, number):
if not (0 <= number < 54):
raise ValueError('invalid pin %d specified (must be 0..53)' % number)
try:
old_pin = cls._PINS[number]
except KeyError:
self = super(MockPin, cls).__new__(cls)
cls._PINS[number] = self
self._number = number
self._function = 'input' self._function = 'input'
self._state = False self._pull = 'up' if factory.pi_info.pulled_up(self.address[-1]) else 'floating'
self._pull = 'floating' self._state = self._pull == 'up'
self._bounce = None self._bounce = None
self._edges = 'both' self._edges = 'both'
self._when_changed = None self._when_changed = None
self.clear_states() self.clear_states()
return self
# Ensure the pin class expected supports PWM (or not)
if issubclass(cls, MockPWMPin) != isinstance(old_pin, MockPWMPin):
raise ValueError('pin %d is already in use as a %s' % (number, old_pin.__class__.__name__))
return old_pin
def __repr__(self):
return 'MOCK%d' % self._number
@property
def number(self):
return self._number
def close(self): def close(self):
self.when_changed = None self.when_changed = None
@@ -74,7 +55,8 @@ class MockPin(Pin):
return self._function return self._function
def _set_function(self, value): def _set_function(self, value):
assert value in ('input', 'output') if value not in ('input', 'output'):
raise PinInvalidFunction('function must be input or output')
self._function = value self._function = value
if value == 'input': if value == 'input':
# Drive the input to the pull # Drive the input to the pull
@@ -110,8 +92,12 @@ class MockPin(Pin):
return self._pull return self._pull
def _set_pull(self, value): def _set_pull(self, value):
assert self._function == 'input' if self.function != 'input':
assert value in ('floating', 'up', 'down') raise PinFixedPull('cannot set pull on non-input pin %r' % self)
if value != 'up' and self.factory.pi_info.pulled_up(self.address[-1]):
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 self._pull = value
if value == 'up': if value == 'up':
self.drive_high() self.drive_high()
@@ -169,14 +155,23 @@ class MockPin(Pin):
assert isclose(actual.state, expected[1]) assert isclose(actual.state, expected[1])
class MockPulledUpPin(MockPin): class MockConnectedPin(MockPin):
""" """
This derivative of :class:`MockPin` emulates a pin with a physical pull-up This derivative of :class:`MockPin` emulates a pin connected to another
resistor. mock pin. This is used in the "real pins" portion of the test suite to
check that one pin can influence another.
""" """
def _set_pull(self, value): def __init__(self, factory, number, input_pin=None):
if value != 'up': super(MockConnectedPin, self).__init__(factory, number)
raise PinFixedPull('pin has a physical pull-up resistor') 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): class MockChargingPin(MockPin):
@@ -186,9 +181,9 @@ class MockChargingPin(MockPin):
(as if attached to, e.g. a typical circuit using an LDR and a capacitor (as if attached to, e.g. a typical circuit using an LDR and a capacitor
to time the charging rate). to time the charging rate).
""" """
def __init__(self, number): def __init__(self, factory, number, charge_time=0.01):
super(MockChargingPin, self).__init__() super(MockChargingPin, self).__init__(factory, number)
self.charge_time = 0.01 # dark charging time self.charge_time = charge_time # dark charging time
self._charge_stop = Event() self._charge_stop = Event()
self._charge_thread = None self._charge_thread = None
@@ -225,10 +220,10 @@ class MockTriggerPin(MockPin):
corresponding pin instance. When this pin is driven high it will trigger corresponding pin instance. When this pin is driven high it will trigger
the echo pin to drive high for the echo time. the echo pin to drive high for the echo time.
""" """
def __init__(self, number): def __init__(self, factory, number, echo_pin=None, echo_time=0.04):
super(MockTriggerPin, self).__init__() super(MockTriggerPin, self).__init__(factory, number)
self.echo_pin = None self.echo_pin = echo_pin
self.echo_time = 0.04 # longest echo time self.echo_time = echo_time # longest echo time
self._echo_thread = None self._echo_thread = None
def _set_state(self, value): def _set_state(self, value):
@@ -250,8 +245,8 @@ class MockPWMPin(MockPin):
""" """
This derivative of :class:`MockPin` adds PWM support. This derivative of :class:`MockPin` adds PWM support.
""" """
def __init__(self, number): def __init__(self, factory, number):
super(MockPWMPin, self).__init__() super(MockPWMPin, self).__init__(factory, number)
self._frequency = None self._frequency = None
def close(self): def close(self):
@@ -283,8 +278,8 @@ class MockSPIClockPin(MockPin):
rather, construct a :class:`MockSPIDevice` with various pin numbers, and rather, construct a :class:`MockSPIDevice` with various pin numbers, and
this class will be used for the clock pin. this class will be used for the clock pin.
""" """
def __init__(self, number): def __init__(self, factory, number):
super(MockSPIClockPin, self).__init__() super(MockSPIClockPin, self).__init__(factory, number)
if not hasattr(self, 'spi_devices'): if not hasattr(self, 'spi_devices'):
self.spi_devices = [] self.spi_devices = []
@@ -301,8 +296,8 @@ class MockSPISelectPin(MockPin):
tests; rather, construct a :class:`MockSPIDevice` with various pin numbers, tests; rather, construct a :class:`MockSPIDevice` with various pin numbers,
and this class will be used for the select pin. and this class will be used for the select pin.
""" """
def __init__(self, number): def __init__(self, factory, number):
super(MockSPISelectPin, self).__init__() super(MockSPISelectPin, self).__init__(factory, number)
if not hasattr(self, 'spi_device'): if not hasattr(self, 'spi_device'):
self.spi_device = None self.spi_device = None
@@ -314,13 +309,13 @@ class MockSPISelectPin(MockPin):
class MockSPIDevice(object): class MockSPIDevice(object):
def __init__( def __init__(
self, clock_pin, mosi_pin, miso_pin, select_pin=None, self, clock_pin, mosi_pin=None, miso_pin=None, select_pin=None,
clock_polarity=False, clock_phase=False, lsb_first=False, clock_polarity=False, clock_phase=False, lsb_first=False,
bits_per_word=8, select_high=False): bits_per_word=8, select_high=False):
self.clock_pin = MockSPIClockPin(clock_pin) self.clock_pin = Device.pin_factory.pin(clock_pin, pin_class=MockSPIClockPin)
self.mosi_pin = None if mosi_pin is None else MockPin(mosi_pin) 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 MockPin(miso_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 MockSPISelectPin(select_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_polarity = clock_polarity
self.clock_phase = clock_phase self.clock_phase = clock_phase
self.lsb_first = lsb_first self.lsb_first = lsb_first
@@ -413,3 +408,42 @@ class MockSPIDevice(object):
bits = reversed(bits) bits = reversed(bits)
self.tx_buf.extend(bits) self.tx_buf.extend(bits)
class MockFactory(LocalPiFactory):
def __init__(
self, revision=os.getenv('GPIOZERO_MOCK_REVISION', 'a21041'),
pin_class=os.getenv('GPIOZERO_MOCK_PIN_CLASS', MockPin)):
super(MockFactory, self).__init__()
self._revision = revision
if not issubclass(pin_class, MockPin):
if isinstance(pin_class, bytes):
pin_class = pin_class.decode('ascii')
dist = pkg_resources.get_distribution('gpiozero')
group = 'gpiozero_mock_pin_classes'
pin_class = pkg_resources.load_entry_point(dist, group, pin_class.lower())
self.pin_class = pin_class
def _get_address(self):
return ('mock',)
def _get_revision(self):
return self._revision
def reset(self):
self.pins.clear()
def pin(self, spec, pin_class=None, **kwargs):
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

View File

@@ -17,16 +17,13 @@ from time import sleep
from threading import Thread, Event, Lock from threading import Thread, Event, Lock
from collections import Counter from collections import Counter
from . import LocalPin, PINS_CLEANUP from .local import LocalPiPin, LocalPiFactory
from .data import pi_info
from ..exc import ( from ..exc import (
PinInvalidPull, PinInvalidPull,
PinInvalidEdges, PinInvalidEdges,
PinInvalidFunction, PinInvalidFunction,
PinFixedPull, PinFixedPull,
PinSetInput, PinSetInput,
PinNonPhysical,
PinNoPins,
) )
@@ -149,7 +146,7 @@ class GPIOFS(object):
f.write(str(pin).encode('ascii')) f.write(str(pin).encode('ascii'))
class NativePin(LocalPin): class NativeFactory(LocalPiFactory):
""" """
Uses a built-in pure Python implementation to interface to the Pi's GPIO Uses a built-in pure Python implementation to interface to the Pi's GPIO
pins. This is the default pin implementation if no third-party libraries pins. This is the default pin implementation if no third-party libraries
@@ -169,10 +166,17 @@ class NativePin(LocalPin):
led = LED(NativePin(12)) led = LED(NativePin(12))
""" """
def __init__(self):
super(NativeFactory, self).__init__()
self.mem = GPIOMemory()
self.pin_class = NativePin
_MEM = None def close(self):
_PINS = {} super(NativeFactory, self).close()
self.mem.close()
class NativePin(LocalPiPin):
GPIO_FUNCTIONS = { GPIO_FUNCTIONS = {
'input': 0b000, 'input': 0b000,
'output': 0b001, 'output': 0b001,
@@ -202,89 +206,62 @@ class NativePin(LocalPin):
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.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()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
PI_INFO = None def __init__(self, factory, number):
super(NativePin, self).__init__(factory, number)
def __new__(cls, number): self._func_offset = self.factory.mem.GPFSEL_OFFSET + (number // 10)
if not cls._PINS:
cls._MEM = GPIOMemory()
PINS_CLEANUP.append(cls._MEM.close)
if cls.PI_INFO is None:
cls.PI_INFO = pi_info()
if not (0 <= number < 54):
raise ValueError('invalid pin %d specified (must be 0..53)' % number)
try:
return cls._PINS[number]
except KeyError:
self = super(NativePin, cls).__new__(cls)
try:
cls.PI_INFO.physical_pin('GPIO%d' % number)
except PinNoPins:
warnings.warn(
PinNonPhysical(
'no physical pins exist for GPIO%d' % number))
self._number = number
self._func_offset = self._MEM.GPFSEL_OFFSET + (number // 10)
self._func_shift = (number % 10) * 3 self._func_shift = (number % 10) * 3
self._set_offset = self._MEM.GPSET_OFFSET + (number // 32) self._set_offset = self.factory.mem.GPSET_OFFSET + (number // 32)
self._set_shift = number % 32 self._set_shift = number % 32
self._clear_offset = self._MEM.GPCLR_OFFSET + (number // 32) self._clear_offset = self.factory.mem.GPCLR_OFFSET + (number // 32)
self._clear_shift = number % 32 self._clear_shift = number % 32
self._level_offset = self._MEM.GPLEV_OFFSET + (number // 32) self._level_offset = self.factory.mem.GPLEV_OFFSET + (number // 32)
self._level_shift = number % 32 self._level_shift = number % 32
self._pull_offset = self._MEM.GPPUDCLK_OFFSET + (number // 32) self._pull_offset = self.factory.mem.GPPUDCLK_OFFSET + (number // 32)
self._pull_shift = number % 32 self._pull_shift = number % 32
self._edge_offset = self._MEM.GPEDS_OFFSET + (number // 32) self._edge_offset = self.factory.mem.GPEDS_OFFSET + (number // 32)
self._edge_shift = number % 32 self._edge_shift = number % 32
self._rising_offset = self._MEM.GPREN_OFFSET + (number // 32) self._rising_offset = self.factory.mem.GPREN_OFFSET + (number // 32)
self._rising_shift = number % 32 self._rising_shift = number % 32
self._falling_offset = self._MEM.GPFEN_OFFSET + (number // 32) self._falling_offset = self.factory.mem.GPFEN_OFFSET + (number // 32)
self._falling_shift = number % 32 self._falling_shift = number % 32
self._when_changed = None self._when_changed = None
self._change_thread = None self._change_thread = None
self._change_event = Event() self._change_event = Event()
self.function = 'input' self.function = 'input'
self.pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' self.pull = 'up' if factory.pi_info.pulled_up(self.address[-1]) else 'floating'
self.bounce = None self.bounce = None
self.edges = 'both' self.edges = 'both'
cls._PINS[number] = self
return self
def __repr__(self):
return "GPIO%d" % self._number
@property
def number(self):
return self._number
def close(self): def close(self):
self.frequency = None
self.when_changed = None self.when_changed = None
self.function = 'input' self.function = 'input'
self.pull = 'up' if self.PI_INFO.pulled_up('GPIO%d' % self.number) else 'floating' self.pull = 'up' if self.factory.pi_info.pulled_up(self.address[-1]) else 'floating'
def _get_function(self): def _get_function(self):
return self.GPIO_FUNCTION_NAMES[(self._MEM[self._func_offset] >> self._func_shift) & 7] return self.GPIO_FUNCTION_NAMES[(self.factory.mem[self._func_offset] >> self._func_shift) & 7]
def _set_function(self, value): def _set_function(self, value):
try: try:
value = self.GPIO_FUNCTIONS[value] value = self.GPIO_FUNCTIONS[value]
except KeyError: except KeyError:
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
self._MEM[self._func_offset] = ( self.factory.mem[self._func_offset] = (
self._MEM[self._func_offset] self.factory.mem[self._func_offset]
& ~(7 << self._func_shift) & ~(7 << self._func_shift)
| (value << self._func_shift) | (value << self._func_shift)
) )
def _get_state(self): def _get_state(self):
return bool(self._MEM[self._level_offset] & (1 << self._level_shift)) return bool(self.factory.mem[self._level_offset] & (1 << self._level_shift))
def _set_state(self, value): def _set_state(self, value):
if self.function == 'input': if self.function == 'input':
raise PinSetInput('cannot set state of pin %r' % self) raise PinSetInput('cannot set state of pin %r' % self)
if value: if value:
self._MEM[self._set_offset] = 1 << self._set_shift self.factory.mem[self._set_offset] = 1 << self._set_shift
else: else:
self._MEM[self._clear_offset] = 1 << self._clear_shift self.factory.mem[self._clear_offset] = 1 << self._clear_shift
def _get_pull(self): def _get_pull(self):
return self.GPIO_PULL_UP_NAMES[self._pull] return self.GPIO_PULL_UP_NAMES[self._pull]
@@ -292,23 +269,23 @@ class NativePin(LocalPin):
def _set_pull(self, value): def _set_pull(self, value):
if self.function != 'input': if self.function != 'input':
raise PinFixedPull('cannot set pull on non-input pin %r' % self) raise PinFixedPull('cannot set pull on non-input pin %r' % self)
if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self.number): if value != 'up' and self.factory.pi_info.pulled_up(self.address[-1]):
raise PinFixedPull('%r has a physical pull-up resistor' % self) raise PinFixedPull('%r has a physical pull-up resistor' % self)
try: try:
value = self.GPIO_PULL_UPS[value] value = self.GPIO_PULL_UPS[value]
except KeyError: except KeyError:
raise PinInvalidPull('invalid pull direction "%s" for pin %r' % (value, self)) raise PinInvalidPull('invalid pull direction "%s" for pin %r' % (value, self))
self._MEM[self._MEM.GPPUD_OFFSET] = value self.factory.mem[self.factory.mem.GPPUD_OFFSET] = value
sleep(0.000000214) sleep(0.000000214)
self._MEM[self._pull_offset] = 1 << self._pull_shift self.factory.mem[self._pull_offset] = 1 << self._pull_shift
sleep(0.000000214) sleep(0.000000214)
self._MEM[self._MEM.GPPUD_OFFSET] = 0 self.factory.mem[self.factory.mem.GPPUD_OFFSET] = 0
self._MEM[self._pull_offset] = 0 self.factory.mem[self._pull_offset] = 0
self._pull = value self._pull = value
def _get_edges(self): def _get_edges(self):
rising = bool(self._MEM[self._rising_offset] & (1 << self._rising_shift)) rising = bool(self.factory.mem[self._rising_offset] & (1 << self._rising_shift))
falling = bool(self._MEM[self._falling_offset] & (1 << self._falling_shift)) falling = bool(self.factory.mem[self._falling_offset] & (1 << self._falling_shift))
return self.GPIO_EDGES_NAMES[(rising, falling)] return self.GPIO_EDGES_NAMES[(rising, falling)]
def _set_edges(self, value): def _set_edges(self, value):
@@ -319,43 +296,36 @@ class NativePin(LocalPin):
f = self.when_changed f = self.when_changed
self.when_changed = None self.when_changed = None
try: try:
self._MEM[self._rising_offset] = ( self.factory.mem[self._rising_offset] = (
self._MEM[self._rising_offset] self.factory.mem[self._rising_offset]
& ~(1 << self._rising_shift) & ~(1 << self._rising_shift)
| (rising << self._rising_shift) | (rising << self._rising_shift)
) )
self._MEM[self._falling_offset] = ( self.factory.mem[self._falling_offset] = (
self._MEM[self._falling_offset] self.factory.mem[self._falling_offset]
& ~(1 << self._falling_shift) & ~(1 << self._falling_shift)
| (falling << self._falling_shift) | (falling << self._falling_shift)
) )
finally: finally:
self.when_changed = f self.when_changed = f
def _get_when_changed(self): def _enable_event_detect(self):
return self._when_changed
def _set_when_changed(self, value):
if self._when_changed is None and value is not None:
self._when_changed = value
self._change_thread = Thread(target=self._change_watch) self._change_thread = Thread(target=self._change_watch)
self._change_thread.daemon = True self._change_thread.daemon = True
self._change_event.clear() self._change_event.clear()
self._change_thread.start() self._change_thread.start()
elif self._when_changed is not None and value is None:
def _disable_event_detect(self):
self._change_event.set() self._change_event.set()
self._change_thread.join() self._change_thread.join()
self._change_thread = None self._change_thread = None
self._when_changed = None
else:
self._when_changed = value
def _change_watch(self): def _change_watch(self):
offset = self._edge_offset offset = self._edge_offset
mask = 1 << self._edge_shift mask = 1 << self._edge_shift
self._MEM[offset] = mask # clear any existing detection bit self.factory.mem[offset] = mask # clear any existing detection bit
while not self._change_event.wait(0.001): while not self._change_event.wait(0.001):
if self._MEM[offset] & mask: if self.factory.mem[offset] & mask:
self._MEM[offset] = mask self.factory.mem[offset] = mask
self._when_changed() self._call_when_changed()

307
gpiozero/pins/pi.py Normal file
View File

@@ -0,0 +1,307 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import io
from threading import RLock
from types import MethodType
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):
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 pin_address(self, spec):
n = self._to_gpio(spec)
return self.address + ('GPIO%d' % n,)
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(self.address[-1])
except PinNoPins:
warnings.warn(
PinNonPhysical(
'no physical pins exist for %s' % self.address[-1]))
@property
def number(self):
return self._number
@property
def factory(self):
return self._factory
def _get_address(self):
return self.factory.address + ('GPIO%d' % self.number,)
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

521
gpiozero/pins/pigpio.py Normal file
View File

@@ -0,0 +1,521 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import os
from weakref import proxy
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 PiGPIOPin
from gpiozero import LED
led = LED(PiGPIOPin(12))
This is particularly useful for controlling pins on a remote machine. To
accomplish this simply specify the host (and optionally port) when
constructing the pin::
from gpiozero.pins.pigpio import PiGPIOPin
from gpiozero import LED
from signal import pause
led = LED(PiGPIOPin(12, host='192.168.0.2'))
.. note::
In some circumstances, especially when playing with PWM, it does appear
to be possible to get the daemon into "unusual" states. We would be
most interested to hear any bug reports relating to this (it may be a
bug in our pin implementation). A workaround for now is simply to
restart the ``pigpiod`` daemon.
.. _pigpio: http://abyz.co.uk/rpi/pigpio/
"""
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 _get_address(self):
return ("%s:%d" % (self.host, self.port),)
def spi(self, **spi_args):
intf = super(PiGPIOFactory, self).spi(**spi_args)
self._spis.append(intf)
return intf
class PiGPIOPin(PiPin):
_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 factory.pi_info.pulled_up(self.address[-1]) 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(self.address[-1]) 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(self.address[-1]):
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):
def __init__(self, factory, port, device):
self._port = port
self._device = device
self._factory = proxy(factory)
self._handle = None
super(PiGPIOHardwareSPI, self).__init__()
pins = SPI_HARDWARE_PINS[port]
self._reserve_pins(*(
factory.address + ('GPIO%d' % pin,)
for pin in (
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._release_all()
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):
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 = proxy(factory)
super(PiGPIOSoftwareSPI, self).__init__()
self._reserve_pins(
factory.pin_address(clock_pin),
factory.pin_address(mosi_pin),
factory.pin_address(miso_pin),
factory.pin_address(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._release_all()
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)

View File

@@ -1,278 +0,0 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import warnings
import pigpio
import os
from . import Pin
from .data import pi_info
from ..exc import (
PinInvalidFunction,
PinSetInput,
PinFixedPull,
PinInvalidPull,
PinInvalidBounce,
PinInvalidState,
PinNonPhysical,
PinNoPins,
)
class PiGPIOPin(Pin):
"""
Uses the `pigpio`_ library to interface to the Pi's GPIO pins. The pigpio
library relies on a daemon (``pigpiod``) to be running as root to provide
access to the GPIO pins, and communicates with this daemon over a network
socket.
While this does mean only the daemon itself should control the pins, the
architecture does have several advantages:
* Pins can be remote controlled from another machine (the other
machine doesn't even have to be a Raspberry Pi; it simply needs the
`pigpio`_ client library installed on it)
* The daemon supports hardware PWM via the DMA controller
* Your script itself doesn't require root privileges; it just needs to
be able to communicate with the daemon
You can construct pigpiod pins manually like so::
from gpiozero.pins.pigpiod import PiGPIOPin
from gpiozero import LED
led = LED(PiGPIOPin(12))
This is particularly useful for controlling pins on a remote machine. To
accomplish this simply specify the host (and optionally port) when
constructing the pin::
from gpiozero.pins.pigpiod import PiGPIOPin
from gpiozero import LED
from signal import pause
led = LED(PiGPIOPin(12, host='192.168.0.2'))
.. note::
In some circumstances, especially when playing with PWM, it does appear
to be possible to get the daemon into "unusual" states. We would be
most interested to hear any bug reports relating to this (it may be a
bug in our pin implementation). A workaround for now is simply to
restart the ``pigpiod`` daemon.
.. _pigpio: http://abyz.co.uk/rpi/pigpio/
"""
_CONNECTIONS = {} # maps (host, port) to (connection, pi_info)
_PINS = {}
GPIO_FUNCTIONS = {
'input': pigpio.INPUT,
'output': pigpio.OUTPUT,
'alt0': pigpio.ALT0,
'alt1': pigpio.ALT1,
'alt2': pigpio.ALT2,
'alt3': pigpio.ALT3,
'alt4': pigpio.ALT4,
'alt5': pigpio.ALT5,
}
GPIO_PULL_UPS = {
'up': pigpio.PUD_UP,
'down': pigpio.PUD_DOWN,
'floating': pigpio.PUD_OFF,
}
GPIO_EDGES = {
'both': pigpio.EITHER_EDGE,
'rising': pigpio.RISING_EDGE,
'falling': pigpio.FALLING_EDGE,
}
GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()}
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
def __new__(
cls, number, host=os.getenv('PIGPIO_ADDR', 'localhost'),
port=int(os.getenv('PIGPIO_PORT', 8888))):
try:
return cls._PINS[(host, port, number)]
except KeyError:
self = super(PiGPIOPin, cls).__new__(cls)
cls.pi_info(host, port) # implicitly creates connection
self._connection, self._pi_info = cls._CONNECTIONS[(host, port)]
try:
self._pi_info.physical_pin('GPIO%d' % number)
except PinNoPins:
warnings.warn(
PinNonPhysical(
'no physical pins exist for GPIO%d' % number))
self._host = host
self._port = port
self._number = number
self._pull = 'up' if self._pi_info.pulled_up('GPIO%d' % number) else 'floating'
self._pwm = False
self._bounce = None
self._when_changed = None
self._callback = None
self._edges = pigpio.EITHER_EDGE
try:
self._connection.set_mode(self._number, pigpio.INPUT)
except pigpio.error as e:
raise ValueError(e)
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[self._pull])
self._connection.set_glitch_filter(self._number, 0)
cls._PINS[(host, port, number)] = self
return self
def __repr__(self):
if self._host == 'localhost':
return "GPIO%d" % self._number
else:
return "GPIO%d on %s:%d" % (self._number, self._host, self._port)
@property
def host(self):
return self._host
@property
def port(self):
return self._port
@property
def number(self):
return self._number
def close(self):
# If we're shutting down, the connection may have disconnected itself
# already. Unfortunately, the connection's "connected" property is
# rather buggy - disconnecting doesn't set it to False! So we're
# naughty and check an internal variable instead...
if self._connection.sl.s is not None:
self.frequency = None
self.when_changed = None
self.function = 'input'
self.pull = 'up' if self._pi_info.pulled_up('GPIO%d' % self.number) else 'floating'
def _get_function(self):
return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)]
def _set_function(self, value):
if value != 'input':
self._pull = 'floating'
try:
self._connection.set_mode(self._number, self.GPIO_FUNCTIONS[value])
except KeyError:
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
def _get_state(self):
if self._pwm:
return (
self._connection.get_PWM_dutycycle(self._number) /
self._connection.get_PWM_range(self._number)
)
else:
return bool(self._connection.read(self._number))
def _set_state(self, value):
if self._pwm:
try:
value = int(value * self._connection.get_PWM_range(self._number))
if value != self._connection.get_PWM_dutycycle(self._number):
self._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._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._pi_info.pulled_up('GPIO%d' % self._number):
raise PinFixedPull('%r has a physical pull-up resistor' % self)
try:
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value])
self._pull = value
except KeyError:
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
def _get_frequency(self):
if self._pwm:
return self._connection.get_PWM_frequency(self._number)
return None
def _set_frequency(self, value):
if not self._pwm and value is not None:
self._connection.set_PWM_frequency(self._number, value)
self._connection.set_PWM_range(self._number, 10000)
self._connection.set_PWM_dutycycle(self._number, 0)
self._pwm = True
elif self._pwm and value is not None:
if value != self._connection.get_PWM_frequency(self._number):
self._connection.set_PWM_frequency(self._number, value)
self._connection.set_PWM_range(self._number, 10000)
elif self._pwm and value is None:
self._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._connection.set_glitch_filter(self._number, int(value * 1000000))
def _get_edges(self):
return self.GPIO_EDGES_NAMES[self._edges]
def _set_edges(self, value):
f = self.when_changed
self.when_changed = None
try:
self._edges = self.GPIO_EDGES[value]
finally:
self.when_changed = f
def _get_when_changed(self):
if self._callback is None:
return None
return self._callback.callb.func
def _set_when_changed(self, value):
if self._callback is not None:
self._callback.cancel()
self._callback = None
if value is not None:
self._callback = self._connection.callback(
self._number, self._edges,
lambda gpio, level, tick: value())
@classmethod
def pi_info(
cls, host=os.getenv('PIGPIO_ADDR', 'localhost'),
port=int(os.getenv('PIGPIO_PORT', 8888))):
try:
connection, info = cls._CONNECTIONS[(host, port)]
except KeyError:
connection = pigpio.pi(host, port)
revision = '%04x' % connection.get_hardware_revision()
info = pi_info(revision)
cls._CONNECTIONS[(host, port)] = (connection, info)
return info

View File

@@ -7,10 +7,10 @@ from __future__ import (
str = type('') str = type('')
import warnings import warnings
from RPi import GPIO from RPi import GPIO
from . import LocalPin from .local import LocalPiFactory, LocalPiPin
from .data import pi_info
from ..exc import ( from ..exc import (
PinInvalidFunction, PinInvalidFunction,
PinSetInput, PinSetInput,
@@ -19,12 +19,10 @@ from ..exc import (
PinInvalidState, PinInvalidState,
PinInvalidBounce, PinInvalidBounce,
PinPWMFixedValue, PinPWMFixedValue,
PinNonPhysical,
PinNoPins,
) )
class RPiGPIOPin(LocalPin): class RPiGPIOFactory(LocalPiFactory):
""" """
Uses the `RPi.GPIO`_ library to interface to the Pi's GPIO pins. This is 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. the default pin implementation if the RPi.GPIO library is installed.
@@ -39,7 +37,7 @@ class RPiGPIOPin(LocalPin):
However, you can also construct RPi.GPIO pins manually if you wish:: However, you can also construct RPi.GPIO pins manually if you wish::
from gpiozero.pins.rpigpio import RPiGPIOPin from gpiozero.pins.rpigpio import RPiGPIOFactory
from gpiozero import LED from gpiozero import LED
led = LED(RPiGPIOPin(12)) led = LED(RPiGPIOPin(12))
@@ -47,8 +45,18 @@ class RPiGPIOPin(LocalPin):
.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO .. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO
""" """
_PINS = {} 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):
GPIO_FUNCTIONS = { GPIO_FUNCTIONS = {
'input': GPIO.IN, 'input': GPIO.IN,
'output': GPIO.OUT, 'output': GPIO.OUT,
@@ -75,69 +83,42 @@ class RPiGPIOPin(LocalPin):
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.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()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
PI_INFO = None def __init__(self, factory, number):
super(RPiGPIOPin, self).__init__(factory, number)
def __new__(cls, number): self._pull = 'up' if factory.pi_info.pulled_up(self.address[-1]) else 'floating'
if not cls._PINS:
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
if cls.PI_INFO is None:
cls.PI_INFO = pi_info()
try:
return cls._PINS[number]
except KeyError:
self = super(RPiGPIOPin, cls).__new__(cls)
try:
cls.PI_INFO.physical_pin('GPIO%d' % number)
except PinNoPins:
warnings.warn(
PinNonPhysical(
'no physical pins exist for GPIO%d' % number))
self._number = number
self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating'
self._pwm = None self._pwm = None
self._frequency = None self._frequency = None
self._duty_cycle = None self._duty_cycle = None
self._bounce = -666 self._bounce = -666
self._when_changed = None
self._edges = GPIO.BOTH self._edges = GPIO.BOTH
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[self._pull]) GPIO.setup(self.number, GPIO.IN, self.GPIO_PULL_UPS[self._pull])
cls._PINS[number] = self
return self
def __repr__(self):
return "GPIO%d" % self._number
@property
def number(self):
return self._number
def close(self): def close(self):
self.frequency = None self.frequency = None
self.when_changed = None self.when_changed = None
GPIO.cleanup(self._number) GPIO.cleanup(self.number)
def output_with_state(self, state): def output_with_state(self, state):
self._pull = 'floating' self._pull = 'floating'
GPIO.setup(self._number, GPIO.OUT, initial=state) GPIO.setup(self.number, GPIO.OUT, initial=state)
def input_with_pull(self, pull): def input_with_pull(self, pull):
if pull != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): if pull != 'up' and self.factory.pi_info.pulled_up(self.address[-1]):
raise PinFixedPull('%r has a physical pull-up resistor' % self) raise PinFixedPull('%r has a physical pull-up resistor' % self)
try: try:
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[pull]) GPIO.setup(self.number, GPIO.IN, self.GPIO_PULL_UPS[pull])
self._pull = pull self._pull = pull
except KeyError: except KeyError:
raise PinInvalidPull('invalid pull "%s" for pin %r' % (pull, self)) raise PinInvalidPull('invalid pull "%s" for pin %r' % (pull, self))
def _get_function(self): def _get_function(self):
return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self._number)] return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self.number)]
def _set_function(self, value): def _set_function(self, value):
if value != 'input': if value != 'input':
self._pull = 'floating' self._pull = 'floating'
if value in ('input', 'output') and value in self.GPIO_FUNCTIONS: 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]) GPIO.setup(self.number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull])
else: else:
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
@@ -145,7 +126,7 @@ class RPiGPIOPin(LocalPin):
if self._pwm: if self._pwm:
return self._duty_cycle return self._duty_cycle
else: else:
return GPIO.input(self._number) return GPIO.input(self.number)
def _set_state(self, value): def _set_state(self, value):
if self._pwm: if self._pwm:
@@ -156,7 +137,7 @@ class RPiGPIOPin(LocalPin):
self._duty_cycle = value self._duty_cycle = value
else: else:
try: try:
GPIO.output(self._number, value) GPIO.output(self.number, value)
except ValueError: except ValueError:
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
except RuntimeError: except RuntimeError:
@@ -168,10 +149,10 @@ class RPiGPIOPin(LocalPin):
def _set_pull(self, value): def _set_pull(self, value):
if self.function != 'input': if self.function != 'input':
raise PinFixedPull('cannot set pull on non-input pin %r' % self) raise PinFixedPull('cannot set pull on non-input pin %r' % self)
if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): if value != 'up' and self.factory.pi_info.pulled_up(self.address[-1]):
raise PinFixedPull('%r has a physical pull-up resistor' % self) raise PinFixedPull('%r has a physical pull-up resistor' % self)
try: try:
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[value]) GPIO.setup(self.number, GPIO.IN, self.GPIO_PULL_UPS[value])
self._pull = value self._pull = value
except KeyError: except KeyError:
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
@@ -182,7 +163,7 @@ class RPiGPIOPin(LocalPin):
def _set_frequency(self, value): def _set_frequency(self, value):
if self._frequency is None and value is not None: if self._frequency is None and value is not None:
try: try:
self._pwm = GPIO.PWM(self._number, value) self._pwm = GPIO.PWM(self.number, value)
except RuntimeError: except RuntimeError:
raise PinPWMFixedValue('cannot start PWM on pin %r' % self) raise PinPWMFixedValue('cannot start PWM on pin %r' % self)
self._pwm.start(0) self._pwm.start(0)
@@ -221,19 +202,15 @@ class RPiGPIOPin(LocalPin):
finally: finally:
self.when_changed = f self.when_changed = f
def _get_when_changed(self): def _call_when_changed(self, channel):
return self._when_changed super(RPiGPIOPin, self)._call_when_changed()
def _set_when_changed(self, value): def _enable_event_detect(self):
if self._when_changed is None and value is not None:
self._when_changed = value
GPIO.add_event_detect( GPIO.add_event_detect(
self._number, self._edges, self.number, self._edges,
callback=lambda channel: self._when_changed(), callback=self._call_when_changed,
bouncetime=self._bounce) bouncetime=self._bounce)
elif self._when_changed is not None and value is None:
GPIO.remove_event_detect(self._number) def _disable_event_detect(self):
self._when_changed = None GPIO.remove_event_detect(self.number)
else:
self._when_changed = value

View File

@@ -8,11 +8,12 @@ str = type('')
import warnings import warnings
import RPIO import RPIO
import RPIO.PWM import RPIO.PWM
from RPIO.Exceptions import InvalidChannelException from RPIO.Exceptions import InvalidChannelException
from . import LocalPin, PINS_CLEANUP from .local import LocalPiPin, LocalPiFactory
from .data import pi_info from .data import pi_info
from ..exc import ( from ..exc import (
PinInvalidFunction, PinInvalidFunction,
@@ -22,12 +23,10 @@ from ..exc import (
PinInvalidBounce, PinInvalidBounce,
PinInvalidState, PinInvalidState,
PinPWMError, PinPWMError,
PinNonPhysical,
PinNoPins,
) )
class RPIOPin(LocalPin): class RPIOFactory(LocalPiFactory):
""" """
Uses the `RPIO`_ library to interface to the Pi's GPIO pins. This is 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, the default pin implementation if the RPi.GPIO library is not installed,
@@ -48,9 +47,22 @@ class RPIOPin(LocalPin):
.. _RPIO: https://pythonhosted.org/RPIO/ .. _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
_PINS = {} def close(self):
RPIO.PWM.cleanup()
RPIO.stop_waiting_for_interrupts()
RPIO.cleanup()
class RPIOPin(LocalPiPin):
GPIO_FUNCTIONS = { GPIO_FUNCTIONS = {
'input': RPIO.IN, 'input': RPIO.IN,
'output': RPIO.OUT, 'output': RPIO.OUT,
@@ -66,64 +78,31 @@ class RPIOPin(LocalPin):
GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()} 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_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
PI_INFO = None def __init__(self, factory, number):
super(RPIOPin, self).__init__(factory, number)
def __new__(cls, number): self._pull = 'up' if factory.pi_info.pulled_up(self.address[-1]) else 'floating'
if not cls._PINS:
RPIO.setmode(RPIO.BCM)
RPIO.setwarnings(False)
RPIO.wait_for_interrupts(threaded=True)
RPIO.PWM.setup()
RPIO.PWM.init_channel(0, 10000)
PINS_CLEANUP.append(RPIO.PWM.cleanup)
PINS_CLEANUP.append(RPIO.stop_waiting_for_interrupts)
PINS_CLEANUP.append(RPIO.cleanup)
if cls.PI_INFO is None:
cls.PI_INFO = pi_info()
try:
return cls._PINS[number]
except KeyError:
self = super(RPIOPin, cls).__new__(cls)
try:
cls.PI_INFO.physical_pin('GPIO%d' % number)
except PinNoPins:
warnings.warn(
PinNonPhysical(
'no physical pins exist for GPIO%d' % number))
self._number = number
self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating'
self._pwm = False self._pwm = False
self._duty_cycle = None self._duty_cycle = None
self._bounce = None self._bounce = None
self._when_changed = None
self._edges = 'both' self._edges = 'both'
try: try:
RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[self._pull]) RPIO.setup(self.number, RPIO.IN, self.GPIO_PULL_UPS[self._pull])
except InvalidChannelException as e: except InvalidChannelException as e:
raise ValueError(e) raise ValueError(e)
cls._PINS[number] = self
return self
def __repr__(self):
return "GPIO%d" % self._number
@property
def number(self):
return self._number
def close(self): def close(self):
self.frequency = None self.frequency = None
self.when_changed = None self.when_changed = None
RPIO.setup(self._number, RPIO.IN, RPIO.PUD_OFF) RPIO.setup(self.number, RPIO.IN, RPIO.PUD_OFF)
def _get_function(self): def _get_function(self):
return self.GPIO_FUNCTION_NAMES[RPIO.gpio_function(self._number)] return self.GPIO_FUNCTION_NAMES[RPIO.gpio_function(self.number)]
def _set_function(self, value): def _set_function(self, value):
if value != 'input': if value != 'input':
self._pull = 'floating' self._pull = 'floating'
try: try:
RPIO.setup(self._number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull]) RPIO.setup(self.number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull])
except KeyError: except KeyError:
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
@@ -131,23 +110,23 @@ class RPIOPin(LocalPin):
if self._pwm: if self._pwm:
return self._duty_cycle return self._duty_cycle
else: else:
return RPIO.input(self._number) return RPIO.input(self.number)
def _set_state(self, value): def _set_state(self, value):
if not 0 <= value <= 1: if not 0 <= value <= 1:
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
if self._pwm: if self._pwm:
RPIO.PWM.clear_channel_gpio(0, self._number) RPIO.PWM.clear_channel_gpio(0, self.number)
if value == 0: if value == 0:
RPIO.output(self._number, False) RPIO.output(self.number, False)
elif value == 1: elif value == 1:
RPIO.output(self._number, True) RPIO.output(self.number, True)
else: else:
RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=int(1000 * value)) RPIO.PWM.add_channel_pulse(0, self.number, start=0, width=int(1000 * value))
self._duty_cycle = value self._duty_cycle = value
else: else:
try: try:
RPIO.output(self._number, value) RPIO.output(self.number, value)
except ValueError: except ValueError:
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
except RuntimeError: except RuntimeError:
@@ -159,10 +138,10 @@ class RPIOPin(LocalPin):
def _set_pull(self, value): def _set_pull(self, value):
if self.function != 'input': if self.function != 'input':
raise PinFixedPull('cannot set pull on non-input pin %r' % self) raise PinFixedPull('cannot set pull on non-input pin %r' % self)
if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): if value != 'up' and self.factory.pi_info.pulled_up(self.address[-1]):
raise PinFixedPull('%r has a physical pull-up resistor' % self) raise PinFixedPull('%r has a physical pull-up resistor' % self)
try: try:
RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[value]) RPIO.setup(self.number, RPIO.IN, self.GPIO_PULL_UPS[value])
self._pull = value self._pull = value
except KeyError: except KeyError:
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
@@ -182,10 +161,10 @@ class RPIOPin(LocalPin):
self._pwm = True self._pwm = True
# Dirty hack to get RPIO's PWM support to setup, but do nothing, # Dirty hack to get RPIO's PWM support to setup, but do nothing,
# for a given GPIO pin # for a given GPIO pin
RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=0) RPIO.PWM.add_channel_pulse(0, self.number, start=0, width=0)
RPIO.PWM.clear_channel_gpio(0, self._number) RPIO.PWM.clear_channel_gpio(0, self.number)
elif self._pwm and value is None: elif self._pwm and value is None:
RPIO.PWM.clear_channel_gpio(0, self._number) RPIO.PWM.clear_channel_gpio(0, self.number)
self._pwm = False self._pwm = False
def _get_bounce(self): def _get_bounce(self):
@@ -212,25 +191,20 @@ class RPIOPin(LocalPin):
finally: finally:
self.when_changed = f self.when_changed = f
def _get_when_changed(self): def _call_when_changed(self, channel, value):
return self._when_changed super(RPIOPin, self)._call_when_changed()
def _set_when_changed(self, value): def _enable_event_detect(self):
if self._when_changed is None and value is not None:
self._when_changed = value
RPIO.add_interrupt_callback( RPIO.add_interrupt_callback(
self._number, self.number, self._call_when_changed, self._edges,
lambda channel, value: self._when_changed(), self.GPIO_PULL_UPS[self._pull], self._bounce)
self._edges, self.GPIO_PULL_UPS[self._pull], self._bounce)
elif self._when_changed is not None and value is None: def _disable_event_detect(self):
try: try:
RPIO.del_interrupt_callback(self._number) RPIO.del_interrupt_callback(self.number)
except KeyError: except KeyError:
# Ignore this exception which occurs during shutdown; this # Ignore this exception which occurs during shutdown; this
# simply means RPIO's built-in cleanup has already run and # simply means RPIO's built-in cleanup has already run and
# removed the handler # removed the handler
pass pass
self._when_changed = None
else:
self._when_changed = value

98
gpiozero/pins/spi.py Normal file
View File

@@ -0,0 +1,98 @@
from __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 self.lock:
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

View File

@@ -1,419 +0,0 @@
from __future__ import (
unicode_literals,
print_function,
absolute_import,
division,
)
str = type('')
import warnings
import operator
from threading import RLock
try:
from spidev import SpiDev
except ImportError:
SpiDev = None
from .devices import Device, SharedMixin, _PINS, _PINS_LOCK
from .input_devices import InputDevice
from .output_devices import OutputDevice
from .exc import SPIBadArgs, SPISoftwareFallback, GPIOPinInUse, DeviceClosed
class SPIHardwareInterface(Device):
def __init__(self, port, device):
self._device = None
super(SPIHardwareInterface, self).__init__()
# XXX How can we detect conflicts with existing GPIO instances? This
# isn't ideal ... in fact, it's downright crap and doesn't guard
# against conflicts created *after* this instance, but it's all I can
# come up with right now ...
conflicts = (11, 10, 9, (8, 7)[device])
with _PINS_LOCK:
for pin in _PINS:
if pin.number in conflicts:
raise GPIOPinInUse(
'pin %r is already in use by another gpiozero object' % pin
)
self._device_num = device
self._device = SpiDev()
self._device.open(port, device)
self._device.max_speed_hz = 500000
def close(self):
if self._device:
try:
self._device.close()
finally:
self._device = None
super(SPIHardwareInterface, self).close()
@property
def closed(self):
return self._device is None
def __repr__(self):
try:
self._check_open()
return (
"hardware SPI on clock_pin=11, mosi_pin=10, miso_pin=9, "
"select_pin=%d" % (
8 if self._device_num == 0 else 7))
except DeviceClosed:
return "hardware SPI closed"
def read(self, n):
return self.transfer((0,) * n)
def write(self, data):
return len(self.transfer(data))
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._device.xfer2(data)
def _get_clock_mode(self):
return self._device.mode
def _set_clock_mode(self, value):
self._device.mode = value
def _get_clock_polarity(self):
return bool(self.clock_mode & 2)
def _set_clock_polarity(self, value):
self.clock_mode = self.clock_mode & (~2) | (bool(value) << 1)
def _get_clock_phase(self):
return bool(self.clock_mode & 1)
def _set_clock_phase(self, value):
self.clock_mode = self.clock_mode & (~1) | bool(value)
def _get_lsb_first(self):
return self._device.lsbfirst
def _set_lsb_first(self, value):
self._device.lsbfirst = bool(value)
def _get_select_high(self):
return self._device.cshigh
def _set_select_high(self, value):
self._device.cshigh = bool(value)
def _get_bits_per_word(self):
return self._device.bits_per_word
def _set_bits_per_word(self, value):
self._device.bits_per_word = value
clock_polarity = property(_get_clock_polarity, _set_clock_polarity)
clock_phase = property(_get_clock_phase, _set_clock_phase)
clock_mode = property(_get_clock_mode, _set_clock_mode)
lsb_first = property(_get_lsb_first, _set_lsb_first)
select_high = property(_get_select_high, _set_select_high)
bits_per_word = property(_get_bits_per_word, _set_bits_per_word)
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 self.lock:
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:
shift = operator.lshift if lsb_first else operator.rshift
for write_word in data:
mask = 1 if lsb_first else 1 << (bits_per_word - 1)
read_word = 0
for _ in range(bits_per_word):
if self.mosi is not None:
self.mosi.value = bool(write_word & mask)
self.clock.on()
if self.miso is not None and not clock_phase:
if self.miso.value:
read_word |= mask
self.clock.off()
if self.miso is not None and clock_phase:
if self.miso.value:
read_word |= mask
mask = shift(mask, 1)
result.append(read_word)
return result
class SPISoftwareInterface(OutputDevice):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin):
self._bus = None
super(SPISoftwareInterface, 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 close(self):
if self._bus:
self._bus.close()
self._bus = None
super(SPISoftwareInterface, self).close()
def __repr__(self):
try:
self._check_open()
return (
"software SPI on 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 "software SPI closed"
def read(self, n):
return self.transfer((0,) * n)
def write(self, data):
return len(self.transfer(data))
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):
return (self.clock_polarity << 1) | self.clock_phase
def _set_clock_mode(self, value):
value = int(value)
if not 0 <= value <= 3:
raise ValueError('clock_mode must be a value between 0 and 3 inclusive')
self.clock_polarity = bool(value & 2)
self.clock_phase = bool(value & 1)
def _get_clock_polarity(self):
with self._bus.lock:
return not self._bus.clock.active_high
def _set_clock_polarity(self, value):
with self._bus.lock:
self._bus.clock.active_high = not value
self._bus.clock.off()
def _get_clock_phase(self):
return self._clock_phase
def _set_clock_phase(self, value):
self._clock_phase = bool(value)
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()
clock_polarity = property(_get_clock_polarity, _set_clock_polarity)
clock_phase = property(_get_clock_phase, _set_clock_phase)
clock_mode = property(_get_clock_mode, _set_clock_mode)
lsb_first = property(_get_lsb_first, _set_lsb_first)
bits_per_word = property(_get_bits_per_word, _set_bits_per_word)
select_high = property(_get_select_high, _set_select_high)
class SharedSPIHardwareInterface(SharedMixin, SPIHardwareInterface):
@classmethod
def _shared_key(cls, port, device):
return (port, device)
class SharedSPISoftwareInterface(SharedMixin, SPISoftwareInterface):
@classmethod
def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin):
return (clock_pin, mosi_pin, miso_pin, select_pin)
def extract_spi_args(**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)``.
"""
pin_defaults = {
'clock_pin': 11,
'mosi_pin': 10,
'miso_pin': 9,
'select_pin': 8,
}
dev_defaults = {
'port': 0,
'device': 0,
}
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: 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')
if spi_args['device'] not in (0, 1):
raise SPIBadArgs('device must be 0 or 1')
spi_args = {
key: value if key != 'select_pin' else (8, 7)[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
def SPI(**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).
Finally, the *shared* keyword argument specifies whether the resulting
SPI interface can be repeatedly created and used by multiple devices
(useful with multi-channel devices like numerous ADCs).
"""
spi_args, kwargs = extract_spi_args(**spi_args)
shared = kwargs.pop('shared', False)
if kwargs:
raise SPIBadArgs(
'unrecognized keyword argument %s' % kwargs.popitem()[0])
if all((
spi_args['clock_pin'] == 11,
spi_args['mosi_pin'] == 10,
spi_args['miso_pin'] == 9,
spi_args['select_pin'] in (7, 8),
)):
if SpiDev is None:
warnings.warn(
SPISoftwareFallback(
'failed to import spidev, falling back to software SPI'))
else:
try:
hardware_spi_args = {
'port': 0,
'device': {8: 0, 7: 1}[spi_args['select_pin']],
}
if shared:
return SharedSPIHardwareInterface(**hardware_spi_args)
else:
return SPIHardwareInterface(**hardware_spi_args)
except Exception as e:
warnings.warn(
SPISoftwareFallback(
'failed to initialize hardware SPI, falling back to '
'software (error was: %s)' % str(e)))
if shared:
return SharedSPISoftwareInterface(**spi_args)
else:
return SPISoftwareInterface(**spi_args)

View File

@@ -16,7 +16,6 @@ except ImportError:
from .exc import DeviceClosed, SPIBadChannel from .exc import DeviceClosed, SPIBadChannel
from .devices import Device from .devices import Device
from .spi import SPI
class SPIDevice(Device): class SPIDevice(Device):
@@ -28,13 +27,14 @@ class SPIDevice(Device):
specified with the constructor. specified with the constructor.
""" """
def __init__(self, **spi_args): def __init__(self, **spi_args):
self._spi = SPI(**spi_args) self._spi = None
super(SPIDevice, self).__init__()
self._spi = self.pin_factory.spi(**spi_args)
def close(self): def close(self):
if self._spi: if self._spi:
s = self._spi self._spi.close()
self._spi = None self._spi = None
s.close()
super(SPIDevice, self).close() super(SPIDevice, self).close()
@property @property

View File

@@ -22,7 +22,7 @@ except ImportError:
pass pass
__project__ = 'gpiozero' __project__ = 'gpiozero'
__version__ = '1.3.1' __version__ = '1.3.2'
__author__ = 'Ben Nuttall' __author__ = 'Ben Nuttall'
__author_email__ = 'ben@raspberrypi.org' __author_email__ = 'ben@raspberrypi.org'
__url__ = 'https://github.com/RPi-Distro/python-gpiozero' __url__ = 'https://github.com/RPi-Distro/python-gpiozero'
@@ -68,13 +68,26 @@ if sys.version_info[:2] == (3, 2):
__entry_points__ = { __entry_points__ = {
'gpiozero_pin_factories': [ 'gpiozero_pin_factories': [
'PiGPIOPin = gpiozero.pins.pigpiod:PiGPIOPin', 'pigpio = gpiozero.pins.pigpio:PiGPIOFactory',
'RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOPin', 'rpigpio = gpiozero.pins.rpigpio:RPiGPIOFactory',
'RPIOPin = gpiozero.pins.rpio:RPIOPin', 'rpio = gpiozero.pins.rpio:RPIOFactory',
'NativePin = gpiozero.pins.native:NativePin', 'native = gpiozero.pins.native:NativeFactory',
'MockPin = gpiozero.pins.mock:MockPin', 'mock = gpiozero.pins.mock:MockFactory',
'MockPWMPin = gpiozero.pins.mock:MockPWMPin', # 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 = gpiozero.cli.pinout:main',
]
} }

35
tests/cli/test_pinout.py Normal file
View File

@@ -0,0 +1,35 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import pytest
from gpiozero.cli.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 pinout' in out

10
tests/conftest.py Normal file
View File

@@ -0,0 +1,10 @@
from __future__ import (
unicode_literals,
print_function,
absolute_import,
division,
)
str = type('')
import os
os.environ['GPIOZERO_PIN_FACTORY'] = 'mock'

View File

@@ -11,26 +11,40 @@ import sys
import pytest import pytest
from time import sleep from time import sleep
from gpiozero.pins.mock import MockPin, MockPWMPin
from gpiozero import * from gpiozero import *
from gpiozero.pins.mock import MockPWMPin, MockPin
def setup_function(function): def setup_function(function):
import gpiozero.devices
# dirty, but it does the job # dirty, but it does the job
if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot', 'test_led_borg', 'test_snow_pi_initial_value_pwm'): Device.pin_factory.pin_class = MockPWMPin if function.__name__ in (
gpiozero.devices.pin_factory = MockPWMPin 'test_robot',
else: 'test_ryanteck_robot',
gpiozero.devices.pin_factory = MockPin 'test_camjam_kit_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',
) else MockPin
def teardown_function(function): def teardown_function(function):
MockPin.clear_pins() Device.pin_factory.reset()
Device._reservations.clear()
def teardown_module(module):
# make sure we reset the default
Device.pin_factory.pwm = False
def test_composite_output_on_off(): def test_composite_output_on_off():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device:
device.on() device.on()
assert all((pin1.state, pin2.state, pin3.state)) assert all((pin1.state, pin2.state, pin3.state))
@@ -38,9 +52,9 @@ def test_composite_output_on_off():
assert not any((pin1.state, pin2.state, pin3.state)) assert not any((pin1.state, pin2.state, pin3.state))
def test_composite_output_toggle(): def test_composite_output_toggle():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device:
device.toggle() device.toggle()
assert all((pin1.state, pin2.state, pin3.state)) assert all((pin1.state, pin2.state, pin3.state))
@@ -51,9 +65,9 @@ def test_composite_output_toggle():
assert not pin3.state assert not pin3.state
def test_composite_output_value(): def test_composite_output_value():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device:
assert device.value == (0, 0, 0) assert device.value == (0, 0, 0)
device.toggle() device.toggle()
@@ -64,9 +78,9 @@ def test_composite_output_value():
assert device[2].is_active assert device[2].is_active
def test_led_board_on_off(): def test_led_board_on_off():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBoard(pin1, pin2, foo=pin3) as board: with LEDBoard(pin1, pin2, foo=pin3) as board:
assert isinstance(board[0], LED) assert isinstance(board[0], LED)
assert isinstance(board[1], LED) assert isinstance(board[1], LED)
@@ -121,9 +135,9 @@ def test_led_board_on_off():
assert pin3.state assert pin3.state
def test_led_board_active_low(): def test_led_board_active_low():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBoard(pin1, pin2, foo=pin3, active_high=False) as board: with LEDBoard(pin1, pin2, foo=pin3, active_high=False) as board:
assert not board.active_high assert not board.active_high
assert not board[0].active_high assert not board[0].active_high
@@ -145,9 +159,9 @@ def test_led_board_active_low():
assert not pin3.state assert not pin3.state
def test_led_board_value(): def test_led_board_value():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBoard(pin1, pin2, foo=pin3) as board: with LEDBoard(pin1, pin2, foo=pin3) as board:
assert board.value == (0, 0, 0) assert board.value == (0, 0, 0)
board.value = (0, 1, 0) board.value = (0, 1, 0)
@@ -156,9 +170,9 @@ def test_led_board_value():
assert board.value == (1, 0, 1) assert board.value == (1, 0, 1)
def test_led_board_pwm_value(): def test_led_board_pwm_value():
pin1 = MockPWMPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPWMPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPWMPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board:
assert board.value == (0, 0, 0) assert board.value == (0, 0, 0)
board.value = (0, 1, 0) board.value = (0, 1, 0)
@@ -167,9 +181,9 @@ def test_led_board_pwm_value():
assert board.value == (0.5, 0, 0.75) assert board.value == (0.5, 0, 0.75)
def test_led_board_pwm_bad_value(): def test_led_board_pwm_bad_value():
pin1 = MockPWMPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPWMPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPWMPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board:
with pytest.raises(ValueError): with pytest.raises(ValueError):
board.value = (-1, 0, 0) board.value = (-1, 0, 0)
@@ -177,18 +191,18 @@ def test_led_board_pwm_bad_value():
board.value = (0, 2, 0) board.value = (0, 2, 0)
def test_led_board_initial_value(): def test_led_board_initial_value():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBoard(pin1, pin2, foo=pin3, initial_value=0) as board: with LEDBoard(pin1, pin2, foo=pin3, initial_value=0) as board:
assert board.value == (0, 0, 0) assert board.value == (0, 0, 0)
with LEDBoard(pin1, pin2, foo=pin3, initial_value=1) as board: with LEDBoard(pin1, pin2, foo=pin3, initial_value=1) as board:
assert board.value == (1, 1, 1) assert board.value == (1, 1, 1)
def test_led_board_pwm_initial_value(): def test_led_board_pwm_initial_value():
pin1 = MockPWMPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPWMPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPWMPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0) as board: with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0) as board:
assert board.value == (0, 0, 0) assert board.value == (0, 0, 0)
with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=1) as board: with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=1) as board:
@@ -197,18 +211,18 @@ def test_led_board_pwm_initial_value():
assert board.value == (0.5, 0.5, 0.5) assert board.value == (0.5, 0.5, 0.5)
def test_led_board_pwm_bad_initial_value(): def test_led_board_pwm_bad_initial_value():
pin1 = MockPWMPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPWMPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPWMPin(4) pin3 = Device.pin_factory.pin(4)
with pytest.raises(ValueError): with pytest.raises(ValueError):
LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=-1) LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=-1)
with pytest.raises(ValueError): with pytest.raises(ValueError):
LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=2) LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=2)
def test_led_board_nested(): def test_led_board_nested():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
assert list(led.pin for led in board.leds) == [pin1, pin2, pin3] assert list(led.pin for led in board.leds) == [pin1, pin2, pin3]
assert board.value == (0, (0, 0)) assert board.value == (0, (0, 0))
@@ -218,9 +232,9 @@ def test_led_board_nested():
assert pin3.state assert pin3.state
def test_led_board_bad_blink(): def test_led_board_bad_blink():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
with pytest.raises(ValueError): with pytest.raises(ValueError):
board.blink(fade_in_time=1, fade_out_time=1) board.blink(fade_in_time=1, fade_out_time=1)
@@ -232,10 +246,15 @@ def test_led_board_bad_blink():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_led_board_blink_background(): def test_led_board_blink_background():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(4)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(5)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3)) 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(0.1, 0.1, n=2)
board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test
test = [ test = [
@@ -252,10 +271,13 @@ def test_led_board_blink_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_led_board_blink_foreground(): def test_led_board_blink_foreground():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(4)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(5)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0.1, 0.1, n=2, background=False) board.blink(0.1, 0.1, n=2, background=False)
test = [ test = [
(0.0, False), (0.0, False),
@@ -271,10 +293,13 @@ def test_led_board_blink_foreground():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_led_board_blink_control(): def test_led_board_blink_control():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(4)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(5)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0.1, 0.1, n=2) board.blink(0.1, 0.1, n=2)
# make sure the blink thread's started # make sure the blink thread's started
while not board._blink_leds: while not board._blink_leds:
@@ -296,10 +321,13 @@ def test_led_board_blink_control():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_led_board_blink_take_over(): def test_led_board_blink_take_over():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(4)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(5)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board[1].blink(0.1, 0.1, n=2) board[1].blink(0.1, 0.1, n=2)
board.blink(0.1, 0.1, n=2) # immediately take over blinking board.blink(0.1, 0.1, n=2) # immediately take over blinking
board[1]._blink_thread.join() board[1]._blink_thread.join()
@@ -318,10 +346,13 @@ def test_led_board_blink_take_over():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_led_board_blink_control_all(): def test_led_board_blink_control_all():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(4)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(5)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0.1, 0.1, n=2) board.blink(0.1, 0.1, n=2)
# make sure the blink thread's started # make sure the blink thread's started
while not board._blink_leds: while not board._blink_leds:
@@ -340,9 +371,9 @@ def test_led_board_blink_control_all():
pin3.assert_states_and_times(test) pin3.assert_states_and_times(test)
def test_led_board_blink_interrupt_on(): def test_led_board_blink_interrupt_on():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(4)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(5)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
board.blink(1, 0.1) board.blink(1, 0.1)
sleep(0.2) sleep(0.2)
@@ -352,10 +383,13 @@ def test_led_board_blink_interrupt_on():
pin3.assert_states([False, True, False]) pin3.assert_states([False, True, False])
def test_led_board_blink_interrupt_off(): def test_led_board_blink_interrupt_off():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(4)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(5)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0.1, 1) board.blink(0.1, 1)
sleep(0.2) sleep(0.2)
board.off() # should interrupt while off board.off() # should interrupt while off
@@ -366,10 +400,13 @@ def test_led_board_blink_interrupt_off():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_led_board_fade_background(): def test_led_board_fade_background():
pin1 = MockPWMPin(2) pin1 = Device.pin_factory.pin(4)
pin2 = MockPWMPin(3) pin2 = Device.pin_factory.pin(5)
pin3 = MockPWMPin(4) pin3 = Device.pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3, pwm=True), pwm=True) as board: with LEDBoard(pin1, LEDBoard(pin2, pin3, 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(0, 0, 0.2, 0.2, n=2)
board._blink_thread.join() board._blink_thread.join()
test = [ test = [
@@ -400,9 +437,9 @@ def test_led_board_fade_background():
pin3.assert_states_and_times(test) pin3.assert_states_and_times(test)
def test_led_bar_graph_value(): def test_led_bar_graph_value():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBarGraph(pin1, pin2, pin3) as graph: with LEDBarGraph(pin1, pin2, pin3) as graph:
assert isinstance(graph[0], LED) assert isinstance(graph[0], LED)
assert isinstance(graph[1], LED) assert isinstance(graph[1], LED)
@@ -433,9 +470,9 @@ def test_led_bar_graph_value():
assert graph.value == -2/3 assert graph.value == -2/3
def test_led_bar_graph_active_low(): def test_led_bar_graph_active_low():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBarGraph(pin1, pin2, pin3, active_high=False) as graph: with LEDBarGraph(pin1, pin2, pin3, active_high=False) as graph:
assert not graph.active_high assert not graph.active_high
assert not graph[0].active_high assert not graph[0].active_high
@@ -455,9 +492,9 @@ def test_led_bar_graph_active_low():
assert not pin3.state and pin1.state and pin2.state assert not pin3.state and pin1.state and pin2.state
def test_led_bar_graph_pwm_value(): def test_led_bar_graph_pwm_value():
pin1 = MockPWMPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPWMPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPWMPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBarGraph(pin1, pin2, pin3, pwm=True) as graph: with LEDBarGraph(pin1, pin2, pin3, pwm=True) as graph:
assert isinstance(graph[0], PWMLED) assert isinstance(graph[0], PWMLED)
assert isinstance(graph[1], PWMLED) assert isinstance(graph[1], PWMLED)
@@ -482,9 +519,9 @@ def test_led_bar_graph_pwm_value():
assert graph.value == -1/2 assert graph.value == -1/2
def test_led_bar_graph_bad_value(): def test_led_bar_graph_bad_value():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBarGraph(pin1, pin2, pin3) as graph: with LEDBarGraph(pin1, pin2, pin3) as graph:
with pytest.raises(ValueError): with pytest.raises(ValueError):
graph.value = -2 graph.value = -2
@@ -492,9 +529,9 @@ def test_led_bar_graph_bad_value():
graph.value = 2 graph.value = 2
def test_led_bar_graph_bad_init(): def test_led_bar_graph_bad_init():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with pytest.raises(TypeError): with pytest.raises(TypeError):
LEDBarGraph(pin1, pin2, foo=pin3) LEDBarGraph(pin1, pin2, foo=pin3)
with pytest.raises(ValueError): with pytest.raises(ValueError):
@@ -503,9 +540,9 @@ def test_led_bar_graph_bad_init():
LEDBarGraph(pin1, pin2, pin3, initial_value=2) LEDBarGraph(pin1, pin2, pin3, initial_value=2)
def test_led_bar_graph_initial_value(): def test_led_bar_graph_initial_value():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBarGraph(pin1, pin2, pin3, initial_value=1/3) as graph: with LEDBarGraph(pin1, pin2, pin3, initial_value=1/3) as graph:
assert graph.value == 1/3 assert graph.value == 1/3
assert pin1.state and not (pin2.state or pin3.state) assert pin1.state and not (pin2.state or pin3.state)
@@ -514,9 +551,9 @@ def test_led_bar_graph_initial_value():
assert pin3.state and not (pin1.state or pin2.state) assert pin3.state and not (pin1.state or pin2.state)
def test_led_bar_graph_pwm_initial_value(): def test_led_bar_graph_pwm_initial_value():
pin1 = MockPWMPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPWMPin(3) pin2 = Device.pin_factory.pin(3)
pin3 = MockPWMPin(4) pin3 = Device.pin_factory.pin(4)
with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=0.5) as graph: with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=0.5) as graph:
assert graph.value == 0.5 assert graph.value == 0.5
assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0) assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0)
@@ -525,17 +562,17 @@ def test_led_bar_graph_pwm_initial_value():
assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1) assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1)
def test_led_borg(): def test_led_borg():
pins = [MockPWMPin(n) for n in (17, 27, 22)] pins = [Device.pin_factory.pin(n) for n in (17, 27, 22)]
with LedBorg() as board: with LedBorg() as board:
assert [device.pin for device in board._leds] == pins assert [device.pin for device in board._leds] == pins
def test_pi_liter(): def test_pi_liter():
pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] pins = [Device.pin_factory.pin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)]
with PiLiter() as board: with PiLiter() as board:
assert [device.pin for device in board] == pins assert [device.pin for device in board] == pins
def test_pi_liter_graph(): def test_pi_liter_graph():
pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] pins = [Device.pin_factory.pin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)]
with PiLiterBarGraph() as board: with PiLiterBarGraph() as board:
board.value = 0.5 board.value = 0.5
assert [pin.state for pin in pins] == [1, 1, 1, 1, 0, 0, 0, 0] assert [pin.state for pin in pins] == [1, 1, 1, 1, 0, 0, 0, 0]
@@ -543,9 +580,9 @@ def test_pi_liter_graph():
assert board.value == 5/8 assert board.value == 5/8
def test_traffic_lights(): def test_traffic_lights():
red_pin = MockPin(2) red_pin = Device.pin_factory.pin(2)
amber_pin = MockPin(3) amber_pin = Device.pin_factory.pin(3)
green_pin = MockPin(4) green_pin = Device.pin_factory.pin(4)
with TrafficLights(red_pin, amber_pin, green_pin) as board: with TrafficLights(red_pin, amber_pin, green_pin) as board:
board.red.on() board.red.on()
assert board.red.value assert board.red.value
@@ -574,15 +611,15 @@ def test_traffic_lights():
def test_traffic_lights_bad_init(): def test_traffic_lights_bad_init():
with pytest.raises(ValueError): with pytest.raises(ValueError):
TrafficLights() TrafficLights()
red_pin = MockPin(2) red_pin = Device.pin_factory.pin(2)
amber_pin = MockPin(3) amber_pin = Device.pin_factory.pin(3)
green_pin = MockPin(4) green_pin = Device.pin_factory.pin(4)
yellow_pin = MockPin(5) yellow_pin = Device.pin_factory.pin(5)
with pytest.raises(ValueError): with pytest.raises(ValueError):
TrafficLights(red=red_pin, amber=amber_pin, yellow=yellow_pin, green=green_pin) TrafficLights(red=red_pin, amber=amber_pin, yellow=yellow_pin, green=green_pin)
def test_pi_traffic(): def test_pi_traffic():
pins = [MockPin(n) for n in (9, 10, 11)] pins = [Device.pin_factory.pin(n) for n in (9, 10, 11)]
with PiTraffic() as board: with PiTraffic() as board:
assert [device.pin for device in board] == pins assert [device.pin for device in board] == pins
@@ -591,27 +628,27 @@ def test_pi_stop():
PiStop() PiStop()
with pytest.raises(ValueError): with pytest.raises(ValueError):
PiStop('E') PiStop('E')
pins_a = [MockPin(n) for n in (7, 8, 25)] pins_a = [Device.pin_factory.pin(n) for n in (7, 8, 25)]
with PiStop('A') as board: with PiStop('A') as board:
assert [device.pin for device in board] == pins_a assert [device.pin for device in board] == pins_a
pins_aplus = [MockPin(n) for n in (21, 20, 16)] pins_aplus = [Device.pin_factory.pin(n) for n in (21, 20, 16)]
with PiStop('A+') as board: with PiStop('A+') as board:
assert [device.pin for device in board] == pins_aplus assert [device.pin for device in board] == pins_aplus
pins_b = [MockPin(n) for n in (10, 9, 11)] pins_b = [Device.pin_factory.pin(n) for n in (10, 9, 11)]
with PiStop('B') as board: with PiStop('B') as board:
assert [device.pin for device in board] == pins_b assert [device.pin for device in board] == pins_b
pins_bplus = [MockPin(n) for n in (13, 19, 26)] pins_bplus = [Device.pin_factory.pin(n) for n in (13, 19, 26)]
with PiStop('B+') as board: with PiStop('B+') as board:
assert [device.pin for device in board] == pins_bplus assert [device.pin for device in board] == pins_bplus
pins_c = [MockPin(n) for n in (18, 15, 14)] pins_c = [Device.pin_factory.pin(n) for n in (18, 15, 14)]
with PiStop('C') as board: with PiStop('C') as board:
assert [device.pin for device in board] == pins_c assert [device.pin for device in board] == pins_c
pins_d = [MockPin(n) for n in (2, 3, 4)] pins_d = [Device.pin_factory.pin(n) for n in (2, 3, 4)]
with PiStop('D') as board: with PiStop('D') as board:
assert [device.pin for device in board] == pins_d assert [device.pin for device in board] == pins_d
def test_snow_pi(): def test_snow_pi():
pins = [MockPin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] pins = [Device.pin_factory.pin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)]
with SnowPi() as board: with SnowPi() as board:
assert [device.pin for device in board.leds] == pins assert [device.pin for device in board.leds] == pins
@@ -626,17 +663,17 @@ def test_snow_pi_initial_value():
assert all(device.pin.state == True for device in board.leds) assert all(device.pin.state == True for device in board.leds)
def test_snow_pi_initial_value_pwm(): def test_snow_pi_initial_value_pwm():
pins = [MockPWMPin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] 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: with SnowPi(pwm=True, initial_value=0.5) as board:
assert [device.pin for device in board.leds] == pins assert [device.pin for device in board.leds] == pins
assert all(device.pin.state == 0.5 for device in board.leds) assert all(device.pin.state == 0.5 for device in board.leds)
def test_traffic_lights_buzzer(): def test_traffic_lights_buzzer():
red_pin = MockPin(2) red_pin = Device.pin_factory.pin(2)
amber_pin = MockPin(3) amber_pin = Device.pin_factory.pin(3)
green_pin = MockPin(4) green_pin = Device.pin_factory.pin(4)
buzzer_pin = MockPin(5) buzzer_pin = Device.pin_factory.pin(5)
button_pin = MockPin(6) button_pin = Device.pin_factory.pin(6)
with TrafficLightsBuzzer( with TrafficLightsBuzzer(
TrafficLights(red_pin, amber_pin, green_pin), TrafficLights(red_pin, amber_pin, green_pin),
Buzzer(buzzer_pin), Buzzer(buzzer_pin),
@@ -651,17 +688,17 @@ def test_traffic_lights_buzzer():
assert board.button.is_active assert board.button.is_active
def test_fish_dish(): def test_fish_dish():
pins = [MockPin(n) for n in (9, 22, 4, 8, 7)] pins = [Device.pin_factory.pin(n) for n in (9, 22, 4, 8, 7)]
with FishDish() as board: with FishDish() as board:
assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins
def test_traffic_hat(): def test_traffic_hat():
pins = [MockPin(n) for n in (24, 23, 22, 5, 25)] pins = [Device.pin_factory.pin(n) for n in (24, 23, 22, 5, 25)]
with TrafficHat() as board: with TrafficHat() as board:
assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins
def test_robot(): def test_robot():
pins = [MockPWMPin(n) for n in (2, 3, 4, 5)] pins = [Device.pin_factory.pin(n) for n in (2, 3, 4, 5)]
with Robot((2, 3), (4, 5)) as robot: with Robot((2, 3), (4, 5)) as robot:
assert ( assert (
[device.pin for device in robot.left_motor] + [device.pin for device in robot.left_motor] +
@@ -696,12 +733,12 @@ def test_robot():
assert robot.value == (0, -0.5) assert robot.value == (0, -0.5)
def test_ryanteck_robot(): def test_ryanteck_robot():
pins = [MockPWMPin(n) for n in (17, 18, 22, 23)] pins = [Device.pin_factory.pin(n) for n in (17, 18, 22, 23)]
with RyanteckRobot() as board: with RyanteckRobot() as board:
assert [device.pin for motor in board for device in motor] == pins assert [device.pin for motor in board for device in motor] == pins
def test_camjam_kit_robot(): def test_camjam_kit_robot():
pins = [MockPWMPin(n) for n in (9, 10, 7, 8)] pins = [Device.pin_factory.pin(n) for n in (9, 10, 7, 8)]
with CamJamKitRobot() as board: with CamJamKitRobot() as board:
assert [device.pin for motor in board for device in motor] == pins assert [device.pin for motor in board for device in motor] == pins
@@ -714,7 +751,7 @@ def test_energenie_bad_init():
Energenie(5) Energenie(5)
def test_energenie(): def test_energenie():
pins = [MockPin(n) for n in (17, 22, 23, 27, 24, 25)] pins = [Device.pin_factory.pin(n) for n in (17, 22, 23, 27, 24, 25)]
with Energenie(1, initial_value=True) as device1, \ with Energenie(1, initial_value=True) as device1, \
Energenie(2, initial_value=False) as device2: Energenie(2, initial_value=False) as device2:
assert repr(device1) == '<gpiozero.Energenie object on socket 1>' assert repr(device1) == '<gpiozero.Energenie object on socket 1>'

View File

@@ -6,96 +6,97 @@ from __future__ import (
) )
str = type('') str = type('')
import warnings
import pytest import pytest
from gpiozero.pins.mock import MockPin
from gpiozero import * from gpiozero import *
def teardown_function(function): def teardown_function(function):
MockPin.clear_pins() Device.pin_factory.reset()
# TODO add more devices tests! # TODO add more devices tests!
def test_device_no_pin(): def test_device_bad_pin():
with pytest.raises(GPIOPinMissing): with pytest.raises(GPIOPinMissing):
device = GPIODevice() 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(): def test_device_init():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with GPIODevice(pin) as device: with GPIODevice(pin) as device:
assert not device.closed assert not device.closed
assert device.pin == pin assert device.pin == pin
def test_device_init_twice_same_pin(): def test_device_init_twice_same_pin():
pin = MockPin(2) with GPIODevice(2) as device:
with GPIODevice(pin) as device:
with pytest.raises(GPIOPinInUse): with pytest.raises(GPIOPinInUse):
device2 = GPIODevice(pin) GPIODevice(2)
def test_device_init_twice_different_pin(): def test_device_init_twice_different_pin():
pin = MockPin(2) with GPIODevice(2) as device:
pin2 = MockPin(3) with GPIODevice(3) as device2:
with GPIODevice(pin) as device:
with GPIODevice(pin2) as device2:
pass pass
def test_device_close(): def test_device_close():
pin = MockPin(2) device = GPIODevice(2)
device = GPIODevice(pin) # Don't use "with" here; we're testing close explicitly
device.close() device.close()
assert device.closed assert device.closed
assert device.pin is None assert device.pin is None
def test_device_reopen_same_pin(): def test_device_reopen_same_pin():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
device = GPIODevice(pin) with GPIODevice(pin) as device:
device.close() pass
device2 = GPIODevice(pin) with GPIODevice(pin) as device2:
assert not device2.closed assert not device2.closed
assert device2.pin == pin assert device2.pin is pin
assert device.closed assert device.closed
assert device.pin is None assert device.pin is None
device2.close()
def test_device_repr(): def test_device_repr():
pin = MockPin(2) with GPIODevice(4) as device:
with GPIODevice(pin) as device: assert repr(device) == '<gpiozero.GPIODevice object on pin %s, is_active=False>' % device.pin
assert repr(device) == '<gpiozero.GPIODevice object on pin %s, is_active=False>' % pin
def test_device_repr_after_close(): def test_device_repr_after_close():
pin = MockPin(2) with GPIODevice(2) as device:
device = GPIODevice(pin) pass
device.close()
assert repr(device) == '<gpiozero.GPIODevice object closed>' assert repr(device) == '<gpiozero.GPIODevice object closed>'
def test_device_unknown_attr(): def test_device_unknown_attr():
pin = MockPin(2) with GPIODevice(2) as device:
with GPIODevice(pin) as device:
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
device.foo = 1 device.foo = 1
def test_device_context_manager(): def test_device_context_manager():
pin = MockPin(2) with GPIODevice(2) as device:
with GPIODevice(pin) as device:
assert not device.closed assert not device.closed
assert device.closed assert device.closed
def test_composite_device_sequence(): def test_composite_device_sequence():
with CompositeDevice( with CompositeDevice(
InputDevice(MockPin(2)), InputDevice(4),
InputDevice(MockPin(3)) InputDevice(5)
) as device: ) as device:
assert len(device) == 2 assert len(device) == 2
assert device[0].pin.number == 2 assert device[0].pin.number == 4
assert device[1].pin.number == 3 assert device[1].pin.number == 5
assert device.namedtuple._fields == ('_0', '_1') assert device.namedtuple._fields == ('_0', '_1')
def test_composite_device_values(): def test_composite_device_values():
with CompositeDevice( with CompositeDevice(
InputDevice(MockPin(2)), InputDevice(4),
InputDevice(MockPin(3)) InputDevice(5)
) as device: ) as device:
assert device.value == (0, 0) assert device.value == (0, 0)
assert not device.is_active assert not device.is_active
@@ -105,8 +106,8 @@ def test_composite_device_values():
def test_composite_device_named(): def test_composite_device_named():
with CompositeDevice( with CompositeDevice(
foo=InputDevice(MockPin(2)), foo=InputDevice(4),
bar=InputDevice(MockPin(3)), bar=InputDevice(5),
_order=('foo', 'bar') _order=('foo', 'bar')
) as device: ) as device:
assert device.namedtuple._fields == ('foo', 'bar') assert device.namedtuple._fields == ('foo', 'bar')
@@ -121,13 +122,13 @@ def test_composite_device_bad_init():
with pytest.raises(ValueError): with pytest.raises(ValueError):
CompositeDevice(2) CompositeDevice(2)
with pytest.raises(ValueError): with pytest.raises(ValueError):
CompositeDevice(MockPin(2)) CompositeDevice(Device.pin_factory.pin(2))
def test_composite_device_read_only(): def test_composite_device_read_only():
device = CompositeDevice( with CompositeDevice(
foo=InputDevice(MockPin(2)), foo=InputDevice(4),
bar=InputDevice(MockPin(3)) bar=InputDevice(5)
) ) as device:
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
device.foo = 1 device.foo = 1

View File

@@ -12,57 +12,53 @@ import pytest
from threading import Event from threading import Event
from functools import partial from functools import partial
from gpiozero.pins.mock import ( from gpiozero.pins.mock import MockChargingPin, MockTriggerPin
MockPin,
MockPulledUpPin,
MockChargingPin,
MockTriggerPin,
)
from gpiozero import * from gpiozero import *
def teardown_function(function): def teardown_function(function):
MockPin.clear_pins() Device.pin_factory.reset()
Device._reservations.clear()
def test_input_initial_values(): def test_input_initial_values():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with InputDevice(pin, pull_up=True) as device: with InputDevice(pin, pull_up=True) as device:
assert pin.function == 'input' assert pin.function == 'input'
assert pin.pull == 'up' assert pin.pull == 'up'
assert device.pull_up assert device.pull_up
device.close() with InputDevice(pin, pull_up=False) as device:
device = InputDevice(pin, pull_up=False)
assert pin.pull == 'down' assert pin.pull == 'down'
assert not device.pull_up assert not device.pull_up
def test_input_is_active_low(): def test_input_is_active_low():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with InputDevice(pin, pull_up=True) as device: with InputDevice(pin, pull_up=True) as device:
pin.drive_high() pin.drive_high()
assert not device.is_active assert not device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=True, is_active=False>' assert repr(device) == '<gpiozero.InputDevice object on pin GPIO2, pull_up=True, is_active=False>'
pin.drive_low() pin.drive_low()
assert device.is_active assert device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=True, is_active=True>' assert repr(device) == '<gpiozero.InputDevice object on pin GPIO2, pull_up=True, is_active=True>'
def test_input_is_active_high(): def test_input_is_active_high():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with InputDevice(pin, pull_up=False) as device: with InputDevice(pin, pull_up=False) as device:
pin.drive_high() pin.drive_high()
assert device.is_active assert device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=False, is_active=True>' assert repr(device) == '<gpiozero.InputDevice object on pin GPIO4, pull_up=False, is_active=True>'
pin.drive_low() pin.drive_low()
assert not device.is_active assert not device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=False, is_active=False>' assert repr(device) == '<gpiozero.InputDevice object on pin GPIO4, pull_up=False, is_active=False>'
def test_input_pulled_up(): def test_input_pulled_up():
pin = MockPulledUpPin(2) pin = Device.pin_factory.pin(2)
with pytest.raises(PinFixedPull): with pytest.raises(PinFixedPull):
InputDevice(pin, pull_up=False) InputDevice(pin, pull_up=False)
def test_input_event_activated(): def test_input_event_activated():
event = Event() event = Event()
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with DigitalInputDevice(pin) as device: with DigitalInputDevice(pin) as device:
device.when_activated = lambda: event.set() device.when_activated = lambda: event.set()
assert not event.is_set() assert not event.is_set()
@@ -71,7 +67,7 @@ def test_input_event_activated():
def test_input_event_deactivated(): def test_input_event_deactivated():
event = Event() event = Event()
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with DigitalInputDevice(pin) as device: with DigitalInputDevice(pin) as device:
device.when_deactivated = lambda: event.set() device.when_deactivated = lambda: event.set()
assert not event.is_set() assert not event.is_set()
@@ -82,7 +78,7 @@ def test_input_event_deactivated():
def test_input_partial_callback(): def test_input_partial_callback():
event = Event() event = Event()
pin = MockPin(2) pin = Device.pin_factory.pin(4)
def foo(a, b): def foo(a, b):
event.set() event.set()
return a + b return a + b
@@ -95,22 +91,22 @@ def test_input_partial_callback():
assert event.is_set() assert event.is_set()
def test_input_wait_active(): def test_input_wait_active():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with DigitalInputDevice(pin) as device: with DigitalInputDevice(pin) as device:
pin.drive_high() pin.drive_high()
assert device.wait_for_active(1) assert device.wait_for_active(1)
assert not device.wait_for_inactive(0) assert not device.wait_for_inactive(0)
def test_input_wait_inactive(): def test_input_wait_inactive():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with DigitalInputDevice(pin) as device: with DigitalInputDevice(pin) as device:
assert device.wait_for_inactive(1) assert device.wait_for_inactive(1)
assert not device.wait_for_active(0) assert not device.wait_for_active(0)
def test_input_smoothed_attrib(): def test_input_smoothed_attrib():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with SmoothedInputDevice(pin, threshold=0.5, queue_len=5, partial=False) as device: with SmoothedInputDevice(pin, threshold=0.5, queue_len=5, partial=False) as device:
assert repr(device) == '<gpiozero.SmoothedInputDevice object on pin=MOCK2, pull_up=False>' assert repr(device) == '<gpiozero.SmoothedInputDevice object on pin GPIO4, pull_up=False>'
assert device.threshold == 0.5 assert device.threshold == 0.5
assert device.queue_len == 5 assert device.queue_len == 5
assert not device.partial assert not device.partial
@@ -120,7 +116,7 @@ def test_input_smoothed_attrib():
device.threshold = 1 device.threshold = 1
def test_input_smoothed_values(): def test_input_smoothed_values():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with SmoothedInputDevice(pin) as device: with SmoothedInputDevice(pin) as device:
device._queue.start() device._queue.start()
assert not device.is_active assert not device.is_active
@@ -130,7 +126,7 @@ def test_input_smoothed_values():
assert device.wait_for_inactive(1) assert device.wait_for_inactive(1)
def test_input_button(): def test_input_button():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with Button(pin) as button: with Button(pin) as button:
assert pin.pull == 'up' assert pin.pull == 'up'
assert not button.is_pressed assert not button.is_pressed
@@ -142,7 +138,7 @@ def test_input_button():
assert button.wait_for_release(1) assert button.wait_for_release(1)
def test_input_line_sensor(): def test_input_line_sensor():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with LineSensor(pin) as sensor: with LineSensor(pin) as sensor:
pin.drive_low() # logic is inverted for line sensor pin.drive_low() # logic is inverted for line sensor
assert sensor.wait_for_line(1) assert sensor.wait_for_line(1)
@@ -152,7 +148,7 @@ def test_input_line_sensor():
assert not sensor.line_detected assert not sensor.line_detected
def test_input_motion_sensor(): def test_input_motion_sensor():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with MotionSensor(pin) as sensor: with MotionSensor(pin) as sensor:
pin.drive_high() pin.drive_high()
assert sensor.wait_for_motion(1) assert sensor.wait_for_motion(1)
@@ -164,7 +160,7 @@ def test_input_motion_sensor():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_input_light_sensor(): def test_input_light_sensor():
pin = MockChargingPin(2) pin = Device.pin_factory.pin(4, pin_class=MockChargingPin)
with LightSensor(pin) as sensor: with LightSensor(pin) as sensor:
pin.charge_time = 0.1 pin.charge_time = 0.1
assert sensor.wait_for_dark(1) assert sensor.wait_for_dark(1)
@@ -174,10 +170,8 @@ def test_input_light_sensor():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_input_distance_sensor(): def test_input_distance_sensor():
echo_pin = MockPin(2) echo_pin = Device.pin_factory.pin(4)
trig_pin = MockTriggerPin(3) trig_pin = Device.pin_factory.pin(5, pin_class=MockTriggerPin, echo_pin=echo_pin, echo_time=0.02)
trig_pin.echo_pin = echo_pin
trig_pin.echo_time = 0.02
with pytest.raises(ValueError): with pytest.raises(ValueError):
DistanceSensor(echo_pin, trig_pin, max_distance=-1) DistanceSensor(echo_pin, trig_pin, max_distance=-1)
# normal queue len is large (because the sensor is *really* jittery) but # normal queue len is large (because the sensor is *really* jittery) but

View File

@@ -11,25 +11,24 @@ from threading import Event
import pytest import pytest
from gpiozero.pins.mock import MockPin, MockPWMPin from gpiozero.pins.mock import MockPWMPin, MockPin
from gpiozero import * from gpiozero import *
def teardown_function(function): def teardown_function(function):
MockPin.clear_pins() Device.pin_factory.reset()
# Some rough tests to make sure our MockPin is up to snuff. This is just # 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... # enough to get reasonable coverage but it's by no means comprehensive...
def test_mock_pin_init(): def test_mock_pin_init():
with pytest.raises(TypeError):
MockPin()
with pytest.raises(ValueError): with pytest.raises(ValueError):
MockPin(60) Device.pin_factory.pin(60)
assert MockPin(2).number == 2 assert Device.pin_factory.pin(2).number == 2
def test_mock_pin_defaults(): def test_mock_pin_defaults():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
assert pin.bounce == None assert pin.bounce == None
assert pin.edges == 'both' assert pin.edges == 'both'
assert pin.frequency == None assert pin.frequency == None
@@ -37,32 +36,28 @@ def test_mock_pin_defaults():
assert pin.pull == 'floating' assert pin.pull == 'floating'
assert pin.state == 0 assert pin.state == 0
assert pin.when_changed == None assert pin.when_changed == None
pin.close()
pin = Device.pin_factory.pin(2)
assert pin.pull == 'up'
def test_mock_pin_open_close(): def test_mock_pin_open_close():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
pin.close() pin.close()
def test_mock_pin_init_twice_same_pin(): def test_mock_pin_init_twice_same_pin():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(pin1.number) pin2 = Device.pin_factory.pin(pin1.number)
assert pin1 is pin2 assert pin1 is pin2
def test_mock_pin_init_twice_different_pin(): def test_mock_pin_init_twice_different_pin():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2)
pin2 = MockPin(pin1.number+1) pin2 = Device.pin_factory.pin(pin1.number+1)
assert pin1 != pin2 assert pin1 != pin2
assert pin1.number == 2 assert pin1.number == 2
assert pin2.number == pin1.number+1 assert pin2.number == pin1.number+1
def test_mock_pwm_pin_init():
with pytest.raises(TypeError):
MockPWMPin()
with pytest.raises(ValueError):
MockPWMPin(60)
assert MockPWMPin(2).number == 2
def test_mock_pwm_pin_defaults(): def test_mock_pwm_pin_defaults():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
assert pin.bounce == None assert pin.bounce == None
assert pin.edges == 'both' assert pin.edges == 'both'
assert pin.frequency == None assert pin.frequency == None
@@ -70,40 +65,43 @@ def test_mock_pwm_pin_defaults():
assert pin.pull == 'floating' assert pin.pull == 'floating'
assert pin.state == 0 assert pin.state == 0
assert pin.when_changed == None 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(): def test_mock_pwm_pin_open_close():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
pin.close() pin.close()
def test_mock_pwm_pin_init_twice_same_pin(): def test_mock_pwm_pin_init_twice_same_pin():
pin1 = MockPWMPin(2) pin1 = Device.pin_factory.pin(2, pin_class=MockPWMPin)
pin2 = MockPWMPin(pin1.number) pin2 = Device.pin_factory.pin(pin1.number, pin_class=MockPWMPin)
assert pin1 is pin2 assert pin1 is pin2
def test_mock_pwm_pin_init_twice_different_pin(): def test_mock_pwm_pin_init_twice_different_pin():
pin1 = MockPWMPin(2) pin1 = Device.pin_factory.pin(2, pin_class=MockPWMPin)
pin2 = MockPWMPin(pin1.number+1) pin2 = Device.pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin)
assert pin1 != pin2 assert pin1 != pin2
assert pin1.number == 2 assert pin1.number == 2
assert pin2.number == pin1.number+1 assert pin2.number == pin1.number+1
def test_mock_pin_init_twice_different_modes(): def test_mock_pin_init_twice_different_modes():
pin1 = MockPin(2) pin1 = Device.pin_factory.pin(2, pin_class=MockPin)
pin2 = MockPWMPin(pin1.number+1) pin2 = Device.pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin)
assert pin1 != pin2 assert pin1 != pin2
with pytest.raises(ValueError): with pytest.raises(ValueError):
pin3 = MockPWMPin(pin1.number) Device.pin_factory.pin(pin1.number, pin_class=MockPWMPin)
with pytest.raises(ValueError): with pytest.raises(ValueError):
pin4 = MockPin(pin2.number) Device.pin_factory.pin(pin2.number, pin_class=MockPin)
def test_mock_pin_frequency_unsupported(): def test_mock_pin_frequency_unsupported():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
pin.frequency = None pin.frequency = None
with pytest.raises(PinPWMUnsupported): with pytest.raises(PinPWMUnsupported):
pin.frequency = 100 pin.frequency = 100
def test_mock_pin_frequency_supported(): def test_mock_pin_frequency_supported():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
pin.function = 'output' pin.function = 'output'
assert pin.frequency is None assert pin.frequency is None
pin.frequency = 100 pin.frequency = 100
@@ -112,20 +110,25 @@ def test_mock_pin_frequency_supported():
assert not pin.state assert not pin.state
def test_mock_pin_pull(): def test_mock_pin_pull():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
pin.function = 'input' pin.function = 'input'
assert pin.pull == 'floating' assert pin.pull == 'floating'
pin.pull = 'up' pin.pull = 'up'
assert pin.state assert pin.state
pin.pull = 'down' pin.pull = 'down'
assert not pin.state 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(): def test_mock_pin_state():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with pytest.raises(PinSetInput): with pytest.raises(PinSetInput):
pin.state = 1 pin.state = 1
pin.function = 'output' pin.function = 'output'
assert pin.state == 0
pin.state = 1 pin.state = 1
assert pin.state == 1 assert pin.state == 1
pin.state = 0 pin.state = 0
@@ -134,11 +137,10 @@ def test_mock_pin_state():
assert pin.state == 1 assert pin.state == 1
def test_mock_pwm_pin_state(): def test_mock_pwm_pin_state():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with pytest.raises(PinSetInput): with pytest.raises(PinSetInput):
pin.state = 1 pin.state = 1
pin.function = 'output' pin.function = 'output'
assert pin.state == 0
pin.state = 1 pin.state = 1
assert pin.state == 1 assert pin.state == 1
pin.state = 0 pin.state = 0
@@ -147,7 +149,7 @@ def test_mock_pwm_pin_state():
assert pin.state == 0.5 assert pin.state == 0.5
def test_mock_pin_edges(): def test_mock_pin_edges():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
assert pin.when_changed is None assert pin.when_changed is None
fired = Event() fired = Event()
pin.function = 'input' pin.function = 'input'

View File

@@ -16,15 +16,17 @@ except ImportError:
import pytest import pytest
from gpiozero.pins.mock import MockPin, MockPWMPin from gpiozero.pins.mock import MockPWMPin
from gpiozero import * from gpiozero import *
def teardown_function(function): def teardown_function(function):
MockPin.clear_pins() Device.pin_factory.reset()
Device._reservations.clear()
def test_output_initial_values(): def test_output_initial_values():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with OutputDevice(pin, initial_value=False) as device: with OutputDevice(pin, initial_value=False) as device:
assert pin.function == 'output' assert pin.function == 'output'
assert not pin.state assert not pin.state
@@ -35,7 +37,7 @@ def test_output_initial_values():
assert state == pin.state assert state == pin.state
def test_output_write_active_high(): def test_output_write_active_high():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with OutputDevice(pin) as device: with OutputDevice(pin) as device:
device.on() device.on()
assert pin.state assert pin.state
@@ -43,7 +45,7 @@ def test_output_write_active_high():
assert not pin.state assert not pin.state
def test_output_write_active_low(): def test_output_write_active_low():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with OutputDevice(pin, active_high=False) as device: with OutputDevice(pin, active_high=False) as device:
device.on() device.on()
assert not pin.state assert not pin.state
@@ -51,7 +53,7 @@ def test_output_write_active_low():
assert pin.state assert pin.state
def test_output_write_closed(): def test_output_write_closed():
with OutputDevice(MockPin(2)) as device: with OutputDevice(Device.pin_factory.pin(2)) as device:
device.close() device.close()
assert device.closed assert device.closed
device.close() device.close()
@@ -60,14 +62,14 @@ def test_output_write_closed():
device.on() device.on()
def test_output_write_silly(): def test_output_write_silly():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with OutputDevice(pin) as device: with OutputDevice(pin) as device:
pin.function = 'input' pin.function = 'input'
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
device.on() device.on()
def test_output_value(): def test_output_value():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with OutputDevice(pin) as device: with OutputDevice(pin) as device:
assert not device.value assert not device.value
assert not pin.state assert not pin.state
@@ -79,7 +81,7 @@ def test_output_value():
assert not pin.state assert not pin.state
def test_output_digital_toggle(): def test_output_digital_toggle():
pin = MockPin(2) pin = Device.pin_factory.pin(2)
with DigitalOutputDevice(pin) as device: with DigitalOutputDevice(pin) as device:
assert not device.value assert not device.value
assert not pin.state assert not pin.state
@@ -93,7 +95,7 @@ def test_output_digital_toggle():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_output_blink_background(): def test_output_blink_background():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with DigitalOutputDevice(pin) as device: with DigitalOutputDevice(pin) as device:
start = time() start = time()
device.blink(0.1, 0.1, n=2) device.blink(0.1, 0.1, n=2)
@@ -111,7 +113,7 @@ def test_output_blink_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_output_blink_foreground(): def test_output_blink_foreground():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with DigitalOutputDevice(pin) as device: with DigitalOutputDevice(pin) as device:
start = time() start = time()
device.blink(0.1, 0.1, n=2, background=False) device.blink(0.1, 0.1, n=2, background=False)
@@ -125,7 +127,7 @@ def test_output_blink_foreground():
]) ])
def test_output_blink_interrupt_on(): def test_output_blink_interrupt_on():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with DigitalOutputDevice(pin) as device: with DigitalOutputDevice(pin) as device:
device.blink(1, 0.1) device.blink(1, 0.1)
sleep(0.2) sleep(0.2)
@@ -133,7 +135,7 @@ def test_output_blink_interrupt_on():
pin.assert_states([False, True, False]) pin.assert_states([False, True, False])
def test_output_blink_interrupt_off(): def test_output_blink_interrupt_off():
pin = MockPin(2) pin = Device.pin_factory.pin(4)
with DigitalOutputDevice(pin) as device: with DigitalOutputDevice(pin) as device:
device.blink(0.1, 1) device.blink(0.1, 1)
sleep(0.2) sleep(0.2)
@@ -142,14 +144,14 @@ def test_output_blink_interrupt_off():
def test_output_pwm_bad_initial_value(): def test_output_pwm_bad_initial_value():
with pytest.raises(ValueError): with pytest.raises(ValueError):
PWMOutputDevice(MockPin(2), initial_value=2) PWMOutputDevice(Device.pin_factory.pin(2), initial_value=2)
def test_output_pwm_not_supported(): def test_output_pwm_not_supported():
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
PWMOutputDevice(MockPin(2)) PWMOutputDevice(Device.pin_factory.pin(2))
def test_output_pwm_states(): def test_output_pwm_states():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
device.value = 0.1 device.value = 0.1
device.value = 0.2 device.value = 0.2
@@ -157,7 +159,7 @@ def test_output_pwm_states():
pin.assert_states([0.0, 0.1, 0.2, 0.0]) pin.assert_states([0.0, 0.1, 0.2, 0.0])
def test_output_pwm_read(): def test_output_pwm_read():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with PWMOutputDevice(pin, frequency=100) as device: with PWMOutputDevice(pin, frequency=100) as device:
assert device.frequency == 100 assert device.frequency == 100
device.value = 0.1 device.value = 0.1
@@ -170,14 +172,14 @@ def test_output_pwm_read():
assert device.frequency is None assert device.frequency is None
def test_output_pwm_write(): def test_output_pwm_write():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
device.on() device.on()
device.off() device.off()
pin.assert_states([False, True, False]) pin.assert_states([False, True, False])
def test_output_pwm_toggle(): def test_output_pwm_toggle():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
device.toggle() device.toggle()
device.value = 0.5 device.value = 0.5
@@ -187,7 +189,7 @@ def test_output_pwm_toggle():
pin.assert_states([False, True, 0.5, 0.1, 0.9, False]) pin.assert_states([False, True, 0.5, 0.1, 0.9, False])
def test_output_pwm_active_high_read(): def test_output_pwm_active_high_read():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with PWMOutputDevice(pin, active_high=False) as device: with PWMOutputDevice(pin, active_high=False) as device:
device.value = 0.1 device.value = 0.1
assert isclose(device.value, 0.1) assert isclose(device.value, 0.1)
@@ -196,17 +198,18 @@ def test_output_pwm_active_high_read():
assert device.value assert device.value
def test_output_pwm_bad_value(): def test_output_pwm_bad_value():
with PWMOutputDevice(Device.pin_factory.pin(2, pin_class=MockPWMPin)) as device:
with pytest.raises(ValueError): with pytest.raises(ValueError):
PWMOutputDevice(MockPWMPin(2)).value = 2 device.value = 2
def test_output_pwm_write_closed(): def test_output_pwm_write_closed():
device = PWMOutputDevice(MockPWMPin(2)) with PWMOutputDevice(Device.pin_factory.pin(2, pin_class=MockPWMPin)) as device:
device.close() device.close()
with pytest.raises(GPIODeviceClosed): with pytest.raises(GPIODeviceClosed):
device.on() device.on()
def test_output_pwm_write_silly(): def test_output_pwm_write_silly():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
pin.function = 'input' pin.function = 'input'
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
@@ -215,7 +218,7 @@ def test_output_pwm_write_silly():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_output_pwm_blink_background(): def test_output_pwm_blink_background():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
start = time() start = time()
device.blink(0.1, 0.1, n=2) device.blink(0.1, 0.1, n=2)
@@ -233,7 +236,7 @@ def test_output_pwm_blink_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_output_pwm_blink_foreground(): def test_output_pwm_blink_foreground():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
start = time() start = time()
device.blink(0.1, 0.1, n=2, background=False) device.blink(0.1, 0.1, n=2, background=False)
@@ -249,7 +252,7 @@ def test_output_pwm_blink_foreground():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_output_pwm_fade_background(): def test_output_pwm_fade_background():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
start = time() start = time()
device.blink(0, 0, 0.2, 0.2, n=2) device.blink(0, 0, 0.2, 0.2, n=2)
@@ -283,7 +286,7 @@ def test_output_pwm_fade_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_output_pwm_fade_foreground(): def test_output_pwm_fade_foreground():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
start = time() start = time()
device.blink(0, 0, 0.2, 0.2, n=2, background=False) device.blink(0, 0, 0.2, 0.2, n=2, background=False)
@@ -315,7 +318,7 @@ def test_output_pwm_fade_foreground():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_output_pwm_pulse_background(): def test_output_pwm_pulse_background():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
start = time() start = time()
device.pulse(0.2, 0.2, n=2) device.pulse(0.2, 0.2, n=2)
@@ -349,7 +352,7 @@ def test_output_pwm_pulse_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_output_pwm_pulse_foreground(): def test_output_pwm_pulse_foreground():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
start = time() start = time()
device.pulse(0.2, 0.2, n=2, background=False) device.pulse(0.2, 0.2, n=2, background=False)
@@ -379,7 +382,7 @@ def test_output_pwm_pulse_foreground():
]) ])
def test_output_pwm_blink_interrupt(): def test_output_pwm_blink_interrupt():
pin = MockPWMPin(2) pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device: with PWMOutputDevice(pin) as device:
device.blink(1, 0.1) device.blink(1, 0.1)
sleep(0.2) sleep(0.2)
@@ -391,7 +394,7 @@ def test_rgbled_missing_pins():
RGBLED() RGBLED()
def test_rgbled_initial_value(): def test_rgbled_initial_value():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
with RGBLED(r, g, b, initial_value=(0.1, 0.2, 0)) as device: with RGBLED(r, g, b, initial_value=(0.1, 0.2, 0)) as device:
assert r.frequency assert r.frequency
assert g.frequency assert g.frequency
@@ -401,24 +404,24 @@ def test_rgbled_initial_value():
assert isclose(b.state, 0.0) assert isclose(b.state, 0.0)
def test_rgbled_initial_value_nonpwm(): def test_rgbled_initial_value_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with RGBLED(r, g, b, pwm=False, initial_value=(0, 1, 1)) as device: with RGBLED(r, g, b, pwm=False, initial_value=(0, 1, 1)) as device:
assert r.state == 0 assert r.state == 0
assert g.state == 1 assert g.state == 1
assert b.state == 1 assert b.state == 1
def test_rgbled_initial_bad_value(): def test_rgbled_initial_bad_value():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
with pytest.raises(ValueError): with pytest.raises(ValueError):
RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2)) RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2))
def test_rgbled_initial_bad_value_nonpwm(): def test_rgbled_initial_bad_value_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with pytest.raises(ValueError): with pytest.raises(ValueError):
RGBLED(r, g, b, pwm=False, initial_value=(0.1, 0.2, 0)) RGBLED(r, g, b, pwm=False, initial_value=(0.1, 0.2, 0))
def test_rgbled_value(): def test_rgbled_value():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
assert isinstance(device._leds[0], PWMLED) assert isinstance(device._leds[0], PWMLED)
assert isinstance(device._leds[1], PWMLED) assert isinstance(device._leds[1], PWMLED)
@@ -436,7 +439,7 @@ def test_rgbled_value():
assert device.value == (0.5, 0.5, 0.5) assert device.value == (0.5, 0.5, 0.5)
def test_rgbled_value_nonpwm(): def test_rgbled_value_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
assert isinstance(device._leds[0], LED) assert isinstance(device._leds[0], LED)
assert isinstance(device._leds[1], LED) assert isinstance(device._leds[1], LED)
@@ -451,7 +454,7 @@ def test_rgbled_value_nonpwm():
assert device.value == (0, 0, 0) assert device.value == (0, 0, 0)
def test_rgbled_bad_value(): def test_rgbled_bad_value():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
with pytest.raises(ValueError): with pytest.raises(ValueError):
device.value = (2, 0, 0) device.value = (2, 0, 0)
@@ -460,7 +463,7 @@ def test_rgbled_bad_value():
device.value = (0, -1, 0) device.value = (0, -1, 0)
def test_rgbled_bad_value_nonpwm(): def test_rgbled_bad_value_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
with pytest.raises(ValueError): with pytest.raises(ValueError):
device.value = (2, 0, 0) device.value = (2, 0, 0)
@@ -478,7 +481,7 @@ def test_rgbled_bad_value_nonpwm():
device.value = (0, 0, 0.5) device.value = (0, 0, 0.5)
def test_rgbled_toggle(): def test_rgbled_toggle():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
assert not device.is_active assert not device.is_active
assert device.value == (0, 0, 0) assert device.value == (0, 0, 0)
@@ -490,7 +493,7 @@ def test_rgbled_toggle():
assert device.value == (0, 0, 0) assert device.value == (0, 0, 0)
def test_rgbled_toggle_nonpwm(): def test_rgbled_toggle_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
assert not device.is_active assert not device.is_active
assert device.value == (0, 0, 0) assert device.value == (0, 0, 0)
@@ -501,10 +504,18 @@ def test_rgbled_toggle_nonpwm():
assert not device.is_active assert not device.is_active
assert device.value == (0, 0, 0) 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(r, g, b, 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'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_rgbled_blink_background(): def test_rgbled_blink_background():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
start = time() start = time()
device.blink(0.1, 0.1, n=2) device.blink(0.1, 0.1, n=2)
@@ -525,7 +536,7 @@ def test_rgbled_blink_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_rgbled_blink_background_nonpwm(): def test_rgbled_blink_background_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
start = time() start = time()
device.blink(0.1, 0.1, n=2) device.blink(0.1, 0.1, n=2)
@@ -546,7 +557,7 @@ def test_rgbled_blink_background_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_rgbled_blink_foreground(): def test_rgbled_blink_foreground():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
start = time() start = time()
device.blink(0.1, 0.1, n=2, background=False) device.blink(0.1, 0.1, n=2, background=False)
@@ -565,7 +576,7 @@ def test_rgbled_blink_foreground():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_rgbled_blink_foreground_nonpwm(): def test_rgbled_blink_foreground_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
start = time() start = time()
device.blink(0.1, 0.1, n=2, background=False) device.blink(0.1, 0.1, n=2, background=False)
@@ -584,7 +595,7 @@ def test_rgbled_blink_foreground_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_rgbled_fade_background(): def test_rgbled_fade_background():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
start = time() start = time()
device.blink(0, 0, 0.2, 0.2, n=2) device.blink(0, 0, 0.2, 0.2, n=2)
@@ -619,7 +630,7 @@ def test_rgbled_fade_background():
b.assert_states_and_times(expected) b.assert_states_and_times(expected)
def test_rgbled_fade_background_nonpwm(): def test_rgbled_fade_background_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
with pytest.raises(ValueError): with pytest.raises(ValueError):
device.blink(0, 0, 0.2, 0.2, n=2) device.blink(0, 0, 0.2, 0.2, n=2)
@@ -627,7 +638,7 @@ def test_rgbled_fade_background_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_rgbled_fade_foreground(): def test_rgbled_fade_foreground():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
start = time() start = time()
device.blink(0, 0, 0.2, 0.2, n=2, background=False) device.blink(0, 0, 0.2, 0.2, n=2, background=False)
@@ -660,7 +671,7 @@ def test_rgbled_fade_foreground():
b.assert_states_and_times(expected) b.assert_states_and_times(expected)
def test_rgbled_fade_foreground_nonpwm(): def test_rgbled_fade_foreground_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
with pytest.raises(ValueError): with pytest.raises(ValueError):
device.blink(0, 0, 0.2, 0.2, n=2, background=False) device.blink(0, 0, 0.2, 0.2, n=2, background=False)
@@ -668,7 +679,7 @@ def test_rgbled_fade_foreground_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_rgbled_pulse_background(): def test_rgbled_pulse_background():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
start = time() start = time()
device.pulse(0.2, 0.2, n=2) device.pulse(0.2, 0.2, n=2)
@@ -703,7 +714,7 @@ def test_rgbled_pulse_background():
b.assert_states_and_times(expected) b.assert_states_and_times(expected)
def test_rgbled_pulse_background_nonpwm(): def test_rgbled_pulse_background_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
with pytest.raises(ValueError): with pytest.raises(ValueError):
device.pulse(0.2, 0.2, n=2) device.pulse(0.2, 0.2, n=2)
@@ -711,7 +722,7 @@ def test_rgbled_pulse_background_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy') reason='timing is too random on pypy')
def test_rgbled_pulse_foreground(): def test_rgbled_pulse_foreground():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
start = time() start = time()
device.pulse(0.2, 0.2, n=2, background=False) device.pulse(0.2, 0.2, n=2, background=False)
@@ -744,13 +755,13 @@ def test_rgbled_pulse_foreground():
b.assert_states_and_times(expected) b.assert_states_and_times(expected)
def test_rgbled_pulse_foreground_nonpwm(): def test_rgbled_pulse_foreground_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
with pytest.raises(ValueError): with pytest.raises(ValueError):
device.pulse(0.2, 0.2, n=2, background=False) device.pulse(0.2, 0.2, n=2, background=False)
def test_rgbled_blink_interrupt(): def test_rgbled_blink_interrupt():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
device.blink(1, 0.1) device.blink(1, 0.1)
sleep(0.2) sleep(0.2)
@@ -760,7 +771,7 @@ def test_rgbled_blink_interrupt():
b.assert_states([0, 1, 0]) b.assert_states([0, 1, 0])
def test_rgbled_blink_interrupt_nonpwm(): def test_rgbled_blink_interrupt_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (4, 5, 6))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
device.blink(1, 0.1) device.blink(1, 0.1)
sleep(0.2) sleep(0.2)
@@ -770,7 +781,7 @@ def test_rgbled_blink_interrupt_nonpwm():
b.assert_states([0, 1, 0]) b.assert_states([0, 1, 0])
def test_rgbled_close(): def test_rgbled_close():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
with RGBLED(r, g, b) as device: with RGBLED(r, g, b) as device:
assert not device.closed assert not device.closed
device.close() device.close()
@@ -779,7 +790,7 @@ def test_rgbled_close():
assert device.closed assert device.closed
def test_rgbled_close_nonpwm(): def test_rgbled_close_nonpwm():
r, g, b = (MockPin(i) for i in (1, 2, 3)) r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
with RGBLED(r, g, b, pwm=False) as device: with RGBLED(r, g, b, pwm=False) as device:
assert not device.closed assert not device.closed
device.close() device.close()
@@ -792,8 +803,8 @@ def test_motor_missing_pins():
Motor() Motor()
def test_motor_pins(): def test_motor_pins():
f = MockPWMPin(1) f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
b = MockPWMPin(2) b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with Motor(f, b) as device: with Motor(f, b) as device:
assert device.forward_device.pin is f assert device.forward_device.pin is f
assert isinstance(device.forward_device, PWMOutputDevice) assert isinstance(device.forward_device, PWMOutputDevice)
@@ -801,8 +812,8 @@ def test_motor_pins():
assert isinstance(device.backward_device, PWMOutputDevice) assert isinstance(device.backward_device, PWMOutputDevice)
def test_motor_pins_nonpwm(): def test_motor_pins_nonpwm():
f = MockPin(1) f = Device.pin_factory.pin(1)
b = MockPin(2) b = Device.pin_factory.pin(2)
with Motor(f, b, pwm=False) as device: with Motor(f, b, pwm=False) as device:
assert device.forward_device.pin is f assert device.forward_device.pin is f
assert isinstance(device.forward_device, DigitalOutputDevice) assert isinstance(device.forward_device, DigitalOutputDevice)
@@ -810,8 +821,8 @@ def test_motor_pins_nonpwm():
assert isinstance(device.backward_device, DigitalOutputDevice) assert isinstance(device.backward_device, DigitalOutputDevice)
def test_motor_close(): def test_motor_close():
f = MockPWMPin(1) f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
b = MockPWMPin(2) b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with Motor(f, b) as device: with Motor(f, b) as device:
device.close() device.close()
assert device.closed assert device.closed
@@ -821,8 +832,8 @@ def test_motor_close():
assert device.closed assert device.closed
def test_motor_close_nonpwm(): def test_motor_close_nonpwm():
f = MockPin(1) f = Device.pin_factory.pin(1)
b = MockPin(2) b = Device.pin_factory.pin(2)
with Motor(f, b, pwm=False) as device: with Motor(f, b, pwm=False) as device:
device.close() device.close()
assert device.closed assert device.closed
@@ -830,8 +841,8 @@ def test_motor_close_nonpwm():
assert device.backward_device.pin is None assert device.backward_device.pin is None
def test_motor_value(): def test_motor_value():
f = MockPWMPin(1) f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
b = MockPWMPin(2) b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with Motor(f, b) as device: with Motor(f, b) as device:
device.value = -1 device.value = -1
assert device.is_active assert device.is_active
@@ -855,8 +866,8 @@ def test_motor_value():
assert b.state == 0 and f.state == 0 assert b.state == 0 and f.state == 0
def test_motor_value_nonpwm(): def test_motor_value_nonpwm():
f = MockPin(1) f = Device.pin_factory.pin(1)
b = MockPin(2) b = Device.pin_factory.pin(2)
with Motor(f, b, pwm=False) as device: with Motor(f, b, pwm=False) as device:
device.value = -1 device.value = -1
assert device.is_active assert device.is_active
@@ -872,17 +883,21 @@ def test_motor_value_nonpwm():
assert b.state == 0 and f.state == 0 assert b.state == 0 and f.state == 0
def test_motor_bad_value(): def test_motor_bad_value():
f = MockPWMPin(1) f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
b = MockPWMPin(2) b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with Motor(f, b) as device: with Motor(f, b) as device:
with pytest.raises(ValueError): with pytest.raises(ValueError):
device.value = -2 device.value = -2
with pytest.raises(ValueError): with pytest.raises(ValueError):
device.value = 2 device.value = 2
with pytest.raises(ValueError):
device.forward(2)
with pytest.raises(ValueError):
device.backward(2)
def test_motor_bad_value_nonpwm(): def test_motor_bad_value_nonpwm():
f = MockPin(1) f = Device.pin_factory.pin(1)
b = MockPin(2) b = Device.pin_factory.pin(2)
with Motor(f, b, pwm=False) as device: with Motor(f, b, pwm=False) as device:
with pytest.raises(ValueError): with pytest.raises(ValueError):
device.value = -2 device.value = -2
@@ -894,8 +909,8 @@ def test_motor_bad_value_nonpwm():
device.value = -0.5 device.value = -0.5
def test_motor_reverse(): def test_motor_reverse():
f = MockPWMPin(1) f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
b = MockPWMPin(2) b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with Motor(f, b) as device: with Motor(f, b) as device:
device.forward() device.forward()
assert device.value == 1 assert device.value == 1
@@ -911,8 +926,8 @@ def test_motor_reverse():
assert b.state == 0 and f.state == 0.5 assert b.state == 0 and f.state == 0.5
def test_motor_reverse_nonpwm(): def test_motor_reverse_nonpwm():
f = MockPin(1) f = Device.pin_factory.pin(1)
b = MockPin(2) b = Device.pin_factory.pin(2)
with Motor(f, b, pwm=False) as device: with Motor(f, b, pwm=False) as device:
device.forward() device.forward()
assert device.value == 1 assert device.value == 1
@@ -922,13 +937,13 @@ def test_motor_reverse_nonpwm():
assert b.state == 1 and f.state == 0 assert b.state == 1 and f.state == 0
def test_servo_pins(): def test_servo_pins():
p = MockPWMPin(1) p = Device.pin_factory.pin(1, pin_class=MockPWMPin)
with Servo(p) as device: with Servo(p) as device:
assert device.pwm_device.pin is p assert device.pwm_device.pin is p
assert isinstance(device.pwm_device, PWMOutputDevice) assert isinstance(device.pwm_device, PWMOutputDevice)
def test_servo_bad_value(): def test_servo_bad_value():
p = MockPWMPin(1) p = Device.pin_factory.pin(1, pin_class=MockPWMPin)
with pytest.raises(ValueError): with pytest.raises(ValueError):
Servo(p, initial_value=2) Servo(p, initial_value=2)
with pytest.raises(ValueError): with pytest.raises(ValueError):
@@ -937,12 +952,12 @@ def test_servo_bad_value():
Servo(p, max_pulse_width=30/1000) Servo(p, max_pulse_width=30/1000)
def test_servo_pins_nonpwm(): def test_servo_pins_nonpwm():
p = MockPin(2) p = Device.pin_factory.pin(2)
with pytest.raises(PinPWMUnsupported): with pytest.raises(PinPWMUnsupported):
Servo(p) Servo(p)
def test_servo_close(): def test_servo_close():
p = MockPWMPin(2) p = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with Servo(p) as device: with Servo(p) as device:
device.close() device.close()
assert device.closed assert device.closed
@@ -951,7 +966,7 @@ def test_servo_close():
assert device.closed assert device.closed
def test_servo_pulse_width(): def test_servo_pulse_width():
p = MockPWMPin(2) p = Device.pin_factory.pin(2, pin_class=MockPWMPin)
with Servo(p, min_pulse_width=5/10000, max_pulse_width=25/10000) as device: with Servo(p, min_pulse_width=5/10000, max_pulse_width=25/10000) as device:
assert isclose(device.min_pulse_width, 5/10000) assert isclose(device.min_pulse_width, 5/10000)
assert isclose(device.max_pulse_width, 25/10000) assert isclose(device.max_pulse_width, 25/10000)
@@ -965,7 +980,7 @@ def test_servo_pulse_width():
assert device.pulse_width is None assert device.pulse_width is None
def test_servo_values(): def test_servo_values():
p = MockPWMPin(1) p = Device.pin_factory.pin(1, pin_class=MockPWMPin)
with Servo(p) as device: with Servo(p) as device:
device.min() device.min()
assert device.is_active assert device.is_active
@@ -992,13 +1007,13 @@ def test_servo_values():
assert device.value is None assert device.value is None
def test_angular_servo_range(): def test_angular_servo_range():
p = MockPWMPin(1) p = Device.pin_factory.pin(1, pin_class=MockPWMPin)
with AngularServo(p, initial_angle=15, min_angle=0, max_angle=90) as device: with AngularServo(p, initial_angle=15, min_angle=0, max_angle=90) as device:
assert device.min_angle == 0 assert device.min_angle == 0
assert device.max_angle == 90 assert device.max_angle == 90
def test_angular_servo_angles(): def test_angular_servo_angles():
p = MockPWMPin(1) p = Device.pin_factory.pin(1, pin_class=MockPWMPin)
with AngularServo(p) as device: with AngularServo(p) as device:
device.angle = 0 device.angle = 0
assert device.angle == 0 assert device.angle == 0

View File

@@ -11,44 +11,42 @@ import re
import pytest import pytest
from mock import patch, MagicMock from mock import patch, MagicMock
import gpiozero.devices
import gpiozero.pins.data import gpiozero.pins.data
import gpiozero.pins.native import gpiozero.pins.local
from gpiozero.pins.data import pi_info, Style, HeaderInfo, PinInfo from gpiozero.pins.local import LocalPiFactory
from gpiozero import PinMultiplePins, PinNoPins, PinUnknownPi from gpiozero.pins.data import Style, HeaderInfo, PinInfo
from gpiozero import *
def test_pi_revision(): def test_pi_revision():
save_factory = gpiozero.devices.pin_factory with patch('gpiozero.devices.Device.pin_factory', LocalPiFactory()):
try:
# Can't use MockPin for this as we want something that'll actually try # 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); # and read /proc/cpuinfo (MockPin simply parrots the 2B's data);
# NativePin is used as we're guaranteed to be able to import it # LocalPiFactory is used as we can definitely instantiate it (strictly
gpiozero.devices.pin_factory = gpiozero.pins.native.NativePin # speaking it's abstract but we're only interested in the pi_info
# stuff)
with patch('io.open') as m: with patch('io.open') as m:
m.return_value.__enter__.return_value = ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial: xxxxxxxxxxx'] m.return_value.__enter__.return_value = ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial: xxxxxxxxxxx']
assert pi_info().revision == '0002' assert pi_info().revision == '0002'
# LocalPin caches the revision (because realistically it isn't going to # LocalPiFactory caches the revision (because realistically it
# change at runtime); we need to wipe it here though # isn't going to change at runtime); we need to wipe it here though
gpiozero.pins.native.NativePin._PI_REVISION = None Device.pin_factory._info = None
m.return_value.__enter__.return_value = ['Revision: a21042'] m.return_value.__enter__.return_value = ['Revision: a21042']
assert pi_info().revision == 'a21042' assert pi_info().revision == 'a21042'
# Check over-volting result (some argument over whether this is 7 or # Check over-volting result (some argument over whether this is 7 or
# 8 character result; make sure both work) # 8 character result; make sure both work)
gpiozero.pins.native.NativePin._PI_REVISION = None Device.pin_factory._info = None
m.return_value.__enter__.return_value = ['Revision: 1000003'] m.return_value.__enter__.return_value = ['Revision: 1000003']
assert pi_info().revision == '0003' assert pi_info().revision == '0003'
gpiozero.pins.native.NativePin._PI_REVISION = None Device.pin_factory._info = None
m.return_value.__enter__.return_value = ['Revision: 100003'] m.return_value.__enter__.return_value = ['Revision: 100003']
assert pi_info().revision == '0003' assert pi_info().revision == '0003'
with pytest.raises(PinUnknownPi): with pytest.raises(PinUnknownPi):
m.return_value.__enter__.return_value = ['nothing', 'relevant', 'at all'] m.return_value.__enter__.return_value = ['nothing', 'relevant', 'at all']
gpiozero.pins.native.NativePin._PI_REVISION = None Device.pin_factory._info = None
pi_info() pi_info()
with pytest.raises(PinUnknownPi): with pytest.raises(PinUnknownPi):
pi_info('0fff') pi_info('0fff')
finally:
gpiozero.devices.pin_factory = save_factory
def test_pi_info(): def test_pi_info():
r = pi_info('900011') r = pi_info('900011')
@@ -73,14 +71,14 @@ def test_pi_info_other_types():
def test_physical_pins(): def test_physical_pins():
# Assert physical pins for some well-known Pi's; a21041 is a Pi2B # Assert physical pins for some well-known Pi's; a21041 is a Pi2B
assert pi_info('a21041').physical_pins('3V3') == {('P1', 1), ('P1', 17)} assert pi_info('a21041').physical_pins('3V3') == {('J8', 1), ('J8', 17)}
assert pi_info('a21041').physical_pins('GPIO2') == {('P1', 3)} assert pi_info('a21041').physical_pins('GPIO2') == {('J8', 3)}
assert pi_info('a21041').physical_pins('GPIO47') == set() assert pi_info('a21041').physical_pins('GPIO47') == set()
def test_physical_pin(): def test_physical_pin():
with pytest.raises(PinMultiplePins): with pytest.raises(PinMultiplePins):
assert pi_info('a21041').physical_pin('GND') assert pi_info('a21041').physical_pin('GND')
assert pi_info('a21041').physical_pin('GPIO3') == ('P1', 5) assert pi_info('a21041').physical_pin('GPIO3') == ('J8', 5)
with pytest.raises(PinNoPins): with pytest.raises(PinNoPins):
assert pi_info('a21041').physical_pin('GPIO47') assert pi_info('a21041').physical_pin('GPIO47')
@@ -114,6 +112,18 @@ def test_pprint_content():
pi_info('0014').headers['SODIMM'].pprint(color=False) pi_info('0014').headers['SODIMM'].pprint(color=False)
assert len(''.join(stdout.output).splitlines()) == 100 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(): def test_pprint_headers():
assert len(pi_info('0002').headers) == 1 assert len(pi_info('0002').headers) == 1
assert len(pi_info('000e').headers) == 2 assert len(pi_info('000e').headers) == 2
@@ -133,7 +143,8 @@ def test_pprint_headers():
stdout.output = [] stdout.output = []
pi_info('900092').pprint() pi_info('900092').pprint()
s = ''.join(stdout.output) s = ''.join(stdout.output)
assert 'P1:\n' in s assert 'J8:\n' in s
assert 'P1:\n' not in s
assert 'P5:\n' not in s assert 'P5:\n' not in s
def test_pprint_color(): def test_pprint_color():
@@ -194,11 +205,12 @@ def test_pprint_missing_pin():
assert ('(%d)' % i) assert ('(%d)' % i)
def test_pprint_rows_cols(): def test_pprint_rows_cols():
assert '{0:row1}'.format(pi_info('900092').headers['P1']) == '1o' assert '{0:row1}'.format(pi_info('900092').headers['J8']) == '1o'
assert '{0:row2}'.format(pi_info('900092').headers['P1']) == 'oo' assert '{0:row2}'.format(pi_info('900092').headers['J8']) == 'oo'
assert '{0:col1}'.format(pi_info('0002').headers['P1']) == '1oooooooooooo' assert '{0:col1}'.format(pi_info('0002').headers['P1']) == '1oooooooooooo'
assert '{0:col2}'.format(pi_info('0002').headers['P1']) == 'ooooooooooooo' assert '{0:col2}'.format(pi_info('0002').headers['P1']) == 'ooooooooooooo'
with pytest.raises(ValueError): with pytest.raises(ValueError):
'{0:row16}'.format(pi_info('0002').headers['P1']) '{0:row16}'.format(pi_info('0002').headers['P1'])
with pytest.raises(ValueError): with pytest.raises(ValueError):
'{0:col3}'.format(pi_info('0002').headers['P1']) '{0:col3}'.format(pi_info('0002').headers['P1'])

View File

@@ -11,80 +11,66 @@ except NameError:
pass pass
import io import io
import subprocess import os
from time import sleep
import pytest import pytest
import pkg_resources
from gpiozero import PinFixedPull, PinInvalidPull, PinInvalidFunction from gpiozero import (
PinFixedPull,
PinInvalidPull,
PinInvalidFunction,
PinPWMUnsupported,
Device,
)
from gpiozero.pins.mock import MockConnectedPin, MockFactory
try: try:
from math import isclose from math import isclose
except ImportError: except ImportError:
from gpiozero.compat import isclose from gpiozero.compat import isclose
# This module assumes you've wired the following GPIO pins together # This module assumes you've wired the following GPIO pins together. The pins
TEST_PIN = 22 # can be re-configured via the listed environment variables (useful for when
INPUT_PIN = 27 # 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'))
# Skip the entire module if we're not on a Pi @pytest.fixture(
def is_a_pi(): scope='module',
with io.open('/proc/cpuinfo', 'r') as cpuinfo: params=[
for line in cpuinfo: name
if line.startswith('Hardware'): for name in pkg_resources.get_distribution('gpiozero').get_entry_map('gpiozero_pin_factories').keys()
hardware, colon, soc = line.strip().split(None, 2) if not name.endswith('Pin') # leave out compatibility names
return soc in ('BCM2708', 'BCM2709', 'BCM2835', 'BCM2836') ])
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: else:
return False Device.pin_factory = factory
pytestmark = pytest.mark.skipif(not is_a_pi(), reason='tests cannot run on non-Pi platforms') def fin():
del is_a_pi Device.pin_factory = MockFactory()
request.addfinalizer(fin)
return factory
# Try and import as many pin libraries as possible
PIN_CLASSES = []
try:
from gpiozero.pins.rpigpio import RPiGPIOPin
PIN_CLASSES.append(RPiGPIOPin)
except ImportError:
RPiGPIOPin = None
try:
from gpiozero.pins.rpio import RPIOPin
PIN_CLASSES.append(RPIOPin)
except ImportError:
RPIOPin = None
try:
from gpiozero.pins.pigpiod import PiGPIOPin
PIN_CLASSES.append(PiGPIOPin)
except ImportError:
PiGPIOPin = None
try:
from gpiozero.pins.native import NativePin
PIN_CLASSES.append(NativePin)
except ImportError:
NativePin = None
@pytest.fixture(scope='module', params=PIN_CLASSES)
def pin_class(request):
# pigpiod needs to be running for PiGPIOPin
if request.param.__name__ == 'PiGPIOPin':
subprocess.check_call(['sudo', 'pigpiod'])
# Unfortunately, pigpiod provides no option for running in the
# foreground, so we have to use the sledgehammer of killall to get shot
# of it
def kill_pigpiod():
subprocess.check_call(['sudo', 'killall', 'pigpiod'])
request.addfinalizer(kill_pigpiod)
return request.param
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def pins(request, pin_class): def pins(request, pin_factory):
# Why return both pins in a single fixture? If we defined one fixture for # Why return both pins in a single fixture? If we defined one fixture for
# each pin then pytest will (correctly) test RPiGPIOPin(22) against # 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 # NativePin(27) and so on. This isn't supported, so we don't test it
test_pin = pin_class(TEST_PIN) input_pin = pin_factory.pin(INPUT_PIN)
input_pin = pin_class(INPUT_PIN)
input_pin.function = 'input' input_pin.function = 'input'
input_pin.pull = 'down' 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(): def fin():
test_pin.close() test_pin.close()
input_pin.close() input_pin.close()
@@ -134,10 +120,9 @@ def test_pull_bad(pins):
with pytest.raises(PinInvalidPull): with pytest.raises(PinInvalidPull):
test_pin.input_with_pull('foo') test_pin.input_with_pull('foo')
def test_pull_down_warning(pin_class): def test_pull_down_warning(pin_factory):
# XXX This assumes we're on a vaguely modern Pi and not a compute module if pin_factory.pi_info.pulled_up('GPIO2'):
# Might want to refine this with the pi-info database pin = pin_factory.pin(2)
pin = pin_class(2)
try: try:
with pytest.raises(PinFixedPull): with pytest.raises(PinFixedPull):
pin.pull = 'down' pin.pull = 'down'
@@ -145,6 +130,8 @@ def test_pull_down_warning(pin_class):
pin.input_with_pull('down') pin.input_with_pull('down')
finally: finally:
pin.close() pin.close()
else:
pytest.skip("GPIO2 isn't pulled up on this pi")
def test_input_with_pull(pins): def test_input_with_pull(pins):
test_pin, input_pin = pins test_pin, input_pin = pins
@@ -153,25 +140,42 @@ def test_input_with_pull(pins):
test_pin.input_with_pull('down') test_pin.input_with_pull('down')
assert input_pin.state == 0 assert input_pin.state == 0
@pytest.mark.skipif(True, reason='causes segfaults')
def test_bad_duty_cycle(pins): def test_bad_duty_cycle(pins):
test_pin, input_pin = pins test_pin, input_pin = pins
if test_pin.__class__.__name__ == 'NativePin':
pytest.skip("native pin doesn't support PWM")
test_pin.function = 'output' 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 test_pin.frequency = 100
except PinPWMUnsupported:
pytest.skip("%r doesn't support PWM" % test_pin.factory)
else:
try:
with pytest.raises(ValueError): with pytest.raises(ValueError):
test_pin.state = 1.1 test_pin.state = 1.1
finally:
test_pin.frequency = None
def test_duty_cycles(pins): def test_duty_cycles(pins):
test_pin, input_pin = pins test_pin, input_pin = pins
if test_pin.__class__.__name__ == 'NativePin':
pytest.skip("native pin doesn't support PWM")
test_pin.function = 'output' test_pin.function = 'output'
try:
# NOTE: see above
if Device.pin_factory.__class__.__name__ == 'RPiGPIOFactory':
sleep(0.1)
test_pin.frequency = 100 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): for duty_cycle in (0.0, 0.1, 0.5, 1.0):
test_pin.state = duty_cycle test_pin.state = duty_cycle
assert test_pin.state == duty_cycle assert test_pin.state == duty_cycle
total = sum(input_pin.state for i in range(20000)) total = sum(input_pin.state for i in range(20000))
assert isclose(total / 20000, duty_cycle, rel_tol=0.1, abs_tol=0.1) assert isclose(total / 20000, duty_cycle, rel_tol=0.1, abs_tol=0.1)
finally:
test_pin.frequency = None

View File

@@ -4,93 +4,114 @@ from __future__ import (
print_function, print_function,
division, division,
) )
nstr = str
str = type('') str = type('')
import sys import sys
import mock
import pytest import pytest
from array import array
from mock import patch
from collections import namedtuple 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 * from gpiozero import *
from gpiozero.pins.mock import MockPin, MockSPIDevice
from gpiozero.spi import *
def setup_function(function):
import gpiozero.devices
gpiozero.devices.pin_factory = MockPin
def teardown_function(function): def teardown_function(function):
MockPin.clear_pins() Device.pin_factory.reset()
def test_spi_hardware_params(): def test_spi_hardware_params():
with mock.patch('gpiozero.spi.SpiDev') as spidev: with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open:
with SPI() as device: mmap_mmap.return_value = array(nstr('B'), (0,) * 4096)
assert isinstance(device, SPIHardwareInterface) io_open.return_value.__enter__.return_value = ['Revision: a21042']
with SPI(port=0, device=0) as device: with patch('gpiozero.devices.Device.pin_factory', NativeFactory()), \
assert isinstance(device, SPIHardwareInterface) patch('gpiozero.pins.local.SpiDev'):
with SPI(port=0, device=1) as device: with Device.pin_factory.spi() as device:
assert isinstance(device, SPIHardwareInterface) assert isinstance(device, LocalPiHardwareSPI)
with SPI(clock_pin=11) as device: with Device.pin_factory.spi(port=0, device=0) as device:
assert isinstance(device, SPIHardwareInterface) assert isinstance(device, LocalPiHardwareSPI)
with SPI(clock_pin=11, mosi_pin=10, select_pin=8) as device: with Device.pin_factory.spi(port=0, device=1) as device:
assert isinstance(device, SPIHardwareInterface) assert isinstance(device, LocalPiHardwareSPI)
with SPI(clock_pin=11, mosi_pin=10, select_pin=7) as device: with Device.pin_factory.spi(clock_pin=11) as device:
assert isinstance(device, SPIHardwareInterface) assert isinstance(device, LocalPiHardwareSPI)
with SPI(shared=True) as device: with Device.pin_factory.spi(clock_pin=11, mosi_pin=10, select_pin=8) as device:
assert isinstance(device, SharedSPIHardwareInterface) assert isinstance(device, LocalPiHardwareSPI)
with Device.pin_factory.spi(clock_pin=11, mosi_pin=10, select_pin=7) as device:
assert isinstance(device, LocalPiHardwareSPI)
with Device.pin_factory.spi(shared=True) as device:
assert isinstance(device, LocalPiHardwareSPIShared)
with pytest.raises(ValueError): with pytest.raises(ValueError):
SPI(port=1) Device.pin_factory.spi(port=1)
with pytest.raises(ValueError): with pytest.raises(ValueError):
SPI(device=2) Device.pin_factory.spi(device=2)
with pytest.raises(ValueError): with pytest.raises(ValueError):
SPI(port=0, clock_pin=12) Device.pin_factory.spi(port=0, clock_pin=12)
with pytest.raises(ValueError): with pytest.raises(ValueError):
SPI(foo='bar') Device.pin_factory.spi(foo='bar')
def test_spi_software_params(): def test_spi_software_params():
with mock.patch('gpiozero.spi.SpiDev') as spidev: with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open:
with SPI(select_pin=6) as device: mmap_mmap.return_value = array(nstr('B'), (0,) * 4096)
assert isinstance(device, SPISoftwareInterface) io_open.return_value.__enter__.return_value = ['Revision: a21042']
with SPI(clock_pin=11, mosi_pin=9, miso_pin=10) as device: with patch('gpiozero.devices.Device.pin_factory', NativeFactory()), \
assert isinstance(device, SPISoftwareInterface) patch('gpiozero.pins.local.SpiDev'):
with SPI(select_pin=6, shared=True) as device: with Device.pin_factory.spi(select_pin=6) as device:
assert isinstance(device, SharedSPISoftwareInterface) assert isinstance(device, LocalPiSoftwareSPI)
with Device.pin_factory.spi(clock_pin=11, mosi_pin=9, miso_pin=10) as device:
assert isinstance(device, LocalPiSoftwareSPI)
with Device.pin_factory.spi(select_pin=6, shared=True) as device:
assert isinstance(device, LocalPiSoftwareSPIShared)
with patch('gpiozero.devices.Device.pin_factory', NativeFactory()), \
patch('gpiozero.pins.local.SpiDev', None):
# Clear out the old factory's pins cache (this is only necessary
# because we're being very naughty switching out pin factories)
Device.pin_factory.pins.clear()
# Ensure software fallback works when SpiDev isn't present # Ensure software fallback works when SpiDev isn't present
with SPI() as device: with Device.pin_factory.spi() as device:
assert isinstance(device, SPISoftwareInterface) assert isinstance(device, LocalPiSoftwareSPI)
def test_spi_hardware_conflict(): def test_spi_hardware_conflict():
with mock.patch('gpiozero.spi.SpiDev') as spidev: with patch('gpiozero.pins.local.SpiDev') as spidev:
with LED(11) as led: with LED(11) as led:
with pytest.raises(GPIOPinInUse): with pytest.raises(GPIOPinInUse):
SPI(port=0, device=0) 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(): def test_spi_hardware_read():
with mock.patch('gpiozero.spi.SpiDev') as spidev: with patch('gpiozero.pins.local.SpiDev') as spidev:
spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)]
with SPI() as device: with Device.pin_factory.spi() as device:
assert device.read(3) == [0, 1, 2] assert device.read(3) == [0, 1, 2]
assert device.read(6) == list(range(6)) assert device.read(6) == list(range(6))
def test_spi_hardware_write(): def test_spi_hardware_write():
with mock.patch('gpiozero.spi.SpiDev') as spidev: with patch('gpiozero.pins.local.SpiDev') as spidev:
spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)]
with SPI() as device: with Device.pin_factory.spi() as device:
assert device.write([0, 1, 2]) == 3 assert device.write([0, 1, 2]) == 3
assert spidev.return_value.xfer2.called_with([0, 1, 2]) assert spidev.return_value.xfer2.called_with([0, 1, 2])
assert device.write(list(range(6))) == 6 assert device.write(list(range(6))) == 6
assert spidev.return_value.xfer2.called_with(list(range(6))) assert spidev.return_value.xfer2.called_with(list(range(6)))
def test_spi_hardware_modes(): def test_spi_hardware_modes():
with mock.patch('gpiozero.spi.SpiDev') as spidev: with patch('gpiozero.pins.local.SpiDev') as spidev:
spidev.return_value.mode = 0 spidev.return_value.mode = 0
spidev.return_value.lsbfirst = False spidev.return_value.lsbfirst = False
spidev.return_value.cshigh = True spidev.return_value.cshigh = True
spidev.return_value.bits_per_word = 8 spidev.return_value.bits_per_word = 8
with SPI() as device: with Device.pin_factory.spi() as device:
assert device.clock_mode == 0 assert device.clock_mode == 0
assert not device.clock_polarity assert not device.clock_polarity
assert not device.clock_phase assert not device.clock_phase
@@ -116,7 +137,9 @@ def test_spi_software_read():
super(SPISlave, self).on_start() super(SPISlave, self).on_start()
for i in range(10): for i in range(10):
self.tx_word(i) self.tx_word(i)
with SPISlave(11, 10, 9, 8) as slave, SPI() as master: 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(3) == [0, 1, 2]
assert master.read(6) == [0, 1, 2, 3, 4, 5] assert master.read(6) == [0, 1, 2, 3, 4, 5]
slave.clock_phase = True slave.clock_phase = True
@@ -125,7 +148,9 @@ def test_spi_software_read():
assert master.read(6) == [0, 1, 2, 3, 4, 5] assert master.read(6) == [0, 1, 2, 3, 4, 5]
def test_spi_software_write(): def test_spi_software_write():
with MockSPIDevice(11, 10, 9, 8) as test_device, SPI() as master: with patch('gpiozero.pins.local.SpiDev', None), \
MockSPIDevice(11, 10, 9, 8) as test_device, \
Device.pin_factory.spi() as master:
master.write([0]) master.write([0])
assert test_device.rx_word() == 0 assert test_device.rx_word() == 0
master.write([2, 0]) master.write([2, 0])
@@ -134,7 +159,8 @@ def test_spi_software_write():
assert test_device.rx_word() == 257 assert test_device.rx_word() == 257
def test_spi_software_clock_mode(): def test_spi_software_clock_mode():
with SPI() as master: with patch('gpiozero.pins.local.SpiDev', None), \
Device.pin_factory.spi() as master:
assert master.clock_mode == 0 assert master.clock_mode == 0
assert not master.clock_polarity assert not master.clock_polarity
assert not master.clock_phase assert not master.clock_phase
@@ -151,7 +177,8 @@ def test_spi_software_clock_mode():
master.clock_mode = 5 master.clock_mode = 5
def test_spi_software_attr(): def test_spi_software_attr():
with SPI() as master: with patch('gpiozero.pins.local.SpiDev', None), \
Device.pin_factory.spi() as master:
assert not master.lsb_first assert not master.lsb_first
assert not master.select_high assert not master.select_high
assert master.bits_per_word == 8 assert master.bits_per_word == 8

View File

@@ -9,22 +9,19 @@ str = type('')
import sys import sys
import pytest import pytest
from mock import patch
from collections import namedtuple from collections import namedtuple
try: try:
from math import isclose from math import isclose
except ImportError: except ImportError:
from gpiozero.compat import isclose from gpiozero.compat import isclose
from gpiozero import *
from gpiozero.pins.mock import MockSPIDevice, MockPin from gpiozero.pins.mock import MockSPIDevice, MockPin
from gpiozero import *
def setup_function(function):
import gpiozero.devices
gpiozero.devices.pin_factory = MockPin
def teardown_function(function): def teardown_function(function):
MockPin.clear_pins() Device.pin_factory.reset()
def clamp(v, min_value, max_value): def clamp(v, min_value, max_value):
return min(max_value, max(min_value, v)) return min(max_value, max(min_value, v))
@@ -251,11 +248,13 @@ def differential_mcp_test(mock, pot, pos_channel, neg_channel, bits, full=False)
def test_MCP3001(): def test_MCP3001():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3001(11, 10, 9, 8) mock = MockMCP3001(11, 10, 9, 8)
with MCP3001() as pot: with MCP3001() as pot:
differential_mcp_test(mock, pot, 0, 1, 10) differential_mcp_test(mock, pot, 0, 1, 10)
def test_MCP3002(): def test_MCP3002():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3002(11, 10, 9, 8) mock = MockMCP3002(11, 10, 9, 8)
with pytest.raises(ValueError): with pytest.raises(ValueError):
MCP3002(channel=5) MCP3002(channel=5)
@@ -265,6 +264,7 @@ def test_MCP3002():
differential_mcp_test(mock, pot, 1, 0, 10) differential_mcp_test(mock, pot, 1, 0, 10)
def test_MCP3004(): def test_MCP3004():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3004(11, 10, 9, 8) mock = MockMCP3004(11, 10, 9, 8)
with pytest.raises(ValueError): with pytest.raises(ValueError):
MCP3004(channel=5) MCP3004(channel=5)
@@ -274,6 +274,7 @@ def test_MCP3004():
differential_mcp_test(mock, pot, 3, 2, 10) differential_mcp_test(mock, pot, 3, 2, 10)
def test_MCP3008(): def test_MCP3008():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3008(11, 10, 9, 8) mock = MockMCP3008(11, 10, 9, 8)
with pytest.raises(ValueError): with pytest.raises(ValueError):
MCP3008(channel=9) MCP3008(channel=9)
@@ -283,11 +284,13 @@ def test_MCP3008():
differential_mcp_test(mock, pot, 0, 1, 10) differential_mcp_test(mock, pot, 0, 1, 10)
def test_MCP3201(): def test_MCP3201():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3201(11, 10, 9, 8) mock = MockMCP3201(11, 10, 9, 8)
with MCP3201() as pot: with MCP3201() as pot:
differential_mcp_test(mock, pot, 0, 1, 12) differential_mcp_test(mock, pot, 0, 1, 12)
def test_MCP3202(): def test_MCP3202():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3202(11, 10, 9, 8) mock = MockMCP3202(11, 10, 9, 8)
with pytest.raises(ValueError): with pytest.raises(ValueError):
MCP3202(channel=5) MCP3202(channel=5)
@@ -297,6 +300,7 @@ def test_MCP3202():
differential_mcp_test(mock, pot, 1, 0, 12) differential_mcp_test(mock, pot, 1, 0, 12)
def test_MCP3204(): def test_MCP3204():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3204(11, 10, 9, 8) mock = MockMCP3204(11, 10, 9, 8)
with pytest.raises(ValueError): with pytest.raises(ValueError):
MCP3204(channel=5) MCP3204(channel=5)
@@ -306,6 +310,7 @@ def test_MCP3204():
differential_mcp_test(mock, pot, 1, 0, 12) differential_mcp_test(mock, pot, 1, 0, 12)
def test_MCP3208(): def test_MCP3208():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3208(11, 10, 9, 8) mock = MockMCP3208(11, 10, 9, 8)
with pytest.raises(ValueError): with pytest.raises(ValueError):
MCP3208(channel=9) MCP3208(channel=9)
@@ -315,11 +320,13 @@ def test_MCP3208():
differential_mcp_test(mock, pot, 7, 6, 12) differential_mcp_test(mock, pot, 7, 6, 12)
def test_MCP3301(): def test_MCP3301():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3301(11, 10, 9, 8) mock = MockMCP3301(11, 10, 9, 8)
with MCP3301() as pot: with MCP3301() as pot:
differential_mcp_test(mock, pot, 0, 1, 12, full=True) differential_mcp_test(mock, pot, 0, 1, 12, full=True)
def test_MCP3302(): def test_MCP3302():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3302(11, 10, 9, 8) mock = MockMCP3302(11, 10, 9, 8)
with pytest.raises(ValueError): with pytest.raises(ValueError):
MCP3302(channel=4) MCP3302(channel=4)
@@ -329,6 +336,7 @@ def test_MCP3302():
differential_mcp_test(mock, pot, 0, 1, 12, full=True) differential_mcp_test(mock, pot, 0, 1, 12, full=True)
def test_MCP3304(): def test_MCP3304():
with patch('gpiozero.pins.local.SpiDev', None):
mock = MockMCP3304(11, 10, 9, 8) mock = MockMCP3304(11, 10, 9, 8)
with pytest.raises(ValueError): with pytest.raises(ValueError):
MCP3304(channel=9) MCP3304(channel=9)