2795 lines
101 KiB
Python
2795 lines
101 KiB
Python
|
|
# vim: set fileencoding=utf-8:
|
|||
|
|
#
|
|||
|
|
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
|||
|
|
#
|
|||
|
|
# Copyright (c) 2015-2024 Dave Jones <dave@waveform.org.uk>
|
|||
|
|
# Copyright (c) 2016-2022 Andrew Scheller <github@loowis.durge.org>
|
|||
|
|
# Copyright (c) 2015-2021 Ben Nuttall <ben@bennuttall.com>
|
|||
|
|
# Copyright (c) 2020 Ryan Walmsley <ryanteck@gmail.com>
|
|||
|
|
# Copyright (c) 2020 Jack Wearden <jack@jackwearden.co.uk>
|
|||
|
|
# Copyright (c) 2019 tuftii <3215045+tuftii@users.noreply.github.com>
|
|||
|
|
# Copyright (c) 2019 ForToffee <ForToffee@users.noreply.github.com>
|
|||
|
|
# Copyright (c) 2018 SteveAmor <steveamor@users.noreply.github.com>
|
|||
|
|
# Copyright (c) 2018 Rick Ansell <rick@nbinvincible.org.uk>
|
|||
|
|
# Copyright (c) 2018 Claire Pollard <claire.r.pollard@gmail.com>
|
|||
|
|
# Copyright (c) 2016 Ian Harcombe <ian.harcombe@gmail.com>
|
|||
|
|
# Copyright (c) 2016 Andrew Scheller <lurch@durge.org>
|
|||
|
|
#
|
|||
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|||
|
|
|
|||
|
|
import warnings
|
|||
|
|
from time import sleep
|
|||
|
|
from itertools import repeat, cycle, chain, tee
|
|||
|
|
from threading import Lock
|
|||
|
|
from collections import OrderedDict, Counter, namedtuple
|
|||
|
|
from collections.abc import MutableMapping
|
|||
|
|
from pprint import pformat
|
|||
|
|
|
|||
|
|
# Remove the try clause when 3.7 support is no longer trivial
|
|||
|
|
try:
|
|||
|
|
import importlib_resources as resources
|
|||
|
|
except ImportError:
|
|||
|
|
from importlib import resources
|
|||
|
|
|
|||
|
|
from .exc import (
|
|||
|
|
DeviceClosed,
|
|||
|
|
PinInvalidPin,
|
|||
|
|
GPIOPinMissing,
|
|||
|
|
EnergenieSocketMissing,
|
|||
|
|
EnergenieBadSocket,
|
|||
|
|
OutputDeviceBadValue,
|
|||
|
|
CompositeDeviceBadDevice,
|
|||
|
|
BadWaitTime,
|
|||
|
|
)
|
|||
|
|
from .input_devices import Button
|
|||
|
|
from .output_devices import (
|
|||
|
|
OutputDevice,
|
|||
|
|
LED,
|
|||
|
|
PWMLED,
|
|||
|
|
RGBLED,
|
|||
|
|
Buzzer,
|
|||
|
|
Motor,
|
|||
|
|
PhaseEnableMotor,
|
|||
|
|
TonalBuzzer,
|
|||
|
|
)
|
|||
|
|
from .threads import GPIOThread
|
|||
|
|
from .devices import Device, CompositeDevice
|
|||
|
|
from .mixins import SharedMixin, SourceMixin, HoldMixin, event
|
|||
|
|
from .fonts import load_font_7seg, load_font_14seg
|
|||
|
|
|
|||
|
|
|
|||
|
|
def pairwise(it):
|
|||
|
|
a, b = tee(it)
|
|||
|
|
next(b, None)
|
|||
|
|
return zip(a, b)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class CompositeOutputDevice(SourceMixin, CompositeDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeDevice` with :meth:`on`, :meth:`off`, and
|
|||
|
|
:meth:`toggle` methods for controlling subordinate output devices. Also
|
|||
|
|
extends :attr:`value` to be writeable.
|
|||
|
|
|
|||
|
|
:param Device \\*args:
|
|||
|
|
The un-named devices that belong to the composite device. The
|
|||
|
|
:attr:`~Device.value` attributes of these devices will be represented
|
|||
|
|
within the composite device's tuple :attr:`value` in the order
|
|||
|
|
specified here.
|
|||
|
|
|
|||
|
|
:type _order: list or None
|
|||
|
|
:param _order:
|
|||
|
|
If specified, this is the order of named items specified by keyword
|
|||
|
|
arguments (to ensure that the :attr:`value` tuple is constructed with a
|
|||
|
|
specific order). All keyword arguments *must* be included in the
|
|||
|
|
collection. If omitted, an alphabetically sorted order will be selected
|
|||
|
|
for keyword arguments.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
:param Device \\*\\*kwargs:
|
|||
|
|
The named devices that belong to the composite device. These devices
|
|||
|
|
will be accessible as named attributes on the resulting device, and
|
|||
|
|
their :attr:`value` attributes will be accessible as named elements of
|
|||
|
|
the composite device's tuple :attr:`value`.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def on(self):
|
|||
|
|
"""
|
|||
|
|
Turn all the output devices on.
|
|||
|
|
"""
|
|||
|
|
for device in self:
|
|||
|
|
if isinstance(device, (OutputDevice, CompositeOutputDevice)):
|
|||
|
|
device.on()
|
|||
|
|
|
|||
|
|
def off(self):
|
|||
|
|
"""
|
|||
|
|
Turn all the output devices off.
|
|||
|
|
"""
|
|||
|
|
for device in self:
|
|||
|
|
if isinstance(device, (OutputDevice, CompositeOutputDevice)):
|
|||
|
|
device.off()
|
|||
|
|
|
|||
|
|
def toggle(self):
|
|||
|
|
"""
|
|||
|
|
Toggle all the output devices. For each device, if it's on, turn it
|
|||
|
|
off; if it's off, turn it on.
|
|||
|
|
"""
|
|||
|
|
for device in self:
|
|||
|
|
if isinstance(device, (OutputDevice, CompositeOutputDevice)):
|
|||
|
|
device.toggle()
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def value(self):
|
|||
|
|
"""
|
|||
|
|
A tuple containing a value for each subordinate device. This property
|
|||
|
|
can also be set to update the state of all subordinate output devices.
|
|||
|
|
"""
|
|||
|
|
return super().value
|
|||
|
|
|
|||
|
|
@value.setter
|
|||
|
|
def value(self, value):
|
|||
|
|
for device, v in zip(self, value):
|
|||
|
|
if isinstance(device, (OutputDevice, CompositeOutputDevice)):
|
|||
|
|
device.value = v
|
|||
|
|
# Simply ignore values for non-output devices
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ButtonBoard(HoldMixin, CompositeDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeDevice` and represents a generic button board or
|
|||
|
|
collection of buttons. The :attr:`value` of the button board is a tuple
|
|||
|
|
of all the buttons states. This can be used to control all the LEDs in a
|
|||
|
|
:class:`LEDBoard` with a :class:`ButtonBoard`::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDBoard, ButtonBoard
|
|||
|
|
from signal import pause
|
|||
|
|
|
|||
|
|
leds = LEDBoard(2, 3, 4, 5)
|
|||
|
|
btns = ButtonBoard(6, 7, 8, 9)
|
|||
|
|
leds.source = btns
|
|||
|
|
|
|||
|
|
pause()
|
|||
|
|
|
|||
|
|
Alternatively you could represent the number of pressed buttons with an
|
|||
|
|
:class:`LEDBarGraph`::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDBarGraph, ButtonBoard
|
|||
|
|
from statistics import mean
|
|||
|
|
from signal import pause
|
|||
|
|
|
|||
|
|
graph = LEDBarGraph(2, 3, 4, 5)
|
|||
|
|
bb = ButtonBoard(6, 7, 8, 9)
|
|||
|
|
graph.source = (mean(values) for values in bb.values)
|
|||
|
|
|
|||
|
|
pause()
|
|||
|
|
|
|||
|
|
:type pins: int or str
|
|||
|
|
:param \\*pins:
|
|||
|
|
Specify the GPIO pins that the buttons of the board are attached to.
|
|||
|
|
See :ref:`pin-numbering` for valid pin numbers. You can designate as
|
|||
|
|
many pins as necessary.
|
|||
|
|
|
|||
|
|
:type pull_up: bool or None
|
|||
|
|
:param pull_up:
|
|||
|
|
If :data:`True` (the default), the GPIO pins will be pulled high by
|
|||
|
|
default. In this case, connect the other side of the buttons to
|
|||
|
|
ground. If :data:`False`, the GPIO pins will be pulled low by default.
|
|||
|
|
In this case, connect the other side of the buttons to 3V3. If
|
|||
|
|
:data:`None`, the pin will be floating, so it must be externally pulled
|
|||
|
|
up or down and the ``active_state`` parameter must be set accordingly.
|
|||
|
|
|
|||
|
|
:type active_state: bool or None
|
|||
|
|
:param active_state:
|
|||
|
|
See description under :class:`InputDevice` for more information.
|
|||
|
|
|
|||
|
|
:param float bounce_time:
|
|||
|
|
If :data:`None` (the default), no software bounce compensation will be
|
|||
|
|
performed. Otherwise, this is the length of time (in seconds) that the
|
|||
|
|
buttons will ignore changes in state after an initial change.
|
|||
|
|
|
|||
|
|
:param float hold_time:
|
|||
|
|
The length of time (in seconds) to wait after any button is pushed,
|
|||
|
|
until executing the :attr:`when_held` handler. Defaults to ``1``.
|
|||
|
|
|
|||
|
|
:param bool hold_repeat:
|
|||
|
|
If :data:`True`, the :attr:`when_held` handler will be repeatedly
|
|||
|
|
executed as long as any buttons remain held, every *hold_time* seconds.
|
|||
|
|
If :data:`False` (the default) the :attr:`when_held` handler will be
|
|||
|
|
only be executed once per hold.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
:type named_pins: int or str
|
|||
|
|
:param \\*\\*named_pins:
|
|||
|
|
Specify GPIO pins that buttons of the board are attached to,
|
|||
|
|
associating each button with a property name. You can designate as
|
|||
|
|
many pins as necessary and use any names, provided they're not already
|
|||
|
|
in use by something else.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *pins, pull_up=True, active_state=None,
|
|||
|
|
bounce_time=None, hold_time=1, hold_repeat=False,
|
|||
|
|
_order=None, pin_factory=None, **named_pins):
|
|||
|
|
super().__init__(
|
|||
|
|
*(
|
|||
|
|
Button(pin, pull_up=pull_up, active_state=active_state,
|
|||
|
|
bounce_time=bounce_time, hold_time=hold_time,
|
|||
|
|
hold_repeat=hold_repeat, pin_factory=pin_factory)
|
|||
|
|
for pin in pins
|
|||
|
|
),
|
|||
|
|
_order=_order,
|
|||
|
|
pin_factory=pin_factory,
|
|||
|
|
**{
|
|||
|
|
name: Button(pin, pull_up=pull_up, active_state=active_state,
|
|||
|
|
bounce_time=bounce_time, hold_time=hold_time,
|
|||
|
|
hold_repeat=hold_repeat, pin_factory=pin_factory)
|
|||
|
|
for name, pin in named_pins.items()
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
if len(self) == 0:
|
|||
|
|
raise GPIOPinMissing('No pins given')
|
|||
|
|
def get_new_handler(device):
|
|||
|
|
def fire_both_events(ticks, state):
|
|||
|
|
device._fire_events(ticks, device._state_to_value(state))
|
|||
|
|
self._fire_events(ticks, self.is_active)
|
|||
|
|
return fire_both_events
|
|||
|
|
# _handlers only exists to ensure that we keep a reference to the
|
|||
|
|
# generated fire_both_events handler for each Button (remember that
|
|||
|
|
# pin.when_changed only keeps a weak reference to handlers)
|
|||
|
|
self._handlers = tuple(get_new_handler(device) for device in self)
|
|||
|
|
for button, handler in zip(self, self._handlers):
|
|||
|
|
button.pin.when_changed = handler
|
|||
|
|
self._when_changed = None
|
|||
|
|
self._last_value = None
|
|||
|
|
# Call _fire_events once to set initial state of events
|
|||
|
|
self._fire_events(self.pin_factory.ticks(), self.is_active)
|
|||
|
|
self.hold_time = hold_time
|
|||
|
|
self.hold_repeat = hold_repeat
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def pull_up(self):
|
|||
|
|
"""
|
|||
|
|
If :data:`True`, the device uses a pull-up resistor to set the GPIO pin
|
|||
|
|
"high" by default.
|
|||
|
|
"""
|
|||
|
|
return self[0].pull_up
|
|||
|
|
|
|||
|
|
when_changed = event()
|
|||
|
|
|
|||
|
|
def _fire_changed(self):
|
|||
|
|
if self.when_changed:
|
|||
|
|
self.when_changed()
|
|||
|
|
|
|||
|
|
def _fire_events(self, ticks, new_value):
|
|||
|
|
super()._fire_events(ticks, new_value)
|
|||
|
|
old_value, self._last_value = self._last_value, new_value
|
|||
|
|
if old_value is None:
|
|||
|
|
# Initial "indeterminate" value; don't do anything
|
|||
|
|
pass
|
|||
|
|
elif old_value != new_value:
|
|||
|
|
self._fire_changed()
|
|||
|
|
|
|||
|
|
ButtonBoard.is_pressed = ButtonBoard.is_active
|
|||
|
|
ButtonBoard.pressed_time = ButtonBoard.active_time
|
|||
|
|
ButtonBoard.when_pressed = ButtonBoard.when_activated
|
|||
|
|
ButtonBoard.when_released = ButtonBoard.when_deactivated
|
|||
|
|
ButtonBoard.wait_for_press = ButtonBoard.wait_for_active
|
|||
|
|
ButtonBoard.wait_for_release = ButtonBoard.wait_for_inactive
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LEDCollection(CompositeOutputDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeOutputDevice`. Abstract base class for
|
|||
|
|
:class:`LEDBoard` and :class:`LEDBarGraph`.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *pins, pwm=False, active_high=True, initial_value=False,
|
|||
|
|
_order=None, pin_factory=None, **named_pins):
|
|||
|
|
LEDClass = PWMLED if pwm else LED
|
|||
|
|
super().__init__(
|
|||
|
|
*(
|
|||
|
|
pin_or_collection
|
|||
|
|
if isinstance(pin_or_collection, LEDCollection) else
|
|||
|
|
LEDClass(
|
|||
|
|
pin_or_collection, active_high=active_high,
|
|||
|
|
initial_value=initial_value, pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
for pin_or_collection in pins
|
|||
|
|
),
|
|||
|
|
_order=_order,
|
|||
|
|
pin_factory=pin_factory,
|
|||
|
|
**{
|
|||
|
|
name: pin_or_collection
|
|||
|
|
if isinstance(pin_or_collection, LEDCollection) else
|
|||
|
|
LEDClass(
|
|||
|
|
pin_or_collection, active_high=active_high,
|
|||
|
|
initial_value=initial_value, pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
for name, pin_or_collection in named_pins.items()
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
if len(self) == 0:
|
|||
|
|
raise GPIOPinMissing('No pins given')
|
|||
|
|
leds = []
|
|||
|
|
for item in self:
|
|||
|
|
if isinstance(item, LEDCollection):
|
|||
|
|
for subitem in item.leds:
|
|||
|
|
leds.append(subitem)
|
|||
|
|
else:
|
|||
|
|
leds.append(item)
|
|||
|
|
self._leds = tuple(leds)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def leds(self):
|
|||
|
|
"""
|
|||
|
|
A flat tuple of all LEDs contained in this collection (and all
|
|||
|
|
sub-collections).
|
|||
|
|
"""
|
|||
|
|
return self._leds
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def active_high(self):
|
|||
|
|
return self[0].active_high
|
|||
|
|
|
|||
|
|
|
|||
|
|
LEDCollection.is_lit = LEDCollection.is_active
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LEDBoard(LEDCollection):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDCollection` and represents a generic LED board or
|
|||
|
|
collection of LEDs.
|
|||
|
|
|
|||
|
|
The following example turns on all the LEDs on a board containing 5 LEDs
|
|||
|
|
attached to GPIO pins 2 through 6::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDBoard
|
|||
|
|
|
|||
|
|
leds = LEDBoard(2, 3, 4, 5, 6)
|
|||
|
|
leds.on()
|
|||
|
|
|
|||
|
|
:type pins: int or str or LEDCollection
|
|||
|
|
:param \\*pins:
|
|||
|
|
Specify the GPIO pins that the LEDs of the board are attached to. See
|
|||
|
|
:ref:`pin-numbering` for valid pin numbers. You can designate as many
|
|||
|
|
pins as necessary. You can also specify :class:`LEDBoard` instances to
|
|||
|
|
create trees of LEDs.
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances for each pin. If
|
|||
|
|
:data:`False` (the default), construct regular :class:`LED` instances.
|
|||
|
|
|
|||
|
|
:param bool active_high:
|
|||
|
|
If :data:`True` (the default), the :meth:`on` method will set all the
|
|||
|
|
associated pins to HIGH. If :data:`False`, the :meth:`on` method will
|
|||
|
|
set all pins to LOW (the :meth:`off` method always does the opposite).
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type _order: list or None
|
|||
|
|
:param _order:
|
|||
|
|
If specified, this is the order of named items specified by keyword
|
|||
|
|
arguments (to ensure that the :attr:`value` tuple is constructed with a
|
|||
|
|
specific order). All keyword arguments *must* be included in the
|
|||
|
|
collection. If omitted, an alphabetically sorted order will be selected
|
|||
|
|
for keyword arguments.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
:type named_pins: int or str
|
|||
|
|
:param \\*\\*named_pins:
|
|||
|
|
Specify GPIO pins that LEDs of the board are attached to, associating
|
|||
|
|
each LED with a property name. You can designate as many pins as
|
|||
|
|
necessary and use any names, provided they're not already in use by
|
|||
|
|
something else. You can also specify :class:`LEDBoard` instances to
|
|||
|
|
create trees of LEDs.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *pins, pwm=False, active_high=True, initial_value=False,
|
|||
|
|
_order=None, pin_factory=None, **named_pins):
|
|||
|
|
self._blink_thread = None
|
|||
|
|
self._blink_leds = []
|
|||
|
|
self._blink_lock = Lock()
|
|||
|
|
super().__init__(*pins, pwm=pwm, active_high=active_high,
|
|||
|
|
initial_value=initial_value, _order=_order,
|
|||
|
|
pin_factory=pin_factory, **named_pins)
|
|||
|
|
|
|||
|
|
def close(self):
|
|||
|
|
try:
|
|||
|
|
self._stop_blink()
|
|||
|
|
except AttributeError:
|
|||
|
|
pass
|
|||
|
|
super().close()
|
|||
|
|
|
|||
|
|
def on(self, *args):
|
|||
|
|
"""
|
|||
|
|
If no arguments are specified, turn all the LEDs on. If arguments are
|
|||
|
|
specified, they must be the indexes of the LEDs you wish to turn on.
|
|||
|
|
For example::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDBoard
|
|||
|
|
|
|||
|
|
leds = LEDBoard(2, 3, 4, 5)
|
|||
|
|
leds.on(0) # turn on the first LED (pin 2)
|
|||
|
|
leds.on(-1) # turn on the last LED (pin 5)
|
|||
|
|
leds.on(1, 2) # turn on the middle LEDs (pins 3 and 4)
|
|||
|
|
leds.off() # turn off all LEDs
|
|||
|
|
leds.on() # turn on all LEDs
|
|||
|
|
|
|||
|
|
If :meth:`blink` is currently active, it will be stopped first.
|
|||
|
|
|
|||
|
|
:param int args:
|
|||
|
|
The index(es) of the LED(s) to turn on. If no indexes are specified
|
|||
|
|
turn on all LEDs.
|
|||
|
|
"""
|
|||
|
|
self._stop_blink()
|
|||
|
|
if args:
|
|||
|
|
for index in args:
|
|||
|
|
self[index].on()
|
|||
|
|
else:
|
|||
|
|
super().on()
|
|||
|
|
|
|||
|
|
def off(self, *args):
|
|||
|
|
"""
|
|||
|
|
If no arguments are specified, turn all the LEDs off. If arguments are
|
|||
|
|
specified, they must be the indexes of the LEDs you wish to turn off.
|
|||
|
|
For example::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDBoard
|
|||
|
|
|
|||
|
|
leds = LEDBoard(2, 3, 4, 5)
|
|||
|
|
leds.on() # turn on all LEDs
|
|||
|
|
leds.off(0) # turn off the first LED (pin 2)
|
|||
|
|
leds.off(-1) # turn off the last LED (pin 5)
|
|||
|
|
leds.off(1, 2) # turn off the middle LEDs (pins 3 and 4)
|
|||
|
|
leds.on() # turn on all LEDs
|
|||
|
|
|
|||
|
|
If :meth:`blink` is currently active, it will be stopped first.
|
|||
|
|
|
|||
|
|
:param int args:
|
|||
|
|
The index(es) of the LED(s) to turn off. If no indexes are
|
|||
|
|
specified turn off all LEDs.
|
|||
|
|
"""
|
|||
|
|
self._stop_blink()
|
|||
|
|
if args:
|
|||
|
|
for index in args:
|
|||
|
|
self[index].off()
|
|||
|
|
else:
|
|||
|
|
super().off()
|
|||
|
|
|
|||
|
|
def toggle(self, *args):
|
|||
|
|
"""
|
|||
|
|
If no arguments are specified, toggle the state of all LEDs. If
|
|||
|
|
arguments are specified, they must be the indexes of the LEDs you wish
|
|||
|
|
to toggle. For example::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDBoard
|
|||
|
|
|
|||
|
|
leds = LEDBoard(2, 3, 4, 5)
|
|||
|
|
leds.toggle(0) # turn on the first LED (pin 2)
|
|||
|
|
leds.toggle(-1) # turn on the last LED (pin 5)
|
|||
|
|
leds.toggle() # turn the first and last LED off, and the
|
|||
|
|
# middle pair on
|
|||
|
|
|
|||
|
|
If :meth:`blink` is currently active, it will be stopped first.
|
|||
|
|
|
|||
|
|
:param int args:
|
|||
|
|
The index(es) of the LED(s) to toggle. If no indexes are specified
|
|||
|
|
toggle the state of all LEDs.
|
|||
|
|
"""
|
|||
|
|
self._stop_blink()
|
|||
|
|
if args:
|
|||
|
|
for index in args:
|
|||
|
|
self[index].toggle()
|
|||
|
|
else:
|
|||
|
|
super().toggle()
|
|||
|
|
|
|||
|
|
def blink(
|
|||
|
|
self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0,
|
|||
|
|
n=None, background=True):
|
|||
|
|
"""
|
|||
|
|
Make all the LEDs turn on and off repeatedly.
|
|||
|
|
|
|||
|
|
:param float on_time:
|
|||
|
|
Number of seconds on. Defaults to 1 second.
|
|||
|
|
|
|||
|
|
:param float off_time:
|
|||
|
|
Number of seconds off. Defaults to 1 second.
|
|||
|
|
|
|||
|
|
:param float fade_in_time:
|
|||
|
|
Number of seconds to spend fading in. Defaults to 0. Must be 0 if
|
|||
|
|
``pwm`` was :data:`False` when the class was constructed
|
|||
|
|
(:exc:`ValueError` will be raised if not).
|
|||
|
|
|
|||
|
|
:param float fade_out_time:
|
|||
|
|
Number of seconds to spend fading out. Defaults to 0. Must be 0 if
|
|||
|
|
``pwm`` was :data:`False` when the class was constructed
|
|||
|
|
(:exc:`ValueError` will be raised if not).
|
|||
|
|
|
|||
|
|
:type n: int or None
|
|||
|
|
:param n:
|
|||
|
|
Number of times to blink; :data:`None` (the default) means forever.
|
|||
|
|
|
|||
|
|
:param bool background:
|
|||
|
|
If :data:`True`, start a background thread to continue blinking and
|
|||
|
|
return immediately. If :data:`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:
|
|||
|
|
if isinstance(led, LED):
|
|||
|
|
if fade_in_time:
|
|||
|
|
raise ValueError('fade_in_time must be 0 with non-PWM LEDs')
|
|||
|
|
if fade_out_time:
|
|||
|
|
raise ValueError('fade_out_time must be 0 with non-PWM LEDs')
|
|||
|
|
self._stop_blink()
|
|||
|
|
self._blink_thread = GPIOThread(
|
|||
|
|
self._blink_device,
|
|||
|
|
(on_time, off_time, fade_in_time, fade_out_time, n))
|
|||
|
|
self._blink_thread.start()
|
|||
|
|
if not background:
|
|||
|
|
self._blink_thread.join()
|
|||
|
|
self._blink_thread = None
|
|||
|
|
|
|||
|
|
def _stop_blink(self, led=None):
|
|||
|
|
if led is None:
|
|||
|
|
if self._blink_thread:
|
|||
|
|
self._blink_thread.stop()
|
|||
|
|
self._blink_thread = None
|
|||
|
|
else:
|
|||
|
|
with self._blink_lock:
|
|||
|
|
self._blink_leds.remove(led)
|
|||
|
|
|
|||
|
|
def pulse(self, fade_in_time=1, fade_out_time=1, n=None, background=True):
|
|||
|
|
"""
|
|||
|
|
Make all LEDs fade in and out repeatedly. Note that this method will
|
|||
|
|
only work if the *pwm* parameter was :data:`True` at construction time.
|
|||
|
|
|
|||
|
|
:param float fade_in_time:
|
|||
|
|
Number of seconds to spend fading in. Defaults to 1.
|
|||
|
|
|
|||
|
|
:param float fade_out_time:
|
|||
|
|
Number of seconds to spend fading out. Defaults to 1.
|
|||
|
|
|
|||
|
|
:type n: int or None
|
|||
|
|
:param n:
|
|||
|
|
Number of times to blink; :data:`None` (the default) means forever.
|
|||
|
|
|
|||
|
|
:param bool background:
|
|||
|
|
If :data:`True` (the default), start a background thread to
|
|||
|
|
continue blinking and return immediately. If :data:`False`, only
|
|||
|
|
return when the blink is finished (warning: the default value of
|
|||
|
|
*n* will result in this method never returning).
|
|||
|
|
"""
|
|||
|
|
on_time = off_time = 0
|
|||
|
|
self.blink(
|
|||
|
|
on_time, off_time, fade_in_time, fade_out_time, n, background)
|
|||
|
|
|
|||
|
|
def _blink_device(
|
|||
|
|
self, on_time, off_time, fade_in_time, fade_out_time, n, fps=25):
|
|||
|
|
sequence = []
|
|||
|
|
if fade_in_time > 0:
|
|||
|
|
sequence += [
|
|||
|
|
(i * (1 / fps) / fade_in_time, 1 / fps)
|
|||
|
|
for i in range(int(fps * fade_in_time))
|
|||
|
|
]
|
|||
|
|
sequence.append((1, on_time))
|
|||
|
|
if fade_out_time > 0:
|
|||
|
|
sequence += [
|
|||
|
|
(1 - (i * (1 / fps) / fade_out_time), 1 / fps)
|
|||
|
|
for i in range(int(fps * fade_out_time))
|
|||
|
|
]
|
|||
|
|
sequence.append((0, off_time))
|
|||
|
|
if n is None:
|
|||
|
|
sequence = cycle(sequence)
|
|||
|
|
else:
|
|||
|
|
sequence = chain.from_iterable(repeat(sequence, n))
|
|||
|
|
with self._blink_lock:
|
|||
|
|
self._blink_leds = list(self.leds)
|
|||
|
|
for led in self._blink_leds:
|
|||
|
|
if led._controller not in (None, self):
|
|||
|
|
led._controller._stop_blink(led)
|
|||
|
|
led._controller = self
|
|||
|
|
for value, delay in sequence:
|
|||
|
|
with self._blink_lock:
|
|||
|
|
if not self._blink_leds:
|
|||
|
|
break
|
|||
|
|
for led in self._blink_leds:
|
|||
|
|
led._write(value)
|
|||
|
|
if self._blink_thread.stopping.wait(delay):
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LEDBarGraph(LEDCollection):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDCollection` to control a line of LEDs representing a
|
|||
|
|
bar graph. Positive values (0 to 1) light the LEDs from first to last.
|
|||
|
|
Negative values (-1 to 0) light the LEDs from last to first.
|
|||
|
|
|
|||
|
|
The following example demonstrates turning on the first two and last two
|
|||
|
|
LEDs in a board containing five LEDs attached to GPIOs 2 through 6::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDBarGraph
|
|||
|
|
from time import sleep
|
|||
|
|
|
|||
|
|
graph = LEDBarGraph(2, 3, 4, 5, 6)
|
|||
|
|
graph.value = 2/5 # Light the first two LEDs only
|
|||
|
|
sleep(1)
|
|||
|
|
graph.value = -2/5 # Light the last two LEDs only
|
|||
|
|
sleep(1)
|
|||
|
|
graph.off()
|
|||
|
|
|
|||
|
|
As with all other output devices, :attr:`source` and :attr:`values` are
|
|||
|
|
supported::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDBarGraph, MCP3008
|
|||
|
|
from signal import pause
|
|||
|
|
|
|||
|
|
graph = LEDBarGraph(2, 3, 4, 5, 6, pwm=True)
|
|||
|
|
pot = MCP3008(channel=0)
|
|||
|
|
|
|||
|
|
graph.source = pot
|
|||
|
|
|
|||
|
|
pause()
|
|||
|
|
|
|||
|
|
:type pins: int or str
|
|||
|
|
:param \\*pins:
|
|||
|
|
Specify the GPIO pins that the LEDs of the bar graph are attached to.
|
|||
|
|
See :ref:`pin-numbering` for valid pin numbers. You can designate as
|
|||
|
|
many pins as necessary.
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances for each pin. If
|
|||
|
|
:data:`False` (the default), construct regular :class:`LED` instances.
|
|||
|
|
This parameter can only be specified as a keyword parameter.
|
|||
|
|
|
|||
|
|
:param bool active_high:
|
|||
|
|
If :data:`True` (the default), the :meth:`on` method will set all the
|
|||
|
|
associated pins to HIGH. If :data:`False`, the :meth:`on` method will
|
|||
|
|
set all pins to LOW (the :meth:`off` method always does the opposite).
|
|||
|
|
This parameter can only be specified as a keyword parameter.
|
|||
|
|
|
|||
|
|
:param float initial_value:
|
|||
|
|
The initial :attr:`value` of the graph given as a float between -1 and
|
|||
|
|
+1. Defaults to 0.0. This parameter can only be specified as a
|
|||
|
|
keyword parameter.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *pins, pwm=False, active_high=True, initial_value=0.0,
|
|||
|
|
pin_factory=None):
|
|||
|
|
# Don't allow graphs to contain collections
|
|||
|
|
for pin in pins:
|
|||
|
|
if isinstance(pin, Device):
|
|||
|
|
raise CompositeDeviceBadDevice(
|
|||
|
|
'Only pins may be specified for LEDBarGraph')
|
|||
|
|
super().__init__(
|
|||
|
|
*pins, pwm=pwm, active_high=active_high, pin_factory=pin_factory)
|
|||
|
|
try:
|
|||
|
|
self.value = initial_value
|
|||
|
|
except:
|
|||
|
|
self.close()
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def value(self):
|
|||
|
|
"""
|
|||
|
|
The value of the LED bar graph. When no LEDs are lit, the value is 0.
|
|||
|
|
When all LEDs are lit, the value is 1. Values between 0 and 1
|
|||
|
|
light LEDs linearly from first to last. Values between 0 and -1
|
|||
|
|
light LEDs linearly from last to first.
|
|||
|
|
|
|||
|
|
To light a particular number of LEDs, simply divide that number by
|
|||
|
|
the number of LEDs. For example, if your graph contains 3 LEDs, the
|
|||
|
|
following will light the first::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDBarGraph
|
|||
|
|
|
|||
|
|
graph = LEDBarGraph(12, 16, 19)
|
|||
|
|
graph.value = 1/3
|
|||
|
|
|
|||
|
|
.. note::
|
|||
|
|
|
|||
|
|
Setting value to -1 will light all LEDs. However, querying it
|
|||
|
|
subsequently will return 1 as both representations are the same in
|
|||
|
|
hardware. The readable range of :attr:`value` is effectively
|
|||
|
|
-1 < value <= 1.
|
|||
|
|
"""
|
|||
|
|
result = sum(led.value for led in self)
|
|||
|
|
if self[0].value < self[-1].value:
|
|||
|
|
result = -result
|
|||
|
|
return result / len(self)
|
|||
|
|
|
|||
|
|
@value.setter
|
|||
|
|
def value(self, value):
|
|||
|
|
if not -1 <= value <= 1:
|
|||
|
|
raise OutputDeviceBadValue(
|
|||
|
|
'LEDBarGraph value must be between -1 and 1')
|
|||
|
|
count = len(self)
|
|||
|
|
leds = self
|
|||
|
|
if value < 0:
|
|||
|
|
leds = reversed(leds)
|
|||
|
|
value = -value
|
|||
|
|
if isinstance(self[0], PWMLED):
|
|||
|
|
calc_value = lambda index: min(1, max(0, count * value - index))
|
|||
|
|
else:
|
|||
|
|
calc_value = lambda index: value >= ((index + 1) / count)
|
|||
|
|
for index, led in enumerate(leds):
|
|||
|
|
led.value = calc_value(index)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def lit_count(self):
|
|||
|
|
"""
|
|||
|
|
The number of LEDs on the bar graph actually lit up. Note that just
|
|||
|
|
like :attr:`value`, this can be negative if the LEDs are lit from last
|
|||
|
|
to first.
|
|||
|
|
"""
|
|||
|
|
lit_value = self.value * len(self)
|
|||
|
|
if not isinstance(self[0], PWMLED):
|
|||
|
|
lit_value = int(lit_value)
|
|||
|
|
return lit_value
|
|||
|
|
|
|||
|
|
@lit_count.setter
|
|||
|
|
def lit_count(self, value):
|
|||
|
|
self.value = value / len(self)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LEDCharFont(MutableMapping):
|
|||
|
|
"""
|
|||
|
|
Contains a mapping of values to tuples of LED states.
|
|||
|
|
|
|||
|
|
This effectively acts as a "font" for :class:`LEDCharDisplay`, and two
|
|||
|
|
default fonts (for 7-segment and 14-segment displays) are shipped with GPIO
|
|||
|
|
Zero by default. You can construct your own font instance from a
|
|||
|
|
:class:`dict` which maps values (usually single-character strings) to
|
|||
|
|
a tuple of LED states::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDCharDisplay, LEDCharFont
|
|||
|
|
|
|||
|
|
my_font = LEDCharFont({
|
|||
|
|
' ': (0, 0, 0, 0, 0, 0, 0),
|
|||
|
|
'D': (1, 1, 1, 1, 1, 1, 0),
|
|||
|
|
'A': (1, 1, 1, 0, 1, 1, 1),
|
|||
|
|
'd': (0, 1, 1, 1, 1, 0, 1),
|
|||
|
|
'a': (1, 1, 1, 1, 1, 0, 1),
|
|||
|
|
})
|
|||
|
|
display = LEDCharDisplay(26, 13, 12, 22, 17, 19, 6, dp=5, font=my_font)
|
|||
|
|
display.value = 'D'
|
|||
|
|
|
|||
|
|
Font instances are mutable and can be changed while actively in use by
|
|||
|
|
an instance of :class:`LEDCharDisplay`. However, changing the font will
|
|||
|
|
*not* change the state of the LEDs in the display (though it may change
|
|||
|
|
the :attr:`~LEDCharDisplay.value` of the display when next queried).
|
|||
|
|
|
|||
|
|
.. note::
|
|||
|
|
|
|||
|
|
Your custom mapping should always include a value (typically space)
|
|||
|
|
which represents all the LEDs off. This will usually be the default
|
|||
|
|
value for an instance of :class:`LEDCharDisplay`.
|
|||
|
|
|
|||
|
|
You may also wish to load fonts from a friendly text-based format. A simple
|
|||
|
|
parser for such formats (supporting an arbitrary number of segments) is
|
|||
|
|
provided by :func:`gpiozero.fonts.load_segment_font`.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, font):
|
|||
|
|
super().__init__()
|
|||
|
|
self._map = {
|
|||
|
|
char: tuple(int(bool(pin)) for pin in pins)
|
|||
|
|
for char, pins in font.items()
|
|||
|
|
}
|
|||
|
|
self._refresh_rmap()
|
|||
|
|
|
|||
|
|
def __repr__(self):
|
|||
|
|
return f'{self.__class__.__name__}({pformat(self._map, indent=4)})'
|
|||
|
|
|
|||
|
|
def _refresh_rmap(self):
|
|||
|
|
# The reverse mapping is pre-calculated for speed of lookup. Given that
|
|||
|
|
# the font mapping can be 1:n, we cannot guarantee the reverse is
|
|||
|
|
# unique. In case the provided font is an ordered dictionary, we
|
|||
|
|
# explicitly take only the first definition of each non-unique pin
|
|||
|
|
# definition so that value lookups are predictable
|
|||
|
|
rmap = {}
|
|||
|
|
for char, pins in self._map.items():
|
|||
|
|
rmap.setdefault(pins, char)
|
|||
|
|
self._rmap = rmap
|
|||
|
|
|
|||
|
|
def __len__(self):
|
|||
|
|
return len(self._map)
|
|||
|
|
|
|||
|
|
def __iter__(self):
|
|||
|
|
return iter(self._map)
|
|||
|
|
|
|||
|
|
def __getitem__(self, char):
|
|||
|
|
return self._map[char]
|
|||
|
|
|
|||
|
|
def __setitem__(self, char, pins):
|
|||
|
|
try:
|
|||
|
|
# This is necessary to ensure that _rmap is correct in the case
|
|||
|
|
# that we're overwriting an existing char->pins mapping
|
|||
|
|
del self[char]
|
|||
|
|
except KeyError:
|
|||
|
|
pass
|
|||
|
|
pins = tuple(int(bool(pin)) for pin in pins)
|
|||
|
|
self._map[char] = pins
|
|||
|
|
self._rmap.setdefault(pins, char)
|
|||
|
|
|
|||
|
|
def __delitem__(self, char):
|
|||
|
|
pins = self._map[char]
|
|||
|
|
del self._map[char]
|
|||
|
|
# If the reverse mapping of the char's pins maps to the char we need
|
|||
|
|
# to find if it now maps to another char (given the n:1 mapping)
|
|||
|
|
if self._rmap[pins] == char:
|
|||
|
|
del self._rmap[pins]
|
|||
|
|
for char, char_pins in self._map.items():
|
|||
|
|
if pins == char_pins:
|
|||
|
|
self._rmap[pins] = char
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LEDCharDisplay(LEDCollection):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDCollection` for a multi-segment LED display.
|
|||
|
|
|
|||
|
|
`Multi-segment LED displays`_ typically have 7 pins (labelled "a" through
|
|||
|
|
"g") representing 7 LEDs layed out in a figure-of-8 fashion. Frequently, an
|
|||
|
|
eigth pin labelled "dp" is included for a trailing decimal-point:
|
|||
|
|
|
|||
|
|
.. code-block:: text
|
|||
|
|
|
|||
|
|
a
|
|||
|
|
━━━━━
|
|||
|
|
f ┃ ┃ b
|
|||
|
|
┃ g ┃
|
|||
|
|
━━━━━
|
|||
|
|
e ┃ ┃ c
|
|||
|
|
┃ ┃
|
|||
|
|
━━━━━ • dp
|
|||
|
|
d
|
|||
|
|
|
|||
|
|
Other common layouts are 9, 14, and 16 segment displays which include
|
|||
|
|
additional segments permitting more accurate renditions of alphanumerics.
|
|||
|
|
For example:
|
|||
|
|
|
|||
|
|
.. code-block:: text
|
|||
|
|
|
|||
|
|
a
|
|||
|
|
━━━━━
|
|||
|
|
f ┃╲i┃j╱┃ b
|
|||
|
|
┃ ╲┃╱k┃
|
|||
|
|
g━━ ━━h
|
|||
|
|
e ┃ ╱┃╲n┃ c
|
|||
|
|
┃╱l┃m╲┃
|
|||
|
|
━━━━━ • dp
|
|||
|
|
d
|
|||
|
|
|
|||
|
|
Such displays have either a common anode, or common cathode pin. This class
|
|||
|
|
defaults to the latter; when using a common anode display *active_high*
|
|||
|
|
should be set to :data:`False`.
|
|||
|
|
|
|||
|
|
Instances of this class can be used to display characters or control
|
|||
|
|
individual LEDs on the display. For example::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDCharDisplay
|
|||
|
|
|
|||
|
|
char = LEDCharDisplay(4, 5, 6, 7, 8, 9, 10, active_high=False)
|
|||
|
|
char.value = 'C'
|
|||
|
|
|
|||
|
|
If the class is constructed with 7 or 14 segments, a default :attr:`font`
|
|||
|
|
will be loaded, mapping some ASCII characters to typical layouts. In other
|
|||
|
|
cases, the default mapping will simply assign " " (space) to all LEDs off.
|
|||
|
|
You can assign your own mapping at construction time or after
|
|||
|
|
instantiation.
|
|||
|
|
|
|||
|
|
While the example above shows the display with a :class:`str` value,
|
|||
|
|
theoretically the *font* can map any value that can be the key in a
|
|||
|
|
:class:`dict`, so the value of the display can be likewise be any valid
|
|||
|
|
key value (e.g. you could map integer digits to LED patterns). That said,
|
|||
|
|
there is one exception to this: when *dp* is specified to enable the
|
|||
|
|
decimal-point, the :attr:`value` must be a :class:`str` as the presence
|
|||
|
|
or absence of a "." suffix indicates whether the *dp* LED is lit.
|
|||
|
|
|
|||
|
|
:type pins: int or str
|
|||
|
|
:param \\*pins:
|
|||
|
|
Specify the GPIO pins that the multi-segment display is attached to.
|
|||
|
|
Pins should be in the LED segment order A, B, C, D, E, F, G, and will
|
|||
|
|
be named automatically by the class. If a decimal-point pin is
|
|||
|
|
present, specify it separately as the *dp* parameter.
|
|||
|
|
|
|||
|
|
:type dp: int or str
|
|||
|
|
:param dp:
|
|||
|
|
If a decimal-point segment is present, specify it as this named
|
|||
|
|
parameter.
|
|||
|
|
|
|||
|
|
:type font: dict or None
|
|||
|
|
:param font:
|
|||
|
|
A mapping of values (typically characters, but may also be numbers) to
|
|||
|
|
tuples of LED states. A default mapping for ASCII characters is
|
|||
|
|
provided for 7 and 14 segment displays.
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances for each pin. If
|
|||
|
|
:data:`False` (the default), construct regular :class:`LED` instances.
|
|||
|
|
|
|||
|
|
:param bool active_high:
|
|||
|
|
If :data:`True` (the default), the :meth:`on` method will set all the
|
|||
|
|
associated pins to HIGH. If :data:`False`, the :meth:`on` method will
|
|||
|
|
set all pins to LOW (the :meth:`off` method always does the opposite).
|
|||
|
|
|
|||
|
|
:param initial_value:
|
|||
|
|
The initial value to display. Defaults to space (" ") which typically
|
|||
|
|
maps to all LEDs being inactive. If :data:`None`, each device will be
|
|||
|
|
left in whatever state the pin is found in when configured for output
|
|||
|
|
(warning: this can be on).
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Multi-segment LED displays: https://en.wikipedia.org/wiki/Seven-segment_display
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *pins, dp=None, font=None, pwm=False, active_high=True,
|
|||
|
|
initial_value=" ", pin_factory=None):
|
|||
|
|
if not 1 < len(pins) <= 26:
|
|||
|
|
raise PinInvalidPin(
|
|||
|
|
'Must have between 2 and 26 LEDs in LEDCharDisplay')
|
|||
|
|
for pin in pins:
|
|||
|
|
if isinstance(pin, LEDCollection):
|
|||
|
|
raise PinInvalidPin(
|
|||
|
|
'Cannot use LEDCollection in LEDCharDisplay')
|
|||
|
|
|
|||
|
|
if font is None:
|
|||
|
|
if len(pins) == 7:
|
|||
|
|
with resources.files('gpiozero.fonts').joinpath('7seg.txt').open() as f:
|
|||
|
|
font = load_font_7seg(f)
|
|||
|
|
elif len(pins) == 14:
|
|||
|
|
with resources.files('gpiozero.fonts').joinpath('14seg.txt').open() as f:
|
|||
|
|
font = load_font_14seg(f)
|
|||
|
|
else:
|
|||
|
|
# Construct a default dict containing a definition for " "
|
|||
|
|
font = {" ": (0,) * len(pins)}
|
|||
|
|
self._font = LEDCharFont(font)
|
|||
|
|
|
|||
|
|
pins = {chr(ord('a') + i): pin for i, pin in enumerate(pins)}
|
|||
|
|
order = sorted(pins.keys())
|
|||
|
|
if dp is not None:
|
|||
|
|
pins['dp'] = dp
|
|||
|
|
order.append('dp')
|
|||
|
|
super().__init__(
|
|||
|
|
pwm=pwm, active_high=active_high, initial_value=None,
|
|||
|
|
_order=order, pin_factory=pin_factory, **pins)
|
|||
|
|
if initial_value is not None:
|
|||
|
|
self.value = initial_value
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def font(self):
|
|||
|
|
"""
|
|||
|
|
An :class:`LEDCharFont` mapping characters to tuples of LED states.
|
|||
|
|
The font is mutable after construction. You can assign a tuple of LED
|
|||
|
|
states to a character to modify the font, delete an existing character
|
|||
|
|
in the font, or assign a mapping of characters to tuples to replace the
|
|||
|
|
entire font.
|
|||
|
|
|
|||
|
|
Note that modifying the :attr:`font` never alters the underlying LED
|
|||
|
|
states. Only assignment to :attr:`value`, or calling the inherited
|
|||
|
|
:class:`LEDCollection` methods (:meth:`on`, :meth:`off`, etc.) modifies
|
|||
|
|
LED states. However, modifying the font may alter the character
|
|||
|
|
returned by querying :attr:`value`.
|
|||
|
|
"""
|
|||
|
|
return self._font
|
|||
|
|
|
|||
|
|
@font.setter
|
|||
|
|
def font(self, value):
|
|||
|
|
self._font = LEDCharFont(value)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def value(self):
|
|||
|
|
"""
|
|||
|
|
The character the display should show. This is mapped by the current
|
|||
|
|
:attr:`font` to a tuple of LED states which is applied to the
|
|||
|
|
underlying LED objects when this attribute is set.
|
|||
|
|
|
|||
|
|
When queried, the current LED states are looked up in the font to
|
|||
|
|
determine the character shown. If the current LED states do not
|
|||
|
|
correspond to any character in the :attr:`font`, the value is
|
|||
|
|
:data:`None`.
|
|||
|
|
|
|||
|
|
It is possible for multiple characters in the font to map to the same
|
|||
|
|
LED states (e.g. S and 5). In this case, if the font was constructed
|
|||
|
|
from an ordered mapping (which is the default), then the first matching
|
|||
|
|
mapping will always be returned. This also implies that the value
|
|||
|
|
queried need not match the value set.
|
|||
|
|
"""
|
|||
|
|
state = super().value
|
|||
|
|
if hasattr(self, 'dp'):
|
|||
|
|
state, dp = state[:-1], state[-1]
|
|||
|
|
else:
|
|||
|
|
dp = False
|
|||
|
|
try:
|
|||
|
|
result = self._font._rmap[state]
|
|||
|
|
except KeyError:
|
|||
|
|
# Raising exceptions on lookup is problematic; in case the LED
|
|||
|
|
# state is not representable we simply return None (although
|
|||
|
|
# technically that is a valid item we can map :)
|
|||
|
|
return None
|
|||
|
|
else:
|
|||
|
|
if dp:
|
|||
|
|
return result + '.'
|
|||
|
|
else:
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
@value.setter
|
|||
|
|
def value(self, value):
|
|||
|
|
for led, v in zip(self, self._parse_state(value)):
|
|||
|
|
led.value = v
|
|||
|
|
|
|||
|
|
def _parse_state(self, value):
|
|||
|
|
if hasattr(self, 'dp'):
|
|||
|
|
if len(value) > 1 and value.endswith('.'):
|
|||
|
|
value = value[:-1]
|
|||
|
|
dp = 1
|
|||
|
|
else:
|
|||
|
|
dp = 0
|
|||
|
|
return self._font[value] + (dp,)
|
|||
|
|
else:
|
|||
|
|
return self._font[value]
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LEDMultiCharDisplay(CompositeOutputDevice):
|
|||
|
|
"""
|
|||
|
|
Wraps :class:`LEDCharDisplay` for multi-character `multiplexed`_ LED
|
|||
|
|
character displays.
|
|||
|
|
|
|||
|
|
The class is constructed with a *char* which is an instance of the
|
|||
|
|
:class:`LEDCharDisplay` class, capable of controlling the LEDs in one
|
|||
|
|
character of the display, and an additional set of *pins* that represent
|
|||
|
|
the common cathode (or anode) of each character.
|
|||
|
|
|
|||
|
|
.. warning::
|
|||
|
|
|
|||
|
|
You should not attempt to connect the common cathode (or anode) off
|
|||
|
|
each character directly to a GPIO. Rather, use a set of transistors (or
|
|||
|
|
some other suitable component capable of handling the current of all
|
|||
|
|
the segment LEDs simultaneously) to connect the common cathode to
|
|||
|
|
ground (or the common anode to the supply) and control those
|
|||
|
|
transistors from the GPIOs specified under *pins*.
|
|||
|
|
|
|||
|
|
The *active_high* parameter defaults to :data:`True`. Note that it only
|
|||
|
|
applies to the specified *pins*, which are assumed to be controlling a set
|
|||
|
|
of transistors (hence the default). The specified *char* will use its own
|
|||
|
|
*active_high* parameter. Finally, *initial_value* defaults to a tuple of
|
|||
|
|
:attr:`~LEDCharDisplay.value` attribute of the specified display multiplied
|
|||
|
|
by the number of *pins* provided.
|
|||
|
|
|
|||
|
|
When the :attr:`value` is set such that one or more characters in the
|
|||
|
|
display differ in value, a background thread is implicitly started to
|
|||
|
|
rotate the active character, relying on `persistence of vision`_ to display
|
|||
|
|
the complete value.
|
|||
|
|
|
|||
|
|
.. _multiplexed: https://en.wikipedia.org/wiki/Multiplexed_display
|
|||
|
|
.. _persistence of vision: https://en.wikipedia.org/wiki/Persistence_of_vision
|
|||
|
|
"""
|
|||
|
|
def __init__(self, char, *pins, active_high=True, initial_value=None,
|
|||
|
|
pin_factory=None):
|
|||
|
|
if not isinstance(char, LEDCharDisplay):
|
|||
|
|
raise ValueError('char must be an LEDCharDisplay')
|
|||
|
|
if initial_value is None:
|
|||
|
|
initial_value = (char.value,) * len(pins)
|
|||
|
|
if pin_factory is None:
|
|||
|
|
pin_factory = char.pin_factory
|
|||
|
|
self._plex_thread = None
|
|||
|
|
self._plex_delay = 0.005
|
|||
|
|
plex = CompositeOutputDevice(*(
|
|||
|
|
OutputDevice(
|
|||
|
|
pin, active_high=active_high, initial_value=None,
|
|||
|
|
pin_factory=pin_factory)
|
|||
|
|
for pin in pins
|
|||
|
|
))
|
|||
|
|
super().__init__(
|
|||
|
|
plex=plex, char=char, pin_factory=pin_factory)
|
|||
|
|
self.value = initial_value
|
|||
|
|
|
|||
|
|
def close(self):
|
|||
|
|
try:
|
|||
|
|
self._stop_plex()
|
|||
|
|
except AttributeError:
|
|||
|
|
pass
|
|||
|
|
super().close()
|
|||
|
|
|
|||
|
|
def _stop_plex(self):
|
|||
|
|
if self._plex_thread:
|
|||
|
|
self._plex_thread.stop()
|
|||
|
|
self._plex_thread = None
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def plex_delay(self):
|
|||
|
|
"""
|
|||
|
|
The delay (measured in seconds) in the loop used to switch each
|
|||
|
|
character in the multiplexed display on. Defaults to 0.005 seconds
|
|||
|
|
which is generally sufficient to provide a "stable" (non-flickery)
|
|||
|
|
display.
|
|||
|
|
"""
|
|||
|
|
return self._plex_delay
|
|||
|
|
|
|||
|
|
@plex_delay.setter
|
|||
|
|
def plex_delay(self, value):
|
|||
|
|
if value < 0:
|
|||
|
|
raise BadWaitTime('plex_delay must be 0 or greater')
|
|||
|
|
self._plex_delay = float(value)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def value(self):
|
|||
|
|
"""
|
|||
|
|
The sequence of values to display.
|
|||
|
|
|
|||
|
|
This can be any sequence containing keys from the
|
|||
|
|
:attr:`~LEDCharDisplay.font` of the associated character display. For
|
|||
|
|
example, if the value consists only of single-character strings, it's
|
|||
|
|
valid to assign a string to this property (as a string is simply a
|
|||
|
|
sequence of individual character keys)::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDCharDisplay, LEDMultiCharDisplay
|
|||
|
|
|
|||
|
|
c = LEDCharDisplay(4, 5, 6, 7, 8, 9, 10)
|
|||
|
|
d = LEDMultiCharDisplay(c, 19, 20, 21, 22)
|
|||
|
|
d.value = 'LEDS'
|
|||
|
|
|
|||
|
|
However, things get more complicated if a decimal point is in use as
|
|||
|
|
then this class needs to know explicitly where to break the value for
|
|||
|
|
use on each character of the display. This can be handled by simply
|
|||
|
|
assigning a sequence of strings thus::
|
|||
|
|
|
|||
|
|
from gpiozero import LEDCharDisplay, LEDMultiCharDisplay
|
|||
|
|
|
|||
|
|
c = LEDCharDisplay(4, 5, 6, 7, 8, 9, 10)
|
|||
|
|
d = LEDMultiCharDisplay(c, 19, 20, 21, 22)
|
|||
|
|
d.value = ('L.', 'E', 'D', 'S')
|
|||
|
|
|
|||
|
|
This is how the value will always be represented when queried (as a
|
|||
|
|
tuple of individual values) as it neatly handles dealing with
|
|||
|
|
heterogeneous types and the aforementioned decimal point issue.
|
|||
|
|
|
|||
|
|
.. note::
|
|||
|
|
|
|||
|
|
The value also controls whether a background thread is in use to
|
|||
|
|
multiplex the display. When all positions in the value are equal
|
|||
|
|
the background thread is disabled and all characters are
|
|||
|
|
simultaneously enabled.
|
|||
|
|
"""
|
|||
|
|
return self._value
|
|||
|
|
|
|||
|
|
@value.setter
|
|||
|
|
def value(self, value):
|
|||
|
|
if len(value) > len(self.plex):
|
|||
|
|
raise ValueError(
|
|||
|
|
'length of value must not exceed the number of characters in '
|
|||
|
|
'the display')
|
|||
|
|
elif len(value) < len(self.plex):
|
|||
|
|
# Right-align the short value on the display
|
|||
|
|
value = (' ',) * (len(self.plex) - len(value)) + tuple(value)
|
|||
|
|
else:
|
|||
|
|
value = tuple(value)
|
|||
|
|
|
|||
|
|
# Get the list of tuples of states that the character LEDs will pass
|
|||
|
|
# through. Prune any entirely blank state (which we can skip by never
|
|||
|
|
# activating the plex for them) but remember which plex index each
|
|||
|
|
# (non-blank) state is associated with
|
|||
|
|
states = {}
|
|||
|
|
for index, char in enumerate(value):
|
|||
|
|
state = self.char._parse_state(char)
|
|||
|
|
if any(state):
|
|||
|
|
states.setdefault(state, set()).add(index)
|
|||
|
|
# Calculate the transitions between states for an ordering of chars
|
|||
|
|
# based on activated LEDs. This a vague attempt at minimizing the
|
|||
|
|
# number of LEDs that need flipping between chars; to do this
|
|||
|
|
# "properly" is O(n!) which gets silly quickly so ... fudge it
|
|||
|
|
order = sorted(states)
|
|||
|
|
if len(order) > 1:
|
|||
|
|
transitions = [
|
|||
|
|
[(self.plex[index], 0) for index in states[old]] +
|
|||
|
|
[
|
|||
|
|
(led, new_value)
|
|||
|
|
for led, old_value, new_value in zip(self.char, old, new)
|
|||
|
|
if old_value ^ new_value
|
|||
|
|
] +
|
|||
|
|
[(self.plex[index], 1) for index in states[new]]
|
|||
|
|
for old, new in pairwise(order + [order[0]])
|
|||
|
|
]
|
|||
|
|
else:
|
|||
|
|
transitions = []
|
|||
|
|
|
|||
|
|
# Stop any current display thread and disable the display
|
|||
|
|
self._stop_plex()
|
|||
|
|
self.plex.off()
|
|||
|
|
|
|||
|
|
# If there's any characters to display, set the character LEDs to the
|
|||
|
|
# state of the first character in the display order. If there's
|
|||
|
|
# transitions to display, activate the plex thread; otherwise, just
|
|||
|
|
# switch on each plex with a char to display
|
|||
|
|
if order:
|
|||
|
|
for led, state in zip(self.char, order[0]):
|
|||
|
|
led.value = state
|
|||
|
|
if transitions:
|
|||
|
|
self._plex_thread = GPIOThread(self._show_chars, (transitions,))
|
|||
|
|
self._plex_thread.start()
|
|||
|
|
else:
|
|||
|
|
for index in states[order[0]]:
|
|||
|
|
self.plex[index].on()
|
|||
|
|
self._value = value
|
|||
|
|
|
|||
|
|
def _show_chars(self, transitions):
|
|||
|
|
for transition in cycle(transitions):
|
|||
|
|
for device, value in transition:
|
|||
|
|
device.value = value
|
|||
|
|
if self._plex_thread.stopping.wait(self._plex_delay):
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PiHutXmasTree(LEDBoard):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDBoard` for `The Pi Hut's Xmas board`_: a 3D Christmas
|
|||
|
|
tree board with 24 red LEDs and a white LED as a star on top.
|
|||
|
|
|
|||
|
|
The 24 red LEDs can be accessed through the attributes led0, led1, led2,
|
|||
|
|
and so on. The white star LED is accessed through the :attr:`star`
|
|||
|
|
attribute. Alternatively, as with all descendents of :class:`LEDBoard`,
|
|||
|
|
you can treat the instance as a sequence of LEDs (the first element is the
|
|||
|
|
:attr:`star`).
|
|||
|
|
|
|||
|
|
The Xmas Tree board pins are fixed and therefore there's no need to specify
|
|||
|
|
them when constructing this class. The following example turns all the LEDs
|
|||
|
|
on one at a time::
|
|||
|
|
|
|||
|
|
from gpiozero import PiHutXmasTree
|
|||
|
|
from time import sleep
|
|||
|
|
|
|||
|
|
tree = PiHutXmasTree()
|
|||
|
|
|
|||
|
|
for light in tree:
|
|||
|
|
light.on()
|
|||
|
|
sleep(1)
|
|||
|
|
|
|||
|
|
The following example turns the star LED on and sets all the red LEDs to
|
|||
|
|
flicker randomly::
|
|||
|
|
|
|||
|
|
from gpiozero import PiHutXmasTree
|
|||
|
|
from gpiozero.tools import random_values
|
|||
|
|
from signal import pause
|
|||
|
|
|
|||
|
|
tree = PiHutXmasTree(pwm=True)
|
|||
|
|
|
|||
|
|
tree.star.on()
|
|||
|
|
|
|||
|
|
for led in tree[1:]:
|
|||
|
|
led.source_delay = 0.1
|
|||
|
|
led.source = random_values()
|
|||
|
|
|
|||
|
|
pause()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances for each pin. If
|
|||
|
|
:data:`False` (the default), construct regular :class:`LED` instances.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _The Pi Hut's Xmas board: https://thepihut.com/xmas
|
|||
|
|
|
|||
|
|
.. attribute:: star
|
|||
|
|
|
|||
|
|
Returns the :class:`LED` or :class:`PWMLED` representing the white
|
|||
|
|
star on top of the tree.
|
|||
|
|
|
|||
|
|
.. attribute:: led0, led1, led2, ...
|
|||
|
|
|
|||
|
|
Returns the :class:`LED` or :class:`PWMLED` representing one of the red
|
|||
|
|
LEDs. There are actually 24 of these properties named led0, led1, and
|
|||
|
|
so on but for the sake of brevity we represent all 24 under this
|
|||
|
|
section.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=False, initial_value=False, pin_factory=None):
|
|||
|
|
pins_dict = OrderedDict(star=2)
|
|||
|
|
pins = (4, 15, 13, 21, 25, 8, 5, 10, 16, 17, 27, 26,
|
|||
|
|
24, 9, 12, 6, 20, 19, 14, 18, 11, 7, 23, 22)
|
|||
|
|
for i, pin in enumerate(pins):
|
|||
|
|
pins_dict[f'led{i + 1:d}'] = pin
|
|||
|
|
super().__init__(
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=pins_dict.keys(),
|
|||
|
|
pin_factory=pin_factory,
|
|||
|
|
**pins_dict
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LedBorg(RGBLED):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`RGBLED` for the `PiBorg LedBorg`_: an add-on board
|
|||
|
|
containing a very bright RGB LED.
|
|||
|
|
|
|||
|
|
The LedBorg pins are fixed and therefore there's no need to specify them
|
|||
|
|
when constructing this class. The following example turns the LedBorg
|
|||
|
|
purple::
|
|||
|
|
|
|||
|
|
from gpiozero import LedBorg
|
|||
|
|
|
|||
|
|
led = LedBorg()
|
|||
|
|
led.color = (1, 0, 1)
|
|||
|
|
|
|||
|
|
:type initial_value: ~colorzero.Color or tuple
|
|||
|
|
:param initial_value:
|
|||
|
|
The initial color for the LedBorg. Defaults to black ``(0, 0, 0)``.
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True` (the default), construct :class:`PWMLED` instances for
|
|||
|
|
each component of the LedBorg. If :data:`False`, construct regular
|
|||
|
|
:class:`LED` instances, which prevents smooth color graduations.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _PiBorg LedBorg: https://www.piborg.org/ledborg
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, *, initial_value=(0, 0, 0), pwm=True, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
red='BOARD11', green='BOARD13', blue='BOARD15',
|
|||
|
|
pwm=pwm, initial_value=initial_value, pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PiLiter(LEDBoard):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDBoard` for the `Ciseco Pi-LITEr`_: a strip of 8 very
|
|||
|
|
bright LEDs.
|
|||
|
|
|
|||
|
|
The Pi-LITEr pins are fixed and therefore there's no need to specify them
|
|||
|
|
when constructing this class. The following example turns on all the LEDs
|
|||
|
|
of the Pi-LITEr::
|
|||
|
|
|
|||
|
|
from gpiozero import PiLiter
|
|||
|
|
|
|||
|
|
lite = PiLiter()
|
|||
|
|
lite.on()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances for each pin. If
|
|||
|
|
:data:`False` (the default), construct regular :class:`LED` instances.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each LED will be left in whatever state the pin is found
|
|||
|
|
in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the each LED will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, *, pwm=False, initial_value=False, pin_factory=None):
|
|||
|
|
pins = ('BOARD7', 'BOARD11', 'BOARD13', 'BOARD12',
|
|||
|
|
'BOARD15', 'BOARD16', 'BOARD18', 'BOARD22')
|
|||
|
|
super().__init__(
|
|||
|
|
*pins, pwm=pwm, initial_value=initial_value, pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PiLiterBarGraph(LEDBarGraph):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDBarGraph` to treat the `Ciseco Pi-LITEr`_ as an
|
|||
|
|
8-segment bar graph.
|
|||
|
|
|
|||
|
|
The Pi-LITEr pins are fixed and therefore there's no need to specify them
|
|||
|
|
when constructing this class. The following example sets the graph value
|
|||
|
|
to 0.5::
|
|||
|
|
|
|||
|
|
from gpiozero import PiLiterBarGraph
|
|||
|
|
|
|||
|
|
graph = PiLiterBarGraph()
|
|||
|
|
graph.value = 0.5
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances for each pin. If
|
|||
|
|
:data:`False` (the default), construct regular :class:`LED` instances.
|
|||
|
|
|
|||
|
|
:param float initial_value:
|
|||
|
|
The initial :attr:`value` of the graph given as a float between -1 and
|
|||
|
|
+1. Defaults to ``0.0``.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, *, pwm=False, initial_value=0.0, pin_factory=None):
|
|||
|
|
pins = ('BOARD7', 'BOARD11', 'BOARD13', 'BOARD12',
|
|||
|
|
'BOARD15', 'BOARD16', 'BOARD18', 'BOARD22')
|
|||
|
|
super().__init__(
|
|||
|
|
*pins, pwm=pwm, initial_value=initial_value, pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TrafficLights(LEDBoard):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDBoard` for devices containing red, yellow, and green
|
|||
|
|
LEDs.
|
|||
|
|
|
|||
|
|
The following example initializes a device connected to GPIO pins 2, 3,
|
|||
|
|
and 4, then lights the amber (yellow) LED attached to GPIO 3::
|
|||
|
|
|
|||
|
|
from gpiozero import TrafficLights
|
|||
|
|
|
|||
|
|
traffic = TrafficLights(2, 3, 4)
|
|||
|
|
traffic.amber.on()
|
|||
|
|
|
|||
|
|
:type red: int or str
|
|||
|
|
:param red:
|
|||
|
|
The GPIO pin that the red LED is attached to. See :ref:`pin-numbering`
|
|||
|
|
for valid pin numbers.
|
|||
|
|
|
|||
|
|
:type amber: int or str or None
|
|||
|
|
:param amber:
|
|||
|
|
The GPIO pin that the amber LED is attached to. See
|
|||
|
|
:ref:`pin-numbering` for valid pin numbers.
|
|||
|
|
|
|||
|
|
:type yellow: int or str or None
|
|||
|
|
:param yellow:
|
|||
|
|
The GPIO pin that the yellow LED is attached to. This is merely an
|
|||
|
|
alias for the ``amber`` parameter; you can't specify both ``amber`` and
|
|||
|
|
``yellow``. See :ref:`pin-numbering` for valid pin numbers.
|
|||
|
|
|
|||
|
|
:type green: int or str
|
|||
|
|
:param green:
|
|||
|
|
The GPIO pin that the green LED is attached to. See
|
|||
|
|
:ref:`pin-numbering` for valid pin numbers.
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. attribute:: red
|
|||
|
|
|
|||
|
|
The red :class:`LED` or :class:`PWMLED`.
|
|||
|
|
|
|||
|
|
.. attribute:: amber
|
|||
|
|
|
|||
|
|
The amber :class:`LED` or :class:`PWMLED`. Note that this attribute
|
|||
|
|
will not be present when the instance is constructed with the
|
|||
|
|
*yellow* keyword parameter.
|
|||
|
|
|
|||
|
|
.. attribute:: yellow
|
|||
|
|
|
|||
|
|
The yellow :class:`LED` or :class:`PWMLED`. Note that this attribute
|
|||
|
|
will only be present when the instance is constructed with the
|
|||
|
|
*yellow* keyword parameter.
|
|||
|
|
|
|||
|
|
.. attribute:: green
|
|||
|
|
|
|||
|
|
The green :class:`LED` or :class:`PWMLED`.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, red=None, amber=None, green=None, *,
|
|||
|
|
pwm=False, initial_value=False, yellow=None,
|
|||
|
|
pin_factory=None):
|
|||
|
|
if amber is not None and yellow is not None:
|
|||
|
|
raise OutputDeviceBadValue(
|
|||
|
|
'Only one of amber or yellow can be specified')
|
|||
|
|
devices = OrderedDict((('red', red), ))
|
|||
|
|
self._display_yellow = amber is None and yellow is not None
|
|||
|
|
if self._display_yellow:
|
|||
|
|
devices['yellow'] = yellow
|
|||
|
|
else:
|
|||
|
|
devices['amber'] = amber
|
|||
|
|
devices['green'] = green
|
|||
|
|
if not all(p is not None for p in devices.values()):
|
|||
|
|
raise GPIOPinMissing(
|
|||
|
|
f'{", ".join(devices.keys())} pins must be provided')
|
|||
|
|
super().__init__(
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=devices.keys(), pin_factory=pin_factory,
|
|||
|
|
**devices)
|
|||
|
|
|
|||
|
|
def __getattr__(self, name):
|
|||
|
|
if name == 'amber' and self._display_yellow:
|
|||
|
|
name = 'yellow'
|
|||
|
|
elif name == 'yellow' and not self._display_yellow:
|
|||
|
|
name = 'amber'
|
|||
|
|
return super().__getattr__(name)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PiTraffic(TrafficLights):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`TrafficLights` for the `Low Voltage Labs PI-TRAFFIC`_
|
|||
|
|
vertical traffic lights board when attached to GPIO pins 9, 10, and 11.
|
|||
|
|
|
|||
|
|
There's no need to specify the pins if the PI-TRAFFIC is connected to the
|
|||
|
|
default pins (9, 10, 11). The following example turns on the amber LED on
|
|||
|
|
the PI-TRAFFIC::
|
|||
|
|
|
|||
|
|
from gpiozero import PiTraffic
|
|||
|
|
|
|||
|
|
traffic = PiTraffic()
|
|||
|
|
traffic.amber.on()
|
|||
|
|
|
|||
|
|
To use the PI-TRAFFIC board when attached to a non-standard set of pins,
|
|||
|
|
simply use the parent class, :class:`TrafficLights`.
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param bool initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Low Voltage Labs PI-TRAFFIC: http://lowvoltagelabs.com/products/pi-traffic/
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=False, initial_value=False, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
'BOARD21', 'BOARD19', 'BOARD23',
|
|||
|
|
pwm=pwm, initial_value=initial_value, pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PiStop(TrafficLights):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`TrafficLights` for the `PiHardware Pi-Stop`_: a vertical
|
|||
|
|
traffic lights board.
|
|||
|
|
|
|||
|
|
The following example turns on the amber LED on a Pi-Stop connected to
|
|||
|
|
location ``A+``::
|
|||
|
|
|
|||
|
|
from gpiozero import PiStop
|
|||
|
|
|
|||
|
|
traffic = PiStop('A+')
|
|||
|
|
traffic.amber.on()
|
|||
|
|
|
|||
|
|
:param str location:
|
|||
|
|
The `location`_ on the GPIO header to which the Pi-Stop is connected.
|
|||
|
|
Must be one of: ``A``, ``A+``, ``B``, ``B+``, ``C``, ``D``.
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param bool initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _PiHardware Pi-Stop: https://pihw.wordpress.com/meltwaters-pi-hardware-kits/pi-stop/
|
|||
|
|
.. _location: https://github.com/PiHw/Pi-Stop/blob/master/markdown_source/markdown/Discover-PiStop.md
|
|||
|
|
"""
|
|||
|
|
LOCATIONS = {
|
|||
|
|
'A': ('BOARD26', 'BOARD24', 'BOARD22'),
|
|||
|
|
'A+': ('BOARD40', 'BOARD38', 'BOARD36'),
|
|||
|
|
'B': ('BOARD19', 'BOARD21', 'BOARD23'),
|
|||
|
|
'B+': ('BOARD33', 'BOARD35', 'BOARD37'),
|
|||
|
|
'C': ('BOARD12', 'BOARD10', 'BOARD8'),
|
|||
|
|
'D': ('BOARD3', 'BOARD5', 'BOARD7'),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def __init__(
|
|||
|
|
self, location=None, *, pwm=False, initial_value=False,
|
|||
|
|
pin_factory=None):
|
|||
|
|
gpios = self.LOCATIONS.get(location, None)
|
|||
|
|
if gpios is None:
|
|||
|
|
locations = ', '.join(sorted(self.LOCATIONS.keys()))
|
|||
|
|
raise ValueError(f'location must be one of: {locations}')
|
|||
|
|
super().__init__(
|
|||
|
|
*gpios, pwm=pwm, initial_value=initial_value,
|
|||
|
|
pin_factory=pin_factory)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class StatusZero(LEDBoard):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDBoard` for The Pi Hut's `STATUS Zero`_: a Pi Zero sized
|
|||
|
|
add-on board with three sets of red/green LEDs to provide a status
|
|||
|
|
indicator.
|
|||
|
|
|
|||
|
|
The following example designates the first strip the label "wifi" and the
|
|||
|
|
second "raining", and turns them green and red respectfully::
|
|||
|
|
|
|||
|
|
from gpiozero import StatusZero
|
|||
|
|
|
|||
|
|
status = StatusZero('wifi', 'raining')
|
|||
|
|
status.wifi.green.on()
|
|||
|
|
status.raining.red.on()
|
|||
|
|
|
|||
|
|
Each designated label will contain two :class:`LED` objects named "red"
|
|||
|
|
and "green".
|
|||
|
|
|
|||
|
|
:param str \\*labels:
|
|||
|
|
Specify the names of the labels you wish to designate the strips to.
|
|||
|
|
You can list up to three labels. If no labels are given, three strips
|
|||
|
|
will be initialised with names 'one', 'two', and 'three'. If some, but
|
|||
|
|
not all strips are given labels, any remaining strips will not be
|
|||
|
|
initialised.
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param bool initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _STATUS Zero: https://thepihut.com/statuszero
|
|||
|
|
|
|||
|
|
.. attribute:: your-label-here, your-label-here, ...
|
|||
|
|
|
|||
|
|
This entry represents one of the three labelled attributes supported on
|
|||
|
|
the STATUS Zero board. It is an :class:`LEDBoard` which contains:
|
|||
|
|
|
|||
|
|
.. attribute:: red
|
|||
|
|
|
|||
|
|
The :class:`LED` or :class:`PWMLED` representing the red LED
|
|||
|
|
next to the label.
|
|||
|
|
|
|||
|
|
.. attribute:: green
|
|||
|
|
|
|||
|
|
The :class:`LED` or :class:`PWMLED` representing the green LED
|
|||
|
|
next to the label.
|
|||
|
|
"""
|
|||
|
|
default_labels = ('one', 'two', 'three')
|
|||
|
|
|
|||
|
|
def __init__(self, *labels, pwm=False, initial_value=False,
|
|||
|
|
pin_factory=None):
|
|||
|
|
pins = (
|
|||
|
|
('BOARD11', 'BOARD7'),
|
|||
|
|
('BOARD15', 'BOARD13'),
|
|||
|
|
('BOARD21', 'BOARD19'),
|
|||
|
|
)
|
|||
|
|
if len(labels) == 0:
|
|||
|
|
labels = self.default_labels
|
|||
|
|
elif len(labels) > len(pins):
|
|||
|
|
raise ValueError(
|
|||
|
|
f"StatusZero doesn't support more than {len(pins)} labels")
|
|||
|
|
dup, count = Counter(labels).most_common(1)[0]
|
|||
|
|
if count > 1:
|
|||
|
|
raise ValueError(f"Duplicate label {dup}")
|
|||
|
|
super().__init__(
|
|||
|
|
_order=labels, pin_factory=pin_factory, **{
|
|||
|
|
label: LEDBoard(
|
|||
|
|
red=red, green=green, _order=('red', 'green'),
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
pin_factory=pin_factory)
|
|||
|
|
for (green, red), label in zip(pins, labels)
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class StatusBoard(CompositeOutputDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeOutputDevice` for The Pi Hut's `STATUS`_ board: a
|
|||
|
|
HAT sized add-on board with five sets of red/green LEDs and buttons to
|
|||
|
|
provide a status indicator with additional input.
|
|||
|
|
|
|||
|
|
The following example designates the first strip the label "wifi" and the
|
|||
|
|
second "raining", turns the wifi green and then activates the button to
|
|||
|
|
toggle its lights when pressed::
|
|||
|
|
|
|||
|
|
from gpiozero import StatusBoard
|
|||
|
|
|
|||
|
|
status = StatusBoard('wifi', 'raining')
|
|||
|
|
status.wifi.lights.green.on()
|
|||
|
|
status.wifi.button.when_pressed = status.wifi.lights.toggle
|
|||
|
|
|
|||
|
|
Each designated label will contain a "lights" :class:`LEDBoard` containing
|
|||
|
|
two :class:`LED` objects named "red" and "green", and a :class:`Button`
|
|||
|
|
object named "button".
|
|||
|
|
|
|||
|
|
:param str \\*labels:
|
|||
|
|
Specify the names of the labels you wish to designate the strips to.
|
|||
|
|
You can list up to five labels. If no labels are given, five strips
|
|||
|
|
will be initialised with names 'one' to 'five'. If some, but not all
|
|||
|
|
strips are given labels, any remaining strips will not be initialised.
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param bool initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _STATUS: https://thepihut.com/status
|
|||
|
|
|
|||
|
|
.. attribute:: your-label-here, your-label-here, ...
|
|||
|
|
|
|||
|
|
This entry represents one of the five labelled attributes supported on
|
|||
|
|
the STATUS board. It is an :class:`CompositeOutputDevice` which
|
|||
|
|
contains:
|
|||
|
|
|
|||
|
|
.. attribute:: lights
|
|||
|
|
|
|||
|
|
A :class:`LEDBoard` representing the lights next to the label. It
|
|||
|
|
contains:
|
|||
|
|
|
|||
|
|
.. attribute:: red
|
|||
|
|
|
|||
|
|
The :class:`LED` or :class:`PWMLED` representing the red LED
|
|||
|
|
next to the label.
|
|||
|
|
|
|||
|
|
.. attribute:: green
|
|||
|
|
|
|||
|
|
The :class:`LED` or :class:`PWMLED` representing the green LED
|
|||
|
|
next to the label.
|
|||
|
|
|
|||
|
|
.. attribute:: button
|
|||
|
|
|
|||
|
|
A :class:`Button` representing the button next to the label.
|
|||
|
|
"""
|
|||
|
|
default_labels = ('one', 'two', 'three', 'four', 'five')
|
|||
|
|
|
|||
|
|
def __init__(self, *labels, pwm=False, initial_value=False,
|
|||
|
|
pin_factory=None):
|
|||
|
|
pins = (
|
|||
|
|
('BOARD11', 'BOARD7', 'BOARD8'),
|
|||
|
|
('BOARD15', 'BOARD13', 'BOARD35'),
|
|||
|
|
('BOARD21', 'BOARD19', 'BOARD10'),
|
|||
|
|
('BOARD29', 'BOARD23', 'BOARD37'),
|
|||
|
|
('BOARD33', 'BOARD31', 'BOARD12'),
|
|||
|
|
)
|
|||
|
|
if len(labels) == 0:
|
|||
|
|
labels = self.default_labels
|
|||
|
|
elif len(labels) > len(pins):
|
|||
|
|
raise ValueError("StatusBoard doesn't support more than five labels")
|
|||
|
|
dup, count = Counter(labels).most_common(1)[0]
|
|||
|
|
if count > 1:
|
|||
|
|
raise ValueError(f"Duplicate label {dup}")
|
|||
|
|
super().__init__(
|
|||
|
|
_order=labels, pin_factory=pin_factory, **{
|
|||
|
|
label: CompositeOutputDevice(
|
|||
|
|
button=Button(button, pin_factory=pin_factory),
|
|||
|
|
lights=LEDBoard(
|
|||
|
|
red=red, green=green, _order=('red', 'green'),
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
pin_factory=pin_factory),
|
|||
|
|
_order=('button', 'lights'), pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
for (green, red, button), label in zip(pins, labels)
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class SnowPi(LEDBoard):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDBoard` for the `Ryanteck SnowPi`_ board.
|
|||
|
|
|
|||
|
|
The SnowPi pins are fixed and therefore there's no need to specify them
|
|||
|
|
when constructing this class. The following example turns on the eyes, sets
|
|||
|
|
the nose pulsing, and the arms blinking::
|
|||
|
|
|
|||
|
|
from gpiozero import SnowPi
|
|||
|
|
|
|||
|
|
snowman = SnowPi(pwm=True)
|
|||
|
|
snowman.eyes.on()
|
|||
|
|
snowman.nose.pulse()
|
|||
|
|
snowman.arms.blink()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param bool initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Ryanteck SnowPi: https://ryanteck.uk/raspberry-pi/114-snowpi-the-gpio-snowman-for-raspberry-pi-0635648608303.html
|
|||
|
|
|
|||
|
|
.. attribute:: arms
|
|||
|
|
|
|||
|
|
A :class:`LEDBoard` representing the arms of the snow man. It contains
|
|||
|
|
the following attributes:
|
|||
|
|
|
|||
|
|
.. attribute:: left, right
|
|||
|
|
|
|||
|
|
Two :class:`LEDBoard` objects representing the left and right arms
|
|||
|
|
of the snow-man. They contain:
|
|||
|
|
|
|||
|
|
.. attribute:: top, middle, bottom
|
|||
|
|
|
|||
|
|
The :class:`LED` or :class:`PWMLED` down the snow-man's arms.
|
|||
|
|
|
|||
|
|
.. attribute:: eyes
|
|||
|
|
|
|||
|
|
A :class:`LEDBoard` representing the eyes of the snow-man. It contains:
|
|||
|
|
|
|||
|
|
.. attribute:: left, right
|
|||
|
|
|
|||
|
|
The :class:`LED` or :class:`PWMLED` for the snow-man's eyes.
|
|||
|
|
|
|||
|
|
.. attribute:: nose
|
|||
|
|
|
|||
|
|
The :class:`LED` or :class:`PWMLED` for the snow-man's nose.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=False, initial_value=False, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
arms=LEDBoard(
|
|||
|
|
left=LEDBoard(
|
|||
|
|
top='BOARD11', middle='BOARD12', bottom='BOARD15',
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=('top', 'middle', 'bottom'),
|
|||
|
|
pin_factory=pin_factory),
|
|||
|
|
right=LEDBoard(
|
|||
|
|
top='BOARD26', middle='BOARD24', bottom='BOARD21',
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=('top', 'middle', 'bottom'),
|
|||
|
|
pin_factory=pin_factory),
|
|||
|
|
_order=('left', 'right'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
),
|
|||
|
|
eyes=LEDBoard(
|
|||
|
|
left='BOARD16', right='BOARD18',
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=('left', 'right'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
),
|
|||
|
|
nose='BOARD22',
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=('eyes', 'nose', 'arms'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TrafficLightsBuzzer(CompositeOutputDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeOutputDevice` and is a generic class for HATs with
|
|||
|
|
traffic lights, a button and a buzzer.
|
|||
|
|
|
|||
|
|
:param TrafficLights lights:
|
|||
|
|
An instance of :class:`TrafficLights` representing the traffic lights
|
|||
|
|
of the HAT.
|
|||
|
|
|
|||
|
|
:param Buzzer buzzer:
|
|||
|
|
An instance of :class:`Buzzer` representing the buzzer on the HAT.
|
|||
|
|
|
|||
|
|
:param Button button:
|
|||
|
|
An instance of :class:`Button` representing the button on the HAT.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. attribute:: lights
|
|||
|
|
|
|||
|
|
The :class:`TrafficLights` instance passed as the *lights* parameter.
|
|||
|
|
|
|||
|
|
.. attribute:: buzzer
|
|||
|
|
|
|||
|
|
The :class:`Buzzer` instance passed as the *buzzer* parameter.
|
|||
|
|
|
|||
|
|
.. attribute:: button
|
|||
|
|
|
|||
|
|
The :class:`Button` instance passed as the *button* parameter.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, lights, buzzer, button, *, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
lights=lights, buzzer=buzzer, button=button,
|
|||
|
|
_order=('lights', 'buzzer', 'button'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class FishDish(CompositeOutputDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeOutputDevice` for the `Pi Supply FishDish`_: traffic
|
|||
|
|
light LEDs, a button and a buzzer.
|
|||
|
|
|
|||
|
|
The FishDish pins are fixed and therefore there's no need to specify them
|
|||
|
|
when constructing this class. The following example waits for the button
|
|||
|
|
to be pressed on the FishDish, then turns on all the LEDs::
|
|||
|
|
|
|||
|
|
from gpiozero import FishDish
|
|||
|
|
|
|||
|
|
fish = FishDish()
|
|||
|
|
fish.button.wait_for_press()
|
|||
|
|
fish.lights.on()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Pi Supply FishDish: https://www.pi-supply.com/product/fish-dish-raspberry-pi-led-buzzer-board/
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=False, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
lights=TrafficLights(
|
|||
|
|
'BOARD21', 'BOARD15', 'BOARD7', pwm=pwm, pin_factory=pin_factory
|
|||
|
|
),
|
|||
|
|
buzzer=Buzzer('BOARD24', pin_factory=pin_factory),
|
|||
|
|
button=Button('BOARD26', pull_up=False, pin_factory=pin_factory),
|
|||
|
|
_order=('lights', 'buzzer', 'button'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TrafficHat(CompositeOutputDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeOutputDevice` for the `Pi Supply Traffic HAT`_: a
|
|||
|
|
board with traffic light LEDs, a button and a buzzer.
|
|||
|
|
|
|||
|
|
The Traffic HAT pins are fixed and therefore there's no need to specify
|
|||
|
|
them when constructing this class. The following example waits for the
|
|||
|
|
button to be pressed on the Traffic HAT, then turns on all the LEDs::
|
|||
|
|
|
|||
|
|
from gpiozero import TrafficHat
|
|||
|
|
|
|||
|
|
hat = TrafficHat()
|
|||
|
|
hat.button.wait_for_press()
|
|||
|
|
hat.lights.on()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Pi Supply Traffic HAT: https://uk.pi-supply.com/products/traffic-hat-for-raspberry-pi
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=False, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
lights=TrafficLights(
|
|||
|
|
'BOARD18', 'BOARD16', 'BOARD15',
|
|||
|
|
pwm=pwm, pin_factory=pin_factory
|
|||
|
|
),
|
|||
|
|
buzzer=Buzzer('BOARD29', pin_factory=pin_factory),
|
|||
|
|
button=Button('BOARD22', pin_factory=pin_factory),
|
|||
|
|
_order=('lights', 'buzzer', 'button'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TrafficpHat(TrafficLights):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`TrafficLights` for the `Pi Supply Traffic pHAT`_: a small
|
|||
|
|
board with traffic light LEDs.
|
|||
|
|
|
|||
|
|
The Traffic pHAT pins are fixed and therefore there's no need to specify
|
|||
|
|
them when constructing this class. The following example then turns on all
|
|||
|
|
the LEDs::
|
|||
|
|
|
|||
|
|
from gpiozero import TrafficpHat
|
|||
|
|
phat = TrafficpHat()
|
|||
|
|
phat.red.on()
|
|||
|
|
phat.blink()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Pi Supply Traffic pHAT: http://pisupp.ly/trafficphat
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=False, initial_value=False, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
red='BOARD22', amber='BOARD18', green='BOARD16',
|
|||
|
|
pwm=pwm, initial_value=initial_value, pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def PhaseEnableRobot(left=None, right=None, pwm=True, pin_factory=None, *args):
|
|||
|
|
"""
|
|||
|
|
Deprecated alias of :class:`Robot`. The :class:`Robot` class can now be
|
|||
|
|
constructed directly with :class:`Motor` or :class:`PhaseEnableMotor`
|
|||
|
|
classes.
|
|||
|
|
"""
|
|||
|
|
warnings.warn(
|
|||
|
|
DeprecationWarning(
|
|||
|
|
"PhaseEnableRobot is deprecated; please construct Robot directly "
|
|||
|
|
"with PhaseEnableMotor instances"))
|
|||
|
|
return Robot(
|
|||
|
|
PhaseEnableMotor(*left),
|
|||
|
|
PhaseEnableMotor(*right),
|
|||
|
|
pwm=pwm, pin_factory=pin_factory,
|
|||
|
|
*args)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Robot(SourceMixin, CompositeDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeDevice` to represent a generic dual-motor robot.
|
|||
|
|
|
|||
|
|
This class is constructed with two motor instances representing the left
|
|||
|
|
and right wheels of the robot respectively. For example, if the left
|
|||
|
|
motor's controller is connected to GPIOs 4 and 14, while the right motor's
|
|||
|
|
controller is connected to GPIOs 17 and 18 then the following example will
|
|||
|
|
drive the robot forward::
|
|||
|
|
|
|||
|
|
from gpiozero import Robot
|
|||
|
|
|
|||
|
|
robot = Robot(left=Motor(4, 14), right=Motor(17, 18))
|
|||
|
|
robot.forward()
|
|||
|
|
|
|||
|
|
:type left: Motor or PhaseEnableMotor
|
|||
|
|
:param left:
|
|||
|
|
A :class:`~gpiozero.Motor` or a :class:`~gpiozero.PhaseEnableMotor`
|
|||
|
|
for the left wheel of the robot.
|
|||
|
|
|
|||
|
|
:type right: Motor or PhaseEnableMotor
|
|||
|
|
:param right:
|
|||
|
|
A :class:`~gpiozero.Motor` or a :class:`~gpiozero.PhaseEnableMotor`
|
|||
|
|
for the right wheel of the robot.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. attribute:: left_motor
|
|||
|
|
|
|||
|
|
The :class:`Motor` on the left of the robot.
|
|||
|
|
|
|||
|
|
.. attribute:: right_motor
|
|||
|
|
|
|||
|
|
The :class:`Motor` on the right of the robot.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, left, right, *, pin_factory=None):
|
|||
|
|
if not isinstance(left, (Motor, PhaseEnableMotor)):
|
|||
|
|
if isinstance(left, tuple):
|
|||
|
|
warnings.warn(
|
|||
|
|
DeprecationWarning(
|
|||
|
|
"Passing a tuple as the left parameter of the Robot "
|
|||
|
|
"constructor is deprecated; please pass a Motor or "
|
|||
|
|
"PhaseEnableMotor instance instead"))
|
|||
|
|
left = Motor(*left, pin_factory=pin_factory)
|
|||
|
|
else:
|
|||
|
|
raise GPIOPinMissing('left must be a Motor or PhaseEnableMotor')
|
|||
|
|
if not isinstance(right, (Motor, PhaseEnableMotor)):
|
|||
|
|
if isinstance(right, tuple):
|
|||
|
|
warnings.warn(
|
|||
|
|
DeprecationWarning(
|
|||
|
|
"Passing a tuple as the right parameter of the Robot "
|
|||
|
|
"constructor is deprecated; please pass a Motor or "
|
|||
|
|
"PhaseEnableMotor instance instead"))
|
|||
|
|
right = Motor(*right, pin_factory=pin_factory)
|
|||
|
|
else:
|
|||
|
|
raise GPIOPinMissing('right must be a Motor or PhaseEnableMotor')
|
|||
|
|
super().__init__(left_motor=left, right_motor=right,
|
|||
|
|
_order=('left_motor', 'right_motor'),
|
|||
|
|
pin_factory=pin_factory)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def value(self):
|
|||
|
|
"""
|
|||
|
|
Represents the motion of the robot as a tuple of (left_motor_speed,
|
|||
|
|
right_motor_speed) with ``(-1, -1)`` representing full speed backwards,
|
|||
|
|
``(1, 1)`` representing full speed forwards, and ``(0, 0)``
|
|||
|
|
representing stopped.
|
|||
|
|
"""
|
|||
|
|
return super().value
|
|||
|
|
|
|||
|
|
@value.setter
|
|||
|
|
def value(self, value):
|
|||
|
|
self.left_motor.value, self.right_motor.value = value
|
|||
|
|
|
|||
|
|
def forward(self, speed=1, *, curve_left=0, curve_right=0):
|
|||
|
|
"""
|
|||
|
|
Drive the robot forward by running both motors forward.
|
|||
|
|
|
|||
|
|
:param float speed:
|
|||
|
|
Speed at which to drive the motors, as a value between 0 (stopped)
|
|||
|
|
and 1 (full speed). The default is 1.
|
|||
|
|
|
|||
|
|
:param float curve_left:
|
|||
|
|
The amount to curve left while moving forwards, by driving the
|
|||
|
|
left motor at a slower speed. Maximum *curve_left* is 1, the
|
|||
|
|
default is 0 (no curve). This parameter can only be specified as a
|
|||
|
|
keyword parameter, and is mutually exclusive with *curve_right*.
|
|||
|
|
|
|||
|
|
:param float curve_right:
|
|||
|
|
The amount to curve right while moving forwards, by driving the
|
|||
|
|
right motor at a slower speed. Maximum *curve_right* is 1, the
|
|||
|
|
default is 0 (no curve). This parameter can only be specified as a
|
|||
|
|
keyword parameter, and is mutually exclusive with *curve_left*.
|
|||
|
|
"""
|
|||
|
|
if not 0 <= curve_left <= 1:
|
|||
|
|
raise ValueError('curve_left must be between 0 and 1')
|
|||
|
|
if not 0 <= curve_right <= 1:
|
|||
|
|
raise ValueError('curve_right must be between 0 and 1')
|
|||
|
|
if curve_left != 0 and curve_right != 0:
|
|||
|
|
raise ValueError("curve_left and curve_right can't be used at "
|
|||
|
|
"the same time")
|
|||
|
|
self.left_motor.forward(speed * (1 - curve_left))
|
|||
|
|
self.right_motor.forward(speed * (1 - curve_right))
|
|||
|
|
|
|||
|
|
def backward(self, speed=1, *, curve_left=0, curve_right=0):
|
|||
|
|
"""
|
|||
|
|
Drive the robot backward by running both motors backward.
|
|||
|
|
|
|||
|
|
:param float speed:
|
|||
|
|
Speed at which to drive the motors, as a value between 0 (stopped)
|
|||
|
|
and 1 (full speed). The default is 1.
|
|||
|
|
|
|||
|
|
:param float curve_left:
|
|||
|
|
The amount to curve left while moving backwards, by driving the
|
|||
|
|
left motor at a slower speed. Maximum *curve_left* is 1, the
|
|||
|
|
default is 0 (no curve). This parameter can only be specified as a
|
|||
|
|
keyword parameter, and is mutually exclusive with *curve_right*.
|
|||
|
|
|
|||
|
|
:param float curve_right:
|
|||
|
|
The amount to curve right while moving backwards, by driving the
|
|||
|
|
right motor at a slower speed. Maximum *curve_right* is 1, the
|
|||
|
|
default is 0 (no curve). This parameter can only be specified as a
|
|||
|
|
keyword parameter, and is mutually exclusive with *curve_left*.
|
|||
|
|
"""
|
|||
|
|
if not 0 <= curve_left <= 1:
|
|||
|
|
raise ValueError('curve_left must be between 0 and 1')
|
|||
|
|
if not 0 <= curve_right <= 1:
|
|||
|
|
raise ValueError('curve_right must be between 0 and 1')
|
|||
|
|
if curve_left != 0 and curve_right != 0:
|
|||
|
|
raise ValueError("curve_left and curve_right can't be used at "
|
|||
|
|
"the same time")
|
|||
|
|
self.left_motor.backward(speed * (1 - curve_left))
|
|||
|
|
self.right_motor.backward(speed * (1 - curve_right))
|
|||
|
|
|
|||
|
|
def left(self, speed=1):
|
|||
|
|
"""
|
|||
|
|
Make the robot turn left by running the right motor forward and left
|
|||
|
|
motor backward.
|
|||
|
|
|
|||
|
|
:param float speed:
|
|||
|
|
Speed at which to drive the motors, as a value between 0 (stopped)
|
|||
|
|
and 1 (full speed). The default is 1.
|
|||
|
|
"""
|
|||
|
|
self.right_motor.forward(speed)
|
|||
|
|
self.left_motor.backward(speed)
|
|||
|
|
|
|||
|
|
def right(self, speed=1):
|
|||
|
|
"""
|
|||
|
|
Make the robot turn right by running the left motor forward and right
|
|||
|
|
motor backward.
|
|||
|
|
|
|||
|
|
:param float speed:
|
|||
|
|
Speed at which to drive the motors, as a value between 0 (stopped)
|
|||
|
|
and 1 (full speed). The default is 1.
|
|||
|
|
"""
|
|||
|
|
self.left_motor.forward(speed)
|
|||
|
|
self.right_motor.backward(speed)
|
|||
|
|
|
|||
|
|
def reverse(self):
|
|||
|
|
"""
|
|||
|
|
Reverse the robot's current motor directions. If the robot is currently
|
|||
|
|
running full speed forward, it will run full speed backward. If the
|
|||
|
|
robot is turning left at half-speed, it will turn right at half-speed.
|
|||
|
|
If the robot is currently stopped it will remain stopped.
|
|||
|
|
"""
|
|||
|
|
self.left_motor.reverse()
|
|||
|
|
self.right_motor.reverse()
|
|||
|
|
|
|||
|
|
def stop(self):
|
|||
|
|
"""
|
|||
|
|
Stop the robot.
|
|||
|
|
"""
|
|||
|
|
self.left_motor.stop()
|
|||
|
|
self.right_motor.stop()
|
|||
|
|
|
|||
|
|
|
|||
|
|
class RyanteckRobot(Robot):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`Robot` for the `Ryanteck motor controller board`_.
|
|||
|
|
|
|||
|
|
The Ryanteck MCB pins are fixed and therefore there's no need to specify
|
|||
|
|
them when constructing this class. The following example drives the robot
|
|||
|
|
forward::
|
|||
|
|
|
|||
|
|
from gpiozero import RyanteckRobot
|
|||
|
|
|
|||
|
|
robot = RyanteckRobot()
|
|||
|
|
robot.forward()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True` (the default), construct :class:`PWMOutputDevice`
|
|||
|
|
instances for the motor controller pins, allowing both direction and
|
|||
|
|
variable speed control. If :data:`False`, construct
|
|||
|
|
:class:`DigitalOutputDevice` instances, allowing only direction
|
|||
|
|
control.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Ryanteck motor controller board: https://uk.pi-supply.com/products/ryanteck-rtk-000-001-motor-controller-board-kit-raspberry-pi
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, *, pwm=True, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
left=Motor('BOARD11', 'BOARD12', pwm=pwm, pin_factory=pin_factory),
|
|||
|
|
right=Motor('BOARD15', 'BOARD16', pwm=pwm, pin_factory=pin_factory),
|
|||
|
|
pin_factory=pin_factory)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class CamJamKitRobot(Robot):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`Robot` for the `CamJam #3 EduKit`_ motor controller board.
|
|||
|
|
|
|||
|
|
The CamJam robot controller pins are fixed and therefore there's no need
|
|||
|
|
to specify them when constructing this class. The following example drives
|
|||
|
|
the robot forward::
|
|||
|
|
|
|||
|
|
from gpiozero import CamJamKitRobot
|
|||
|
|
|
|||
|
|
robot = CamJamKitRobot()
|
|||
|
|
robot.forward()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True` (the default), construct :class:`PWMOutputDevice`
|
|||
|
|
instances for the motor controller pins, allowing both direction and
|
|||
|
|
variable speed control. If :data:`False`, construct
|
|||
|
|
:class:`DigitalOutputDevice` instances, allowing only direction
|
|||
|
|
control.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _CamJam #3 EduKit: http://camjam.me/?page_id=1035
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=True, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
left=Motor('BOARD21', 'BOARD19', pwm=pwm, pin_factory=pin_factory),
|
|||
|
|
right=Motor('BOARD26', 'BOARD24', pwm=pwm, pin_factory=pin_factory),
|
|||
|
|
pin_factory=pin_factory)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PololuDRV8835Robot(Robot):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`Robot` for the `Pololu DRV8835 Dual Motor Driver Kit`_.
|
|||
|
|
|
|||
|
|
The Pololu DRV8835 pins are fixed and therefore there's no need to specify
|
|||
|
|
them when constructing this class. The following example drives the robot
|
|||
|
|
forward::
|
|||
|
|
|
|||
|
|
from gpiozero import PololuDRV8835Robot
|
|||
|
|
|
|||
|
|
robot = PololuDRV8835Robot()
|
|||
|
|
robot.forward()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True` (the default), construct :class:`PWMOutputDevice`
|
|||
|
|
instances for the motor controller's enable pins, allowing both
|
|||
|
|
direction and variable speed control. If :data:`False`, construct
|
|||
|
|
:class:`DigitalOutputDevice` instances, allowing only direction
|
|||
|
|
control.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Pololu DRV8835 Dual Motor Driver Kit: https://www.pololu.com/product/2753
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=True, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
left=PhaseEnableMotor('BOARD29', 'BOARD32', pwm=pwm, pin_factory=pin_factory),
|
|||
|
|
right=PhaseEnableMotor('BOARD31', 'BOARD33', pwm=pwm, pin_factory=pin_factory),
|
|||
|
|
pin_factory=pin_factory)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class _EnergenieMaster(SharedMixin, CompositeOutputDevice):
|
|||
|
|
def __init__(self, *, pin_factory=None):
|
|||
|
|
self._lock = Lock()
|
|||
|
|
super().__init__(
|
|||
|
|
*(
|
|||
|
|
OutputDevice(pin, pin_factory=pin_factory)
|
|||
|
|
for pin in ('BOARD11', 'BOARD15', 'BOARD16', 'BOARD13')
|
|||
|
|
),
|
|||
|
|
mode=OutputDevice('BOARD18', pin_factory=pin_factory),
|
|||
|
|
enable=OutputDevice('BOARD22', pin_factory=pin_factory),
|
|||
|
|
_order=('mode', 'enable'), pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def close(self):
|
|||
|
|
if getattr(self, '_lock', None):
|
|||
|
|
with self._lock:
|
|||
|
|
super().close()
|
|||
|
|
self._lock = None
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def _shared_key(cls, pin_factory):
|
|||
|
|
# There's only one Energenie master
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def transmit(self, socket, enable):
|
|||
|
|
with self._lock:
|
|||
|
|
try:
|
|||
|
|
code = (8 * bool(enable)) + (8 - socket)
|
|||
|
|
for bit in self[:4]:
|
|||
|
|
bit.value = (code & 1)
|
|||
|
|
code >>= 1
|
|||
|
|
sleep(0.1)
|
|||
|
|
self.enable.on()
|
|||
|
|
sleep(0.25)
|
|||
|
|
finally:
|
|||
|
|
self.enable.off()
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Energenie(SourceMixin, Device):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`Device` to represent an `Energenie socket`_ controller.
|
|||
|
|
|
|||
|
|
This class is constructed with a socket number and an optional initial
|
|||
|
|
state (defaults to :data:`False`, meaning off). Instances of this class can
|
|||
|
|
be used to switch peripherals on and off. For example::
|
|||
|
|
|
|||
|
|
from gpiozero import Energenie
|
|||
|
|
|
|||
|
|
lamp = Energenie(1)
|
|||
|
|
lamp.on()
|
|||
|
|
|
|||
|
|
:param int socket:
|
|||
|
|
Which socket this instance should control. This is an integer number
|
|||
|
|
between 1 and 4.
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param initial_value:
|
|||
|
|
The initial state of the socket. As Energenie sockets provide no
|
|||
|
|
means of reading their state, you may provide an initial state for
|
|||
|
|
the socket, which will be set upon construction. This defaults to
|
|||
|
|
:data:`False` which will switch the socket off.
|
|||
|
|
Specifying :data:`None` will not set any initial state nor transmit any
|
|||
|
|
control signal to the device.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Energenie socket: https://energenie4u.co.uk/index.php/catalogue/product/ENER002-2PI
|
|||
|
|
"""
|
|||
|
|
def __init__(self, socket=None, *, initial_value=False, pin_factory=None):
|
|||
|
|
if socket is None:
|
|||
|
|
raise EnergenieSocketMissing('socket number must be provided')
|
|||
|
|
if not (1 <= socket <= 4):
|
|||
|
|
raise EnergenieBadSocket('socket number must be between 1 and 4')
|
|||
|
|
self._value = None
|
|||
|
|
super().__init__(pin_factory=pin_factory)
|
|||
|
|
self._socket = socket
|
|||
|
|
self._master = _EnergenieMaster(pin_factory=pin_factory)
|
|||
|
|
if initial_value:
|
|||
|
|
self.on()
|
|||
|
|
elif initial_value is not None:
|
|||
|
|
self.off()
|
|||
|
|
|
|||
|
|
def close(self):
|
|||
|
|
if getattr(self, '_master', None):
|
|||
|
|
self._master.close()
|
|||
|
|
self._master = None
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def closed(self):
|
|||
|
|
return self._master is None
|
|||
|
|
|
|||
|
|
def __repr__(self):
|
|||
|
|
try:
|
|||
|
|
self._check_open()
|
|||
|
|
return f"<gpiozero.Energenie object on socket {self._socket}>"
|
|||
|
|
except DeviceClosed:
|
|||
|
|
return "<gpiozero.Energenie object closed>"
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def socket(self):
|
|||
|
|
"""
|
|||
|
|
Returns the socket number.
|
|||
|
|
"""
|
|||
|
|
return self._socket
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def value(self):
|
|||
|
|
"""
|
|||
|
|
Returns :data:`True` if the socket is on and :data:`False` if the
|
|||
|
|
socket is off. Setting this property changes the state of the socket.
|
|||
|
|
Returns :data:`None` only when constructed with :data:`initial_value`
|
|||
|
|
set to :data:`None` and neither :data:`on()` nor :data:`off()` have
|
|||
|
|
been called since construction.
|
|||
|
|
"""
|
|||
|
|
return self._value
|
|||
|
|
|
|||
|
|
@value.setter
|
|||
|
|
def value(self, value):
|
|||
|
|
if value is None:
|
|||
|
|
raise TypeError('value cannot be None')
|
|||
|
|
value = bool(value)
|
|||
|
|
self._master.transmit(self._socket, value)
|
|||
|
|
self._value = value
|
|||
|
|
|
|||
|
|
def on(self):
|
|||
|
|
"""
|
|||
|
|
Turns the socket on.
|
|||
|
|
"""
|
|||
|
|
self.value = True
|
|||
|
|
|
|||
|
|
def off(self):
|
|||
|
|
"""
|
|||
|
|
Turns the socket off.
|
|||
|
|
"""
|
|||
|
|
self.value = False
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PumpkinPi(LEDBoard):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`LEDBoard` for the `ModMyPi PumpkinPi`_ board.
|
|||
|
|
|
|||
|
|
There are twelve LEDs connected up to individual pins, so for the PumpkinPi
|
|||
|
|
the pins are fixed. For example::
|
|||
|
|
|
|||
|
|
from gpiozero import PumpkinPi
|
|||
|
|
|
|||
|
|
pumpkin = PumpkinPi(pwm=True)
|
|||
|
|
pumpkin.sides.pulse()
|
|||
|
|
pumpkin.off()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED. If :data:`False` (the default), construct regular :class:`LED`
|
|||
|
|
instances
|
|||
|
|
|
|||
|
|
:type initial_value: bool or None
|
|||
|
|
:param initial_value:
|
|||
|
|
If :data:`False` (the default), all LEDs will be off initially. If
|
|||
|
|
:data:`None`, each device will be left in whatever state the pin is
|
|||
|
|
found in when configured for output (warning: this can be on). If
|
|||
|
|
:data:`True`, the device will be switched on initially.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _ModMyPi PumpkinPi: https://www.modmypi.com/halloween-pumpkin-programmable-kit
|
|||
|
|
|
|||
|
|
.. attribute:: sides
|
|||
|
|
|
|||
|
|
A :class:`LEDBoard` representing the LEDs around the edge of the
|
|||
|
|
pumpkin. It contains:
|
|||
|
|
|
|||
|
|
.. attribute:: left, right
|
|||
|
|
|
|||
|
|
Two :class:`LEDBoard` instances representing the LEDs on the left
|
|||
|
|
and right sides of the pumpkin. They each contain:
|
|||
|
|
|
|||
|
|
.. attribute:: top, midtop, middle, midbottom, bottom
|
|||
|
|
|
|||
|
|
Each :class:`LED` or :class:`PWMLED` around the specified side
|
|||
|
|
of the pumpkin.
|
|||
|
|
|
|||
|
|
.. attribute:: eyes
|
|||
|
|
|
|||
|
|
A :class:`LEDBoard` representing the eyes of the pumpkin. It contains:
|
|||
|
|
|
|||
|
|
.. attribute:: left, right
|
|||
|
|
|
|||
|
|
The :class:`LED` or :class:`PWMLED` for each of the pumpkin's eyes.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=False, initial_value=False, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
sides=LEDBoard(
|
|||
|
|
left=LEDBoard(
|
|||
|
|
bottom='BOARD12', midbottom='BOARD11', middle='BOARD36',
|
|||
|
|
midtop='BOARD33', top='BOARD18',
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=('bottom', 'midbottom', 'middle', 'midtop', 'top'),
|
|||
|
|
pin_factory=pin_factory),
|
|||
|
|
right=LEDBoard(
|
|||
|
|
bottom='BOARD35', midbottom='BOARD38', middle='BOARD40',
|
|||
|
|
midtop='BOARD15', top='BOARD16',
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=('bottom', 'midbottom', 'middle', 'midtop', 'top'),
|
|||
|
|
pin_factory=pin_factory),
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=('left', 'right'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
),
|
|||
|
|
eyes=LEDBoard(
|
|||
|
|
left='BOARD32', right='BOARD31',
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=('left', 'right'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
),
|
|||
|
|
pwm=pwm, initial_value=initial_value,
|
|||
|
|
_order=('eyes', 'sides'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class JamHat(CompositeOutputDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeOutputDevice` for the `ModMyPi JamHat`_ board.
|
|||
|
|
|
|||
|
|
There are 6 LEDs, two buttons and a tonal buzzer. The pins are fixed.
|
|||
|
|
Usage::
|
|||
|
|
|
|||
|
|
from gpiozero import JamHat
|
|||
|
|
|
|||
|
|
hat = JamHat()
|
|||
|
|
|
|||
|
|
hat.button_1.wait_for_press()
|
|||
|
|
hat.lights_1.on()
|
|||
|
|
hat.buzzer.play('C4')
|
|||
|
|
hat.button_2.wait_for_press()
|
|||
|
|
hat.off()
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED on the board. If :data:`False` (the default), construct regular
|
|||
|
|
:class:`LED` instances.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _ModMyPi JamHat: https://thepihut.com/products/jam-hat
|
|||
|
|
|
|||
|
|
.. attribute:: lights_1, lights_2
|
|||
|
|
|
|||
|
|
Two :class:`LEDBoard` instances representing the top (lights_1) and
|
|||
|
|
bottom (lights_2) rows of LEDs on the JamHat.
|
|||
|
|
|
|||
|
|
.. attribute:: red, yellow, green
|
|||
|
|
|
|||
|
|
:class:`LED` or :class:`PWMLED` instances representing the red,
|
|||
|
|
yellow, and green LEDs along the top row.
|
|||
|
|
|
|||
|
|
.. attribute:: button_1, button_2
|
|||
|
|
|
|||
|
|
The left (button_1) and right (button_2) :class:`Button` objects on the
|
|||
|
|
JamHat.
|
|||
|
|
|
|||
|
|
.. attribute:: buzzer
|
|||
|
|
|
|||
|
|
The :class:`TonalBuzzer` at the bottom right of the JamHat.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=False, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
lights_1=LEDBoard(
|
|||
|
|
red='BOARD29', yellow='BOARD32', green='BOARD36',
|
|||
|
|
pwm=pwm, _order=('red', 'yellow', 'green'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
),
|
|||
|
|
lights_2=LEDBoard(
|
|||
|
|
red='BOARD31', yellow='BOARD33', green='BOARD11',
|
|||
|
|
pwm=pwm, _order=('red', 'yellow', 'green'),
|
|||
|
|
pin_factory=pin_factory),
|
|||
|
|
button_1=Button('BOARD35', pull_up=False, pin_factory=pin_factory),
|
|||
|
|
button_2=Button('BOARD12', pull_up=False, pin_factory=pin_factory),
|
|||
|
|
buzzer=TonalBuzzer('BOARD38', pin_factory=pin_factory),
|
|||
|
|
_order=('lights_1', 'lights_2', 'button_1', 'button_2', 'buzzer'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def on(self):
|
|||
|
|
"""
|
|||
|
|
Turns all the LEDs on and makes the buzzer play its mid tone.
|
|||
|
|
"""
|
|||
|
|
self.buzzer.value = 0
|
|||
|
|
super().on()
|
|||
|
|
|
|||
|
|
def off(self):
|
|||
|
|
"""
|
|||
|
|
Turns all the LEDs off and stops the buzzer.
|
|||
|
|
"""
|
|||
|
|
self.buzzer.value = None
|
|||
|
|
super().off()
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Pibrella(CompositeOutputDevice):
|
|||
|
|
"""
|
|||
|
|
Extends :class:`CompositeOutputDevice` for the Cyntech/Pimoroni `Pibrella`_
|
|||
|
|
board.
|
|||
|
|
|
|||
|
|
The Pibrella board comprises 3 LEDs, a button, a tonal buzzer, four general
|
|||
|
|
purpose input channels, and four general purpose output channels (with LEDs).
|
|||
|
|
|
|||
|
|
This class exposes the LEDs, button and buzzer.
|
|||
|
|
|
|||
|
|
Usage::
|
|||
|
|
|
|||
|
|
from gpiozero import Pibrella
|
|||
|
|
|
|||
|
|
pb = Pibrella()
|
|||
|
|
|
|||
|
|
pb.button.wait_for_press()
|
|||
|
|
pb.lights.on()
|
|||
|
|
pb.buzzer.play('A4')
|
|||
|
|
pb.off()
|
|||
|
|
|
|||
|
|
The four input and output channels are exposed so you can create GPIO Zero
|
|||
|
|
devices using these pins without looking up their respective pin numbers::
|
|||
|
|
|
|||
|
|
from gpiozero import Pibrella, LED, Button
|
|||
|
|
|
|||
|
|
pb = Pibrella()
|
|||
|
|
btn = Button(pb.inputs.a, pull_up=False)
|
|||
|
|
led = LED(pb.outputs.e)
|
|||
|
|
|
|||
|
|
btn.when_pressed = led.on
|
|||
|
|
|
|||
|
|
:param bool pwm:
|
|||
|
|
If :data:`True`, construct :class:`PWMLED` instances to represent each
|
|||
|
|
LED on the board, otherwise if :data:`False` (the default), construct
|
|||
|
|
regular :class:`LED` instances.
|
|||
|
|
|
|||
|
|
:type pin_factory: Factory or None
|
|||
|
|
:param pin_factory:
|
|||
|
|
See :doc:`api_pins` for more information (this is an advanced feature
|
|||
|
|
which most users can ignore).
|
|||
|
|
|
|||
|
|
.. _Pibrella: http://www.pibrella.com/
|
|||
|
|
|
|||
|
|
.. attribute:: lights
|
|||
|
|
|
|||
|
|
:class:`TrafficLights` instance representing the three LEDs
|
|||
|
|
|
|||
|
|
.. attribute:: red, amber, green
|
|||
|
|
|
|||
|
|
:class:`LED` or :class:`PWMLED` instances representing the red,
|
|||
|
|
amber, and green LEDs
|
|||
|
|
|
|||
|
|
.. attribute:: button
|
|||
|
|
|
|||
|
|
The red :class:`Button` object on the Pibrella
|
|||
|
|
|
|||
|
|
.. attribute:: buzzer
|
|||
|
|
|
|||
|
|
A :class:`TonalBuzzer` object representing the buzzer
|
|||
|
|
|
|||
|
|
.. attribute:: inputs
|
|||
|
|
|
|||
|
|
A :func:`~collections.namedtuple` of the input pin numbers
|
|||
|
|
|
|||
|
|
.. attribute:: a, b, c, d
|
|||
|
|
|
|||
|
|
.. attribute:: outputs
|
|||
|
|
|
|||
|
|
A :func:`~collections.namedtuple` of the output pin numbers
|
|||
|
|
|
|||
|
|
.. attribute:: e, f, g, h
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *, pwm=False, pin_factory=None):
|
|||
|
|
super().__init__(
|
|||
|
|
lights=TrafficLights(
|
|||
|
|
red='BOARD13', amber='BOARD11', green='BOARD7',
|
|||
|
|
pwm=pwm, pin_factory=pin_factory
|
|||
|
|
),
|
|||
|
|
button=Button('BOARD23', pull_up=False, pin_factory=pin_factory),
|
|||
|
|
buzzer=TonalBuzzer('BOARD12', pin_factory=pin_factory),
|
|||
|
|
_order=('lights', 'button', 'buzzer'),
|
|||
|
|
pin_factory=pin_factory
|
|||
|
|
)
|
|||
|
|
InputPins = namedtuple('InputPins', ['a', 'b', 'c', 'd'])
|
|||
|
|
OutputPins = namedtuple('OutputPins', ['e', 'f', 'g', 'h'])
|
|||
|
|
self.inputs = InputPins(
|
|||
|
|
a='BOARD21', b='BOARD26', c='BOARD24', d='BOARD19'
|
|||
|
|
)
|
|||
|
|
self.outputs = OutputPins(
|
|||
|
|
e='BOARD15', f='BOARD16', g='BOARD18', h='BOARD22'
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def on(self):
|
|||
|
|
"""
|
|||
|
|
Turns all the LEDs on and makes the buzzer play its mid tone.
|
|||
|
|
"""
|
|||
|
|
self.buzzer.value = 0
|
|||
|
|
super().on()
|
|||
|
|
|
|||
|
|
def off(self):
|
|||
|
|
"""
|
|||
|
|
Turns all the LEDs off and stops the buzzer.
|
|||
|
|
"""
|
|||
|
|
self.buzzer.value = None
|
|||
|
|
super().off()
|