mirror of
				https://github.com/KevinMidboe/python-gpiozero.git
				synced 2025-10-29 17:50:37 +00:00 
			
		
		
		
	Merge pull request #251 from waveform80/source-tools
The source/values toolkit
This commit is contained in:
		| @@ -41,6 +41,8 @@ Errors | |||||||
|  |  | ||||||
| .. autoexception:: DeviceClosed | .. autoexception:: DeviceClosed | ||||||
|  |  | ||||||
|  | .. autoexception:: BadEventHandler | ||||||
|  |  | ||||||
| .. autoexception:: CompositeDeviceError | .. autoexception:: CompositeDeviceError | ||||||
|  |  | ||||||
| .. autoexception:: CompositeDeviceBadName | .. autoexception:: CompositeDeviceBadName | ||||||
|   | |||||||
| @@ -16,6 +16,9 @@ classes: | |||||||
| * :class:`SPIDevice` represents devices that communicate over an SPI interface | * :class:`SPIDevice` represents devices that communicate over an SPI interface | ||||||
|   (implemented as four GPIO pins) |   (implemented as four GPIO pins) | ||||||
|  |  | ||||||
|  | * :class:`InternalDevice` represents devices that are entirely internal to | ||||||
|  |   the Pi (usually operating system related services) | ||||||
|  |  | ||||||
| * :class:`CompositeDevice` represents devices composed of multiple other | * :class:`CompositeDevice` represents devices composed of multiple other | ||||||
|   devices like HATs |   devices like HATs | ||||||
|  |  | ||||||
| @@ -31,6 +34,9 @@ There are also several `mixin classes`_: | |||||||
| * :class:`SharedMixin` which causes classes to track their construction and | * :class:`SharedMixin` which causes classes to track their construction and | ||||||
|   return existing instances when equivalent constructor arguments are passed |   return existing instances when equivalent constructor arguments are passed | ||||||
|  |  | ||||||
|  | * :class:`EventsMixin` which adds activated/deactivated events to devices | ||||||
|  |   along with the machinery to trigger those events | ||||||
|  |  | ||||||
| .. _mixin classes: https://en.wikipedia.org/wiki/Mixin | .. _mixin classes: https://en.wikipedia.org/wiki/Mixin | ||||||
|  |  | ||||||
| The current class hierarchies are displayed below. For brevity, the mixin | The current class hierarchies are displayed below. For brevity, the mixin | ||||||
| @@ -47,6 +53,10 @@ Next, the classes below :class:`SPIDevice`: | |||||||
|  |  | ||||||
| .. image:: images/spi_device_hierarchy.* | .. image:: images/spi_device_hierarchy.* | ||||||
|  |  | ||||||
|  | Next, the classes below :class:`InternalDevice`: | ||||||
|  |  | ||||||
|  | .. image:: images/other_device_hierarchy.* | ||||||
|  |  | ||||||
| Next, the classes below :class:`CompositeDevice`: | Next, the classes below :class:`CompositeDevice`: | ||||||
|  |  | ||||||
| .. image:: images/composite_device_hierarchy.* | .. image:: images/composite_device_hierarchy.* | ||||||
| @@ -60,15 +70,18 @@ Base Classes | |||||||
| ============ | ============ | ||||||
|  |  | ||||||
| .. autoclass:: Device | .. autoclass:: Device | ||||||
|     :members: close, closed |     :members: close, closed, value, is_active | ||||||
|  |  | ||||||
| .. autoclass:: GPIODevice(pin) | .. autoclass:: GPIODevice(pin) | ||||||
|     :members: |     :members: | ||||||
|  |  | ||||||
| .. autoclass:: CompositeDevice | .. autoclass:: SPIDevice | ||||||
|     :members: |     :members: | ||||||
|  |  | ||||||
| .. autoclass:: SPIDevice | .. autoclass:: InternalDevice | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: CompositeDevice | ||||||
|     :members: |     :members: | ||||||
|  |  | ||||||
| Input Devices | Input Devices | ||||||
| @@ -77,9 +90,6 @@ Input Devices | |||||||
| .. autoclass:: InputDevice(pin, pull_up=False) | .. autoclass:: InputDevice(pin, pull_up=False) | ||||||
|     :members: |     :members: | ||||||
|  |  | ||||||
| .. autoclass:: WaitableInputDevice |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| .. autoclass:: DigitalInputDevice(pin, pull_up=False, bounce_time=None) | .. autoclass:: DigitalInputDevice(pin, pull_up=False, bounce_time=None) | ||||||
|     :members: |     :members: | ||||||
|  |  | ||||||
| @@ -128,3 +138,6 @@ Mixin Classes | |||||||
| .. autoclass:: SharedMixin(...) | .. autoclass:: SharedMixin(...) | ||||||
|     :members: _shared_key |     :members: _shared_key | ||||||
|  |  | ||||||
|  | .. autoclass:: EventsMixin(...) | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								docs/api_other.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								docs/api_other.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | ================ | ||||||
|  | Internal Devices | ||||||
|  | ================ | ||||||
|  |  | ||||||
|  | .. currentmodule:: gpiozero | ||||||
|  |  | ||||||
|  | GPIO Zero also provides several "internal" devices which represent facilities | ||||||
|  | provided by the operating system itself. These can be used to react to things | ||||||
|  | like the time of day, or whether a server is available on the network. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .. autoclass:: TimeOfDay | ||||||
|  |  | ||||||
|  | .. autoclass:: PingServer | ||||||
							
								
								
									
										49
									
								
								docs/api_source_tools.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								docs/api_source_tools.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | ============ | ||||||
|  | Source Tools | ||||||
|  | ============ | ||||||
|  |  | ||||||
|  | .. currentmodule:: gpiozero | ||||||
|  |  | ||||||
|  | GPIO Zero includes several utility routines which are intended to be used with | ||||||
|  | the :attr:`~SourceMixin.source` and :attr:`~ValuesMixin.values` attributes | ||||||
|  | common to most devices in the library. Given that ``source`` and ``values`` | ||||||
|  | deal with infinite iterators, another excellent source of utilities is the | ||||||
|  | :mod:`itertools` module in the standard library. | ||||||
|  |  | ||||||
|  | Single source conversions | ||||||
|  | ========================= | ||||||
|  |  | ||||||
|  | .. autofunction:: negated | ||||||
|  |  | ||||||
|  | .. autofunction:: inverted | ||||||
|  |  | ||||||
|  | .. autofunction:: scaled | ||||||
|  |  | ||||||
|  | .. autofunction:: clamped | ||||||
|  |  | ||||||
|  | .. autofunction:: post_delayed | ||||||
|  |  | ||||||
|  | .. autofunction:: pre_delayed | ||||||
|  |  | ||||||
|  | .. autofunction:: quantized | ||||||
|  |  | ||||||
|  | .. autofunction:: queued | ||||||
|  |  | ||||||
|  | Combining sources | ||||||
|  | ================= | ||||||
|  |  | ||||||
|  | .. autofunction:: conjunction | ||||||
|  |  | ||||||
|  | .. autofunction:: disjunction | ||||||
|  |  | ||||||
|  | .. autofunction:: averaged | ||||||
|  |  | ||||||
|  | Artifical sources | ||||||
|  | ================= | ||||||
|  |  | ||||||
|  | .. autofunction:: random_values | ||||||
|  |  | ||||||
|  | .. autofunction:: sin_values | ||||||
|  |  | ||||||
|  | .. autofunction:: cos_values | ||||||
|  |  | ||||||
| @@ -9,7 +9,6 @@ digraph classes { | |||||||
|     node [color="#9ec6e0", fontcolor="#000000"] |     node [color="#9ec6e0", fontcolor="#000000"] | ||||||
|     Device; |     Device; | ||||||
|     GPIODevice; |     GPIODevice; | ||||||
|     WaitableInputDevice; |  | ||||||
|     SmoothedInputDevice; |     SmoothedInputDevice; | ||||||
|  |  | ||||||
|     /* Concrete classes */ |     /* Concrete classes */ | ||||||
| @@ -17,9 +16,8 @@ digraph classes { | |||||||
|  |  | ||||||
|     GPIODevice->Device; |     GPIODevice->Device; | ||||||
|     InputDevice->GPIODevice; |     InputDevice->GPIODevice; | ||||||
|     WaitableInputDevice->InputDevice; |     DigitalInputDevice->InputDevice; | ||||||
|     DigitalInputDevice->WaitableInputDevice; |     SmoothedInputDevice->InputDevice; | ||||||
|     SmoothedInputDevice->WaitableInputDevice; |  | ||||||
|     Button->DigitalInputDevice; |     Button->DigitalInputDevice; | ||||||
|     MotionSensor->SmoothedInputDevice; |     MotionSensor->SmoothedInputDevice; | ||||||
|     LightSensor->SmoothedInputDevice; |     LightSensor->SmoothedInputDevice; | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 42 KiB | 
| @@ -4,175 +4,165 @@ | |||||||
| <!-- Generated by graphviz version 2.36.0 (20140111.2315) | <!-- Generated by graphviz version 2.36.0 (20140111.2315) | ||||||
|  --> |  --> | ||||||
| <!-- Title: classes Pages: 1 --> | <!-- Title: classes Pages: 1 --> | ||||||
| <svg width="701pt" height="404pt" | <svg width="721pt" height="332pt" | ||||||
|  viewBox="0.00 0.00 701.00 404.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> |  viewBox="0.00 0.00 721.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 400)"> | <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 328)"> | ||||||
| <title>classes</title> | <title>classes</title> | ||||||
| <polygon fill="white" stroke="none" points="-4,4 -4,-400 697,-400 697,4 -4,4"/> | <polygon fill="white" stroke="none" points="-4,4 -4,-328 717,-328 717,4 -4,4"/> | ||||||
| <!-- Device --> | <!-- Device --> | ||||||
| <g id="node1" class="node"><title>Device</title> | <g id="node1" class="node"><title>Device</title> | ||||||
| <polygon fill="#9ec6e0" stroke="#9ec6e0" points="485,-396 431,-396 431,-360 485,-360 485,-396"/> | <polygon fill="#9ec6e0" stroke="#9ec6e0" points="499,-324 445,-324 445,-288 499,-288 499,-324"/> | ||||||
| <text text-anchor="middle" x="458" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> | <text text-anchor="middle" x="472" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> | ||||||
| </g> | </g> | ||||||
| <!-- GPIODevice --> | <!-- GPIODevice --> | ||||||
| <g id="node2" class="node"><title>GPIODevice</title> | <g id="node2" class="node"><title>GPIODevice</title> | ||||||
| <polygon fill="#9ec6e0" stroke="#9ec6e0" points="494,-324 422,-324 422,-288 494,-288 494,-324"/> | <polygon fill="#9ec6e0" stroke="#9ec6e0" points="508,-252 436,-252 436,-216 508,-216 508,-252"/> | ||||||
| <text text-anchor="middle" x="458" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text> | <text text-anchor="middle" x="472" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text> | ||||||
| </g> | </g> | ||||||
| <!-- GPIODevice->Device --> | <!-- GPIODevice->Device --> | ||||||
| <g id="edge1" class="edge"><title>GPIODevice->Device</title> | <g id="edge1" class="edge"><title>GPIODevice->Device</title> | ||||||
| <path fill="none" stroke="black" d="M458,-324.303C458,-332.017 458,-341.288 458,-349.888"/> | <path fill="none" stroke="black" d="M472,-252.303C472,-260.017 472,-269.288 472,-277.888"/> | ||||||
| <polygon fill="black" stroke="black" points="454.5,-349.896 458,-359.896 461.5,-349.896 454.5,-349.896"/> | <polygon fill="black" stroke="black" points="468.5,-277.896 472,-287.896 475.5,-277.896 468.5,-277.896"/> | ||||||
| </g> |  | ||||||
| <!-- WaitableInputDevice --> |  | ||||||
| <g id="node3" class="node"><title>WaitableInputDevice</title> |  | ||||||
| <polygon fill="#9ec6e0" stroke="#9ec6e0" points="440.5,-180 327.5,-180 327.5,-144 440.5,-144 440.5,-180"/> |  | ||||||
| <text text-anchor="middle" x="384" 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="438.5,-252 365.5,-252 365.5,-216 438.5,-216 438.5,-252"/> |  | ||||||
| <text text-anchor="middle" x="402" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">InputDevice</text> |  | ||||||
| </g> |  | ||||||
| <!-- WaitableInputDevice->InputDevice --> |  | ||||||
| <g id="edge3" class="edge"><title>WaitableInputDevice->InputDevice</title> |  | ||||||
| <path fill="none" stroke="black" d="M388.449,-180.303C390.455,-188.102 392.869,-197.491 395.101,-206.171"/> |  | ||||||
| <polygon fill="black" stroke="black" points="391.722,-207.082 397.602,-215.896 398.501,-205.339 391.722,-207.082"/> |  | ||||||
| </g> | </g> | ||||||
| <!-- SmoothedInputDevice --> | <!-- SmoothedInputDevice --> | ||||||
| <g id="node4" class="node"><title>SmoothedInputDevice</title> | <g id="node3" class="node"><title>SmoothedInputDevice</title> | ||||||
| <polygon fill="#9ec6e0" stroke="#9ec6e0" points="289.5,-108 166.5,-108 166.5,-72 289.5,-72 289.5,-108"/> | <polygon fill="#9ec6e0" stroke="#9ec6e0" points="289.5,-108 166.5,-108 166.5,-72 289.5,-72 289.5,-108"/> | ||||||
| <text text-anchor="middle" x="228" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">SmoothedInputDevice</text> | <text text-anchor="middle" x="228" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">SmoothedInputDevice</text> | ||||||
| </g> | </g> | ||||||
| <!-- SmoothedInputDevice->WaitableInputDevice --> | <!-- InputDevice --> | ||||||
| <g id="edge5" class="edge"><title>SmoothedInputDevice->WaitableInputDevice</title> | <g id="node4" class="node"><title>InputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M266.162,-108.124C287.393,-117.651 314.008,-129.593 336.569,-139.717"/> | <polygon fill="#2980b9" stroke="#2980b9" points="445.5,-180 372.5,-180 372.5,-144 445.5,-144 445.5,-180"/> | ||||||
| <polygon fill="black" stroke="black" points="335.406,-143.031 345.962,-143.932 338.271,-136.644 335.406,-143.031"/> | <text text-anchor="middle" x="409" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">InputDevice</text> | ||||||
|  | </g> | ||||||
|  | <!-- SmoothedInputDevice->InputDevice --> | ||||||
|  | <g id="edge4" class="edge"><title>SmoothedInputDevice->InputDevice</title> | ||||||
|  | <path fill="none" stroke="black" d="M272.278,-108.124C299.802,-118.769 335.123,-132.429 362.97,-143.198"/> | ||||||
|  | <polygon fill="black" stroke="black" points="361.893,-146.535 372.483,-146.877 364.418,-140.006 361.893,-146.535"/> | ||||||
| </g> | </g> | ||||||
| <!-- InputDevice->GPIODevice --> | <!-- InputDevice->GPIODevice --> | ||||||
| <g id="edge2" class="edge"><title>InputDevice->GPIODevice</title> | <g id="edge2" class="edge"><title>InputDevice->GPIODevice</title> | ||||||
| <path fill="none" stroke="black" d="M415.843,-252.303C422.489,-260.611 430.578,-270.723 437.887,-279.859"/> | <path fill="none" stroke="black" d="M424.573,-180.303C432.202,-188.78 441.523,-199.136 449.876,-208.417"/> | ||||||
| <polygon fill="black" stroke="black" points="435.336,-282.273 444.317,-287.896 440.803,-277.901 435.336,-282.273"/> | <polygon fill="black" stroke="black" points="447.315,-210.804 456.606,-215.896 452.518,-206.121 447.315,-210.804"/> | ||||||
| </g> | </g> | ||||||
| <!-- DigitalInputDevice --> | <!-- DigitalInputDevice --> | ||||||
| <g id="node6" class="node"><title>DigitalInputDevice</title> | <g id="node5" class="node"><title>DigitalInputDevice</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="439.5,-108 336.5,-108 336.5,-72 439.5,-72 439.5,-108"/> | <polygon fill="#2980b9" stroke="#2980b9" points="460.5,-108 357.5,-108 357.5,-72 460.5,-72 460.5,-108"/> | ||||||
| <text text-anchor="middle" x="388" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text> | <text text-anchor="middle" x="409" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text> | ||||||
| </g> | </g> | ||||||
| <!-- DigitalInputDevice->WaitableInputDevice --> | <!-- DigitalInputDevice->InputDevice --> | ||||||
| <g id="edge4" class="edge"><title>DigitalInputDevice->WaitableInputDevice</title> | <g id="edge3" class="edge"><title>DigitalInputDevice->InputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M387.011,-108.303C386.57,-116.017 386.041,-125.288 385.549,-133.888"/> | <path fill="none" stroke="black" d="M409,-108.303C409,-116.017 409,-125.288 409,-133.888"/> | ||||||
| <polygon fill="black" stroke="black" points="382.054,-133.712 384.977,-143.896 389.042,-134.112 382.054,-133.712"/> | <polygon fill="black" stroke="black" points="405.5,-133.896 409,-143.896 412.5,-133.896 405.5,-133.896"/> | ||||||
| </g> | </g> | ||||||
| <!-- Button --> | <!-- Button --> | ||||||
| <g id="node7" class="node"><title>Button</title> | <g id="node6" class="node"><title>Button</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="445,-36 391,-36 391,-0 445,-0 445,-36"/> | <polygon fill="#2980b9" stroke="#2980b9" points="445,-36 391,-36 391,-0 445,-0 445,-36"/> | ||||||
| <text text-anchor="middle" x="418" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text> | <text text-anchor="middle" x="418" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text> | ||||||
| </g> | </g> | ||||||
| <!-- Button->DigitalInputDevice --> | <!-- Button->DigitalInputDevice --> | ||||||
| <g id="edge6" class="edge"><title>Button->DigitalInputDevice</title> | <g id="edge5" class="edge"><title>Button->DigitalInputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M410.584,-36.3034C407.206,-44.1868 403.13,-53.6958 399.377,-62.4536"/> | <path fill="none" stroke="black" d="M415.775,-36.3034C414.783,-44.0173 413.592,-53.2875 412.486,-61.8876"/> | ||||||
| <polygon fill="black" stroke="black" points="396.053,-61.3255 395.33,-71.8957 402.487,-64.0829 396.053,-61.3255"/> | <polygon fill="black" stroke="black" points="409.003,-61.531 411.199,-71.8957 415.946,-62.4237 409.003,-61.531"/> | ||||||
| </g> | </g> | ||||||
| <!-- MotionSensor --> | <!-- MotionSensor --> | ||||||
| <g id="node8" class="node"><title>MotionSensor</title> | <g id="node7" class="node"><title>MotionSensor</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="82.5,-36 -0.5,-36 -0.5,-0 82.5,-0 82.5,-36"/> | <polygon fill="#2980b9" stroke="#2980b9" points="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> | <text text-anchor="middle" x="41" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MotionSensor</text> | ||||||
| </g> | </g> | ||||||
| <!-- MotionSensor->SmoothedInputDevice --> | <!-- MotionSensor->SmoothedInputDevice --> | ||||||
| <g id="edge7" class="edge"><title>MotionSensor->SmoothedInputDevice</title> | <g id="edge6" class="edge"><title>MotionSensor->SmoothedInputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M82.5014,-34.5353C109.086,-44.4869 143.895,-57.5168 172.8,-68.3368"/> | <path fill="none" stroke="black" d="M82.5014,-34.5353C109.086,-44.4869 143.895,-57.5168 172.8,-68.3368"/> | ||||||
| <polygon fill="black" stroke="black" points="171.763,-71.6859 182.356,-71.9139 174.217,-65.1302 171.763,-71.6859"/> | <polygon fill="black" stroke="black" points="171.763,-71.6859 182.356,-71.9139 174.217,-65.1302 171.763,-71.6859"/> | ||||||
| </g> | </g> | ||||||
| <!-- LightSensor --> | <!-- LightSensor --> | ||||||
| <g id="node9" class="node"><title>LightSensor</title> | <g id="node8" class="node"><title>LightSensor</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="175,-36 101,-36 101,-0 175,-0 175,-36"/> | <polygon fill="#2980b9" stroke="#2980b9" points="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> | <text text-anchor="middle" x="138" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LightSensor</text> | ||||||
| </g> | </g> | ||||||
| <!-- LightSensor->SmoothedInputDevice --> | <!-- LightSensor->SmoothedInputDevice --> | ||||||
| <g id="edge8" class="edge"><title>LightSensor->SmoothedInputDevice</title> | <g id="edge7" class="edge"><title>LightSensor->SmoothedInputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M160.247,-36.3034C171.582,-45.1193 185.53,-55.9679 197.819,-65.5258"/> | <path fill="none" stroke="black" d="M160.247,-36.3034C171.582,-45.1193 185.53,-55.9679 197.819,-65.5258"/> | ||||||
| <polygon fill="black" stroke="black" points="195.966,-68.519 206.009,-71.8957 200.264,-62.9935 195.966,-68.519"/> | <polygon fill="black" stroke="black" points="195.966,-68.519 206.009,-71.8957 200.264,-62.9935 195.966,-68.519"/> | ||||||
| </g> | </g> | ||||||
| <!-- LineSensor --> | <!-- LineSensor --> | ||||||
| <g id="node10" class="node"><title>LineSensor</title> | <g id="node9" class="node"><title>LineSensor</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="263,-36 193,-36 193,-0 263,-0 263,-36"/> | <polygon fill="#2980b9" stroke="#2980b9" points="263,-36 193,-36 193,-0 263,-0 263,-36"/> | ||||||
| <text text-anchor="middle" x="228" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LineSensor</text> | <text text-anchor="middle" x="228" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LineSensor</text> | ||||||
| </g> | </g> | ||||||
| <!-- LineSensor->SmoothedInputDevice --> | <!-- LineSensor->SmoothedInputDevice --> | ||||||
| <g id="edge9" class="edge"><title>LineSensor->SmoothedInputDevice</title> | <g id="edge8" class="edge"><title>LineSensor->SmoothedInputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M228,-36.3034C228,-44.0173 228,-53.2875 228,-61.8876"/> | <path fill="none" stroke="black" d="M228,-36.3034C228,-44.0173 228,-53.2875 228,-61.8876"/> | ||||||
| <polygon fill="black" stroke="black" points="224.5,-61.8956 228,-71.8957 231.5,-61.8957 224.5,-61.8956"/> | <polygon fill="black" stroke="black" points="224.5,-61.8956 228,-71.8957 231.5,-61.8957 224.5,-61.8956"/> | ||||||
| </g> | </g> | ||||||
| <!-- DistanceSensor --> | <!-- DistanceSensor --> | ||||||
| <g id="node11" class="node"><title>DistanceSensor</title> | <g id="node10" class="node"><title>DistanceSensor</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="373,-36 281,-36 281,-0 373,-0 373,-36"/> | <polygon fill="#2980b9" stroke="#2980b9" points="373,-36 281,-36 281,-0 373,-0 373,-36"/> | ||||||
| <text text-anchor="middle" x="327" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">DistanceSensor</text> | <text text-anchor="middle" x="327" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">DistanceSensor</text> | ||||||
| </g> | </g> | ||||||
| <!-- DistanceSensor->SmoothedInputDevice --> | <!-- DistanceSensor->SmoothedInputDevice --> | ||||||
| <g id="edge10" class="edge"><title>DistanceSensor->SmoothedInputDevice</title> | <g id="edge9" class="edge"><title>DistanceSensor->SmoothedInputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M302.782,-36.1239C290.077,-45.1069 274.336,-56.2375 260.577,-65.9659"/> | <path fill="none" stroke="black" d="M302.782,-36.1239C290.077,-45.1069 274.336,-56.2375 260.577,-65.9659"/> | ||||||
| <polygon fill="black" stroke="black" points="258.456,-63.1791 252.311,-71.8102 262.497,-68.8947 258.456,-63.1791"/> | <polygon fill="black" stroke="black" points="258.456,-63.1791 252.311,-71.8102 262.497,-68.8947 258.456,-63.1791"/> | ||||||
| </g> | </g> | ||||||
| <!-- OutputDevice --> | <!-- OutputDevice --> | ||||||
| <g id="node12" class="node"><title>OutputDevice</title> | <g id="node11" class="node"><title>OutputDevice</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="556,-252 474,-252 474,-216 556,-216 556,-252"/> | <polygon fill="#2980b9" stroke="#2980b9" points="576,-180 494,-180 494,-144 576,-144 576,-180"/> | ||||||
| <text text-anchor="middle" x="515" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text> | <text text-anchor="middle" x="535" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text> | ||||||
| </g> | </g> | ||||||
| <!-- OutputDevice->GPIODevice --> | <!-- OutputDevice->GPIODevice --> | ||||||
| <g id="edge11" class="edge"><title>OutputDevice->GPIODevice</title> | <g id="edge10" class="edge"><title>OutputDevice->GPIODevice</title> | ||||||
| <path fill="none" stroke="black" d="M500.91,-252.303C494.077,-260.695 485.743,-270.93 478.244,-280.139"/> | <path fill="none" stroke="black" d="M519.427,-180.303C511.798,-188.78 502.477,-199.136 494.124,-208.417"/> | ||||||
| <polygon fill="black" stroke="black" points="475.528,-277.931 471.928,-287.896 480.956,-282.351 475.528,-277.931"/> | <polygon fill="black" stroke="black" points="491.482,-206.121 487.394,-215.896 496.685,-210.804 491.482,-206.121"/> | ||||||
| </g> | </g> | ||||||
| <!-- DigitalOutputDevice --> | <!-- DigitalOutputDevice --> | ||||||
| <g id="node13" class="node"><title>DigitalOutputDevice</title> | <g id="node12" class="node"><title>DigitalOutputDevice</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="571,-180 459,-180 459,-144 571,-144 571,-180"/> | <polygon fill="#2980b9" stroke="#2980b9" points="591,-108 479,-108 479,-72 591,-72 591,-108"/> | ||||||
| <text text-anchor="middle" x="515" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text> | <text text-anchor="middle" x="535" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text> | ||||||
| </g> | </g> | ||||||
| <!-- DigitalOutputDevice->OutputDevice --> | <!-- DigitalOutputDevice->OutputDevice --> | ||||||
| <g id="edge12" class="edge"><title>DigitalOutputDevice->OutputDevice</title> | <g id="edge11" class="edge"><title>DigitalOutputDevice->OutputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M515,-180.303C515,-188.017 515,-197.288 515,-205.888"/> | <path fill="none" stroke="black" d="M535,-108.303C535,-116.017 535,-125.288 535,-133.888"/> | ||||||
| <polygon fill="black" stroke="black" points="511.5,-205.896 515,-215.896 518.5,-205.896 511.5,-205.896"/> | <polygon fill="black" stroke="black" points="531.5,-133.896 535,-143.896 538.5,-133.896 531.5,-133.896"/> | ||||||
| </g> | </g> | ||||||
| <!-- LED --> | <!-- LED --> | ||||||
| <g id="node14" class="node"><title>LED</title> | <g id="node13" class="node"><title>LED</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="519,-108 465,-108 465,-72 519,-72 519,-108"/> | <polygon fill="#2980b9" stroke="#2980b9" points="534,-36 480,-36 480,-0 534,-0 534,-36"/> | ||||||
| <text text-anchor="middle" x="492" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text> | <text text-anchor="middle" x="507" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text> | ||||||
| </g> | </g> | ||||||
| <!-- LED->DigitalOutputDevice --> | <!-- LED->DigitalOutputDevice --> | ||||||
| <g id="edge13" class="edge"><title>LED->DigitalOutputDevice</title> | <g id="edge12" class="edge"><title>LED->DigitalOutputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M497.685,-108.303C500.248,-116.102 503.333,-125.491 506.185,-134.171"/> | <path fill="none" stroke="black" d="M513.921,-36.3034C517.075,-44.1868 520.878,-53.6958 524.381,-62.4536"/> | ||||||
| <polygon fill="black" stroke="black" points="502.933,-135.488 509.38,-143.896 509.584,-133.303 502.933,-135.488"/> | <polygon fill="black" stroke="black" points="521.195,-63.9108 528.158,-71.8957 527.694,-61.311 521.195,-63.9108"/> | ||||||
| </g> | </g> | ||||||
| <!-- Buzzer --> | <!-- Buzzer --> | ||||||
| <g id="node15" class="node"><title>Buzzer</title> | <g id="node14" class="node"><title>Buzzer</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="591,-108 537,-108 537,-72 591,-72 591,-108"/> | <polygon fill="#2980b9" stroke="#2980b9" points="606,-36 552,-36 552,-0 606,-0 606,-36"/> | ||||||
| <text text-anchor="middle" x="564" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text> | <text text-anchor="middle" x="579" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text> | ||||||
| </g> | </g> | ||||||
| <!-- Buzzer->DigitalOutputDevice --> | <!-- Buzzer->DigitalOutputDevice --> | ||||||
| <g id="edge14" class="edge"><title>Buzzer->DigitalOutputDevice</title> | <g id="edge13" class="edge"><title>Buzzer->DigitalOutputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M551.888,-108.303C546.132,-116.526 539.138,-126.517 532.794,-135.579"/> | <path fill="none" stroke="black" d="M568.124,-36.3034C563.008,-44.4411 556.805,-54.311 551.155,-63.2987"/> | ||||||
| <polygon fill="black" stroke="black" points="529.84,-133.696 526.973,-143.896 535.575,-137.71 529.84,-133.696"/> | <polygon fill="black" stroke="black" points="548.11,-61.5667 545.751,-71.8957 554.036,-65.2919 548.11,-61.5667"/> | ||||||
| </g> | </g> | ||||||
| <!-- PWMOutputDevice --> | <!-- PWMOutputDevice --> | ||||||
| <g id="node16" class="node"><title>PWMOutputDevice</title> | <g id="node15" class="node"><title>PWMOutputDevice</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="693,-180 589,-180 589,-144 693,-144 693,-180"/> | <polygon fill="#2980b9" stroke="#2980b9" points="713,-108 609,-108 609,-72 713,-72 713,-108"/> | ||||||
| <text text-anchor="middle" x="641" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text> | <text text-anchor="middle" x="661" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text> | ||||||
| </g> | </g> | ||||||
| <!-- PWMOutputDevice->OutputDevice --> | <!-- PWMOutputDevice->OutputDevice --> | ||||||
| <g id="edge15" class="edge"><title>PWMOutputDevice->OutputDevice</title> | <g id="edge14" class="edge"><title>PWMOutputDevice->OutputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M610.177,-180.124C593.55,-189.361 572.835,-200.869 554.981,-210.788"/> | <path fill="none" stroke="black" d="M630.177,-108.124C613.55,-117.361 592.835,-128.869 574.981,-138.788"/> | ||||||
| <polygon fill="black" stroke="black" points="552.983,-207.894 545.942,-215.81 556.383,-214.013 552.983,-207.894"/> | <polygon fill="black" stroke="black" points="572.983,-135.894 565.942,-143.81 576.383,-142.013 572.983,-135.894"/> | ||||||
| </g> | </g> | ||||||
| <!-- PWMLED --> | <!-- PWMLED --> | ||||||
| <g id="node17" class="node"><title>PWMLED</title> | <g id="node16" class="node"><title>PWMLED</title> | ||||||
| <polygon fill="#2980b9" stroke="#2980b9" points="670,-108 612,-108 612,-72 670,-72 670,-108"/> | <polygon fill="#2980b9" stroke="#2980b9" points="690,-36 632,-36 632,-0 690,-0 690,-36"/> | ||||||
| <text text-anchor="middle" x="641" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text> | <text text-anchor="middle" x="661" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text> | ||||||
| </g> | </g> | ||||||
| <!-- PWMLED->PWMOutputDevice --> | <!-- PWMLED->PWMOutputDevice --> | ||||||
| <g id="edge16" class="edge"><title>PWMLED->PWMOutputDevice</title> | <g id="edge15" class="edge"><title>PWMLED->PWMOutputDevice</title> | ||||||
| <path fill="none" stroke="black" d="M641,-108.303C641,-116.017 641,-125.288 641,-133.888"/> | <path fill="none" stroke="black" d="M661,-36.3034C661,-44.0173 661,-53.2875 661,-61.8876"/> | ||||||
| <polygon fill="black" stroke="black" points="637.5,-133.896 641,-143.896 644.5,-133.896 637.5,-133.896"/> | <polygon fill="black" stroke="black" points="657.5,-61.8956 661,-71.8957 664.5,-61.8957 657.5,-61.8956"/> | ||||||
| </g> | </g> | ||||||
| </g> | </g> | ||||||
| </svg> | </svg> | ||||||
|   | |||||||
| Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB | 
							
								
								
									
										19
									
								
								docs/images/other_device_hierarchy.dot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								docs/images/other_device_hierarchy.dot
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | /* vim: set et sw=4 sts=4: */ | ||||||
|  |  | ||||||
|  | digraph classes { | ||||||
|  |     graph [rankdir=BT]; | ||||||
|  |     node [shape=rect, style=filled, fontname=Sans, fontsize=10]; | ||||||
|  |     edge []; | ||||||
|  |  | ||||||
|  |     /* Abstract classes */ | ||||||
|  |     node [color="#9ec6e0", fontcolor="#000000"] | ||||||
|  |     Device; | ||||||
|  |     InternalDevice; | ||||||
|  |  | ||||||
|  |     /* Concrete classes */ | ||||||
|  |     node [color="#2980b9", fontcolor="#ffffff"]; | ||||||
|  |  | ||||||
|  |     InternalDevice->Device; | ||||||
|  |     TimeOfDay->InternalDevice; | ||||||
|  |     PingServer->InternalDevice; | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								docs/images/other_device_hierarchy.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/images/other_device_hierarchy.pdf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/images/other_device_hierarchy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/images/other_device_hierarchy.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 8.8 KiB | 
							
								
								
									
										48
									
								
								docs/images/other_device_hierarchy.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								docs/images/other_device_hierarchy.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | <?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="163pt" height="188pt" | ||||||
|  |  viewBox="0.00 0.00 163.00 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||||||
|  | <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)"> | ||||||
|  | <title>classes</title> | ||||||
|  | <polygon fill="white" stroke="none" points="-4,4 -4,-184 159,-184 159,4 -4,4"/> | ||||||
|  | <!-- Device --> | ||||||
|  | <g id="node1" class="node"><title>Device</title> | ||||||
|  | <polygon fill="#9ec6e0" stroke="#9ec6e0" points="104,-180 50,-180 50,-144 104,-144 104,-180"/> | ||||||
|  | <text text-anchor="middle" x="77" y="-159.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> | ||||||
|  | </g> | ||||||
|  | <!-- InternalDevice --> | ||||||
|  | <g id="node2" class="node"><title>InternalDevice</title> | ||||||
|  | <polygon fill="#9ec6e0" stroke="#9ec6e0" points="119.5,-108 34.5,-108 34.5,-72 119.5,-72 119.5,-108"/> | ||||||
|  | <text text-anchor="middle" x="77" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">InternalDevice</text> | ||||||
|  | </g> | ||||||
|  | <!-- InternalDevice->Device --> | ||||||
|  | <g id="edge1" class="edge"><title>InternalDevice->Device</title> | ||||||
|  | <path fill="none" stroke="black" d="M77,-108.303C77,-116.017 77,-125.288 77,-133.888"/> | ||||||
|  | <polygon fill="black" stroke="black" points="73.5001,-133.896 77,-143.896 80.5001,-133.896 73.5001,-133.896"/> | ||||||
|  | </g> | ||||||
|  | <!-- TimeOfDay --> | ||||||
|  | <g id="node3" class="node"><title>TimeOfDay</title> | ||||||
|  | <polygon fill="#2980b9" stroke="#2980b9" points="68.5,-36 -0.5,-36 -0.5,-0 68.5,-0 68.5,-36"/> | ||||||
|  | <text text-anchor="middle" x="34" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">TimeOfDay</text> | ||||||
|  | </g> | ||||||
|  | <!-- TimeOfDay->InternalDevice --> | ||||||
|  | <g id="edge2" class="edge"><title>TimeOfDay->InternalDevice</title> | ||||||
|  | <path fill="none" stroke="black" d="M44.6292,-36.3034C49.6281,-44.4411 55.691,-54.311 61.2121,-63.2987"/> | ||||||
|  | <polygon fill="black" stroke="black" points="58.2766,-65.2069 66.4931,-71.8957 64.2411,-61.5429 58.2766,-65.2069"/> | ||||||
|  | </g> | ||||||
|  | <!-- PingServer --> | ||||||
|  | <g id="node4" class="node"><title>PingServer</title> | ||||||
|  | <polygon fill="#2980b9" stroke="#2980b9" points="155,-36 87,-36 87,-0 155,-0 155,-36"/> | ||||||
|  | <text text-anchor="middle" x="121" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PingServer</text> | ||||||
|  | </g> | ||||||
|  | <!-- PingServer->InternalDevice --> | ||||||
|  | <g id="edge3" class="edge"><title>PingServer->InternalDevice</title> | ||||||
|  | <path fill="none" stroke="black" d="M110.124,-36.3034C105.008,-44.4411 98.8045,-54.311 93.1551,-63.2987"/> | ||||||
|  | <polygon fill="black" stroke="black" points="90.1098,-61.5667 87.7513,-71.8957 96.0363,-65.2919 90.1098,-61.5667"/> | ||||||
|  | </g> | ||||||
|  | </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 2.7 KiB | 
| @@ -12,7 +12,9 @@ Table of Contents | |||||||
|     api_output |     api_output | ||||||
|     api_spi |     api_spi | ||||||
|     api_boards |     api_boards | ||||||
|  |     api_other | ||||||
|     api_generic |     api_generic | ||||||
|  |     api_source_tools | ||||||
|     api_pins |     api_pins | ||||||
|     api_exc |     api_exc | ||||||
|     changelog |     changelog | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ from .pins import ( | |||||||
| from .exc import ( | from .exc import ( | ||||||
|     GPIOZeroError, |     GPIOZeroError, | ||||||
|     DeviceClosed, |     DeviceClosed, | ||||||
|  |     BadEventHandler, | ||||||
|     CompositeDeviceError, |     CompositeDeviceError, | ||||||
|     CompositeDeviceBadName, |     CompositeDeviceBadName, | ||||||
|     SPIError, |     SPIError, | ||||||
| @@ -45,13 +46,15 @@ from .devices import ( | |||||||
|     Device, |     Device, | ||||||
|     GPIODevice, |     GPIODevice, | ||||||
|     CompositeDevice, |     CompositeDevice, | ||||||
|  | ) | ||||||
|  | from .mixins import ( | ||||||
|     SharedMixin, |     SharedMixin, | ||||||
|     SourceMixin, |     SourceMixin, | ||||||
|     ValuesMixin, |     ValuesMixin, | ||||||
|  |     EventsMixin, | ||||||
| ) | ) | ||||||
| from .input_devices import ( | from .input_devices import ( | ||||||
|     InputDevice, |     InputDevice, | ||||||
|     WaitableInputDevice, |  | ||||||
|     DigitalInputDevice, |     DigitalInputDevice, | ||||||
|     SmoothedInputDevice, |     SmoothedInputDevice, | ||||||
|     Button, |     Button, | ||||||
| @@ -103,3 +106,24 @@ from .boards import ( | |||||||
|     CamJamKitRobot, |     CamJamKitRobot, | ||||||
|     Energenie, |     Energenie, | ||||||
| ) | ) | ||||||
|  | from .other_devices import ( | ||||||
|  |     InternalDevice, | ||||||
|  |     PingServer, | ||||||
|  |     TimeOfDay, | ||||||
|  | ) | ||||||
|  | from .source_tools import ( | ||||||
|  |     averaged, | ||||||
|  |     clamped, | ||||||
|  |     conjunction, | ||||||
|  |     cos_values, | ||||||
|  |     disjunction, | ||||||
|  |     inverted, | ||||||
|  |     negated, | ||||||
|  |     post_delayed, | ||||||
|  |     pre_delayed, | ||||||
|  |     quantized, | ||||||
|  |     queued, | ||||||
|  |     random_values, | ||||||
|  |     scaled, | ||||||
|  |     sin_values, | ||||||
|  | ) | ||||||
|   | |||||||
| @@ -22,7 +22,8 @@ from .exc import ( | |||||||
| from .input_devices import Button | from .input_devices import Button | ||||||
| from .output_devices import OutputDevice, LED, PWMLED, Buzzer, Motor | from .output_devices import OutputDevice, LED, PWMLED, Buzzer, Motor | ||||||
| from .threads import GPIOThread | from .threads import GPIOThread | ||||||
| from .devices import Device, CompositeDevice, SharedMixin, SourceMixin | from .devices import Device, CompositeDevice | ||||||
|  | from .mixins import SharedMixin, SourceMixin | ||||||
|  |  | ||||||
|  |  | ||||||
| class CompositeOutputDevice(SourceMixin, CompositeDevice): | class CompositeOutputDevice(SourceMixin, CompositeDevice): | ||||||
|   | |||||||
| @@ -15,6 +15,10 @@ from types import FunctionType | |||||||
| from threading import RLock | from threading import RLock | ||||||
|  |  | ||||||
| from .threads import GPIOThread, _threads_shutdown | from .threads import GPIOThread, _threads_shutdown | ||||||
|  | from .mixins import ( | ||||||
|  |     ValuesMixin, | ||||||
|  |     SharedMixin, | ||||||
|  |     ) | ||||||
| from .exc import ( | from .exc import ( | ||||||
|     DeviceClosed, |     DeviceClosed, | ||||||
|     GPIOPinMissing, |     GPIOPinMissing, | ||||||
| @@ -201,127 +205,35 @@ class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})): | |||||||
|         self.close() |         self.close() | ||||||
|  |  | ||||||
|  |  | ||||||
| class ValuesMixin(object): |  | ||||||
|     """ |  | ||||||
|     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): |  | ||||||
|         """ |  | ||||||
|         An infinite iterator of values read from `value`. |  | ||||||
|         """ |  | ||||||
|         while True: |  | ||||||
|             try: |  | ||||||
|                 yield self.value |  | ||||||
|             except GPIODeviceClosed: |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SourceMixin(object): |  | ||||||
|     """ |  | ||||||
|     Adds a :attr:`source` property to the class which, given an iterable, |  | ||||||
|     sets :attr:`value` to each member of that iterable until it is exhausted. |  | ||||||
|  |  | ||||||
|     .. note:: |  | ||||||
|  |  | ||||||
|         Use this mixin *first* in the parent class list. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): |  | ||||||
|         self._source = None |  | ||||||
|         self._source_thread = None |  | ||||||
|         self._source_delay = 0.01 |  | ||||||
|         super(SourceMixin, self).__init__(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     def close(self): |  | ||||||
|         try: |  | ||||||
|             super(SourceMixin, self).close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         self.source = None |  | ||||||
|  |  | ||||||
|     def _copy_values(self, source): |  | ||||||
|         for v in source: |  | ||||||
|             self.value = v |  | ||||||
|             if self._source_thread.stopping.wait(self._source_delay): |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def source_delay(self): |  | ||||||
|         """ |  | ||||||
|         The delay (measured in seconds) in the loop used to read values from |  | ||||||
|         :attr:`source`. Defaults to 0.01 seconds which is generally sufficient |  | ||||||
|         to keep CPU usage to a minimum while providing adequate responsiveness. |  | ||||||
|         """ |  | ||||||
|         return self._source_delay |  | ||||||
|  |  | ||||||
|     @source_delay.setter |  | ||||||
|     def source_delay(self, value): |  | ||||||
|         if value < 0: |  | ||||||
|             raise GPIOBadSourceDelay('source_delay must be 0 or greater') |  | ||||||
|         self._source_delay = float(value) |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def source(self): |  | ||||||
|         """ |  | ||||||
|         The iterable to use as a source of values for :attr:`value`. |  | ||||||
|         """ |  | ||||||
|         return self._source |  | ||||||
|  |  | ||||||
|     @source.setter |  | ||||||
|     def source(self, value): |  | ||||||
|         if self._source_thread is not None: |  | ||||||
|             self._source_thread.stop() |  | ||||||
|             self._source_thread = None |  | ||||||
|         self._source = value |  | ||||||
|         if value is not None: |  | ||||||
|             self._source_thread = GPIOThread(target=self._copy_values, args=(value,)) |  | ||||||
|             self._source_thread.start() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SharedMixin(object): |  | ||||||
|     """ |  | ||||||
|     This mixin marks a class as "shared". In this case, the meta-class |  | ||||||
|     (GPIOMeta) will use :meth:`_shared_key` to convert the constructor |  | ||||||
|     arguments to an immutable key, and will check whether any existing |  | ||||||
|     instances match that key. If they do, they will be returned by the |  | ||||||
|     constructor instead of a new instance. An internal reference counter is |  | ||||||
|     used to determine how many times an instance has been "constructed" in this |  | ||||||
|     way. |  | ||||||
|  |  | ||||||
|     When :meth:`close` is called, an internal reference counter will be |  | ||||||
|     decremented and the instance will only close when it reaches zero. |  | ||||||
|     """ |  | ||||||
|     _INSTANCES = {} |  | ||||||
|  |  | ||||||
|     def __del__(self): |  | ||||||
|         self._refs = 0 |  | ||||||
|         super(SharedMixin, self).__del__() |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def _shared_key(cls, *args, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Given the constructor arguments, returns an immutable key representing |  | ||||||
|         the instance. The default simply assumes all positional arguments are |  | ||||||
|         immutable. |  | ||||||
|         """ |  | ||||||
|         return args |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Device(ValuesMixin, GPIOBase): | class Device(ValuesMixin, GPIOBase): | ||||||
|     """ |     """ | ||||||
|     Represents a single device of any type; GPIO-based, SPI-based, I2C-based, |     Represents a single device of any type; GPIO-based, SPI-based, I2C-based, | ||||||
|     etc. This is the base class of the device hierarchy. |     etc. This is the base class of the device hierarchy. It defines the | ||||||
|  |     basic services applicable to all devices (specifically thhe :attr:`is_active` | ||||||
|  |     property, the :attr:`value` property, and the :meth:`close` method). | ||||||
|     """ |     """ | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return "<gpiozero.%s object>" % (self.__class__.__name__) |         return "<gpiozero.%s object>" % (self.__class__.__name__) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def value(self): | ||||||
|  |         """ | ||||||
|  |         Returns a value representing the device's state. Frequently, this is a | ||||||
|  |         boolean value, or a number between 0 and 1 but some devices use larger | ||||||
|  |         ranges (e.g. -1 to +1) and composite devices usually use tuples to | ||||||
|  |         return the states of all their subordinate components. | ||||||
|  |         """ | ||||||
|  |         return 0 | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def is_active(self): | ||||||
|  |         """ | ||||||
|  |         Returns ``True`` if the device is currently active and ``False`` | ||||||
|  |         otherwise. This property is usually derived from :attr:`value`. Unlike | ||||||
|  |         :attr:`value`, this is *always* a boolean. | ||||||
|  |         """ | ||||||
|  |         return bool(self.value) | ||||||
|  |  | ||||||
|  |  | ||||||
| class CompositeDevice(Device): | class CompositeDevice(Device): | ||||||
|     """ |     """ | ||||||
| @@ -417,15 +329,16 @@ class CompositeDevice(Device): | |||||||
|     def value(self): |     def value(self): | ||||||
|         return self.tuple(*(device.value for device in self)) |         return self.tuple(*(device.value for device in self)) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def is_active(self): | ||||||
|  |         return any(self.value) | ||||||
|  |  | ||||||
|  |  | ||||||
| class GPIODevice(Device): | class GPIODevice(Device): | ||||||
|     """ |     """ | ||||||
|     Extends :class:`Device`. Represents a generic GPIO device. |     Extends :class:`Device`. Represents a generic GPIO device and provides | ||||||
|  |     the services common to all single-pin GPIO devices (like ensuring two | ||||||
|     This is the class at the root of the gpiozero class hierarchy. It handles |     GPIO devices do no share a :attr:`pin`). | ||||||
|     ensuring that two GPIO devices do not share the same pin, and provides |  | ||||||
|     basic services applicable to all devices (specifically the :attr:`pin` |  | ||||||
|     property, :attr:`is_active` property, and the :attr:`close` method). |  | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin (in BCM numbering) that the device is connected to. If |         The GPIO pin (in BCM numbering) that the device is connected to. If | ||||||
| @@ -494,14 +407,8 @@ class GPIODevice(Device): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def value(self): |     def value(self): | ||||||
|         """ |  | ||||||
|         Returns ``True`` if the device is currently active and ``False`` |  | ||||||
|         otherwise. |  | ||||||
|         """ |  | ||||||
|         return self._read() |         return self._read() | ||||||
|  |  | ||||||
|     is_active = value |  | ||||||
|  |  | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         try: |         try: | ||||||
|             return "<gpiozero.%s object on pin %r, is_active=%s>" % ( |             return "<gpiozero.%s object on pin %r, is_active=%s>" % ( | ||||||
|   | |||||||
| @@ -13,6 +13,9 @@ class GPIOZeroError(Exception): | |||||||
| class DeviceClosed(GPIOZeroError): | class DeviceClosed(GPIOZeroError): | ||||||
|     "Error raised when an operation is attempted on a closed device" |     "Error raised when an operation is attempted on a closed device" | ||||||
|  |  | ||||||
|  | class BadEventHandler(GPIOZeroError, ValueError): | ||||||
|  |     "Error raised when an event handler with an incompatible prototype is specified" | ||||||
|  |  | ||||||
| class CompositeDeviceError(GPIOZeroError): | class CompositeDeviceError(GPIOZeroError): | ||||||
|     "Base class for errors specific to the CompositeDevice hierarchy" |     "Base class for errors specific to the CompositeDevice hierarchy" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,15 +7,12 @@ from __future__ import ( | |||||||
|     division, |     division, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| import inspect |  | ||||||
| import warnings | import warnings | ||||||
| from functools import wraps |  | ||||||
| from time import sleep, time | from time import sleep, time | ||||||
| from threading import Event |  | ||||||
|  |  | ||||||
| from .exc import InputDeviceError, GPIODeviceError, GPIODeviceClosed | from .exc import InputDeviceError, GPIODeviceError, GPIODeviceClosed | ||||||
| from .devices import GPIODevice, CompositeDevice | from .devices import GPIODevice, CompositeDevice | ||||||
| from .threads import GPIOQueue | from .mixins import GPIOQueue, EventsMixin | ||||||
|  |  | ||||||
|  |  | ||||||
| class InputDevice(GPIODevice): | class InputDevice(GPIODevice): | ||||||
| @@ -65,148 +62,7 @@ class InputDevice(GPIODevice): | |||||||
|             return super(InputDevice, self).__repr__() |             return super(InputDevice, self).__repr__() | ||||||
|  |  | ||||||
|  |  | ||||||
| class WaitableInputDevice(InputDevice): | class DigitalInputDevice(EventsMixin, InputDevice): | ||||||
|     """ |  | ||||||
|     Represents a generic input device with distinct waitable states. |  | ||||||
|  |  | ||||||
|     This class extends :class:`InputDevice` with methods for waiting on the |  | ||||||
|     device's status (:meth:`wait_for_active` and :meth:`wait_for_inactive`), |  | ||||||
|     and properties that hold functions to be called when the device changes |  | ||||||
|     state (:meth:`when_activated` and :meth:`when_deactivated`). These are |  | ||||||
|     aliased appropriately in various subclasses. |  | ||||||
|  |  | ||||||
|     .. 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) |  | ||||||
|         self._active_event = Event() |  | ||||||
|         self._inactive_event = Event() |  | ||||||
|         self._when_activated = None |  | ||||||
|         self._when_deactivated = None |  | ||||||
|         self._last_state = None |  | ||||||
|  |  | ||||||
|     def wait_for_active(self, timeout=None): |  | ||||||
|         """ |  | ||||||
|         Pause the script until the device is activated, or the timeout is |  | ||||||
|         reached. |  | ||||||
|  |  | ||||||
|         :param float timeout: |  | ||||||
|             Number of seconds to wait before proceeding. If this is ``None`` |  | ||||||
|             (the default), then wait indefinitely until the device is active. |  | ||||||
|         """ |  | ||||||
|         return self._active_event.wait(timeout) |  | ||||||
|  |  | ||||||
|     def wait_for_inactive(self, timeout=None): |  | ||||||
|         """ |  | ||||||
|         Pause the script until the device is deactivated, or the timeout is |  | ||||||
|         reached. |  | ||||||
|  |  | ||||||
|         :param float timeout: |  | ||||||
|             Number of seconds to wait before proceeding. If this is ``None`` |  | ||||||
|             (the default), then wait indefinitely until the device is inactive. |  | ||||||
|         """ |  | ||||||
|         return self._inactive_event.wait(timeout) |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def when_activated(self): |  | ||||||
|         """ |  | ||||||
|         The function to run when the device changes state from inactive to |  | ||||||
|         active. |  | ||||||
|  |  | ||||||
|         This can be set to a function which accepts no (mandatory) parameters, |  | ||||||
|         or a Python function which accepts a single mandatory parameter (with |  | ||||||
|         as many optional parameters as you like). If the function accepts a |  | ||||||
|         single mandatory parameter, the device that activated will be passed |  | ||||||
|         as that parameter. |  | ||||||
|  |  | ||||||
|         Set this property to ``None`` (the default) to disable the event. |  | ||||||
|         """ |  | ||||||
|         return self._when_activated |  | ||||||
|  |  | ||||||
|     @when_activated.setter |  | ||||||
|     def when_activated(self, value): |  | ||||||
|         self._when_activated = self._wrap_callback(value) |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def when_deactivated(self): |  | ||||||
|         """ |  | ||||||
|         The function to run when the device changes state from active to |  | ||||||
|         inactive. |  | ||||||
|  |  | ||||||
|         This can be set to a function which accepts no (mandatory) parameters, |  | ||||||
|         or a Python function which accepts a single mandatory parameter (with |  | ||||||
|         as many optional parameters as you like). If the function accepts a |  | ||||||
|         single mandatory parameter, the device that deactivated will be |  | ||||||
|         passed as that parameter. |  | ||||||
|  |  | ||||||
|         Set this property to ``None`` (the default) to disable the event. |  | ||||||
|         """ |  | ||||||
|         return self._when_deactivated |  | ||||||
|  |  | ||||||
|     @when_deactivated.setter |  | ||||||
|     def when_deactivated(self, value): |  | ||||||
|         self._when_deactivated = self._wrap_callback(value) |  | ||||||
|  |  | ||||||
|     def _wrap_callback(self, fn): |  | ||||||
|         if fn is None: |  | ||||||
|             return None |  | ||||||
|         elif not callable(fn): |  | ||||||
|             raise InputDeviceError('value must be None or a callable') |  | ||||||
|         elif inspect.isbuiltin(fn): |  | ||||||
|             # We can't introspect the prototype of builtins. In this case we |  | ||||||
|             # assume that the builtin has no (mandatory) parameters; this is |  | ||||||
|             # the most reasonable assumption on the basis that pre-existing |  | ||||||
|             # builtins have no knowledge of gpiozero, and the sole parameter |  | ||||||
|             # we would pass is a gpiozero object |  | ||||||
|             return fn |  | ||||||
|         else: |  | ||||||
|             # Try binding ourselves to the argspec of the provided callable. |  | ||||||
|             # If this works, assume the function is capable of accepting no |  | ||||||
|             # parameters |  | ||||||
|             try: |  | ||||||
|                 inspect.getcallargs(fn) |  | ||||||
|                 return fn |  | ||||||
|             except TypeError: |  | ||||||
|                 try: |  | ||||||
|                     # If the above fails, try binding with a single parameter |  | ||||||
|                     # (ourselves). If this works, wrap the specified callback |  | ||||||
|                     inspect.getcallargs(fn, self) |  | ||||||
|                     @wraps(fn) |  | ||||||
|                     def wrapper(): |  | ||||||
|                         return fn(self) |  | ||||||
|                     return wrapper |  | ||||||
|                 except TypeError: |  | ||||||
|                     raise InputDeviceError( |  | ||||||
|                         'value must be a callable which accepts up to one ' |  | ||||||
|                         'mandatory parameter') |  | ||||||
|  |  | ||||||
|     def _fire_events(self): |  | ||||||
|         old_state = self._last_state |  | ||||||
|         new_state = self._last_state = self.is_active |  | ||||||
|         if old_state is None: |  | ||||||
|             # Initial "indeterminate" state; set events but don't fire |  | ||||||
|             # callbacks as there's not necessarily an edge |  | ||||||
|             if new_state: |  | ||||||
|                 self._active_event.set() |  | ||||||
|             else: |  | ||||||
|                 self._inactive_event.set() |  | ||||||
|         else: |  | ||||||
|             if not old_state and new_state: |  | ||||||
|                 self._inactive_event.clear() |  | ||||||
|                 self._active_event.set() |  | ||||||
|                 if self.when_activated: |  | ||||||
|                     self.when_activated() |  | ||||||
|             elif old_state and not new_state: |  | ||||||
|                 self._active_event.clear() |  | ||||||
|                 self._inactive_event.set() |  | ||||||
|                 if self.when_deactivated: |  | ||||||
|                     self.when_deactivated() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DigitalInputDevice(WaitableInputDevice): |  | ||||||
|     """ |     """ | ||||||
|     Represents a generic input device with typical on/off behaviour. |     Represents a generic input device with typical on/off behaviour. | ||||||
|  |  | ||||||
| @@ -233,7 +89,7 @@ class DigitalInputDevice(WaitableInputDevice): | |||||||
|             raise |             raise | ||||||
|  |  | ||||||
|  |  | ||||||
| class SmoothedInputDevice(WaitableInputDevice): | class SmoothedInputDevice(EventsMixin, InputDevice): | ||||||
|     """ |     """ | ||||||
|     Represents a generic input device which takes its value from the mean of a |     Represents a generic input device which takes its value from the mean of a | ||||||
|     queue of historical values. |     queue of historical values. | ||||||
|   | |||||||
							
								
								
									
										326
									
								
								gpiozero/mixins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								gpiozero/mixins.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,326 @@ | |||||||
|  | from __future__ import ( | ||||||
|  |     unicode_literals, | ||||||
|  |     print_function, | ||||||
|  |     absolute_import, | ||||||
|  |     division, | ||||||
|  |     ) | ||||||
|  | nstr = str | ||||||
|  | str = type('') | ||||||
|  |  | ||||||
|  | import inspect | ||||||
|  | import weakref | ||||||
|  | from functools import wraps | ||||||
|  | from threading import Event | ||||||
|  | from collections import deque | ||||||
|  | try: | ||||||
|  |     from statistics import median, mean | ||||||
|  | except ImportError: | ||||||
|  |     from .compat import median, mean | ||||||
|  |  | ||||||
|  | from .threads import GPIOThread | ||||||
|  | from .exc import BadEventHandler, DeviceClosed | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ValuesMixin(object): | ||||||
|  |     """ | ||||||
|  |     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): | ||||||
|  |         """ | ||||||
|  |         An infinite iterator of values read from `value`. | ||||||
|  |         """ | ||||||
|  |         while True: | ||||||
|  |             try: | ||||||
|  |                 yield self.value | ||||||
|  |             except DeviceClosed: | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SourceMixin(object): | ||||||
|  |     """ | ||||||
|  |     Adds a :attr:`source` property to the class which, given an iterable, | ||||||
|  |     sets :attr:`value` to each member of that iterable until it is exhausted. | ||||||
|  |  | ||||||
|  |     .. note:: | ||||||
|  |  | ||||||
|  |         Use this mixin *first* in the parent class list. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, *args, **kwargs): | ||||||
|  |         self._source = None | ||||||
|  |         self._source_thread = None | ||||||
|  |         self._source_delay = 0.01 | ||||||
|  |         super(SourceMixin, self).__init__(*args, **kwargs) | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         try: | ||||||
|  |             super(SourceMixin, self).close() | ||||||
|  |         except AttributeError: | ||||||
|  |             pass | ||||||
|  |         self.source = None | ||||||
|  |  | ||||||
|  |     def _copy_values(self, source): | ||||||
|  |         for v in source: | ||||||
|  |             self.value = v | ||||||
|  |             if self._source_thread.stopping.wait(self._source_delay): | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def source_delay(self): | ||||||
|  |         """ | ||||||
|  |         The delay (measured in seconds) in the loop used to read values from | ||||||
|  |         :attr:`source`. Defaults to 0.01 seconds which is generally sufficient | ||||||
|  |         to keep CPU usage to a minimum while providing adequate responsiveness. | ||||||
|  |         """ | ||||||
|  |         return self._source_delay | ||||||
|  |  | ||||||
|  |     @source_delay.setter | ||||||
|  |     def source_delay(self, value): | ||||||
|  |         if value < 0: | ||||||
|  |             raise GPIOBadSourceDelay('source_delay must be 0 or greater') | ||||||
|  |         self._source_delay = float(value) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def source(self): | ||||||
|  |         """ | ||||||
|  |         The iterable to use as a source of values for :attr:`value`. | ||||||
|  |         """ | ||||||
|  |         return self._source | ||||||
|  |  | ||||||
|  |     @source.setter | ||||||
|  |     def source(self, value): | ||||||
|  |         if self._source_thread is not None: | ||||||
|  |             self._source_thread.stop() | ||||||
|  |             self._source_thread = None | ||||||
|  |         self._source = value | ||||||
|  |         if value is not None: | ||||||
|  |             self._source_thread = GPIOThread(target=self._copy_values, args=(value,)) | ||||||
|  |             self._source_thread.start() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SharedMixin(object): | ||||||
|  |     """ | ||||||
|  |     This mixin marks a class as "shared". In this case, the meta-class | ||||||
|  |     (GPIOMeta) will use :meth:`_shared_key` to convert the constructor | ||||||
|  |     arguments to an immutable key, and will check whether any existing | ||||||
|  |     instances match that key. If they do, they will be returned by the | ||||||
|  |     constructor instead of a new instance. An internal reference counter is | ||||||
|  |     used to determine how many times an instance has been "constructed" in this | ||||||
|  |     way. | ||||||
|  |  | ||||||
|  |     When :meth:`close` is called, an internal reference counter will be | ||||||
|  |     decremented and the instance will only close when it reaches zero. | ||||||
|  |     """ | ||||||
|  |     _INSTANCES = {} | ||||||
|  |  | ||||||
|  |     def __del__(self): | ||||||
|  |         self._refs = 0 | ||||||
|  |         super(SharedMixin, self).__del__() | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def _shared_key(cls, *args, **kwargs): | ||||||
|  |         """ | ||||||
|  |         Given the constructor arguments, returns an immutable key representing | ||||||
|  |         the instance. The default simply assumes all positional arguments are | ||||||
|  |         immutable. | ||||||
|  |         """ | ||||||
|  |         return args | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class EventsMixin(object): | ||||||
|  |     """ | ||||||
|  |     Adds edge-detected :meth:`when_activated` and :meth:`when_deactivated` | ||||||
|  |     events to a device based on changes to the :attr:`~Device.is_active` | ||||||
|  |     property common to all devices. Also adds :meth:`wait_for_active` and | ||||||
|  |     :meth:`wait_for_inactive` methods for level-waiting. | ||||||
|  |  | ||||||
|  |     .. note:: | ||||||
|  |  | ||||||
|  |         Note that this mixin provides no means of actually firing its events; | ||||||
|  |         call :meth:`_fire_events` in sub-classes when device state changes to | ||||||
|  |         trigger the events. This should also be called once at the end of | ||||||
|  |         initialization to set initial states. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, *args, **kwargs): | ||||||
|  |         super(EventsMixin, self).__init__(*args, **kwargs) | ||||||
|  |         self._active_event = Event() | ||||||
|  |         self._inactive_event = Event() | ||||||
|  |         self._when_activated = None | ||||||
|  |         self._when_deactivated = None | ||||||
|  |         self._last_state = None | ||||||
|  |  | ||||||
|  |     def wait_for_active(self, timeout=None): | ||||||
|  |         """ | ||||||
|  |         Pause the script until the device is activated, or the timeout is | ||||||
|  |         reached. | ||||||
|  |  | ||||||
|  |         :param float timeout: | ||||||
|  |             Number of seconds to wait before proceeding. If this is ``None`` | ||||||
|  |             (the default), then wait indefinitely until the device is active. | ||||||
|  |         """ | ||||||
|  |         return self._active_event.wait(timeout) | ||||||
|  |  | ||||||
|  |     def wait_for_inactive(self, timeout=None): | ||||||
|  |         """ | ||||||
|  |         Pause the script until the device is deactivated, or the timeout is | ||||||
|  |         reached. | ||||||
|  |  | ||||||
|  |         :param float timeout: | ||||||
|  |             Number of seconds to wait before proceeding. If this is ``None`` | ||||||
|  |             (the default), then wait indefinitely until the device is inactive. | ||||||
|  |         """ | ||||||
|  |         return self._inactive_event.wait(timeout) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def when_activated(self): | ||||||
|  |         """ | ||||||
|  |         The function to run when the device changes state from inactive to | ||||||
|  |         active. | ||||||
|  |  | ||||||
|  |         This can be set to a function which accepts no (mandatory) parameters, | ||||||
|  |         or a Python function which accepts a single mandatory parameter (with | ||||||
|  |         as many optional parameters as you like). If the function accepts a | ||||||
|  |         single mandatory parameter, the device that activated will be passed | ||||||
|  |         as that parameter. | ||||||
|  |  | ||||||
|  |         Set this property to ``None`` (the default) to disable the event. | ||||||
|  |         """ | ||||||
|  |         return self._when_activated | ||||||
|  |  | ||||||
|  |     @when_activated.setter | ||||||
|  |     def when_activated(self, value): | ||||||
|  |         self._when_activated = self._wrap_callback(value) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def when_deactivated(self): | ||||||
|  |         """ | ||||||
|  |         The function to run when the device changes state from active to | ||||||
|  |         inactive. | ||||||
|  |  | ||||||
|  |         This can be set to a function which accepts no (mandatory) parameters, | ||||||
|  |         or a Python function which accepts a single mandatory parameter (with | ||||||
|  |         as many optional parameters as you like). If the function accepts a | ||||||
|  |         single mandatory parameter, the device that deactivated will be | ||||||
|  |         passed as that parameter. | ||||||
|  |  | ||||||
|  |         Set this property to ``None`` (the default) to disable the event. | ||||||
|  |         """ | ||||||
|  |         return self._when_deactivated | ||||||
|  |  | ||||||
|  |     @when_deactivated.setter | ||||||
|  |     def when_deactivated(self, value): | ||||||
|  |         self._when_deactivated = self._wrap_callback(value) | ||||||
|  |  | ||||||
|  |     def _wrap_callback(self, fn): | ||||||
|  |         if fn is None: | ||||||
|  |             return None | ||||||
|  |         elif not callable(fn): | ||||||
|  |             raise BadEventHandler('value must be None or a callable') | ||||||
|  |         elif inspect.isbuiltin(fn): | ||||||
|  |             # We can't introspect the prototype of builtins. In this case we | ||||||
|  |             # assume that the builtin has no (mandatory) parameters; this is | ||||||
|  |             # the most reasonable assumption on the basis that pre-existing | ||||||
|  |             # builtins have no knowledge of gpiozero, and the sole parameter | ||||||
|  |             # we would pass is a gpiozero object | ||||||
|  |             return fn | ||||||
|  |         else: | ||||||
|  |             # Try binding ourselves to the argspec of the provided callable. | ||||||
|  |             # If this works, assume the function is capable of accepting no | ||||||
|  |             # parameters | ||||||
|  |             try: | ||||||
|  |                 inspect.getcallargs(fn) | ||||||
|  |                 return fn | ||||||
|  |             except TypeError: | ||||||
|  |                 try: | ||||||
|  |                     # If the above fails, try binding with a single parameter | ||||||
|  |                     # (ourselves). If this works, wrap the specified callback | ||||||
|  |                     inspect.getcallargs(fn, self) | ||||||
|  |                     @wraps(fn) | ||||||
|  |                     def wrapper(): | ||||||
|  |                         return fn(self) | ||||||
|  |                     return wrapper | ||||||
|  |                 except TypeError: | ||||||
|  |                     raise BadEventHandler( | ||||||
|  |                         'value must be a callable which accepts up to one ' | ||||||
|  |                         'mandatory parameter') | ||||||
|  |  | ||||||
|  |     def _fire_events(self): | ||||||
|  |         old_state = self._last_state | ||||||
|  |         new_state = self._last_state = self.is_active | ||||||
|  |         if old_state is None: | ||||||
|  |             # Initial "indeterminate" state; set events but don't fire | ||||||
|  |             # callbacks as there's not necessarily an edge | ||||||
|  |             if new_state: | ||||||
|  |                 self._active_event.set() | ||||||
|  |             else: | ||||||
|  |                 self._inactive_event.set() | ||||||
|  |         else: | ||||||
|  |             if not old_state and new_state: | ||||||
|  |                 self._inactive_event.clear() | ||||||
|  |                 self._active_event.set() | ||||||
|  |                 if self.when_activated: | ||||||
|  |                     self.when_activated() | ||||||
|  |             elif old_state and not new_state: | ||||||
|  |                 self._active_event.clear() | ||||||
|  |                 self._inactive_event.set() | ||||||
|  |                 if self.when_deactivated: | ||||||
|  |                     self.when_deactivated() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GPIOQueue(GPIOThread): | ||||||
|  |     """ | ||||||
|  |     Extends :class:`GPIOThread`. Provides a background thread that monitors a | ||||||
|  |     device's values and provides a running *average* (defaults to median) of | ||||||
|  |     those values. If the *parent* device includes the :class:`EventsMixin` in | ||||||
|  |     its ancestry, the thread automatically calls | ||||||
|  |     :meth:`~EventsMixin._fire_events`. | ||||||
|  |     """ | ||||||
|  |     def __init__( | ||||||
|  |             self, parent, queue_len=5, sample_wait=0.0, partial=False, | ||||||
|  |             average=median): | ||||||
|  |         assert callable(average) | ||||||
|  |         super(GPIOQueue, self).__init__(target=self.fill) | ||||||
|  |         if queue_len < 1: | ||||||
|  |             raise 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 and isinstance(self.parent, EventsMixin): | ||||||
|  |                     self.parent._fire_events() | ||||||
|  |             self.full.set() | ||||||
|  |             while not self.stopping.wait(self.sample_wait): | ||||||
|  |                 self.queue.append(self.parent._read()) | ||||||
|  |                 if isinstance(self.parent, EventsMixin): | ||||||
|  |                     self.parent._fire_events() | ||||||
|  |         except ReferenceError: | ||||||
|  |             # Parent is dead; time to die! | ||||||
|  |             pass | ||||||
|  |  | ||||||
							
								
								
									
										168
									
								
								gpiozero/other_devices.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								gpiozero/other_devices.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | |||||||
|  | # vim: set fileencoding=utf-8: | ||||||
|  |  | ||||||
|  | from __future__ import ( | ||||||
|  |     unicode_literals, | ||||||
|  |     print_function, | ||||||
|  |     absolute_import, | ||||||
|  |     division, | ||||||
|  | ) | ||||||
|  | str = type('') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | import io | ||||||
|  | import subprocess | ||||||
|  | from datetime import datetime, time | ||||||
|  |  | ||||||
|  | from .devices import Device | ||||||
|  | from .mixins import EventsMixin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InternalDevice(EventsMixin, Device): | ||||||
|  |     """ | ||||||
|  |     Extends :class:`Device` to provide a basis for devices which have no | ||||||
|  |     specific hardware representation. This are effectively pseudo-devices and | ||||||
|  |     usually represent operating system services like the internal clock, file | ||||||
|  |     systems or network facilities. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PingServer(InternalDevice): | ||||||
|  |     """ | ||||||
|  |     Extends :class:`InternalDevice` to provide a device which is active when a | ||||||
|  |     *host* on the network can be pinged. | ||||||
|  |  | ||||||
|  |     The following example lights an LED while a server is reachable (note the | ||||||
|  |     use of :attr:`~SourceMixin.source_delay` to ensure the server is not | ||||||
|  |     flooded with pings):: | ||||||
|  |  | ||||||
|  |         from gpiozero import PingServer, LED | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         server = PingServer('my-server') | ||||||
|  |         led = LED(4) | ||||||
|  |         led.source_delay = 1 | ||||||
|  |         led.source = server.values | ||||||
|  |         pause() | ||||||
|  |  | ||||||
|  |     :param str host: | ||||||
|  |         The hostname or IP address to attempt to ping. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, host): | ||||||
|  |         self.host = host | ||||||
|  |         super(PingServer, self).__init__() | ||||||
|  |         self._fire_events() | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         return '<gpiozero.PingDevice host="%s">' % self.host | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def value(self): | ||||||
|  |         # XXX This is doing a DNS lookup every time it's queried; should we | ||||||
|  |         # call gethostbyname in the constructor and ping that instead (good | ||||||
|  |         # for consistency, but what if the user *expects* the host to change | ||||||
|  |         # address?) | ||||||
|  |         with io.open(os.devnull, 'wb') as devnull: | ||||||
|  |             try: | ||||||
|  |                 subprocess.check_call( | ||||||
|  |                     ['ping', '-c1', self.host], | ||||||
|  |                     stdout=devnull, stderr=devnull) | ||||||
|  |             except subprocess.CalledProcessError: | ||||||
|  |                 return False | ||||||
|  |             else: | ||||||
|  |                 return True | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TimeOfDay(InternalDevice): | ||||||
|  |     """ | ||||||
|  |     Extends :class:`InternalDevice` to provide a device which is active when | ||||||
|  |     the computer's clock indicates that the current time is between | ||||||
|  |     *start_time* and *end_time* (inclusive) which are :class:`~datetime.time` | ||||||
|  |     instances. | ||||||
|  |  | ||||||
|  |     The following example turns on a lamp attached to an :class:`Energenie` | ||||||
|  |     plug between 7 and 8 AM:: | ||||||
|  |  | ||||||
|  |         from datetime import time | ||||||
|  |         from gpiozero import TimeOfDay, Energenie | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         lamp = Energenie(0) | ||||||
|  |         morning = TimeOfDay(time(7), time(8)) | ||||||
|  |         morning.when_activated = lamp.on | ||||||
|  |         morning.when_deactivated = lamp.off | ||||||
|  |         pause() | ||||||
|  |  | ||||||
|  |     :param ~datetime.time start_time: | ||||||
|  |         The time from which the device will be considered active. | ||||||
|  |  | ||||||
|  |     :param ~datetime.time end_time: | ||||||
|  |         The time after which the device will be considered inactive. | ||||||
|  |  | ||||||
|  |     :param bool utc: | ||||||
|  |         If ``True`` (the default), a naive UTC time will be used for the | ||||||
|  |         comparison rather than a local time-zone reading. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, start_time, end_time, utc=True): | ||||||
|  |         self._start_time = None | ||||||
|  |         self._end_time = None | ||||||
|  |         self._utc = True | ||||||
|  |         super(TimeOfDay, self).__init__() | ||||||
|  |         self.start_time = start_time | ||||||
|  |         self.end_time = end_time | ||||||
|  |         self.utc = utc | ||||||
|  |         self._fire_events() | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         return '<gpiozero.TimeOfDay active between %s and %s %s>' % ( | ||||||
|  |                 self.start_time, self.end_time, ('local', 'UTC')[self.utc]) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def start_time(self): | ||||||
|  |         """ | ||||||
|  |         The time of day after which the device will be considered active. | ||||||
|  |         """ | ||||||
|  |         return self._start_time | ||||||
|  |  | ||||||
|  |     @start_time.setter | ||||||
|  |     def start_time(self, value): | ||||||
|  |         if isinstance(value, datetime): | ||||||
|  |             value = value.time() | ||||||
|  |         if not isinstance(value, time): | ||||||
|  |             raise ValueError('start_time must be a datetime, or time instance') | ||||||
|  |         self._start_time = value | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def end_time(self): | ||||||
|  |         """ | ||||||
|  |         The time of day after which the device will be considered inactive. | ||||||
|  |         """ | ||||||
|  |         return self._end_time | ||||||
|  |  | ||||||
|  |     @end_time.setter | ||||||
|  |     def end_time(self, value): | ||||||
|  |         if isinstance(value, datetime): | ||||||
|  |             value = value.time() | ||||||
|  |         if not isinstance(value, time): | ||||||
|  |             raise ValueError('end_time must be a datetime, or time instance') | ||||||
|  |         self._end_time = value | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def utc(self): | ||||||
|  |         """ | ||||||
|  |         If ``True``, use a naive UTC time reading for comparison instead of a | ||||||
|  |         local timezone reading. | ||||||
|  |         """ | ||||||
|  |         return self._utc | ||||||
|  |  | ||||||
|  |     @utc.setter | ||||||
|  |     def utc(self, value): | ||||||
|  |         self._utc = bool(value) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def value(self): | ||||||
|  |         if self.utc: | ||||||
|  |             return self.start_time <= datetime.utcnow().time() <= self.end_time | ||||||
|  |         else: | ||||||
|  |             return self.start_time <= datetime.now().time() <= self.end_time | ||||||
|  |  | ||||||
| @@ -11,7 +11,8 @@ from threading import Lock | |||||||
| from itertools import repeat, cycle, chain | from itertools import repeat, cycle, chain | ||||||
|  |  | ||||||
| from .exc import OutputDeviceBadValue, GPIOPinMissing | from .exc import OutputDeviceBadValue, GPIOPinMissing | ||||||
| from .devices import GPIODevice, Device, CompositeDevice, SourceMixin | from .devices import GPIODevice, Device, CompositeDevice | ||||||
|  | from .mixins import SourceMixin | ||||||
| from .threads import GPIOThread | from .threads import GPIOThread | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										296
									
								
								gpiozero/source_tools.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								gpiozero/source_tools.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,296 @@ | |||||||
|  | # vim: set fileencoding=utf-8: | ||||||
|  |  | ||||||
|  | from __future__ import ( | ||||||
|  |     unicode_literals, | ||||||
|  |     print_function, | ||||||
|  |     absolute_import, | ||||||
|  |     division, | ||||||
|  | ) | ||||||
|  | str = type('') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | from random import random | ||||||
|  | from time import sleep | ||||||
|  | try: | ||||||
|  |     from itertools import izip as zip | ||||||
|  | except ImportError: | ||||||
|  |     pass | ||||||
|  | from itertools import count, cycle | ||||||
|  | from math import sin, cos, floor | ||||||
|  | try: | ||||||
|  |     from statistics import mean | ||||||
|  | except ImportError: | ||||||
|  |     from .compat import mean | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def negated(values): | ||||||
|  |     """ | ||||||
|  |     Returns the negation of the supplied values (``True`` becomes ``False``, | ||||||
|  |     and ``False`` becomes ``True``). For example:: | ||||||
|  |  | ||||||
|  |         from gpiozero import Button, LED, negated | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         led = LED(4) | ||||||
|  |         btn = Button(17) | ||||||
|  |         led.source = negated(btn.values) | ||||||
|  |         pause() | ||||||
|  |     """ | ||||||
|  |     for v in values: | ||||||
|  |         yield not v | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def inverted(values): | ||||||
|  |     """ | ||||||
|  |     Returns the inversion of the supplied values (1 becomes 0, 0 becomes 1, | ||||||
|  |     0.1 becomes 0.9, etc.). For example:: | ||||||
|  |  | ||||||
|  |         from gpiozero import MCP3008, PWMLED, inverted | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         led = PWMLED(4) | ||||||
|  |         pot = MCP3008(channel=0) | ||||||
|  |         led.source = inverted(pot.values) | ||||||
|  |         pause() | ||||||
|  |     """ | ||||||
|  |     for v in values: | ||||||
|  |         yield 1 - v | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def scaled(values, range_min, range_max, domain_min=0, domain_max=1): | ||||||
|  |     """ | ||||||
|  |     Returns *values* scaled from *range_min* to *range_max*, assuming that all | ||||||
|  |     items in *values* lie between *domain_min* and *domain_max* (which default | ||||||
|  |     to 0 and 1 respectively). For example, to control the direction of a motor | ||||||
|  |     (which is represented as a value between -1 and 1) using a potentiometer | ||||||
|  |     (which typically provides values between 0 and 1):: | ||||||
|  |  | ||||||
|  |         from gpiozero import Motor, MCP3008, scaled | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         motor = Motor(20, 21) | ||||||
|  |         pot = MCP3008(channel=0) | ||||||
|  |         motor.source = scaled(pot.values, -1, 1) | ||||||
|  |         pause() | ||||||
|  |     """ | ||||||
|  |     domain_size = domain_max - domain_min | ||||||
|  |     range_size = range_max - range_min | ||||||
|  |     for v in values: | ||||||
|  |         yield (((v - domain_min) / domain_size) * range_size) + range_min | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def clamped(values, range_min=0, range_max=1): | ||||||
|  |     """ | ||||||
|  |     Returns *values* clamped from *range_min* to *range_max*, i.e. any items | ||||||
|  |     less than *range_min* will be returned as *range_min* and any items | ||||||
|  |     larger than *range_max* will be returned as *range_max* (these default to | ||||||
|  |     0 and 1 respectively). For example:: | ||||||
|  |  | ||||||
|  |         from gpiozero import PWMLED, MCP3008, clamped | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         led = PWMLED(4) | ||||||
|  |         pot = MCP3008(channel=0) | ||||||
|  |         led.source = clamped(pot.values, 0.5, 1.0) | ||||||
|  |         pause() | ||||||
|  |     """ | ||||||
|  |     for v in values: | ||||||
|  |         yield min(max(v, range_min), range_max) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def quantized(values, steps, range_min=0, range_max=1): | ||||||
|  |     """ | ||||||
|  |     Returns *values* quantized to *steps* increments. All items in *values* are | ||||||
|  |     assumed to be between *range_min* and *range_max* (use :func:`scaled` to | ||||||
|  |     ensure this if necessary). | ||||||
|  |  | ||||||
|  |     For example, to quantize values between 0 and 1 to 5 "steps" (0.0, 0.25, | ||||||
|  |     0.5, 0.75, 1.0):: | ||||||
|  |  | ||||||
|  |         from gpiozero import PWMLED, MCP3008, quantized | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         led = PWMLED(4) | ||||||
|  |         pot = MCP3008(channel=0) | ||||||
|  |         led.source = quantized(pot.values, 4) | ||||||
|  |         pause() | ||||||
|  |     """ | ||||||
|  |     range_size = range_max - range_min | ||||||
|  |     for v in scaled(values, 0, 1, range_min, range_max): | ||||||
|  |         yield ((int(v * steps) / steps) * range_size) + range_min | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def conjunction(*values): | ||||||
|  |     """ | ||||||
|  |     Returns the `logical conjunction`_ of all supplied values (the result is | ||||||
|  |     only ``True`` if and only if all input values are simultaneously ``True``). | ||||||
|  |     One or more *values* can be specified. For example, to light an | ||||||
|  |     :class:`LED` only when *both* buttons are pressed:: | ||||||
|  |  | ||||||
|  |         from gpiozero import LED, Button, conjunction | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         led = LED(4) | ||||||
|  |         btn1 = Button(20) | ||||||
|  |         btn2 = Button(21) | ||||||
|  |         led.source = conjunction(btn1.values, btn2.values) | ||||||
|  |         pause() | ||||||
|  |  | ||||||
|  |     .. _logical conjunction: https://en.wikipedia.org/wiki/Logical_conjunction | ||||||
|  |     """ | ||||||
|  |     for v in zip(*values): | ||||||
|  |         yield all(v) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def disjunction(*values): | ||||||
|  |     """ | ||||||
|  |     Returns the `logical disjunction`_ of all supplied values (the result is | ||||||
|  |     ``True`` if any of the input values are currently ``True``). One or more | ||||||
|  |     *values* can be specified. For example, the light an :class:`LED` when | ||||||
|  |     *any* button is pressed:: | ||||||
|  |  | ||||||
|  |         from gpiozero import LED, Button, conjunction | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         led = LED(4) | ||||||
|  |         btn1 = Button(20) | ||||||
|  |         btn2 = Button(21) | ||||||
|  |         led.source = disjunction(btn1.values, btn2.values) | ||||||
|  |         pause() | ||||||
|  |  | ||||||
|  |     .. _logical disjunction: https://en.wikipedia.org/wiki/Logical_disjunction | ||||||
|  |     """ | ||||||
|  |     for v in zip(*values): | ||||||
|  |         yield any(v) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def averaged(*values): | ||||||
|  |     """ | ||||||
|  |     Returns the mean of all supplied values. One or more *values* can be | ||||||
|  |     specified. For example, to light a :class:`PWMLED` as the average of | ||||||
|  |     several potentiometers connected to an :class:`MCP3008` ADC:: | ||||||
|  |  | ||||||
|  |         from gpiozero import MCP3008, PWMLED, averaged | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         pot1 = MCP3008(channel=0) | ||||||
|  |         pot2 = MCP3008(channel=1) | ||||||
|  |         pot3 = MCP3008(channel=2) | ||||||
|  |         led = PWMLED(4) | ||||||
|  |         led.source = averaged(pot1.values, pot2.values, pot3.values) | ||||||
|  |         pause() | ||||||
|  |     """ | ||||||
|  |     for v in zip(*values): | ||||||
|  |         yield mean(v) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def queued(values, qsize): | ||||||
|  |     """ | ||||||
|  |     Queues up readings from *values* (the number of readings queued is | ||||||
|  |     determined by *qsize*) and begins yielding values only when the queue is | ||||||
|  |     full. For example, to "cascade" values along a sequence of LEDs:: | ||||||
|  |  | ||||||
|  |         from gpiozero import LEDBoard, Button, queued | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         leds = LEDBoard(5, 6, 13, 19, 26) | ||||||
|  |         btn = Button(17) | ||||||
|  |         for i in range(4): | ||||||
|  |             leds[i].source = queued(leds[i + 1].values, 5) | ||||||
|  |             leds[i].source_delay = 0.01 | ||||||
|  |         leds[4].source = btn.values | ||||||
|  |         pause() | ||||||
|  |     """ | ||||||
|  |     q = [] | ||||||
|  |     it = iter(values) | ||||||
|  |     for i in range(qsize): | ||||||
|  |         q.append(next(it)) | ||||||
|  |     for i in cycle(range(qsize)): | ||||||
|  |         yield q[i] | ||||||
|  |         try: | ||||||
|  |             q[i] = next(it) | ||||||
|  |         except StopIteration: | ||||||
|  |             break | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def pre_delayed(values, delay): | ||||||
|  |     """ | ||||||
|  |     Waits for *delay* seconds before returning each item from *values*. | ||||||
|  |     """ | ||||||
|  |     for v in values: | ||||||
|  |         sleep(delay) | ||||||
|  |         yield v | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def post_delayed(values, delay): | ||||||
|  |     """ | ||||||
|  |     Waits for *delay* seconds after returning each item from *values*. | ||||||
|  |     """ | ||||||
|  |     for v in values: | ||||||
|  |         yield v | ||||||
|  |         sleep(delay) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def random_values(): | ||||||
|  |     """ | ||||||
|  |     Provides an infinite source of random values between 0 and 1. For example, | ||||||
|  |     to produce a "flickering candle" effect with an LED:: | ||||||
|  |  | ||||||
|  |         from gpiozero import PWMLED, random_values | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         led = PWMLED(4) | ||||||
|  |         led.source = random_values() | ||||||
|  |         pause() | ||||||
|  |  | ||||||
|  |     If you require a wider range than 0 to 1, see :func:`scaled`. | ||||||
|  |     """ | ||||||
|  |     while True: | ||||||
|  |         yield random() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def sin_values(): | ||||||
|  |     """ | ||||||
|  |     Provides an infinite source of values representing a sine wave (from -1 to | ||||||
|  |     +1), calculated as the result of applying sign to a simple degrees counter | ||||||
|  |     that increments by one for each requested value. For example, to produce a | ||||||
|  |     "siren" effect with a couple of LEDs:: | ||||||
|  |  | ||||||
|  |         from gpiozero import PWMLED, sin_values, scaled, inverted | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         red = PWMLED(2) | ||||||
|  |         blue = PWMLED(3) | ||||||
|  |         red.source_delay = 0.1 | ||||||
|  |         blue.source_delay = 0.1 | ||||||
|  |         red.source = scaled(sin_values(), 0, 1, -1, 1) | ||||||
|  |         blue.source = inverted(red.values) | ||||||
|  |         pause() | ||||||
|  |  | ||||||
|  |     If you require a wider range than 0 to 1, see :func:`scaled`. | ||||||
|  |     """ | ||||||
|  |     for d in cycle(range(360)): | ||||||
|  |         yield sin(d) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def cos_values(): | ||||||
|  |     """ | ||||||
|  |     Provides an infinite source of values representing a cosine wave (from -1 | ||||||
|  |     to +1), calculated as the result of applying sign to a simple degrees | ||||||
|  |     counter that increments by one for each requested value. For example, to | ||||||
|  |     produce a "siren" effect with a couple of LEDs:: | ||||||
|  |  | ||||||
|  |         from gpiozero import PWMLED, cos_values, scaled, inverted | ||||||
|  |         from signal import pause | ||||||
|  |  | ||||||
|  |         red = PWMLED(2) | ||||||
|  |         blue = PWMLED(3) | ||||||
|  |         red.source = scaled(cos_values(), 0, 1, -1, 1) | ||||||
|  |         blue.source = inverted(red.values) | ||||||
|  |         pause() | ||||||
|  |  | ||||||
|  |     If you require a wider range than 0 to 1, see :func:`scaled`. | ||||||
|  |     """ | ||||||
|  |     for d in cycle(range(360)): | ||||||
|  |         yield cos(d) | ||||||
|  |  | ||||||
| @@ -6,13 +6,7 @@ from __future__ import ( | |||||||
|     ) |     ) | ||||||
| str = type('') | str = type('') | ||||||
|  |  | ||||||
| import weakref | from threading import Thread, Event | ||||||
| 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 ( | from .exc import ( | ||||||
|     GPIOBadQueueLen, |     GPIOBadQueueLen, | ||||||
| @@ -46,46 +40,3 @@ class GPIOThread(Thread): | |||||||
|         super(GPIOThread, self).join() |         super(GPIOThread, self).join() | ||||||
|         _THREADS.discard(self) |         _THREADS.discard(self) | ||||||
|  |  | ||||||
|  |  | ||||||
| class GPIOQueue(GPIOThread): |  | ||||||
|     def __init__( |  | ||||||
|             self, parent, queue_len=5, sample_wait=0.0, partial=False, |  | ||||||
|             average=median): |  | ||||||
|         assert callable(average) |  | ||||||
|         super(GPIOQueue, self).__init__(target=self.fill) |  | ||||||
|         if queue_len < 1: |  | ||||||
|             raise 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 |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user