Merge branch 'master' into docs-updates
@@ -1,2 +1,3 @@
|
||||
include README.rst
|
||||
recursive-include tests *.py
|
||||
include LICENCE.txt
|
||||
|
||||
2
Makefile
@@ -101,7 +101,7 @@ develop: tags
|
||||
$(PIP) install -e .[doc,test]
|
||||
|
||||
test:
|
||||
$(COVERAGE) run -m $(PYTEST) tests -v
|
||||
$(COVERAGE) run -m $(PYTEST) tests -v -r sx
|
||||
$(COVERAGE) report --rcfile coverage.cfg
|
||||
|
||||
clean:
|
||||
|
||||
20
README.rst
@@ -2,17 +2,19 @@
|
||||
gpiozero
|
||||
========
|
||||
|
||||
.. image:: https://badge.fury.io/py/gpiozero.svg
|
||||
:target: https://badge.fury.io/py/gpiozero
|
||||
:alt: Latest Version
|
||||
.. only:: builder_html
|
||||
|
||||
.. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master
|
||||
:target: https://travis-ci.org/RPi-Distro/python-gpiozero
|
||||
:alt: Build Tests
|
||||
.. image:: https://badge.fury.io/py/gpiozero.svg
|
||||
:target: https://badge.fury.io/py/gpiozero
|
||||
:alt: Latest Version
|
||||
|
||||
.. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000
|
||||
:target: https://codecov.io/github/RPi-Distro/python-gpiozero
|
||||
:alt: Code Coverage
|
||||
.. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master
|
||||
:target: https://travis-ci.org/RPi-Distro/python-gpiozero
|
||||
:alt: Build Tests
|
||||
|
||||
.. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000
|
||||
:target: https://codecov.io/github/RPi-Distro/python-gpiozero
|
||||
:alt: Code Coverage
|
||||
|
||||
A simple interface to GPIO devices with Raspberry Pi.
|
||||
|
||||
|
||||
6
debian/changelog
vendored
@@ -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
|
||||
|
||||
* Fixed hardware SPI support which Dave broke in 1.3.0. Sorry!
|
||||
|
||||
37
debian/control
vendored
@@ -11,28 +11,30 @@ X-Python3-Version: >= 3.2
|
||||
Package: python-gpiozero
|
||||
Architecture: all
|
||||
Section: python
|
||||
Depends: ${misc:Depends}, ${python:Depends}
|
||||
Depends: ${misc:Depends}, ${python:Depends}, python-pkg-resources
|
||||
Recommends: python-rpi.gpio, python-spidev
|
||||
Suggests: python-gpiozero-docs
|
||||
Description: Simple API for controlling devices attached to the GPIO pins.
|
||||
gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify
|
||||
interaction with devices connected to the GPIO pins, from simple buttons and
|
||||
LEDs, up to various add-on boards. The API tries to adhere closely to Python's
|
||||
idioms and naming conventions.
|
||||
Description: Simple API for controlling devices attached to a Pi's GPIO pins.
|
||||
gpiozero builds on various pin libraries to provide a set of classes designed
|
||||
to simplify interaction with devices connected to the GPIO pins, from simple
|
||||
buttons and LEDs, up to various add-on boards. The API tries to adhere closely
|
||||
to Python's idioms and naming conventions, and features alternative
|
||||
programming paradigm approaches.
|
||||
.
|
||||
This is the Python 2 version of the package.
|
||||
|
||||
Package: python3-gpiozero
|
||||
Architecture: all
|
||||
Section: python
|
||||
Depends: ${misc:Depends}, ${python3:Depends}
|
||||
Depends: ${misc:Depends}, ${python3:Depends}, python3-pkg-resources
|
||||
Recommends: python3-rpi.gpio, python3-spidev
|
||||
Suggests: python-gpiozero-docs
|
||||
Description: Simple API for controlling devices attached to the GPIO pins.
|
||||
gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify
|
||||
interaction with devices connected to the GPIO pins, from simple buttons and
|
||||
LEDs, up to various add-on boards. The API tries to adhere closely to Python's
|
||||
idioms and naming conventions.
|
||||
Description: Simple API for controlling devices attached to a Pi's GPIO pins.
|
||||
gpiozero builds on various pin libraries to provide a set of classes designed
|
||||
to simplify interaction with devices connected to the GPIO pins, from simple
|
||||
buttons and LEDs, up to various add-on boards. The API tries to adhere closely
|
||||
to Python's idioms and naming conventions, and features alternative
|
||||
programming paradigm approaches.
|
||||
.
|
||||
This is the Python 3 version of the package.
|
||||
|
||||
@@ -40,10 +42,11 @@ Package: python-gpiozero-doc
|
||||
Architecture: all
|
||||
Section: doc
|
||||
Depends: ${sphinxdoc:Depends}, ${misc:Depends}
|
||||
Description: Documentation for the gpiozero API
|
||||
gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify
|
||||
interaction with devices connected to the GPIO pins, from simple buttons and
|
||||
LEDs, up to various add-on boards. The API tries to adhere closely to Python's
|
||||
idioms and naming conventions.
|
||||
Description: Simple API for controlling devices attached to a Pi's GPIO pins.
|
||||
gpiozero builds on various pin libraries to provide a set of classes designed
|
||||
to simplify interaction with devices connected to the GPIO pins, from simple
|
||||
buttons and LEDs, up to various add-on boards. The API tries to adhere closely
|
||||
to Python's idioms and naming conventions, and features alternative
|
||||
programming paradigm approaches.
|
||||
.
|
||||
This is the version independent documentation for the package.
|
||||
|
||||
@@ -59,6 +59,20 @@ Errors
|
||||
|
||||
.. autoexception:: SPIBadArgs
|
||||
|
||||
.. autoexception:: SPIBadChannel
|
||||
|
||||
.. autoexception:: SPIFixedClockMode
|
||||
|
||||
.. autoexception:: SPIInvalidClockMode
|
||||
|
||||
.. autoexception:: SPIFixedBitOrder
|
||||
|
||||
.. autoexception:: SPIFixedSelect
|
||||
|
||||
.. autoexception:: SPIFixedWordSize
|
||||
|
||||
.. autoexception:: SPIInvalidWordSize
|
||||
|
||||
.. autoexception:: GPIODeviceError
|
||||
|
||||
.. autoexception:: GPIODeviceClosed
|
||||
@@ -83,23 +97,31 @@ Errors
|
||||
|
||||
.. autoexception:: PinInvalidEdges
|
||||
|
||||
.. autoexception:: PinInvalidBounce
|
||||
|
||||
.. autoexception:: PinSetInput
|
||||
|
||||
.. autoexception:: PinFixedPull
|
||||
|
||||
.. autoexception:: PinEdgeDetectUnsupported
|
||||
|
||||
.. autoexception:: PinUnsupported
|
||||
|
||||
.. autoexception:: PinSPIUnsupported
|
||||
|
||||
.. autoexception:: PinPWMError
|
||||
|
||||
.. autoexception:: PinPWMUnsupported
|
||||
|
||||
.. autoexception:: PinPWMFixedValue
|
||||
|
||||
.. autoexception:: PinUnknownPi
|
||||
|
||||
.. autoexception:: PinMultiplePins
|
||||
|
||||
.. autoexception:: PinNoPins
|
||||
|
||||
.. autoexception:: PinUnknownPi
|
||||
.. autoexception:: PinInvalidPin
|
||||
|
||||
Warnings
|
||||
========
|
||||
@@ -110,3 +132,7 @@ Warnings
|
||||
|
||||
.. autoexception:: SPISoftwareFallback
|
||||
|
||||
.. autoexception:: PinFactoryFallback
|
||||
|
||||
.. autoexception:: PinNonPhysical
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ classes (most of which are documented in their corresponding chapters):
|
||||
devices like HATs
|
||||
|
||||
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.*
|
||||
|
||||
|
||||
@@ -52,7 +52,8 @@ 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)
|
||||
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.*
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ 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)
|
||||
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.*
|
||||
|
||||
|
||||
@@ -62,7 +62,8 @@ 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)
|
||||
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.*
|
||||
|
||||
|
||||
@@ -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
|
||||
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
|
||||
library actually expects is a :class:`Pin` implementation. If it finds a simple
|
||||
integer number instead, it uses one of the following classes to provide the
|
||||
:class:`Pin` implementation (classes are listed in favoured order):
|
||||
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 anything
|
||||
else, it uses the existing ``Device.pin_factory`` to construct a :class:`Pin`
|
||||
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
|
||||
``pin_factory`` global in the ``devices`` module like so::
|
||||
To set the ``GPIOZERO_PIN_FACTORY`` for the rest of your session you can
|
||||
export this value:
|
||||
|
||||
from gpiozero.pins.native import NativePin
|
||||
import gpiozero.devices
|
||||
# Force the default pin implementation to be NativePin
|
||||
gpiozero.devices.pin_factory = NativePin
|
||||
.. code-block:: console
|
||||
|
||||
pi@raspberrypi $ export GPIOZERO_PIN_FACTORY=native
|
||||
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
|
||||
|
||||
# This will now use NativePin instead of RPiGPIOPin
|
||||
led = LED(16)
|
||||
|
||||
``pin_factory`` is a concrete descendent of the abstract :class:`Pin` class.
|
||||
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.
|
||||
Certain factories may take default information from additional sources.
|
||||
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
|
||||
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,
|
||||
you can pass an object derived from :class:`Pin` itself::
|
||||
$ GPIOZERO_PIN_FACTORY=pigpio PIGPIO_ADDR=remote-pi python3 my_script.py
|
||||
|
||||
from gpiozero.pins.native import NativePin
|
||||
from gpiozero import LED
|
||||
|
||||
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!
|
||||
Like the ``GPIOZERO_PIN_FACTORY`` value, these can be exported from your
|
||||
:file:`~/.bashrc` script too.
|
||||
|
||||
.. 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.
|
||||
|
||||
|
||||
RPiGPIOPin
|
||||
==========
|
||||
RPi.GPIO
|
||||
========
|
||||
|
||||
.. autoclass:: gpiozero.pins.rpigpio.RPiGPIOFactory
|
||||
|
||||
.. autoclass:: gpiozero.pins.rpigpio.RPiGPIOPin
|
||||
|
||||
|
||||
RPIOPin
|
||||
=======
|
||||
RPIO
|
||||
====
|
||||
|
||||
.. autoclass:: gpiozero.pins.rpio.RPIOFactory
|
||||
|
||||
.. 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
|
||||
|
||||
|
||||
Abstract Pin
|
||||
Base classes
|
||||
============
|
||||
|
||||
.. autoclass:: Factory
|
||||
:members:
|
||||
|
||||
.. autoclass:: Pin
|
||||
: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:
|
||||
|
||||
|
||||
@@ -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
|
||||
functions and classes can be used to query this database:
|
||||
|
||||
.. currentmodule:: gpiozero
|
||||
|
||||
.. autofunction:: pi_info
|
||||
|
||||
.. autoclass:: PiBoardInfo
|
||||
|
||||
@@ -121,7 +121,8 @@ 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)
|
||||
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.*
|
||||
|
||||
|
||||
@@ -5,6 +5,12 @@ Changelog
|
||||
.. 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)
|
||||
====================================
|
||||
|
||||
@@ -55,7 +61,7 @@ Release 1.2.0 (2016-04-10)
|
||||
* Added support for lots of ADC chips (MCP3xxx family) (`#162`_) - many thanks
|
||||
to pcopa and lurch!
|
||||
* 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
|
||||
devices and several bugs squashed (`#164`_, `#175`_, `#182`_, `#189`_,
|
||||
`#193`_, `#229`_)
|
||||
|
||||
26
docs/cli_tools.rst
Normal 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
@@ -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
|
||||
Exuberant’s 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
@@ -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()
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@@ -1,7 +1,7 @@
|
||||
/* vim: set et sw=4 sts=4: */
|
||||
|
||||
digraph classes {
|
||||
graph [rankdir=BT];
|
||||
graph [rankdir=RL];
|
||||
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
|
||||
edge [];
|
||||
|
||||
@@ -24,6 +24,7 @@ digraph classes {
|
||||
PiLiter->LEDBoard;
|
||||
PiLiterBarGraph->LEDBarGraph;
|
||||
TrafficLights->LEDBoard;
|
||||
SnowPi->LEDBoard;
|
||||
PiTraffic->TrafficLights;
|
||||
PiStop->TrafficLights;
|
||||
TrafficLightsBuzzer->CompositeOutputDevice;
|
||||
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 62 KiB |
@@ -1,228 +1,238 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"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 -->
|
||||
<svg width="733pt" height="476pt"
|
||||
viewBox="0.00 0.00 733.00 476.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)">
|
||||
<svg width="845pt" height="462pt"
|
||||
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 458)">
|
||||
<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 -->
|
||||
<g id="node1" class="node"><title>Device</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="607,-468 553,-468 553,-432 607,-432 607,-468"/>
|
||||
<text text-anchor="middle" x="580" y="-447.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-117 0,-117 0,-81 54,-81 54,-117"/>
|
||||
<text text-anchor="middle" x="27" y="-96.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
</g>
|
||||
<!-- CompositeDevice -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="530" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeDevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="191,-144 90,-144 90,-108 191,-108 191,-144"/>
|
||||
<text text-anchor="middle" x="140.5" y="-123.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeDevice</text>
|
||||
</g>
|
||||
<!-- CompositeDevice->Device -->
|
||||
<g id="edge1" class="edge"><title>CompositeDevice->Device</title>
|
||||
<path fill="none" stroke="black" d="M542.36,-396.303C548.233,-404.526 555.369,-414.517 561.842,-423.579"/>
|
||||
<polygon fill="black" stroke="black" points="559.122,-425.793 567.783,-431.896 564.818,-421.724 559.122,-425.793"/>
|
||||
<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="64.7221,-104.291 54.1792,-105.339 63.0746,-111.095 64.7221,-104.291"/>
|
||||
</g>
|
||||
<!-- CompositeOutputDevice -->
|
||||
<g id="node3" class="node"><title>CompositeOutputDevice</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="375,-324 241,-324 241,-288 375,-288 375,-324"/>
|
||||
<text text-anchor="middle" x="308" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeOutputDevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="361,-252 227,-252 227,-216 361,-216 361,-252"/>
|
||||
<text text-anchor="middle" x="294" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeOutputDevice</text>
|
||||
</g>
|
||||
<!-- CompositeOutputDevice->CompositeDevice -->
|
||||
<g id="edge2" class="edge"><title>CompositeOutputDevice->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M362.023,-324.034C394.615,-334.311 436.197,-347.422 469.834,-358.029"/>
|
||||
<polygon fill="black" stroke="black" points="468.958,-361.422 479.547,-361.091 471.063,-354.746 468.958,-361.422"/>
|
||||
<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="166.284,-149.169 156.958,-144.142 161.114,-153.888 166.284,-149.169"/>
|
||||
</g>
|
||||
<!-- LEDCollection -->
|
||||
<g id="node4" class="node"><title>LEDCollection</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="237,-252 155,-252 155,-216 237,-216 237,-252"/>
|
||||
<text text-anchor="middle" x="196" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">LEDCollection</text>
|
||||
<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="449.5" y="-285.5" font-family="Sans" font-size="10.00" fill="#000000">LEDCollection</text>
|
||||
</g>
|
||||
<!-- LEDCollection->CompositeOutputDevice -->
|
||||
<g id="edge3" class="edge"><title>LEDCollection->CompositeOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M223.398,-252.124C237.907,-261.192 255.916,-272.448 271.585,-282.241"/>
|
||||
<polygon fill="black" stroke="black" points="270.161,-285.478 280.496,-287.81 273.871,-279.542 270.161,-285.478"/>
|
||||
<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="356.945,-252.081 346.35,-252.065 354.622,-258.685 356.945,-252.081"/>
|
||||
</g>
|
||||
<!-- LEDBoard -->
|
||||
<g id="node5" class="node"><title>LEDBoard</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="140,-180 76,-180 76,-144 140,-144 140,-180"/>
|
||||
<text text-anchor="middle" x="108" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBoard</text>
|
||||
<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="578.5" y="-366.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBoard</text>
|
||||
</g>
|
||||
<!-- LEDBoard->LEDCollection -->
|
||||
<g id="edge4" class="edge"><title>LEDBoard->LEDCollection</title>
|
||||
<path fill="none" stroke="black" d="M129.753,-180.303C140.836,-189.119 154.474,-199.968 166.49,-209.526"/>
|
||||
<polygon fill="black" stroke="black" points="164.493,-212.41 174.497,-215.896 168.85,-206.931 164.493,-212.41"/>
|
||||
<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="489.219,-308.544 478.906,-306.117 485.455,-314.446 489.219,-308.544"/>
|
||||
</g>
|
||||
<!-- LEDBarGraph -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="203" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBarGraph</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="619,-306 538,-306 538,-270 619,-270 619,-306"/>
|
||||
<text text-anchor="middle" x="578.5" y="-285.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBarGraph</text>
|
||||
</g>
|
||||
<!-- LEDBarGraph->LEDCollection -->
|
||||
<g id="edge5" class="edge"><title>LEDBarGraph->LEDCollection</title>
|
||||
<path fill="none" stroke="black" d="M201.27,-180.303C200.498,-188.017 199.571,-197.288 198.711,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="195.223,-205.597 197.71,-215.896 202.188,-206.294 195.223,-205.597"/>
|
||||
<path fill="none" stroke="black" d="M537.665,-288C525.958,-288 512.998,-288 500.73,-288"/>
|
||||
<polygon fill="black" stroke="black" points="500.63,-284.5 490.63,-288 500.63,-291.5 500.63,-284.5"/>
|
||||
</g>
|
||||
<!-- PiLiter -->
|
||||
<g id="node7" class="node"><title>PiLiter</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="54,-108 0,-108 0,-72 54,-72 54,-108"/>
|
||||
<text text-anchor="middle" x="27" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiLiter</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="728,-454 674,-454 674,-418 728,-418 728,-454"/>
|
||||
<text text-anchor="middle" x="701" y="-433.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiLiter</text>
|
||||
</g>
|
||||
<!-- PiLiter->LEDBoard -->
|
||||
<g id="edge6" class="edge"><title>PiLiter->LEDBoard</title>
|
||||
<path fill="none" stroke="black" d="M47.0225,-108.303C57.1256,-117.035 69.536,-127.76 80.5175,-137.25"/>
|
||||
<polygon fill="black" stroke="black" points="78.3532,-140.005 88.2078,-143.896 82.9302,-134.709 78.3532,-140.005"/>
|
||||
<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="621.163,-388.161 610.723,-386.36 617.762,-394.279 621.163,-388.161"/>
|
||||
</g>
|
||||
<!-- PiLiterBarGraph -->
|
||||
<g id="node8" class="node"><title>PiLiterBarGraph</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="254,-108 162,-108 162,-72 254,-72 254,-108"/>
|
||||
<text text-anchor="middle" x="208" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiLiterBarGraph</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="747,-292 655,-292 655,-256 747,-256 747,-292"/>
|
||||
<text text-anchor="middle" x="701" y="-271.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiLiterBarGraph</text>
|
||||
</g>
|
||||
<!-- PiLiterBarGraph->LEDBarGraph -->
|
||||
<g id="edge7" class="edge"><title>PiLiterBarGraph->LEDBarGraph</title>
|
||||
<path fill="none" stroke="black" d="M206.764,-108.303C206.213,-116.017 205.551,-125.288 204.937,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="201.443,-133.672 204.222,-143.896 208.425,-134.17 201.443,-133.672"/>
|
||||
<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="628.558,-278.777 619.029,-283.407 629.366,-285.73 628.558,-278.777"/>
|
||||
</g>
|
||||
<!-- TrafficLights -->
|
||||
<g id="node9" class="node"><title>TrafficLights</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="144,-108 72,-108 72,-72 144,-72 144,-108"/>
|
||||
<text text-anchor="middle" x="108" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLights</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="737,-400 665,-400 665,-364 737,-364 737,-400"/>
|
||||
<text text-anchor="middle" x="701" y="-379.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLights</text>
|
||||
</g>
|
||||
<!-- TrafficLights->LEDBoard -->
|
||||
<g id="edge8" class="edge"><title>TrafficLights->LEDBoard</title>
|
||||
<path fill="none" stroke="black" d="M108,-108.303C108,-116.017 108,-125.288 108,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="104.5,-133.896 108,-143.896 111.5,-133.896 104.5,-133.896"/>
|
||||
<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="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->LEDBoard -->
|
||||
<g id="edge9" class="edge"><title>SnowPi->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>
|
||||
<!-- PiTraffic -->
|
||||
<g id="node10" class="node"><title>PiTraffic</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="99,-36 45,-36 45,-0 99,-0 99,-36"/>
|
||||
<text text-anchor="middle" x="72" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiTraffic</text>
|
||||
<g id="node11" class="node"><title>PiTraffic</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="837,-427 783,-427 783,-391 837,-391 837,-427"/>
|
||||
<text text-anchor="middle" x="810" y="-406.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiTraffic</text>
|
||||
</g>
|
||||
<!-- PiTraffic->TrafficLights -->
|
||||
<g id="edge9" class="edge"><title>PiTraffic->TrafficLights</title>
|
||||
<path fill="none" stroke="black" d="M80.8989,-36.3034C84.9968,-44.2716 89.9488,-53.9005 94.4927,-62.7359"/>
|
||||
<polygon fill="black" stroke="black" points="91.5174,-64.6035 99.2035,-71.8957 97.7425,-61.402 91.5174,-64.6035"/>
|
||||
<g id="edge10" class="edge"><title>PiTraffic->TrafficLights</title>
|
||||
<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="747.749,-389.935 737.197,-390.881 746.036,-396.722 747.749,-389.935"/>
|
||||
</g>
|
||||
<!-- PiStop -->
|
||||
<g id="node11" class="node"><title>PiStop</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="171,-36 117,-36 117,-0 171,-0 171,-36"/>
|
||||
<text text-anchor="middle" x="144" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiStop</text>
|
||||
<g id="node12" class="node"><title>PiStop</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="837,-373 783,-373 783,-337 837,-337 837,-373"/>
|
||||
<text text-anchor="middle" x="810" y="-352.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiStop</text>
|
||||
</g>
|
||||
<!-- PiStop->TrafficLights -->
|
||||
<g id="edge10" class="edge"><title>PiStop->TrafficLights</title>
|
||||
<path fill="none" stroke="black" d="M135.101,-36.3034C131.003,-44.2716 126.051,-53.9005 121.507,-62.7359"/>
|
||||
<polygon fill="black" stroke="black" points="118.258,-61.402 116.797,-71.8957 124.483,-64.6035 118.258,-61.402"/>
|
||||
<g id="edge11" class="edge"><title>PiStop->TrafficLights</title>
|
||||
<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="746.036,-367.278 737.197,-373.119 747.749,-374.065 746.036,-367.278"/>
|
||||
</g>
|
||||
<!-- TrafficLightsBuzzer -->
|
||||
<g id="node12" 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"/>
|
||||
<text text-anchor="middle" x="308" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLightsBuzzer</text>
|
||||
<g id="node13" class="node"><title>TrafficLightsBuzzer</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="502,-252 397,-252 397,-216 502,-216 502,-252"/>
|
||||
<text text-anchor="middle" x="449.5" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLightsBuzzer</text>
|
||||
</g>
|
||||
<!-- TrafficLightsBuzzer->CompositeOutputDevice -->
|
||||
<g id="edge11" class="edge"><title>TrafficLightsBuzzer->CompositeOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M308,-252.303C308,-260.017 308,-269.288 308,-277.888"/>
|
||||
<polygon fill="black" stroke="black" points="304.5,-277.896 308,-287.896 311.5,-277.896 304.5,-277.896"/>
|
||||
<g id="edge12" class="edge"><title>TrafficLightsBuzzer->CompositeOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M396.96,-234C388.696,-234 379.995,-234 371.3,-234"/>
|
||||
<polygon fill="black" stroke="black" points="371.125,-230.5 361.124,-234 371.124,-237.5 371.125,-230.5"/>
|
||||
</g>
|
||||
<!-- FishDish -->
|
||||
<g id="node13" class="node"><title>FishDish</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="324,-180 268,-180 268,-144 324,-144 324,-180"/>
|
||||
<text text-anchor="middle" x="296" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">FishDish</text>
|
||||
<g id="node14" class="node"><title>FishDish</title>
|
||||
<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="578.5" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">FishDish</text>
|
||||
</g>
|
||||
<!-- FishDish->TrafficLightsBuzzer -->
|
||||
<g id="edge12" class="edge"><title>FishDish->TrafficLightsBuzzer</title>
|
||||
<path fill="none" stroke="black" d="M298.966,-180.303C300.289,-188.017 301.878,-197.288 303.352,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="299.928,-206.631 305.068,-215.896 306.828,-205.448 299.928,-206.631"/>
|
||||
<g id="edge13" class="edge"><title>FishDish->TrafficLightsBuzzer</title>
|
||||
<path fill="none" stroke="black" d="M550.255,-234C539.046,-234 525.57,-234 512.2,-234"/>
|
||||
<polygon fill="black" stroke="black" points="512.036,-230.5 502.036,-234 512.036,-237.5 512.036,-230.5"/>
|
||||
</g>
|
||||
<!-- TrafficHat -->
|
||||
<g id="node14" 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"/>
|
||||
<text text-anchor="middle" x="373" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficHat</text>
|
||||
<g id="node15" class="node"><title>TrafficHat</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="609,-198 548,-198 548,-162 609,-162 609,-198"/>
|
||||
<text text-anchor="middle" x="578.5" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficHat</text>
|
||||
</g>
|
||||
<!-- TrafficHat->TrafficLightsBuzzer -->
|
||||
<g id="edge13" class="edge"><title>TrafficHat->TrafficLightsBuzzer</title>
|
||||
<path fill="none" stroke="black" d="M356.933,-180.303C349.061,-188.78 339.445,-199.136 330.827,-208.417"/>
|
||||
<polygon fill="black" stroke="black" points="328.122,-206.186 323.883,-215.896 333.252,-210.949 328.122,-206.186"/>
|
||||
<g id="edge14" class="edge"><title>TrafficHat->TrafficLightsBuzzer</title>
|
||||
<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="500.908,-208.763 493.075,-215.897 503.647,-215.205 500.908,-208.763"/>
|
||||
</g>
|
||||
<!-- Robot -->
|
||||
<g id="node15" class="node"><title>Robot</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="485,-324 431,-324 431,-288 485,-288 485,-324"/>
|
||||
<text text-anchor="middle" x="458" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text>
|
||||
<g id="node16" class="node"><title>Robot</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="321,-198 267,-198 267,-162 321,-162 321,-198"/>
|
||||
<text text-anchor="middle" x="294" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text>
|
||||
</g>
|
||||
<!-- Robot->CompositeDevice -->
|
||||
<g id="edge14" class="edge"><title>Robot->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M475.798,-324.303C484.604,-332.865 495.382,-343.344 505.001,-352.696"/>
|
||||
<polygon fill="black" stroke="black" points="502.797,-355.434 512.407,-359.896 507.677,-350.415 502.797,-355.434"/>
|
||||
<g id="edge15" class="edge"><title>Robot->CompositeDevice</title>
|
||||
<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="201.644,-143.722 191.049,-143.661 199.294,-150.316 201.644,-143.722"/>
|
||||
</g>
|
||||
<!-- RyanteckRobot -->
|
||||
<g id="node16" class="node"><title>RyanteckRobot</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="465,-252 379,-252 379,-216 465,-216 465,-252"/>
|
||||
<text text-anchor="middle" x="422" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">RyanteckRobot</text>
|
||||
<g id="node17" class="node"><title>RyanteckRobot</title>
|
||||
<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="449.5" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">RyanteckRobot</text>
|
||||
</g>
|
||||
<!-- RyanteckRobot->Robot -->
|
||||
<g id="edge15" class="edge"><title>RyanteckRobot->Robot</title>
|
||||
<path fill="none" stroke="black" d="M430.899,-252.303C434.997,-260.272 439.949,-269.9 444.493,-278.736"/>
|
||||
<polygon fill="black" stroke="black" points="441.517,-280.604 449.203,-287.896 447.742,-277.402 441.517,-280.604"/>
|
||||
<g id="edge16" class="edge"><title>RyanteckRobot->Robot</title>
|
||||
<path fill="none" stroke="black" d="M406.149,-180C382.707,-180 353.867,-180 331.439,-180"/>
|
||||
<polygon fill="black" stroke="black" points="331.313,-176.5 321.313,-180 331.313,-183.5 331.313,-176.5"/>
|
||||
</g>
|
||||
<!-- CamJamKitRobot -->
|
||||
<g id="node17" class="node"><title>CamJamKitRobot</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="579,-252 483,-252 483,-216 579,-216 579,-252"/>
|
||||
<text text-anchor="middle" x="531" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">CamJamKitRobot</text>
|
||||
<g id="node18" class="node"><title>CamJamKitRobot</title>
|
||||
<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="449.5" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">CamJamKitRobot</text>
|
||||
</g>
|
||||
<!-- CamJamKitRobot->Robot -->
|
||||
<g id="edge16" class="edge"><title>CamJamKitRobot->Robot</title>
|
||||
<path fill="none" stroke="black" d="M512.955,-252.303C503.938,-260.95 492.882,-271.551 483.057,-280.973"/>
|
||||
<polygon fill="black" stroke="black" points="480.633,-278.448 475.837,-287.896 485.478,-283.501 480.633,-278.448"/>
|
||||
<g id="edge17" class="edge"><title>CamJamKitRobot->Robot</title>
|
||||
<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="329.661,-164.096 321.389,-170.717 331.984,-170.7 329.661,-164.096"/>
|
||||
</g>
|
||||
<!-- Motor -->
|
||||
<g id="node18" class="node"><title>Motor</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="557,-324 503,-324 503,-288 557,-288 557,-324"/>
|
||||
<text text-anchor="middle" x="530" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Motor</text>
|
||||
<g id="node19" class="node"><title>Motor</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="321,-144 267,-144 267,-108 321,-108 321,-144"/>
|
||||
<text text-anchor="middle" x="294" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">Motor</text>
|
||||
</g>
|
||||
<!-- Motor->CompositeDevice -->
|
||||
<g id="edge17" class="edge"><title>Motor->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M530,-324.303C530,-332.017 530,-341.288 530,-349.888"/>
|
||||
<polygon fill="black" stroke="black" points="526.5,-349.896 530,-359.896 533.5,-349.896 526.5,-349.896"/>
|
||||
<g id="edge18" class="edge"><title>Motor->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M266.956,-126C248.908,-126 224.12,-126 201.342,-126"/>
|
||||
<polygon fill="black" stroke="black" points="201.049,-122.5 191.049,-126 201.049,-129.5 201.049,-122.5"/>
|
||||
</g>
|
||||
<!-- Servo -->
|
||||
<g id="node19" class="node"><title>Servo</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="629,-324 575,-324 575,-288 629,-288 629,-324"/>
|
||||
<text text-anchor="middle" x="602" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Servo</text>
|
||||
<g id="node20" class="node"><title>Servo</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="321,-90 267,-90 267,-54 321,-54 321,-90"/>
|
||||
<text text-anchor="middle" x="294" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">Servo</text>
|
||||
</g>
|
||||
<!-- Servo->CompositeDevice -->
|
||||
<g id="edge18" class="edge"><title>Servo->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M584.202,-324.303C575.396,-332.865 564.618,-343.344 554.999,-352.696"/>
|
||||
<polygon fill="black" stroke="black" points="552.323,-350.415 547.593,-359.896 557.203,-355.434 552.323,-350.415"/>
|
||||
<g id="edge19" class="edge"><title>Servo->CompositeDevice</title>
|
||||
<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="199.294,-101.684 191.049,-108.339 201.644,-108.278 199.294,-101.684"/>
|
||||
</g>
|
||||
<!-- AngularServo -->
|
||||
<g id="node20" 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"/>
|
||||
<text text-anchor="middle" x="638" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">AngularServo</text>
|
||||
<g id="node21" class="node"><title>AngularServo</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="490,-90 409,-90 409,-54 490,-54 490,-90"/>
|
||||
<text text-anchor="middle" x="449.5" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">AngularServo</text>
|
||||
</g>
|
||||
<!-- AngularServo->Servo -->
|
||||
<g id="edge19" class="edge"><title>AngularServo->Servo</title>
|
||||
<path fill="none" stroke="black" d="M629.101,-252.303C625.003,-260.272 620.051,-269.9 615.507,-278.736"/>
|
||||
<polygon fill="black" stroke="black" points="612.258,-277.402 610.797,-287.896 618.483,-280.604 612.258,-277.402"/>
|
||||
<g id="edge20" class="edge"><title>AngularServo->Servo</title>
|
||||
<path fill="none" stroke="black" d="M408.97,-72C385.09,-72 354.867,-72 331.545,-72"/>
|
||||
<polygon fill="black" stroke="black" points="331.363,-68.5001 321.363,-72 331.363,-75.5001 331.363,-68.5001"/>
|
||||
</g>
|
||||
<!-- Energenie -->
|
||||
<g id="node21" 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"/>
|
||||
<text text-anchor="middle" x="631" y="-375.5" font-family="Sans" font-size="10.00" fill="#ffffff">Energenie</text>
|
||||
<g id="node22" class="node"><title>Energenie</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="173,-90 108,-90 108,-54 173,-54 173,-90"/>
|
||||
<text text-anchor="middle" x="140.5" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">Energenie</text>
|
||||
</g>
|
||||
<!-- Energenie->Device -->
|
||||
<g id="edge20" class="edge"><title>Energenie->Device</title>
|
||||
<path fill="none" stroke="black" d="M618.393,-396.303C612.403,-404.526 605.124,-414.517 598.521,-423.579"/>
|
||||
<polygon fill="black" stroke="black" points="595.522,-421.752 592.462,-431.896 601.179,-425.874 595.522,-421.752"/>
|
||||
<g id="edge21" class="edge"><title>Energenie->Device</title>
|
||||
<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="62.9844,-86.9272 54.089,-92.6825 64.6319,-93.7306 62.9844,-86.9272"/>
|
||||
</g>
|
||||
<!-- ButtonBoard -->
|
||||
<g id="node22" 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"/>
|
||||
<text text-anchor="middle" x="686" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">ButtonBoard</text>
|
||||
<g id="node23" class="node"><title>ButtonBoard</title>
|
||||
<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="294" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">ButtonBoard</text>
|
||||
</g>
|
||||
<!-- ButtonBoard->CompositeDevice -->
|
||||
<g id="edge21" class="edge"><title>ButtonBoard->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M647.838,-324.124C626.607,-333.651 599.992,-345.593 577.431,-355.717"/>
|
||||
<polygon fill="black" stroke="black" points="575.729,-352.644 568.038,-359.932 578.594,-359.031 575.729,-352.644"/>
|
||||
<g id="edge22" class="edge"><title>ButtonBoard->CompositeDevice</title>
|
||||
<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="185.354,-99.2363 179.313,-107.94 189.463,-104.903 185.354,-99.2363"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,7 +1,7 @@
|
||||
/* vim: set et sw=4 sts=4: */
|
||||
|
||||
digraph classes {
|
||||
graph [rankdir=BT];
|
||||
graph [rankdir=RL];
|
||||
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
|
||||
edge [];
|
||||
|
||||
@@ -60,11 +60,21 @@ digraph classes {
|
||||
SPIDevice->Device;
|
||||
AnalogInputDevice->SPIDevice;
|
||||
MCP3xxx->AnalogInputDevice;
|
||||
MCP30xx->MCP3xxx;
|
||||
MCP32xx->MCP3xxx;
|
||||
MCP33xx->MCP3xxx;
|
||||
MCP3004->MCP3xxx;
|
||||
MCP3008->MCP3xxx;
|
||||
MCP3204->MCP3xxx;
|
||||
MCP3208->MCP3xxx;
|
||||
MCP3xx2->MCP3xxx;
|
||||
|
||||
MCP3001->MCP30xx;
|
||||
MCP3002->MCP30xx;
|
||||
MCP3004->MCP30xx;
|
||||
MCP3008->MCP30xx;
|
||||
MCP3201->MCP32xx;
|
||||
MCP3202->MCP32xx;
|
||||
MCP3204->MCP32xx;
|
||||
MCP3208->MCP32xx;
|
||||
MCP3002->MCP3xx2;
|
||||
MCP3202->MCP3xx2;
|
||||
MCP3301->MCP33xx;
|
||||
MCP3302->MCP33xx;
|
||||
MCP3304->MCP33xx;
|
||||
@@ -75,10 +85,15 @@ digraph classes {
|
||||
LEDCollection->CompositeOutputDevice;
|
||||
LEDBoard->LEDCollection;
|
||||
LEDBarGraph->LEDCollection;
|
||||
LedBorg->RGBLED;
|
||||
ButtonBoard->CompositeDevice;
|
||||
ButtonBoard->HoldMixin;
|
||||
PiLiter->LEDBoard;
|
||||
PiLiterBarGraph->LEDBarGraph;
|
||||
TrafficLights->LEDBoard;
|
||||
SnowPi->LEDBoard;
|
||||
PiTraffic->TrafficLights;
|
||||
PiStop->TrafficLights;
|
||||
TrafficLightsBuzzer->CompositeOutputDevice;
|
||||
FishDish->TrafficLightsBuzzer;
|
||||
TrafficHat->TrafficLightsBuzzer;
|
||||
@@ -90,10 +105,14 @@ digraph classes {
|
||||
CamJamKitRobot->Robot;
|
||||
Motor->CompositeDevice;
|
||||
Motor->SourceMixin;
|
||||
Servo->CompositeDevice;
|
||||
Servo->SourceMixin;
|
||||
AngularServo->Servo;
|
||||
|
||||
InternalDevice->Device;
|
||||
InternalDevice->EventsMixin;
|
||||
TimeOfDay->InternalDevice;
|
||||
PingServer->InternalDevice;
|
||||
CPUTemperature->InternalDevice;
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,7 +1,7 @@
|
||||
/* vim: set et sw=4 sts=4: */
|
||||
|
||||
digraph classes {
|
||||
graph [rankdir=BT];
|
||||
graph [rankdir=RL];
|
||||
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
|
||||
edge [];
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -1,108 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"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 -->
|
||||
<svg width="453pt" height="332pt"
|
||||
viewBox="0.00 0.00 453.00 332.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)">
|
||||
<svg width="566pt" height="260pt"
|
||||
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 256)">
|
||||
<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 -->
|
||||
<g id="node1" class="node"><title>Device</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="335,-324 281,-324 281,-288 335,-288 335,-324"/>
|
||||
<text text-anchor="middle" x="308" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-103 0,-103 0,-67 54,-67 54,-103"/>
|
||||
<text text-anchor="middle" x="27" y="-82.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
</g>
|
||||
<!-- GPIODevice -->
|
||||
<g id="node2" class="node"><title>GPIODevice</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="344,-252 272,-252 272,-216 344,-216 344,-252"/>
|
||||
<text text-anchor="middle" x="308" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="162,-103 90,-103 90,-67 162,-67 162,-103"/>
|
||||
<text text-anchor="middle" x="126" y="-82.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text>
|
||||
</g>
|
||||
<!-- GPIODevice->Device -->
|
||||
<g id="edge1" class="edge"><title>GPIODevice->Device</title>
|
||||
<path fill="none" stroke="black" d="M308,-252.303C308,-260.017 308,-269.288 308,-277.888"/>
|
||||
<polygon fill="black" stroke="black" points="304.5,-277.896 308,-287.896 311.5,-277.896 304.5,-277.896"/>
|
||||
<path fill="none" stroke="black" d="M89.9808,-85C81.6627,-85 72.77,-85 64.3922,-85"/>
|
||||
<polygon fill="black" stroke="black" points="64.1378,-81.5001 54.1378,-85 64.1377,-88.5001 64.1378,-81.5001"/>
|
||||
</g>
|
||||
<!-- SmoothedInputDevice -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="228" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">SmoothedInputDevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="430,-144 307,-144 307,-108 430,-108 430,-144"/>
|
||||
<text text-anchor="middle" x="368.5" y="-123.5" font-family="Sans" font-size="10.00" fill="#000000">SmoothedInputDevice</text>
|
||||
</g>
|
||||
<!-- InputDevice -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="308" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">InputDevice</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="271,-103 198,-103 198,-67 271,-67 271,-103"/>
|
||||
<text text-anchor="middle" x="234.5" y="-82.5" font-family="Sans" font-size="10.00" fill="#ffffff">InputDevice</text>
|
||||
</g>
|
||||
<!-- SmoothedInputDevice->InputDevice -->
|
||||
<g id="edge4" class="edge"><title>SmoothedInputDevice->InputDevice</title>
|
||||
<path fill="none" stroke="black" d="M247.775,-108.303C257.754,-117.035 270.011,-127.76 280.857,-137.25"/>
|
||||
<polygon fill="black" stroke="black" points="278.622,-139.945 288.452,-143.896 283.231,-134.677 278.622,-139.945"/>
|
||||
<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="281.69,-95.6819 271.102,-96.0581 279.613,-102.367 281.69,-95.6819"/>
|
||||
</g>
|
||||
<!-- InputDevice->GPIODevice -->
|
||||
<g id="edge2" class="edge"><title>InputDevice->GPIODevice</title>
|
||||
<path fill="none" stroke="black" d="M308,-180.303C308,-188.017 308,-197.288 308,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="304.5,-205.896 308,-215.896 311.5,-205.896 304.5,-205.896"/>
|
||||
<path fill="none" stroke="black" d="M197.741,-85C189.578,-85 180.799,-85 172.296,-85"/>
|
||||
<polygon fill="black" stroke="black" points="172.122,-81.5001 162.122,-85 172.122,-88.5001 172.122,-81.5001"/>
|
||||
</g>
|
||||
<!-- DigitalInputDevice -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="388" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="420,-63 317,-63 317,-27 420,-27 420,-63"/>
|
||||
<text text-anchor="middle" x="368.5" y="-42.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text>
|
||||
</g>
|
||||
<!-- DigitalInputDevice->InputDevice -->
|
||||
<g id="edge3" class="edge"><title>DigitalInputDevice->InputDevice</title>
|
||||
<path fill="none" stroke="black" d="M368.225,-108.303C358.246,-117.035 345.989,-127.76 335.143,-137.25"/>
|
||||
<polygon fill="black" stroke="black" points="332.769,-134.677 327.548,-143.896 337.378,-139.945 332.769,-134.677"/>
|
||||
<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="279.635,-67.9685 271.08,-74.2182 281.665,-74.6677 279.635,-67.9685"/>
|
||||
</g>
|
||||
<!-- Button -->
|
||||
<g id="node6" class="node"><title>Button</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="445,-36 391,-36 391,-0 445,-0 445,-36"/>
|
||||
<text text-anchor="middle" x="418" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="539,-36 485,-36 485,-0 539,-0 539,-36"/>
|
||||
<text text-anchor="middle" x="512" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text>
|
||||
</g>
|
||||
<!-- Button->DigitalInputDevice -->
|
||||
<g id="edge5" class="edge"><title>Button->DigitalInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M410.584,-36.3034C407.206,-44.1868 403.13,-53.6958 399.377,-62.4536"/>
|
||||
<polygon fill="black" stroke="black" points="396.053,-61.3255 395.33,-71.8957 402.487,-64.0829 396.053,-61.3255"/>
|
||||
<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="429.447,-29.9981 420.28,-35.3105 430.759,-36.8741 429.447,-29.9981"/>
|
||||
</g>
|
||||
<!-- MotionSensor -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="41" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MotionSensor</text>
|
||||
<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="512" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">MotionSensor</text>
|
||||
</g>
|
||||
<!-- MotionSensor->SmoothedInputDevice -->
|
||||
<g id="edge6" class="edge"><title>MotionSensor->SmoothedInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M82.5014,-34.5353C109.086,-44.4869 143.895,-57.5168 172.8,-68.3368"/>
|
||||
<polygon fill="black" stroke="black" points="171.763,-71.6859 182.356,-71.9139 174.217,-65.1302 171.763,-71.6859"/>
|
||||
<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="398.878,-148.077 389.088,-144.028 394.216,-153.299 398.878,-148.077"/>
|
||||
</g>
|
||||
<!-- LightSensor -->
|
||||
<g id="node8" class="node"><title>LightSensor</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="175,-36 101,-36 101,-0 175,-0 175,-36"/>
|
||||
<text text-anchor="middle" x="138" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LightSensor</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="549,-198 475,-198 475,-162 549,-162 549,-198"/>
|
||||
<text text-anchor="middle" x="512" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">LightSensor</text>
|
||||
</g>
|
||||
<!-- LightSensor->SmoothedInputDevice -->
|
||||
<g id="edge7" class="edge"><title>LightSensor->SmoothedInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M160.247,-36.3034C171.582,-45.1193 185.53,-55.9679 197.819,-65.5258"/>
|
||||
<polygon fill="black" stroke="black" points="195.966,-68.519 206.009,-71.8957 200.264,-62.9935 195.966,-68.519"/>
|
||||
<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="427.325,-144.321 416.734,-144.026 424.829,-150.861 427.325,-144.321"/>
|
||||
</g>
|
||||
<!-- LineSensor -->
|
||||
<g id="node9" class="node"><title>LineSensor</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="263,-36 193,-36 193,-0 263,-0 263,-36"/>
|
||||
<text text-anchor="middle" x="228" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LineSensor</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="547,-144 477,-144 477,-108 547,-108 547,-144"/>
|
||||
<text text-anchor="middle" x="512" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">LineSensor</text>
|
||||
</g>
|
||||
<!-- LineSensor->SmoothedInputDevice -->
|
||||
<g id="edge8" class="edge"><title>LineSensor->SmoothedInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M228,-36.3034C228,-44.0173 228,-53.2875 228,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="224.5,-61.8956 228,-71.8957 231.5,-61.8957 224.5,-61.8956"/>
|
||||
<path fill="none" stroke="black" d="M476.747,-126C465.711,-126 453.043,-126 440.37,-126"/>
|
||||
<polygon fill="black" stroke="black" points="440.237,-122.5 430.237,-126 440.237,-129.5 440.237,-122.5"/>
|
||||
</g>
|
||||
<!-- DistanceSensor -->
|
||||
<g id="node10" class="node"><title>DistanceSensor</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="373,-36 281,-36 281,-0 373,-0 373,-36"/>
|
||||
<text text-anchor="middle" x="327" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">DistanceSensor</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="558,-90 466,-90 466,-54 558,-54 558,-90"/>
|
||||
<text text-anchor="middle" x="512" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">DistanceSensor</text>
|
||||
</g>
|
||||
<!-- DistanceSensor->SmoothedInputDevice -->
|
||||
<g id="edge9" class="edge"><title>DistanceSensor->SmoothedInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M302.782,-36.1239C290.077,-45.1069 274.336,-56.2375 260.577,-65.9659"/>
|
||||
<polygon fill="black" stroke="black" points="258.456,-63.1791 252.311,-71.8102 262.497,-68.8947 258.456,-63.1791"/>
|
||||
<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="424.763,-101.164 416.668,-107.999 427.259,-107.704 424.763,-101.164"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
@@ -1,7 +1,7 @@
|
||||
/* vim: set et sw=4 sts=4: */
|
||||
|
||||
digraph classes {
|
||||
graph [rankdir=BT];
|
||||
graph [rankdir=RL];
|
||||
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
|
||||
edge [];
|
||||
|
||||
@@ -16,4 +16,5 @@ digraph classes {
|
||||
InternalDevice->Device;
|
||||
TimeOfDay->InternalDevice;
|
||||
PingServer->InternalDevice;
|
||||
CPUTemperature->InternalDevice;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 11 KiB |
@@ -1,48 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"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 -->
|
||||
<svg width="163pt" height="188pt"
|
||||
viewBox="0.00 0.00 163.00 188.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)">
|
||||
<svg width="317pt" height="152pt"
|
||||
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 148)">
|
||||
<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 -->
|
||||
<g id="node1" class="node"><title>Device</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="104,-180 50,-180 50,-144 104,-144 104,-180"/>
|
||||
<text text-anchor="middle" x="77" y="-159.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-90 0,-90 0,-54 54,-54 54,-90"/>
|
||||
<text text-anchor="middle" x="27" y="-69.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
</g>
|
||||
<!-- InternalDevice -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="77" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">InternalDevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="175,-90 90,-90 90,-54 175,-54 175,-90"/>
|
||||
<text text-anchor="middle" x="132.5" y="-69.5" font-family="Sans" font-size="10.00" fill="#000000">InternalDevice</text>
|
||||
</g>
|
||||
<!-- InternalDevice->Device -->
|
||||
<g id="edge1" class="edge"><title>InternalDevice->Device</title>
|
||||
<path fill="none" stroke="black" d="M77,-108.303C77,-116.017 77,-125.288 77,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="73.5001,-133.896 77,-143.896 80.5001,-133.896 73.5001,-133.896"/>
|
||||
<path fill="none" stroke="black" d="M89.999,-72C81.4576,-72 72.5283,-72 64.1926,-72"/>
|
||||
<polygon fill="black" stroke="black" points="64.0256,-68.5001 54.0256,-72 64.0255,-75.5001 64.0256,-68.5001"/>
|
||||
</g>
|
||||
<!-- TimeOfDay -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="34" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">TimeOfDay</text>
|
||||
<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="260" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">TimeOfDay</text>
|
||||
</g>
|
||||
<!-- TimeOfDay->InternalDevice -->
|
||||
<g id="edge2" class="edge"><title>TimeOfDay->InternalDevice</title>
|
||||
<path fill="none" stroke="black" d="M44.6292,-36.3034C49.6281,-44.4411 55.691,-54.311 61.2121,-63.2987"/>
|
||||
<polygon fill="black" stroke="black" points="58.2766,-65.2069 66.4931,-71.8957 64.2411,-61.5429 58.2766,-65.2069"/>
|
||||
<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="185.875,-90.7257 175.306,-89.9883 183.108,-97.1558 185.875,-90.7257"/>
|
||||
</g>
|
||||
<!-- PingServer -->
|
||||
<g id="node4" class="node"><title>PingServer</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="155,-36 87,-36 87,-0 155,-0 155,-36"/>
|
||||
<text text-anchor="middle" x="121" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PingServer</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="294,-90 226,-90 226,-54 294,-54 294,-90"/>
|
||||
<text text-anchor="middle" x="260" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">PingServer</text>
|
||||
</g>
|
||||
<!-- PingServer->InternalDevice -->
|
||||
<g id="edge3" class="edge"><title>PingServer->InternalDevice</title>
|
||||
<path fill="none" stroke="black" d="M110.124,-36.3034C105.008,-44.4411 98.8045,-54.311 93.1551,-63.2987"/>
|
||||
<polygon fill="black" stroke="black" points="90.1098,-61.5667 87.7513,-71.8957 96.0363,-65.2919 90.1098,-61.5667"/>
|
||||
<path fill="none" stroke="black" d="M225.698,-72C213.314,-72 198.952,-72 185.303,-72"/>
|
||||
<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->InternalDevice -->
|
||||
<g id="edge4" class="edge"><title>CPUTemperature->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>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.3 KiB |
@@ -1,7 +1,7 @@
|
||||
/* vim: set et sw=4 sts=4: */
|
||||
|
||||
digraph classes {
|
||||
graph [rankdir=BT];
|
||||
graph [rankdir=RL];
|
||||
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
|
||||
edge [];
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
@@ -1,108 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"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 -->
|
||||
<svg width="259pt" height="332pt"
|
||||
viewBox="0.00 0.00 259.00 332.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)">
|
||||
<svg width="530pt" height="179pt"
|
||||
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 175)">
|
||||
<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 -->
|
||||
<g id="node1" class="node"><title>Device</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="206,-324 152,-324 152,-288 206,-288 206,-324"/>
|
||||
<text text-anchor="middle" x="179" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-63 0,-63 0,-27 54,-27 54,-63"/>
|
||||
<text text-anchor="middle" x="27" y="-42.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
</g>
|
||||
<!-- GPIODevice -->
|
||||
<g id="node2" class="node"><title>GPIODevice</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="173,-252 101,-252 101,-216 173,-216 173,-252"/>
|
||||
<text text-anchor="middle" x="137" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="162,-90 90,-90 90,-54 162,-54 162,-90"/>
|
||||
<text text-anchor="middle" x="126" y="-69.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text>
|
||||
</g>
|
||||
<!-- GPIODevice->Device -->
|
||||
<g id="edge1" class="edge"><title>GPIODevice->Device</title>
|
||||
<path fill="none" stroke="black" d="M147.382,-252.303C152.265,-260.441 158.187,-270.311 163.579,-279.299"/>
|
||||
<polygon fill="black" stroke="black" points="160.591,-281.121 168.737,-287.896 166.594,-277.52 160.591,-281.121"/>
|
||||
<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="64.7101,-51.5853 54.1378,-52.2755 62.833,-58.3289 64.7101,-51.5853"/>
|
||||
</g>
|
||||
<!-- OutputDevice -->
|
||||
<g id="node3" class="node"><title>OutputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="177,-180 95,-180 95,-144 177,-144 177,-180"/>
|
||||
<text text-anchor="middle" x="136" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="280,-90 198,-90 198,-54 280,-54 280,-90"/>
|
||||
<text text-anchor="middle" x="239" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text>
|
||||
</g>
|
||||
<!-- OutputDevice->GPIODevice -->
|
||||
<g id="edge2" class="edge"><title>OutputDevice->GPIODevice</title>
|
||||
<path fill="none" stroke="black" d="M136.247,-180.303C136.357,-188.017 136.49,-197.288 136.613,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="133.113,-205.947 136.756,-215.896 140.112,-205.847 133.113,-205.947"/>
|
||||
<path fill="none" stroke="black" d="M197.926,-72C189.627,-72 180.827,-72 172.353,-72"/>
|
||||
<polygon fill="black" stroke="black" points="172.235,-68.5001 162.235,-72 172.235,-75.5001 172.235,-68.5001"/>
|
||||
</g>
|
||||
<!-- DigitalOutputDevice -->
|
||||
<g id="node4" class="node"><title>DigitalOutputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="129,-108 17,-108 17,-72 129,-72 129,-108"/>
|
||||
<text text-anchor="middle" x="73" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="428,-117 316,-117 316,-81 428,-81 428,-117"/>
|
||||
<text text-anchor="middle" x="372" y="-96.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text>
|
||||
</g>
|
||||
<!-- DigitalOutputDevice->OutputDevice -->
|
||||
<g id="edge3" class="edge"><title>DigitalOutputDevice->OutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M88.573,-108.303C96.2022,-116.78 105.523,-127.136 113.876,-136.417"/>
|
||||
<polygon fill="black" stroke="black" points="111.315,-138.804 120.606,-143.896 116.518,-134.121 111.315,-138.804"/>
|
||||
<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="290.886,-78.9146 280.386,-80.3238 289.473,-85.7705 290.886,-78.9146"/>
|
||||
</g>
|
||||
<!-- LED -->
|
||||
<g id="node5" class="node"><title>LED</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="54,-36 0,-36 0,-0 54,-0 54,-36"/>
|
||||
<text text-anchor="middle" x="27" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="520,-171 466,-171 466,-135 520,-135 520,-171"/>
|
||||
<text text-anchor="middle" x="493" y="-150.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text>
|
||||
</g>
|
||||
<!-- LED->DigitalOutputDevice -->
|
||||
<g id="edge4" class="edge"><title>LED->DigitalOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M38.3708,-36.3034C43.7185,-44.4411 50.2043,-54.311 56.1106,-63.2987"/>
|
||||
<polygon fill="black" stroke="black" points="53.3432,-65.4607 61.76,-71.8957 59.1932,-61.6165 53.3432,-65.4607"/>
|
||||
<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="423.647,-118.139 413.095,-117.194 420.755,-124.514 423.647,-118.139"/>
|
||||
</g>
|
||||
<!-- Buzzer -->
|
||||
<g id="node6" class="node"><title>Buzzer</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="126,-36 72,-36 72,-0 126,-0 126,-36"/>
|
||||
<text text-anchor="middle" x="99" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="520,-117 466,-117 466,-81 520,-81 520,-117"/>
|
||||
<text text-anchor="middle" x="493" y="-96.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text>
|
||||
</g>
|
||||
<!-- Buzzer->DigitalOutputDevice -->
|
||||
<g id="edge5" class="edge"><title>Buzzer->DigitalOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M92.573,-36.3034C89.6449,-44.1868 86.113,-53.6958 82.8601,-62.4536"/>
|
||||
<polygon fill="black" stroke="black" points="79.554,-61.3027 79.353,-71.8957 86.116,-63.7401 79.554,-61.3027"/>
|
||||
<path fill="none" stroke="black" d="M465.885,-99C457.659,-99 448.174,-99 438.475,-99"/>
|
||||
<polygon fill="black" stroke="black" points="438.334,-95.5001 428.334,-99 438.334,-102.5 438.334,-95.5001"/>
|
||||
</g>
|
||||
<!-- PWMOutputDevice -->
|
||||
<g id="node7" class="node"><title>PWMOutputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="251,-108 147,-108 147,-72 251,-72 251,-108"/>
|
||||
<text text-anchor="middle" x="199" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="424,-63 320,-63 320,-27 424,-27 424,-63"/>
|
||||
<text text-anchor="middle" x="372" y="-42.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text>
|
||||
</g>
|
||||
<!-- PWMOutputDevice->OutputDevice -->
|
||||
<g id="edge6" class="edge"><title>PWMOutputDevice->OutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M183.427,-108.303C175.798,-116.78 166.477,-127.136 158.124,-136.417"/>
|
||||
<polygon fill="black" stroke="black" points="155.482,-134.121 151.394,-143.896 160.685,-138.804 155.482,-134.121"/>
|
||||
<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="289.226,-58.2805 280.139,-63.7271 290.639,-65.1363 289.226,-58.2805"/>
|
||||
</g>
|
||||
<!-- PWMLED -->
|
||||
<g id="node8" class="node"><title>PWMLED</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="228,-36 170,-36 170,-0 228,-0 228,-36"/>
|
||||
<text text-anchor="middle" x="199" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="522,-63 464,-63 464,-27 522,-27 522,-63"/>
|
||||
<text text-anchor="middle" x="493" y="-42.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text>
|
||||
</g>
|
||||
<!-- PWMLED->PWMOutputDevice -->
|
||||
<g id="edge7" class="edge"><title>PWMLED->PWMOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M199,-36.3034C199,-44.0173 199,-53.2875 199,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="195.5,-61.8956 199,-71.8957 202.5,-61.8957 195.5,-61.8956"/>
|
||||
<path fill="none" stroke="black" d="M463.799,-45C454.932,-45 444.77,-45 434.527,-45"/>
|
||||
<polygon fill="black" stroke="black" points="434.303,-41.5001 424.303,-45 434.303,-48.5001 434.303,-41.5001"/>
|
||||
</g>
|
||||
<!-- RGBLED -->
|
||||
<g id="node9" class="node"><title>RGBLED</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="249,-252 193,-252 193,-216 249,-216 249,-252"/>
|
||||
<text text-anchor="middle" x="221" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="154,-36 98,-36 98,-0 154,-0 154,-36"/>
|
||||
<text text-anchor="middle" x="126" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text>
|
||||
</g>
|
||||
<!-- RGBLED->Device -->
|
||||
<g id="edge8" class="edge"><title>RGBLED->Device</title>
|
||||
<path fill="none" stroke="black" d="M210.618,-252.303C205.735,-260.441 199.813,-270.311 194.421,-279.299"/>
|
||||
<polygon fill="black" stroke="black" points="191.406,-277.52 189.263,-287.896 197.409,-281.121 191.406,-277.52"/>
|
||||
<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="62.9008,-31.6522 54.2057,-37.7056 64.778,-38.3958 62.9008,-31.6522"/>
|
||||
</g>
|
||||
<!-- LedBorg -->
|
||||
<g id="node10" class="node"><title>LedBorg</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="251,-180 195,-180 195,-144 251,-144 251,-180"/>
|
||||
<text text-anchor="middle" x="223" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">LedBorg</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="267,-36 211,-36 211,-0 267,-0 267,-36"/>
|
||||
<text text-anchor="middle" x="239" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LedBorg</text>
|
||||
</g>
|
||||
<!-- LedBorg->RGBLED -->
|
||||
<g id="edge9" class="edge"><title>LedBorg->RGBLED</title>
|
||||
<path fill="none" stroke="black" d="M222.506,-180.303C222.285,-188.017 222.02,-197.288 221.775,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="218.276,-205.8 221.489,-215.896 225.273,-206 218.276,-205.8"/>
|
||||
<path fill="none" stroke="black" d="M210.846,-18C196.871,-18 179.617,-18 164.401,-18"/>
|
||||
<polygon fill="black" stroke="black" points="164.258,-14.5001 154.258,-18 164.258,-21.5001 164.258,-14.5001"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -1,5 +1,5 @@
|
||||
digraph classes {
|
||||
graph [rankdir=BT];
|
||||
graph [rankdir=RL];
|
||||
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
|
||||
edge [];
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 53 KiB |
@@ -4,205 +4,205 @@
|
||||
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
|
||||
-->
|
||||
<!-- Title: classes Pages: 1 -->
|
||||
<svg width="870pt" height="404pt"
|
||||
viewBox="0.00 0.00 870.00 404.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)">
|
||||
<svg width="593pt" height="584pt"
|
||||
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 580)">
|
||||
<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 -->
|
||||
<g id="node1" class="node"><title>Device</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="418,-396 364,-396 364,-360 418,-360 418,-396"/>
|
||||
<text text-anchor="middle" x="391" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="54,-333 0,-333 0,-297 54,-297 54,-333"/>
|
||||
<text text-anchor="middle" x="27" y="-312.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
</g>
|
||||
<!-- SPIDevice -->
|
||||
<g id="node2" class="node"><title>SPIDevice</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="423,-324 359,-324 359,-288 423,-288 423,-324"/>
|
||||
<text text-anchor="middle" x="391" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">SPIDevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="154,-333 90,-333 90,-297 154,-297 154,-333"/>
|
||||
<text text-anchor="middle" x="122" y="-312.5" font-family="Sans" font-size="10.00" fill="#000000">SPIDevice</text>
|
||||
</g>
|
||||
<!-- SPIDevice->Device -->
|
||||
<g id="edge1" class="edge"><title>SPIDevice->Device</title>
|
||||
<path fill="none" stroke="black" d="M391,-324.303C391,-332.017 391,-341.288 391,-349.888"/>
|
||||
<polygon fill="black" stroke="black" points="387.5,-349.896 391,-359.896 394.5,-349.896 387.5,-349.896"/>
|
||||
<path fill="none" stroke="black" d="M89.7736,-315C81.5203,-315 72.5448,-315 64.0586,-315"/>
|
||||
<polygon fill="black" stroke="black" points="64.0313,-311.5 54.0313,-315 64.0312,-318.5 64.0313,-311.5"/>
|
||||
</g>
|
||||
<!-- AnalogInputDevice -->
|
||||
<g id="node3" class="node"><title>AnalogInputDevice</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="444,-252 338,-252 338,-216 444,-216 444,-252"/>
|
||||
<text text-anchor="middle" x="391" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">AnalogInputDevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="296,-333 190,-333 190,-297 296,-297 296,-333"/>
|
||||
<text text-anchor="middle" x="243" y="-312.5" font-family="Sans" font-size="10.00" fill="#000000">AnalogInputDevice</text>
|
||||
</g>
|
||||
<!-- AnalogInputDevice->SPIDevice -->
|
||||
<g id="edge2" class="edge"><title>AnalogInputDevice->SPIDevice</title>
|
||||
<path fill="none" stroke="black" d="M391,-252.303C391,-260.017 391,-269.288 391,-277.888"/>
|
||||
<polygon fill="black" stroke="black" points="387.5,-277.896 391,-287.896 394.5,-277.896 387.5,-277.896"/>
|
||||
<path fill="none" stroke="black" d="M189.805,-315C181.286,-315 172.565,-315 164.354,-315"/>
|
||||
<polygon fill="black" stroke="black" points="164.279,-311.5 154.279,-315 164.279,-318.5 164.279,-311.5"/>
|
||||
</g>
|
||||
<!-- MCP3xxx -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="391" y="-159.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xxx</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="391,-333 332,-333 332,-297 391,-297 391,-333"/>
|
||||
<text text-anchor="middle" x="361.5" y="-312.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xxx</text>
|
||||
</g>
|
||||
<!-- MCP3xxx->AnalogInputDevice -->
|
||||
<g id="edge3" class="edge"><title>MCP3xxx->AnalogInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M391,-180.303C391,-188.017 391,-197.288 391,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="387.5,-205.896 391,-215.896 394.5,-205.896 387.5,-205.896"/>
|
||||
<path fill="none" stroke="black" d="M331.702,-315C323.869,-315 315.072,-315 306.152,-315"/>
|
||||
<polygon fill="black" stroke="black" points="306.074,-311.5 296.074,-315 306.074,-318.5 306.074,-311.5"/>
|
||||
</g>
|
||||
<!-- MCP30xx -->
|
||||
<g id="node5" class="node"><title>MCP30xx</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="221,-108 161,-108 161,-72 221,-72 221,-108"/>
|
||||
<text text-anchor="middle" x="191" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP30xx</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="487,-468 427,-468 427,-432 487,-432 487,-468"/>
|
||||
<text text-anchor="middle" x="457" y="-447.5" font-family="Sans" font-size="10.00" fill="#000000">MCP30xx</text>
|
||||
</g>
|
||||
<!-- MCP30xx->MCP3xxx -->
|
||||
<g id="edge4" class="edge"><title>MCP30xx->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M221.376,-101.631C256.184,-113.814 313.335,-133.817 351.556,-147.195"/>
|
||||
<polygon fill="black" stroke="black" points="350.725,-150.612 361.319,-150.612 353.037,-144.005 350.725,-150.612"/>
|
||||
<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="383.78,-339.577 375.209,-333.349 378.025,-343.563 383.78,-339.577"/>
|
||||
</g>
|
||||
<!-- MCP32xx -->
|
||||
<g id="node6" class="node"><title>MCP32xx</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="461,-108 401,-108 401,-72 461,-72 461,-108"/>
|
||||
<text text-anchor="middle" x="431" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP32xx</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="487,-306 427,-306 427,-270 487,-270 487,-306"/>
|
||||
<text text-anchor="middle" x="457" y="-285.5" font-family="Sans" font-size="10.00" fill="#000000">MCP32xx</text>
|
||||
</g>
|
||||
<!-- MCP32xx->MCP3xxx -->
|
||||
<g id="edge5" class="edge"><title>MCP32xx->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M421.112,-108.303C416.511,-116.356 410.94,-126.106 405.847,-135.018"/>
|
||||
<polygon fill="black" stroke="black" points="402.696,-133.477 400.774,-143.896 408.774,-136.95 402.696,-133.477"/>
|
||||
<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="399.869,-300.566 391.232,-306.703 401.811,-307.291 399.869,-300.566"/>
|
||||
</g>
|
||||
<!-- MCP3xx2 -->
|
||||
<g id="node7" class="node"><title>MCP3xx2</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="381,-108 321,-108 321,-72 381,-72 381,-108"/>
|
||||
<text text-anchor="middle" x="351" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xx2</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="487,-360 427,-360 427,-324 487,-324 487,-360"/>
|
||||
<text text-anchor="middle" x="457" y="-339.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xx2</text>
|
||||
</g>
|
||||
<!-- MCP3xx2->MCP3xxx -->
|
||||
<g id="edge7" class="edge"><title>MCP3xx2->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M360.888,-108.303C365.489,-116.356 371.06,-126.106 376.153,-135.018"/>
|
||||
<polygon fill="black" stroke="black" points="373.226,-136.95 381.226,-143.896 379.304,-133.477 373.226,-136.95"/>
|
||||
<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="401.811,-322.709 391.232,-323.297 399.869,-329.434 401.811,-322.709"/>
|
||||
</g>
|
||||
<!-- MCP33xx -->
|
||||
<g id="node8" class="node"><title>MCP33xx</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="741,-108 681,-108 681,-72 741,-72 741,-108"/>
|
||||
<text text-anchor="middle" x="711" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP33xx</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="487,-117 427,-117 427,-81 487,-81 487,-117"/>
|
||||
<text text-anchor="middle" x="457" y="-96.5" font-family="Sans" font-size="10.00" fill="#000000">MCP33xx</text>
|
||||
</g>
|
||||
<!-- MCP33xx->MCP3xxx -->
|
||||
<g id="edge6" class="edge"><title>MCP33xx->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M680.895,-97.5854C622.855,-110.282 495.498,-138.141 430.644,-152.328"/>
|
||||
<polygon fill="black" stroke="black" points="429.813,-148.927 420.792,-154.483 431.309,-155.765 429.813,-148.927"/>
|
||||
<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="371.115,-286.287 370.354,-296.855 377.539,-289.068 371.115,-286.287"/>
|
||||
</g>
|
||||
<!-- MCP3001 -->
|
||||
<g id="node9" class="node"><title>MCP3001</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="142,-36 80,-36 80,-0 142,-0 142,-36"/>
|
||||
<text text-anchor="middle" x="111" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3001</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-576 523,-576 523,-540 585,-540 585,-576"/>
|
||||
<text text-anchor="middle" x="554" y="-555.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3001</text>
|
||||
</g>
|
||||
<!-- MCP3001->MCP30xx -->
|
||||
<g id="edge8" class="edge"><title>MCP3001->MCP30xx</title>
|
||||
<path fill="none" stroke="black" d="M130.775,-36.3034C140.754,-45.0345 153.011,-55.7595 163.857,-65.2497"/>
|
||||
<polygon fill="black" stroke="black" points="161.622,-67.9446 171.452,-71.8957 166.231,-62.6766 161.622,-67.9446"/>
|
||||
<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="479.978,-473.941 471.163,-468.065 474.389,-478.156 479.978,-473.941"/>
|
||||
</g>
|
||||
<!-- MCP3002 -->
|
||||
<g id="node10" class="node"><title>MCP3002</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="302,-36 240,-36 240,-0 302,-0 302,-36"/>
|
||||
<text text-anchor="middle" x="271" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3002</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-414 523,-414 523,-378 585,-378 585,-414"/>
|
||||
<text text-anchor="middle" x="554" y="-393.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3002</text>
|
||||
</g>
|
||||
<!-- MCP3002->MCP30xx -->
|
||||
<g id="edge9" class="edge"><title>MCP3002->MCP30xx</title>
|
||||
<path fill="none" stroke="black" d="M251.225,-36.3034C241.246,-45.0345 228.989,-55.7595 218.143,-65.2497"/>
|
||||
<polygon fill="black" stroke="black" points="215.769,-62.6766 210.548,-71.8957 220.378,-67.9446 215.769,-62.6766"/>
|
||||
<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="494.178,-425.41 487.214,-433.394 497.637,-431.496 494.178,-425.41"/>
|
||||
</g>
|
||||
<!-- MCP3002->MCP3xx2 -->
|
||||
<g id="edge16" class="edge"><title>MCP3002->MCP3xx2</title>
|
||||
<path fill="none" stroke="black" d="M290.775,-36.3034C300.754,-45.0345 313.011,-55.7595 323.857,-65.2497"/>
|
||||
<polygon fill="black" stroke="black" points="321.622,-67.9446 331.452,-71.8957 326.231,-62.6766 321.622,-67.9446"/>
|
||||
<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="497.637,-360.504 487.214,-358.606 494.178,-366.59 497.637,-360.504"/>
|
||||
</g>
|
||||
<!-- MCP3004 -->
|
||||
<g id="node11" class="node"><title>MCP3004</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="222,-36 160,-36 160,-0 222,-0 222,-36"/>
|
||||
<text text-anchor="middle" x="191" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3004</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-522 523,-522 523,-486 585,-486 585,-522"/>
|
||||
<text text-anchor="middle" x="554" y="-501.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3004</text>
|
||||
</g>
|
||||
<!-- MCP3004->MCP30xx -->
|
||||
<g id="edge10" class="edge"><title>MCP3004->MCP30xx</title>
|
||||
<path fill="none" stroke="black" d="M191,-36.3034C191,-44.0173 191,-53.2875 191,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="187.5,-61.8956 191,-71.8957 194.5,-61.8957 187.5,-61.8956"/>
|
||||
<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="497.637,-468.504 487.214,-466.606 494.178,-474.59 497.637,-468.504"/>
|
||||
</g>
|
||||
<!-- MCP3008 -->
|
||||
<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"/>
|
||||
<text text-anchor="middle" x="31" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3008</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-468 523,-468 523,-432 585,-432 585,-468"/>
|
||||
<text text-anchor="middle" x="554" y="-447.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3008</text>
|
||||
</g>
|
||||
<!-- MCP3008->MCP30xx -->
|
||||
<g id="edge11" class="edge"><title>MCP3008->MCP30xx</title>
|
||||
<path fill="none" stroke="black" d="M62.2294,-32.6629C87.7738,-43.8385 124.046,-59.7076 151.587,-71.757"/>
|
||||
<polygon fill="black" stroke="black" points="150.369,-75.044 160.933,-75.8457 153.174,-68.6309 150.369,-75.044"/>
|
||||
<path fill="none" stroke="black" d="M522.941,-450C514.826,-450 505.921,-450 497.378,-450"/>
|
||||
<polygon fill="black" stroke="black" points="497.214,-446.5 487.214,-450 497.213,-453.5 497.214,-446.5"/>
|
||||
</g>
|
||||
<!-- MCP3201 -->
|
||||
<g id="node13" class="node"><title>MCP3201</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="542,-36 480,-36 480,-0 542,-0 542,-36"/>
|
||||
<text text-anchor="middle" x="511" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3201</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-306 523,-306 523,-270 585,-270 585,-306"/>
|
||||
<text text-anchor="middle" x="554" y="-285.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3201</text>
|
||||
</g>
|
||||
<!-- MCP3201->MCP32xx -->
|
||||
<g id="edge12" class="edge"><title>MCP3201->MCP32xx</title>
|
||||
<path fill="none" stroke="black" d="M491.225,-36.3034C481.246,-45.0345 468.989,-55.7595 458.143,-65.2497"/>
|
||||
<polygon fill="black" stroke="black" points="455.769,-62.6766 450.548,-71.8957 460.378,-67.9446 455.769,-62.6766"/>
|
||||
<path fill="none" stroke="black" d="M522.941,-288C514.826,-288 505.921,-288 497.378,-288"/>
|
||||
<polygon fill="black" stroke="black" points="497.214,-284.5 487.214,-288 497.213,-291.5 497.214,-284.5"/>
|
||||
</g>
|
||||
<!-- MCP3202 -->
|
||||
<g id="node14" class="node"><title>MCP3202</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="382,-36 320,-36 320,-0 382,-0 382,-36"/>
|
||||
<text text-anchor="middle" x="351" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3202</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-360 523,-360 523,-324 585,-324 585,-360"/>
|
||||
<text text-anchor="middle" x="554" y="-339.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3202</text>
|
||||
</g>
|
||||
<!-- MCP3202->MCP32xx -->
|
||||
<g id="edge13" class="edge"><title>MCP3202->MCP32xx</title>
|
||||
<path fill="none" stroke="black" d="M370.775,-36.3034C380.754,-45.0345 393.011,-55.7595 403.857,-65.2497"/>
|
||||
<polygon fill="black" stroke="black" points="401.622,-67.9446 411.452,-71.8957 406.231,-62.6766 401.622,-67.9446"/>
|
||||
<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="497.637,-306.504 487.214,-304.606 494.178,-312.59 497.637,-306.504"/>
|
||||
</g>
|
||||
<!-- MCP3202->MCP3xx2 -->
|
||||
<g id="edge17" class="edge"><title>MCP3202->MCP3xx2</title>
|
||||
<path fill="none" stroke="black" d="M351,-36.3034C351,-44.0173 351,-53.2875 351,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="347.5,-61.8956 351,-71.8957 354.5,-61.8957 347.5,-61.8956"/>
|
||||
<path fill="none" stroke="black" d="M522.941,-342C514.826,-342 505.921,-342 497.378,-342"/>
|
||||
<polygon fill="black" stroke="black" points="497.214,-338.5 487.214,-342 497.213,-345.5 497.214,-338.5"/>
|
||||
</g>
|
||||
<!-- MCP3204 -->
|
||||
<g id="node15" class="node"><title>MCP3204</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="622,-36 560,-36 560,-0 622,-0 622,-36"/>
|
||||
<text text-anchor="middle" x="591" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3204</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-252 523,-252 523,-216 585,-216 585,-252"/>
|
||||
<text text-anchor="middle" x="554" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3204</text>
|
||||
</g>
|
||||
<!-- MCP3204->MCP32xx -->
|
||||
<g id="edge14" class="edge"><title>MCP3204->MCP32xx</title>
|
||||
<path fill="none" stroke="black" d="M559.771,-32.6629C534.226,-43.8385 497.954,-59.7076 470.413,-71.757"/>
|
||||
<polygon fill="black" stroke="black" points="468.826,-68.6309 461.067,-75.8457 471.631,-75.044 468.826,-68.6309"/>
|
||||
<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="494.178,-263.41 487.214,-271.394 497.637,-269.496 494.178,-263.41"/>
|
||||
</g>
|
||||
<!-- MCP3208 -->
|
||||
<g id="node16" class="node"><title>MCP3208</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="462,-36 400,-36 400,-0 462,-0 462,-36"/>
|
||||
<text text-anchor="middle" x="431" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3208</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-198 523,-198 523,-162 585,-162 585,-198"/>
|
||||
<text text-anchor="middle" x="554" y="-177.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3208</text>
|
||||
</g>
|
||||
<!-- MCP3208->MCP32xx -->
|
||||
<g id="edge15" class="edge"><title>MCP3208->MCP32xx</title>
|
||||
<path fill="none" stroke="black" d="M431,-36.3034C431,-44.0173 431,-53.2875 431,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="427.5,-61.8956 431,-71.8957 434.5,-61.8957 427.5,-61.8956"/>
|
||||
<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="474.389,-259.844 471.163,-269.935 479.978,-264.059 474.389,-259.844"/>
|
||||
</g>
|
||||
<!-- MCP3301 -->
|
||||
<g id="node17" class="node"><title>MCP3301</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="782,-36 720,-36 720,-0 782,-0 782,-36"/>
|
||||
<text text-anchor="middle" x="751" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3301</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-144 523,-144 523,-108 585,-108 585,-144"/>
|
||||
<text text-anchor="middle" x="554" y="-123.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3301</text>
|
||||
</g>
|
||||
<!-- MCP3301->MCP33xx -->
|
||||
<g id="edge18" class="edge"><title>MCP3301->MCP33xx</title>
|
||||
<path fill="none" stroke="black" d="M741.112,-36.3034C736.511,-44.3564 730.94,-54.1055 725.847,-63.0176"/>
|
||||
<polygon fill="black" stroke="black" points="722.696,-61.4767 720.774,-71.8957 728.774,-64.9497 722.696,-61.4767"/>
|
||||
<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="497.789,-106.67 487.214,-107.303 495.876,-113.403 497.789,-106.67"/>
|
||||
</g>
|
||||
<!-- MCP3302 -->
|
||||
<g id="node18" class="node"><title>MCP3302</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="862,-36 800,-36 800,-0 862,-0 862,-36"/>
|
||||
<text text-anchor="middle" x="831" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3302</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-90 523,-90 523,-54 585,-54 585,-90"/>
|
||||
<text text-anchor="middle" x="554" y="-69.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3302</text>
|
||||
</g>
|
||||
<!-- MCP3302->MCP33xx -->
|
||||
<g id="edge19" class="edge"><title>MCP3302->MCP33xx</title>
|
||||
<path fill="none" stroke="black" d="M801.645,-36.1239C785.955,-45.2764 766.443,-56.6583 749.546,-66.515"/>
|
||||
<polygon fill="black" stroke="black" points="747.342,-63.7483 740.468,-71.8102 750.87,-69.7947 747.342,-63.7483"/>
|
||||
<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="495.876,-84.5966 487.214,-90.6972 497.789,-91.33 495.876,-84.5966"/>
|
||||
</g>
|
||||
<!-- MCP3304 -->
|
||||
<g id="node19" class="node"><title>MCP3304</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="702,-36 640,-36 640,-0 702,-0 702,-36"/>
|
||||
<text text-anchor="middle" x="671" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3304</text>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="585,-36 523,-36 523,-0 585,-0 585,-36"/>
|
||||
<text text-anchor="middle" x="554" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3304</text>
|
||||
</g>
|
||||
<!-- MCP3304->MCP33xx -->
|
||||
<g id="edge20" class="edge"><title>MCP3304->MCP33xx</title>
|
||||
<path fill="none" stroke="black" d="M680.888,-36.3034C685.489,-44.3564 691.06,-54.1055 696.153,-63.0176"/>
|
||||
<polygon fill="black" stroke="black" points="693.226,-64.9497 701.226,-71.8957 699.304,-61.4767 693.226,-64.9497"/>
|
||||
<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="484.483,-71.8201 479.144,-80.9716 489.025,-77.1468 484.483,-71.8201"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -18,10 +18,12 @@ Table of Contents
|
||||
api_tools
|
||||
api_pins
|
||||
api_exc
|
||||
cli_tools
|
||||
source_values
|
||||
remote_gpio
|
||||
recipes_advanced
|
||||
recipes_remote_gpio
|
||||
contributing
|
||||
development
|
||||
changelog
|
||||
license
|
||||
|
||||
@@ -4,6 +4,7 @@ Notes
|
||||
|
||||
.. currentmodule:: gpiozero
|
||||
|
||||
|
||||
.. _keep-your-script-running:
|
||||
|
||||
Keep your script running
|
||||
@@ -45,6 +46,7 @@ events to be detected::
|
||||
button.when_pressed = hello
|
||||
pause()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
How can I tell what version of gpiozero I have installed?
|
||||
=========================================================
|
||||
|
||||
The gpiozero library relies on the setuptools package for installation
|
||||
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
|
||||
>>> require('gpiozero')
|
||||
@@ -88,7 +93,9 @@ first entry in the list will be the version that ``import gpiozero`` will
|
||||
import.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
*may* work under Python 2, but no guarantees!
|
||||
|
||||
.. _pin_numbering:
|
||||
.. _pin-numbering:
|
||||
|
||||
Pin Numbering
|
||||
=============
|
||||
|
||||
@@ -6,8 +6,9 @@ from __future__ import (
|
||||
)
|
||||
|
||||
from .pins import (
|
||||
Factory,
|
||||
Pin,
|
||||
LocalPin,
|
||||
SPI,
|
||||
)
|
||||
from .pins.data import (
|
||||
PiBoardInfo,
|
||||
@@ -15,47 +16,9 @@ from .pins.data import (
|
||||
PinInfo,
|
||||
pi_info,
|
||||
)
|
||||
from .exc import (
|
||||
GPIOZeroError,
|
||||
DeviceClosed,
|
||||
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,
|
||||
)
|
||||
# Yes, import * is naughty, but exc imports nothing else so there's no cross
|
||||
# contamination here ... and besides, have you *seen* the list lately?!
|
||||
from .exc import *
|
||||
from .devices import (
|
||||
Device,
|
||||
GPIODevice,
|
||||
|
||||
0
gpiozero/cli/__init__.py
Normal file
67
gpiozero/cli/pinout.py
Executable 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()
|
||||
@@ -9,6 +9,7 @@ from __future__ import (
|
||||
str = type('')
|
||||
|
||||
import cmath
|
||||
import weakref
|
||||
import collections
|
||||
import operator
|
||||
import functools
|
||||
@@ -81,3 +82,59 @@ class frozendict(collections.Mapping):
|
||||
hashes = map(hash, self.items())
|
||||
self.__hash = functools.reduce(operator.xor, hashes, 0)
|
||||
return self.__hash
|
||||
|
||||
|
||||
# Backported from py3.4
|
||||
class WeakMethod(weakref.ref):
|
||||
"""
|
||||
A custom `weakref.ref` subclass which simulates a weak reference to
|
||||
a bound method, working around the lifetime problem of bound methods.
|
||||
"""
|
||||
|
||||
__slots__ = "_func_ref", "_meth_type", "_alive", "__weakref__"
|
||||
|
||||
def __new__(cls, meth, callback=None):
|
||||
try:
|
||||
obj = meth.__self__
|
||||
func = meth.__func__
|
||||
except AttributeError:
|
||||
raise TypeError("argument should be a bound method, not {0}"
|
||||
.format(type(meth)))
|
||||
def _cb(arg):
|
||||
# The self-weakref trick is needed to avoid creating a reference
|
||||
# cycle.
|
||||
self = self_wr()
|
||||
if self._alive:
|
||||
self._alive = False
|
||||
if callback is not None:
|
||||
callback(self)
|
||||
self = weakref.ref.__new__(cls, obj, _cb)
|
||||
self._func_ref = weakref.ref(func, _cb)
|
||||
self._meth_type = type(meth)
|
||||
self._alive = True
|
||||
self_wr = weakref.ref(self)
|
||||
return self
|
||||
|
||||
def __call__(self):
|
||||
obj = super(WeakMethod, self).__call__()
|
||||
func = self._func_ref()
|
||||
if obj is None or func is None:
|
||||
return None
|
||||
return self._meth_type(func, obj)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, WeakMethod):
|
||||
if not self._alive or not other._alive:
|
||||
return self is other
|
||||
return weakref.ref.__eq__(self, other) and self._func_ref == other._func_ref
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, WeakMethod):
|
||||
if not self._alive or not other._alive:
|
||||
return self is not other
|
||||
return weakref.ref.__ne__(self, other) or self._func_ref != other._func_ref
|
||||
return True
|
||||
|
||||
__hash__ = weakref.ref.__hash__
|
||||
|
||||
|
||||
@@ -10,15 +10,16 @@ str = type('')
|
||||
import os
|
||||
import atexit
|
||||
import weakref
|
||||
from collections import namedtuple
|
||||
import warnings
|
||||
from collections import namedtuple, defaultdict
|
||||
from itertools import chain
|
||||
from types import FunctionType
|
||||
from threading import RLock
|
||||
from threading import Lock
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from .pins import Pin
|
||||
from .threads import _threads_shutdown
|
||||
from .pins import _pins_shutdown
|
||||
from .mixins import (
|
||||
ValuesMixin,
|
||||
SharedMixin,
|
||||
@@ -32,52 +33,11 @@ from .exc import (
|
||||
GPIOPinMissing,
|
||||
GPIOPinInUse,
|
||||
GPIODeviceClosed,
|
||||
PinFactoryFallback,
|
||||
)
|
||||
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):
|
||||
# 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.
|
||||
key = cls._shared_key(*args, **kwargs)
|
||||
try:
|
||||
self = cls._INSTANCES[key]
|
||||
self = cls._instances[key]
|
||||
self._refs += 1
|
||||
except (KeyError, ReferenceError) as e:
|
||||
self = super(GPIOMeta, cls).__call__(*args, **kwargs)
|
||||
@@ -122,14 +82,14 @@ class GPIOMeta(type):
|
||||
old_close()
|
||||
finally:
|
||||
try:
|
||||
del cls._INSTANCES[key]
|
||||
del cls._instances[key]
|
||||
except KeyError:
|
||||
# If the _refs go negative (too many closes)
|
||||
# just ignore the resulting KeyError here -
|
||||
# it's already gone
|
||||
pass
|
||||
self.close = close
|
||||
cls._INSTANCES[key] = weakref.proxy(self)
|
||||
cls._instances[key] = weakref.proxy(self)
|
||||
else:
|
||||
# Construct the instance as normal
|
||||
self = super(GPIOMeta, cls).__call__(*args, **kwargs)
|
||||
@@ -229,13 +189,89 @@ class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})):
|
||||
class Device(ValuesMixin, GPIOBase):
|
||||
"""
|
||||
Represents a single device of any type; GPIO-based, SPI-based, I2C-based,
|
||||
etc. This is the base class of the device hierarchy. It defines the
|
||||
basic services applicable to all devices (specifically the :attr:`is_active`
|
||||
etc. This is the base class of the device hierarchy. It defines the basic
|
||||
services applicable to all devices (specifically the :attr:`is_active`
|
||||
property, the :attr:`value` property, and the :meth:`close` method).
|
||||
"""
|
||||
pin_factory = None # instance of a Factory sub-class
|
||||
_reservations = defaultdict(list) # maps pin addresses to lists of devices
|
||||
_res_lock = Lock()
|
||||
|
||||
def __repr__(self):
|
||||
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
|
||||
def value(self):
|
||||
"""
|
||||
@@ -378,14 +414,12 @@ class GPIODevice(Device):
|
||||
self._pin = None
|
||||
if pin is None:
|
||||
raise GPIOPinMissing('No pin given')
|
||||
if isinstance(pin, int):
|
||||
pin = pin_factory(pin)
|
||||
with _PINS_LOCK:
|
||||
if pin in _PINS:
|
||||
raise GPIOPinInUse(
|
||||
'pin %r is already in use by another gpiozero object' % pin
|
||||
)
|
||||
_PINS.add(pin)
|
||||
if isinstance(pin, Pin):
|
||||
self._reserve_pins(pin)
|
||||
else:
|
||||
# Check you can reserve *before* constructing the pin
|
||||
self._reserve_pins(Device.pin_factory.pin_address(pin))
|
||||
pin = Device.pin_factory.pin(pin)
|
||||
self._pin = pin
|
||||
self._active_state = True
|
||||
self._inactive_state = False
|
||||
@@ -402,12 +436,10 @@ class GPIODevice(Device):
|
||||
|
||||
def close(self):
|
||||
super(GPIODevice, self).close()
|
||||
with _PINS_LOCK:
|
||||
pin = self._pin
|
||||
if self._pin is not None:
|
||||
self._release_pins(self._pin)
|
||||
self._pin.close()
|
||||
self._pin = None
|
||||
if pin in _PINS:
|
||||
_PINS.remove(pin)
|
||||
pin.close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
@@ -441,3 +473,57 @@ class GPIODevice(Device):
|
||||
except DeviceClosed:
|
||||
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)
|
||||
|
||||
@@ -52,6 +52,24 @@ class SPIBadArgs(SPIError, ValueError):
|
||||
class SPIBadChannel(SPIError, ValueError):
|
||||
"Error raised when an invalid channel is given to an :class:`AnalogInputDevice`"
|
||||
|
||||
class SPIFixedClockMode(SPIError, AttributeError):
|
||||
"Error raised when the SPI clock mode cannot be changed"
|
||||
|
||||
class SPIInvalidClockMode(SPIError, ValueError):
|
||||
"Error raised when an invalid clock mode is given to an SPI implementation"
|
||||
|
||||
class SPIFixedBitOrder(SPIError, AttributeError):
|
||||
"Error raised when the SPI bit-endianness cannot be changed"
|
||||
|
||||
class SPIFixedSelect(SPIError, AttributeError):
|
||||
"Error raised when the SPI select polarity cannot be changed"
|
||||
|
||||
class SPIFixedWordSize(SPIError, AttributeError):
|
||||
"Error raised when the number of bits per word cannot be changed"
|
||||
|
||||
class SPIInvalidWordSize(SPIError, ValueError):
|
||||
"Error raised when an invalid (out of range) number of bits per word is specified"
|
||||
|
||||
class GPIODeviceError(GPIOZeroError):
|
||||
"Base class for errors specific to the GPIODevice hierarchy"
|
||||
|
||||
@@ -62,7 +80,7 @@ class GPIOPinInUse(GPIODeviceError):
|
||||
"Error raised when attempting to use a pin already in use by another device"
|
||||
|
||||
class GPIOPinMissing(GPIODeviceError, ValueError):
|
||||
"Error raised when a pin number is not specified"
|
||||
"Error raised when a pin specification is not given"
|
||||
|
||||
class InputDeviceError(GPIODeviceError):
|
||||
"Base class for errors specific to the InputDevice hierarchy"
|
||||
@@ -100,6 +118,12 @@ class PinFixedPull(PinError, AttributeError):
|
||||
class PinEdgeDetectUnsupported(PinError, AttributeError):
|
||||
"Error raised when attempting to use edge detection on unsupported pins"
|
||||
|
||||
class PinUnsupported(PinError, NotImplementedError):
|
||||
"Error raised when attempting to obtain a pin interface on unsupported pins"
|
||||
|
||||
class PinSPIUnsupported(PinError, NotImplementedError):
|
||||
"Error raised when attempting to obtain an SPI interface on unsupported pins"
|
||||
|
||||
class PinPWMError(PinError):
|
||||
"Base class for errors related to PWM implementations"
|
||||
|
||||
@@ -118,6 +142,9 @@ class PinMultiplePins(PinError, RuntimeError):
|
||||
class PinNoPins(PinError, RuntimeError):
|
||||
"Error raised when no pins support the requested function"
|
||||
|
||||
class PinInvalidPin(PinError, ValueError):
|
||||
"Error raised when an invalid pin specification is provided"
|
||||
|
||||
class GPIOZeroWarning(Warning):
|
||||
"Base class for all warnings in GPIO Zero"
|
||||
|
||||
@@ -130,6 +157,9 @@ class SPISoftwareFallback(SPIWarning):
|
||||
class PinWarning(GPIOZeroWarning):
|
||||
"Base class for warnings related to pin implementations"
|
||||
|
||||
class PinFactoryFallback(PinWarning):
|
||||
"Warning raised when a default pin factory fails to load and a fallback is tried"
|
||||
|
||||
class PinNonPhysical(PinWarning):
|
||||
"Warning raised when a non-physical pin is specified in a constructor"
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ class SmoothedInputDevice(EventsMixin, InputDevice):
|
||||
if self.partial or self._queue.full.is_set():
|
||||
return super(SmoothedInputDevice, self).__repr__()
|
||||
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)
|
||||
|
||||
@property
|
||||
@@ -240,7 +240,7 @@ class Button(HoldMixin, DigitalInputDevice):
|
||||
print("The button was pressed!")
|
||||
|
||||
: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.
|
||||
|
||||
:param bool pull_up:
|
||||
@@ -302,7 +302,7 @@ class LineSensor(SmoothedInputDevice):
|
||||
pause()
|
||||
|
||||
: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.
|
||||
|
||||
:param int queue_len:
|
||||
@@ -371,7 +371,7 @@ class MotionSensor(SmoothedInputDevice):
|
||||
print("Motion detected!")
|
||||
|
||||
: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.
|
||||
|
||||
:param int queue_len:
|
||||
@@ -435,7 +435,7 @@ class LightSensor(SmoothedInputDevice):
|
||||
print("Light detected!")
|
||||
|
||||
: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.
|
||||
|
||||
:param int queue_len:
|
||||
@@ -543,11 +543,11 @@ class DistanceSensor(SmoothedInputDevice):
|
||||
|
||||
:param int echo:
|
||||
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:
|
||||
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:
|
||||
The length of the queue used to store values read from the sensor.
|
||||
|
||||
@@ -69,11 +69,14 @@ class SourceMixin(object):
|
||||
super(SourceMixin, self).__init__(*args, **kwargs)
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.source = None
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
super(SourceMixin, self).close()
|
||||
except AttributeError:
|
||||
pass
|
||||
self.source = None
|
||||
|
||||
def _copy_values(self, source):
|
||||
for v in source:
|
||||
@@ -127,7 +130,7 @@ class SharedMixin(object):
|
||||
When :meth:`close` is called, an internal reference counter will be
|
||||
decremented and the instance will only close when it reaches zero.
|
||||
"""
|
||||
_INSTANCES = {}
|
||||
_instances = {}
|
||||
|
||||
def __del__(self):
|
||||
self._refs = 0
|
||||
@@ -438,23 +441,28 @@ class HoldThread(GPIOThread):
|
||||
device is active.
|
||||
"""
|
||||
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.start()
|
||||
|
||||
def held(self, parent):
|
||||
while not self.stopping.is_set():
|
||||
if self.holding.wait(0.1):
|
||||
self.holding.clear()
|
||||
while not (
|
||||
self.stopping.is_set() or
|
||||
parent._inactive_event.wait(parent.hold_time)
|
||||
):
|
||||
if parent._held_from is None:
|
||||
parent._held_from = time()
|
||||
parent._fire_held()
|
||||
if not parent.hold_repeat:
|
||||
break
|
||||
try:
|
||||
while not self.stopping.is_set():
|
||||
if self.holding.wait(0.1):
|
||||
self.holding.clear()
|
||||
while not (
|
||||
self.stopping.is_set() or
|
||||
parent._inactive_event.wait(parent.hold_time)
|
||||
):
|
||||
if parent._held_from is None:
|
||||
parent._held_from = time()
|
||||
parent._fire_held()
|
||||
if not parent.hold_repeat:
|
||||
break
|
||||
except ReferenceError:
|
||||
# Parent is dead; time to die!
|
||||
pass
|
||||
|
||||
|
||||
class GPIOQueue(GPIOThread):
|
||||
|
||||
@@ -56,7 +56,7 @@ class PingServer(InternalDevice):
|
||||
self._fire_events()
|
||||
|
||||
def __repr__(self):
|
||||
return '<gpiozero.PingDevice host="%s">' % self.host
|
||||
return '<gpiozero.PingServer host="%s">' % self.host
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
|
||||
@@ -128,8 +128,8 @@ class DigitalOutputDevice(OutputDevice):
|
||||
"""
|
||||
def __init__(self, pin=None, active_high=True, initial_value=False):
|
||||
self._blink_thread = None
|
||||
super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value)
|
||||
self._controller = None
|
||||
super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
@@ -217,7 +217,7 @@ class LED(DigitalOutputDevice):
|
||||
led.on()
|
||||
|
||||
: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.
|
||||
|
||||
:param bool active_high:
|
||||
@@ -252,7 +252,7 @@ class Buzzer(DigitalOutputDevice):
|
||||
bz.on()
|
||||
|
||||
: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.
|
||||
|
||||
:param bool active_high:
|
||||
@@ -276,7 +276,7 @@ class PWMOutputDevice(OutputDevice):
|
||||
Generic output device configured for pulse-width modulation (PWM).
|
||||
|
||||
:param int pin:
|
||||
The GPIO pin which the device is attached to. See :ref:`pin_numbering`
|
||||
The GPIO pin which the device is attached to. See :ref:`pin-numbering`
|
||||
for valid pin numbers.
|
||||
|
||||
:param bool active_high:
|
||||
@@ -483,7 +483,7 @@ class PWMLED(PWMOutputDevice):
|
||||
an optional resistor to prevent the LED from burning out.
|
||||
|
||||
:param int pin:
|
||||
The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for
|
||||
The GPIO pin which the LED is attached to. See :ref:`pin-numbering` for
|
||||
valid pin numbers.
|
||||
|
||||
:param bool active_high:
|
||||
@@ -926,7 +926,7 @@ class Servo(SourceMixin, CompositeDevice):
|
||||
sleep(1)
|
||||
|
||||
: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.
|
||||
|
||||
:param float initial_value:
|
||||
@@ -1116,7 +1116,7 @@ class AngularServo(Servo):
|
||||
expectations of minimum and maximum.
|
||||
|
||||
: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.
|
||||
|
||||
:param float initial_angle:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
@@ -6,39 +8,137 @@ from __future__ import (
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import io
|
||||
|
||||
from .data import pi_info
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinUnsupported,
|
||||
PinSPIUnsupported,
|
||||
PinPWMUnsupported,
|
||||
PinEdgeDetectUnsupported,
|
||||
SPIFixedClockMode,
|
||||
SPIFixedBitOrder,
|
||||
SPIFixedSelect,
|
||||
SPIFixedWordSize,
|
||||
)
|
||||
|
||||
|
||||
PINS_CLEANUP = []
|
||||
def _pins_shutdown():
|
||||
for routine in PINS_CLEANUP:
|
||||
routine()
|
||||
class Factory(object):
|
||||
"""
|
||||
Generates pins, SPI, and I2C interfaces for devices. This is an abstract
|
||||
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):
|
||||
"""
|
||||
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
|
||||
represent the capabilities of pins. The following functions *must* be
|
||||
overridden:
|
||||
represent the capabilities of pins. Descendents *must* override the
|
||||
following methods:
|
||||
|
||||
* :meth:`_get_address`
|
||||
* :meth:`_get_function`
|
||||
* :meth:`_set_function`
|
||||
* :meth:`_get_state`
|
||||
|
||||
The following functions *may* be overridden if applicable:
|
||||
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`
|
||||
@@ -50,20 +150,10 @@ class Pin(object):
|
||||
* :meth:`_set_edges`
|
||||
* :meth:`_get_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):
|
||||
return "Abstract pin"
|
||||
return self.address[-1]
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
@@ -105,6 +195,18 @@ class Pin(object):
|
||||
self.function = 'input'
|
||||
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):
|
||||
return "input"
|
||||
|
||||
@@ -140,10 +242,19 @@ class Pin(object):
|
||||
doc="""\
|
||||
The state of the pin. This is 0 for low, and 1 for high. As a low level
|
||||
view of the pin, no swapping is performed in the case of pull ups (see
|
||||
:attr:`pull` for more information).
|
||||
:attr:`pull` for more information):
|
||||
|
||||
If PWM is currently active (when :attr:`frequency` is not ``None``),
|
||||
this represents the PWM duty cycle as a value between 0.0 and 1.0.
|
||||
.. code-block:: text
|
||||
|
||||
HIGH - - - - > ,----------------------
|
||||
|
|
||||
|
|
||||
LOW ----------------'
|
||||
|
||||
Descendents which implement analog, or analog-like capabilities can
|
||||
return values between 0 and 1. For example, pins implementing PWM
|
||||
(where :attr:`frequency` is not ``None``) return a value between 0.0
|
||||
and 1.0 representing the current PWM duty cycle.
|
||||
|
||||
If a pin is currently configured for input, and an attempt is made to
|
||||
set this attribute, :exc:`PinSetInput` will be raised. If an invalid
|
||||
@@ -205,6 +316,26 @@ class Pin(object):
|
||||
detection, measured in seconds. If bounce detection is not currently in
|
||||
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
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`. If the pin
|
||||
supports edge detection, the class must implement bounce detection,
|
||||
@@ -223,7 +354,18 @@ class Pin(object):
|
||||
doc="""\
|
||||
The edge that will trigger execution of the function or bound method
|
||||
assigned to :attr:`when_changed`. This can be one of the strings
|
||||
"both" (the default), "rising", "falling", or "none".
|
||||
"both" (the default), "rising", "falling", or "none":
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
HIGH - - - - > ,--------------.
|
||||
| |
|
||||
| |
|
||||
LOW --------------------' `--------------
|
||||
: :
|
||||
: :
|
||||
Fires when_changed "both" "both"
|
||||
when edges is ... "rising" "falling"
|
||||
|
||||
If the pin does not support edge detection, attempts to set this
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`.
|
||||
@@ -247,48 +389,303 @@ class Pin(object):
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
class LocalPin(Pin):
|
||||
class SPI(object):
|
||||
"""
|
||||
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`).
|
||||
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
|
||||
"""
|
||||
_PI_REVISION = None
|
||||
|
||||
@classmethod
|
||||
def pi_info(cls):
|
||||
def read(self, n):
|
||||
"""
|
||||
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)
|
||||
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.
|
||||
""")
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ from __future__ import (
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
@@ -14,7 +13,7 @@ from itertools import cycle
|
||||
from operator import attrgetter
|
||||
from collections import namedtuple
|
||||
|
||||
from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins
|
||||
from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins, PinInvalidPin
|
||||
|
||||
|
||||
# Some useful constants for describing pins
|
||||
@@ -120,8 +119,8 @@ A_BOARD = """\
|
||||
|
||||
BPLUS_BOARD = """\
|
||||
{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}| {P1:{style} col1}{style:white on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:bold}Pi Model {model: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}
|
||||
@@ -135,8 +134,8 @@ BPLUS_BOARD = """\
|
||||
|
||||
APLUS_BOARD = """\
|
||||
{style:white on green},--------------------------.{style:reset}
|
||||
{style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset}
|
||||
{style:white on green}| {P1:{style} col1}{style:white on green} |{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} |{style:reset}
|
||||
{style:white on green}| |{style:reset}
|
||||
{style:white on green}| {style:bold}Pi Model {model: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}
|
||||
@@ -150,20 +149,20 @@ APLUS_BOARD = """\
|
||||
|
||||
ZERO12_BOARD = """\
|
||||
{style:white on green},-------------------------.{style:reset}
|
||||
{style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset}
|
||||
{style:white on green}| {P1:{style} col1}{style:white on green} |{style:reset}
|
||||
{style:black on white}---+{style:white on green} {style:on black}+----+{style:on green} {style:bold}PiZero{style:normal} |{style:reset}
|
||||
{style:black on white} sd|{style:white on green} {style:on black}|SoC |{style:on green} {style:bold}V{pcb_revision:3s}{style:normal} |{style:reset}
|
||||
{style:black on white}---+|hdmi|{style:white on green} {style:on black}+----+{style:on green} {style:black on white}usb{style:on green} {style:black on white}pwr{style:white on green} |{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} |{style:reset}
|
||||
{style:black on white}---+{style:white on green} {style:on black}+---+{style:on green} {style:bold}PiZero{style:normal} |{style:reset}
|
||||
{style:black on white} sd|{style:white on green} {style:on black}|SoC|{style:on green} {style:bold}V{pcb_revision:3s}{style:normal} |{style:reset}
|
||||
{style:black on white}---+|hdmi|{style:white on green} {style:on black}+---+{style:on green} {style:black on white}usb{style:on green} {style:black on white}pwr{style:white on green} |{style:reset}
|
||||
{style:white on green}`---{style:black on white}| |{style:white on green}--------{style:black on white}| |{style:white on green}-{style:black on white}| |{style:white on green}-'{style:reset}"""
|
||||
|
||||
ZERO13_BOARD = """\
|
||||
{style:white on green}.-------------------------.{style:reset}
|
||||
{style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset}
|
||||
{style:white on green}| {P1:{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} sd|{style:white on green} {style:on black}|SoC |{style:on green} {style:bold}V{pcb_revision:3s}{style:normal} {style:black on white}|i{style:reset}
|
||||
{style:black on white}---+|hdmi|{style:white on green} {style:on black}+----+{style:on green} {style:black on white}usb{style:on green} {style:on white}pwr{style:white on green} |{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} {style:black on white}|c{style:reset}
|
||||
{style:black on white}---+{style:white on green} {style:on black}+---+{style:on green} {style:bold}Pi{model:6s}{style:normal}{style:black on white}|s{style:reset}
|
||||
{style:black on white} sd|{style:white on green} {style:on black}|SoC|{style:on green} {style:bold}V{pcb_revision:3s}{style:normal} {style:black on white}|i{style:reset}
|
||||
{style:black on white}---+|hdmi|{style:white on green} {style:on black}+---+{style:on green} {style:black on white}usb{style:on green} {style:on white}pwr{style:white on green} |{style:reset}
|
||||
{style:white on green}`---{style:black on white}| |{style:white on green}--------{style:black on white}| |{style:white on green}-{style:black on white}| |{style:white on green}-'{style:reset}"""
|
||||
|
||||
CM_BOARD = """\
|
||||
@@ -217,7 +216,7 @@ REV2_P5 = {
|
||||
7: (GND, False), 8: (GND, False),
|
||||
}
|
||||
|
||||
PLUS_P1 = {
|
||||
PLUS_J8 = {
|
||||
1: (V3_3, False), 2: (V5, False),
|
||||
3: (GPIO2, True), 4: (V5, False),
|
||||
5: (GPIO3, True), 6: (GND, False),
|
||||
@@ -344,6 +343,23 @@ CM_SODIMM = {
|
||||
199: ('VBAT', False), 200: ('VBAT', False),
|
||||
}
|
||||
|
||||
CM3_SODIMM = CM_SODIMM.copy()
|
||||
CM3_SODIMM.update({
|
||||
4: ('NC / SDX VREF', False),
|
||||
6: ('NC / SDX VREF', False),
|
||||
8: (GND, False),
|
||||
10: ('NC / SDX CLK', False),
|
||||
12: ('NC / SDX CMD', False),
|
||||
14: (GND, False),
|
||||
16: ('NC / SDX D0', False),
|
||||
18: ('NC / SDX D1', False),
|
||||
20: (GND, False),
|
||||
22: ('NC / SDX D2', False),
|
||||
24: ('NC / SDX D3', False),
|
||||
88: ('HDMI HPD N 1V8', False),
|
||||
90: ('EMMC EN N 1V8', False),
|
||||
})
|
||||
|
||||
# The following data is sourced from a combination of the following locations:
|
||||
#
|
||||
# http://elinux.org/RPi_HardwareHistory
|
||||
@@ -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, ),
|
||||
0xe: ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD, ),
|
||||
0xf: ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD, ),
|
||||
0x10: ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'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, ),
|
||||
0x12: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, APLUS_BOARD, ),
|
||||
0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, BPLUS_BOARD, ),
|
||||
0x12: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'J8': PLUS_J8}, APLUS_BOARD, ),
|
||||
0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'J8': PLUS_J8}, BPLUS_BOARD, ),
|
||||
0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Embest', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, CM_BOARD, ),
|
||||
0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Embest', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'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 *
|
||||
|
||||
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: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
|
||||
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
|
||||
|
||||
.. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
The following attributes are defined:
|
||||
|
||||
.. automethod:: pprint
|
||||
@@ -532,7 +548,7 @@ class HeaderInfo(namedtuple('HeaderInfo', (
|
||||
.. attribute:: name
|
||||
|
||||
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
|
||||
|
||||
@@ -545,6 +561,8 @@ class HeaderInfo(namedtuple('HeaderInfo', (
|
||||
.. attribute:: pins
|
||||
|
||||
A dictionary mapping physical pin numbers to :class:`PinInfo` tuples.
|
||||
|
||||
.. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
"""
|
||||
__slots__ = () # workaround python issue #24931
|
||||
|
||||
@@ -563,7 +581,6 @@ class HeaderInfo(namedtuple('HeaderInfo', (
|
||||
def _format_full(self, style):
|
||||
Cell = namedtuple('Cell', ('content', 'align', 'style'))
|
||||
|
||||
pin_digits = len(str(self.rows * self.columns))
|
||||
lines = []
|
||||
for row in range(self.rows):
|
||||
line = []
|
||||
@@ -643,7 +660,6 @@ class HeaderInfo(namedtuple('HeaderInfo', (
|
||||
print('{0:{style} full}'.format(self, style=Style(color)))
|
||||
|
||||
|
||||
|
||||
class PiBoardInfo(namedtuple('PiBoardInfo', (
|
||||
'revision',
|
||||
'model',
|
||||
@@ -671,6 +687,7 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
|
||||
|
||||
from gpiozero import *
|
||||
|
||||
print('{0}'.format(pi_info()))
|
||||
print('{0:full}'.format(pi_info()))
|
||||
print('{0:board}'.format(pi_info()))
|
||||
print('{0:specs}'.format(pi_info()))
|
||||
@@ -787,8 +804,8 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
|
||||
|
||||
A dictionary which maps header labels to :class:`HeaderInfo` tuples.
|
||||
For example, to obtain information about header P1 you would query
|
||||
``headers['P1']``. To obtain information about pin 12 on header P1 you
|
||||
would query ``headers['P1'].pins[12]``.
|
||||
``headers['P1']``. To obtain information about pin 12 on header J8 you
|
||||
would query ``headers['J8'].pins[12]``.
|
||||
|
||||
A rendered version of this data can be obtained by using the
|
||||
:class:`PiBoardInfo` object in a format string::
|
||||
@@ -821,96 +838,120 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
|
||||
# uuuuuuuu - Unused
|
||||
# F - New flag (1=valid new-style revision, 0=old-style)
|
||||
# MMM - Memory size (0=256, 1=512, 2=1024)
|
||||
# CCCC - Manufacturer (0=Sony, 1=Egoman, 2=Embest)
|
||||
# CCCC - Manufacturer (0=Sony, 1=Egoman, 2=Embest, 3=Sony Japan)
|
||||
# PPPP - Processor (0=2835, 1=2836, 2=2837)
|
||||
# TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM, 8=3B, 9=Zero)
|
||||
# TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM,
|
||||
# 8=3B, 9=Zero, 10=CM3, 12=Zero W)
|
||||
# RRRR - Revision (0, 1, 2, etc.)
|
||||
revcode_memory = (revision & 0x700000) >> 20
|
||||
revcode_manufacturer = (revision & 0xf0000) >> 16
|
||||
revcode_processor = (revision & 0xf000) >> 12
|
||||
revcode_type = (revision & 0xff0) >> 4
|
||||
revcode_revision = (revision & 0x0f)
|
||||
try:
|
||||
model = {
|
||||
0: 'A',
|
||||
1: 'B',
|
||||
2: 'A+',
|
||||
3: 'B+',
|
||||
4: '2B',
|
||||
6: 'CM',
|
||||
8: '3B',
|
||||
9: 'Zero',
|
||||
}[(revision & 0xff0) >> 4]
|
||||
0: 'A',
|
||||
1: 'B',
|
||||
2: 'A+',
|
||||
3: 'B+',
|
||||
4: '2B',
|
||||
6: 'CM',
|
||||
8: '3B',
|
||||
9: 'Zero',
|
||||
10: 'CM3',
|
||||
12: 'Zero W',
|
||||
}[revcode_type]
|
||||
if model in ('A', 'B'):
|
||||
pcb_revision = {
|
||||
0: '1.0', # is this right?
|
||||
1: '1.0',
|
||||
2: '2.0',
|
||||
}[revision & 0x0f]
|
||||
}.get(revcode_revision, 'Unknown')
|
||||
else:
|
||||
pcb_revision = '1.%d' % (revision & 0x0f)
|
||||
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]
|
||||
pcb_revision = '1.%d' % revcode_revision
|
||||
soc = {
|
||||
0: 'BCM2835',
|
||||
1: 'BCM2836',
|
||||
2: 'BCM2837',
|
||||
}[(revision & 0xf000) >> 12]
|
||||
}.get(revcode_processor, 'Unknown')
|
||||
manufacturer = {
|
||||
0: 'Sony',
|
||||
1: 'Egoman',
|
||||
2: 'Embest',
|
||||
}[(revision & 0xf0000) >> 16]
|
||||
3: 'Sony Japan',
|
||||
}.get(revcode_manufacturer, 'Unknown')
|
||||
memory = {
|
||||
0: 256,
|
||||
1: 512,
|
||||
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 = {
|
||||
'A': 'SD',
|
||||
'B': 'SD',
|
||||
'CM': 'eMMC',
|
||||
'A': 'SD',
|
||||
'B': 'SD',
|
||||
'CM': 'eMMC',
|
||||
'CM3': 'eMMC / off-board',
|
||||
}.get(model, 'MicroSD')
|
||||
usb = {
|
||||
'A': 1,
|
||||
'A+': 1,
|
||||
'Zero': 1,
|
||||
'B': 2,
|
||||
'CM': 1,
|
||||
'A': 1,
|
||||
'A+': 1,
|
||||
'Zero': 1,
|
||||
'Zero W': 1,
|
||||
'B': 2,
|
||||
'CM': 0,
|
||||
'CM3': 1,
|
||||
}.get(model, 4)
|
||||
ethernet = {
|
||||
'A': 0,
|
||||
'A+': 0,
|
||||
'Zero': 0,
|
||||
'CM': 0,
|
||||
'A': 0,
|
||||
'A+': 0,
|
||||
'Zero': 0,
|
||||
'Zero W': 0,
|
||||
'CM': 0,
|
||||
'CM3': 0,
|
||||
}.get(model, 1)
|
||||
wifi = {
|
||||
'3B': True,
|
||||
'3B': True,
|
||||
'Zero W': True,
|
||||
}.get(model, False)
|
||||
bluetooth = {
|
||||
'3B': True,
|
||||
'3B': True,
|
||||
'Zero W': True,
|
||||
}.get(model, False)
|
||||
csi = {
|
||||
'Zero': 0 if pcb_revision == '1.2' else 1,
|
||||
'CM': 2,
|
||||
'Zero': 0 if pcb_revision == '1.0' else 1,
|
||||
'Zero W': 1,
|
||||
'CM': 2,
|
||||
'CM3': 2,
|
||||
}.get(model, 1)
|
||||
dsi = {
|
||||
'Zero': 0,
|
||||
'Zero': 0,
|
||||
'Zero W': 0,
|
||||
}.get(model, csi)
|
||||
headers = {
|
||||
'A': {'P1': REV2_P1, 'P5': REV2_P5},
|
||||
'B': {'P1': REV1_P1} if pcb_revision == '1.0' else {'P1': REV2_P1, 'P5': REV2_P5},
|
||||
'CM': {'SODIMM': CM_SODIMM},
|
||||
}.get(model, {'P1': PLUS_P1})
|
||||
'A': {'P1': REV2_P1, 'P5': REV2_P5},
|
||||
'B': {'P1': REV1_P1} if pcb_revision == '1.0' else {'P1': REV2_P1, 'P5': REV2_P5},
|
||||
'CM': {'SODIMM': CM_SODIMM},
|
||||
'CM3': {'SODIMM': CM3_SODIMM},
|
||||
}.get(model, {'J8': PLUS_J8})
|
||||
board = {
|
||||
'A': A_BOARD,
|
||||
'B': REV1_BOARD if pcb_revision == '1.0' else REV2_BOARD,
|
||||
'A+': APLUS_BOARD,
|
||||
'CM': CM_BOARD,
|
||||
'Zero': ZERO12_BOARD if pcb_revision == '1.2' else ZERO13_BOARD,
|
||||
'A': A_BOARD,
|
||||
'B': REV1_BOARD if pcb_revision == '1.0' else REV2_BOARD,
|
||||
'A+': APLUS_BOARD,
|
||||
'CM': CM_BOARD,
|
||||
'CM3': CM_BOARD,
|
||||
'Zero': ZERO12_BOARD if pcb_revision == '1.2' else ZERO13_BOARD,
|
||||
'Zero W': ZERO13_BOARD,
|
||||
}.get(model, BPLUS_BOARD)
|
||||
except KeyError:
|
||||
raise PinUnknownPi('unable to parse new-style revision "%x"' % revision)
|
||||
@@ -1077,8 +1118,8 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
|
||||
"""
|
||||
Pretty-print a representation of the board along with header diagrams.
|
||||
|
||||
If *color* is ``None`` (the default, the diagram will include ANSI
|
||||
color codes if stdout is a color-capable terminal). Otherwise *color*
|
||||
If *color* is ``None`` (the default), the diagram will include ANSI
|
||||
color codes if stdout is a color-capable terminal. Otherwise *color*
|
||||
can be set to ``True`` or ``False`` to force color or monochrome
|
||||
output.
|
||||
"""
|
||||
@@ -1096,13 +1137,10 @@ def pi_info(revision=None):
|
||||
the model of Pi it is running on and return information about that.
|
||||
"""
|
||||
if revision is None:
|
||||
# NOTE: This import is declared locally for two reasons. Firstly it
|
||||
# avoids a circular dependency (devices->pins->pins.data->devices).
|
||||
# Secondly, pin_factory is one global which might potentially be
|
||||
# re-written by a user's script at runtime hence we should re-import
|
||||
# here in case it's changed since initialization
|
||||
from ..devices import pin_factory
|
||||
result = pin_factory.pi_info()
|
||||
# The reason this import is located here is to avoid a circular
|
||||
# dependency; devices->pins.local->pins.data->devices
|
||||
from ..devices import Device
|
||||
result = Device.pin_factory.pi_info
|
||||
if result is None:
|
||||
raise PinUnknownPi('The default pin_factory is not attached to a Pi')
|
||||
else:
|
||||
|
||||
245
gpiozero/pins/local.py
Normal 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,)
|
||||
|
||||
@@ -7,6 +7,7 @@ from __future__ import (
|
||||
str = type('')
|
||||
|
||||
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from time import time, sleep
|
||||
from threading import Thread, Event
|
||||
@@ -15,56 +16,36 @@ try:
|
||||
except ImportError:
|
||||
from ..compat import isclose
|
||||
|
||||
from . import Pin
|
||||
from .data import pi_info
|
||||
from ..exc import PinSetInput, PinPWMUnsupported, PinFixedPull
|
||||
import pkg_resources
|
||||
|
||||
from ..exc import (
|
||||
PinPWMUnsupported,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinInvalidFunction,
|
||||
PinInvalidPull,
|
||||
)
|
||||
from ..devices import Device
|
||||
from .pi import PiPin
|
||||
from .local import LocalPiFactory
|
||||
|
||||
|
||||
PinState = namedtuple('PinState', ('timestamp', 'state'))
|
||||
|
||||
class MockPin(Pin):
|
||||
class MockPin(PiPin):
|
||||
"""
|
||||
A mock pin used primarily for testing. This class does *not* support PWM.
|
||||
"""
|
||||
|
||||
_PINS = {}
|
||||
|
||||
@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._state = False
|
||||
self._pull = 'floating'
|
||||
self._bounce = None
|
||||
self._edges = 'both'
|
||||
self._when_changed = None
|
||||
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 __init__(self, factory, number):
|
||||
super(MockPin, self).__init__(factory, number)
|
||||
self._function = 'input'
|
||||
self._pull = 'up' if factory.pi_info.pulled_up(self.address[-1]) else 'floating'
|
||||
self._state = self._pull == 'up'
|
||||
self._bounce = None
|
||||
self._edges = 'both'
|
||||
self._when_changed = None
|
||||
self.clear_states()
|
||||
|
||||
def close(self):
|
||||
self.when_changed = None
|
||||
@@ -74,7 +55,8 @@ class MockPin(Pin):
|
||||
return self._function
|
||||
|
||||
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
|
||||
if value == 'input':
|
||||
# Drive the input to the pull
|
||||
@@ -110,8 +92,12 @@ class MockPin(Pin):
|
||||
return self._pull
|
||||
|
||||
def _set_pull(self, value):
|
||||
assert self._function == 'input'
|
||||
assert value in ('floating', 'up', 'down')
|
||||
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)
|
||||
if value not in ('floating', 'up', 'down'):
|
||||
raise PinInvalidPull('pull must be floating, up, or down')
|
||||
self._pull = value
|
||||
if value == 'up':
|
||||
self.drive_high()
|
||||
@@ -169,14 +155,23 @@ class MockPin(Pin):
|
||||
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
|
||||
resistor.
|
||||
This derivative of :class:`MockPin` emulates a pin connected to another
|
||||
mock pin. This is used in the "real pins" portion of the test suite to
|
||||
check that one pin can influence another.
|
||||
"""
|
||||
def _set_pull(self, value):
|
||||
if value != 'up':
|
||||
raise PinFixedPull('pin has a physical pull-up resistor')
|
||||
def __init__(self, factory, number, input_pin=None):
|
||||
super(MockConnectedPin, self).__init__(factory, number)
|
||||
self.input_pin = input_pin
|
||||
|
||||
def _change_state(self, value):
|
||||
if self.input_pin:
|
||||
if value:
|
||||
self.input_pin.drive_high()
|
||||
else:
|
||||
self.input_pin.drive_low()
|
||||
return super(MockConnectedPin, self)._change_state(value)
|
||||
|
||||
|
||||
class MockChargingPin(MockPin):
|
||||
@@ -186,9 +181,9 @@ class MockChargingPin(MockPin):
|
||||
(as if attached to, e.g. a typical circuit using an LDR and a capacitor
|
||||
to time the charging rate).
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockChargingPin, self).__init__()
|
||||
self.charge_time = 0.01 # dark charging time
|
||||
def __init__(self, factory, number, charge_time=0.01):
|
||||
super(MockChargingPin, self).__init__(factory, number)
|
||||
self.charge_time = charge_time # dark charging time
|
||||
self._charge_stop = Event()
|
||||
self._charge_thread = None
|
||||
|
||||
@@ -225,10 +220,10 @@ class MockTriggerPin(MockPin):
|
||||
corresponding pin instance. When this pin is driven high it will trigger
|
||||
the echo pin to drive high for the echo time.
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockTriggerPin, self).__init__()
|
||||
self.echo_pin = None
|
||||
self.echo_time = 0.04 # longest echo time
|
||||
def __init__(self, factory, number, echo_pin=None, echo_time=0.04):
|
||||
super(MockTriggerPin, self).__init__(factory, number)
|
||||
self.echo_pin = echo_pin
|
||||
self.echo_time = echo_time # longest echo time
|
||||
self._echo_thread = None
|
||||
|
||||
def _set_state(self, value):
|
||||
@@ -250,8 +245,8 @@ class MockPWMPin(MockPin):
|
||||
"""
|
||||
This derivative of :class:`MockPin` adds PWM support.
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockPWMPin, self).__init__()
|
||||
def __init__(self, factory, number):
|
||||
super(MockPWMPin, self).__init__(factory, number)
|
||||
self._frequency = None
|
||||
|
||||
def close(self):
|
||||
@@ -283,8 +278,8 @@ class MockSPIClockPin(MockPin):
|
||||
rather, construct a :class:`MockSPIDevice` with various pin numbers, and
|
||||
this class will be used for the clock pin.
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockSPIClockPin, self).__init__()
|
||||
def __init__(self, factory, number):
|
||||
super(MockSPIClockPin, self).__init__(factory, number)
|
||||
if not hasattr(self, 'spi_devices'):
|
||||
self.spi_devices = []
|
||||
|
||||
@@ -301,8 +296,8 @@ class MockSPISelectPin(MockPin):
|
||||
tests; rather, construct a :class:`MockSPIDevice` with various pin numbers,
|
||||
and this class will be used for the select pin.
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockSPISelectPin, self).__init__()
|
||||
def __init__(self, factory, number):
|
||||
super(MockSPISelectPin, self).__init__(factory, number)
|
||||
if not hasattr(self, 'spi_device'):
|
||||
self.spi_device = None
|
||||
|
||||
@@ -314,13 +309,13 @@ class MockSPISelectPin(MockPin):
|
||||
|
||||
class MockSPIDevice(object):
|
||||
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,
|
||||
bits_per_word=8, select_high=False):
|
||||
self.clock_pin = MockSPIClockPin(clock_pin)
|
||||
self.mosi_pin = None if mosi_pin is None else MockPin(mosi_pin)
|
||||
self.miso_pin = None if miso_pin is None else MockPin(miso_pin)
|
||||
self.select_pin = None if select_pin is None else MockSPISelectPin(select_pin)
|
||||
self.clock_pin = Device.pin_factory.pin(clock_pin, pin_class=MockSPIClockPin)
|
||||
self.mosi_pin = None if mosi_pin is None else Device.pin_factory.pin(mosi_pin)
|
||||
self.miso_pin = None if miso_pin is None else Device.pin_factory.pin(miso_pin)
|
||||
self.select_pin = None if select_pin is None else Device.pin_factory.pin(select_pin, pin_class=MockSPISelectPin)
|
||||
self.clock_polarity = clock_polarity
|
||||
self.clock_phase = clock_phase
|
||||
self.lsb_first = lsb_first
|
||||
@@ -413,3 +408,42 @@ class MockSPIDevice(object):
|
||||
bits = reversed(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
|
||||
|
||||
|
||||
@@ -17,16 +17,13 @@ from time import sleep
|
||||
from threading import Thread, Event, Lock
|
||||
from collections import Counter
|
||||
|
||||
from . import LocalPin, PINS_CLEANUP
|
||||
from .data import pi_info
|
||||
from .local import LocalPiPin, LocalPiFactory
|
||||
from ..exc import (
|
||||
PinInvalidPull,
|
||||
PinInvalidEdges,
|
||||
PinInvalidFunction,
|
||||
PinFixedPull,
|
||||
PinSetInput,
|
||||
PinNonPhysical,
|
||||
PinNoPins,
|
||||
)
|
||||
|
||||
|
||||
@@ -149,7 +146,7 @@ class GPIOFS(object):
|
||||
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
|
||||
pins. This is the default pin implementation if no third-party libraries
|
||||
@@ -169,10 +166,17 @@ class NativePin(LocalPin):
|
||||
|
||||
led = LED(NativePin(12))
|
||||
"""
|
||||
def __init__(self):
|
||||
super(NativeFactory, self).__init__()
|
||||
self.mem = GPIOMemory()
|
||||
self.pin_class = NativePin
|
||||
|
||||
_MEM = None
|
||||
_PINS = {}
|
||||
def close(self):
|
||||
super(NativeFactory, self).close()
|
||||
self.mem.close()
|
||||
|
||||
|
||||
class NativePin(LocalPiPin):
|
||||
GPIO_FUNCTIONS = {
|
||||
'input': 0b000,
|
||||
'output': 0b001,
|
||||
@@ -202,89 +206,62 @@ class NativePin(LocalPin):
|
||||
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()}
|
||||
|
||||
PI_INFO = None
|
||||
|
||||
def __new__(cls, number):
|
||||
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._set_offset = self._MEM.GPSET_OFFSET + (number // 32)
|
||||
self._set_shift = number % 32
|
||||
self._clear_offset = self._MEM.GPCLR_OFFSET + (number // 32)
|
||||
self._clear_shift = number % 32
|
||||
self._level_offset = self._MEM.GPLEV_OFFSET + (number // 32)
|
||||
self._level_shift = number % 32
|
||||
self._pull_offset = self._MEM.GPPUDCLK_OFFSET + (number // 32)
|
||||
self._pull_shift = number % 32
|
||||
self._edge_offset = self._MEM.GPEDS_OFFSET + (number // 32)
|
||||
self._edge_shift = number % 32
|
||||
self._rising_offset = self._MEM.GPREN_OFFSET + (number // 32)
|
||||
self._rising_shift = number % 32
|
||||
self._falling_offset = self._MEM.GPFEN_OFFSET + (number // 32)
|
||||
self._falling_shift = number % 32
|
||||
self._when_changed = None
|
||||
self._change_thread = None
|
||||
self._change_event = Event()
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating'
|
||||
self.bounce = None
|
||||
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 __init__(self, factory, number):
|
||||
super(NativePin, self).__init__(factory, number)
|
||||
self._func_offset = self.factory.mem.GPFSEL_OFFSET + (number // 10)
|
||||
self._func_shift = (number % 10) * 3
|
||||
self._set_offset = self.factory.mem.GPSET_OFFSET + (number // 32)
|
||||
self._set_shift = number % 32
|
||||
self._clear_offset = self.factory.mem.GPCLR_OFFSET + (number // 32)
|
||||
self._clear_shift = number % 32
|
||||
self._level_offset = self.factory.mem.GPLEV_OFFSET + (number // 32)
|
||||
self._level_shift = number % 32
|
||||
self._pull_offset = self.factory.mem.GPPUDCLK_OFFSET + (number // 32)
|
||||
self._pull_shift = number % 32
|
||||
self._edge_offset = self.factory.mem.GPEDS_OFFSET + (number // 32)
|
||||
self._edge_shift = number % 32
|
||||
self._rising_offset = self.factory.mem.GPREN_OFFSET + (number // 32)
|
||||
self._rising_shift = number % 32
|
||||
self._falling_offset = self.factory.mem.GPFEN_OFFSET + (number // 32)
|
||||
self._falling_shift = number % 32
|
||||
self._when_changed = None
|
||||
self._change_thread = None
|
||||
self._change_event = Event()
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if factory.pi_info.pulled_up(self.address[-1]) else 'floating'
|
||||
self.bounce = None
|
||||
self.edges = 'both'
|
||||
|
||||
def close(self):
|
||||
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'
|
||||
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._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):
|
||||
try:
|
||||
value = self.GPIO_FUNCTIONS[value]
|
||||
except KeyError:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
self._MEM[self._func_offset] = (
|
||||
self._MEM[self._func_offset]
|
||||
self.factory.mem[self._func_offset] = (
|
||||
self.factory.mem[self._func_offset]
|
||||
& ~(7 << self._func_shift)
|
||||
| (value << self._func_shift)
|
||||
)
|
||||
|
||||
def _get_state(self):
|
||||
return bool(self._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):
|
||||
if self.function == 'input':
|
||||
raise PinSetInput('cannot set state of pin %r' % self)
|
||||
if value:
|
||||
self._MEM[self._set_offset] = 1 << self._set_shift
|
||||
self.factory.mem[self._set_offset] = 1 << self._set_shift
|
||||
else:
|
||||
self._MEM[self._clear_offset] = 1 << self._clear_shift
|
||||
self.factory.mem[self._clear_offset] = 1 << self._clear_shift
|
||||
|
||||
def _get_pull(self):
|
||||
return self.GPIO_PULL_UP_NAMES[self._pull]
|
||||
@@ -292,23 +269,23 @@ class NativePin(LocalPin):
|
||||
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):
|
||||
if value != 'up' and self.factory.pi_info.pulled_up(self.address[-1]):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
value = self.GPIO_PULL_UPS[value]
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull direction "%s" for pin %r' % (value, self))
|
||||
self._MEM[self._MEM.GPPUD_OFFSET] = value
|
||||
self.factory.mem[self.factory.mem.GPPUD_OFFSET] = value
|
||||
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)
|
||||
self._MEM[self._MEM.GPPUD_OFFSET] = 0
|
||||
self._MEM[self._pull_offset] = 0
|
||||
self.factory.mem[self.factory.mem.GPPUD_OFFSET] = 0
|
||||
self.factory.mem[self._pull_offset] = 0
|
||||
self._pull = value
|
||||
|
||||
def _get_edges(self):
|
||||
rising = bool(self._MEM[self._rising_offset] & (1 << self._rising_shift))
|
||||
falling = bool(self._MEM[self._falling_offset] & (1 << self._falling_shift))
|
||||
rising = bool(self.factory.mem[self._rising_offset] & (1 << self._rising_shift))
|
||||
falling = bool(self.factory.mem[self._falling_offset] & (1 << self._falling_shift))
|
||||
return self.GPIO_EDGES_NAMES[(rising, falling)]
|
||||
|
||||
def _set_edges(self, value):
|
||||
@@ -319,43 +296,36 @@ class NativePin(LocalPin):
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._MEM[self._rising_offset] = (
|
||||
self._MEM[self._rising_offset]
|
||||
self.factory.mem[self._rising_offset] = (
|
||||
self.factory.mem[self._rising_offset]
|
||||
& ~(1 << self._rising_shift)
|
||||
| (rising << self._rising_shift)
|
||||
)
|
||||
self._MEM[self._falling_offset] = (
|
||||
self._MEM[self._falling_offset]
|
||||
self.factory.mem[self._falling_offset] = (
|
||||
self.factory.mem[self._falling_offset]
|
||||
& ~(1 << self._falling_shift)
|
||||
| (falling << self._falling_shift)
|
||||
)
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _get_when_changed(self):
|
||||
return self._when_changed
|
||||
def _enable_event_detect(self):
|
||||
self._change_thread = Thread(target=self._change_watch)
|
||||
self._change_thread.daemon = True
|
||||
self._change_event.clear()
|
||||
self._change_thread.start()
|
||||
|
||||
def _set_when_changed(self, value):
|
||||
if self._when_changed is None and value is not None:
|
||||
self._when_changed = value
|
||||
self._change_thread = Thread(target=self._change_watch)
|
||||
self._change_thread.daemon = True
|
||||
self._change_event.clear()
|
||||
self._change_thread.start()
|
||||
elif self._when_changed is not None and value is None:
|
||||
self._change_event.set()
|
||||
self._change_thread.join()
|
||||
self._change_thread = None
|
||||
self._when_changed = None
|
||||
else:
|
||||
self._when_changed = value
|
||||
def _disable_event_detect(self):
|
||||
self._change_event.set()
|
||||
self._change_thread.join()
|
||||
self._change_thread = None
|
||||
|
||||
def _change_watch(self):
|
||||
offset = self._edge_offset
|
||||
mask = 1 << self._edge_shift
|
||||
self._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):
|
||||
if self._MEM[offset] & mask:
|
||||
self._MEM[offset] = mask
|
||||
self._when_changed()
|
||||
if self.factory.mem[offset] & mask:
|
||||
self.factory.mem[offset] = mask
|
||||
self._call_when_changed()
|
||||
|
||||
|
||||
307
gpiozero/pins/pi.py
Normal 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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,10 +7,10 @@ from __future__ import (
|
||||
str = type('')
|
||||
|
||||
import warnings
|
||||
|
||||
from RPi import GPIO
|
||||
|
||||
from . import LocalPin
|
||||
from .data import pi_info
|
||||
from .local import LocalPiFactory, LocalPiPin
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
@@ -19,12 +19,10 @@ from ..exc import (
|
||||
PinInvalidState,
|
||||
PinInvalidBounce,
|
||||
PinPWMFixedValue,
|
||||
PinNonPhysical,
|
||||
PinNoPins,
|
||||
)
|
||||
|
||||
|
||||
class RPiGPIOPin(LocalPin):
|
||||
class RPiGPIOFactory(LocalPiFactory):
|
||||
"""
|
||||
Uses the `RPi.GPIO`_ library to interface to the Pi's GPIO pins. This is
|
||||
the default pin implementation if the RPi.GPIO library is installed.
|
||||
@@ -39,7 +37,7 @@ class RPiGPIOPin(LocalPin):
|
||||
|
||||
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
|
||||
|
||||
led = LED(RPiGPIOPin(12))
|
||||
@@ -47,8 +45,18 @@ class RPiGPIOPin(LocalPin):
|
||||
.. _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 = {
|
||||
'input': GPIO.IN,
|
||||
'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_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
|
||||
|
||||
PI_INFO = None
|
||||
|
||||
def __new__(cls, number):
|
||||
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._frequency = None
|
||||
self._duty_cycle = None
|
||||
self._bounce = -666
|
||||
self._when_changed = None
|
||||
self._edges = GPIO.BOTH
|
||||
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
cls._PINS[number] = self
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "GPIO%d" % self._number
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return self._number
|
||||
def __init__(self, factory, number):
|
||||
super(RPiGPIOPin, self).__init__(factory, number)
|
||||
self._pull = 'up' if factory.pi_info.pulled_up(self.address[-1]) else 'floating'
|
||||
self._pwm = None
|
||||
self._frequency = None
|
||||
self._duty_cycle = None
|
||||
self._bounce = -666
|
||||
self._edges = GPIO.BOTH
|
||||
GPIO.setup(self.number, GPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
|
||||
def close(self):
|
||||
self.frequency = None
|
||||
self.when_changed = None
|
||||
GPIO.cleanup(self._number)
|
||||
GPIO.cleanup(self.number)
|
||||
|
||||
def output_with_state(self, state):
|
||||
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):
|
||||
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)
|
||||
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
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (pull, self))
|
||||
|
||||
def _get_function(self):
|
||||
return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self._number)]
|
||||
return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self.number)]
|
||||
|
||||
def _set_function(self, value):
|
||||
if value != 'input':
|
||||
self._pull = 'floating'
|
||||
if value in ('input', 'output') and value in self.GPIO_FUNCTIONS:
|
||||
GPIO.setup(self._number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull])
|
||||
GPIO.setup(self.number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull])
|
||||
else:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
@@ -145,7 +126,7 @@ class RPiGPIOPin(LocalPin):
|
||||
if self._pwm:
|
||||
return self._duty_cycle
|
||||
else:
|
||||
return GPIO.input(self._number)
|
||||
return GPIO.input(self.number)
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._pwm:
|
||||
@@ -156,7 +137,7 @@ class RPiGPIOPin(LocalPin):
|
||||
self._duty_cycle = value
|
||||
else:
|
||||
try:
|
||||
GPIO.output(self._number, value)
|
||||
GPIO.output(self.number, value)
|
||||
except ValueError:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
except RuntimeError:
|
||||
@@ -168,10 +149,10 @@ class RPiGPIOPin(LocalPin):
|
||||
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):
|
||||
if value != 'up' and self.factory.pi_info.pulled_up(self.address[-1]):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
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
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
|
||||
@@ -182,7 +163,7 @@ class RPiGPIOPin(LocalPin):
|
||||
def _set_frequency(self, value):
|
||||
if self._frequency is None and value is not None:
|
||||
try:
|
||||
self._pwm = GPIO.PWM(self._number, value)
|
||||
self._pwm = GPIO.PWM(self.number, value)
|
||||
except RuntimeError:
|
||||
raise PinPWMFixedValue('cannot start PWM on pin %r' % self)
|
||||
self._pwm.start(0)
|
||||
@@ -221,19 +202,15 @@ class RPiGPIOPin(LocalPin):
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _get_when_changed(self):
|
||||
return self._when_changed
|
||||
def _call_when_changed(self, channel):
|
||||
super(RPiGPIOPin, self)._call_when_changed()
|
||||
|
||||
def _set_when_changed(self, value):
|
||||
if self._when_changed is None and value is not None:
|
||||
self._when_changed = value
|
||||
GPIO.add_event_detect(
|
||||
self._number, self._edges,
|
||||
callback=lambda channel: self._when_changed(),
|
||||
bouncetime=self._bounce)
|
||||
elif self._when_changed is not None and value is None:
|
||||
GPIO.remove_event_detect(self._number)
|
||||
self._when_changed = None
|
||||
else:
|
||||
self._when_changed = value
|
||||
def _enable_event_detect(self):
|
||||
GPIO.add_event_detect(
|
||||
self.number, self._edges,
|
||||
callback=self._call_when_changed,
|
||||
bouncetime=self._bounce)
|
||||
|
||||
def _disable_event_detect(self):
|
||||
GPIO.remove_event_detect(self.number)
|
||||
|
||||
|
||||
@@ -8,11 +8,12 @@ str = type('')
|
||||
|
||||
|
||||
import warnings
|
||||
|
||||
import RPIO
|
||||
import RPIO.PWM
|
||||
from RPIO.Exceptions import InvalidChannelException
|
||||
|
||||
from . import LocalPin, PINS_CLEANUP
|
||||
from .local import LocalPiPin, LocalPiFactory
|
||||
from .data import pi_info
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
@@ -22,12 +23,10 @@ from ..exc import (
|
||||
PinInvalidBounce,
|
||||
PinInvalidState,
|
||||
PinPWMError,
|
||||
PinNonPhysical,
|
||||
PinNoPins,
|
||||
)
|
||||
|
||||
|
||||
class RPIOPin(LocalPin):
|
||||
class RPIOFactory(LocalPiFactory):
|
||||
"""
|
||||
Uses the `RPIO`_ library to interface to the Pi's GPIO pins. This is
|
||||
the default pin implementation if the RPi.GPIO library is not installed,
|
||||
@@ -48,9 +47,22 @@ class RPIOPin(LocalPin):
|
||||
|
||||
.. _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 = {
|
||||
'input': RPIO.IN,
|
||||
'output': RPIO.OUT,
|
||||
@@ -66,64 +78,31 @@ class RPIOPin(LocalPin):
|
||||
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()}
|
||||
|
||||
PI_INFO = None
|
||||
|
||||
def __new__(cls, number):
|
||||
if not cls._PINS:
|
||||
RPIO.setmode(RPIO.BCM)
|
||||
RPIO.setwarnings(False)
|
||||
RPIO.wait_for_interrupts(threaded=True)
|
||||
RPIO.PWM.setup()
|
||||
RPIO.PWM.init_channel(0, 10000)
|
||||
PINS_CLEANUP.append(RPIO.PWM.cleanup)
|
||||
PINS_CLEANUP.append(RPIO.stop_waiting_for_interrupts)
|
||||
PINS_CLEANUP.append(RPIO.cleanup)
|
||||
if cls.PI_INFO is None:
|
||||
cls.PI_INFO = pi_info()
|
||||
def __init__(self, factory, number):
|
||||
super(RPIOPin, self).__init__(factory, number)
|
||||
self._pull = 'up' if factory.pi_info.pulled_up(self.address[-1]) else 'floating'
|
||||
self._pwm = False
|
||||
self._duty_cycle = None
|
||||
self._bounce = None
|
||||
self._edges = 'both'
|
||||
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._duty_cycle = None
|
||||
self._bounce = None
|
||||
self._when_changed = None
|
||||
self._edges = 'both'
|
||||
try:
|
||||
RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
except InvalidChannelException as 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
|
||||
RPIO.setup(self.number, RPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
except InvalidChannelException as e:
|
||||
raise ValueError(e)
|
||||
|
||||
def close(self):
|
||||
self.frequency = None
|
||||
self.when_changed = None
|
||||
RPIO.setup(self._number, RPIO.IN, RPIO.PUD_OFF)
|
||||
RPIO.setup(self.number, RPIO.IN, RPIO.PUD_OFF)
|
||||
|
||||
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):
|
||||
if value != 'input':
|
||||
self._pull = 'floating'
|
||||
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:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
@@ -131,23 +110,23 @@ class RPIOPin(LocalPin):
|
||||
if self._pwm:
|
||||
return self._duty_cycle
|
||||
else:
|
||||
return RPIO.input(self._number)
|
||||
return RPIO.input(self.number)
|
||||
|
||||
def _set_state(self, value):
|
||||
if not 0 <= value <= 1:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
if self._pwm:
|
||||
RPIO.PWM.clear_channel_gpio(0, self._number)
|
||||
RPIO.PWM.clear_channel_gpio(0, self.number)
|
||||
if value == 0:
|
||||
RPIO.output(self._number, False)
|
||||
RPIO.output(self.number, False)
|
||||
elif value == 1:
|
||||
RPIO.output(self._number, True)
|
||||
RPIO.output(self.number, True)
|
||||
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
|
||||
else:
|
||||
try:
|
||||
RPIO.output(self._number, value)
|
||||
RPIO.output(self.number, value)
|
||||
except ValueError:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
except RuntimeError:
|
||||
@@ -159,10 +138,10 @@ class RPIOPin(LocalPin):
|
||||
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):
|
||||
if value != 'up' and self.factory.pi_info.pulled_up(self.address[-1]):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
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
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
|
||||
@@ -182,10 +161,10 @@ class RPIOPin(LocalPin):
|
||||
self._pwm = True
|
||||
# Dirty hack to get RPIO's PWM support to setup, but do nothing,
|
||||
# for a given GPIO pin
|
||||
RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=0)
|
||||
RPIO.PWM.clear_channel_gpio(0, self._number)
|
||||
RPIO.PWM.add_channel_pulse(0, self.number, start=0, width=0)
|
||||
RPIO.PWM.clear_channel_gpio(0, self.number)
|
||||
elif self._pwm and value is None:
|
||||
RPIO.PWM.clear_channel_gpio(0, self._number)
|
||||
RPIO.PWM.clear_channel_gpio(0, self.number)
|
||||
self._pwm = False
|
||||
|
||||
def _get_bounce(self):
|
||||
@@ -212,25 +191,20 @@ class RPIOPin(LocalPin):
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _get_when_changed(self):
|
||||
return self._when_changed
|
||||
def _call_when_changed(self, channel, value):
|
||||
super(RPIOPin, self)._call_when_changed()
|
||||
|
||||
def _set_when_changed(self, value):
|
||||
if self._when_changed is None and value is not None:
|
||||
self._when_changed = value
|
||||
RPIO.add_interrupt_callback(
|
||||
self._number,
|
||||
lambda channel, value: self._when_changed(),
|
||||
self._edges, self.GPIO_PULL_UPS[self._pull], self._bounce)
|
||||
elif self._when_changed is not None and value is None:
|
||||
try:
|
||||
RPIO.del_interrupt_callback(self._number)
|
||||
except KeyError:
|
||||
# Ignore this exception which occurs during shutdown; this
|
||||
# simply means RPIO's built-in cleanup has already run and
|
||||
# removed the handler
|
||||
pass
|
||||
self._when_changed = None
|
||||
else:
|
||||
self._when_changed = value
|
||||
def _enable_event_detect(self):
|
||||
RPIO.add_interrupt_callback(
|
||||
self.number, self._call_when_changed, self._edges,
|
||||
self.GPIO_PULL_UPS[self._pull], self._bounce)
|
||||
|
||||
def _disable_event_detect(self):
|
||||
try:
|
||||
RPIO.del_interrupt_callback(self.number)
|
||||
except KeyError:
|
||||
# Ignore this exception which occurs during shutdown; this
|
||||
# simply means RPIO's built-in cleanup has already run and
|
||||
# removed the handler
|
||||
pass
|
||||
|
||||
|
||||
98
gpiozero/pins/spi.py
Normal 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
|
||||
|
||||
|
||||
419
gpiozero/spi.py
@@ -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)
|
||||
|
||||
@@ -16,7 +16,6 @@ except ImportError:
|
||||
|
||||
from .exc import DeviceClosed, SPIBadChannel
|
||||
from .devices import Device
|
||||
from .spi import SPI
|
||||
|
||||
|
||||
class SPIDevice(Device):
|
||||
@@ -28,13 +27,14 @@ class SPIDevice(Device):
|
||||
specified with the constructor.
|
||||
"""
|
||||
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):
|
||||
if self._spi:
|
||||
s = self._spi
|
||||
self._spi.close()
|
||||
self._spi = None
|
||||
s.close()
|
||||
super(SPIDevice, self).close()
|
||||
|
||||
@property
|
||||
|
||||
27
setup.py
@@ -22,7 +22,7 @@ except ImportError:
|
||||
pass
|
||||
|
||||
__project__ = 'gpiozero'
|
||||
__version__ = '1.3.1'
|
||||
__version__ = '1.3.2'
|
||||
__author__ = 'Ben Nuttall'
|
||||
__author_email__ = 'ben@raspberrypi.org'
|
||||
__url__ = 'https://github.com/RPi-Distro/python-gpiozero'
|
||||
@@ -68,13 +68,26 @@ if sys.version_info[:2] == (3, 2):
|
||||
|
||||
__entry_points__ = {
|
||||
'gpiozero_pin_factories': [
|
||||
'PiGPIOPin = gpiozero.pins.pigpiod:PiGPIOPin',
|
||||
'RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOPin',
|
||||
'RPIOPin = gpiozero.pins.rpio:RPIOPin',
|
||||
'NativePin = gpiozero.pins.native:NativePin',
|
||||
'MockPin = gpiozero.pins.mock:MockPin',
|
||||
'MockPWMPin = gpiozero.pins.mock:MockPWMPin',
|
||||
'pigpio = gpiozero.pins.pigpio:PiGPIOFactory',
|
||||
'rpigpio = gpiozero.pins.rpigpio:RPiGPIOFactory',
|
||||
'rpio = gpiozero.pins.rpio:RPIOFactory',
|
||||
'native = gpiozero.pins.native:NativeFactory',
|
||||
'mock = gpiozero.pins.mock:MockFactory',
|
||||
# Backwards compatible names
|
||||
'PiGPIOPin = gpiozero.pins.pigpio:PiGPIOFactory',
|
||||
'RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOFactory',
|
||||
'RPIOPin = gpiozero.pins.rpio:RPIOFactory',
|
||||
'NativePin = gpiozero.pins.native:NativeFactory',
|
||||
],
|
||||
'gpiozero_mock_pin_classes': [
|
||||
'mockpin = gpiozero.pins.mock:MockPin',
|
||||
'mockpwmpin = gpiozero.pins.mock:MockPWMPin',
|
||||
'mockchargingpin = gpiozero.pins.mock:MockChargingPin',
|
||||
'mocktriggerpin = gpiozero.pins.mock:MockTriggerPin',
|
||||
],
|
||||
'console_scripts': [
|
||||
'pinout = gpiozero.cli.pinout:main',
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
35
tests/cli/test_pinout.py
Normal 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
@@ -0,0 +1,10 @@
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
print_function,
|
||||
absolute_import,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import os
|
||||
os.environ['GPIOZERO_PIN_FACTORY'] = 'mock'
|
||||
@@ -11,26 +11,40 @@ import sys
|
||||
import pytest
|
||||
from time import sleep
|
||||
|
||||
from gpiozero.pins.mock import MockPin, MockPWMPin
|
||||
from gpiozero import *
|
||||
from gpiozero.pins.mock import MockPWMPin, MockPin
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
import gpiozero.devices
|
||||
# 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'):
|
||||
gpiozero.devices.pin_factory = MockPWMPin
|
||||
else:
|
||||
gpiozero.devices.pin_factory = MockPin
|
||||
Device.pin_factory.pin_class = MockPWMPin if function.__name__ in (
|
||||
'test_robot',
|
||||
'test_ryanteck_robot',
|
||||
'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):
|
||||
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():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device:
|
||||
device.on()
|
||||
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))
|
||||
|
||||
def test_composite_output_toggle():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device:
|
||||
device.toggle()
|
||||
assert all((pin1.state, pin2.state, pin3.state))
|
||||
@@ -51,9 +65,9 @@ def test_composite_output_toggle():
|
||||
assert not pin3.state
|
||||
|
||||
def test_composite_output_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device:
|
||||
assert device.value == (0, 0, 0)
|
||||
device.toggle()
|
||||
@@ -64,9 +78,9 @@ def test_composite_output_value():
|
||||
assert device[2].is_active
|
||||
|
||||
def test_led_board_on_off():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3) as board:
|
||||
assert isinstance(board[0], LED)
|
||||
assert isinstance(board[1], LED)
|
||||
@@ -121,9 +135,9 @@ def test_led_board_on_off():
|
||||
assert pin3.state
|
||||
|
||||
def test_led_board_active_low():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, active_high=False) as board:
|
||||
assert not board.active_high
|
||||
assert not board[0].active_high
|
||||
@@ -145,9 +159,9 @@ def test_led_board_active_low():
|
||||
assert not pin3.state
|
||||
|
||||
def test_led_board_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3) as board:
|
||||
assert board.value == (0, 0, 0)
|
||||
board.value = (0, 1, 0)
|
||||
@@ -156,9 +170,9 @@ def test_led_board_value():
|
||||
assert board.value == (1, 0, 1)
|
||||
|
||||
def test_led_board_pwm_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board:
|
||||
assert board.value == (0, 0, 0)
|
||||
board.value = (0, 1, 0)
|
||||
@@ -167,9 +181,9 @@ def test_led_board_pwm_value():
|
||||
assert board.value == (0.5, 0, 0.75)
|
||||
|
||||
def test_led_board_pwm_bad_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board:
|
||||
with pytest.raises(ValueError):
|
||||
board.value = (-1, 0, 0)
|
||||
@@ -177,18 +191,18 @@ def test_led_board_pwm_bad_value():
|
||||
board.value = (0, 2, 0)
|
||||
|
||||
def test_led_board_initial_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, initial_value=0) as board:
|
||||
assert board.value == (0, 0, 0)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, initial_value=1) as board:
|
||||
assert board.value == (1, 1, 1)
|
||||
|
||||
def test_led_board_pwm_initial_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0) as board:
|
||||
assert board.value == (0, 0, 0)
|
||||
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)
|
||||
|
||||
def test_led_board_pwm_bad_initial_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with pytest.raises(ValueError):
|
||||
LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=-1)
|
||||
with pytest.raises(ValueError):
|
||||
LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=2)
|
||||
|
||||
def test_led_board_nested():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
assert list(led.pin for led in board.leds) == [pin1, pin2, pin3]
|
||||
assert board.value == (0, (0, 0))
|
||||
@@ -218,9 +232,9 @@ def test_led_board_nested():
|
||||
assert pin3.state
|
||||
|
||||
def test_led_board_bad_blink():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
with pytest.raises(ValueError):
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_background():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(4)
|
||||
pin2 = Device.pin_factory.pin(5)
|
||||
pin3 = Device.pin_factory.pin(6)
|
||||
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_thread.join() # naughty, but ensures no arbitrary waits in the test
|
||||
test = [
|
||||
@@ -252,10 +271,13 @@ def test_led_board_blink_background():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_foreground():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(4)
|
||||
pin2 = Device.pin_factory.pin(5)
|
||||
pin3 = Device.pin_factory.pin(6)
|
||||
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)
|
||||
test = [
|
||||
(0.0, False),
|
||||
@@ -271,10 +293,13 @@ def test_led_board_blink_foreground():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_control():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(4)
|
||||
pin2 = Device.pin_factory.pin(5)
|
||||
pin3 = Device.pin_factory.pin(6)
|
||||
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)
|
||||
# make sure the blink thread's started
|
||||
while not board._blink_leds:
|
||||
@@ -296,10 +321,13 @@ def test_led_board_blink_control():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_take_over():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(4)
|
||||
pin2 = Device.pin_factory.pin(5)
|
||||
pin3 = Device.pin_factory.pin(6)
|
||||
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.blink(0.1, 0.1, n=2) # immediately take over blinking
|
||||
board[1]._blink_thread.join()
|
||||
@@ -318,10 +346,13 @@ def test_led_board_blink_take_over():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_control_all():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(4)
|
||||
pin2 = Device.pin_factory.pin(5)
|
||||
pin3 = Device.pin_factory.pin(6)
|
||||
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)
|
||||
# make sure the blink thread's started
|
||||
while not board._blink_leds:
|
||||
@@ -340,9 +371,9 @@ def test_led_board_blink_control_all():
|
||||
pin3.assert_states_and_times(test)
|
||||
|
||||
def test_led_board_blink_interrupt_on():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(4)
|
||||
pin2 = Device.pin_factory.pin(5)
|
||||
pin3 = Device.pin_factory.pin(6)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
board.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -352,10 +383,13 @@ def test_led_board_blink_interrupt_on():
|
||||
pin3.assert_states([False, True, False])
|
||||
|
||||
def test_led_board_blink_interrupt_off():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(4)
|
||||
pin2 = Device.pin_factory.pin(5)
|
||||
pin3 = Device.pin_factory.pin(6)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
pin1.clear_states()
|
||||
pin2.clear_states()
|
||||
pin3.clear_states()
|
||||
board.blink(0.1, 1)
|
||||
sleep(0.2)
|
||||
board.off() # should interrupt while off
|
||||
@@ -366,10 +400,13 @@ def test_led_board_blink_interrupt_off():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_fade_background():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device.pin_factory.pin(4)
|
||||
pin2 = Device.pin_factory.pin(5)
|
||||
pin3 = Device.pin_factory.pin(6)
|
||||
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_thread.join()
|
||||
test = [
|
||||
@@ -400,9 +437,9 @@ def test_led_board_fade_background():
|
||||
pin3.assert_states_and_times(test)
|
||||
|
||||
def test_led_bar_graph_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3) as graph:
|
||||
assert isinstance(graph[0], LED)
|
||||
assert isinstance(graph[1], LED)
|
||||
@@ -433,9 +470,9 @@ def test_led_bar_graph_value():
|
||||
assert graph.value == -2/3
|
||||
|
||||
def test_led_bar_graph_active_low():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3, active_high=False) as graph:
|
||||
assert not graph.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
|
||||
|
||||
def test_led_bar_graph_pwm_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3, pwm=True) as graph:
|
||||
assert isinstance(graph[0], PWMLED)
|
||||
assert isinstance(graph[1], PWMLED)
|
||||
@@ -482,9 +519,9 @@ def test_led_bar_graph_pwm_value():
|
||||
assert graph.value == -1/2
|
||||
|
||||
def test_led_bar_graph_bad_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3) as graph:
|
||||
with pytest.raises(ValueError):
|
||||
graph.value = -2
|
||||
@@ -492,9 +529,9 @@ def test_led_bar_graph_bad_value():
|
||||
graph.value = 2
|
||||
|
||||
def test_led_bar_graph_bad_init():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with pytest.raises(TypeError):
|
||||
LEDBarGraph(pin1, pin2, foo=pin3)
|
||||
with pytest.raises(ValueError):
|
||||
@@ -503,9 +540,9 @@ def test_led_bar_graph_bad_init():
|
||||
LEDBarGraph(pin1, pin2, pin3, initial_value=2)
|
||||
|
||||
def test_led_bar_graph_initial_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3, initial_value=1/3) as graph:
|
||||
assert graph.value == 1/3
|
||||
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)
|
||||
|
||||
def test_led_bar_graph_pwm_initial_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(3)
|
||||
pin3 = Device.pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=0.5) as graph:
|
||||
assert graph.value == 0.5
|
||||
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)
|
||||
|
||||
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:
|
||||
assert [device.pin for device in board._leds] == pins
|
||||
|
||||
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:
|
||||
assert [device.pin for device in board] == pins
|
||||
|
||||
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:
|
||||
board.value = 0.5
|
||||
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
|
||||
|
||||
def test_traffic_lights():
|
||||
red_pin = MockPin(2)
|
||||
amber_pin = MockPin(3)
|
||||
green_pin = MockPin(4)
|
||||
red_pin = Device.pin_factory.pin(2)
|
||||
amber_pin = Device.pin_factory.pin(3)
|
||||
green_pin = Device.pin_factory.pin(4)
|
||||
with TrafficLights(red_pin, amber_pin, green_pin) as board:
|
||||
board.red.on()
|
||||
assert board.red.value
|
||||
@@ -574,15 +611,15 @@ def test_traffic_lights():
|
||||
def test_traffic_lights_bad_init():
|
||||
with pytest.raises(ValueError):
|
||||
TrafficLights()
|
||||
red_pin = MockPin(2)
|
||||
amber_pin = MockPin(3)
|
||||
green_pin = MockPin(4)
|
||||
yellow_pin = MockPin(5)
|
||||
red_pin = Device.pin_factory.pin(2)
|
||||
amber_pin = Device.pin_factory.pin(3)
|
||||
green_pin = Device.pin_factory.pin(4)
|
||||
yellow_pin = Device.pin_factory.pin(5)
|
||||
with pytest.raises(ValueError):
|
||||
TrafficLights(red=red_pin, amber=amber_pin, yellow=yellow_pin, green=green_pin)
|
||||
|
||||
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:
|
||||
assert [device.pin for device in board] == pins
|
||||
|
||||
@@ -591,27 +628,27 @@ def test_pi_stop():
|
||||
PiStop()
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
assert [device.pin for device in board] == pins_d
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
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:
|
||||
assert [device.pin for device in board.leds] == pins
|
||||
assert all(device.pin.state == 0.5 for device in board.leds)
|
||||
|
||||
def test_traffic_lights_buzzer():
|
||||
red_pin = MockPin(2)
|
||||
amber_pin = MockPin(3)
|
||||
green_pin = MockPin(4)
|
||||
buzzer_pin = MockPin(5)
|
||||
button_pin = MockPin(6)
|
||||
red_pin = Device.pin_factory.pin(2)
|
||||
amber_pin = Device.pin_factory.pin(3)
|
||||
green_pin = Device.pin_factory.pin(4)
|
||||
buzzer_pin = Device.pin_factory.pin(5)
|
||||
button_pin = Device.pin_factory.pin(6)
|
||||
with TrafficLightsBuzzer(
|
||||
TrafficLights(red_pin, amber_pin, green_pin),
|
||||
Buzzer(buzzer_pin),
|
||||
@@ -651,17 +688,17 @@ def test_traffic_lights_buzzer():
|
||||
assert board.button.is_active
|
||||
|
||||
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:
|
||||
assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins
|
||||
|
||||
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:
|
||||
assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins
|
||||
|
||||
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:
|
||||
assert (
|
||||
[device.pin for device in robot.left_motor] +
|
||||
@@ -696,12 +733,12 @@ def test_robot():
|
||||
assert robot.value == (0, -0.5)
|
||||
|
||||
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:
|
||||
assert [device.pin for motor in board for device in motor] == pins
|
||||
|
||||
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:
|
||||
assert [device.pin for motor in board for device in motor] == pins
|
||||
|
||||
@@ -714,7 +751,7 @@ def test_energenie_bad_init():
|
||||
Energenie(5)
|
||||
|
||||
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, \
|
||||
Energenie(2, initial_value=False) as device2:
|
||||
assert repr(device1) == '<gpiozero.Energenie object on socket 1>'
|
||||
|
||||
@@ -6,96 +6,97 @@ from __future__ import (
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from gpiozero.pins.mock import MockPin
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
MockPin.clear_pins()
|
||||
Device.pin_factory.reset()
|
||||
|
||||
|
||||
# TODO add more devices tests!
|
||||
|
||||
def test_device_no_pin():
|
||||
def test_device_bad_pin():
|
||||
with pytest.raises(GPIOPinMissing):
|
||||
device = GPIODevice()
|
||||
with pytest.raises(PinInvalidPin):
|
||||
device = GPIODevice(60)
|
||||
|
||||
def test_device_non_physical():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
device = GPIODevice(37)
|
||||
assert len(w) == 1
|
||||
assert w[0].category == PinNonPhysical
|
||||
|
||||
def test_device_init():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
assert not device.closed
|
||||
assert device.pin == pin
|
||||
|
||||
def test_device_init_twice_same_pin():
|
||||
pin = MockPin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
with GPIODevice(2) as device:
|
||||
with pytest.raises(GPIOPinInUse):
|
||||
device2 = GPIODevice(pin)
|
||||
GPIODevice(2)
|
||||
|
||||
def test_device_init_twice_different_pin():
|
||||
pin = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
with GPIODevice(pin) as device:
|
||||
with GPIODevice(pin2) as device2:
|
||||
with GPIODevice(2) as device:
|
||||
with GPIODevice(3) as device2:
|
||||
pass
|
||||
|
||||
def test_device_close():
|
||||
pin = MockPin(2)
|
||||
device = GPIODevice(pin)
|
||||
device = GPIODevice(2)
|
||||
# Don't use "with" here; we're testing close explicitly
|
||||
device.close()
|
||||
assert device.closed
|
||||
assert device.pin is None
|
||||
|
||||
def test_device_reopen_same_pin():
|
||||
pin = MockPin(2)
|
||||
device = GPIODevice(pin)
|
||||
device.close()
|
||||
device2 = GPIODevice(pin)
|
||||
assert not device2.closed
|
||||
assert device2.pin == pin
|
||||
assert device.closed
|
||||
assert device.pin is None
|
||||
device2.close()
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
pass
|
||||
with GPIODevice(pin) as device2:
|
||||
assert not device2.closed
|
||||
assert device2.pin is pin
|
||||
assert device.closed
|
||||
assert device.pin is None
|
||||
|
||||
def test_device_repr():
|
||||
pin = MockPin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
assert repr(device) == '<gpiozero.GPIODevice object on pin %s, is_active=False>' % pin
|
||||
with GPIODevice(4) as device:
|
||||
assert repr(device) == '<gpiozero.GPIODevice object on pin %s, is_active=False>' % device.pin
|
||||
|
||||
def test_device_repr_after_close():
|
||||
pin = MockPin(2)
|
||||
device = GPIODevice(pin)
|
||||
device.close()
|
||||
with GPIODevice(2) as device:
|
||||
pass
|
||||
assert repr(device) == '<gpiozero.GPIODevice object closed>'
|
||||
|
||||
def test_device_unknown_attr():
|
||||
pin = MockPin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
with GPIODevice(2) as device:
|
||||
with pytest.raises(AttributeError):
|
||||
device.foo = 1
|
||||
|
||||
def test_device_context_manager():
|
||||
pin = MockPin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
with GPIODevice(2) as device:
|
||||
assert not device.closed
|
||||
assert device.closed
|
||||
|
||||
def test_composite_device_sequence():
|
||||
with CompositeDevice(
|
||||
InputDevice(MockPin(2)),
|
||||
InputDevice(MockPin(3))
|
||||
InputDevice(4),
|
||||
InputDevice(5)
|
||||
) as device:
|
||||
assert len(device) == 2
|
||||
assert device[0].pin.number == 2
|
||||
assert device[1].pin.number == 3
|
||||
assert device[0].pin.number == 4
|
||||
assert device[1].pin.number == 5
|
||||
assert device.namedtuple._fields == ('_0', '_1')
|
||||
|
||||
def test_composite_device_values():
|
||||
with CompositeDevice(
|
||||
InputDevice(MockPin(2)),
|
||||
InputDevice(MockPin(3))
|
||||
InputDevice(4),
|
||||
InputDevice(5)
|
||||
) as device:
|
||||
assert device.value == (0, 0)
|
||||
assert not device.is_active
|
||||
@@ -105,8 +106,8 @@ def test_composite_device_values():
|
||||
|
||||
def test_composite_device_named():
|
||||
with CompositeDevice(
|
||||
foo=InputDevice(MockPin(2)),
|
||||
bar=InputDevice(MockPin(3)),
|
||||
foo=InputDevice(4),
|
||||
bar=InputDevice(5),
|
||||
_order=('foo', 'bar')
|
||||
) as device:
|
||||
assert device.namedtuple._fields == ('foo', 'bar')
|
||||
@@ -121,13 +122,13 @@ def test_composite_device_bad_init():
|
||||
with pytest.raises(ValueError):
|
||||
CompositeDevice(2)
|
||||
with pytest.raises(ValueError):
|
||||
CompositeDevice(MockPin(2))
|
||||
CompositeDevice(Device.pin_factory.pin(2))
|
||||
|
||||
def test_composite_device_read_only():
|
||||
device = CompositeDevice(
|
||||
foo=InputDevice(MockPin(2)),
|
||||
bar=InputDevice(MockPin(3))
|
||||
)
|
||||
with pytest.raises(AttributeError):
|
||||
device.foo = 1
|
||||
with CompositeDevice(
|
||||
foo=InputDevice(4),
|
||||
bar=InputDevice(5)
|
||||
) as device:
|
||||
with pytest.raises(AttributeError):
|
||||
device.foo = 1
|
||||
|
||||
|
||||
@@ -12,57 +12,53 @@ import pytest
|
||||
from threading import Event
|
||||
from functools import partial
|
||||
|
||||
from gpiozero.pins.mock import (
|
||||
MockPin,
|
||||
MockPulledUpPin,
|
||||
MockChargingPin,
|
||||
MockTriggerPin,
|
||||
)
|
||||
from gpiozero.pins.mock import MockChargingPin, MockTriggerPin
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
MockPin.clear_pins()
|
||||
Device.pin_factory.reset()
|
||||
Device._reservations.clear()
|
||||
|
||||
|
||||
def test_input_initial_values():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with InputDevice(pin, pull_up=True) as device:
|
||||
assert pin.function == 'input'
|
||||
assert pin.pull == 'up'
|
||||
assert device.pull_up
|
||||
device.close()
|
||||
device = InputDevice(pin, pull_up=False)
|
||||
with InputDevice(pin, pull_up=False) as device:
|
||||
assert pin.pull == 'down'
|
||||
assert not device.pull_up
|
||||
|
||||
def test_input_is_active_low():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with InputDevice(pin, pull_up=True) as device:
|
||||
pin.drive_high()
|
||||
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()
|
||||
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():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with InputDevice(pin, pull_up=False) as device:
|
||||
pin.drive_high()
|
||||
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()
|
||||
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():
|
||||
pin = MockPulledUpPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with pytest.raises(PinFixedPull):
|
||||
InputDevice(pin, pull_up=False)
|
||||
|
||||
def test_input_event_activated():
|
||||
event = Event()
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with DigitalInputDevice(pin) as device:
|
||||
device.when_activated = lambda: event.set()
|
||||
assert not event.is_set()
|
||||
@@ -71,7 +67,7 @@ def test_input_event_activated():
|
||||
|
||||
def test_input_event_deactivated():
|
||||
event = Event()
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with DigitalInputDevice(pin) as device:
|
||||
device.when_deactivated = lambda: event.set()
|
||||
assert not event.is_set()
|
||||
@@ -82,7 +78,7 @@ def test_input_event_deactivated():
|
||||
|
||||
def test_input_partial_callback():
|
||||
event = Event()
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
def foo(a, b):
|
||||
event.set()
|
||||
return a + b
|
||||
@@ -95,22 +91,22 @@ def test_input_partial_callback():
|
||||
assert event.is_set()
|
||||
|
||||
def test_input_wait_active():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with DigitalInputDevice(pin) as device:
|
||||
pin.drive_high()
|
||||
assert device.wait_for_active(1)
|
||||
assert not device.wait_for_inactive(0)
|
||||
|
||||
def test_input_wait_inactive():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with DigitalInputDevice(pin) as device:
|
||||
assert device.wait_for_inactive(1)
|
||||
assert not device.wait_for_active(0)
|
||||
|
||||
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:
|
||||
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.queue_len == 5
|
||||
assert not device.partial
|
||||
@@ -120,7 +116,7 @@ def test_input_smoothed_attrib():
|
||||
device.threshold = 1
|
||||
|
||||
def test_input_smoothed_values():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with SmoothedInputDevice(pin) as device:
|
||||
device._queue.start()
|
||||
assert not device.is_active
|
||||
@@ -130,7 +126,7 @@ def test_input_smoothed_values():
|
||||
assert device.wait_for_inactive(1)
|
||||
|
||||
def test_input_button():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with Button(pin) as button:
|
||||
assert pin.pull == 'up'
|
||||
assert not button.is_pressed
|
||||
@@ -142,7 +138,7 @@ def test_input_button():
|
||||
assert button.wait_for_release(1)
|
||||
|
||||
def test_input_line_sensor():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with LineSensor(pin) as sensor:
|
||||
pin.drive_low() # logic is inverted for line sensor
|
||||
assert sensor.wait_for_line(1)
|
||||
@@ -152,7 +148,7 @@ def test_input_line_sensor():
|
||||
assert not sensor.line_detected
|
||||
|
||||
def test_input_motion_sensor():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with MotionSensor(pin) as sensor:
|
||||
pin.drive_high()
|
||||
assert sensor.wait_for_motion(1)
|
||||
@@ -164,7 +160,7 @@ def test_input_motion_sensor():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_input_light_sensor():
|
||||
pin = MockChargingPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockChargingPin)
|
||||
with LightSensor(pin) as sensor:
|
||||
pin.charge_time = 0.1
|
||||
assert sensor.wait_for_dark(1)
|
||||
@@ -174,10 +170,8 @@ def test_input_light_sensor():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_input_distance_sensor():
|
||||
echo_pin = MockPin(2)
|
||||
trig_pin = MockTriggerPin(3)
|
||||
trig_pin.echo_pin = echo_pin
|
||||
trig_pin.echo_time = 0.02
|
||||
echo_pin = Device.pin_factory.pin(4)
|
||||
trig_pin = Device.pin_factory.pin(5, pin_class=MockTriggerPin, echo_pin=echo_pin, echo_time=0.02)
|
||||
with pytest.raises(ValueError):
|
||||
DistanceSensor(echo_pin, trig_pin, max_distance=-1)
|
||||
# normal queue len is large (because the sensor is *really* jittery) but
|
||||
|
||||
@@ -11,25 +11,24 @@ from threading import Event
|
||||
|
||||
import pytest
|
||||
|
||||
from gpiozero.pins.mock import MockPin, MockPWMPin
|
||||
from gpiozero.pins.mock import MockPWMPin, MockPin
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
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
|
||||
# enough to get reasonable coverage but it's by no means comprehensive...
|
||||
|
||||
def test_mock_pin_init():
|
||||
with pytest.raises(TypeError):
|
||||
MockPin()
|
||||
with pytest.raises(ValueError):
|
||||
MockPin(60)
|
||||
assert MockPin(2).number == 2
|
||||
Device.pin_factory.pin(60)
|
||||
assert Device.pin_factory.pin(2).number == 2
|
||||
|
||||
def test_mock_pin_defaults():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
assert pin.bounce == None
|
||||
assert pin.edges == 'both'
|
||||
assert pin.frequency == None
|
||||
@@ -37,32 +36,28 @@ def test_mock_pin_defaults():
|
||||
assert pin.pull == 'floating'
|
||||
assert pin.state == 0
|
||||
assert pin.when_changed == None
|
||||
pin.close()
|
||||
pin = Device.pin_factory.pin(2)
|
||||
assert pin.pull == 'up'
|
||||
|
||||
def test_mock_pin_open_close():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
pin.close()
|
||||
|
||||
def test_mock_pin_init_twice_same_pin():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(pin1.number)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(pin1.number)
|
||||
assert pin1 is pin2
|
||||
|
||||
def test_mock_pin_init_twice_different_pin():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(pin1.number+1)
|
||||
pin1 = Device.pin_factory.pin(2)
|
||||
pin2 = Device.pin_factory.pin(pin1.number+1)
|
||||
assert pin1 != pin2
|
||||
assert pin1.number == 2
|
||||
assert pin2.number == pin1.number+1
|
||||
|
||||
def test_mock_pwm_pin_init():
|
||||
with pytest.raises(TypeError):
|
||||
MockPWMPin()
|
||||
with pytest.raises(ValueError):
|
||||
MockPWMPin(60)
|
||||
assert MockPWMPin(2).number == 2
|
||||
|
||||
def test_mock_pwm_pin_defaults():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
assert pin.bounce == None
|
||||
assert pin.edges == 'both'
|
||||
assert pin.frequency == None
|
||||
@@ -70,40 +65,43 @@ def test_mock_pwm_pin_defaults():
|
||||
assert pin.pull == 'floating'
|
||||
assert pin.state == 0
|
||||
assert pin.when_changed == None
|
||||
pin.close()
|
||||
pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
assert pin.pull == 'up'
|
||||
|
||||
def test_mock_pwm_pin_open_close():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
pin.close()
|
||||
|
||||
def test_mock_pwm_pin_init_twice_same_pin():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(pin1.number)
|
||||
pin1 = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
pin2 = Device.pin_factory.pin(pin1.number, pin_class=MockPWMPin)
|
||||
assert pin1 is pin2
|
||||
|
||||
def test_mock_pwm_pin_init_twice_different_pin():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(pin1.number+1)
|
||||
pin1 = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
pin2 = Device.pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin)
|
||||
assert pin1 != pin2
|
||||
assert pin1.number == 2
|
||||
assert pin2.number == pin1.number+1
|
||||
|
||||
def test_mock_pin_init_twice_different_modes():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPWMPin(pin1.number+1)
|
||||
pin1 = Device.pin_factory.pin(2, pin_class=MockPin)
|
||||
pin2 = Device.pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin)
|
||||
assert pin1 != pin2
|
||||
with pytest.raises(ValueError):
|
||||
pin3 = MockPWMPin(pin1.number)
|
||||
Device.pin_factory.pin(pin1.number, pin_class=MockPWMPin)
|
||||
with pytest.raises(ValueError):
|
||||
pin4 = MockPin(pin2.number)
|
||||
Device.pin_factory.pin(pin2.number, pin_class=MockPin)
|
||||
|
||||
def test_mock_pin_frequency_unsupported():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
pin.frequency = None
|
||||
with pytest.raises(PinPWMUnsupported):
|
||||
pin.frequency = 100
|
||||
|
||||
def test_mock_pin_frequency_supported():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
pin.function = 'output'
|
||||
assert pin.frequency is None
|
||||
pin.frequency = 100
|
||||
@@ -112,20 +110,25 @@ def test_mock_pin_frequency_supported():
|
||||
assert not pin.state
|
||||
|
||||
def test_mock_pin_pull():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
pin.function = 'input'
|
||||
assert pin.pull == 'floating'
|
||||
pin.pull = 'up'
|
||||
assert pin.state
|
||||
pin.pull = 'down'
|
||||
assert not pin.state
|
||||
pin.close()
|
||||
pin = Device.pin_factory.pin(2)
|
||||
pin.function = 'input'
|
||||
assert pin.pull == 'up'
|
||||
with pytest.raises(PinFixedPull):
|
||||
pin.pull = 'floating'
|
||||
|
||||
def test_mock_pin_state():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with pytest.raises(PinSetInput):
|
||||
pin.state = 1
|
||||
pin.function = 'output'
|
||||
assert pin.state == 0
|
||||
pin.state = 1
|
||||
assert pin.state == 1
|
||||
pin.state = 0
|
||||
@@ -134,11 +137,10 @@ def test_mock_pin_state():
|
||||
assert pin.state == 1
|
||||
|
||||
def test_mock_pwm_pin_state():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with pytest.raises(PinSetInput):
|
||||
pin.state = 1
|
||||
pin.function = 'output'
|
||||
assert pin.state == 0
|
||||
pin.state = 1
|
||||
assert pin.state == 1
|
||||
pin.state = 0
|
||||
@@ -147,7 +149,7 @@ def test_mock_pwm_pin_state():
|
||||
assert pin.state == 0.5
|
||||
|
||||
def test_mock_pin_edges():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
assert pin.when_changed is None
|
||||
fired = Event()
|
||||
pin.function = 'input'
|
||||
|
||||
@@ -16,15 +16,17 @@ except ImportError:
|
||||
|
||||
import pytest
|
||||
|
||||
from gpiozero.pins.mock import MockPin, MockPWMPin
|
||||
from gpiozero.pins.mock import MockPWMPin
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
MockPin.clear_pins()
|
||||
Device.pin_factory.reset()
|
||||
Device._reservations.clear()
|
||||
|
||||
|
||||
def test_output_initial_values():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with OutputDevice(pin, initial_value=False) as device:
|
||||
assert pin.function == 'output'
|
||||
assert not pin.state
|
||||
@@ -35,7 +37,7 @@ def test_output_initial_values():
|
||||
assert state == pin.state
|
||||
|
||||
def test_output_write_active_high():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with OutputDevice(pin) as device:
|
||||
device.on()
|
||||
assert pin.state
|
||||
@@ -43,7 +45,7 @@ def test_output_write_active_high():
|
||||
assert not pin.state
|
||||
|
||||
def test_output_write_active_low():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with OutputDevice(pin, active_high=False) as device:
|
||||
device.on()
|
||||
assert not pin.state
|
||||
@@ -51,7 +53,7 @@ def test_output_write_active_low():
|
||||
assert pin.state
|
||||
|
||||
def test_output_write_closed():
|
||||
with OutputDevice(MockPin(2)) as device:
|
||||
with OutputDevice(Device.pin_factory.pin(2)) as device:
|
||||
device.close()
|
||||
assert device.closed
|
||||
device.close()
|
||||
@@ -60,14 +62,14 @@ def test_output_write_closed():
|
||||
device.on()
|
||||
|
||||
def test_output_write_silly():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with OutputDevice(pin) as device:
|
||||
pin.function = 'input'
|
||||
with pytest.raises(AttributeError):
|
||||
device.on()
|
||||
|
||||
def test_output_value():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with OutputDevice(pin) as device:
|
||||
assert not device.value
|
||||
assert not pin.state
|
||||
@@ -79,7 +81,7 @@ def test_output_value():
|
||||
assert not pin.state
|
||||
|
||||
def test_output_digital_toggle():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(2)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
assert not device.value
|
||||
assert not pin.state
|
||||
@@ -93,7 +95,7 @@ def test_output_digital_toggle():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_blink_background():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_blink_foreground():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
device.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -133,7 +135,7 @@ def test_output_blink_interrupt_on():
|
||||
pin.assert_states([False, True, False])
|
||||
|
||||
def test_output_blink_interrupt_off():
|
||||
pin = MockPin(2)
|
||||
pin = Device.pin_factory.pin(4)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
device.blink(0.1, 1)
|
||||
sleep(0.2)
|
||||
@@ -142,14 +144,14 @@ def test_output_blink_interrupt_off():
|
||||
|
||||
def test_output_pwm_bad_initial_value():
|
||||
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():
|
||||
with pytest.raises(AttributeError):
|
||||
PWMOutputDevice(MockPin(2))
|
||||
PWMOutputDevice(Device.pin_factory.pin(2))
|
||||
|
||||
def test_output_pwm_states():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
device.value = 0.1
|
||||
device.value = 0.2
|
||||
@@ -157,7 +159,7 @@ def test_output_pwm_states():
|
||||
pin.assert_states([0.0, 0.1, 0.2, 0.0])
|
||||
|
||||
def test_output_pwm_read():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin, frequency=100) as device:
|
||||
assert device.frequency == 100
|
||||
device.value = 0.1
|
||||
@@ -170,14 +172,14 @@ def test_output_pwm_read():
|
||||
assert device.frequency is None
|
||||
|
||||
def test_output_pwm_write():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
device.on()
|
||||
device.off()
|
||||
pin.assert_states([False, True, False])
|
||||
|
||||
def test_output_pwm_toggle():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
device.toggle()
|
||||
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])
|
||||
|
||||
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:
|
||||
device.value = 0.1
|
||||
assert isclose(device.value, 0.1)
|
||||
@@ -196,17 +198,18 @@ def test_output_pwm_active_high_read():
|
||||
assert device.value
|
||||
|
||||
def test_output_pwm_bad_value():
|
||||
with pytest.raises(ValueError):
|
||||
PWMOutputDevice(MockPWMPin(2)).value = 2
|
||||
with PWMOutputDevice(Device.pin_factory.pin(2, pin_class=MockPWMPin)) as device:
|
||||
with pytest.raises(ValueError):
|
||||
device.value = 2
|
||||
|
||||
def test_output_pwm_write_closed():
|
||||
device = PWMOutputDevice(MockPWMPin(2))
|
||||
device.close()
|
||||
with pytest.raises(GPIODeviceClosed):
|
||||
device.on()
|
||||
with PWMOutputDevice(Device.pin_factory.pin(2, pin_class=MockPWMPin)) as device:
|
||||
device.close()
|
||||
with pytest.raises(GPIODeviceClosed):
|
||||
device.on()
|
||||
|
||||
def test_output_pwm_write_silly():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
pin.function = 'input'
|
||||
with pytest.raises(AttributeError):
|
||||
@@ -215,7 +218,7 @@ def test_output_pwm_write_silly():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_blink_background():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_blink_foreground():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_fade_background():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_fade_foreground():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_pulse_background():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_pulse_foreground():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device.pin_factory.pin(4, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
device.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -391,7 +394,7 @@ def test_rgbled_missing_pins():
|
||||
RGBLED()
|
||||
|
||||
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:
|
||||
assert r.frequency
|
||||
assert g.frequency
|
||||
@@ -401,24 +404,24 @@ def test_rgbled_initial_value():
|
||||
assert isclose(b.state, 0.0)
|
||||
|
||||
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:
|
||||
assert r.state == 0
|
||||
assert g.state == 1
|
||||
assert b.state == 1
|
||||
|
||||
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):
|
||||
RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2))
|
||||
|
||||
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):
|
||||
RGBLED(r, g, b, pwm=False, initial_value=(0.1, 0.2, 0))
|
||||
|
||||
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:
|
||||
assert isinstance(device._leds[0], PWMLED)
|
||||
assert isinstance(device._leds[1], PWMLED)
|
||||
@@ -436,7 +439,7 @@ def test_rgbled_value():
|
||||
assert device.value == (0.5, 0.5, 0.5)
|
||||
|
||||
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:
|
||||
assert isinstance(device._leds[0], LED)
|
||||
assert isinstance(device._leds[1], LED)
|
||||
@@ -451,7 +454,7 @@ def test_rgbled_value_nonpwm():
|
||||
assert device.value == (0, 0, 0)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
device.value = (2, 0, 0)
|
||||
@@ -460,7 +463,7 @@ def test_rgbled_bad_value():
|
||||
device.value = (0, -1, 0)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
device.value = (2, 0, 0)
|
||||
@@ -478,7 +481,7 @@ def test_rgbled_bad_value_nonpwm():
|
||||
device.value = (0, 0, 0.5)
|
||||
|
||||
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:
|
||||
assert not device.is_active
|
||||
assert device.value == (0, 0, 0)
|
||||
@@ -490,7 +493,7 @@ def test_rgbled_toggle():
|
||||
assert device.value == (0, 0, 0)
|
||||
|
||||
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:
|
||||
assert not device.is_active
|
||||
assert device.value == (0, 0, 0)
|
||||
@@ -501,10 +504,18 @@ def test_rgbled_toggle_nonpwm():
|
||||
assert not device.is_active
|
||||
assert device.value == (0, 0, 0)
|
||||
|
||||
def test_rgbled_blink_nonpwm():
|
||||
r, g, b = (Device.pin_factory.pin(i) for i in (1, 2, 3))
|
||||
with RGBLED(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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
device.pulse(0.2, 0.2, n=2)
|
||||
@@ -703,7 +714,7 @@ def test_rgbled_pulse_background():
|
||||
b.assert_states_and_times(expected)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
device.pulse(0.2, 0.2, n=2, background=False)
|
||||
|
||||
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:
|
||||
device.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -760,7 +771,7 @@ def test_rgbled_blink_interrupt():
|
||||
b.assert_states([0, 1, 0])
|
||||
|
||||
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:
|
||||
device.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -770,7 +781,7 @@ def test_rgbled_blink_interrupt_nonpwm():
|
||||
b.assert_states([0, 1, 0])
|
||||
|
||||
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:
|
||||
assert not device.closed
|
||||
device.close()
|
||||
@@ -779,7 +790,7 @@ def test_rgbled_close():
|
||||
assert device.closed
|
||||
|
||||
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:
|
||||
assert not device.closed
|
||||
device.close()
|
||||
@@ -792,8 +803,8 @@ def test_motor_missing_pins():
|
||||
Motor()
|
||||
|
||||
def test_motor_pins():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
assert device.forward_device.pin is f
|
||||
assert isinstance(device.forward_device, PWMOutputDevice)
|
||||
@@ -801,8 +812,8 @@ def test_motor_pins():
|
||||
assert isinstance(device.backward_device, PWMOutputDevice)
|
||||
|
||||
def test_motor_pins_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device.pin_factory.pin(1)
|
||||
b = Device.pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
assert device.forward_device.pin is f
|
||||
assert isinstance(device.forward_device, DigitalOutputDevice)
|
||||
@@ -810,8 +821,8 @@ def test_motor_pins_nonpwm():
|
||||
assert isinstance(device.backward_device, DigitalOutputDevice)
|
||||
|
||||
def test_motor_close():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
device.close()
|
||||
assert device.closed
|
||||
@@ -821,8 +832,8 @@ def test_motor_close():
|
||||
assert device.closed
|
||||
|
||||
def test_motor_close_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device.pin_factory.pin(1)
|
||||
b = Device.pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
device.close()
|
||||
assert device.closed
|
||||
@@ -830,8 +841,8 @@ def test_motor_close_nonpwm():
|
||||
assert device.backward_device.pin is None
|
||||
|
||||
def test_motor_value():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
device.value = -1
|
||||
assert device.is_active
|
||||
@@ -855,8 +866,8 @@ def test_motor_value():
|
||||
assert b.state == 0 and f.state == 0
|
||||
|
||||
def test_motor_value_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device.pin_factory.pin(1)
|
||||
b = Device.pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
device.value = -1
|
||||
assert device.is_active
|
||||
@@ -872,17 +883,21 @@ def test_motor_value_nonpwm():
|
||||
assert b.state == 0 and f.state == 0
|
||||
|
||||
def test_motor_bad_value():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
with pytest.raises(ValueError):
|
||||
device.value = -2
|
||||
with pytest.raises(ValueError):
|
||||
device.value = 2
|
||||
with pytest.raises(ValueError):
|
||||
device.forward(2)
|
||||
with pytest.raises(ValueError):
|
||||
device.backward(2)
|
||||
|
||||
def test_motor_bad_value_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device.pin_factory.pin(1)
|
||||
b = Device.pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
with pytest.raises(ValueError):
|
||||
device.value = -2
|
||||
@@ -894,8 +909,8 @@ def test_motor_bad_value_nonpwm():
|
||||
device.value = -0.5
|
||||
|
||||
def test_motor_reverse():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device.pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
device.forward()
|
||||
assert device.value == 1
|
||||
@@ -911,8 +926,8 @@ def test_motor_reverse():
|
||||
assert b.state == 0 and f.state == 0.5
|
||||
|
||||
def test_motor_reverse_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device.pin_factory.pin(1)
|
||||
b = Device.pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
device.forward()
|
||||
assert device.value == 1
|
||||
@@ -922,13 +937,13 @@ def test_motor_reverse_nonpwm():
|
||||
assert b.state == 1 and f.state == 0
|
||||
|
||||
def test_servo_pins():
|
||||
p = MockPWMPin(1)
|
||||
p = Device.pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
with Servo(p) as device:
|
||||
assert device.pwm_device.pin is p
|
||||
assert isinstance(device.pwm_device, PWMOutputDevice)
|
||||
|
||||
def test_servo_bad_value():
|
||||
p = MockPWMPin(1)
|
||||
p = Device.pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
with pytest.raises(ValueError):
|
||||
Servo(p, initial_value=2)
|
||||
with pytest.raises(ValueError):
|
||||
@@ -937,12 +952,12 @@ def test_servo_bad_value():
|
||||
Servo(p, max_pulse_width=30/1000)
|
||||
|
||||
def test_servo_pins_nonpwm():
|
||||
p = MockPin(2)
|
||||
p = Device.pin_factory.pin(2)
|
||||
with pytest.raises(PinPWMUnsupported):
|
||||
Servo(p)
|
||||
|
||||
def test_servo_close():
|
||||
p = MockPWMPin(2)
|
||||
p = Device.pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Servo(p) as device:
|
||||
device.close()
|
||||
assert device.closed
|
||||
@@ -951,7 +966,7 @@ def test_servo_close():
|
||||
assert device.closed
|
||||
|
||||
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:
|
||||
assert isclose(device.min_pulse_width, 5/10000)
|
||||
assert isclose(device.max_pulse_width, 25/10000)
|
||||
@@ -965,7 +980,7 @@ def test_servo_pulse_width():
|
||||
assert device.pulse_width is None
|
||||
|
||||
def test_servo_values():
|
||||
p = MockPWMPin(1)
|
||||
p = Device.pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
with Servo(p) as device:
|
||||
device.min()
|
||||
assert device.is_active
|
||||
@@ -992,13 +1007,13 @@ def test_servo_values():
|
||||
assert device.value is None
|
||||
|
||||
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:
|
||||
assert device.min_angle == 0
|
||||
assert device.max_angle == 90
|
||||
|
||||
def test_angular_servo_angles():
|
||||
p = MockPWMPin(1)
|
||||
p = Device.pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
with AngularServo(p) as device:
|
||||
device.angle = 0
|
||||
assert device.angle == 0
|
||||
|
||||
@@ -11,44 +11,42 @@ import re
|
||||
import pytest
|
||||
from mock import patch, MagicMock
|
||||
|
||||
import gpiozero.devices
|
||||
import gpiozero.pins.data
|
||||
import gpiozero.pins.native
|
||||
from gpiozero.pins.data import pi_info, Style, HeaderInfo, PinInfo
|
||||
from gpiozero import PinMultiplePins, PinNoPins, PinUnknownPi
|
||||
import gpiozero.pins.local
|
||||
from gpiozero.pins.local import LocalPiFactory
|
||||
from gpiozero.pins.data import Style, HeaderInfo, PinInfo
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
def test_pi_revision():
|
||||
save_factory = gpiozero.devices.pin_factory
|
||||
try:
|
||||
with patch('gpiozero.devices.Device.pin_factory', LocalPiFactory()):
|
||||
# Can't use MockPin for this as we want something that'll actually try
|
||||
# and read /proc/cpuinfo (MockPin simply parrots the 2B's data);
|
||||
# NativePin is used as we're guaranteed to be able to import it
|
||||
gpiozero.devices.pin_factory = gpiozero.pins.native.NativePin
|
||||
# LocalPiFactory is used as we can definitely instantiate it (strictly
|
||||
# speaking it's abstract but we're only interested in the pi_info
|
||||
# stuff)
|
||||
with patch('io.open') as m:
|
||||
m.return_value.__enter__.return_value = ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial: xxxxxxxxxxx']
|
||||
assert pi_info().revision == '0002'
|
||||
# LocalPin caches the revision (because realistically it isn't going to
|
||||
# change at runtime); we need to wipe it here though
|
||||
gpiozero.pins.native.NativePin._PI_REVISION = None
|
||||
# LocalPiFactory caches the revision (because realistically it
|
||||
# isn't going to change at runtime); we need to wipe it here though
|
||||
Device.pin_factory._info = None
|
||||
m.return_value.__enter__.return_value = ['Revision: a21042']
|
||||
assert pi_info().revision == 'a21042'
|
||||
# Check over-volting result (some argument over whether this is 7 or
|
||||
# 8 character result; make sure both work)
|
||||
gpiozero.pins.native.NativePin._PI_REVISION = None
|
||||
Device.pin_factory._info = None
|
||||
m.return_value.__enter__.return_value = ['Revision: 1000003']
|
||||
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']
|
||||
assert pi_info().revision == '0003'
|
||||
with pytest.raises(PinUnknownPi):
|
||||
m.return_value.__enter__.return_value = ['nothing', 'relevant', 'at all']
|
||||
gpiozero.pins.native.NativePin._PI_REVISION = None
|
||||
Device.pin_factory._info = None
|
||||
pi_info()
|
||||
with pytest.raises(PinUnknownPi):
|
||||
pi_info('0fff')
|
||||
finally:
|
||||
gpiozero.devices.pin_factory = save_factory
|
||||
|
||||
def test_pi_info():
|
||||
r = pi_info('900011')
|
||||
@@ -73,14 +71,14 @@ def test_pi_info_other_types():
|
||||
|
||||
def test_physical_pins():
|
||||
# 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('GPIO2') == {('P1', 3)}
|
||||
assert pi_info('a21041').physical_pins('3V3') == {('J8', 1), ('J8', 17)}
|
||||
assert pi_info('a21041').physical_pins('GPIO2') == {('J8', 3)}
|
||||
assert pi_info('a21041').physical_pins('GPIO47') == set()
|
||||
|
||||
def test_physical_pin():
|
||||
with pytest.raises(PinMultiplePins):
|
||||
assert pi_info('a21041').physical_pin('GND')
|
||||
assert pi_info('a21041').physical_pin('GPIO3') == ('P1', 5)
|
||||
assert pi_info('a21041').physical_pin('GPIO3') == ('J8', 5)
|
||||
with pytest.raises(PinNoPins):
|
||||
assert pi_info('a21041').physical_pin('GPIO47')
|
||||
|
||||
@@ -114,6 +112,18 @@ def test_pprint_content():
|
||||
pi_info('0014').headers['SODIMM'].pprint(color=False)
|
||||
assert len(''.join(stdout.output).splitlines()) == 100
|
||||
|
||||
def test_format_content():
|
||||
with patch('sys.stdout') as stdout:
|
||||
stdout.output = []
|
||||
stdout.write = lambda buf: stdout.output.append(buf)
|
||||
pi_info('900092').pprint(color=False)
|
||||
s = ''.join(stdout.output)
|
||||
assert '{0:mono}\n'.format(pi_info('900092')) == s
|
||||
stdout.output = []
|
||||
pi_info('900092').pprint(color=True)
|
||||
s = ''.join(stdout.output)
|
||||
assert '{0:color full}\n'.format(pi_info('900092')) == s
|
||||
|
||||
def test_pprint_headers():
|
||||
assert len(pi_info('0002').headers) == 1
|
||||
assert len(pi_info('000e').headers) == 2
|
||||
@@ -133,7 +143,8 @@ def test_pprint_headers():
|
||||
stdout.output = []
|
||||
pi_info('900092').pprint()
|
||||
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
|
||||
|
||||
def test_pprint_color():
|
||||
@@ -194,11 +205,12 @@ def test_pprint_missing_pin():
|
||||
assert ('(%d)' % i)
|
||||
|
||||
def test_pprint_rows_cols():
|
||||
assert '{0:row1}'.format(pi_info('900092').headers['P1']) == '1o'
|
||||
assert '{0:row2}'.format(pi_info('900092').headers['P1']) == 'oo'
|
||||
assert '{0:row1}'.format(pi_info('900092').headers['J8']) == '1o'
|
||||
assert '{0:row2}'.format(pi_info('900092').headers['J8']) == 'oo'
|
||||
assert '{0:col1}'.format(pi_info('0002').headers['P1']) == '1oooooooooooo'
|
||||
assert '{0:col2}'.format(pi_info('0002').headers['P1']) == 'ooooooooooooo'
|
||||
with pytest.raises(ValueError):
|
||||
'{0:row16}'.format(pi_info('0002').headers['P1'])
|
||||
with pytest.raises(ValueError):
|
||||
'{0:col3}'.format(pi_info('0002').headers['P1'])
|
||||
|
||||
|
||||
@@ -11,80 +11,66 @@ except NameError:
|
||||
pass
|
||||
|
||||
import io
|
||||
import subprocess
|
||||
import os
|
||||
from time import sleep
|
||||
|
||||
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:
|
||||
from math import isclose
|
||||
except ImportError:
|
||||
from gpiozero.compat import isclose
|
||||
|
||||
|
||||
# This module assumes you've wired the following GPIO pins together
|
||||
TEST_PIN = 22
|
||||
INPUT_PIN = 27
|
||||
# This module assumes you've wired the following GPIO pins together. The pins
|
||||
# can be re-configured via the listed environment variables (useful for when
|
||||
# your testing rig requires different pins because the defaults interfere with
|
||||
# attached hardware).
|
||||
TEST_PIN = int(os.getenv('GPIOZERO_TEST_PIN', '22'))
|
||||
INPUT_PIN = int(os.getenv('GPIOZERO_TEST_INPUT_PIN', '27'))
|
||||
|
||||
|
||||
# Skip the entire module if we're not on a Pi
|
||||
def is_a_pi():
|
||||
with io.open('/proc/cpuinfo', 'r') as cpuinfo:
|
||||
for line in cpuinfo:
|
||||
if line.startswith('Hardware'):
|
||||
hardware, colon, soc = line.strip().split(None, 2)
|
||||
return soc in ('BCM2708', 'BCM2709', 'BCM2835', 'BCM2836')
|
||||
else:
|
||||
return False
|
||||
pytestmark = pytest.mark.skipif(not is_a_pi(), reason='tests cannot run on non-Pi platforms')
|
||||
del is_a_pi
|
||||
@pytest.fixture(
|
||||
scope='module',
|
||||
params=[
|
||||
name
|
||||
for name in pkg_resources.get_distribution('gpiozero').get_entry_map('gpiozero_pin_factories').keys()
|
||||
if not name.endswith('Pin') # leave out compatibility names
|
||||
])
|
||||
def pin_factory(request):
|
||||
try:
|
||||
factory = pkg_resources.load_entry_point('gpiozero', 'gpiozero_pin_factories', request.param)()
|
||||
except Exception as e:
|
||||
pytest.skip("skipped factory %s: %s" % (request.param, str(e)))
|
||||
else:
|
||||
Device.pin_factory = factory
|
||||
def fin():
|
||||
Device.pin_factory = MockFactory()
|
||||
request.addfinalizer(fin)
|
||||
return factory
|
||||
|
||||
|
||||
# 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')
|
||||
def pins(request, pin_class):
|
||||
def pins(request, pin_factory):
|
||||
# Why return both pins in a single fixture? If we defined one fixture for
|
||||
# each pin then pytest will (correctly) test RPiGPIOPin(22) against
|
||||
# NativePin(27) and so on. This isn't supported, so we don't test it
|
||||
test_pin = pin_class(TEST_PIN)
|
||||
input_pin = pin_class(INPUT_PIN)
|
||||
input_pin = pin_factory.pin(INPUT_PIN)
|
||||
input_pin.function = 'input'
|
||||
input_pin.pull = 'down'
|
||||
if pin_factory.__class__.__name__ == 'MockFactory':
|
||||
test_pin = pin_factory.pin(TEST_PIN, pin_class=MockConnectedPin, input_pin=input_pin)
|
||||
else:
|
||||
test_pin = pin_factory.pin(TEST_PIN)
|
||||
def fin():
|
||||
test_pin.close()
|
||||
input_pin.close()
|
||||
@@ -134,17 +120,18 @@ def test_pull_bad(pins):
|
||||
with pytest.raises(PinInvalidPull):
|
||||
test_pin.input_with_pull('foo')
|
||||
|
||||
def test_pull_down_warning(pin_class):
|
||||
# XXX This assumes we're on a vaguely modern Pi and not a compute module
|
||||
# Might want to refine this with the pi-info database
|
||||
pin = pin_class(2)
|
||||
try:
|
||||
with pytest.raises(PinFixedPull):
|
||||
pin.pull = 'down'
|
||||
with pytest.raises(PinFixedPull):
|
||||
pin.input_with_pull('down')
|
||||
finally:
|
||||
pin.close()
|
||||
def test_pull_down_warning(pin_factory):
|
||||
if pin_factory.pi_info.pulled_up('GPIO2'):
|
||||
pin = pin_factory.pin(2)
|
||||
try:
|
||||
with pytest.raises(PinFixedPull):
|
||||
pin.pull = 'down'
|
||||
with pytest.raises(PinFixedPull):
|
||||
pin.input_with_pull('down')
|
||||
finally:
|
||||
pin.close()
|
||||
else:
|
||||
pytest.skip("GPIO2 isn't pulled up on this pi")
|
||||
|
||||
def test_input_with_pull(pins):
|
||||
test_pin, input_pin = pins
|
||||
@@ -153,25 +140,42 @@ def test_input_with_pull(pins):
|
||||
test_pin.input_with_pull('down')
|
||||
assert input_pin.state == 0
|
||||
|
||||
@pytest.mark.skipif(True, reason='causes segfaults')
|
||||
def test_bad_duty_cycle(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.frequency = 100
|
||||
with pytest.raises(ValueError):
|
||||
test_pin.state = 1.1
|
||||
try:
|
||||
# NOTE: There's some race in RPi.GPIO that causes a segfault if we
|
||||
# don't pause before starting PWM; only seems to happen when stopping
|
||||
# and restarting PWM very rapidly (i.e. between test cases).
|
||||
if Device.pin_factory.__class__.__name__ == 'RPiGPIOFactory':
|
||||
sleep(0.1)
|
||||
test_pin.frequency = 100
|
||||
except PinPWMUnsupported:
|
||||
pytest.skip("%r doesn't support PWM" % test_pin.factory)
|
||||
else:
|
||||
try:
|
||||
with pytest.raises(ValueError):
|
||||
test_pin.state = 1.1
|
||||
finally:
|
||||
test_pin.frequency = None
|
||||
|
||||
def test_duty_cycles(pins):
|
||||
test_pin, input_pin = pins
|
||||
if test_pin.__class__.__name__ == 'NativePin':
|
||||
pytest.skip("native pin doesn't support PWM")
|
||||
test_pin.function = 'output'
|
||||
test_pin.frequency = 100
|
||||
for duty_cycle in (0.0, 0.1, 0.5, 1.0):
|
||||
test_pin.state = duty_cycle
|
||||
assert test_pin.state == duty_cycle
|
||||
total = sum(input_pin.state for i in range(20000))
|
||||
assert isclose(total / 20000, duty_cycle, rel_tol=0.1, abs_tol=0.1)
|
||||
try:
|
||||
# NOTE: see above
|
||||
if Device.pin_factory.__class__.__name__ == 'RPiGPIOFactory':
|
||||
sleep(0.1)
|
||||
test_pin.frequency = 100
|
||||
except PinPWMUnsupported:
|
||||
pytest.skip("%r doesn't support PWM" % test_pin.factory)
|
||||
else:
|
||||
try:
|
||||
for duty_cycle in (0.0, 0.1, 0.5, 1.0):
|
||||
test_pin.state = duty_cycle
|
||||
assert test_pin.state == duty_cycle
|
||||
total = sum(input_pin.state for i in range(20000))
|
||||
assert isclose(total / 20000, duty_cycle, rel_tol=0.1, abs_tol=0.1)
|
||||
finally:
|
||||
test_pin.frequency = None
|
||||
|
||||
|
||||
@@ -4,93 +4,114 @@ from __future__ import (
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
nstr = str
|
||||
str = type('')
|
||||
|
||||
|
||||
import sys
|
||||
import mock
|
||||
import pytest
|
||||
from array import array
|
||||
from mock import patch
|
||||
from collections import namedtuple
|
||||
|
||||
from gpiozero.pins.native import NativeFactory
|
||||
from gpiozero.pins.local import (
|
||||
LocalPiHardwareSPI,
|
||||
LocalPiSoftwareSPI,
|
||||
LocalPiHardwareSPIShared,
|
||||
LocalPiSoftwareSPIShared,
|
||||
)
|
||||
from gpiozero.pins.mock import MockSPIDevice
|
||||
from gpiozero import *
|
||||
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):
|
||||
MockPin.clear_pins()
|
||||
Device.pin_factory.reset()
|
||||
|
||||
|
||||
def test_spi_hardware_params():
|
||||
with mock.patch('gpiozero.spi.SpiDev') as spidev:
|
||||
with SPI() as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(port=0, device=0) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(port=0, device=1) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(clock_pin=11) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(clock_pin=11, mosi_pin=10, select_pin=8) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(clock_pin=11, mosi_pin=10, select_pin=7) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(shared=True) as device:
|
||||
assert isinstance(device, SharedSPIHardwareInterface)
|
||||
with pytest.raises(ValueError):
|
||||
SPI(port=1)
|
||||
with pytest.raises(ValueError):
|
||||
SPI(device=2)
|
||||
with pytest.raises(ValueError):
|
||||
SPI(port=0, clock_pin=12)
|
||||
with pytest.raises(ValueError):
|
||||
SPI(foo='bar')
|
||||
with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open:
|
||||
mmap_mmap.return_value = array(nstr('B'), (0,) * 4096)
|
||||
io_open.return_value.__enter__.return_value = ['Revision: a21042']
|
||||
with patch('gpiozero.devices.Device.pin_factory', NativeFactory()), \
|
||||
patch('gpiozero.pins.local.SpiDev'):
|
||||
with Device.pin_factory.spi() as device:
|
||||
assert isinstance(device, LocalPiHardwareSPI)
|
||||
with Device.pin_factory.spi(port=0, device=0) as device:
|
||||
assert isinstance(device, LocalPiHardwareSPI)
|
||||
with Device.pin_factory.spi(port=0, device=1) as device:
|
||||
assert isinstance(device, LocalPiHardwareSPI)
|
||||
with Device.pin_factory.spi(clock_pin=11) as device:
|
||||
assert isinstance(device, LocalPiHardwareSPI)
|
||||
with Device.pin_factory.spi(clock_pin=11, mosi_pin=10, select_pin=8) as device:
|
||||
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):
|
||||
Device.pin_factory.spi(port=1)
|
||||
with pytest.raises(ValueError):
|
||||
Device.pin_factory.spi(device=2)
|
||||
with pytest.raises(ValueError):
|
||||
Device.pin_factory.spi(port=0, clock_pin=12)
|
||||
with pytest.raises(ValueError):
|
||||
Device.pin_factory.spi(foo='bar')
|
||||
|
||||
def test_spi_software_params():
|
||||
with mock.patch('gpiozero.spi.SpiDev') as spidev:
|
||||
with SPI(select_pin=6) as device:
|
||||
assert isinstance(device, SPISoftwareInterface)
|
||||
with SPI(clock_pin=11, mosi_pin=9, miso_pin=10) as device:
|
||||
assert isinstance(device, SPISoftwareInterface)
|
||||
with SPI(select_pin=6, shared=True) as device:
|
||||
assert isinstance(device, SharedSPISoftwareInterface)
|
||||
# Ensure software fallback works when SpiDev isn't present
|
||||
with SPI() as device:
|
||||
assert isinstance(device, SPISoftwareInterface)
|
||||
with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open:
|
||||
mmap_mmap.return_value = array(nstr('B'), (0,) * 4096)
|
||||
io_open.return_value.__enter__.return_value = ['Revision: a21042']
|
||||
with patch('gpiozero.devices.Device.pin_factory', NativeFactory()), \
|
||||
patch('gpiozero.pins.local.SpiDev'):
|
||||
with Device.pin_factory.spi(select_pin=6) as device:
|
||||
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
|
||||
with Device.pin_factory.spi() as device:
|
||||
assert isinstance(device, LocalPiSoftwareSPI)
|
||||
|
||||
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 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():
|
||||
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)]
|
||||
with SPI() as device:
|
||||
with Device.pin_factory.spi() as device:
|
||||
assert device.read(3) == [0, 1, 2]
|
||||
assert device.read(6) == list(range(6))
|
||||
|
||||
def test_spi_hardware_write():
|
||||
with 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)]
|
||||
with SPI() as device:
|
||||
with Device.pin_factory.spi() as device:
|
||||
assert device.write([0, 1, 2]) == 3
|
||||
assert spidev.return_value.xfer2.called_with([0, 1, 2])
|
||||
assert device.write(list(range(6))) == 6
|
||||
assert spidev.return_value.xfer2.called_with(list(range(6)))
|
||||
|
||||
def test_spi_hardware_modes():
|
||||
with mock.patch('gpiozero.spi.SpiDev') as spidev:
|
||||
with patch('gpiozero.pins.local.SpiDev') as spidev:
|
||||
spidev.return_value.mode = 0
|
||||
spidev.return_value.lsbfirst = False
|
||||
spidev.return_value.cshigh = True
|
||||
spidev.return_value.bits_per_word = 8
|
||||
with SPI() as device:
|
||||
with Device.pin_factory.spi() as device:
|
||||
assert device.clock_mode == 0
|
||||
assert not device.clock_polarity
|
||||
assert not device.clock_phase
|
||||
@@ -116,7 +137,9 @@ def test_spi_software_read():
|
||||
super(SPISlave, self).on_start()
|
||||
for i in range(10):
|
||||
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(6) == [0, 1, 2, 3, 4, 5]
|
||||
slave.clock_phase = True
|
||||
@@ -125,7 +148,9 @@ def test_spi_software_read():
|
||||
assert master.read(6) == [0, 1, 2, 3, 4, 5]
|
||||
|
||||
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])
|
||||
assert test_device.rx_word() == 0
|
||||
master.write([2, 0])
|
||||
@@ -134,7 +159,8 @@ def test_spi_software_write():
|
||||
assert test_device.rx_word() == 257
|
||||
|
||||
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 not master.clock_polarity
|
||||
assert not master.clock_phase
|
||||
@@ -151,7 +177,8 @@ def test_spi_software_clock_mode():
|
||||
master.clock_mode = 5
|
||||
|
||||
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.select_high
|
||||
assert master.bits_per_word == 8
|
||||
|
||||
@@ -9,22 +9,19 @@ str = type('')
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
from mock import patch
|
||||
from collections import namedtuple
|
||||
try:
|
||||
from math import isclose
|
||||
except ImportError:
|
||||
from gpiozero.compat import isclose
|
||||
|
||||
from gpiozero import *
|
||||
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):
|
||||
MockPin.clear_pins()
|
||||
Device.pin_factory.reset()
|
||||
|
||||
def clamp(v, min_value, max_value):
|
||||
return min(max_value, max(min_value, v))
|
||||
@@ -251,89 +248,100 @@ def differential_mcp_test(mock, pot, pos_channel, neg_channel, bits, full=False)
|
||||
|
||||
|
||||
def test_MCP3001():
|
||||
mock = MockMCP3001(11, 10, 9, 8)
|
||||
with MCP3001() as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 10)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3001(11, 10, 9, 8)
|
||||
with MCP3001() as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 10)
|
||||
|
||||
def test_MCP3002():
|
||||
mock = MockMCP3002(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3002(channel=5)
|
||||
with MCP3002(channel=1) as pot:
|
||||
single_mcp_test(mock, pot, 1, 10)
|
||||
with MCP3002(channel=1, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 1, 0, 10)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3002(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3002(channel=5)
|
||||
with MCP3002(channel=1) as pot:
|
||||
single_mcp_test(mock, pot, 1, 10)
|
||||
with MCP3002(channel=1, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 1, 0, 10)
|
||||
|
||||
def test_MCP3004():
|
||||
mock = MockMCP3004(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3004(channel=5)
|
||||
with MCP3004(channel=3) as pot:
|
||||
single_mcp_test(mock, pot, 3, 10)
|
||||
with MCP3004(channel=3, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 3, 2, 10)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3004(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3004(channel=5)
|
||||
with MCP3004(channel=3) as pot:
|
||||
single_mcp_test(mock, pot, 3, 10)
|
||||
with MCP3004(channel=3, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 3, 2, 10)
|
||||
|
||||
def test_MCP3008():
|
||||
mock = MockMCP3008(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3008(channel=9)
|
||||
with MCP3008(channel=0) as pot:
|
||||
single_mcp_test(mock, pot, 0, 10)
|
||||
with MCP3008(channel=0, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 10)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3008(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3008(channel=9)
|
||||
with MCP3008(channel=0) as pot:
|
||||
single_mcp_test(mock, pot, 0, 10)
|
||||
with MCP3008(channel=0, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 10)
|
||||
|
||||
def test_MCP3201():
|
||||
mock = MockMCP3201(11, 10, 9, 8)
|
||||
with MCP3201() as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 12)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3201(11, 10, 9, 8)
|
||||
with MCP3201() as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 12)
|
||||
|
||||
def test_MCP3202():
|
||||
mock = MockMCP3202(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3202(channel=5)
|
||||
with MCP3202(channel=1) as pot:
|
||||
single_mcp_test(mock, pot, 1, 12)
|
||||
with MCP3202(channel=1, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 1, 0, 12)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3202(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3202(channel=5)
|
||||
with MCP3202(channel=1) as pot:
|
||||
single_mcp_test(mock, pot, 1, 12)
|
||||
with MCP3202(channel=1, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 1, 0, 12)
|
||||
|
||||
def test_MCP3204():
|
||||
mock = MockMCP3204(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3204(channel=5)
|
||||
with MCP3204(channel=1) as pot:
|
||||
single_mcp_test(mock, pot, 1, 12)
|
||||
with MCP3204(channel=1, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 1, 0, 12)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3204(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3204(channel=5)
|
||||
with MCP3204(channel=1) as pot:
|
||||
single_mcp_test(mock, pot, 1, 12)
|
||||
with MCP3204(channel=1, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 1, 0, 12)
|
||||
|
||||
def test_MCP3208():
|
||||
mock = MockMCP3208(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3208(channel=9)
|
||||
with MCP3208(channel=7) as pot:
|
||||
single_mcp_test(mock, pot, 7, 12)
|
||||
with MCP3208(channel=7, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 7, 6, 12)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3208(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3208(channel=9)
|
||||
with MCP3208(channel=7) as pot:
|
||||
single_mcp_test(mock, pot, 7, 12)
|
||||
with MCP3208(channel=7, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 7, 6, 12)
|
||||
|
||||
def test_MCP3301():
|
||||
mock = MockMCP3301(11, 10, 9, 8)
|
||||
with MCP3301() as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 12, full=True)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3301(11, 10, 9, 8)
|
||||
with MCP3301() as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 12, full=True)
|
||||
|
||||
def test_MCP3302():
|
||||
mock = MockMCP3302(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3302(channel=4)
|
||||
with MCP3302(channel=0) as pot:
|
||||
single_mcp_test(mock, pot, 0, 12)
|
||||
with MCP3302(channel=0, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 12, full=True)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3302(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3302(channel=4)
|
||||
with MCP3302(channel=0) as pot:
|
||||
single_mcp_test(mock, pot, 0, 12)
|
||||
with MCP3302(channel=0, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 0, 1, 12, full=True)
|
||||
|
||||
def test_MCP3304():
|
||||
mock = MockMCP3304(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3304(channel=9)
|
||||
with MCP3304(channel=5) as pot:
|
||||
single_mcp_test(mock, pot, 5, 12)
|
||||
with MCP3304(channel=5, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 5, 4, 12, full=True)
|
||||
with patch('gpiozero.pins.local.SpiDev', None):
|
||||
mock = MockMCP3304(11, 10, 9, 8)
|
||||
with pytest.raises(ValueError):
|
||||
MCP3304(channel=9)
|
||||
with MCP3304(channel=5) as pot:
|
||||
single_mcp_test(mock, pot, 5, 12)
|
||||
with MCP3304(channel=5, differential=True) as pot:
|
||||
differential_mcp_test(mock, pot, 5, 4, 12, full=True)
|
||||
|
||||
|
||||