diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c92d923..31de597 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,20 +1,32 @@ # Contributing -This module was designed for use in education; particularly for young children. It is not intended to replace `RPi.GPIO` and it does not claim to be suitable for all purposes. It is intended to provide a simple interface to everyday components. +This module was designed for use in education; particularly for young children. +It is not intended to replace `RPi.GPIO` and it does not claim to be suitable +for all purposes. It is intended to provide a simple interface to everyday +components. -If a proposed change added an advanced feature but made basic usage more complex, it is unlikely to be added. +If a proposed change added an advanced feature but made basic usage more +complex, it is unlikely to be added. ## Suggestions -Please make suggestions by opening an [issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining your reasoning clearly. +Please make suggestions by opening an +[issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining your +reasoning clearly. ## Bugs -Please submit bug reports by opening an [issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining the problem clearly using code examples. +Please submit bug reports by opening an +[issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining the +problem clearly using code examples. ## Documentation -The documentation source lives in the [docs](https://github.com/RPi-Distro/python-gpiozero/tree/master/docs) folder and is rendered from markdown into HTML using [mkdocs](http://www.mkdocs.org/). Contributions to the documentation are welcome but should be easy to read and understand. +The documentation source lives in the +[docs](https://github.com/RPi-Distro/python-gpiozero/tree/master/docs) folder +and is rendered from markdown into HTML using [mkdocs](http://www.mkdocs.org/). +Contributions to the documentation are welcome but should be easy to read and +understand. ## Python diff --git a/docs/index.md b/docs/index.md index f2840dd..f06608d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -74,6 +74,9 @@ Boards & accessories: ## Getting started -See the [input devices](inputs.md) and [output devices](outputs.md) to get started. Also see the [boards & accessories](boards.md) page for examples of using the included accessories. +See the [input devices](inputs.md) and [output devices](outputs.md) to get +started. Also see the [boards & accessories](boards.md) page for examples of +using the included accessories. -For common programs using multiple components together, see the [recipes](recipes.md) page. +For common programs using multiple components together, see the +[recipes](recipes.md) page. diff --git a/docs/inputs.md b/docs/inputs.md index 0d14424..d70c5a5 100644 --- a/docs/inputs.md +++ b/docs/inputs.md @@ -1,10 +1,12 @@ # Input Devices -These input device component interfaces have been provided for simple use of everyday components. +These input device component interfaces have been provided for simple use of +everyday components. Components must be wired up correctly before used in code. -*Note all GPIO pin numbers use BCM numbering. See the [notes](notes.md) page for more information.* +*Note all GPIO pin numbers use BCM numbering. See the [notes](notes.md) page +for more information.* ## Button @@ -22,13 +24,16 @@ Ensure the `Button` class is imported at the top of the file: from gpiozero import Button ``` -Create a `Button` object by passing in the pin number the button is connected to: +Create a `Button` object by passing in the pin number the button is connected +to: ```python button = Button(2) ``` -The default behaviour is to set the *pull* state of the button to *up*. To change this behaviour, set the `pull_up` argument to `False` when creating your `Button` object. +The default behaviour is to set the *pull* state of the button to *up*. To +change this behaviour, set the `pull_up` argument to `False` when creating your +`Button` object. ```python button = Button(pin=2, pull_up=False) @@ -67,7 +72,8 @@ Ensure the `MotionSensor` class is imported at the top of the file: from gpiozero import MotionSensor ``` -Create a `MotionSensor` object by passing in the pin number the sensor is connected to: +Create a `MotionSensor` object by passing in the pin number the sensor is +connected to: ```python pir = MotionSensor(3) @@ -97,7 +103,8 @@ Ensure the `LightSensor` class is imported at the top of the file: from gpiozero import LightSensor ``` -Create a `LightSensor` object by passing in the pin number the sensor is connected to: +Create a `LightSensor` object by passing in the pin number the sensor is +connected to: ```python light = LightSensor(4) @@ -145,7 +152,8 @@ temp = TemperatureSensor() MCP3008 ADC (Analogue-to-Digital converter). -The MCP3008 chip provides access to up to 8 analogue inputs, such as potentiometers, and read their values in digital form. +The MCP3008 chip provides access to up to 8 analogue inputs, such as +potentiometers, and read their values in digital form. ### Wiring @@ -166,7 +174,9 @@ with MCP3008() as pot: print(pot.read()) ``` -It is possible to specify the `bus`, the `device` and the `channel` you wish to access. The previous example used the default value of `0` for each of these. To specify them, pass them in as arguments: +It is possible to specify the `bus`, the `device` and the `channel` you wish to +access. The previous example used the default value of `0` for each of these. +To specify them, pass them in as arguments: ```python with MCP3008(bus=1, device=1, channel=4) as pot: diff --git a/docs/notes.md b/docs/notes.md index 076ee3e..766faf4 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -2,7 +2,8 @@ 1. **BCM pin numbering** - This library uses BCM pin numbering for the GPIO pins, as opposed to BOARD. Unlike the `RPi.GPIO` library, it is not configurable. + This library uses Broadcom (BCM) pin numbering for the GPIO pins, as + opposed to BOARD. Unlike the `RPi.GPIO` library, this is not configurable. Any pin marked `GPIO` can be used for generic components. @@ -36,11 +37,11 @@ - *5V = 5 Volts* - *DNC = Do not connect (special use pins)* -1. **Wiring** +2. **Wiring** All components must be wired up correctly before using with this library. -1. **Keep your program alive with `signal.pause`** +3. **Keep your program alive with `signal.pause`** The following program looks like it should turn an LED on: @@ -52,9 +53,12 @@ led.on() ``` - And it does, if you're using the Python shell, IPython shell or IDLE shell, but if you saved this program as a Python file and ran it, it would flash on for a moment then the program would end and it would turn off. + And it does, if you're using the Python shell, IPython shell or IDLE shell, + but if you saved this program as a Python file and ran it, it would flash + on for a moment then the program would end and it would turn off. - The following file includes an intentional `pause` to keep the program alive: + The following file includes an intentional `pause` to keep the program + alive: ```python from gpiozero import LED @@ -67,9 +71,11 @@ pause() ``` - Now running the program will stay running, leaving the LED on, until it is forced to quit. + Now running the program will stay running, leaving the LED on, until it is + forced to quit. - Similarly, when setting up callbacks on button presses or other input devices, the program needs to be running for the events to be detected: + Similarly, when setting up callbacks on button presses or other input + devices, the program needs to be running for the events to be detected: ```python from gpiozero import Button diff --git a/docs/outputs.md b/docs/outputs.md index 024285a..f21d762 100644 --- a/docs/outputs.md +++ b/docs/outputs.md @@ -1,10 +1,12 @@ # Output Devices -These output device component interfaces have been provided for simple use of everyday components. +These output device component interfaces have been provided for simple use of +everyday components. Components must be wired up correctly before used in code. -*Note all GPIO pin numbers use BCM numbering. See the [notes](notes.md) page for more information.* +*Note all GPIO pin numbers use BCM numbering. See the [notes](notes.md) page +for more information.* ## LED diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 254b8f9..bd3fe03 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -42,20 +42,20 @@ class LEDBoard(object): """ Make all the LEDs turn on and off repeatedly. - on_time: 1 + on_time: `1` Number of seconds to be on - off_time: 1 + off_time: `1` Number of seconds to be off - n: None + n: `None` Number of times to blink; None means forever - background: True - If True, start a background thread to continue blinking and return - immediately. If False, only return when the blink is finished - (warning: the default value of n will result in this method never - returning). + background: `True` + If `True`, start a background thread to continue blinking and + return immediately. If `False`, only return when the blink is + finished (warning: the default value of `n` will result in this + method never returning). """ for led in self._leds: led.blink(on_time, off_time, n, background) @@ -74,13 +74,13 @@ class TrafficLights(LEDBoard): """ Generic Traffic Lights set. - red: None + red: `None` Red LED pin - amber: None + amber: `None` Amber LED pin - green: None + green: `None` Green LED pin """ def __init__(self, red=None, amber=None, green=None): @@ -144,20 +144,20 @@ class FishDish(TrafficLights): """ Make all the board's components turn on and off repeatedly. - on_time: 1 + on_time: `1` Number of seconds to be on - off_time: 1 + off_time: `1` Number of seconds to be off - n: None + n: `None` Number of times to blink; None means forever - background: True - If True, start a background thread to continue blinking and return - immediately. If False, only return when the blink is finished - (warning: the default value of n will result in this method never - returning). + background: `True` + If `True`, start a background thread to continue blinking and + return immediately. If `False`, only return when the blink is + finished (warning: the default value of `n` will result in this + method never returning). """ for thing in self._all: led.blink(on_time, off_time, n, background) @@ -185,20 +185,20 @@ class FishDish(TrafficLights): """ Make all the board's LEDs turn on and off repeatedly. - on_time: 1 + on_time: `1` Number of seconds to be on - off_time: 1 + off_time: `1` Number of seconds to be off - n: None + n: `None` Number of times to blink; None means forever - background: True - If True, start a background thread to continue blinking and return - immediately. If False, only return when the blink is finished - (warning: the default value of n will result in this method never - returning). + background: `True` + If `True`, start a background thread to continue blinking and + return immediately. If `False`, only return when the blink is + finished (warning: the default value of `n` will result in this + method never returning). """ super(FishDish, self).blink(on_time, off_time, n, background) @@ -234,7 +234,7 @@ class Robot(object): Make the robot turn left. If seconds given, stop after given number of seconds. - seconds: None + seconds: `None` Number of seconds to turn left for """ self._left.forward() @@ -249,7 +249,7 @@ class Robot(object): Make the robot turn right. If seconds given, stop after given number of seconds. - seconds: None + seconds: `None` Number of seconds to turn right for """ self._right.forward() @@ -261,9 +261,10 @@ class Robot(object): def forward(self, seconds=None): """ - Drive the robot forward. If seconds given, stop after given number of seconds. + Drive the robot forward. If seconds given, stop after given number of + seconds. - seconds: None + seconds: `None` Number of seconds to drive forward for """ self._left.forward() @@ -275,9 +276,10 @@ class Robot(object): def backward(self, seconds=None): """ - Drive the robot backward. If seconds given, stop after given number of seconds. + Drive the robot backward. If seconds given, stop after given number of + seconds. - seconds: None + seconds: `None` Number of seconds to drive backward for """ self._left.backward() diff --git a/gpiozero/devices.py b/gpiozero/devices.py index 6e0e8ff..cd05cfe 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -36,7 +36,16 @@ class GPIODeviceClosed(GPIODeviceError): class GPIODevice(object): """ - Generic GPIO 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 `pin` property, + `is_active` property, and the `close` method). + + pin: `None` + The GPIO pin (in BCM numbering) that the device is connected to. If + this is `None` a `GPIODeviceError` will be raised. """ def __init__(self, pin=None): # self._pin must be set before any possible exceptions can be raised @@ -74,9 +83,50 @@ class GPIODevice(object): @property def closed(self): + """ + Returns `True` if the device is closed (see the `close` method). Once a + device is closed you can no longer use any other methods or properties + to control or query the device. + """ return self._pin is None def close(self): + """ + Shut down the device and release all associated resources. + + This method is primarily intended for interactive use at the command + line. It disables the device and releases its pin for use by another + device. + + You can attempt to do this simply by deleting an object, but unless + you've cleaned up all references to the object this may not work (even + if you've cleaned up all references, there's still no guarantee the + garbage collector will actually delete the object at that point). By + contrast, the close method provides a means of ensuring that the object + is shut down. + + For example, if you have a breadboard with a buzzer connected to pin + 16, but then wish to attach an LED instead: + + >>> from gpiozero import * + >>> bz = Buzzer(16) + >>> bz.on() + >>> bz.off() + >>> bz.close() + >>> led = LED(16) + >>> led.blink() + + GPIODevice descendents can also be used as context managers using the + `with` statement. For example: + + >>> from gpiozero import * + >>> with Buzzer(16) as bz: + ... bz.on() + ... + >>> with LED(16) as led: + ... led.on() + ... + """ with _GPIO_PINS_LOCK: pin = self._pin self._pin = None @@ -93,10 +143,17 @@ class GPIODevice(object): @property def pin(self): + """ + The pin (in BCM numbering) that the device is connected to. This will + be `None` if the device has been closed (see the `close` method). + """ return self._pin @property def is_active(self): + """ + Returns `True` if the device is currently active and `False` otherwise. + """ return self._read() def __repr__(self): diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index c850647..20f51bf 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -13,10 +13,13 @@ from spidev import SpiDev from .devices import GPIODeviceError, GPIODeviceClosed, GPIODevice, GPIOQueue -def _alias(key): +def _alias(key, doc=None): + if doc is None: + doc = 'Alias for %s' % key return property( lambda self: getattr(self, key), - lambda self, val: setattr(self, key, val) + lambda self, val: setattr(self, key, val), + doc=doc ) @@ -26,7 +29,21 @@ class InputDeviceError(GPIODeviceError): class InputDevice(GPIODevice): """ - Generic GPIO Input Device. + Represents a generic GPIO input device. + + This class extends `GPIODevice` to add facilities common to GPIO input + devices. The constructor adds the optional `pull_up` parameter to specify + how the pin should be pulled by the internal resistors. The `is_active` + property is adjusted accordingly so that `True` still means active + regardless of the `pull_up` setting. + + pin: `None` + The GPIO pin (in BCM numbering) that the device is connected to. If + this is `None` a GPIODeviceError will be raised. + + pull_up: `False` + If `True`, the pin will be pulled high with an internal resistor. If + `False` (the default), the pin will be pulled low. """ def __init__(self, pin=None, pull_up=False): if pin in (2, 3) and not pull_up: @@ -73,7 +90,16 @@ class InputDevice(GPIODevice): class WaitableInputDevice(InputDevice): """ - An action-dependent Generic Input Device. + Represents a generic input device with distinct waitable states. + + This class extends `InputDevice` with methods for waiting on the device's + status (`wait_for_active` and `wait_for_inactive`), and properties that + hold functions to be called when the device changes state (`when_activated` + and `when_deactivated`). These are aliased appropriately in various + subclasses. + + Note that this class provides no means of actually firing its events; it's + effectively an abstract base class. """ def __init__(self, pin=None, pull_up=False): super(WaitableInputDevice, self).__init__(pin, pull_up) @@ -88,18 +114,20 @@ class WaitableInputDevice(InputDevice): Halt the program until the device is activated, or the timeout is reached. - timeout: None - Number of seconds (?) to wait before proceeding + timeout: `None` + 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): """ - Halt the program until the device is inactivated, or the timeout is + Halt the program until the device is deactivated, or the timeout is reached. - timeout: None - Number of seconds (?) to wait before proceeding + timeout: `None` + 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) @@ -109,7 +137,20 @@ class WaitableInputDevice(InputDevice): def _set_when_activated(self, value): self._when_activated = self._wrap_callback(value) - when_activated = property(_get_when_activated, _set_when_activated) + when_activated = property(_get_when_activated, _set_when_activated, doc="""\ + 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 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 activates will be passed as that + parameter. + + Set this property to `None` (the default) to disable the event. + + See also: when_deactivated. + """) def _get_when_deactivated(self): return self._when_deactivated @@ -117,7 +158,20 @@ class WaitableInputDevice(InputDevice): def _set_when_deactivated(self, value): self._when_deactivated = self._wrap_callback(value) - when_deactivated = property(_get_when_deactivated, _set_when_deactivated) + when_deactivated = property(_get_when_deactivated, _set_when_deactivated, doc="""\ + 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 function which accepts a single mandatory parameter (which as + many optional parameters as you like). If the function accepts a single + mandatory parameter, the device the deactives will be passed as that + parameter. + + Set this property to `None` (the default) to disable the event. + + See also: when_activated. + """) def _wrap_callback(self, fn): if fn is None: @@ -170,14 +224,24 @@ class WaitableInputDevice(InputDevice): class DigitalInputDevice(WaitableInputDevice): """ - A Generic Digital Input Device. + Represents a generic input device with typical on/off behaviour. + + This class extends `WaitableInputDevice` with machinery to fire the active + and inactive events for devices that operate in a typical digital manner: + straight forward on / off states with (reasonably) clean transitions + between the two. + + bounce_time: `None` + Specifies the length of time (in seconds) that the component will + ignore changes in state after an initial change. This defaults to + `None` which indicates that no bounce compensation will be performed. """ - def __init__(self, pin=None, pull_up=False, bouncetime=None): + def __init__(self, pin=None, pull_up=False, bounce_time=None): super(DigitalInputDevice, self).__init__(pin, pull_up) # Yes, that's really the default bouncetime in RPi.GPIO... GPIO.add_event_detect( self.pin, GPIO.BOTH, callback=self._fire_events, - bouncetime=-666 if bouncetime is None else bouncetime + bouncetime=-666 if bounce_time is None else int(bounce_time * 1000) ) # Call _fire_events once to set initial state of events super(DigitalInputDevice, self)._fire_events() @@ -188,7 +252,35 @@ class DigitalInputDevice(WaitableInputDevice): class SmoothedInputDevice(WaitableInputDevice): """ - A Generic Digital Input Device with background polling. + Represents a generic input device which takes its value from the mean of a + queue of historical values. + + This class extends `WaitableInputDevice` with a queue which is filled by a + background thread which continually polls the state of the underlying + device. The mean of the values in the queue is compared to a threshold + which is used to determine the state of the `is_active` property. + + This class is intended for use with devices which either exhibit analog + behaviour (such as the charging time of a capacitor with an LDR), or those + which exhibit "twitchy" behaviour (such as certain motion sensors). + + threshold: `0.5` + The value above which the device will be considered "on". + + queue_len: `5` + The length of the internal queue which is filled by the background + thread. + + sample_wait: `0.0` + The length of time to wait between retrieving the state of the + underlying device. Defaults to 0.0 indicating that values are retrieved + as fast as possible. + + partial: `False` + If `False` (the default), attempts to read the state of the device + (from the `is_active` property) will block until the queue has filled. + If `True`, a value will be returned immediately, but be aware that this + value is likely to fluctuate excessively. """ def __init__( self, pin=None, pull_up=False, threshold=0.5, @@ -226,16 +318,28 @@ class SmoothedInputDevice(WaitableInputDevice): @property def queue_len(self): + """ + The length of the internal queue of values which is averaged to + determine the overall state of the device. This defaults to `5`. + """ self._check_open() return self._queue.queue.maxlen @property def partial(self): + """ + If `False` (the default), attempts to read the `value` or `is_active` + properties will block until the queue has filled. + """ self._check_open() return self._queue.partial @property def value(self): + """ + Returns the mean of the values in the internal queue. This is + compared to `threshold` to determine whether `is_active` is `True`. + """ self._check_open() return self._queue.value @@ -249,7 +353,9 @@ class SmoothedInputDevice(WaitableInputDevice): ) self._threshold = float(value) - threshold = property(_get_threshold, _set_threshold) + threshold = property(_get_threshold, _set_threshold, doc="""\ + If `value` exceeds this amount, then `is_active` will return `True`. + """) @property def is_active(self): @@ -259,6 +365,10 @@ class SmoothedInputDevice(WaitableInputDevice): class Button(DigitalInputDevice): """ A physical push button or switch. + + A typical configuration of such a device is to connect a GPIO pin to one + side of the switch, and ground to the other (the default `pull_up` value + is `True`). """ def __init__(self, pin=None, pull_up=True, bouncetime=None): super(Button, self).__init__(pin, pull_up, bouncetime) @@ -275,6 +385,11 @@ class Button(DigitalInputDevice): class MotionSensor(SmoothedInputDevice): """ A PIR (Passive Infra-Red) motion sensor. + + A typical PIR device has a small circuit board with three pins: VCC, OUT, + and GND. VCC should be connected to the Pi's +5V pin, GND to one of the + Pi's ground pins, and finally OUT to the GPIO specified as the value of the + `pin` parameter in the constructor. """ def __init__( self, pin=None, queue_len=5, sample_rate=10, threshold=0.5, @@ -297,6 +412,11 @@ class MotionSensor(SmoothedInputDevice): class LightSensor(SmoothedInputDevice): """ An LDR (Light Dependent Resistor) Light Sensor. + + A typical LDR circuit connects one side of the LDR to the 3v3 line from the + Pi, and the other side to a GPIO pin, and a capacitor tied to ground. This + class repeatedly discharges the capacitor, then times the duration it takes + to charge (which will vary according to the light falling on the LDR). """ def __init__( self, pin=None, queue_len=5, charge_time_limit=0.01, diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 489c15c..5dc8f28 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -14,7 +14,11 @@ class OutputDeviceError(GPIODeviceError): class OutputDevice(GPIODevice): """ - Generic GPIO Output Device (on/off). + Represents a generic GPIO output device. + + This class extends `GPIODevice` to add facilities common to GPIO output + devices: an `on` method to switch the device on, and a corresponding `off` + method. """ def __init__(self, pin=None): super(OutputDevice, self).__init__(pin) @@ -46,7 +50,12 @@ class OutputDevice(GPIODevice): class DigitalOutputDevice(OutputDevice): """ - Generic Digital GPIO Output Device (on/off/toggle/blink). + Represents a generic output device with typical on/off behaviour. + + This class extends `OutputDevice` with a `toggle` method to switch the + device between its on and off states, and a `blink` method which uses an + optional background thread to handle toggling the device state without + further interaction. """ def __init__(self, pin=None): super(DigitalOutputDevice, self).__init__(pin) @@ -125,6 +134,10 @@ class DigitalOutputDevice(OutputDevice): class LED(DigitalOutputDevice): """ An LED (Light Emmitting Diode) component. + + A typical configuration of such a device is to connect a GPIO pin to the + anode (long leg) of the LED, and the cathode (short leg) to ground, with + an optional resistor to prevent the LED from burning out. """ pass @@ -132,6 +145,9 @@ class LED(DigitalOutputDevice): class Buzzer(DigitalOutputDevice): """ A digital Buzzer component. + + A typical configuration of such a device is to connect a GPIO pin to the + anode (long leg) of the buzzer, and the cathode (short leg) to ground. """ pass