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:: BadEventHandler | ||||
|  | ||||
| .. autoexception:: CompositeDeviceError | ||||
|  | ||||
| .. autoexception:: CompositeDeviceBadName | ||||
|   | ||||
| @@ -16,6 +16,9 @@ classes: | ||||
| * :class:`SPIDevice` represents devices that communicate over an SPI interface | ||||
|   (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 | ||||
|   devices like HATs | ||||
|  | ||||
| @@ -31,6 +34,9 @@ There are also several `mixin classes`_: | ||||
| * :class:`SharedMixin` which causes classes to track their construction and | ||||
|   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 | ||||
|  | ||||
| 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.* | ||||
|  | ||||
| Next, the classes below :class:`InternalDevice`: | ||||
|  | ||||
| .. image:: images/other_device_hierarchy.* | ||||
|  | ||||
| Next, the classes below :class:`CompositeDevice`: | ||||
|  | ||||
| .. image:: images/composite_device_hierarchy.* | ||||
| @@ -60,15 +70,18 @@ Base Classes | ||||
| ============ | ||||
|  | ||||
| .. autoclass:: Device | ||||
|     :members: close, closed | ||||
|     :members: close, closed, value, is_active | ||||
|  | ||||
| .. autoclass:: GPIODevice(pin) | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: CompositeDevice | ||||
| .. autoclass:: SPIDevice | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: SPIDevice | ||||
| .. autoclass:: InternalDevice | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: CompositeDevice | ||||
|     :members: | ||||
|  | ||||
| Input Devices | ||||
| @@ -77,9 +90,6 @@ Input Devices | ||||
| .. autoclass:: InputDevice(pin, pull_up=False) | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: WaitableInputDevice | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: DigitalInputDevice(pin, pull_up=False, bounce_time=None) | ||||
|     :members: | ||||
|  | ||||
| @@ -128,3 +138,6 @@ Mixin Classes | ||||
| .. autoclass:: SharedMixin(...) | ||||
|     :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"] | ||||
|     Device; | ||||
|     GPIODevice; | ||||
|     WaitableInputDevice; | ||||
|     SmoothedInputDevice; | ||||
|  | ||||
|     /* Concrete classes */ | ||||
| @@ -17,9 +16,8 @@ digraph classes { | ||||
|  | ||||
|     GPIODevice->Device; | ||||
|     InputDevice->GPIODevice; | ||||
|     WaitableInputDevice->InputDevice; | ||||
|     DigitalInputDevice->WaitableInputDevice; | ||||
|     SmoothedInputDevice->WaitableInputDevice; | ||||
|     DigitalInputDevice->InputDevice; | ||||
|     SmoothedInputDevice->InputDevice; | ||||
|     Button->DigitalInputDevice; | ||||
|     MotionSensor->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) | ||||
|  --> | ||||
| <!-- Title: classes Pages: 1 --> | ||||
| <svg width="701pt" height="404pt" | ||||
|  viewBox="0.00 0.00 701.00 404.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||||
| <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 400)"> | ||||
| <svg width="721pt" height="332pt" | ||||
|  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 328)"> | ||||
| <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 --> | ||||
| <g id="node1" class="node"><title>Device</title> | ||||
| <polygon fill="#9ec6e0" stroke="#9ec6e0" points="485,-396 431,-396 431,-360 485,-360 485,-396"/> | ||||
| <text text-anchor="middle" x="458" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> | ||||
| <polygon fill="#9ec6e0" stroke="#9ec6e0" points="499,-324 445,-324 445,-288 499,-288 499,-324"/> | ||||
| <text text-anchor="middle" x="472" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text> | ||||
| </g> | ||||
| <!-- GPIODevice --> | ||||
| <g id="node2" class="node"><title>GPIODevice</title> | ||||
| <polygon fill="#9ec6e0" stroke="#9ec6e0" points="494,-324 422,-324 422,-288 494,-288 494,-324"/> | ||||
| <text text-anchor="middle" x="458" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text> | ||||
| <polygon fill="#9ec6e0" stroke="#9ec6e0" points="508,-252 436,-252 436,-216 508,-216 508,-252"/> | ||||
| <text text-anchor="middle" x="472" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text> | ||||
| </g> | ||||
| <!-- GPIODevice->Device --> | ||||
| <g id="edge1" class="edge"><title>GPIODevice->Device</title> | ||||
| <path fill="none" stroke="black" d="M458,-324.303C458,-332.017 458,-341.288 458,-349.888"/> | ||||
| <polygon fill="black" stroke="black" points="454.5,-349.896 458,-359.896 461.5,-349.896 454.5,-349.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"/> | ||||
| <path fill="none" stroke="black" d="M472,-252.303C472,-260.017 472,-269.288 472,-277.888"/> | ||||
| <polygon fill="black" stroke="black" points="468.5,-277.896 472,-287.896 475.5,-277.896 468.5,-277.896"/> | ||||
| </g> | ||||
| <!-- 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"/> | ||||
| <text text-anchor="middle" x="228" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">SmoothedInputDevice</text> | ||||
| </g> | ||||
| <!-- SmoothedInputDevice->WaitableInputDevice --> | ||||
| <g id="edge5" class="edge"><title>SmoothedInputDevice->WaitableInputDevice</title> | ||||
| <path fill="none" stroke="black" d="M266.162,-108.124C287.393,-117.651 314.008,-129.593 336.569,-139.717"/> | ||||
| <polygon fill="black" stroke="black" points="335.406,-143.031 345.962,-143.932 338.271,-136.644 335.406,-143.031"/> | ||||
| <!-- InputDevice --> | ||||
| <g id="node4" class="node"><title>InputDevice</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="445.5,-180 372.5,-180 372.5,-144 445.5,-144 445.5,-180"/> | ||||
| <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> | ||||
| <!-- InputDevice->GPIODevice --> | ||||
| <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"/> | ||||
| <polygon fill="black" stroke="black" points="435.336,-282.273 444.317,-287.896 440.803,-277.901 435.336,-282.273"/> | ||||
| <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="447.315,-210.804 456.606,-215.896 452.518,-206.121 447.315,-210.804"/> | ||||
| </g> | ||||
| <!-- DigitalInputDevice --> | ||||
| <g id="node6" class="node"><title>DigitalInputDevice</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="439.5,-108 336.5,-108 336.5,-72 439.5,-72 439.5,-108"/> | ||||
| <text text-anchor="middle" x="388" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text> | ||||
| <g id="node5" class="node"><title>DigitalInputDevice</title> | ||||
| <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="409" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalInputDevice</text> | ||||
| </g> | ||||
| <!-- DigitalInputDevice->WaitableInputDevice --> | ||||
| <g id="edge4" class="edge"><title>DigitalInputDevice->WaitableInputDevice</title> | ||||
| <path fill="none" stroke="black" d="M387.011,-108.303C386.57,-116.017 386.041,-125.288 385.549,-133.888"/> | ||||
| <polygon fill="black" stroke="black" points="382.054,-133.712 384.977,-143.896 389.042,-134.112 382.054,-133.712"/> | ||||
| <!-- DigitalInputDevice->InputDevice --> | ||||
| <g id="edge3" class="edge"><title>DigitalInputDevice->InputDevice</title> | ||||
| <path fill="none" stroke="black" d="M409,-108.303C409,-116.017 409,-125.288 409,-133.888"/> | ||||
| <polygon fill="black" stroke="black" points="405.5,-133.896 409,-143.896 412.5,-133.896 405.5,-133.896"/> | ||||
| </g> | ||||
| <!-- 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"/> | ||||
| <text text-anchor="middle" x="418" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text> | ||||
| </g> | ||||
| <!-- Button->DigitalInputDevice --> | ||||
| <g id="edge6" class="edge"><title>Button->DigitalInputDevice</title> | ||||
| <path fill="none" stroke="black" d="M410.584,-36.3034C407.206,-44.1868 403.13,-53.6958 399.377,-62.4536"/> | ||||
| <polygon fill="black" stroke="black" points="396.053,-61.3255 395.33,-71.8957 402.487,-64.0829 396.053,-61.3255"/> | ||||
| <g id="edge5" class="edge"><title>Button->DigitalInputDevice</title> | ||||
| <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="409.003,-61.531 411.199,-71.8957 415.946,-62.4237 409.003,-61.531"/> | ||||
| </g> | ||||
| <!-- 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"/> | ||||
| <text text-anchor="middle" x="41" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MotionSensor</text> | ||||
| </g> | ||||
| <!-- MotionSensor->SmoothedInputDevice --> | ||||
| <g id="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"/> | ||||
| <polygon fill="black" stroke="black" points="171.763,-71.6859 182.356,-71.9139 174.217,-65.1302 171.763,-71.6859"/> | ||||
| </g> | ||||
| <!-- 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"/> | ||||
| <text text-anchor="middle" x="138" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LightSensor</text> | ||||
| </g> | ||||
| <!-- LightSensor->SmoothedInputDevice --> | ||||
| <g id="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"/> | ||||
| <polygon fill="black" stroke="black" points="195.966,-68.519 206.009,-71.8957 200.264,-62.9935 195.966,-68.519"/> | ||||
| </g> | ||||
| <!-- 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"/> | ||||
| <text text-anchor="middle" x="228" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LineSensor</text> | ||||
| </g> | ||||
| <!-- 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"/> | ||||
| <polygon fill="black" stroke="black" points="224.5,-61.8956 228,-71.8957 231.5,-61.8957 224.5,-61.8956"/> | ||||
| </g> | ||||
| <!-- 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"/> | ||||
| <text text-anchor="middle" x="327" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">DistanceSensor</text> | ||||
| </g> | ||||
| <!-- 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"/> | ||||
| <polygon fill="black" stroke="black" points="258.456,-63.1791 252.311,-71.8102 262.497,-68.8947 258.456,-63.1791"/> | ||||
| </g> | ||||
| <!-- OutputDevice --> | ||||
| <g id="node12" class="node"><title>OutputDevice</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="556,-252 474,-252 474,-216 556,-216 556,-252"/> | ||||
| <text text-anchor="middle" x="515" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text> | ||||
| <g id="node11" class="node"><title>OutputDevice</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="576,-180 494,-180 494,-144 576,-144 576,-180"/> | ||||
| <text text-anchor="middle" x="535" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">OutputDevice</text> | ||||
| </g> | ||||
| <!-- OutputDevice->GPIODevice --> | ||||
| <g id="edge11" 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"/> | ||||
| <polygon fill="black" stroke="black" points="475.528,-277.931 471.928,-287.896 480.956,-282.351 475.528,-277.931"/> | ||||
| <g id="edge10" class="edge"><title>OutputDevice->GPIODevice</title> | ||||
| <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="491.482,-206.121 487.394,-215.896 496.685,-210.804 491.482,-206.121"/> | ||||
| </g> | ||||
| <!-- DigitalOutputDevice --> | ||||
| <g id="node13" class="node"><title>DigitalOutputDevice</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="571,-180 459,-180 459,-144 571,-144 571,-180"/> | ||||
| <text text-anchor="middle" x="515" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text> | ||||
| <g id="node12" class="node"><title>DigitalOutputDevice</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="591,-108 479,-108 479,-72 591,-72 591,-108"/> | ||||
| <text text-anchor="middle" x="535" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text> | ||||
| </g> | ||||
| <!-- DigitalOutputDevice->OutputDevice --> | ||||
| <g id="edge12" class="edge"><title>DigitalOutputDevice->OutputDevice</title> | ||||
| <path fill="none" stroke="black" d="M515,-180.303C515,-188.017 515,-197.288 515,-205.888"/> | ||||
| <polygon fill="black" stroke="black" points="511.5,-205.896 515,-215.896 518.5,-205.896 511.5,-205.896"/> | ||||
| <g id="edge11" class="edge"><title>DigitalOutputDevice->OutputDevice</title> | ||||
| <path fill="none" stroke="black" d="M535,-108.303C535,-116.017 535,-125.288 535,-133.888"/> | ||||
| <polygon fill="black" stroke="black" points="531.5,-133.896 535,-143.896 538.5,-133.896 531.5,-133.896"/> | ||||
| </g> | ||||
| <!-- LED --> | ||||
| <g id="node14" class="node"><title>LED</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="519,-108 465,-108 465,-72 519,-72 519,-108"/> | ||||
| <text text-anchor="middle" x="492" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text> | ||||
| <g id="node13" class="node"><title>LED</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="534,-36 480,-36 480,-0 534,-0 534,-36"/> | ||||
| <text text-anchor="middle" x="507" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text> | ||||
| </g> | ||||
| <!-- LED->DigitalOutputDevice --> | ||||
| <g id="edge13" 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"/> | ||||
| <polygon fill="black" stroke="black" points="502.933,-135.488 509.38,-143.896 509.584,-133.303 502.933,-135.488"/> | ||||
| <g id="edge12" class="edge"><title>LED->DigitalOutputDevice</title> | ||||
| <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="521.195,-63.9108 528.158,-71.8957 527.694,-61.311 521.195,-63.9108"/> | ||||
| </g> | ||||
| <!-- Buzzer --> | ||||
| <g id="node15" class="node"><title>Buzzer</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="591,-108 537,-108 537,-72 591,-72 591,-108"/> | ||||
| <text text-anchor="middle" x="564" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text> | ||||
| <g id="node14" class="node"><title>Buzzer</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="606,-36 552,-36 552,-0 606,-0 606,-36"/> | ||||
| <text text-anchor="middle" x="579" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text> | ||||
| </g> | ||||
| <!-- Buzzer->DigitalOutputDevice --> | ||||
| <g id="edge14" 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"/> | ||||
| <polygon fill="black" stroke="black" points="529.84,-133.696 526.973,-143.896 535.575,-137.71 529.84,-133.696"/> | ||||
| <g id="edge13" class="edge"><title>Buzzer->DigitalOutputDevice</title> | ||||
| <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="548.11,-61.5667 545.751,-71.8957 554.036,-65.2919 548.11,-61.5667"/> | ||||
| </g> | ||||
| <!-- PWMOutputDevice --> | ||||
| <g id="node16" class="node"><title>PWMOutputDevice</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="693,-180 589,-180 589,-144 693,-144 693,-180"/> | ||||
| <text text-anchor="middle" x="641" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text> | ||||
| <g id="node15" class="node"><title>PWMOutputDevice</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="713,-108 609,-108 609,-72 713,-72 713,-108"/> | ||||
| <text text-anchor="middle" x="661" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text> | ||||
| </g> | ||||
| <!-- PWMOutputDevice->OutputDevice --> | ||||
| <g id="edge15" 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"/> | ||||
| <polygon fill="black" stroke="black" points="552.983,-207.894 545.942,-215.81 556.383,-214.013 552.983,-207.894"/> | ||||
| <g id="edge14" class="edge"><title>PWMOutputDevice->OutputDevice</title> | ||||
| <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="572.983,-135.894 565.942,-143.81 576.383,-142.013 572.983,-135.894"/> | ||||
| </g> | ||||
| <!-- PWMLED --> | ||||
| <g id="node17" class="node"><title>PWMLED</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="670,-108 612,-108 612,-72 670,-72 670,-108"/> | ||||
| <text text-anchor="middle" x="641" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text> | ||||
| <g id="node16" class="node"><title>PWMLED</title> | ||||
| <polygon fill="#2980b9" stroke="#2980b9" points="690,-36 632,-36 632,-0 690,-0 690,-36"/> | ||||
| <text text-anchor="middle" x="661" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text> | ||||
| </g> | ||||
| <!-- PWMLED->PWMOutputDevice --> | ||||
| <g id="edge16" class="edge"><title>PWMLED->PWMOutputDevice</title> | ||||
| <path fill="none" stroke="black" d="M641,-108.303C641,-116.017 641,-125.288 641,-133.888"/> | ||||
| <polygon fill="black" stroke="black" points="637.5,-133.896 641,-143.896 644.5,-133.896 637.5,-133.896"/> | ||||
| <g id="edge15" class="edge"><title>PWMLED->PWMOutputDevice</title> | ||||
| <path fill="none" stroke="black" d="M661,-36.3034C661,-44.0173 661,-53.2875 661,-61.8876"/> | ||||
| <polygon fill="black" stroke="black" points="657.5,-61.8956 661,-71.8957 664.5,-61.8957 657.5,-61.8956"/> | ||||
| </g> | ||||
| </g> | ||||
| </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_spi | ||||
|     api_boards | ||||
|     api_other | ||||
|     api_generic | ||||
|     api_source_tools | ||||
|     api_pins | ||||
|     api_exc | ||||
|     changelog | ||||
|   | ||||
| @@ -11,6 +11,7 @@ from .pins import ( | ||||
| from .exc import ( | ||||
|     GPIOZeroError, | ||||
|     DeviceClosed, | ||||
|     BadEventHandler, | ||||
|     CompositeDeviceError, | ||||
|     CompositeDeviceBadName, | ||||
|     SPIError, | ||||
| @@ -45,13 +46,15 @@ from .devices import ( | ||||
|     Device, | ||||
|     GPIODevice, | ||||
|     CompositeDevice, | ||||
| ) | ||||
| from .mixins import ( | ||||
|     SharedMixin, | ||||
|     SourceMixin, | ||||
|     ValuesMixin, | ||||
|     EventsMixin, | ||||
| ) | ||||
| from .input_devices import ( | ||||
|     InputDevice, | ||||
|     WaitableInputDevice, | ||||
|     DigitalInputDevice, | ||||
|     SmoothedInputDevice, | ||||
|     Button, | ||||
| @@ -103,3 +106,24 @@ from .boards import ( | ||||
|     CamJamKitRobot, | ||||
|     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 .output_devices import OutputDevice, LED, PWMLED, Buzzer, Motor | ||||
| 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): | ||||
|   | ||||
| @@ -15,6 +15,10 @@ from types import FunctionType | ||||
| from threading import RLock | ||||
|  | ||||
| from .threads import GPIOThread, _threads_shutdown | ||||
| from .mixins import ( | ||||
|     ValuesMixin, | ||||
|     SharedMixin, | ||||
|     ) | ||||
| from .exc import ( | ||||
|     DeviceClosed, | ||||
|     GPIOPinMissing, | ||||
| @@ -201,127 +205,35 @@ class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})): | ||||
|         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): | ||||
|     """ | ||||
|     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): | ||||
|         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): | ||||
|     """ | ||||
| @@ -417,15 +329,16 @@ class CompositeDevice(Device): | ||||
|     def value(self): | ||||
|         return self.tuple(*(device.value for device in self)) | ||||
|  | ||||
|     @property | ||||
|     def is_active(self): | ||||
|         return any(self.value) | ||||
|  | ||||
|  | ||||
| class GPIODevice(Device): | ||||
|     """ | ||||
|     Extends :class:`Device`. Represents a generic GPIO device. | ||||
|  | ||||
|     This is the class at the root of the gpiozero class hierarchy. It handles | ||||
|     ensuring that two GPIO devices do not share the same pin, and provides | ||||
|     basic services applicable to all devices (specifically the :attr:`pin` | ||||
|     property, :attr:`is_active` property, and the :attr:`close` method). | ||||
|     Extends :class:`Device`. Represents a generic GPIO device and provides | ||||
|     the services common to all single-pin GPIO devices (like ensuring two | ||||
|     GPIO devices do no share a :attr:`pin`). | ||||
|  | ||||
|     :param int pin: | ||||
|         The GPIO pin (in BCM numbering) that the device is connected to. If | ||||
| @@ -494,14 +407,8 @@ class GPIODevice(Device): | ||||
|  | ||||
|     @property | ||||
|     def value(self): | ||||
|         """ | ||||
|         Returns ``True`` if the device is currently active and ``False`` | ||||
|         otherwise. | ||||
|         """ | ||||
|         return self._read() | ||||
|  | ||||
|     is_active = value | ||||
|  | ||||
|     def __repr__(self): | ||||
|         try: | ||||
|             return "<gpiozero.%s object on pin %r, is_active=%s>" % ( | ||||
|   | ||||
| @@ -13,6 +13,9 @@ class GPIOZeroError(Exception): | ||||
| class DeviceClosed(GPIOZeroError): | ||||
|     "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): | ||||
|     "Base class for errors specific to the CompositeDevice hierarchy" | ||||
|  | ||||
|   | ||||
| @@ -7,15 +7,12 @@ from __future__ import ( | ||||
|     division, | ||||
| ) | ||||
|  | ||||
| import inspect | ||||
| import warnings | ||||
| from functools import wraps | ||||
| from time import sleep, time | ||||
| from threading import Event | ||||
|  | ||||
| from .exc import InputDeviceError, GPIODeviceError, GPIODeviceClosed | ||||
| from .devices import GPIODevice, CompositeDevice | ||||
| from .threads import GPIOQueue | ||||
| from .mixins import GPIOQueue, EventsMixin | ||||
|  | ||||
|  | ||||
| class InputDevice(GPIODevice): | ||||
| @@ -65,148 +62,7 @@ class InputDevice(GPIODevice): | ||||
|             return super(InputDevice, self).__repr__() | ||||
|  | ||||
|  | ||||
| class WaitableInputDevice(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): | ||||
| class DigitalInputDevice(EventsMixin, InputDevice): | ||||
|     """ | ||||
|     Represents a generic input device with typical on/off behaviour. | ||||
|  | ||||
| @@ -233,7 +89,7 @@ class DigitalInputDevice(WaitableInputDevice): | ||||
|             raise | ||||
|  | ||||
|  | ||||
| class SmoothedInputDevice(WaitableInputDevice): | ||||
| class SmoothedInputDevice(EventsMixin, InputDevice): | ||||
|     """ | ||||
|     Represents a generic input device which takes its value from the mean of a | ||||
|     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 .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 | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										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('') | ||||
|  | ||||
| import weakref | ||||
| from collections import deque | ||||
| from threading import Thread, Event, RLock | ||||
| try: | ||||
|     from statistics import median, mean | ||||
| except ImportError: | ||||
|     from .compat import median, mean | ||||
| from threading import Thread, Event | ||||
|  | ||||
| from .exc import ( | ||||
|     GPIOBadQueueLen, | ||||
| @@ -46,46 +40,3 @@ class GPIOThread(Thread): | ||||
|         super(GPIOThread, self).join() | ||||
|         _THREADS.discard(self) | ||||
|  | ||||
|  | ||||
| class GPIOQueue(GPIOThread): | ||||
|     def __init__( | ||||
|             self, parent, queue_len=5, sample_wait=0.0, partial=False, | ||||
|             average=median): | ||||
|         assert 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