This PR adds a software SPI implementation. Firstly this removes the
absolute necessity for spidev (#140), which also means when it's not
present things still work (effectively fixes #185), and also enables any
four pins to be used for SPI devices (which don't require the hardware
implementation).

The software implementation is simplistic but still supports clock
polarity and phase, select-high, and variable bits per word. However it
doesn't allow precise speeds to be implemented because it just wibbles
the clock as fast as it can (which being pure Python isn't actually that
fast).

Finally, because this PR involves creating a framework for "shared"
devices (like SPI devices with multiple channels), it made sense to bung
Energenie (#69) in as wells as this is a really simple shared device.
This commit is contained in:
Dave Jones
2016-02-12 22:55:41 +00:00
parent e09e21a42e
commit 759a6a58e6
48 changed files with 1996 additions and 1076 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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()

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -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&#45;&gt;CompositeDevice -->
<g id="edge1" class="edge"><title>AnalogInputDevice&#45;&gt;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&#45;&gt;Device -->
<g id="edge1" class="edge"><title>CompositeDevice&#45;&gt;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&#45;&gt;AnalogInputDevice -->
<g id="edge2" class="edge"><title>MCP3xxx&#45;&gt;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&#45;&gt;CompositeDevice -->
<g id="edge2" class="edge"><title>CompositeOutputDevice&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge3" class="edge"><title>MCP33xx&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge4" class="edge"><title>MCP3004&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge5" class="edge"><title>MCP3008&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge6" class="edge"><title>MCP3204&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge7" class="edge"><title>MCP3208&#45;&gt;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&#45;&gt;MCP33xx -->
<g id="edge8" class="edge"><title>MCP3301&#45;&gt;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&#45;&gt;MCP33xx -->
<g id="edge9" class="edge"><title>MCP3302&#45;&gt;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&#45;&gt;MCP33xx -->
<g id="edge10" class="edge"><title>MCP3304&#45;&gt;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&#45;&gt;CompositeDevice -->
<g id="edge11" class="edge"><title>RGBLED&#45;&gt;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&#45;&gt;CompositeDevice -->
<g id="edge12" class="edge"><title>Motor&#45;&gt;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&#45;&gt;CompositeOutputDevice -->
<g id="edge3" class="edge"><title>LEDCollection&#45;&gt;CompositeOutputDevice</title>
<path fill="none" stroke="black" d="M223.398,-252.124C237.907,-261.192 255.916,-272.448 271.585,-282.241"/>
<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&#45;&gt;CompositeDevice -->
<g id="edge13" class="edge"><title>LEDBoard&#45;&gt;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&#45;&gt;LEDCollection -->
<g id="edge4" class="edge"><title>LEDBoard&#45;&gt;LEDCollection</title>
<path fill="none" stroke="black" d="M129.753,-180.303C140.836,-189.119 154.474,-199.968 166.49,-209.526"/>
<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&#45;&gt;LEDCollection -->
<g id="edge5" class="edge"><title>LEDBarGraph&#45;&gt;LEDCollection</title>
<path fill="none" stroke="black" d="M201.27,-180.303C200.498,-188.017 199.571,-197.288 198.711,-205.888"/>
<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&#45;&gt;LEDBoard -->
<g id="edge14" class="edge"><title>PiLiter&#45;&gt;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&#45;&gt;LEDBoard</title>
<path fill="none" stroke="black" d="M47.0225,-108.303C57.1256,-117.035 69.536,-127.76 80.5175,-137.25"/>
<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&#45;&gt;LEDBarGraph -->
<g id="edge7" class="edge"><title>PiLiterBarGraph&#45;&gt;LEDBarGraph</title>
<path fill="none" stroke="black" d="M206.764,-108.303C206.213,-116.017 205.551,-125.288 204.937,-133.888"/>
<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&#45;&gt;LEDBoard -->
<g id="edge15" class="edge"><title>TrafficLights&#45;&gt;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&#45;&gt;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&#45;&gt;TrafficLights -->
<g id="edge16" class="edge"><title>PiTraffic&#45;&gt;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&#45;&gt;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&#45;&gt;CompositeDevice -->
<g id="edge17" class="edge"><title>TrafficLightsBuzzer&#45;&gt;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&#45;&gt;CompositeOutputDevice -->
<g id="edge10" class="edge"><title>TrafficLightsBuzzer&#45;&gt;CompositeOutputDevice</title>
<path fill="none" stroke="black" d="M308,-252.303C308,-260.017 308,-269.288 308,-277.888"/>
<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&#45;&gt;TrafficLightsBuzzer -->
<g id="edge18" class="edge"><title>FishDish&#45;&gt;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&#45;&gt;TrafficLightsBuzzer</title>
<path fill="none" stroke="black" d="M298.966,-180.303C300.289,-188.017 301.878,-197.288 303.352,-205.888"/>
<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&#45;&gt;TrafficLightsBuzzer -->
<g id="edge19" class="edge"><title>TrafficHat&#45;&gt;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&#45;&gt;TrafficLightsBuzzer</title>
<path fill="none" stroke="black" d="M356.933,-180.303C349.061,-188.78 339.445,-199.136 330.827,-208.417"/>
<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&#45;&gt;CompositeDevice -->
<g id="edge20" class="edge"><title>Robot&#45;&gt;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&#45;&gt;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&#45;&gt;Robot -->
<g id="edge21" class="edge"><title>RyanteckRobot&#45;&gt;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&#45;&gt;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&#45;&gt;Robot -->
<g id="edge22" class="edge"><title>CamJamKitRobot&#45;&gt;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&#45;&gt;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&#45;&gt;CompositeDevice -->
<g id="edge16" class="edge"><title>RGBLED&#45;&gt;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&#45;&gt;CompositeDevice -->
<g id="edge17" class="edge"><title>Motor&#45;&gt;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

View File

@@ -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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -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&#45;&gt;GPIODevice -->
<g id="edge1" class="edge"><title>InputDevice&#45;&gt;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&#45;&gt;Device -->
<g id="edge1" class="edge"><title>GPIODevice&#45;&gt;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&#45;&gt;InputDevice -->
<g id="edge2" class="edge"><title>WaitableInputDevice&#45;&gt;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&#45;&gt;WaitableInputDevice -->
<g id="edge3" class="edge"><title>DigitalInputDevice&#45;&gt;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&#45;&gt;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&#45;&gt;WaitableInputDevice -->
<g id="edge4" class="edge"><title>SmoothedInputDevice&#45;&gt;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&#45;&gt;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&#45;&gt;GPIODevice -->
<g id="edge2" class="edge"><title>InputDevice&#45;&gt;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&#45;&gt;WaitableInputDevice -->
<g id="edge4" class="edge"><title>DigitalInputDevice&#45;&gt;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&#45;&gt;DigitalInputDevice -->
<g id="edge5" class="edge"><title>Button&#45;&gt;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&#45;&gt;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&#45;&gt;SmoothedInputDevice -->
<g id="edge6" class="edge"><title>MotionSensor&#45;&gt;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&#45;&gt;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&#45;&gt;SmoothedInputDevice -->
<g id="edge7" class="edge"><title>LightSensor&#45;&gt;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&#45;&gt;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&#45;&gt;GPIODevice -->
<g id="edge8" class="edge"><title>OutputDevice&#45;&gt;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&#45;&gt;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&#45;&gt;OutputDevice -->
<g id="edge9" class="edge"><title>DigitalOutputDevice&#45;&gt;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&#45;&gt;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&#45;&gt;DigitalOutputDevice -->
<g id="edge10" class="edge"><title>LED&#45;&gt;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&#45;&gt;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&#45;&gt;DigitalOutputDevice -->
<g id="edge11" class="edge"><title>Buzzer&#45;&gt;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&#45;&gt;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&#45;&gt;OutputDevice -->
<g id="edge12" class="edge"><title>PWMOutputDevice&#45;&gt;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&#45;&gt;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&#45;&gt;PWMOutputDevice -->
<g id="edge13" class="edge"><title>PWMLED&#45;&gt;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&#45;&gt;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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View 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;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View 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&#45;&gt;Device -->
<g id="edge1" class="edge"><title>SPIDevice&#45;&gt;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&#45;&gt;SPIDevice -->
<g id="edge2" class="edge"><title>AnalogInputDevice&#45;&gt;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&#45;&gt;AnalogInputDevice -->
<g id="edge3" class="edge"><title>MCP3xxx&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge4" class="edge"><title>MCP33xx&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge5" class="edge"><title>MCP3004&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge6" class="edge"><title>MCP3008&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge7" class="edge"><title>MCP3204&#45;&gt;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&#45;&gt;MCP3xxx -->
<g id="edge8" class="edge"><title>MCP3208&#45;&gt;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&#45;&gt;MCP33xx -->
<g id="edge9" class="edge"><title>MCP3301&#45;&gt;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&#45;&gt;MCP33xx -->
<g id="edge10" class="edge"><title>MCP3302&#45;&gt;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&#45;&gt;MCP33xx -->
<g id="edge11" class="edge"><title>MCP3304&#45;&gt;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

Binary file not shown.

View File

@@ -10,6 +10,7 @@ Table of Contents
notes
api_input
api_output
api_spi
api_boards
api_generic
api_pins

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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)

View File

@@ -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:

View File

@@ -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
View 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
View 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
View 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

View File

@@ -45,7 +45,6 @@ __keywords__ = [
]
__requires__ = [
'spidev',
]
__extra_requires__ = {