Merge pull request #239 from waveform80/energenie
Fix #140, fix #69, fix #185
@@ -18,7 +18,7 @@ individually.
|
||||
LED Board
|
||||
=========
|
||||
|
||||
.. autoclass:: LEDBoard(\*pins, pwm=False)
|
||||
.. autoclass:: LEDBoard(\*pins, pwm=False, active_high=True, initial_value=False)
|
||||
:inherited-members:
|
||||
:members:
|
||||
|
||||
|
||||
@@ -34,10 +34,25 @@ so you can still do::
|
||||
print('Bad value specified')
|
||||
|
||||
|
||||
Errors
|
||||
======
|
||||
|
||||
.. autoexception:: GPIOZeroError
|
||||
|
||||
.. autoexception:: DeviceClosed
|
||||
|
||||
.. autoexception:: CompositeDeviceError
|
||||
|
||||
.. autoexception:: CompositeDeviceBadName
|
||||
|
||||
.. autoexception:: EnergenieSocketMissing
|
||||
|
||||
.. autoexception:: EnergenieBadSocket
|
||||
|
||||
.. autoexception:: SPIError
|
||||
|
||||
.. autoexception:: SPIBadArgs
|
||||
|
||||
.. autoexception:: GPIODeviceError
|
||||
|
||||
.. autoexception:: GPIODeviceClosed
|
||||
@@ -80,3 +95,12 @@ so you can still do::
|
||||
|
||||
.. autoexception:: PinPWMFixedValue
|
||||
|
||||
Warnings
|
||||
========
|
||||
|
||||
.. autoexception:: GPIOZeroWarning
|
||||
|
||||
.. autoexception:: SPIWarning
|
||||
|
||||
.. autoexception:: SPISoftwareFallback
|
||||
|
||||
|
||||
@@ -4,15 +4,22 @@ Generic Devices
|
||||
|
||||
.. currentmodule:: gpiozero
|
||||
|
||||
The GPIO Zero class hierarchy is quite extensive. It contains a couple of base
|
||||
The GPIO Zero class hierarchy is quite extensive. It contains several base
|
||||
classes:
|
||||
|
||||
* :class:`GPIODevice` for individual devices that attach to a single GPIO pin
|
||||
* :class:`Device` is the root of the hierarchy, implementing base functionality
|
||||
like :meth:`~Device.close` and context manager handlers.
|
||||
|
||||
* :class:`CompositeDevice` for devices composed of multiple other devices like
|
||||
HATs
|
||||
* :class:`GPIODevice` represents individual devices that attach to a single
|
||||
GPIO pin
|
||||
|
||||
There are also a couple of `mixin classes`_:
|
||||
* :class:`SPIDevice` represents devices that communicate over an SPI interface
|
||||
(implemented as four GPIO pins)
|
||||
|
||||
* :class:`CompositeDevice` represents devices composed of multiple other
|
||||
devices like HATs
|
||||
|
||||
There are also several `mixin classes`_:
|
||||
|
||||
* :class:`ValuesMixin` which defines the ``values`` properties; there is rarely
|
||||
a need to use this as the base classes mentioned above both include it
|
||||
@@ -21,13 +28,27 @@ There are also a couple of `mixin classes`_:
|
||||
* :class:`SourceMixin` which defines the ``source`` property; this is generally
|
||||
included in novel output device classes
|
||||
|
||||
* :class:`SharedMixin` which causes classes to track their construction and
|
||||
return existing instances when equivalent constructor arguments are passed
|
||||
|
||||
.. _mixin classes: https://en.wikipedia.org/wiki/Mixin
|
||||
|
||||
The current class hierarchies are displayed below. For brevity, the mixin
|
||||
classes are omitted:
|
||||
classes (and some other details) are omitted, and the chart is broken into
|
||||
pieces by base class. The lighter boxes represent classes that are "effectively
|
||||
abstract". These classes aren't directly useful without sub-classing them and
|
||||
adding bits.
|
||||
|
||||
First, the classes below :class:`GPIODevice`:
|
||||
|
||||
.. image:: images/gpio_device_hierarchy.*
|
||||
|
||||
Next, the classes below :class:`SPIDevice`:
|
||||
|
||||
.. image:: images/spi_device_hierarchy.*
|
||||
|
||||
Next, the classes below :class:`CompositeDevice`:
|
||||
|
||||
.. image:: images/composite_device_hierarchy.*
|
||||
|
||||
Finally, for composite devices, the following chart shows which devices are
|
||||
@@ -38,12 +59,16 @@ composed of which other devices:
|
||||
Base Classes
|
||||
============
|
||||
|
||||
.. autoclass:: Device
|
||||
:members: close, closed
|
||||
|
||||
.. autoclass:: GPIODevice(pin)
|
||||
:inherited-members:
|
||||
:members:
|
||||
|
||||
.. autoclass:: CompositeDevice
|
||||
:inherited-members:
|
||||
:members:
|
||||
|
||||
.. autoclass:: SPIDevice
|
||||
:members:
|
||||
|
||||
Input Devices
|
||||
@@ -61,19 +86,34 @@ Input Devices
|
||||
.. autoclass:: SmoothedInputDevice
|
||||
:members:
|
||||
|
||||
.. autoclass:: AnalogInputDevice
|
||||
:members:
|
||||
|
||||
Output Devices
|
||||
==============
|
||||
|
||||
.. autoclass:: OutputDevice(pin, active_high=True, initial_value=False)
|
||||
:members:
|
||||
|
||||
.. autoclass:: DigitalOutputDevice(pin, active_high=True, initial_value=False)
|
||||
:members:
|
||||
|
||||
.. autoclass:: PWMOutputDevice(pin, active_high=True, initial_value=0, frequency=100)
|
||||
:members:
|
||||
|
||||
.. autoclass:: DigitalOutputDevice(pin, active_high=True, initial_value=False)
|
||||
SPI Devices
|
||||
===========
|
||||
|
||||
.. autoclass:: SPIDevice
|
||||
:members:
|
||||
|
||||
.. autoclass:: AnalogInputDevice
|
||||
:members:
|
||||
|
||||
Composite Devices
|
||||
=================
|
||||
|
||||
.. autoclass:: CompositeOutputDevice
|
||||
:members:
|
||||
|
||||
.. autoclass:: LEDCollection
|
||||
:members:
|
||||
|
||||
Mixin Classes
|
||||
@@ -85,3 +125,6 @@ Mixin Classes
|
||||
.. autoclass:: SourceMixin(...)
|
||||
:members:
|
||||
|
||||
.. autoclass:: SharedMixin(...)
|
||||
:members: _shared_key
|
||||
|
||||
|
||||
@@ -40,28 +40,3 @@ Distance Sensor (HC-SR04)
|
||||
.. autoclass:: DistanceSensor(echo, trigger, queue_len=30, max_distance=1, threshold_distance=0.3, partial=False)
|
||||
:members: wait_for_in_range, wait_for_out_of_range, trigger, echo, when_in_range, when_out_of_range, max_distance, distance, threshold_distance
|
||||
|
||||
|
||||
Analog to Digital Converters (ADC)
|
||||
==================================
|
||||
|
||||
.. autoclass:: MCP3004
|
||||
:members: bus, device, channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3008
|
||||
:members: bus, device, channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3204
|
||||
:members: bus, device, channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3208
|
||||
:members: bus, device, channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3301
|
||||
:members: bus, device, value
|
||||
|
||||
.. autoclass:: MCP3302
|
||||
:members: bus, device, channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3304
|
||||
:members: bus, device, channel, value, differential
|
||||
|
||||
|
||||
106
docs/api_spi.rst
Normal file
@@ -0,0 +1,106 @@
|
||||
===========
|
||||
SPI Devices
|
||||
===========
|
||||
|
||||
.. currentmodule:: gpiozero
|
||||
|
||||
SPI stands for `Serial Peripheral Interface`_ and is a mechanism allowing
|
||||
compatible devices to communicate with the Pi. SPI is a four-wire protocol
|
||||
meaning it usually requires four pins to operate:
|
||||
|
||||
* A "clock" pin which provides timing information.
|
||||
|
||||
* A "MOSI" pin (Master Out, Slave In) which the Pi uses to send information
|
||||
to the device.
|
||||
|
||||
* A "MISO" pin (Master In, Slave Out) which the Pi uses to receive information
|
||||
from the device.
|
||||
|
||||
* A "select" pin which the Pi uses to indicate which device it's talking to.
|
||||
This last pin is necessary because multiple devices can share the clock,
|
||||
MOSI, and MISO pins, but only one device can be connected to each select
|
||||
pin.
|
||||
|
||||
The gpiozero library provides two SPI implementations:
|
||||
|
||||
* A software based implementation. This is always available, can use any four
|
||||
GPIO pins for SPI communication, but is rather slow and won't work with all
|
||||
devices.
|
||||
|
||||
* A hardware based implementation. This is only available when the SPI kernel
|
||||
module is loaded, and the Python spidev library is available. It can only use
|
||||
specific pins for SPI communication (GPIO11=clock, GPIO10=MOSI, GPIO9=MISO,
|
||||
while GPIO8 is select for device 0 and GPIO7 is select for device 1).
|
||||
However, it is extremely fast and works with all devices.
|
||||
|
||||
.. _Serial Peripheral Interface: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
|
||||
|
||||
|
||||
.. _spi_args:
|
||||
|
||||
SPI keyword args
|
||||
================
|
||||
|
||||
When constructing an SPI device the are two schemes for specifying which pins
|
||||
it is connected to:
|
||||
|
||||
* You can specify *port* and *device* keyword arguments. The *port* parameter
|
||||
must be 0 (there is only one user-accessible hardware SPI interface on the Pi
|
||||
using GPIO11 as the clock pin, GPIO10 as the MOSI pin, and GPIO9 as the MISO
|
||||
pin), while the *device* parameter must be 0 or 1. If *device* is 0, the
|
||||
select pin will be GPIO8. If *device* is 1, the select pin will be GPIO7.
|
||||
|
||||
* Alternatively you can specify *clock_pin*, *mosi_pin*, *miso_pin*, and
|
||||
*select_pin* keyword arguments. In this case the pins can be any 4 GPIO pins
|
||||
(remember that SPI devices can share clock, MOSI, and MISO pins, but not
|
||||
select pins - the gpiozero library will enforce this restriction).
|
||||
|
||||
You cannot mix these two schemes, i.e. attempting to specify *port* and
|
||||
*clock_pin* will result in :exc:`SPIBadArgs` being raised. However, you can
|
||||
omit any arguments from either scheme. The defaults are:
|
||||
|
||||
* *port* and *device* both default to 0.
|
||||
|
||||
* *clock_pin* defaults to 11, *mosi_pin* defaults to 10, *miso_pin* defaults
|
||||
to 9, and *select_pin* defaults to 8.
|
||||
|
||||
Hence the following constructors are all equiavlent::
|
||||
|
||||
from gpiozero import MCP3008
|
||||
|
||||
MCP3008(channel=0)
|
||||
MCP3008(channel=0, device=0)
|
||||
MCP3008(channel=0, port=0, device=0)
|
||||
MCP3008(channel=0, select_pin=8)
|
||||
MCP3008(channel=0, clock_pin=11, mosi_pin=10, miso_pin=9, select_pin=8)
|
||||
|
||||
Note that the defaults describe equivalent sets of pins and that these pins are
|
||||
compatible with the hardware implementation. Regardless of which scheme you
|
||||
use, gpiozero will attempt to use the hardware implementation if it is
|
||||
available and if the selected pins are compatible, falling back to the software
|
||||
implementation if not.
|
||||
|
||||
Analog to Digital Converters (ADC)
|
||||
==================================
|
||||
|
||||
.. autoclass:: MCP3004
|
||||
:members: channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3008
|
||||
:members: channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3204
|
||||
:members: channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3208
|
||||
:members: channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3301
|
||||
:members: value
|
||||
|
||||
.. autoclass:: MCP3302
|
||||
:members: channel, value, differential
|
||||
|
||||
.. autoclass:: MCP3304
|
||||
:members: channel, value, differential
|
||||
|
||||
@@ -39,6 +39,7 @@ sys.modules['RPi'] = Mock()
|
||||
sys.modules['RPi.GPIO'] = sys.modules['RPi'].GPIO
|
||||
sys.modules['RPIO'] = Mock()
|
||||
sys.modules['RPIO.PWM'] = sys.modules['RPIO'].PWM
|
||||
sys.modules['RPIO.Exceptions'] = sys.modules['RPIO'].Exceptions
|
||||
sys.modules['pigpio'] = Mock()
|
||||
sys.modules['w1thermsensor'] = Mock()
|
||||
sys.modules['spidev'] = Mock()
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -2,32 +2,36 @@
|
||||
|
||||
digraph classes {
|
||||
graph [rankdir=BT];
|
||||
node [shape=rect, style=filled, color="#2980b9", fontname=Sans, fontcolor="#ffffff", fontsize=10];
|
||||
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
|
||||
edge [];
|
||||
|
||||
AnalogInputDevice->CompositeDevice;
|
||||
MCP3xxx->AnalogInputDevice;
|
||||
MCP33xx->MCP3xxx;
|
||||
MCP3004->MCP3xxx;
|
||||
MCP3008->MCP3xxx;
|
||||
MCP3204->MCP3xxx;
|
||||
MCP3208->MCP3xxx;
|
||||
MCP3301->MCP33xx;
|
||||
MCP3302->MCP33xx;
|
||||
MCP3304->MCP33xx;
|
||||
/* Abstract classes */
|
||||
node [color="#9ec6e0", fontcolor="#000000"]
|
||||
Device;
|
||||
CompositeDevice;
|
||||
CompositeOutputDevice;
|
||||
LEDCollection;
|
||||
|
||||
/* Concrete classes */
|
||||
node [color="#2980b9", fontcolor="#ffffff"];
|
||||
CompositeDevice->Device;
|
||||
CompositeOutputDevice->CompositeDevice;
|
||||
LEDCollection->CompositeOutputDevice;
|
||||
|
||||
LEDBoard->LEDCollection;
|
||||
LEDBarGraph->LEDCollection;
|
||||
|
||||
RGBLED->CompositeDevice;
|
||||
Motor->CompositeDevice;
|
||||
LEDBoard->CompositeDevice;
|
||||
PiLiter->LEDBoard;
|
||||
PiLiterBarGraph->LEDBarGraph;
|
||||
TrafficLights->LEDBoard;
|
||||
PiTraffic->TrafficLights;
|
||||
|
||||
TrafficLightsBuzzer->CompositeDevice;
|
||||
TrafficLightsBuzzer->CompositeOutputDevice;
|
||||
FishDish->TrafficLightsBuzzer;
|
||||
TrafficHat->TrafficLightsBuzzer;
|
||||
|
||||
Robot->CompositeDevice;
|
||||
RyanteckRobot->Robot;
|
||||
CamJamKitRobot->Robot;
|
||||
|
||||
RGBLED->CompositeDevice;
|
||||
Motor->CompositeDevice;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 46 KiB |
@@ -4,235 +4,185 @@
|
||||
<!-- Generated by graphviz version 2.36.0 (20140111.2315)
|
||||
-->
|
||||
<!-- Title: classes Pages: 1 -->
|
||||
<svg width="808pt" height="332pt"
|
||||
viewBox="0.00 0.00 808.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="603pt" height="476pt"
|
||||
viewBox="0.00 0.00 603.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)">
|
||||
<title>classes</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-328 804,-328 804,4 -4,4"/>
|
||||
<!-- AnalogInputDevice -->
|
||||
<g id="node1" class="node"><title>AnalogInputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="608,-252 502,-252 502,-216 608,-216 608,-252"/>
|
||||
<text text-anchor="middle" x="555" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">AnalogInputDevice</text>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-472 599,-472 599,4 -4,4"/>
|
||||
<!-- Device -->
|
||||
<g id="node1" class="node"><title>Device</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="485,-468 431,-468 431,-432 485,-432 485,-468"/>
|
||||
<text text-anchor="middle" x="458" y="-447.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
</g>
|
||||
<!-- CompositeDevice -->
|
||||
<g id="node2" class="node"><title>CompositeDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="279.5,-324 178.5,-324 178.5,-288 279.5,-288 279.5,-324"/>
|
||||
<text text-anchor="middle" x="229" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">CompositeDevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="508.5,-396 407.5,-396 407.5,-360 508.5,-360 508.5,-396"/>
|
||||
<text text-anchor="middle" x="458" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeDevice</text>
|
||||
</g>
|
||||
<!-- AnalogInputDevice->CompositeDevice -->
|
||||
<g id="edge1" class="edge"><title>AnalogInputDevice->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M502.26,-246.325C443.965,-258.842 350.25,-278.965 289.144,-292.086"/>
|
||||
<polygon fill="black" stroke="black" points="288.296,-288.688 279.253,-294.209 289.765,-295.532 288.296,-288.688"/>
|
||||
<!-- CompositeDevice->Device -->
|
||||
<g id="edge1" class="edge"><title>CompositeDevice->Device</title>
|
||||
<path fill="none" stroke="black" d="M458,-396.303C458,-404.017 458,-413.288 458,-421.888"/>
|
||||
<polygon fill="black" stroke="black" points="454.5,-421.896 458,-431.896 461.5,-421.896 454.5,-421.896"/>
|
||||
</g>
|
||||
<!-- MCP3xxx -->
|
||||
<g id="node3" class="node"><title>MCP3xxx</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="638.5,-180 579.5,-180 579.5,-144 638.5,-144 638.5,-180"/>
|
||||
<text text-anchor="middle" x="609" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3xxx</text>
|
||||
<!-- 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>
|
||||
</g>
|
||||
<!-- MCP3xxx->AnalogInputDevice -->
|
||||
<g id="edge2" class="edge"><title>MCP3xxx->AnalogInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M595.652,-180.303C589.243,-188.611 581.442,-198.723 574.394,-207.859"/>
|
||||
<polygon fill="black" stroke="black" points="571.532,-205.84 568.195,-215.896 577.074,-210.116 571.532,-205.84"/>
|
||||
<!-- CompositeOutputDevice->CompositeDevice -->
|
||||
<g id="edge2" class="edge"><title>CompositeOutputDevice->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M344.694,-324.124C365.018,-333.608 390.471,-345.487 412.102,-355.581"/>
|
||||
<polygon fill="black" stroke="black" points="410.883,-358.874 421.425,-359.932 413.843,-352.531 410.883,-358.874"/>
|
||||
</g>
|
||||
<!-- MCP33xx -->
|
||||
<g id="node4" class="node"><title>MCP33xx</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="480,-108 420,-108 420,-72 480,-72 480,-108"/>
|
||||
<text text-anchor="middle" x="450" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP33xx</text>
|
||||
<!-- 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>
|
||||
</g>
|
||||
<!-- MCP33xx->MCP3xxx -->
|
||||
<g id="edge3" class="edge"><title>MCP33xx->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M479.534,-104.002C505.081,-115.249 542.204,-131.593 570.154,-143.898"/>
|
||||
<polygon fill="black" stroke="black" points="569.062,-147.241 579.624,-148.067 571.882,-140.835 569.062,-147.241"/>
|
||||
</g>
|
||||
<!-- MCP3004 -->
|
||||
<g id="node5" class="node"><title>MCP3004</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="560,-108 498,-108 498,-72 560,-72 560,-108"/>
|
||||
<text text-anchor="middle" x="529" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3004</text>
|
||||
</g>
|
||||
<!-- MCP3004->MCP3xxx -->
|
||||
<g id="edge4" class="edge"><title>MCP3004->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M548.775,-108.303C558.754,-117.035 571.011,-127.76 581.857,-137.25"/>
|
||||
<polygon fill="black" stroke="black" points="579.622,-139.945 589.452,-143.896 584.231,-134.677 579.622,-139.945"/>
|
||||
</g>
|
||||
<!-- MCP3008 -->
|
||||
<g id="node6" class="node"><title>MCP3008</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="640,-108 578,-108 578,-72 640,-72 640,-108"/>
|
||||
<text text-anchor="middle" x="609" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3008</text>
|
||||
</g>
|
||||
<!-- MCP3008->MCP3xxx -->
|
||||
<g id="edge5" class="edge"><title>MCP3008->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M609,-108.303C609,-116.017 609,-125.288 609,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="605.5,-133.896 609,-143.896 612.5,-133.896 605.5,-133.896"/>
|
||||
</g>
|
||||
<!-- MCP3204 -->
|
||||
<g id="node7" class="node"><title>MCP3204</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="720,-108 658,-108 658,-72 720,-72 720,-108"/>
|
||||
<text text-anchor="middle" x="689" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3204</text>
|
||||
</g>
|
||||
<!-- MCP3204->MCP3xxx -->
|
||||
<g id="edge6" class="edge"><title>MCP3204->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M669.225,-108.303C659.246,-117.035 646.989,-127.76 636.143,-137.25"/>
|
||||
<polygon fill="black" stroke="black" points="633.769,-134.677 628.548,-143.896 638.378,-139.945 633.769,-134.677"/>
|
||||
</g>
|
||||
<!-- MCP3208 -->
|
||||
<g id="node8" class="node"><title>MCP3208</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="800,-108 738,-108 738,-72 800,-72 800,-108"/>
|
||||
<text text-anchor="middle" x="769" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3208</text>
|
||||
</g>
|
||||
<!-- MCP3208->MCP3xxx -->
|
||||
<g id="edge7" class="edge"><title>MCP3208->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M738.15,-104.497C712.4,-115.762 675.557,-131.881 647.808,-144.022"/>
|
||||
<polygon fill="black" stroke="black" points="646.163,-140.921 638.405,-148.135 648.969,-147.334 646.163,-140.921"/>
|
||||
</g>
|
||||
<!-- MCP3301 -->
|
||||
<g id="node9" class="node"><title>MCP3301</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="401,-36 339,-36 339,-0 401,-0 401,-36"/>
|
||||
<text text-anchor="middle" x="370" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3301</text>
|
||||
</g>
|
||||
<!-- MCP3301->MCP33xx -->
|
||||
<g id="edge8" class="edge"><title>MCP3301->MCP33xx</title>
|
||||
<path fill="none" stroke="black" d="M389.775,-36.3034C399.754,-45.0345 412.011,-55.7595 422.857,-65.2497"/>
|
||||
<polygon fill="black" stroke="black" points="420.622,-67.9446 430.452,-71.8957 425.231,-62.6766 420.622,-67.9446"/>
|
||||
</g>
|
||||
<!-- MCP3302 -->
|
||||
<g id="node10" class="node"><title>MCP3302</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="481,-36 419,-36 419,-0 481,-0 481,-36"/>
|
||||
<text text-anchor="middle" x="450" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3302</text>
|
||||
</g>
|
||||
<!-- MCP3302->MCP33xx -->
|
||||
<g id="edge9" class="edge"><title>MCP3302->MCP33xx</title>
|
||||
<path fill="none" stroke="black" d="M450,-36.3034C450,-44.0173 450,-53.2875 450,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="446.5,-61.8956 450,-71.8957 453.5,-61.8957 446.5,-61.8956"/>
|
||||
</g>
|
||||
<!-- MCP3304 -->
|
||||
<g id="node11" class="node"><title>MCP3304</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="561,-36 499,-36 499,-0 561,-0 561,-36"/>
|
||||
<text text-anchor="middle" x="530" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3304</text>
|
||||
</g>
|
||||
<!-- MCP3304->MCP33xx -->
|
||||
<g id="edge10" class="edge"><title>MCP3304->MCP33xx</title>
|
||||
<path fill="none" stroke="black" d="M510.225,-36.3034C500.246,-45.0345 487.989,-55.7595 477.143,-65.2497"/>
|
||||
<polygon fill="black" stroke="black" points="474.769,-62.6766 469.548,-71.8957 479.378,-67.9446 474.769,-62.6766"/>
|
||||
</g>
|
||||
<!-- RGBLED -->
|
||||
<g id="node12" class="node"><title>RGBLED</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="56,-252 0,-252 0,-216 56,-216 56,-252"/>
|
||||
<text text-anchor="middle" x="28" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text>
|
||||
</g>
|
||||
<!-- RGBLED->CompositeDevice -->
|
||||
<g id="edge11" class="edge"><title>RGBLED->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M55.7289,-248.167C58.8345,-249.514 61.9702,-250.819 65,-252 98.836,-265.187 137.45,-277.783 168.706,-287.384"/>
|
||||
<polygon fill="black" stroke="black" points="167.994,-290.826 178.58,-290.393 170.035,-284.13 167.994,-290.826"/>
|
||||
</g>
|
||||
<!-- Motor -->
|
||||
<g id="node13" class="node"><title>Motor</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="128,-252 74,-252 74,-216 128,-216 128,-252"/>
|
||||
<text text-anchor="middle" x="101" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">Motor</text>
|
||||
</g>
|
||||
<!-- Motor->CompositeDevice -->
|
||||
<g id="edge12" class="edge"><title>Motor->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M128.146,-249.845C145.837,-259.52 169.228,-272.312 189.029,-283.141"/>
|
||||
<polygon fill="black" stroke="black" points="187.388,-286.233 197.841,-287.96 190.747,-280.091 187.388,-286.233"/>
|
||||
<!-- 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"/>
|
||||
</g>
|
||||
<!-- LEDBoard -->
|
||||
<g id="node14" class="node"><title>LEDBoard</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="210,-252 146,-252 146,-216 210,-216 210,-252"/>
|
||||
<text text-anchor="middle" x="178" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBoard</text>
|
||||
<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>
|
||||
</g>
|
||||
<!-- LEDBoard->CompositeDevice -->
|
||||
<g id="edge13" class="edge"><title>LEDBoard->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M190.607,-252.303C196.597,-260.526 203.876,-270.517 210.479,-279.579"/>
|
||||
<polygon fill="black" stroke="black" points="207.821,-281.874 216.538,-287.896 213.478,-277.752 207.821,-281.874"/>
|
||||
<!-- 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"/>
|
||||
</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>
|
||||
</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"/>
|
||||
</g>
|
||||
<!-- PiLiter -->
|
||||
<g id="node15" class="node"><title>PiLiter</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="99,-180 45,-180 45,-144 99,-144 99,-180"/>
|
||||
<text text-anchor="middle" x="72" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiLiter</text>
|
||||
<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>
|
||||
</g>
|
||||
<!-- PiLiter->LEDBoard -->
|
||||
<g id="edge14" class="edge"><title>PiLiter->LEDBoard</title>
|
||||
<path fill="none" stroke="black" d="M97.9305,-180.124C111.662,-189.192 128.706,-200.448 143.536,-210.241"/>
|
||||
<polygon fill="black" stroke="black" points="141.696,-213.22 151.97,-215.81 145.554,-207.379 141.696,-213.22"/>
|
||||
<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"/>
|
||||
</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>
|
||||
</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"/>
|
||||
</g>
|
||||
<!-- TrafficLights -->
|
||||
<g id="node16" class="node"><title>TrafficLights</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="189,-180 117,-180 117,-144 189,-144 189,-180"/>
|
||||
<text text-anchor="middle" x="153" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLights</text>
|
||||
<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>
|
||||
</g>
|
||||
<!-- TrafficLights->LEDBoard -->
|
||||
<g id="edge15" class="edge"><title>TrafficLights->LEDBoard</title>
|
||||
<path fill="none" stroke="black" d="M159.18,-180.303C161.995,-188.187 165.391,-197.696 168.519,-206.454"/>
|
||||
<polygon fill="black" stroke="black" points="165.232,-207.655 171.891,-215.896 171.824,-205.301 165.232,-207.655"/>
|
||||
<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"/>
|
||||
</g>
|
||||
<!-- PiTraffic -->
|
||||
<g id="node17" class="node"><title>PiTraffic</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="180,-108 126,-108 126,-72 180,-72 180,-108"/>
|
||||
<text text-anchor="middle" x="153" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiTraffic</text>
|
||||
<g id="node10" class="node"><title>PiTraffic</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="135,-36 81,-36 81,-0 135,-0 135,-36"/>
|
||||
<text text-anchor="middle" x="108" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PiTraffic</text>
|
||||
</g>
|
||||
<!-- PiTraffic->TrafficLights -->
|
||||
<g id="edge16" class="edge"><title>PiTraffic->TrafficLights</title>
|
||||
<path fill="none" stroke="black" d="M153,-108.303C153,-116.017 153,-125.288 153,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="149.5,-133.896 153,-143.896 156.5,-133.896 149.5,-133.896"/>
|
||||
<g id="edge9" class="edge"><title>PiTraffic->TrafficLights</title>
|
||||
<path fill="none" stroke="black" d="M108,-36.3034C108,-44.0173 108,-53.2875 108,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="104.5,-61.8956 108,-71.8957 111.5,-61.8957 104.5,-61.8956"/>
|
||||
</g>
|
||||
<!-- TrafficLightsBuzzer -->
|
||||
<g id="node18" class="node"><title>TrafficLightsBuzzer</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="333.5,-252 228.5,-252 228.5,-216 333.5,-216 333.5,-252"/>
|
||||
<text text-anchor="middle" x="281" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLightsBuzzer</text>
|
||||
<g id="node11" 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>
|
||||
<!-- TrafficLightsBuzzer->CompositeDevice -->
|
||||
<g id="edge17" class="edge"><title>TrafficLightsBuzzer->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M268.146,-252.303C261.975,-260.611 254.463,-270.723 247.676,-279.859"/>
|
||||
<polygon fill="black" stroke="black" points="244.86,-277.781 241.706,-287.896 250.479,-281.955 244.86,-277.781"/>
|
||||
<!-- TrafficLightsBuzzer->CompositeOutputDevice -->
|
||||
<g id="edge10" 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>
|
||||
<!-- FishDish -->
|
||||
<g id="node19" class="node"><title>FishDish</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="263,-180 207,-180 207,-144 263,-144 263,-180"/>
|
||||
<text text-anchor="middle" x="235" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">FishDish</text>
|
||||
<g id="node12" 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>
|
||||
<!-- FishDish->TrafficLightsBuzzer -->
|
||||
<g id="edge18" class="edge"><title>FishDish->TrafficLightsBuzzer</title>
|
||||
<path fill="none" stroke="black" d="M246.371,-180.303C251.718,-188.441 258.204,-198.311 264.111,-207.299"/>
|
||||
<polygon fill="black" stroke="black" points="261.343,-209.461 269.76,-215.896 267.193,-205.616 261.343,-209.461"/>
|
||||
<g id="edge11" 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>
|
||||
<!-- TrafficHat -->
|
||||
<g id="node20" class="node"><title>TrafficHat</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="342.5,-180 281.5,-180 281.5,-144 342.5,-144 342.5,-180"/>
|
||||
<text text-anchor="middle" x="312" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficHat</text>
|
||||
<g id="node13" 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>
|
||||
<!-- TrafficHat->TrafficLightsBuzzer -->
|
||||
<g id="edge19" class="edge"><title>TrafficHat->TrafficLightsBuzzer</title>
|
||||
<path fill="none" stroke="black" d="M304.337,-180.303C300.808,-188.272 296.544,-197.9 292.631,-206.736"/>
|
||||
<polygon fill="black" stroke="black" points="289.424,-205.335 288.575,-215.896 295.824,-208.169 289.424,-205.335"/>
|
||||
<g id="edge12" 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>
|
||||
<!-- Robot -->
|
||||
<g id="node21" class="node"><title>Robot</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="431,-252 377,-252 377,-216 431,-216 431,-252"/>
|
||||
<text text-anchor="middle" x="404" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text>
|
||||
<g id="node14" class="node"><title>Robot</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="449,-324 395,-324 395,-288 449,-288 449,-324"/>
|
||||
<text text-anchor="middle" x="422" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text>
|
||||
</g>
|
||||
<!-- Robot->CompositeDevice -->
|
||||
<g id="edge20" class="edge"><title>Robot->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M376.656,-245.938C351.218,-256.113 312.631,-271.547 281.284,-284.087"/>
|
||||
<polygon fill="black" stroke="black" points="279.795,-280.912 271.81,-287.876 282.394,-287.412 279.795,-280.912"/>
|
||||
<g id="edge13" class="edge"><title>Robot->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M430.899,-324.303C434.997,-332.272 439.949,-341.9 444.493,-350.736"/>
|
||||
<polygon fill="black" stroke="black" points="441.517,-352.604 449.203,-359.896 447.742,-349.402 441.517,-352.604"/>
|
||||
</g>
|
||||
<!-- RyanteckRobot -->
|
||||
<g id="node22" class="node"><title>RyanteckRobot</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="447,-180 361,-180 361,-144 447,-144 447,-180"/>
|
||||
<text text-anchor="middle" x="404" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">RyanteckRobot</text>
|
||||
<g id="node15" 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>
|
||||
<!-- RyanteckRobot->Robot -->
|
||||
<g id="edge21" class="edge"><title>RyanteckRobot->Robot</title>
|
||||
<path fill="none" stroke="black" d="M404,-180.303C404,-188.017 404,-197.288 404,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="400.5,-205.896 404,-215.896 407.5,-205.896 400.5,-205.896"/>
|
||||
<g id="edge14" class="edge"><title>RyanteckRobot->Robot</title>
|
||||
<path fill="none" stroke="black" d="M422,-252.303C422,-260.017 422,-269.288 422,-277.888"/>
|
||||
<polygon fill="black" stroke="black" points="418.5,-277.896 422,-287.896 425.5,-277.896 418.5,-277.896"/>
|
||||
</g>
|
||||
<!-- CamJamKitRobot -->
|
||||
<g id="node23" class="node"><title>CamJamKitRobot</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="561,-180 465,-180 465,-144 561,-144 561,-180"/>
|
||||
<text text-anchor="middle" x="513" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">CamJamKitRobot</text>
|
||||
<g id="node16" 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>
|
||||
<!-- CamJamKitRobot->Robot -->
|
||||
<g id="edge22" class="edge"><title>CamJamKitRobot->Robot</title>
|
||||
<path fill="none" stroke="black" d="M486.336,-180.124C472.216,-189.192 454.689,-200.448 439.439,-210.241"/>
|
||||
<polygon fill="black" stroke="black" points="437.29,-207.462 430.767,-215.81 441.073,-213.352 437.29,-207.462"/>
|
||||
<g id="edge15" class="edge"><title>CamJamKitRobot->Robot</title>
|
||||
<path fill="none" stroke="black" d="M504.336,-252.124C490.216,-261.192 472.689,-272.448 457.439,-282.241"/>
|
||||
<polygon fill="black" stroke="black" points="455.29,-279.462 448.767,-287.81 459.073,-285.352 455.29,-279.462"/>
|
||||
</g>
|
||||
<!-- RGBLED -->
|
||||
<g id="node17" class="node"><title>RGBLED</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="523,-324 467,-324 467,-288 523,-288 523,-324"/>
|
||||
<text text-anchor="middle" x="495" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text>
|
||||
</g>
|
||||
<!-- RGBLED->CompositeDevice -->
|
||||
<g id="edge16" class="edge"><title>RGBLED->CompositeDevice</title>
|
||||
<path fill="none" stroke="black" d="M485.854,-324.303C481.597,-332.356 476.444,-342.106 471.734,-351.018"/>
|
||||
<polygon fill="black" stroke="black" points="468.62,-349.419 467.041,-359.896 474.808,-352.69 468.62,-349.419"/>
|
||||
</g>
|
||||
<!-- Motor -->
|
||||
<g id="node18" class="node"><title>Motor</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="595,-324 541,-324 541,-288 595,-288 595,-324"/>
|
||||
<text text-anchor="middle" x="568" y="-303.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="M541.091,-324.124C526.842,-333.192 509.154,-344.448 493.764,-354.241"/>
|
||||
<polygon fill="black" stroke="black" points="491.57,-351.489 485.012,-359.81 495.328,-357.394 491.57,-351.489"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 11 KiB |
@@ -2,9 +2,20 @@
|
||||
|
||||
digraph classes {
|
||||
graph [rankdir=BT];
|
||||
node [shape=rect, style=filled, color="#2980b9", fontname=Sans, fontcolor="#ffffff", fontsize=10];
|
||||
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
|
||||
edge [];
|
||||
|
||||
/* Abstract classes */
|
||||
node [color="#9ec6e0", fontcolor="#000000"]
|
||||
Device;
|
||||
GPIODevice;
|
||||
WaitableInputDevice;
|
||||
SmoothedInputDevice;
|
||||
|
||||
/* Concrete classes */
|
||||
node [color="#2980b9", fontcolor="#ffffff"];
|
||||
|
||||
GPIODevice->Device;
|
||||
InputDevice->GPIODevice;
|
||||
WaitableInputDevice->InputDevice;
|
||||
DigitalInputDevice->WaitableInputDevice;
|
||||
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 40 KiB |
@@ -4,145 +4,155 @@
|
||||
<!-- Generated by graphviz version 2.36.0 (20140111.2315)
|
||||
-->
|
||||
<!-- Title: classes Pages: 1 -->
|
||||
<svg width="563pt" height="332pt"
|
||||
viewBox="0.00 0.00 563.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="557pt" height="404pt"
|
||||
viewBox="0.00 0.00 557.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)">
|
||||
<title>classes</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-328 559,-328 559,4 -4,4"/>
|
||||
<!-- InputDevice -->
|
||||
<g id="node1" class="node"><title>InputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="346.5,-252 273.5,-252 273.5,-216 346.5,-216 346.5,-252"/>
|
||||
<text text-anchor="middle" x="310" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">InputDevice</text>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-400 553,-400 553,4 -4,4"/>
|
||||
<!-- Device -->
|
||||
<g id="node1" class="node"><title>Device</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="341,-396 287,-396 287,-360 341,-360 341,-396"/>
|
||||
<text text-anchor="middle" x="314" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
|
||||
</g>
|
||||
<!-- GPIODevice -->
|
||||
<g id="node2" class="node"><title>GPIODevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="290,-324 218,-324 218,-288 290,-288 290,-324"/>
|
||||
<text text-anchor="middle" x="254" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">GPIODevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="350,-324 278,-324 278,-288 350,-288 350,-324"/>
|
||||
<text text-anchor="middle" x="314" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text>
|
||||
</g>
|
||||
<!-- InputDevice->GPIODevice -->
|
||||
<g id="edge1" class="edge"><title>InputDevice->GPIODevice</title>
|
||||
<path fill="none" stroke="black" d="M296.157,-252.303C289.511,-260.611 281.422,-270.723 274.113,-279.859"/>
|
||||
<polygon fill="black" stroke="black" points="271.197,-277.901 267.683,-287.896 276.664,-282.273 271.197,-277.901"/>
|
||||
<!-- GPIODevice->Device -->
|
||||
<g id="edge1" class="edge"><title>GPIODevice->Device</title>
|
||||
<path fill="none" stroke="black" d="M314,-324.303C314,-332.017 314,-341.288 314,-349.888"/>
|
||||
<polygon fill="black" stroke="black" points="310.5,-349.896 314,-359.896 317.5,-349.896 310.5,-349.896"/>
|
||||
</g>
|
||||
<!-- WaitableInputDevice -->
|
||||
<g id="node3" class="node"><title>WaitableInputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="382.5,-180 269.5,-180 269.5,-144 382.5,-144 382.5,-180"/>
|
||||
<text text-anchor="middle" x="326" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">WaitableInputDevice</text>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="296.5,-180 183.5,-180 183.5,-144 296.5,-144 296.5,-180"/>
|
||||
<text text-anchor="middle" x="240" y="-159.5" font-family="Sans" font-size="10.00" fill="#000000">WaitableInputDevice</text>
|
||||
</g>
|
||||
<!-- InputDevice -->
|
||||
<g id="node5" class="node"><title>InputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="294.5,-252 221.5,-252 221.5,-216 294.5,-216 294.5,-252"/>
|
||||
<text text-anchor="middle" x="258" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">InputDevice</text>
|
||||
</g>
|
||||
<!-- WaitableInputDevice->InputDevice -->
|
||||
<g id="edge2" class="edge"><title>WaitableInputDevice->InputDevice</title>
|
||||
<path fill="none" stroke="black" d="M322.045,-180.303C320.282,-188.017 318.163,-197.288 316.197,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="312.726,-205.367 313.91,-215.896 319.55,-206.927 312.726,-205.367"/>
|
||||
</g>
|
||||
<!-- DigitalInputDevice -->
|
||||
<g id="node4" class="node"><title>DigitalInputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="363.5,-108 260.5,-108 260.5,-72 363.5,-72 363.5,-108"/>
|
||||
<text text-anchor="middle" x="312" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text>
|
||||
</g>
|
||||
<!-- DigitalInputDevice->WaitableInputDevice -->
|
||||
<g id="edge3" class="edge"><title>DigitalInputDevice->WaitableInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M315.461,-108.303C317.003,-116.017 318.858,-125.288 320.578,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="317.186,-134.776 322.579,-143.896 324.05,-133.403 317.186,-134.776"/>
|
||||
<g id="edge3" class="edge"><title>WaitableInputDevice->InputDevice</title>
|
||||
<path fill="none" stroke="black" d="M244.449,-180.303C246.455,-188.102 248.869,-197.491 251.101,-206.171"/>
|
||||
<polygon fill="black" stroke="black" points="247.722,-207.082 253.602,-215.896 254.501,-205.339 247.722,-207.082"/>
|
||||
</g>
|
||||
<!-- SmoothedInputDevice -->
|
||||
<g id="node5" class="node"><title>SmoothedInputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="504.5,-108 381.5,-108 381.5,-72 504.5,-72 504.5,-108"/>
|
||||
<text text-anchor="middle" x="443" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">SmoothedInputDevice</text>
|
||||
<g id="node4" class="node"><title>SmoothedInputDevice</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="174.5,-108 51.5,-108 51.5,-72 174.5,-72 174.5,-108"/>
|
||||
<text text-anchor="middle" x="113" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">SmoothedInputDevice</text>
|
||||
</g>
|
||||
<!-- SmoothedInputDevice->WaitableInputDevice -->
|
||||
<g id="edge4" class="edge"><title>SmoothedInputDevice->WaitableInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M414.379,-108.124C399.081,-117.276 380.057,-128.658 363.582,-138.515"/>
|
||||
<polygon fill="black" stroke="black" points="361.516,-135.673 354.731,-143.81 365.11,-141.68 361.516,-135.673"/>
|
||||
<g id="edge5" class="edge"><title>SmoothedInputDevice->WaitableInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M144.068,-108.124C160.827,-117.361 181.706,-128.869 199.702,-138.788"/>
|
||||
<polygon fill="black" stroke="black" points="198.366,-142.048 208.813,-143.81 201.745,-135.918 198.366,-142.048"/>
|
||||
</g>
|
||||
<!-- InputDevice->GPIODevice -->
|
||||
<g id="edge2" class="edge"><title>InputDevice->GPIODevice</title>
|
||||
<path fill="none" stroke="black" d="M271.843,-252.303C278.489,-260.611 286.578,-270.723 293.887,-279.859"/>
|
||||
<polygon fill="black" stroke="black" points="291.336,-282.273 300.317,-287.896 296.803,-277.901 291.336,-282.273"/>
|
||||
</g>
|
||||
<!-- DigitalInputDevice -->
|
||||
<g id="node6" class="node"><title>DigitalInputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="295.5,-108 192.5,-108 192.5,-72 295.5,-72 295.5,-108"/>
|
||||
<text text-anchor="middle" x="244" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text>
|
||||
</g>
|
||||
<!-- DigitalInputDevice->WaitableInputDevice -->
|
||||
<g id="edge4" class="edge"><title>DigitalInputDevice->WaitableInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M243.011,-108.303C242.57,-116.017 242.041,-125.288 241.549,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="238.054,-133.712 240.977,-143.896 245.042,-134.112 238.054,-133.712"/>
|
||||
</g>
|
||||
<!-- Button -->
|
||||
<g id="node6" class="node"><title>Button</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="339,-36 285,-36 285,-0 339,-0 339,-36"/>
|
||||
<text text-anchor="middle" x="312" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text>
|
||||
<g id="node7" class="node"><title>Button</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="271,-36 217,-36 217,-0 271,-0 271,-36"/>
|
||||
<text text-anchor="middle" x="244" 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="M312,-36.3034C312,-44.0173 312,-53.2875 312,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="308.5,-61.8956 312,-71.8957 315.5,-61.8957 308.5,-61.8956"/>
|
||||
<g id="edge6" class="edge"><title>Button->DigitalInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M244,-36.3034C244,-44.0173 244,-53.2875 244,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="240.5,-61.8956 244,-71.8957 247.5,-61.8957 240.5,-61.8956"/>
|
||||
</g>
|
||||
<!-- MotionSensor -->
|
||||
<g id="node7" class="node"><title>MotionSensor</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="462.5,-36 379.5,-36 379.5,-0 462.5,-0 462.5,-36"/>
|
||||
<text text-anchor="middle" x="421" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MotionSensor</text>
|
||||
<g id="node8" 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>
|
||||
</g>
|
||||
<!-- MotionSensor->SmoothedInputDevice -->
|
||||
<g id="edge6" class="edge"><title>MotionSensor->SmoothedInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M426.438,-36.3034C428.889,-44.1021 431.84,-53.4915 434.568,-62.1708"/>
|
||||
<polygon fill="black" stroke="black" points="431.287,-63.4051 437.624,-71.8957 437.965,-61.3063 431.287,-63.4051"/>
|
||||
<g id="edge7" class="edge"><title>MotionSensor->SmoothedInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M58.7978,-36.3034C67.604,-44.865 78.3821,-55.3438 88.0011,-64.6955"/>
|
||||
<polygon fill="black" stroke="black" points="85.7972,-67.4343 95.407,-71.8957 90.6768,-62.4154 85.7972,-67.4343"/>
|
||||
</g>
|
||||
<!-- LightSensor -->
|
||||
<g id="node8" class="node"><title>LightSensor</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="555,-36 481,-36 481,-0 555,-0 555,-36"/>
|
||||
<text text-anchor="middle" x="518" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LightSensor</text>
|
||||
<g id="node9" 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>
|
||||
</g>
|
||||
<!-- LightSensor->SmoothedInputDevice -->
|
||||
<g id="edge7" class="edge"><title>LightSensor->SmoothedInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M499.461,-36.3034C490.197,-44.9497 478.838,-55.5514 468.743,-64.973"/>
|
||||
<polygon fill="black" stroke="black" points="466.249,-62.5137 461.326,-71.8957 471.025,-67.6312 466.249,-62.5137"/>
|
||||
<g id="edge8" class="edge"><title>LightSensor->SmoothedInputDevice</title>
|
||||
<path fill="none" stroke="black" d="M131.82,-36.3034C129.005,-44.1868 125.609,-53.6958 122.481,-62.4536"/>
|
||||
<polygon fill="black" stroke="black" points="119.176,-61.301 119.109,-71.8957 125.768,-63.6554 119.176,-61.301"/>
|
||||
</g>
|
||||
<!-- OutputDevice -->
|
||||
<g id="node9" class="node"><title>OutputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="240,-252 158,-252 158,-216 240,-216 240,-252"/>
|
||||
<text text-anchor="middle" x="199" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text>
|
||||
<g id="node10" class="node"><title>OutputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="412,-252 330,-252 330,-216 412,-216 412,-252"/>
|
||||
<text text-anchor="middle" x="371" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text>
|
||||
</g>
|
||||
<!-- OutputDevice->GPIODevice -->
|
||||
<g id="edge8" class="edge"><title>OutputDevice->GPIODevice</title>
|
||||
<path fill="none" stroke="black" d="M212.596,-252.303C219.123,-260.611 227.068,-270.723 234.247,-279.859"/>
|
||||
<polygon fill="black" stroke="black" points="231.631,-282.195 240.561,-287.896 237.135,-277.87 231.631,-282.195"/>
|
||||
<g id="edge9" class="edge"><title>OutputDevice->GPIODevice</title>
|
||||
<path fill="none" stroke="black" d="M356.91,-252.303C350.077,-260.695 341.743,-270.93 334.244,-280.139"/>
|
||||
<polygon fill="black" stroke="black" points="331.528,-277.931 327.928,-287.896 336.956,-282.351 331.528,-277.931"/>
|
||||
</g>
|
||||
<!-- DigitalOutputDevice -->
|
||||
<g id="node10" class="node"><title>DigitalOutputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="129,-180 17,-180 17,-144 129,-144 129,-180"/>
|
||||
<text text-anchor="middle" x="73" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text>
|
||||
<g id="node11" class="node"><title>DigitalOutputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="427,-180 315,-180 315,-144 427,-144 427,-180"/>
|
||||
<text text-anchor="middle" x="371" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text>
|
||||
</g>
|
||||
<!-- DigitalOutputDevice->OutputDevice -->
|
||||
<g id="edge9" class="edge"><title>DigitalOutputDevice->OutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M103.823,-180.124C120.45,-189.361 141.165,-200.869 159.019,-210.788"/>
|
||||
<polygon fill="black" stroke="black" points="157.617,-214.013 168.058,-215.81 161.017,-207.894 157.617,-214.013"/>
|
||||
<g id="edge10" class="edge"><title>DigitalOutputDevice->OutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M371,-180.303C371,-188.017 371,-197.288 371,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="367.5,-205.896 371,-215.896 374.5,-205.896 367.5,-205.896"/>
|
||||
</g>
|
||||
<!-- LED -->
|
||||
<g id="node11" class="node"><title>LED</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">LED</text>
|
||||
<g id="node12" class="node"><title>LED</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="375,-108 321,-108 321,-72 375,-72 375,-108"/>
|
||||
<text text-anchor="middle" x="348" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text>
|
||||
</g>
|
||||
<!-- LED->DigitalOutputDevice -->
|
||||
<g id="edge10" class="edge"><title>LED->DigitalOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M38.3708,-108.303C43.7185,-116.441 50.2043,-126.311 56.1106,-135.299"/>
|
||||
<polygon fill="black" stroke="black" points="53.3432,-137.461 61.76,-143.896 59.1932,-133.616 53.3432,-137.461"/>
|
||||
<g id="edge11" class="edge"><title>LED->DigitalOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M353.685,-108.303C356.248,-116.102 359.333,-125.491 362.185,-134.171"/>
|
||||
<polygon fill="black" stroke="black" points="358.933,-135.488 365.38,-143.896 365.584,-133.303 358.933,-135.488"/>
|
||||
</g>
|
||||
<!-- Buzzer -->
|
||||
<g id="node12" class="node"><title>Buzzer</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="126,-108 72,-108 72,-72 126,-72 126,-108"/>
|
||||
<text text-anchor="middle" x="99" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text>
|
||||
<g id="node13" class="node"><title>Buzzer</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="447,-108 393,-108 393,-72 447,-72 447,-108"/>
|
||||
<text text-anchor="middle" x="420" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text>
|
||||
</g>
|
||||
<!-- Buzzer->DigitalOutputDevice -->
|
||||
<g id="edge11" class="edge"><title>Buzzer->DigitalOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M92.573,-108.303C89.6449,-116.187 86.113,-125.696 82.8601,-134.454"/>
|
||||
<polygon fill="black" stroke="black" points="79.554,-133.303 79.353,-143.896 86.116,-135.74 79.554,-133.303"/>
|
||||
<g id="edge12" class="edge"><title>Buzzer->DigitalOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M407.888,-108.303C402.132,-116.526 395.138,-126.517 388.794,-135.579"/>
|
||||
<polygon fill="black" stroke="black" points="385.84,-133.696 382.973,-143.896 391.575,-137.71 385.84,-133.696"/>
|
||||
</g>
|
||||
<!-- PWMOutputDevice -->
|
||||
<g id="node13" class="node"><title>PWMOutputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="251,-180 147,-180 147,-144 251,-144 251,-180"/>
|
||||
<text text-anchor="middle" x="199" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text>
|
||||
<g id="node14" class="node"><title>PWMOutputDevice</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="549,-180 445,-180 445,-144 549,-144 549,-180"/>
|
||||
<text text-anchor="middle" x="497" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text>
|
||||
</g>
|
||||
<!-- PWMOutputDevice->OutputDevice -->
|
||||
<g id="edge12" class="edge"><title>PWMOutputDevice->OutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M199,-180.303C199,-188.017 199,-197.288 199,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="195.5,-205.896 199,-215.896 202.5,-205.896 195.5,-205.896"/>
|
||||
<g id="edge13" class="edge"><title>PWMOutputDevice->OutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M466.177,-180.124C449.55,-189.361 428.835,-200.869 410.981,-210.788"/>
|
||||
<polygon fill="black" stroke="black" points="408.983,-207.894 401.942,-215.81 412.383,-214.013 408.983,-207.894"/>
|
||||
</g>
|
||||
<!-- PWMLED -->
|
||||
<g id="node14" class="node"><title>PWMLED</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="228,-108 170,-108 170,-72 228,-72 228,-108"/>
|
||||
<text text-anchor="middle" x="199" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text>
|
||||
<g id="node15" class="node"><title>PWMLED</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="526,-108 468,-108 468,-72 526,-72 526,-108"/>
|
||||
<text text-anchor="middle" x="497" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text>
|
||||
</g>
|
||||
<!-- PWMLED->PWMOutputDevice -->
|
||||
<g id="edge13" class="edge"><title>PWMLED->PWMOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M199,-108.303C199,-116.017 199,-125.288 199,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="195.5,-133.896 199,-143.896 202.5,-133.896 195.5,-133.896"/>
|
||||
<g id="edge14" class="edge"><title>PWMLED->PWMOutputDevice</title>
|
||||
<path fill="none" stroke="black" d="M497,-108.303C497,-116.017 497,-125.288 497,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="493.5,-133.896 497,-143.896 500.5,-133.896 493.5,-133.896"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 64 KiB |
27
docs/images/spi_device_hierarchy.dot
Normal file
@@ -0,0 +1,27 @@
|
||||
digraph classes {
|
||||
graph [rankdir=BT];
|
||||
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
|
||||
edge [];
|
||||
|
||||
/* Abstract classes */
|
||||
node [color="#9ec6e0", fontcolor="#000000"]
|
||||
Device;
|
||||
SPIDevice;
|
||||
AnalogInputDevice;
|
||||
MCP3xxx;
|
||||
MCP33xx;
|
||||
|
||||
/* Concrete classes */
|
||||
node [color="#2980b9", fontcolor="#ffffff"];
|
||||
SPIDevice->Device;
|
||||
AnalogInputDevice->SPIDevice;
|
||||
MCP3xxx->AnalogInputDevice;
|
||||
MCP33xx->MCP3xxx;
|
||||
MCP3004->MCP3xxx;
|
||||
MCP3008->MCP3xxx;
|
||||
MCP3204->MCP3xxx;
|
||||
MCP3208->MCP3xxx;
|
||||
MCP3301->MCP33xx;
|
||||
MCP3302->MCP33xx;
|
||||
MCP3304->MCP33xx;
|
||||
}
|
||||
BIN
docs/images/spi_device_hierarchy.pdf
Normal file
BIN
docs/images/spi_device_hierarchy.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
128
docs/images/spi_device_hierarchy.svg
Normal file
@@ -0,0 +1,128 @@
|
||||
<?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)
|
||||
-->
|
||||
<!-- Title: classes Pages: 1 -->
|
||||
<svg width="469pt" height="404pt"
|
||||
viewBox="0.00 0.00 469.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)">
|
||||
<title>classes</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-400 465,-400 465,4 -4,4"/>
|
||||
<!-- Device -->
|
||||
<g id="node1" class="node"><title>Device</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="297,-396 243,-396 243,-360 297,-360 297,-396"/>
|
||||
<text text-anchor="middle" x="270" y="-375.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="302,-324 238,-324 238,-288 302,-288 302,-324"/>
|
||||
<text text-anchor="middle" x="270" y="-303.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="M270,-324.303C270,-332.017 270,-341.288 270,-349.888"/>
|
||||
<polygon fill="black" stroke="black" points="266.5,-349.896 270,-359.896 273.5,-349.896 266.5,-349.896"/>
|
||||
</g>
|
||||
<!-- AnalogInputDevice -->
|
||||
<g id="node3" class="node"><title>AnalogInputDevice</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="323,-252 217,-252 217,-216 323,-216 323,-252"/>
|
||||
<text text-anchor="middle" x="270" y="-231.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="M270,-252.303C270,-260.017 270,-269.288 270,-277.888"/>
|
||||
<polygon fill="black" stroke="black" points="266.5,-277.896 270,-287.896 273.5,-277.896 266.5,-277.896"/>
|
||||
</g>
|
||||
<!-- MCP3xxx -->
|
||||
<g id="node4" class="node"><title>MCP3xxx</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="299.5,-180 240.5,-180 240.5,-144 299.5,-144 299.5,-180"/>
|
||||
<text text-anchor="middle" x="270" y="-159.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="M270,-180.303C270,-188.017 270,-197.288 270,-205.888"/>
|
||||
<polygon fill="black" stroke="black" points="266.5,-205.896 270,-215.896 273.5,-205.896 266.5,-205.896"/>
|
||||
</g>
|
||||
<!-- MCP33xx -->
|
||||
<g id="node5" class="node"><title>MCP33xx</title>
|
||||
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="141,-108 81,-108 81,-72 141,-72 141,-108"/>
|
||||
<text text-anchor="middle" x="111" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP33xx</text>
|
||||
</g>
|
||||
<!-- MCP33xx->MCP3xxx -->
|
||||
<g id="edge4" class="edge"><title>MCP33xx->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M140.534,-104.002C166.081,-115.249 203.204,-131.593 231.154,-143.898"/>
|
||||
<polygon fill="black" stroke="black" points="230.062,-147.241 240.624,-148.067 232.882,-140.835 230.062,-147.241"/>
|
||||
</g>
|
||||
<!-- MCP3004 -->
|
||||
<g id="node6" class="node"><title>MCP3004</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="221,-108 159,-108 159,-72 221,-72 221,-108"/>
|
||||
<text text-anchor="middle" x="190" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3004</text>
|
||||
</g>
|
||||
<!-- MCP3004->MCP3xxx -->
|
||||
<g id="edge5" class="edge"><title>MCP3004->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M209.775,-108.303C219.754,-117.035 232.011,-127.76 242.857,-137.25"/>
|
||||
<polygon fill="black" stroke="black" points="240.622,-139.945 250.452,-143.896 245.231,-134.677 240.622,-139.945"/>
|
||||
</g>
|
||||
<!-- MCP3008 -->
|
||||
<g id="node7" class="node"><title>MCP3008</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="301,-108 239,-108 239,-72 301,-72 301,-108"/>
|
||||
<text text-anchor="middle" x="270" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3008</text>
|
||||
</g>
|
||||
<!-- MCP3008->MCP3xxx -->
|
||||
<g id="edge6" class="edge"><title>MCP3008->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M270,-108.303C270,-116.017 270,-125.288 270,-133.888"/>
|
||||
<polygon fill="black" stroke="black" points="266.5,-133.896 270,-143.896 273.5,-133.896 266.5,-133.896"/>
|
||||
</g>
|
||||
<!-- MCP3204 -->
|
||||
<g id="node8" class="node"><title>MCP3204</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="381,-108 319,-108 319,-72 381,-72 381,-108"/>
|
||||
<text text-anchor="middle" x="350" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3204</text>
|
||||
</g>
|
||||
<!-- MCP3204->MCP3xxx -->
|
||||
<g id="edge7" class="edge"><title>MCP3204->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M330.225,-108.303C320.246,-117.035 307.989,-127.76 297.143,-137.25"/>
|
||||
<polygon fill="black" stroke="black" points="294.769,-134.677 289.548,-143.896 299.378,-139.945 294.769,-134.677"/>
|
||||
</g>
|
||||
<!-- MCP3208 -->
|
||||
<g id="node9" class="node"><title>MCP3208</title>
|
||||
<polygon fill="#2980b9" stroke="#2980b9" points="461,-108 399,-108 399,-72 461,-72 461,-108"/>
|
||||
<text text-anchor="middle" x="430" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3208</text>
|
||||
</g>
|
||||
<!-- MCP3208->MCP3xxx -->
|
||||
<g id="edge8" class="edge"><title>MCP3208->MCP3xxx</title>
|
||||
<path fill="none" stroke="black" d="M399.15,-104.497C373.4,-115.762 336.557,-131.881 308.808,-144.022"/>
|
||||
<polygon fill="black" stroke="black" points="307.163,-140.921 299.405,-148.135 309.969,-147.334 307.163,-140.921"/>
|
||||
</g>
|
||||
<!-- MCP3301 -->
|
||||
<g id="node10" class="node"><title>MCP3301</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">MCP3301</text>
|
||||
</g>
|
||||
<!-- MCP3301->MCP33xx -->
|
||||
<g id="edge9" class="edge"><title>MCP3301->MCP33xx</title>
|
||||
<path fill="none" stroke="black" d="M50.7753,-36.3034C60.7537,-45.0345 73.0109,-55.7595 83.8568,-65.2497"/>
|
||||
<polygon fill="black" stroke="black" points="81.6216,-67.9446 91.4522,-71.8957 86.2312,-62.6766 81.6216,-67.9446"/>
|
||||
</g>
|
||||
<!-- MCP3302 -->
|
||||
<g id="node11" class="node"><title>MCP3302</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">MCP3302</text>
|
||||
</g>
|
||||
<!-- MCP3302->MCP33xx -->
|
||||
<g id="edge10" class="edge"><title>MCP3302->MCP33xx</title>
|
||||
<path fill="none" stroke="black" d="M111,-36.3034C111,-44.0173 111,-53.2875 111,-61.8876"/>
|
||||
<polygon fill="black" stroke="black" points="107.5,-61.8956 111,-71.8957 114.5,-61.8957 107.5,-61.8956"/>
|
||||
</g>
|
||||
<!-- MCP3304 -->
|
||||
<g id="node12" class="node"><title>MCP3304</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">MCP3304</text>
|
||||
</g>
|
||||
<!-- MCP3304->MCP33xx -->
|
||||
<g id="edge11" class="edge"><title>MCP3304->MCP33xx</title>
|
||||
<path fill="none" stroke="black" d="M171.225,-36.3034C161.246,-45.0345 148.989,-55.7595 138.143,-65.2497"/>
|
||||
<polygon fill="black" stroke="black" points="135.769,-62.6766 130.548,-71.8957 140.378,-67.9446 135.769,-62.6766"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
@@ -10,6 +10,7 @@ Table of Contents
|
||||
notes
|
||||
api_input
|
||||
api_output
|
||||
api_spi
|
||||
api_boards
|
||||
api_generic
|
||||
api_pins
|
||||
|
||||
@@ -10,7 +10,13 @@ from .pins import (
|
||||
)
|
||||
from .exc import (
|
||||
GPIOZeroError,
|
||||
DeviceClosed,
|
||||
CompositeDeviceError,
|
||||
CompositeDeviceBadName,
|
||||
SPIError,
|
||||
SPIBadArgs,
|
||||
EnergenieSocketMissing,
|
||||
EnergenieBadSocket,
|
||||
GPIODeviceError,
|
||||
GPIODeviceClosed,
|
||||
GPIOPinInUse,
|
||||
@@ -32,10 +38,15 @@ from .exc import (
|
||||
PinPWMError,
|
||||
PinPWMUnsupported,
|
||||
PinPWMFixedValue,
|
||||
GPIOZeroWarning,
|
||||
SPIWarning,
|
||||
SPISoftwareFallback,
|
||||
)
|
||||
from .devices import (
|
||||
Device,
|
||||
GPIODevice,
|
||||
CompositeDevice,
|
||||
SharedMixin,
|
||||
SourceMixin,
|
||||
ValuesMixin,
|
||||
)
|
||||
@@ -44,12 +55,14 @@ from .input_devices import (
|
||||
WaitableInputDevice,
|
||||
DigitalInputDevice,
|
||||
SmoothedInputDevice,
|
||||
AnalogInputDevice,
|
||||
Button,
|
||||
LineSensor,
|
||||
MotionSensor,
|
||||
LightSensor,
|
||||
DistanceSensor,
|
||||
)
|
||||
from .spi_devices import (
|
||||
SPIDevice,
|
||||
AnalogInputDevice,
|
||||
MCP3001,
|
||||
MCP3002,
|
||||
@@ -74,6 +87,8 @@ from .output_devices import (
|
||||
RGBLED,
|
||||
)
|
||||
from .boards import (
|
||||
CompositeOutputDevice,
|
||||
LEDCollection,
|
||||
LEDBoard,
|
||||
LEDBarGraph,
|
||||
PiLiter,
|
||||
@@ -86,4 +101,5 @@ from .boards import (
|
||||
Robot,
|
||||
RyanteckRobot,
|
||||
CamJamKitRobot,
|
||||
Energenie,
|
||||
)
|
||||
|
||||
@@ -12,36 +12,84 @@ except ImportError:
|
||||
from time import sleep
|
||||
from collections import namedtuple
|
||||
from itertools import repeat, cycle, chain
|
||||
from threading import Lock
|
||||
|
||||
from .exc import InputDeviceError, OutputDeviceError
|
||||
from .exc import (
|
||||
GPIOPinMissing,
|
||||
EnergenieSocketMissing,
|
||||
EnergenieBadSocket,
|
||||
)
|
||||
from .input_devices import Button
|
||||
from .output_devices import LED, PWMLED, Buzzer, Motor
|
||||
from .devices import GPIOThread, CompositeDevice, SourceMixin
|
||||
from .output_devices import OutputDevice, LED, PWMLED, Buzzer, Motor
|
||||
from .threads import GPIOThread
|
||||
from .devices import Device, CompositeDevice, SharedMixin, SourceMixin
|
||||
|
||||
|
||||
class LEDCollection(SourceMixin, CompositeDevice):
|
||||
class CompositeOutputDevice(SourceMixin, CompositeDevice):
|
||||
"""
|
||||
Abstract base class for :class:`LEDBoard` and :class:`LEDBarGraph`.
|
||||
Extends :class:`CompositeDevice` with :meth:`on`, :meth:`off`, and
|
||||
:meth:`toggle` methods for controlling subordinate output devices. Also
|
||||
extends :attr:`value` to be writeable.
|
||||
"""
|
||||
|
||||
def __init__(self, *pins, **kwargs):
|
||||
self._blink_thread = None
|
||||
super(LEDCollection, self).__init__()
|
||||
pwm = kwargs.get('pwm', False)
|
||||
active_high = kwargs.get('active_high', True)
|
||||
initial_value = kwargs.get('initial_value', False)
|
||||
LEDClass = PWMLED if pwm else LED
|
||||
self._leds = tuple(
|
||||
LEDClass(pin, active_high, initial_value) for pin in pins
|
||||
)
|
||||
def on(self):
|
||||
"""
|
||||
Turn all the output devices on.
|
||||
"""
|
||||
for device in self.all:
|
||||
if isinstance(device, OutputDevice):
|
||||
device.on()
|
||||
|
||||
def close(self):
|
||||
for led in self.leds:
|
||||
led.close()
|
||||
def off(self):
|
||||
"""
|
||||
Turn all the output devices off.
|
||||
"""
|
||||
for device in self.all:
|
||||
if isinstance(device, OutputDevice):
|
||||
device.off()
|
||||
|
||||
def toggle(self):
|
||||
"""
|
||||
Toggle all the output devices. For each device, if it's on, turn it
|
||||
off; if it's off, turn it on.
|
||||
"""
|
||||
for device in self.all:
|
||||
if isinstance(device, OutputDevice):
|
||||
device.toggle()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return all(led.closed for led in self.leds)
|
||||
def value(self):
|
||||
"""
|
||||
A tuple containing a value for each subordinate device. This property
|
||||
can also be set to update the state of all output subordinate devices.
|
||||
"""
|
||||
return super(CompositeOutputDevice, self).value
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
for device, v in zip(self.all, value):
|
||||
if isinstance(device, OutputDevice):
|
||||
device.value = v
|
||||
# Simply ignore values for non-output devices
|
||||
|
||||
|
||||
class LEDCollection(CompositeOutputDevice):
|
||||
"""
|
||||
Extends :class:`CompositeOutputDevice`. Abstract base class for
|
||||
:class:`LEDBoard` and :class:`LEDBarGraph`.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._blink_thread = None
|
||||
pwm = kwargs.pop('pwm', False)
|
||||
active_high = kwargs.pop('active_high', True)
|
||||
initial_value = kwargs.pop('initial_value', False)
|
||||
order = kwargs.pop('_order', None)
|
||||
LEDClass = PWMLED if pwm else LED
|
||||
super(LEDCollection, self).__init__(
|
||||
*(LEDClass(pin, active_high, initial_value) for pin in args),
|
||||
_order=order,
|
||||
**{name: LEDClass(pin, active_high, initial_value) for name, pin in kwargs.items()})
|
||||
|
||||
@property
|
||||
def leds(self):
|
||||
@@ -49,35 +97,12 @@ class LEDCollection(SourceMixin, CompositeDevice):
|
||||
A tuple of all the :class:`LED` or :class:`PWMLED` objects contained by
|
||||
the instance.
|
||||
"""
|
||||
return self._leds
|
||||
|
||||
def on(self):
|
||||
"""
|
||||
Turn all the LEDs on.
|
||||
"""
|
||||
for led in self.leds:
|
||||
led.on()
|
||||
|
||||
def off(self):
|
||||
"""
|
||||
Turn all the LEDs off.
|
||||
"""
|
||||
for led in self.leds:
|
||||
led.off()
|
||||
|
||||
def toggle(self):
|
||||
"""
|
||||
Toggle all the LEDs. For each LED, if it's on, turn it off; if it's
|
||||
off, turn it on.
|
||||
"""
|
||||
for led in self.leds:
|
||||
led.toggle()
|
||||
|
||||
return self.all
|
||||
|
||||
|
||||
class LEDBoard(LEDCollection):
|
||||
"""
|
||||
Extends :class:`CompositeDevice` and represents a generic LED board or
|
||||
Extends :class:`LEDCollection` and represents a generic LED board or
|
||||
collection of LEDs.
|
||||
|
||||
The following example turns on all the LEDs on a board containing 5 LEDs
|
||||
@@ -96,26 +121,23 @@ class LEDBoard(LEDCollection):
|
||||
If ``True``, construct :class:`PWMLED` instances for each pin. If
|
||||
``False`` (the default), construct regular :class:`LED` instances. This
|
||||
parameter can only be specified as a keyword parameter.
|
||||
|
||||
:param bool active_high:
|
||||
If ``True`` (the default), the :meth:`on` method will set all the
|
||||
associates pins to HIGH. If ``False``, the :meth:`on` method will set
|
||||
all pins to LOW (the :meth:`off` method always does the opposite).
|
||||
|
||||
:param bool initial_value:
|
||||
If ``False`` (the default), all LEDs will be off initially. If
|
||||
``None``, each device will be left in whatever state the pin is found
|
||||
in when configured for output (warning: this can be on). The ``True``,
|
||||
the device will be switched on initially.
|
||||
"""
|
||||
|
||||
def close(self):
|
||||
self._stop_blink()
|
||||
super(LEDBoard, self).close()
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
A tuple containing a value for each LED on the board. This property can
|
||||
also be set to update the state of all LEDs on the board.
|
||||
"""
|
||||
return tuple(led.value for led in self._leds)
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._stop_blink()
|
||||
for l, v in zip(self.leds, value):
|
||||
l.value = v
|
||||
|
||||
def on(self):
|
||||
self._stop_blink()
|
||||
super(LEDBoard, self).on()
|
||||
@@ -266,8 +288,12 @@ class LEDBarGraph(LEDCollection):
|
||||
|
||||
def __init__(self, *pins, **kwargs):
|
||||
super(LEDBarGraph, self).__init__(*pins, pwm=False)
|
||||
initial_value = kwargs.get('initial_value', 0)
|
||||
self.value = initial_value
|
||||
try:
|
||||
initial_value = kwargs.pop('initial_value', 0)
|
||||
self.value = initial_value
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
@@ -336,6 +362,7 @@ class PiLiter(LEDBoard):
|
||||
|
||||
.. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/
|
||||
"""
|
||||
|
||||
def __init__(self, pwm=False):
|
||||
super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25, pwm=pwm)
|
||||
|
||||
@@ -360,14 +387,12 @@ class PiLiterBarGraph(LEDBarGraph):
|
||||
|
||||
.. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/
|
||||
"""
|
||||
|
||||
def __init__(self, initial_value=0):
|
||||
super(PiLiterBarGraph, self).__init__(
|
||||
4, 17, 27, 18, 22, 23, 24, 25, initial_value=initial_value)
|
||||
|
||||
|
||||
TrafficLightTuple = namedtuple('TrafficLightTuple', ('red', 'amber', 'green'))
|
||||
|
||||
|
||||
class TrafficLights(LEDBoard):
|
||||
"""
|
||||
Extends :class:`LEDBoard` for devices containing red, amber, and green
|
||||
@@ -397,44 +422,12 @@ class TrafficLights(LEDBoard):
|
||||
"""
|
||||
def __init__(self, red=None, amber=None, green=None, pwm=False):
|
||||
if not all([red, amber, green]):
|
||||
raise OutputDeviceError(
|
||||
raise GPIOPinMissing(
|
||||
'red, amber and green pins must be provided'
|
||||
)
|
||||
super(TrafficLights, self).__init__(red, amber, green, pwm=pwm)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
A 3-tuple containing values for the red, amber, and green LEDs. This
|
||||
property can also be set to alter the state of the LEDs.
|
||||
"""
|
||||
return TrafficLightTuple(*super(TrafficLights, self).value)
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
# Eurgh, this is horrid but necessary (see #90)
|
||||
super(TrafficLights, self.__class__).value.fset(self, value)
|
||||
|
||||
@property
|
||||
def red(self):
|
||||
"""
|
||||
The :class:`LED` or :class:`PWMLED` object representing the red LED.
|
||||
"""
|
||||
return self.leds[0]
|
||||
|
||||
@property
|
||||
def amber(self):
|
||||
"""
|
||||
The :class:`LED` or :class:`PWMLED` object representing the red LED.
|
||||
"""
|
||||
return self.leds[1]
|
||||
|
||||
@property
|
||||
def green(self):
|
||||
"""
|
||||
The :class:`LED` or :class:`PWMLED` object representing the green LED.
|
||||
"""
|
||||
return self.leds[2]
|
||||
super(TrafficLights, self).__init__(
|
||||
red=red, amber=amber, green=green, pwm=pwm,
|
||||
_order=('red', 'amber', 'green'))
|
||||
|
||||
|
||||
class PiTraffic(TrafficLights):
|
||||
@@ -454,15 +447,12 @@ class PiTraffic(TrafficLights):
|
||||
To use the PI-TRAFFIC board when attached to a non-standard set of pins,
|
||||
simply use the parent class, :class:`TrafficLights`.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(PiTraffic, self).__init__(9, 10, 11)
|
||||
|
||||
|
||||
TrafficLightsBuzzerTuple = namedtuple('TrafficLightsBuzzerTuple', (
|
||||
'red', 'amber', 'green', 'buzzer'))
|
||||
|
||||
|
||||
class TrafficLightsBuzzer(SourceMixin, CompositeDevice):
|
||||
class TrafficLightsBuzzer(CompositeOutputDevice):
|
||||
"""
|
||||
Extends :class:`CompositeDevice` and is a generic class for HATs with
|
||||
traffic lights, a button and a buzzer.
|
||||
@@ -477,146 +467,11 @@ class TrafficLightsBuzzer(SourceMixin, CompositeDevice):
|
||||
:param Button button:
|
||||
An instance of :class:`Button` representing the button on the HAT.
|
||||
"""
|
||||
|
||||
def __init__(self, lights, buzzer, button):
|
||||
self._blink_thread = None
|
||||
super(TrafficLightsBuzzer, self).__init__()
|
||||
self.lights = lights
|
||||
self.buzzer = buzzer
|
||||
self.button = button
|
||||
self._all = self.lights.leds + (self.buzzer,)
|
||||
|
||||
def close(self):
|
||||
self.lights.close()
|
||||
self.buzzer.close()
|
||||
self.button.close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return all(o.closed for o in self.all)
|
||||
|
||||
@property
|
||||
def all(self):
|
||||
"""
|
||||
A tuple containing objects for all the items on the board (several
|
||||
:class:`LED` objects, a :class:`Buzzer`, and a :class:`Button`).
|
||||
"""
|
||||
return self._all
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Returns a named-tuple containing values representing the states of
|
||||
the LEDs, and the buzzer. This property can also be set to a 4-tuple
|
||||
to update the state of all the board's components.
|
||||
"""
|
||||
return TrafficLightsBuzzerTuple(
|
||||
self.lights.red.value,
|
||||
self.lights.amber.value,
|
||||
self.lights.green.value,
|
||||
self.buzzer.value,
|
||||
)
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
for i, v in zip(self.all, value):
|
||||
i.value = v
|
||||
|
||||
def on(self):
|
||||
"""
|
||||
Turn all the board's components on.
|
||||
"""
|
||||
for thing in self.all:
|
||||
thing.on()
|
||||
|
||||
def off(self):
|
||||
"""
|
||||
Turn all the board's components off.
|
||||
"""
|
||||
for thing in self.all:
|
||||
thing.off()
|
||||
|
||||
def toggle(self):
|
||||
"""
|
||||
Toggle all the board's components. For each component, if it's on, turn
|
||||
it off; if it's off, turn it on.
|
||||
"""
|
||||
for thing in self.all:
|
||||
thing.toggle()
|
||||
|
||||
def blink(
|
||||
self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0,
|
||||
n=None, background=True):
|
||||
"""
|
||||
Make all the LEDs turn on and off repeatedly.
|
||||
|
||||
:param float on_time:
|
||||
Number of seconds on. Defaults to 1 second.
|
||||
|
||||
:param float off_time:
|
||||
Number of seconds off. Defaults to 1 second.
|
||||
|
||||
:param float fade_in_time:
|
||||
Number of seconds to spend fading in. Defaults to 0. Must be 0 if
|
||||
``pwm`` was ``False`` when the class was constructed
|
||||
(:exc:`ValueError` will be raised if not).
|
||||
|
||||
:param float fade_out_time:
|
||||
Number of seconds to spend fading out. Defaults to 0. Must be 0 if
|
||||
``pwm`` was ``False`` when the class was constructed
|
||||
(:exc:`ValueError` will be raised if not).
|
||||
|
||||
:param int n:
|
||||
Number of times to blink; ``None`` (the default) means forever.
|
||||
|
||||
:param bool background:
|
||||
If ``True``, start a background thread to continue blinking and
|
||||
return immediately. If ``False``, only return when the blink is
|
||||
finished (warning: the default value of *n* will result in this
|
||||
method never returning).
|
||||
"""
|
||||
if isinstance(self.lights.leds[0], LED):
|
||||
if fade_in_time:
|
||||
raise ValueError('fade_in_time must be 0 with non-PWM LEDs')
|
||||
if fade_out_time:
|
||||
raise ValueError('fade_out_time must be 0 with non-PWM LEDs')
|
||||
self._stop_blink()
|
||||
self._blink_thread = GPIOThread(
|
||||
target=self._blink_device,
|
||||
args=(on_time, off_time, fade_in_time, fade_out_time, n)
|
||||
)
|
||||
self._blink_thread.start()
|
||||
if not background:
|
||||
self._blink_thread.join()
|
||||
self._blink_thread = None
|
||||
|
||||
def _stop_blink(self):
|
||||
if self._blink_thread:
|
||||
self._blink_thread.stop()
|
||||
self._blink_thread = None
|
||||
|
||||
def _blink_device(self, on_time, off_time, fade_in_time, fade_out_time, n, fps=50):
|
||||
sequence = []
|
||||
if fade_in_time > 0:
|
||||
sequence += [
|
||||
(i * (1 / fps) / fade_in_time, 1 / fps)
|
||||
for i in range(int(fps * fade_in_time))
|
||||
]
|
||||
sequence.append((1, on_time))
|
||||
if fade_out_time > 0:
|
||||
sequence += [
|
||||
(1 - (i * (1 / fps) / fade_out_time), 1 / fps)
|
||||
for i in range(int(fps * fade_out_time))
|
||||
]
|
||||
sequence.append((0, off_time))
|
||||
sequence = (
|
||||
cycle(sequence) if n is None else
|
||||
chain.from_iterable(repeat(sequence, n))
|
||||
)
|
||||
for value, delay in sequence:
|
||||
for thing in self._all:
|
||||
thing.value = value
|
||||
if self._blink_thread.stopping.wait(delay):
|
||||
break
|
||||
super(TrafficLightsBuzzer, self).__init__(
|
||||
lights=lights, buzzer=buzzer, button=button,
|
||||
_order=('lights', 'buzzer', 'button'))
|
||||
|
||||
|
||||
class FishDish(TrafficLightsBuzzer):
|
||||
@@ -639,6 +494,7 @@ class FishDish(TrafficLightsBuzzer):
|
||||
LED. If ``False`` (the default), construct regular :class:`LED`
|
||||
instances.
|
||||
"""
|
||||
|
||||
def __init__(self, pwm=False):
|
||||
super(FishDish, self).__init__(
|
||||
TrafficLights(9, 22, 4, pwm=pwm),
|
||||
@@ -667,6 +523,7 @@ class TrafficHat(TrafficLightsBuzzer):
|
||||
LED. If ``False`` (the default), construct regular :class:`LED`
|
||||
instances.
|
||||
"""
|
||||
|
||||
def __init__(self, pwm=False):
|
||||
super(TrafficHat, self).__init__(
|
||||
TrafficLights(24, 23, 22, pwm=pwm),
|
||||
@@ -701,9 +558,10 @@ class Robot(SourceMixin, CompositeDevice):
|
||||
A tuple of two GPIO pins representing the forward and backward inputs
|
||||
of the right motor's controller.
|
||||
"""
|
||||
|
||||
def __init__(self, left=None, right=None):
|
||||
if not all([left, right]):
|
||||
raise OutputDeviceError(
|
||||
raise GPIOPinMissing(
|
||||
'left and right motor pins must be provided'
|
||||
)
|
||||
super(Robot, self).__init__()
|
||||
@@ -822,6 +680,7 @@ class RyanteckRobot(Robot):
|
||||
robot = RyanteckRobot()
|
||||
robot.left()
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(RyanteckRobot, self).__init__(left=(17, 18), right=(22, 23))
|
||||
|
||||
@@ -841,5 +700,111 @@ class CamJamKitRobot(Robot):
|
||||
|
||||
.. _CamJam #3 EduKit: http://camjam.me/?page_id=1035
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CamJamKitRobot, self).__init__(left=(9, 10), right=(7, 8))
|
||||
|
||||
|
||||
class _EnergenieMaster(SharedMixin, CompositeOutputDevice):
|
||||
def __init__(self):
|
||||
self._lock = Lock()
|
||||
super(_EnergenieMaster, self).__init__(
|
||||
*(OutputDevice(pin) for pin in (17, 22, 23, 27)),
|
||||
mode=OutputDevice(24), enable=OutputDevice(25))
|
||||
|
||||
def close(self):
|
||||
if self._lock:
|
||||
with self._lock:
|
||||
super(_EnergenieMaster, self).close()
|
||||
self._lock = None
|
||||
|
||||
@classmethod
|
||||
def _shared_key(cls):
|
||||
# There's only one Energenie master
|
||||
return None
|
||||
|
||||
def transmit(self, socket, enable):
|
||||
with self._lock:
|
||||
try:
|
||||
code = (8 * bool(enable)) + (7 - socket)
|
||||
for bit in self.all[:4]:
|
||||
bit.value = (code & 1)
|
||||
code >>= 1
|
||||
sleep(0.1)
|
||||
self.enable.on()
|
||||
sleep(0.25)
|
||||
finally:
|
||||
self.enable.off()
|
||||
|
||||
|
||||
class Energenie(SourceMixin, Device):
|
||||
"""
|
||||
Extends :class:`Device` to represent an `Energenie socket`_ controller.
|
||||
|
||||
This class is constructed with a socket number and an optional initial
|
||||
state (defaults to ``False``, meaning off). Instances of this class can
|
||||
be used to switch peripherals on and off. For example::
|
||||
|
||||
from gpiozero import Energenie
|
||||
|
||||
lamp = Energenie(0)
|
||||
lamp.on()
|
||||
|
||||
:param int socket:
|
||||
Which socket this instance should control. This is an integer number
|
||||
between 0 and 3.
|
||||
|
||||
:param bool initial_value:
|
||||
The initial state of the socket. As Energenie sockets provide no
|
||||
means of reading their state, you must provide an initial state for
|
||||
the socket, which will be set upon construction. This defaults to
|
||||
``False`` which will switch the socket off.
|
||||
|
||||
.. _Energenie socket: https://energenie4u.co.uk/index.php/catalogue/product/ENER002-2PI
|
||||
"""
|
||||
|
||||
def __init__(self, socket=None, initial_value=False):
|
||||
if socket is None:
|
||||
raise EnergenieSocketMissing('socket number must be provided')
|
||||
if not (0 <= socket < 4):
|
||||
raise EnergenieBadSocket('socket number must be between 0 and 3')
|
||||
super(Energenie, self).__init__()
|
||||
self._socket = socket
|
||||
self._master = _EnergenieMaster()
|
||||
if initial_value:
|
||||
self.on()
|
||||
else:
|
||||
self.off()
|
||||
|
||||
def close(self):
|
||||
if self._master:
|
||||
m = self._master
|
||||
self._master = None
|
||||
m.close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._master is None
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return "<gpiozero.Energenie object on socket %d>" % self._socket
|
||||
except DeviceClosed:
|
||||
return "<gpiozero.Energenie object closed>"
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._master.transmit(self._socket, bool(value))
|
||||
self._value = bool(value)
|
||||
|
||||
def on(self):
|
||||
self.value = True
|
||||
|
||||
def off(self):
|
||||
self.value = False
|
||||
|
||||
|
||||
@@ -9,20 +9,17 @@ str = type('')
|
||||
|
||||
import atexit
|
||||
import weakref
|
||||
from threading import Thread, Event, RLock
|
||||
from collections import deque
|
||||
from collections import namedtuple
|
||||
from itertools import chain
|
||||
from types import FunctionType
|
||||
try:
|
||||
from statistics import median, mean
|
||||
except ImportError:
|
||||
from .compat import median, mean
|
||||
from threading import RLock
|
||||
|
||||
from .threads import GPIOThread, _threads_shutdown
|
||||
from .exc import (
|
||||
DeviceClosed,
|
||||
GPIOPinMissing,
|
||||
GPIOPinInUse,
|
||||
GPIODeviceClosed,
|
||||
GPIOBadQueueLen,
|
||||
GPIOBadSampleWait,
|
||||
GPIOBadSourceDelay,
|
||||
)
|
||||
|
||||
@@ -30,7 +27,7 @@ from .exc import (
|
||||
# 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
|
||||
from .pins import PINS_CLEANUP
|
||||
from .pins import _pins_shutdown
|
||||
try:
|
||||
from .pins.rpigpio import RPiGPIOPin
|
||||
DefaultPin = RPiGPIOPin
|
||||
@@ -47,24 +44,17 @@ except ImportError:
|
||||
DefaultPin = NativePin
|
||||
|
||||
|
||||
_THREADS = set()
|
||||
_PINS = set()
|
||||
# Due to interactions between RPi.GPIO cleanup and the GPIODevice.close()
|
||||
# method the same thread may attempt to acquire this lock, leading to deadlock
|
||||
# unless the lock is re-entrant
|
||||
_PINS_LOCK = RLock()
|
||||
_PINS_LOCK = RLock() # Yes, this needs to be re-entrant
|
||||
|
||||
def _shutdown():
|
||||
while _THREADS:
|
||||
for t in _THREADS.copy():
|
||||
t.stop()
|
||||
_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
|
||||
for routine in PINS_CLEANUP:
|
||||
routine()
|
||||
_pins_shutdown()
|
||||
|
||||
atexit.register(_shutdown)
|
||||
|
||||
@@ -75,9 +65,9 @@ class GPIOMeta(type):
|
||||
def __new__(mcls, name, bases, cls_dict):
|
||||
# Construct the class as normal
|
||||
cls = super(GPIOMeta, mcls).__new__(mcls, name, bases, cls_dict)
|
||||
# If there's a method in the class which has no docstring, search
|
||||
# the base classes recursively for a docstring to copy
|
||||
for attr_name, attr in cls_dict.items():
|
||||
# If there's a method in the class which has no docstring, search
|
||||
# the base classes recursively for a docstring to copy
|
||||
if isinstance(attr, FunctionType) and not attr.__doc__:
|
||||
for base_cls in cls.__mro__:
|
||||
if hasattr(base_cls, attr_name):
|
||||
@@ -87,17 +77,45 @@ class GPIOMeta(type):
|
||||
break
|
||||
return cls
|
||||
|
||||
def __call__(mcls, *args, **kwargs):
|
||||
# Construct the instance as normal and ensure it's an instance of
|
||||
# GPIOBase (defined below with a custom __setattrs__)
|
||||
result = super(GPIOMeta, mcls).__call__(*args, **kwargs)
|
||||
assert isinstance(result, GPIOBase)
|
||||
def __call__(cls, *args, **kwargs):
|
||||
# Make sure cls has GPIOBase somewhere in its ancestry (otherwise
|
||||
# setting __attrs__ below will be rather pointless)
|
||||
assert issubclass(cls, GPIOBase)
|
||||
if issubclass(cls, SharedMixin):
|
||||
# If SharedMixin appears in the class' ancestry, convert the
|
||||
# constructor arguments to a key and check whether an instance
|
||||
# already exists. Only construct the instance if the key's new.
|
||||
key = cls._shared_key(*args, **kwargs)
|
||||
try:
|
||||
self = cls._INSTANCES[key]
|
||||
self._refs += 1
|
||||
except (KeyError, ReferenceError) as e:
|
||||
self = super(GPIOMeta, cls).__call__(*args, **kwargs)
|
||||
self._refs = 1
|
||||
# Replace the close method with one that merely decrements
|
||||
# the refs counter and calls the original close method when
|
||||
# it reaches zero
|
||||
old_close = self.close
|
||||
def close():
|
||||
self._refs = max(0, self._refs - 1)
|
||||
if not self._refs:
|
||||
try:
|
||||
old_close()
|
||||
finally:
|
||||
del cls._INSTANCES[key]
|
||||
self.close = close
|
||||
cls._INSTANCES[key] = weakref.proxy(self)
|
||||
else:
|
||||
# Construct the instance as normal
|
||||
self = super(GPIOMeta, cls).__call__(*args, **kwargs)
|
||||
# At this point __new__ and __init__ have all been run. We now fix the
|
||||
# set of attributes on the class by dir'ing the instance and creating a
|
||||
# frozenset of the result called __attrs__ (which is queried by
|
||||
# GPIOBase.__setattr__)
|
||||
result.__attrs__ = frozenset(dir(result))
|
||||
return result
|
||||
# GPIOBase.__setattr__). An exception is made for SharedMixin devices
|
||||
# which can be constructed multiple times, returning the same instance
|
||||
if not issubclass(cls, SharedMixin) or self._refs == 1:
|
||||
self.__attrs__ = frozenset(dir(self))
|
||||
return self
|
||||
|
||||
|
||||
# Cross-version compatible method of using a metaclass
|
||||
@@ -119,13 +137,47 @@ class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Shut down the device and release all associated resources. This method
|
||||
can be called on an already closed device without raising an exception.
|
||||
|
||||
This method is primarily intended for interactive use at the command
|
||||
line. It disables the device and releases its pin(s) for use by another
|
||||
device.
|
||||
|
||||
You can attempt to do this simply by deleting an object, but unless
|
||||
you've cleaned up all references to the object this may not work (even
|
||||
if you've cleaned up all references, there's still no guarantee the
|
||||
garbage collector will actually delete the object at that point). By
|
||||
contrast, the close method provides a means of ensuring that the object
|
||||
is shut down.
|
||||
|
||||
For example, if you have a breadboard with a buzzer connected to pin
|
||||
16, but then wish to attach an LED instead:
|
||||
|
||||
>>> from gpiozero import *
|
||||
>>> bz = Buzzer(16)
|
||||
>>> bz.on()
|
||||
>>> bz.off()
|
||||
>>> bz.close()
|
||||
>>> led = LED(16)
|
||||
>>> led.blink()
|
||||
|
||||
:class:`Device` descendents can also be used as context managers using
|
||||
the :keyword:`with` statement. For example:
|
||||
|
||||
>>> from gpiozero import *
|
||||
>>> with Buzzer(16) as bz:
|
||||
... bz.on()
|
||||
...
|
||||
>>> with LED(16) as led:
|
||||
... led.on()
|
||||
...
|
||||
"""
|
||||
# This is a placeholder which is simply here to ensure close() can be
|
||||
# safely called from subclasses without worrying whether super-class'
|
||||
# have it (which in turn is useful in conjunction with the SourceMixin
|
||||
# class).
|
||||
"""
|
||||
Shut down the device and release all associated resources.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@@ -137,6 +189,11 @@ class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})):
|
||||
"""
|
||||
return False
|
||||
|
||||
def _check_open(self):
|
||||
if self.closed:
|
||||
raise DeviceClosed(
|
||||
'%s is closed or uninitialized' % self.__class__.__name__)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
@@ -145,7 +202,14 @@ class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})):
|
||||
|
||||
|
||||
class ValuesMixin(object):
|
||||
# NOTE Use this mixin *first* in the parent list
|
||||
"""
|
||||
Adds a :attr:`values` property to the class which returns an infinite
|
||||
generator of readings from the :attr:`value` property.
|
||||
|
||||
.. note::
|
||||
|
||||
Use this mixin *first* in the parent class list.
|
||||
"""
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
@@ -160,7 +224,14 @@ class ValuesMixin(object):
|
||||
|
||||
|
||||
class SourceMixin(object):
|
||||
# NOTE Use this mixin *first* in the parent list
|
||||
"""
|
||||
Adds a :attr:`source` property to the class which, given an iterable,
|
||||
sets :attr:`value` to each member of that iterable until it is exhausted.
|
||||
|
||||
.. note::
|
||||
|
||||
Use this mixin *first* in the parent class list.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._source = None
|
||||
@@ -214,18 +285,121 @@ class SourceMixin(object):
|
||||
self._source_thread.start()
|
||||
|
||||
|
||||
class CompositeDevice(ValuesMixin, GPIOBase):
|
||||
class SharedMixin(object):
|
||||
"""
|
||||
Represents a device composed of multiple GPIO devices like simple HATs,
|
||||
H-bridge motor controllers, robots composed of multiple motors, etc.
|
||||
This mixin marks a class as "shared". In this case, the meta-class
|
||||
(GPIOMeta) will use :meth:`_shared_key` to convert the constructor
|
||||
arguments to an immutable key, and will check whether any existing
|
||||
instances match that key. If they do, they will be returned by the
|
||||
constructor instead of a new instance. An internal reference counter is
|
||||
used to determine how many times an instance has been "constructed" in this
|
||||
way.
|
||||
|
||||
When :meth:`close` is called, an internal reference counter will be
|
||||
decremented and the instance will only close when it reaches zero.
|
||||
"""
|
||||
_INSTANCES = {}
|
||||
|
||||
def __del__(self):
|
||||
self._refs = 0
|
||||
super(SharedMixin, self).__del__()
|
||||
|
||||
@classmethod
|
||||
def _shared_key(cls, *args, **kwargs):
|
||||
"""
|
||||
Given the constructor arguments, returns an immutable key representing
|
||||
the instance. The default simply assumes all positional arguments are
|
||||
immutable.
|
||||
"""
|
||||
return args
|
||||
|
||||
|
||||
class 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.
|
||||
"""
|
||||
def __repr__(self):
|
||||
return "<gpiozero.%s object>" % (self.__class__.__name__)
|
||||
|
||||
|
||||
class GPIODevice(ValuesMixin, GPIOBase):
|
||||
class CompositeDevice(Device):
|
||||
"""
|
||||
Represents a generic GPIO device.
|
||||
Extends :class:`Device`. Represents a device composed of multiple devices
|
||||
like simple HATs, H-bridge motor controllers, robots composed of multiple
|
||||
motors, etc.
|
||||
|
||||
The constructor accepts subordinate devices as positional or keyword
|
||||
arguments. Positional arguments form unnamed devices accessed via the
|
||||
:attr:`all` attribute, while keyword arguments are added to the device
|
||||
as named (read-only) attributes.
|
||||
|
||||
:param list _order:
|
||||
If specified, this is the order of named items specified by keyword
|
||||
arguments (to ensure that the :attr:`value` tuple is constructed with a
|
||||
specific order). All keyword arguments *must* be included in the
|
||||
collection. If omitted, an arbitrary order will be selected for keyword
|
||||
arguments.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._all = ()
|
||||
self._named = {}
|
||||
self._tuple = None
|
||||
self._order = kwargs.pop('_order', None)
|
||||
if self._order is None:
|
||||
self._order = kwargs.keys()
|
||||
self._order = tuple(self._order)
|
||||
for missing_name in set(self._order) - set(kwargs.keys()):
|
||||
raise ValueError('%s missing from _order' % missing_name)
|
||||
super(CompositeDevice, self).__init__()
|
||||
for name in set(self._order) & set(dir(self)):
|
||||
raise CompositeDeviceBadName('%s is a reserved name' % name)
|
||||
self._all = args + tuple(kwargs[v] for v in self._order)
|
||||
self._named = kwargs
|
||||
self._tuple = namedtuple('CompositeDeviceValue', chain(
|
||||
(str(i) for i in range(len(args))), self._order),
|
||||
rename=True)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# if _named doesn't exist yet, pretend it's an empty dict
|
||||
if name == '_named':
|
||||
return {}
|
||||
try:
|
||||
return self._named[name]
|
||||
except KeyError:
|
||||
raise AttributeError("no such attribute %s" % name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# make named components read-only properties
|
||||
if name in self._named:
|
||||
raise AttributeError("can't set attribute %s" % name)
|
||||
return super(CompositeDevice, self).__setattr__(name, value)
|
||||
|
||||
@property
|
||||
def all(self):
|
||||
return self._all
|
||||
|
||||
def close(self):
|
||||
for device in self._all:
|
||||
device.close()
|
||||
self._all = ()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return bool(self._all)
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
return self._tuple
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.tuple(*(device.value for device in self._all))
|
||||
|
||||
|
||||
class GPIODevice(Device):
|
||||
"""
|
||||
Extends :class:`Device`. Represents a generic GPIO device.
|
||||
|
||||
This is the class at the root of the gpiozero class hierarchy. It handles
|
||||
ensuring that two GPIO devices do not share the same pin, and provides
|
||||
@@ -267,48 +441,7 @@ class GPIODevice(ValuesMixin, GPIOBase):
|
||||
def _fire_events(self):
|
||||
pass
|
||||
|
||||
def _check_open(self):
|
||||
if self.closed:
|
||||
raise GPIODeviceClosed(
|
||||
'%s is closed or uninitialized' % self.__class__.__name__)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Shut down the device and release all associated resources.
|
||||
|
||||
This method is primarily intended for interactive use at the command
|
||||
line. It disables the device and releases its pin for use by another
|
||||
device.
|
||||
|
||||
You can attempt to do this simply by deleting an object, but unless
|
||||
you've cleaned up all references to the object this may not work (even
|
||||
if you've cleaned up all references, there's still no guarantee the
|
||||
garbage collector will actually delete the object at that point). By
|
||||
contrast, the close method provides a means of ensuring that the object
|
||||
is shut down.
|
||||
|
||||
For example, if you have a breadboard with a buzzer connected to pin
|
||||
16, but then wish to attach an LED instead:
|
||||
|
||||
>>> from gpiozero import *
|
||||
>>> bz = Buzzer(16)
|
||||
>>> bz.on()
|
||||
>>> bz.off()
|
||||
>>> bz.close()
|
||||
>>> led = LED(16)
|
||||
>>> led.blink()
|
||||
|
||||
:class:`GPIODevice` descendents can also be used as context managers
|
||||
using the :keyword:`with` statement. For example:
|
||||
|
||||
>>> from gpiozero import *
|
||||
>>> with Buzzer(16) as bz:
|
||||
... bz.on()
|
||||
...
|
||||
>>> with LED(16) as led:
|
||||
... led.on()
|
||||
...
|
||||
"""
|
||||
super(GPIODevice, self).close()
|
||||
with _PINS_LOCK:
|
||||
pin = self._pin
|
||||
@@ -321,6 +454,13 @@ class GPIODevice(ValuesMixin, GPIOBase):
|
||||
def closed(self):
|
||||
return self._pin is None
|
||||
|
||||
def _check_open(self):
|
||||
try:
|
||||
super(GPIODevice, self)._check_open()
|
||||
except DeviceClosed as e:
|
||||
# For backwards compatibility; GPIODeviceClosed is deprecated
|
||||
raise GPIODeviceClosed(str(e))
|
||||
|
||||
@property
|
||||
def pin(self):
|
||||
"""
|
||||
@@ -349,66 +489,3 @@ class GPIODevice(ValuesMixin, GPIOBase):
|
||||
return "<gpiozero.%s object closed>" % self.__class__.__name__
|
||||
|
||||
|
||||
class GPIOThread(Thread):
|
||||
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
|
||||
super(GPIOThread, self).__init__(group, target, name, args, kwargs)
|
||||
self.stopping = Event()
|
||||
self.daemon = True
|
||||
|
||||
def start(self):
|
||||
self.stopping.clear()
|
||||
_THREADS.add(self)
|
||||
super(GPIOThread, self).start()
|
||||
|
||||
def stop(self):
|
||||
self.stopping.set()
|
||||
self.join()
|
||||
|
||||
def join(self):
|
||||
super(GPIOThread, self).join()
|
||||
_THREADS.discard(self)
|
||||
|
||||
|
||||
class GPIOQueue(GPIOThread):
|
||||
def __init__(
|
||||
self, parent, queue_len=5, sample_wait=0.0, partial=False,
|
||||
average=median):
|
||||
assert isinstance(parent, GPIODevice)
|
||||
assert callable(average)
|
||||
super(GPIOQueue, self).__init__(target=self.fill)
|
||||
if queue_len < 1:
|
||||
raise GPIOBadQueueLen('queue_len must be at least one')
|
||||
if sample_wait < 0:
|
||||
raise GPIOBadSampleWait('sample_wait must be 0 or greater')
|
||||
self.queue = deque(maxlen=queue_len)
|
||||
self.partial = partial
|
||||
self.sample_wait = sample_wait
|
||||
self.full = Event()
|
||||
self.parent = weakref.proxy(parent)
|
||||
self.average = average
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
if not self.partial:
|
||||
self.full.wait()
|
||||
try:
|
||||
return self.average(self.queue)
|
||||
except ZeroDivisionError:
|
||||
# No data == inactive value
|
||||
return 0.0
|
||||
|
||||
def fill(self):
|
||||
try:
|
||||
while (not self.stopping.wait(self.sample_wait) and
|
||||
len(self.queue) < self.queue.maxlen):
|
||||
self.queue.append(self.parent._read())
|
||||
if self.partial:
|
||||
self.parent._fire_events()
|
||||
self.full.set()
|
||||
while not self.stopping.wait(self.sample_wait):
|
||||
self.queue.append(self.parent._read())
|
||||
self.parent._fire_events()
|
||||
except ReferenceError:
|
||||
# Parent is dead; time to die!
|
||||
pass
|
||||
|
||||
|
||||
@@ -10,14 +10,32 @@ str = type('')
|
||||
class GPIOZeroError(Exception):
|
||||
"Base class for all exceptions in GPIO Zero"
|
||||
|
||||
class DeviceClosed(GPIOZeroError):
|
||||
"Error raised when an operation is attempted on a closed device"
|
||||
|
||||
class CompositeDeviceError(GPIOZeroError):
|
||||
"Base class for errors specific to the CompositeDevice hierarchy"
|
||||
|
||||
class CompositeDeviceBadName(CompositeDeviceError, ValueError):
|
||||
"Error raised when a composite device is constructed with a reserved name"
|
||||
|
||||
class EnergenieSocketMissing(CompositeDeviceError, ValueError):
|
||||
"Error raised when socket number is not specified"
|
||||
|
||||
class EnergenieBadSocket(CompositeDeviceError, ValueError):
|
||||
"Error raised when an invalid socket number is passed to :class:`Energenie`"
|
||||
|
||||
class SPIError(GPIOZeroError):
|
||||
"Base class for errors related to the SPI implementation"
|
||||
|
||||
class SPIBadArgs(SPIError, ValueError):
|
||||
"Error raised when invalid arguments are given while constructing :class:`SPIDevice`"
|
||||
|
||||
class GPIODeviceError(GPIOZeroError):
|
||||
"Base class for errors specific to the GPIODevice hierarchy"
|
||||
|
||||
class GPIODeviceClosed(GPIODeviceError):
|
||||
"Error raised when an operation is attempted on a closed device"
|
||||
"Deprecated descendent of :exc:`DeviceClosed`"
|
||||
|
||||
class GPIOPinInUse(GPIODeviceError):
|
||||
"Error raised when attempting to use a pin already in use by another device"
|
||||
@@ -82,3 +100,12 @@ class PinPWMUnsupported(PinPWMError, AttributeError):
|
||||
class PinPWMFixedValue(PinPWMError, AttributeError):
|
||||
"Error raised when attempting to initialize PWM on an input pin"
|
||||
|
||||
class GPIOZeroWarning(Warning):
|
||||
"Base class for all warnings in GPIO Zero"
|
||||
|
||||
class SPIWarning(GPIOZeroWarning):
|
||||
"Base class for warnings related to the SPI implementation"
|
||||
|
||||
class SPISoftwareFallback(SPIWarning):
|
||||
"Warning raised when falling back to the software implementation"
|
||||
|
||||
|
||||
@@ -13,10 +13,9 @@ from functools import wraps
|
||||
from time import sleep, time
|
||||
from threading import Event
|
||||
|
||||
from spidev import SpiDev
|
||||
|
||||
from .exc import InputDeviceError, GPIODeviceError, GPIODeviceClosed
|
||||
from .devices import GPIODevice, CompositeDevice, GPIOQueue
|
||||
from .devices import GPIODevice, CompositeDevice
|
||||
from .threads import GPIOQueue
|
||||
|
||||
|
||||
class InputDevice(GPIODevice):
|
||||
@@ -77,8 +76,10 @@ class WaitableInputDevice(InputDevice):
|
||||
state (:meth:`when_activated` and :meth:`when_deactivated`). These are
|
||||
aliased appropriately in various subclasses.
|
||||
|
||||
Note that this class provides no means of actually firing its events; it's
|
||||
effectively an abstract base class.
|
||||
.. note::
|
||||
|
||||
Note that this class provides no means of actually firing its events;
|
||||
it's effectively an abstract base class.
|
||||
"""
|
||||
def __init__(self, pin=None, pull_up=False):
|
||||
super(WaitableInputDevice, self).__init__(pin, pull_up)
|
||||
@@ -244,6 +245,13 @@ class SmoothedInputDevice(WaitableInputDevice):
|
||||
threshold which is used to determine the state of the :attr:`is_active`
|
||||
property.
|
||||
|
||||
.. note::
|
||||
|
||||
The background queue is not automatically started upon construction.
|
||||
This is to allow descendents to set up additional components before the
|
||||
queue starts reading values. Effectively this is an abstract base
|
||||
class.
|
||||
|
||||
This class is intended for use with devices which either exhibit analog
|
||||
behaviour (such as the charging time of a capacitor with an LDR), or those
|
||||
which exhibit "twitchy" behaviour (such as certain motion sensors).
|
||||
@@ -760,350 +768,3 @@ DistanceSensor.wait_for_out_of_range = DistanceSensor.wait_for_active
|
||||
DistanceSensor.wait_for_in_range = DistanceSensor.wait_for_inactive
|
||||
|
||||
|
||||
class AnalogInputDevice(CompositeDevice):
|
||||
"""
|
||||
Represents an analog input device connected to SPI (serial interface).
|
||||
|
||||
Typical analog input devices are `analog to digital converters`_ (ADCs).
|
||||
Several classes are provided for specific ADC chips, including
|
||||
:class:`MCP3004`, :class:`MCP3008`, :class:`MCP3204`, and :class:`MCP3208`.
|
||||
|
||||
The following code demonstrates reading the first channel of an MCP3008
|
||||
chip attached to the Pi's SPI pins::
|
||||
|
||||
from gpiozero import MCP3008
|
||||
|
||||
pot = MCP3008(0)
|
||||
print(pot.value)
|
||||
|
||||
The :attr:`value` attribute is normalized such that its value is always
|
||||
between 0.0 and 1.0 (or in special cases, such as differential sampling,
|
||||
-1 to +1). Hence, you can use an analog input to control the brightness of
|
||||
a :class:`PWMLED` like so::
|
||||
|
||||
from gpiozero import MCP3008, PWMLED
|
||||
|
||||
pot = MCP3008(0)
|
||||
led = PWMLED(17)
|
||||
led.source = pot.values
|
||||
|
||||
.. _analog to digital converters: https://en.wikipedia.org/wiki/Analog-to-digital_converter
|
||||
"""
|
||||
|
||||
def __init__(self, device=0, bits=None):
|
||||
if bits is None:
|
||||
raise InputDeviceError('you must specify the bit resolution of the device')
|
||||
if device not in (0, 1):
|
||||
raise InputDeviceError('device must be 0 or 1')
|
||||
self._device = device
|
||||
self._bits = bits
|
||||
self._spi = SpiDev()
|
||||
self._spi.open(0, self.device)
|
||||
super(AnalogInputDevice, self).__init__()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Shut down the device and release all associated resources.
|
||||
"""
|
||||
if self._spi:
|
||||
s = self._spi
|
||||
self._spi = None
|
||||
s.close()
|
||||
super(AnalogInputDevice, self).close()
|
||||
|
||||
@property
|
||||
def bits(self):
|
||||
"""
|
||||
The bit-resolution of the device/channel.
|
||||
"""
|
||||
return self._bits
|
||||
|
||||
@property
|
||||
def bus(self):
|
||||
"""
|
||||
The SPI bus that the device is connected to. As the Pi only has a
|
||||
single (user accessible) SPI bus, this always returns 0.
|
||||
"""
|
||||
return 0
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
"""
|
||||
The select pin that the device is connected to. The Pi has two select
|
||||
pins so this will be 0 or 1.
|
||||
"""
|
||||
return self._device
|
||||
|
||||
def _read(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
The current value read from the device, scaled to a value between 0 and
|
||||
1.
|
||||
"""
|
||||
return self._read() / (2**self.bits - 1)
|
||||
|
||||
@property
|
||||
def raw_value(self):
|
||||
"""
|
||||
The raw value as read from the device.
|
||||
"""
|
||||
return self._read()
|
||||
|
||||
|
||||
class MCP3xxx(AnalogInputDevice):
|
||||
"""
|
||||
Extends :class:`AnalogInputDevice` to implement an interface for all ADC
|
||||
chips with a protocol similar to the Microchip MCP3xxx series of devices.
|
||||
"""
|
||||
|
||||
def __init__(self, channel=0, device=0, bits=10, differential=False):
|
||||
self._channel = channel
|
||||
self._bits = bits
|
||||
self._differential = bool(differential)
|
||||
super(MCP3xxx, self).__init__(device, bits)
|
||||
|
||||
@property
|
||||
def channel(self):
|
||||
"""
|
||||
The channel to read data from. The MCP3008/3208/3304 have 8 channels
|
||||
(0-7), while the MCP3004/3204/3302 have 4 channels (0-3), and the
|
||||
MCP3301 only has 1 channel.
|
||||
"""
|
||||
return self._channel
|
||||
|
||||
@property
|
||||
def differential(self):
|
||||
"""
|
||||
If ``True``, the device is operated in pseudo-differential mode. In
|
||||
this mode one channel (specified by the channel attribute) is read
|
||||
relative to the value of a second channel (implied by the chip's
|
||||
design).
|
||||
|
||||
Please refer to the device data-sheet to determine which channel is
|
||||
used as the relative base value (for example, when using an
|
||||
:class:`MCP3008` in differential mode, channel 0 is read relative to
|
||||
channel 1).
|
||||
"""
|
||||
return self._differential
|
||||
|
||||
def _read(self):
|
||||
# MCP3008/04 or MCP3208/04 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1 2
|
||||
# ==== ======== ======== ========
|
||||
# Tx 0001MCCC xxxxxxxx xxxxxxxx
|
||||
# Rx xxxxxxxx x0RRRRRR RRRRxxxx for the 3004/08
|
||||
# Rx xxxxxxxx x0RRRRRR RRRRRRxx for the 3204/08
|
||||
#
|
||||
# The transmit bits start with 3 preamble bits "000" (to warm up), a
|
||||
# start bit "1" followed by the single/differential bit (M) which is 1
|
||||
# for single-ended read, and 0 for differential read, followed by
|
||||
# 3-bits for the channel (C). The remainder of the transmission are
|
||||
# "don't care" bits (x).
|
||||
#
|
||||
# The first byte received and the top 1 bit of the second byte are
|
||||
# don't care bits (x). These are followed by a null bit (0), and then
|
||||
# the result bits (R). 10 bits for the MCP300x, 12 bits for the
|
||||
# MCP320x.
|
||||
#
|
||||
# XXX Differential mode still requires testing
|
||||
data = self._spi.xfer2([16 + [8, 0][self.differential] + self.channel, 0, 0])
|
||||
return ((data[1] & 63) << (self.bits - 6)) | (data[2] >> (14 - self.bits))
|
||||
|
||||
|
||||
class MCP33xx(MCP3xxx):
|
||||
"""
|
||||
Extends :class:`MCP3xxx` with functionality specific to the MCP33xx family
|
||||
of ADCs; specifically this handles the full differential capability of
|
||||
these chips supporting the full 13-bit signed range of output values.
|
||||
"""
|
||||
|
||||
def __init__(self, channel=0, device=0, differential=False):
|
||||
super(MCP33xx, self).__init__(channel, device, 12, differential)
|
||||
|
||||
def _read(self):
|
||||
# MCP3304/02 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1 2
|
||||
# ==== ======== ======== ========
|
||||
# Tx 0001MCCC xxxxxxxx xxxxxxxx
|
||||
# Rx xxxxxxxx x0SRRRRR RRRRRRRx
|
||||
#
|
||||
# The transmit bits start with 3 preamble bits "000" (to warm up), a
|
||||
# start bit "1" followed by the single/differential bit (M) which is 1
|
||||
# for single-ended read, and 0 for differential read, followed by
|
||||
# 3-bits for the channel (C). The remainder of the transmission are
|
||||
# "don't care" bits (x).
|
||||
#
|
||||
# The first byte received and the top 1 bit of the second byte are
|
||||
# don't care bits (x). These are followed by a null bit (0), then the
|
||||
# sign bit (S), and then the 12 result bits (R).
|
||||
#
|
||||
# In single read mode (the default) the sign bit is always zero and the
|
||||
# result is effectively 12-bits. In differential mode, the sign bit is
|
||||
# significant and the result is a two's-complement 13-bit value.
|
||||
#
|
||||
# The MCP3301 variant of the chip always operates in differential
|
||||
# mode and effectively only has one channel (composed of an IN+ and
|
||||
# IN-). As such it requires no input, just output. This is the reason
|
||||
# we split out _send() below; so that MCP3301 can override it.
|
||||
data = self._spi.xfer2(self._send())
|
||||
# Extract the last two bytes (again, for MCP3301)
|
||||
data = data[-2:]
|
||||
result = ((data[0] & 63) << 7) | (data[1] >> 1)
|
||||
# Account for the sign bit
|
||||
if self.differential and value > 4095:
|
||||
result = -(8192 - result)
|
||||
assert -4096 <= result < 4096
|
||||
return result
|
||||
|
||||
def _send(self):
|
||||
return [16 + [8, 0][self.differential] + self.channel, 0, 0]
|
||||
|
||||
|
||||
class MCP3001(MCP3xxx):
|
||||
"""
|
||||
The `MCP3001`_ is a 10-bit analog to digital converter with 1 channel
|
||||
|
||||
.. _MCP3001: http://www.farnell.com/datasheets/630400.pdf
|
||||
"""
|
||||
def __init__(self, device=0):
|
||||
super(MCP3001, self).__init__(0, device, 10, differential=True)
|
||||
|
||||
|
||||
class MCP3002(MCP3xxx):
|
||||
"""
|
||||
The `MCP3002`_ is a 10-bit analog to digital converter with 2 channels
|
||||
(0-3).
|
||||
|
||||
.. _MCP3002: http://www.farnell.com/datasheets/1599363.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, device=0, differential=False):
|
||||
if not 0 <= channel < 2:
|
||||
raise InputDeviceError('channel must be 0 or 1')
|
||||
super(MCP3002, self).__init__(channel, device, 10, differential)
|
||||
|
||||
|
||||
class MCP3004(MCP3xxx):
|
||||
"""
|
||||
The `MCP3004`_ is a 10-bit analog to digital converter with 4 channels
|
||||
(0-3).
|
||||
|
||||
.. _MCP3004: http://www.farnell.com/datasheets/808965.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, device=0, differential=False):
|
||||
if not 0 <= channel < 4:
|
||||
raise InputDeviceError('channel must be between 0 and 3')
|
||||
super(MCP3004, self).__init__(channel, device, 10, differential)
|
||||
|
||||
|
||||
class MCP3008(MCP3xxx):
|
||||
"""
|
||||
The `MCP3008`_ is a 10-bit analog to digital converter with 8 channels
|
||||
(0-7).
|
||||
|
||||
.. _MCP3008: http://www.farnell.com/datasheets/808965.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, device=0, differential=False):
|
||||
if not 0 <= channel < 8:
|
||||
raise InputDeviceError('channel must be between 0 and 7')
|
||||
super(MCP3008, self).__init__(channel, device, 10, differential)
|
||||
|
||||
|
||||
class MCP3201(MCP3xxx):
|
||||
"""
|
||||
The `MCP3201`_ is a 12-bit analog to digital converter with 1 channel
|
||||
|
||||
.. _MCP3201: http://www.farnell.com/datasheets/1669366.pdf
|
||||
"""
|
||||
def __init__(self, device=0):
|
||||
super(MCP3201, self).__init__(0, device, 12, differential=True)
|
||||
|
||||
|
||||
class MCP3202(MCP3xxx):
|
||||
"""
|
||||
The `MCP3202`_ is a 12-bit analog to digital converter with 2 channels
|
||||
(0-1).
|
||||
|
||||
.. _MCP3202: http://www.farnell.com/datasheets/1669376.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, device=0, differential=False):
|
||||
if not 0 <= channel < 2:
|
||||
raise InputDeviceError('channel must be 0 or 1')
|
||||
super(MCP3202, self).__init__(channel, device, 12, differential)
|
||||
|
||||
|
||||
class MCP3204(MCP3xxx):
|
||||
"""
|
||||
The `MCP3204`_ is a 12-bit analog to digital converter with 4 channels
|
||||
(0-3).
|
||||
|
||||
.. _MCP3204: http://www.farnell.com/datasheets/808967.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, device=0, differential=False):
|
||||
if not 0 <= channel < 4:
|
||||
raise InputDeviceError('channel must be between 0 and 3')
|
||||
super(MCP3204, self).__init__(channel, device, 12, differential)
|
||||
|
||||
|
||||
class MCP3208(MCP3xxx):
|
||||
"""
|
||||
The `MCP3208`_ is a 12-bit analog to digital converter with 8 channels
|
||||
(0-7).
|
||||
|
||||
.. _MCP3208: http://www.farnell.com/datasheets/808967.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, device=0, differential=False):
|
||||
if not 0 <= channel < 8:
|
||||
raise InputDeviceError('channel must be between 0 and 7')
|
||||
super(MCP3208, self).__init__(channel, device, 12, differential)
|
||||
|
||||
|
||||
class MCP3301(MCP33xx):
|
||||
"""
|
||||
The `MCP3301`_ is a signed 13-bit analog to digital converter. Please note
|
||||
that the MCP3301 always operates in differential mode between its two
|
||||
channels and the output value is scaled from -1 to +1.
|
||||
|
||||
.. _MCP3301: http://www.farnell.com/datasheets/1669397.pdf
|
||||
"""
|
||||
def __init__(self, device=0):
|
||||
super(MCP3301, self).__init__(0, device, differential=True)
|
||||
|
||||
def _send(self):
|
||||
return [0, 0]
|
||||
|
||||
|
||||
class MCP3302(MCP33xx):
|
||||
"""
|
||||
The `MCP3302`_ is a 12/13-bit analog to digital converter with 4 channels
|
||||
(0-3). When operated in differential mode, the device outputs a signed
|
||||
13-bit value which is scaled from -1 to +1. When operated in single-ended
|
||||
mode (the default), the device outputs an unsigned 12-bit value scaled from
|
||||
0 to 1.
|
||||
|
||||
.. _MCP3302: http://www.farnell.com/datasheets/1486116.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, device=0, differential=False):
|
||||
if not 0 <= channel < 4:
|
||||
raise InputDeviceError('channel must be between 0 and 4')
|
||||
super(MCP3302, self).__init__(channel, device, differential)
|
||||
|
||||
|
||||
class MCP3304(MCP33xx):
|
||||
"""
|
||||
The `MCP3304`_ is a 12/13-bit analog to digital converter with 8 channels
|
||||
(0-7). When operated in differential mode, the device outputs a signed
|
||||
13-bit value which is scaled from -1 to +1. When operated in single-ended
|
||||
mode (the default), the device outputs an unsigned 12-bit value scaled from
|
||||
0 to 1.
|
||||
|
||||
.. _MCP3304: http://www.farnell.com/datasheets/1486116.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, device=0, differential=False):
|
||||
if not 0 <= channel < 8:
|
||||
raise InputDeviceError('channel must be between 0 and 7')
|
||||
super(MCP3304, self).__init__(channel, device, differential)
|
||||
|
||||
@@ -10,8 +10,9 @@ from time import sleep
|
||||
from threading import Lock
|
||||
from itertools import repeat, cycle, chain
|
||||
|
||||
from .exc import OutputDeviceBadValue, GPIOPinMissing, GPIODeviceClosed
|
||||
from .devices import GPIODevice, GPIOThread, CompositeDevice, SourceMixin
|
||||
from .exc import OutputDeviceBadValue, GPIOPinMissing
|
||||
from .devices import GPIODevice, CompositeDevice, SourceMixin
|
||||
from .threads import GPIOThread
|
||||
|
||||
|
||||
class OutputDevice(SourceMixin, GPIODevice):
|
||||
@@ -38,10 +39,8 @@ class OutputDevice(SourceMixin, GPIODevice):
|
||||
device will be switched on initially.
|
||||
"""
|
||||
def __init__(self, pin=None, active_high=True, initial_value=False):
|
||||
self._active_high = active_high
|
||||
super(OutputDevice, self).__init__(pin)
|
||||
self._active_state = True if active_high else False
|
||||
self._inactive_state = False if active_high else True
|
||||
self.active_high = active_high
|
||||
if initial_value is None:
|
||||
self.pin.function = 'output'
|
||||
elif initial_value:
|
||||
@@ -72,6 +71,10 @@ class OutputDevice(SourceMixin, GPIODevice):
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Returns ``True`` if the device is currently active and ``False``
|
||||
otherwise. Setting this property changes the state of the device.
|
||||
"""
|
||||
return super(OutputDevice, self).value
|
||||
|
||||
@value.setter
|
||||
@@ -80,7 +83,22 @@ class OutputDevice(SourceMixin, GPIODevice):
|
||||
|
||||
@property
|
||||
def active_high(self):
|
||||
return self._active_high
|
||||
"""
|
||||
When ``True``, the :attr:`value` property is ``True`` when the device's
|
||||
:attr:`pin` is high. When ``False`` the :attr:`value` property is
|
||||
``True`` when the device's pin is low (i.e. the value is inverted).
|
||||
|
||||
This property can be set after construction; be warned that changing it
|
||||
will invert :attr:`value` (i.e. changing this property doesn't change
|
||||
the device's pin state - it just changes how that state is
|
||||
interpreted).
|
||||
"""
|
||||
return self._active_state
|
||||
|
||||
@active_high.setter
|
||||
def active_high(self, value):
|
||||
self._active_state = True if value else False
|
||||
self._inactive_state = False if value else True
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
|
||||
@@ -16,6 +16,9 @@ from ..exc import (
|
||||
|
||||
|
||||
PINS_CLEANUP = []
|
||||
def _pins_shutdown():
|
||||
for routine in PINS_CLEANUP:
|
||||
routine()
|
||||
|
||||
|
||||
class Pin(object):
|
||||
|
||||
421
gpiozero/spi.py
Normal file
@@ -0,0 +1,421 @@
|
||||
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.mode & 2)
|
||||
|
||||
def _set_clock_polarity(self, value):
|
||||
self.mode = self.mode & (~2) | (bool(value) << 1)
|
||||
|
||||
def _get_clock_phase(self):
|
||||
return bool(self.mode & 1)
|
||||
|
||||
def _set_clock_phase(self, value):
|
||||
self.mode = self.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()
|
||||
self.clock_phase = False
|
||||
self.lsb_first = False
|
||||
self.bits_per_word = 8
|
||||
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(self, clock_pin, mosi_pin, miso_pin):
|
||||
return (clock_pin, mosi_pin, miso_pin)
|
||||
|
||||
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.
|
||||
"""
|
||||
result = []
|
||||
with self.lock:
|
||||
shift = operator.lshift if self.lsb_first else operator.rshift
|
||||
for write_word in data:
|
||||
mask = 1 if self.lsb_first else 1 << (self.bits_per_word - 1)
|
||||
read_word = 0
|
||||
for bit in range(self.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 self.clock_phase:
|
||||
if self.miso.value:
|
||||
read_word |= mask
|
||||
self.clock.off()
|
||||
if self.miso is not None and self.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._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._bus.read(n)
|
||||
|
||||
def write(self, data):
|
||||
return self._bus.write(data)
|
||||
|
||||
def transfer(self, data):
|
||||
with self._bus.lock:
|
||||
self.on()
|
||||
try:
|
||||
return self._bus.transfer(data)
|
||||
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')
|
||||
with self._bus.lock:
|
||||
self._bus.clock.active_high = not (value & 2)
|
||||
self._bus.clock.off()
|
||||
self._bus.clock_phase = bool(value & 1)
|
||||
|
||||
def _get_clock_polarity(self):
|
||||
return not self._bus.clock.active_high
|
||||
|
||||
def _set_clock_polarity(self, value):
|
||||
with self._bus.lock:
|
||||
self._bus.clock.active_high = not value
|
||||
|
||||
def _get_clock_phase(self):
|
||||
return self._bus.clock_phase
|
||||
|
||||
def _set_clock_phase(self, value):
|
||||
with self._bus.lock:
|
||||
self._bus.clock_phase = bool(value)
|
||||
|
||||
def _get_lsb_first(self):
|
||||
return self._bus.lsb_first
|
||||
|
||||
def _set_lsb_first(self, value):
|
||||
with self._bus.lock:
|
||||
self._bus.lsb_first = bool(value)
|
||||
|
||||
def _get_bits_per_word(self):
|
||||
return self._bus.bits_per_word
|
||||
|
||||
def _set_bits_per_word(self, value):
|
||||
if value < 1:
|
||||
raise ValueError('bits_per_word must be positive')
|
||||
with self._bus.lock:
|
||||
self._bus.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((
|
||||
SpiDev is not None,
|
||||
spi_args['clock_pin'] == 11,
|
||||
spi_args['mosi_pin'] == 10,
|
||||
spi_args['miso_pin'] == 9,
|
||||
spi_args['select_pin'] in (7, 8),
|
||||
)):
|
||||
try:
|
||||
if shared:
|
||||
return SharedSPIHardwareInterface(
|
||||
port=0, device={8: 0, 7: 1}[spi_args['select_pin']])
|
||||
else:
|
||||
return SPIHardwareInterface(
|
||||
port=0, device={8: 0, 7: 1}[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)))
|
||||
if shared:
|
||||
return SharedSPISoftwareInterface(**spi_args)
|
||||
else:
|
||||
return SPISoftwareInterface(**spi_args)
|
||||
|
||||
361
gpiozero/spi_devices.py
Normal file
@@ -0,0 +1,361 @@
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
print_function,
|
||||
absolute_import,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
|
||||
from .exc import DeviceClosed
|
||||
from .devices import Device
|
||||
from .spi import extract_spi_args, SPI
|
||||
|
||||
|
||||
class SPIDevice(Device):
|
||||
"""
|
||||
Extends :class:`Device`. Represents a device that communicates via the SPI
|
||||
protocol.
|
||||
|
||||
See :ref:`spi_args` for information on the keyword arguments that can be
|
||||
specified with the constructor.
|
||||
"""
|
||||
def __init__(self, **spi_args):
|
||||
self._spi = SPI(**spi_args)
|
||||
|
||||
def close(self):
|
||||
if self._spi:
|
||||
s = self._spi
|
||||
self._spi = None
|
||||
s.close()
|
||||
super(SPIDevice, self).close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._spi is None
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return "<gpiozero.%s object using %r>" % (self.__class__.__name__, self._spi)
|
||||
except DeviceClosed:
|
||||
return "<gpiozero.%s object closed>" % self.__class__.__name__
|
||||
|
||||
|
||||
class AnalogInputDevice(SPIDevice):
|
||||
"""
|
||||
Represents an analog input device connected to SPI (serial interface).
|
||||
|
||||
Typical analog input devices are `analog to digital converters`_ (ADCs).
|
||||
Several classes are provided for specific ADC chips, including
|
||||
:class:`MCP3004`, :class:`MCP3008`, :class:`MCP3204`, and :class:`MCP3208`.
|
||||
|
||||
The following code demonstrates reading the first channel of an MCP3008
|
||||
chip attached to the Pi's SPI pins::
|
||||
|
||||
from gpiozero import MCP3008
|
||||
|
||||
pot = MCP3008(0)
|
||||
print(pot.value)
|
||||
|
||||
The :attr:`value` attribute is normalized such that its value is always
|
||||
between 0.0 and 1.0 (or in special cases, such as differential sampling,
|
||||
-1 to +1). Hence, you can use an analog input to control the brightness of
|
||||
a :class:`PWMLED` like so::
|
||||
|
||||
from gpiozero import MCP3008, PWMLED
|
||||
|
||||
pot = MCP3008(0)
|
||||
led = PWMLED(17)
|
||||
led.source = pot.values
|
||||
|
||||
.. _analog to digital converters: https://en.wikipedia.org/wiki/Analog-to-digital_converter
|
||||
"""
|
||||
|
||||
def __init__(self, bits=None, **spi_args):
|
||||
if bits is None:
|
||||
raise InputDeviceError('you must specify the bit resolution of the device')
|
||||
self._bits = bits
|
||||
super(AnalogInputDevice, self).__init__(shared=True, **spi_args)
|
||||
|
||||
@property
|
||||
def bits(self):
|
||||
"""
|
||||
The bit-resolution of the device/channel.
|
||||
"""
|
||||
return self._bits
|
||||
|
||||
def _read(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
The current value read from the device, scaled to a value between 0 and
|
||||
1 (or -1 to +1 for devices operating in differential mode).
|
||||
"""
|
||||
return self._read() / (2**self.bits - 1)
|
||||
|
||||
@property
|
||||
def raw_value(self):
|
||||
"""
|
||||
The raw value as read from the device.
|
||||
"""
|
||||
return self._read()
|
||||
|
||||
|
||||
class MCP3xxx(AnalogInputDevice):
|
||||
"""
|
||||
Extends :class:`AnalogInputDevice` to implement an interface for all ADC
|
||||
chips with a protocol similar to the Microchip MCP3xxx series of devices.
|
||||
"""
|
||||
|
||||
def __init__(self, channel=0, bits=10, differential=False, **spi_args):
|
||||
self._channel = channel
|
||||
self._bits = bits
|
||||
self._differential = bool(differential)
|
||||
super(MCP3xxx, self).__init__(bits, **spi_args)
|
||||
|
||||
@property
|
||||
def channel(self):
|
||||
"""
|
||||
The channel to read data from. The MCP3008/3208/3304 have 8 channels
|
||||
(0-7), while the MCP3004/3204/3302 have 4 channels (0-3), and the
|
||||
MCP3301 only has 1 channel.
|
||||
"""
|
||||
return self._channel
|
||||
|
||||
@property
|
||||
def differential(self):
|
||||
"""
|
||||
If ``True``, the device is operated in pseudo-differential mode. In
|
||||
this mode one channel (specified by the channel attribute) is read
|
||||
relative to the value of a second channel (implied by the chip's
|
||||
design).
|
||||
|
||||
Please refer to the device data-sheet to determine which channel is
|
||||
used as the relative base value (for example, when using an
|
||||
:class:`MCP3008` in differential mode, channel 0 is read relative to
|
||||
channel 1).
|
||||
"""
|
||||
return self._differential
|
||||
|
||||
def _read(self):
|
||||
# MCP3008/04 or MCP3208/04 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1 2
|
||||
# ==== ======== ======== ========
|
||||
# Tx 0001MCCC xxxxxxxx xxxxxxxx
|
||||
# Rx xxxxxxxx x0RRRRRR RRRRxxxx for the 3004/08
|
||||
# Rx xxxxxxxx x0RRRRRR RRRRRRxx for the 3204/08
|
||||
#
|
||||
# The transmit bits start with 3 preamble bits "000" (to warm up), a
|
||||
# start bit "1" followed by the single/differential bit (M) which is 1
|
||||
# for single-ended read, and 0 for differential read, followed by
|
||||
# 3-bits for the channel (C). The remainder of the transmission are
|
||||
# "don't care" bits (x).
|
||||
#
|
||||
# The first byte received and the top 1 bit of the second byte are
|
||||
# don't care bits (x). These are followed by a null bit (0), and then
|
||||
# the result bits (R). 10 bits for the MCP300x, 12 bits for the
|
||||
# MCP320x.
|
||||
#
|
||||
# XXX Differential mode still requires testing
|
||||
data = self._spi.transfer([16 + [8, 0][self.differential] + self.channel, 0, 0])
|
||||
return ((data[1] & 63) << (self.bits - 6)) | (data[2] >> (14 - self.bits))
|
||||
|
||||
|
||||
class MCP33xx(MCP3xxx):
|
||||
"""
|
||||
Extends :class:`MCP3xxx` with functionality specific to the MCP33xx family
|
||||
of ADCs; specifically this handles the full differential capability of
|
||||
these chips supporting the full 13-bit signed range of output values.
|
||||
"""
|
||||
|
||||
def __init__(self, channel=0, differential=False, **spi_args):
|
||||
super(MCP33xx, self).__init__(channel, 12, differential, **spi_args)
|
||||
|
||||
def _read(self):
|
||||
# MCP3304/02 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1 2
|
||||
# ==== ======== ======== ========
|
||||
# Tx 0001MCCC xxxxxxxx xxxxxxxx
|
||||
# Rx xxxxxxxx x0SRRRRR RRRRRRRx
|
||||
#
|
||||
# The transmit bits start with 3 preamble bits "000" (to warm up), a
|
||||
# start bit "1" followed by the single/differential bit (M) which is 1
|
||||
# for single-ended read, and 0 for differential read, followed by
|
||||
# 3-bits for the channel (C). The remainder of the transmission are
|
||||
# "don't care" bits (x).
|
||||
#
|
||||
# The first byte received and the top 1 bit of the second byte are
|
||||
# don't care bits (x). These are followed by a null bit (0), then the
|
||||
# sign bit (S), and then the 12 result bits (R).
|
||||
#
|
||||
# In single read mode (the default) the sign bit is always zero and the
|
||||
# result is effectively 12-bits. In differential mode, the sign bit is
|
||||
# significant and the result is a two's-complement 13-bit value.
|
||||
#
|
||||
# The MCP3301 variant of the chip always operates in differential
|
||||
# mode and effectively only has one channel (composed of an IN+ and
|
||||
# IN-). As such it requires no input, just output. This is the reason
|
||||
# we split out _send() below; so that MCP3301 can override it.
|
||||
data = self._spi.transfer(self._send())
|
||||
# Extract the last two bytes (again, for MCP3301)
|
||||
data = data[-2:]
|
||||
result = ((data[0] & 63) << 7) | (data[1] >> 1)
|
||||
# Account for the sign bit
|
||||
if self.differential and value > 4095:
|
||||
result = -(8192 - result)
|
||||
assert -4096 <= result < 4096
|
||||
return result
|
||||
|
||||
def _send(self):
|
||||
return [16 + [8, 0][self.differential] + self.channel, 0, 0]
|
||||
|
||||
|
||||
class MCP3001(MCP3xxx):
|
||||
"""
|
||||
The `MCP3001`_ is a 10-bit analog to digital converter with 1 channel
|
||||
|
||||
.. _MCP3001: http://www.farnell.com/datasheets/630400.pdf
|
||||
"""
|
||||
def __init__(self, **spi_args):
|
||||
super(MCP3001, self).__init__(0, 10, differential=True, **spi_args)
|
||||
|
||||
|
||||
class MCP3002(MCP3xxx):
|
||||
"""
|
||||
The `MCP3002`_ is a 10-bit analog to digital converter with 2 channels
|
||||
(0-3).
|
||||
|
||||
.. _MCP3002: http://www.farnell.com/datasheets/1599363.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, **spi_args):
|
||||
if not 0 <= channel < 2:
|
||||
raise InputDeviceError('channel must be 0 or 1')
|
||||
super(MCP3002, self).__init__(channel, 10, differential, **spi_args)
|
||||
|
||||
|
||||
class MCP3004(MCP3xxx):
|
||||
"""
|
||||
The `MCP3004`_ is a 10-bit analog to digital converter with 4 channels
|
||||
(0-3).
|
||||
|
||||
.. _MCP3004: http://www.farnell.com/datasheets/808965.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, **spi_args):
|
||||
if not 0 <= channel < 4:
|
||||
raise InputDeviceError('channel must be between 0 and 3')
|
||||
super(MCP3004, self).__init__(channel, 10, differential, **spi_args)
|
||||
|
||||
|
||||
class MCP3008(MCP3xxx):
|
||||
"""
|
||||
The `MCP3008`_ is a 10-bit analog to digital converter with 8 channels
|
||||
(0-7).
|
||||
|
||||
.. _MCP3008: http://www.farnell.com/datasheets/808965.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, **spi_args):
|
||||
if not 0 <= channel < 8:
|
||||
raise InputDeviceError('channel must be between 0 and 7')
|
||||
super(MCP3008, self).__init__(channel, 10, differential, **spi_args)
|
||||
|
||||
|
||||
class MCP3201(MCP3xxx):
|
||||
"""
|
||||
The `MCP3201`_ is a 12-bit analog to digital converter with 1 channel
|
||||
|
||||
.. _MCP3201: http://www.farnell.com/datasheets/1669366.pdf
|
||||
"""
|
||||
def __init__(self, **spi_args):
|
||||
super(MCP3201, self).__init__(0, 12, differential=True, **spi_args)
|
||||
|
||||
|
||||
class MCP3202(MCP3xxx):
|
||||
"""
|
||||
The `MCP3202`_ is a 12-bit analog to digital converter with 2 channels
|
||||
(0-1).
|
||||
|
||||
.. _MCP3202: http://www.farnell.com/datasheets/1669376.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, **spi_args):
|
||||
if not 0 <= channel < 2:
|
||||
raise InputDeviceError('channel must be 0 or 1')
|
||||
super(MCP3202, self).__init__(channel, 12, differential, **spi_args)
|
||||
|
||||
|
||||
class MCP3204(MCP3xxx):
|
||||
"""
|
||||
The `MCP3204`_ is a 12-bit analog to digital converter with 4 channels
|
||||
(0-3).
|
||||
|
||||
.. _MCP3204: http://www.farnell.com/datasheets/808967.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, **spi_args):
|
||||
if not 0 <= channel < 4:
|
||||
raise InputDeviceError('channel must be between 0 and 3')
|
||||
super(MCP3204, self).__init__(channel, 12, differential, **spi_args)
|
||||
|
||||
|
||||
class MCP3208(MCP3xxx):
|
||||
"""
|
||||
The `MCP3208`_ is a 12-bit analog to digital converter with 8 channels
|
||||
(0-7).
|
||||
|
||||
.. _MCP3208: http://www.farnell.com/datasheets/808967.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, **spi_args):
|
||||
if not 0 <= channel < 8:
|
||||
raise InputDeviceError('channel must be between 0 and 7')
|
||||
super(MCP3208, self).__init__(channel, 12, differential, **spi_args)
|
||||
|
||||
|
||||
class MCP3301(MCP33xx):
|
||||
"""
|
||||
The `MCP3301`_ is a signed 13-bit analog to digital converter. Please note
|
||||
that the MCP3301 always operates in differential mode between its two
|
||||
channels and the output value is scaled from -1 to +1.
|
||||
|
||||
.. _MCP3301: http://www.farnell.com/datasheets/1669397.pdf
|
||||
"""
|
||||
def __init__(self, **spi_args):
|
||||
super(MCP3301, self).__init__(0, differential=True, **spi_args)
|
||||
|
||||
def _send(self):
|
||||
return [0, 0]
|
||||
|
||||
|
||||
class MCP3302(MCP33xx):
|
||||
"""
|
||||
The `MCP3302`_ is a 12/13-bit analog to digital converter with 4 channels
|
||||
(0-3). When operated in differential mode, the device outputs a signed
|
||||
13-bit value which is scaled from -1 to +1. When operated in single-ended
|
||||
mode (the default), the device outputs an unsigned 12-bit value scaled from
|
||||
0 to 1.
|
||||
|
||||
.. _MCP3302: http://www.farnell.com/datasheets/1486116.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, **spi_args):
|
||||
if not 0 <= channel < 4:
|
||||
raise InputDeviceError('channel must be between 0 and 4')
|
||||
super(MCP3302, self).__init__(channel, differential, **spi_args)
|
||||
|
||||
|
||||
class MCP3304(MCP33xx):
|
||||
"""
|
||||
The `MCP3304`_ is a 12/13-bit analog to digital converter with 8 channels
|
||||
(0-7). When operated in differential mode, the device outputs a signed
|
||||
13-bit value which is scaled from -1 to +1. When operated in single-ended
|
||||
mode (the default), the device outputs an unsigned 12-bit value scaled from
|
||||
0 to 1.
|
||||
|
||||
.. _MCP3304: http://www.farnell.com/datasheets/1486116.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, **spi_args):
|
||||
if not 0 <= channel < 8:
|
||||
raise InputDeviceError('channel must be between 0 and 7')
|
||||
super(MCP3304, self).__init__(channel, differential, **spi_args)
|
||||
|
||||
92
gpiozero/threads.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
print_function,
|
||||
absolute_import,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import weakref
|
||||
from collections import deque
|
||||
from threading import Thread, Event, RLock
|
||||
try:
|
||||
from statistics import median, mean
|
||||
except ImportError:
|
||||
from .compat import median, mean
|
||||
|
||||
from .exc import (
|
||||
GPIOBadQueueLen,
|
||||
GPIOBadSampleWait,
|
||||
)
|
||||
|
||||
|
||||
_THREADS = set()
|
||||
def _threads_shutdown():
|
||||
while _THREADS:
|
||||
for t in _THREADS.copy():
|
||||
t.stop()
|
||||
|
||||
|
||||
class GPIOThread(Thread):
|
||||
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
|
||||
super(GPIOThread, self).__init__(group, target, name, args, kwargs)
|
||||
self.stopping = Event()
|
||||
self.daemon = True
|
||||
|
||||
def start(self):
|
||||
self.stopping.clear()
|
||||
_THREADS.add(self)
|
||||
super(GPIOThread, self).start()
|
||||
|
||||
def stop(self):
|
||||
self.stopping.set()
|
||||
self.join()
|
||||
|
||||
def join(self):
|
||||
super(GPIOThread, self).join()
|
||||
_THREADS.discard(self)
|
||||
|
||||
|
||||
class GPIOQueue(GPIOThread):
|
||||
def __init__(
|
||||
self, parent, queue_len=5, sample_wait=0.0, partial=False,
|
||||
average=median):
|
||||
assert isinstance(parent, GPIODevice)
|
||||
assert callable(average)
|
||||
super(GPIOQueue, self).__init__(target=self.fill)
|
||||
if queue_len < 1:
|
||||
raise GPIOBadQueueLen('queue_len must be at least one')
|
||||
if sample_wait < 0:
|
||||
raise GPIOBadSampleWait('sample_wait must be 0 or greater')
|
||||
self.queue = deque(maxlen=queue_len)
|
||||
self.partial = partial
|
||||
self.sample_wait = sample_wait
|
||||
self.full = Event()
|
||||
self.parent = weakref.proxy(parent)
|
||||
self.average = average
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
if not self.partial:
|
||||
self.full.wait()
|
||||
try:
|
||||
return self.average(self.queue)
|
||||
except ZeroDivisionError:
|
||||
# No data == inactive value
|
||||
return 0.0
|
||||
|
||||
def fill(self):
|
||||
try:
|
||||
while (not self.stopping.wait(self.sample_wait) and
|
||||
len(self.queue) < self.queue.maxlen):
|
||||
self.queue.append(self.parent._read())
|
||||
if self.partial:
|
||||
self.parent._fire_events()
|
||||
self.full.set()
|
||||
while not self.stopping.wait(self.sample_wait):
|
||||
self.queue.append(self.parent._read())
|
||||
self.parent._fire_events()
|
||||
except ReferenceError:
|
||||
# Parent is dead; time to die!
|
||||
pass
|
||||
|
||||