mirror of
				https://github.com/KevinMidboe/python-gpiozero.git
				synced 2025-10-29 17:50:37 +00:00 
			
		
		
		
	Also updated StatusBoard and StatusZero to reject duplicate identifiers (namedtuple doesn't pick 'em up because they're passed in a dict and thus the dups are squashed prior to the call). Added tests for all the relevant stuff.
		
			
				
	
	
		
			1284 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1284 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from __future__ import (
 | 
						|
    unicode_literals,
 | 
						|
    print_function,
 | 
						|
    absolute_import,
 | 
						|
    division,
 | 
						|
    )
 | 
						|
try:
 | 
						|
    from itertools import izip as zip
 | 
						|
except ImportError:
 | 
						|
    pass
 | 
						|
 | 
						|
from time import sleep
 | 
						|
from itertools import repeat, cycle, chain
 | 
						|
from threading import Lock
 | 
						|
from collections import OrderedDict, Counter
 | 
						|
 | 
						|
from .exc import (
 | 
						|
    DeviceClosed,
 | 
						|
    GPIOPinMissing,
 | 
						|
    EnergenieSocketMissing,
 | 
						|
    EnergenieBadSocket,
 | 
						|
    OutputDeviceBadValue,
 | 
						|
    )
 | 
						|
from .input_devices import Button
 | 
						|
from .output_devices import (
 | 
						|
    OutputDevice,
 | 
						|
    LED,
 | 
						|
    PWMLED,
 | 
						|
    RGBLED,
 | 
						|
    Buzzer,
 | 
						|
    Motor,
 | 
						|
    )
 | 
						|
from .threads import GPIOThread
 | 
						|
from .devices import Device, CompositeDevice
 | 
						|
from .mixins import SharedMixin, SourceMixin, HoldMixin
 | 
						|
 | 
						|
 | 
						|
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 list _order:
 | 
						|
        If specified, this is the order of named items specified by keyword
 | 
						|
        arguments (to ensure that the :attr:`value` tuple is constructed with a
 | 
						|
        specific order). All keyword arguments *must* be included in the
 | 
						|
        collection. If omitted, an alphabetically sorted order will be selected
 | 
						|
        for keyword arguments.
 | 
						|
    """
 | 
						|
 | 
						|
    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(CompositeOutputDevice, self).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.
 | 
						|
 | 
						|
    :param int \*pins:
 | 
						|
        Specify the GPIO pins that the buttons of the board are attached to.
 | 
						|
        You can designate as many pins as necessary.
 | 
						|
 | 
						|
    :param bool pull_up:
 | 
						|
        If ``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 ``False``, the GPIO pins will be pulled low by default. In
 | 
						|
        this case, connect the other side of the buttons to 3V3. This
 | 
						|
        parameter can only be specified as a keyword parameter.
 | 
						|
 | 
						|
    :param float bounce_time:
 | 
						|
        If ``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. This
 | 
						|
        parameter can only be specified as a keyword parameter.
 | 
						|
 | 
						|
    :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``. This
 | 
						|
        parameter can only be specified as a keyword parameter.
 | 
						|
 | 
						|
    :param bool hold_repeat:
 | 
						|
        If ``True``, the :attr:`when_held` handler will be repeatedly executed
 | 
						|
        as long as any buttons remain held, every *hold_time* seconds. If
 | 
						|
        ``False`` (the default) the :attr:`when_held` handler will be only be
 | 
						|
        executed once per hold. This parameter can only be specified as a
 | 
						|
        keyword parameter.
 | 
						|
 | 
						|
    :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, *args, **kwargs):
 | 
						|
        pull_up = kwargs.pop('pull_up', True)
 | 
						|
        bounce_time = kwargs.pop('bounce_time', None)
 | 
						|
        hold_time = kwargs.pop('hold_time', 1)
 | 
						|
        hold_repeat = kwargs.pop('hold_repeat', False)
 | 
						|
        order = kwargs.pop('_order', None)
 | 
						|
        super(ButtonBoard, self).__init__(
 | 
						|
            *(
 | 
						|
                Button(pin, pull_up, bounce_time, hold_time, hold_repeat)
 | 
						|
                for pin in args
 | 
						|
                ),
 | 
						|
            _order=order,
 | 
						|
            **{
 | 
						|
                name: Button(pin, pull_up, bounce_time, hold_time, hold_repeat)
 | 
						|
                for name, pin in kwargs.items()
 | 
						|
                })
 | 
						|
        def get_new_handler(device):
 | 
						|
            def fire_both_events():
 | 
						|
                device._fire_events()
 | 
						|
                self._fire_events()
 | 
						|
            return fire_both_events
 | 
						|
        for button in self:
 | 
						|
            button.pin.when_changed = get_new_handler(button)
 | 
						|
        self._when_changed = None
 | 
						|
        self._last_value = None
 | 
						|
        # Call _fire_events once to set initial state of events
 | 
						|
        self._fire_events()
 | 
						|
        self.hold_time = hold_time
 | 
						|
        self.hold_repeat = hold_repeat
 | 
						|
 | 
						|
    @property
 | 
						|
    def pull_up(self):
 | 
						|
        """
 | 
						|
        If ``True``, the device uses a pull-up resistor to set the GPIO pin
 | 
						|
        "high" by default.
 | 
						|
        """
 | 
						|
        return self[0].pull_up
 | 
						|
 | 
						|
    @property
 | 
						|
    def when_changed(self):
 | 
						|
        return self._when_changed
 | 
						|
 | 
						|
    @when_changed.setter
 | 
						|
    def when_changed(self, value):
 | 
						|
        self._when_changed = self._wrap_callback(value)
 | 
						|
 | 
						|
    def _fire_changed(self):
 | 
						|
        if self.when_changed:
 | 
						|
            self.when_changed()
 | 
						|
 | 
						|
    def _fire_events(self):
 | 
						|
        super(ButtonBoard, self)._fire_events()
 | 
						|
        old_value = self._last_value
 | 
						|
        new_value = self._last_value = self.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, *args, **kwargs):
 | 
						|
        self._blink_thread = None
 | 
						|
        pwm = kwargs.pop('pwm', False)
 | 
						|
        active_high = kwargs.pop('active_high', True)
 | 
						|
        initial_value = kwargs.pop('initial_value', False)
 | 
						|
        order = kwargs.pop('_order', None)
 | 
						|
        LEDClass = PWMLED if pwm else LED
 | 
						|
        super(LEDCollection, self).__init__(
 | 
						|
            *(
 | 
						|
                pin_or_collection
 | 
						|
                if isinstance(pin_or_collection, LEDCollection) else
 | 
						|
                LEDClass(pin_or_collection, active_high, initial_value)
 | 
						|
                for pin_or_collection in args
 | 
						|
                ),
 | 
						|
            _order=order,
 | 
						|
            **{
 | 
						|
                name: pin_or_collection
 | 
						|
                if isinstance(pin_or_collection, LEDCollection) else
 | 
						|
                LEDClass(pin_or_collection, active_high, initial_value)
 | 
						|
                for name, pin_or_collection in kwargs.items()
 | 
						|
                })
 | 
						|
        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
 | 
						|
 | 
						|
 | 
						|
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()
 | 
						|
 | 
						|
    :param int \*pins:
 | 
						|
        Specify the GPIO pins that the LEDs of the board are attached to. You
 | 
						|
        can designate as many pins as necessary. You can also specify
 | 
						|
        :class:`LEDBoard` instances to create trees of LEDs.
 | 
						|
 | 
						|
    :param bool pwm:
 | 
						|
        If ``True``, construct :class:`PWMLED` instances for each pin. If
 | 
						|
        ``False`` (the default), construct regular :class:`LED` instances. This
 | 
						|
        parameter can only be specified as a keyword parameter.
 | 
						|
 | 
						|
    :param bool active_high:
 | 
						|
        If ``True`` (the default), the :meth:`on` method will set all the
 | 
						|
        associated pins to HIGH. If ``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 bool initial_value:
 | 
						|
        If ``False`` (the default), all LEDs will be off initially. If
 | 
						|
        ``None``, each device will be left in whatever state the pin is found
 | 
						|
        in when configured for output (warning: this can be on). If ``True``,
 | 
						|
        the device will be switched on initially. This parameter can only be
 | 
						|
        specified as a keyword parameter.
 | 
						|
 | 
						|
    :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, *args, **kwargs):
 | 
						|
        self._blink_leds = []
 | 
						|
        self._blink_lock = Lock()
 | 
						|
        super(LEDBoard, self).__init__(*args, **kwargs)
 | 
						|
 | 
						|
    def close(self):
 | 
						|
        self._stop_blink()
 | 
						|
        super(LEDBoard, self).close()
 | 
						|
 | 
						|
    def on(self, *args):
 | 
						|
        self._stop_blink()
 | 
						|
        if args:
 | 
						|
            for index in args:
 | 
						|
                self[index].on()
 | 
						|
        else:
 | 
						|
            super(LEDBoard, self).on()
 | 
						|
 | 
						|
    def off(self, *args):
 | 
						|
        self._stop_blink()
 | 
						|
        if args:
 | 
						|
            for index in args:
 | 
						|
                self[index].off()
 | 
						|
        else:
 | 
						|
            super(LEDBoard, self).off()
 | 
						|
 | 
						|
    def toggle(self, *args):
 | 
						|
        self._stop_blink()
 | 
						|
        if args:
 | 
						|
            for index in args:
 | 
						|
                self[index].toggle()
 | 
						|
        else:
 | 
						|
            super(LEDBoard, self).toggle()
 | 
						|
 | 
						|
    def blink(
 | 
						|
            self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0,
 | 
						|
            n=None, background=True):
 | 
						|
        """
 | 
						|
        Make all the LEDs turn on and off repeatedly.
 | 
						|
 | 
						|
        :param float on_time:
 | 
						|
            Number of seconds on. Defaults to 1 second.
 | 
						|
 | 
						|
        :param float off_time:
 | 
						|
            Number of seconds off. Defaults to 1 second.
 | 
						|
 | 
						|
        :param float fade_in_time:
 | 
						|
            Number of seconds to spend fading in. Defaults to 0. Must be 0 if
 | 
						|
            ``pwm`` was ``False`` when the class was constructed
 | 
						|
            (:exc:`ValueError` will be raised if not).
 | 
						|
 | 
						|
        :param float fade_out_time:
 | 
						|
            Number of seconds to spend fading out. Defaults to 0. Must be 0 if
 | 
						|
            ``pwm`` was ``False`` when the class was constructed
 | 
						|
            (:exc:`ValueError` will be raised if not).
 | 
						|
 | 
						|
        :param int n:
 | 
						|
            Number of times to blink; ``None`` (the default) means forever.
 | 
						|
 | 
						|
        :param bool background:
 | 
						|
            If ``True``, start a background thread to continue blinking and
 | 
						|
            return immediately. If ``False``, only return when the blink is
 | 
						|
            finished (warning: the default value of *n* will result in this
 | 
						|
            method never returning).
 | 
						|
        """
 | 
						|
        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(
 | 
						|
            target=self._blink_device,
 | 
						|
            args=(on_time, off_time, fade_in_time, fade_out_time, n)
 | 
						|
        )
 | 
						|
        self._blink_thread.start()
 | 
						|
        if not background:
 | 
						|
            self._blink_thread.join()
 | 
						|
            self._blink_thread = None
 | 
						|
 | 
						|
    def _stop_blink(self, 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 the device fade in and out repeatedly.
 | 
						|
 | 
						|
        :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.
 | 
						|
 | 
						|
        :param int n:
 | 
						|
            Number of times to blink; ``None`` (the default) means forever.
 | 
						|
 | 
						|
        :param bool background:
 | 
						|
            If ``True`` (the default), start a background thread to continue
 | 
						|
            blinking and return immediately. If ``False``, only return when the
 | 
						|
            blink is finished (warning: the default value of *n* will result in
 | 
						|
            this method never returning).
 | 
						|
        """
 | 
						|
        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))
 | 
						|
        sequence = (
 | 
						|
                cycle(sequence) if n is None else
 | 
						|
                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 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.values
 | 
						|
        pause()
 | 
						|
 | 
						|
    :param int \*pins:
 | 
						|
        Specify the GPIO pins that the LEDs of the bar graph are attached to.
 | 
						|
        You can designate as many pins as necessary.
 | 
						|
 | 
						|
    :param bool pwm:
 | 
						|
        If ``True``, construct :class:`PWMLED` instances for each pin. If
 | 
						|
        ``False`` (the default), construct regular :class:`LED` instances. This
 | 
						|
        parameter can only be specified as a keyword parameter.
 | 
						|
 | 
						|
    :param bool active_high:
 | 
						|
        If ``True`` (the default), the :meth:`on` method will set all the
 | 
						|
        associated pins to HIGH. If ``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.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, *pins, **kwargs):
 | 
						|
        # Don't allow graphs to contain collections
 | 
						|
        for pin in pins:
 | 
						|
            assert not isinstance(pin, LEDCollection)
 | 
						|
        pwm = kwargs.pop('pwm', False)
 | 
						|
        active_high = kwargs.pop('active_high', True)
 | 
						|
        initial_value = kwargs.pop('initial_value', 0.0)
 | 
						|
        if kwargs:
 | 
						|
            raise TypeError('unexpected keyword argument: %s' % kwargs.popitem()[0])
 | 
						|
        super(LEDBarGraph, self).__init__(*pins, pwm=pwm, active_high=active_high)
 | 
						|
        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)
 | 
						|
 | 
						|
 | 
						|
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)
 | 
						|
 | 
						|
    :param tuple initial_value:
 | 
						|
        The initial color for the LedBorg. Defaults to black ``(0, 0, 0)``.
 | 
						|
 | 
						|
    :param bool pwm:
 | 
						|
        If ``True`` (the default), construct :class:`PWMLED` instances for
 | 
						|
        each component of the LedBorg. If ``False``, construct regular
 | 
						|
        :class:`LED` instances, which prevents smooth color graduations.
 | 
						|
 | 
						|
    .. _PiBorg LedBorg: https://www.piborg.org/ledborg
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, initial_value=(0, 0, 0), pwm=True):
 | 
						|
        super(LedBorg, self).__init__(red=17, green=27, blue=22,
 | 
						|
                                      pwm=pwm, initial_value=initial_value)
 | 
						|
 | 
						|
 | 
						|
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 ``True``, construct :class:`PWMLED` instances for each pin. If
 | 
						|
        ``False`` (the default), construct regular :class:`LED` instances.
 | 
						|
 | 
						|
    :param bool initial_value:
 | 
						|
        If ``False`` (the default), all LEDs will be off initially. If
 | 
						|
        ``None``, each device will be left in whatever state the pin is found
 | 
						|
        in when configured for output (warning: this can be on). If ``True``,
 | 
						|
        the device will be switched on initially.
 | 
						|
 | 
						|
    .. _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):
 | 
						|
        super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25,
 | 
						|
                                      pwm=pwm, initial_value=initial_value)
 | 
						|
 | 
						|
 | 
						|
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 ``True``, construct :class:`PWMLED` instances for each pin. If
 | 
						|
        ``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``.
 | 
						|
 | 
						|
    .. _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):
 | 
						|
        pins = (4, 17, 27, 18, 22, 23, 24, 25)
 | 
						|
        super(PiLiterBarGraph, self).__init__(*pins,
 | 
						|
                pwm=pwm, initial_value=initial_value)
 | 
						|
 | 
						|
 | 
						|
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()
 | 
						|
 | 
						|
    :param int red:
 | 
						|
        The GPIO pin that the red LED is attached to.
 | 
						|
 | 
						|
    :param int amber:
 | 
						|
        The GPIO pin that the amber LED is attached to.
 | 
						|
 | 
						|
    :param int green:
 | 
						|
        The GPIO pin that the green LED is attached to.
 | 
						|
 | 
						|
    :param bool pwm:
 | 
						|
        If ``True``, construct :class:`PWMLED` instances to represent each
 | 
						|
        LED. If ``False`` (the default), construct regular :class:`LED`
 | 
						|
        instances.
 | 
						|
 | 
						|
    :param bool initial_value:
 | 
						|
        If ``False`` (the default), all LEDs will be off initially. If
 | 
						|
        ``None``, each device will be left in whatever state the pin is found
 | 
						|
        in when configured for output (warning: this can be on). If ``True``,
 | 
						|
        the device will be switched on initially.
 | 
						|
 | 
						|
    :param int 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``.
 | 
						|
    """
 | 
						|
    def __init__(self, red=None, amber=None, green=None,
 | 
						|
                 pwm=False, initial_value=False, yellow=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(
 | 
						|
                ', '.join(devices.keys())+' pins must be provided'
 | 
						|
            )
 | 
						|
        super(TrafficLights, self).__init__(
 | 
						|
            pwm=pwm, initial_value=initial_value,
 | 
						|
            _order=devices.keys(),
 | 
						|
            **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(TrafficLights, self).__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 ``True``, construct :class:`PWMLED` instances to represent each
 | 
						|
        LED. If ``False`` (the default), construct regular :class:`LED`
 | 
						|
        instances.
 | 
						|
 | 
						|
    :param bool initial_value:
 | 
						|
        If ``False`` (the default), all LEDs will be off initially. If
 | 
						|
        ``None``, each device will be left in whatever state the pin is found
 | 
						|
        in when configured for output (warning: this can be on). If ``True``,
 | 
						|
        the device will be switched on initially.
 | 
						|
 | 
						|
    .. _Low Voltage Labs PI-TRAFFIC: http://lowvoltagelabs.com/products/pi-traffic/
 | 
						|
    """
 | 
						|
    def __init__(self, pwm=False, initial_value=False):
 | 
						|
        super(PiTraffic, self).__init__(9, 10, 11,
 | 
						|
                                        pwm=pwm, initial_value=initial_value)
 | 
						|
 | 
						|
 | 
						|
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 ``True``, construct :class:`PWMLED` instances to represent each
 | 
						|
        LED. If ``False`` (the default), construct regular :class:`LED`
 | 
						|
        instances.
 | 
						|
 | 
						|
    :param bool initial_value:
 | 
						|
        If ``False`` (the default), all LEDs will be off initially. If
 | 
						|
        ``None``, each device will be left in whatever state the pin is found
 | 
						|
        in when configured for output (warning: this can be on). If ``True``,
 | 
						|
        the device will be switched on initially.
 | 
						|
 | 
						|
    .. _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': (7, 8, 25),
 | 
						|
        'A+': (21, 20, 16),
 | 
						|
        'B': (10, 9, 11),
 | 
						|
        'B+': (13, 19, 26),
 | 
						|
        'C': (18, 15, 14),
 | 
						|
        'D': (2, 3, 4),
 | 
						|
    }
 | 
						|
 | 
						|
    def __init__(self, location=None, pwm=False, initial_value=False):
 | 
						|
        gpios = self.LOCATIONS.get(location, None)
 | 
						|
        if gpios is None:
 | 
						|
            raise ValueError('location must be one of: %s' %
 | 
						|
                             ', '.join(sorted(self.LOCATIONS.keys())))
 | 
						|
        super(PiStop, self).__init__(*gpios,
 | 
						|
                                        pwm=pwm, initial_value=initial_value)
 | 
						|
 | 
						|
 | 
						|
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()
 | 
						|
 | 
						|
    :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.
 | 
						|
 | 
						|
    .. _STATUS Zero: https://thepihut.com/statuszero
 | 
						|
    """
 | 
						|
    default_labels = ('one', 'two', 'three')
 | 
						|
 | 
						|
    def __init__(self, *labels, **kwargs):
 | 
						|
        pins = (
 | 
						|
            (17, 4),
 | 
						|
            (22, 27),
 | 
						|
            (9, 10),
 | 
						|
        )
 | 
						|
        if len(labels) == 0:
 | 
						|
            labels = self.default_labels
 | 
						|
        elif len(labels) > len(pins):
 | 
						|
            raise ValueError("StatusZero doesn't support more than three labels")
 | 
						|
        dup, count = Counter(labels).most_common(1)[0]
 | 
						|
        if count > 1:
 | 
						|
            raise ValueError("Duplicate label %s" % dup)
 | 
						|
        strips = OrderedDict()
 | 
						|
        for (green, red), label in zip(pins, labels):
 | 
						|
            strips[label] = LEDBoard(red=red, green=green,
 | 
						|
                _order=('red', 'green'), **kwargs)
 | 
						|
        super(StatusZero, self).__init__(_order=strips.keys(), **strips)
 | 
						|
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
    :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' to 'five'. If some, but not all
 | 
						|
        strips are given labels, any remaining strips will not be initialised.
 | 
						|
 | 
						|
    .. _STATUS: https://thepihut.com/status
 | 
						|
    """
 | 
						|
    default_labels = ('one', 'two', 'three', 'four', 'five')
 | 
						|
 | 
						|
    def __init__(self, *labels, **kwargs):
 | 
						|
        pins = (
 | 
						|
            (17, 4, 14),
 | 
						|
            (22, 27, 19),
 | 
						|
            (9, 10, 15),
 | 
						|
            (5, 11, 26),
 | 
						|
            (13, 6, 18),
 | 
						|
        )
 | 
						|
        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("Duplicate label %s" % dup)
 | 
						|
        strips = OrderedDict()
 | 
						|
        for (green, red, button), label in zip(pins, labels):
 | 
						|
            strips[label] = CompositeOutputDevice(
 | 
						|
                button=Button(button),
 | 
						|
                lights=LEDBoard(
 | 
						|
                    red=red, green=green, _order=('red', 'green'), **kwargs
 | 
						|
                ), _order=('button', 'lights'))
 | 
						|
        super(StatusBoard, self).__init__(_order=strips.keys(), **strips)
 | 
						|
 | 
						|
 | 
						|
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 ``True``, construct :class:`PWMLED` instances to represent each
 | 
						|
        LED. If ``False`` (the default), construct regular :class:`LED`
 | 
						|
        instances.
 | 
						|
 | 
						|
    :param bool initial_value:
 | 
						|
        If ``False`` (the default), all LEDs will be off initially. If
 | 
						|
        ``None``, each device will be left in whatever state the pin is found
 | 
						|
        in when configured for output (warning: this can be on). If ``True``,
 | 
						|
        the device will be switched on initially.
 | 
						|
 | 
						|
    .. _Ryanteck SnowPi: https://ryanteck.uk/raspberry-pi/114-snowpi-the-gpio-snowman-for-raspberry-pi-0635648608303.html
 | 
						|
    """
 | 
						|
    def __init__(self, pwm=False, initial_value=False):
 | 
						|
        super(SnowPi, self).__init__(
 | 
						|
            arms=LEDBoard(
 | 
						|
                left=LEDBoard(
 | 
						|
                    top=17, middle=18, bottom=22,
 | 
						|
                    pwm=pwm, initial_value=initial_value,
 | 
						|
                    _order=('top', 'middle', 'bottom')),
 | 
						|
                right=LEDBoard(
 | 
						|
                    top=7, middle=8, bottom=9,
 | 
						|
                    pwm=pwm, initial_value=initial_value,
 | 
						|
                    _order=('top', 'middle', 'bottom')),
 | 
						|
                _order=('left', 'right')
 | 
						|
                ),
 | 
						|
            eyes=LEDBoard(
 | 
						|
                left=23, right=24,
 | 
						|
                pwm=pwm, initial_value=initial_value,
 | 
						|
                _order=('left', 'right')
 | 
						|
                ),
 | 
						|
            nose=25,
 | 
						|
            pwm=pwm, initial_value=initial_value,
 | 
						|
            _order=('eyes', 'nose', 'arms')
 | 
						|
            )
 | 
						|
 | 
						|
 | 
						|
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.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, lights, buzzer, button):
 | 
						|
        super(TrafficLightsBuzzer, self).__init__(
 | 
						|
            lights=lights, buzzer=buzzer, button=button,
 | 
						|
            _order=('lights', 'buzzer', 'button'))
 | 
						|
 | 
						|
 | 
						|
class FishDish(TrafficLightsBuzzer):
 | 
						|
    """
 | 
						|
    Extends :class:`TrafficLightsBuzzer` 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 ``True``, construct :class:`PWMLED` instances to represent each
 | 
						|
        LED. If ``False`` (the default), construct regular :class:`LED`
 | 
						|
        instances.
 | 
						|
 | 
						|
    .. _Pi Supply FishDish: https://www.pi-supply.com/product/fish-dish-raspberry-pi-led-buzzer-board/
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, pwm=False):
 | 
						|
        super(FishDish, self).__init__(
 | 
						|
            TrafficLights(9, 22, 4, pwm=pwm),
 | 
						|
            Buzzer(8),
 | 
						|
            Button(7, pull_up=False),
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class TrafficHat(TrafficLightsBuzzer):
 | 
						|
    """
 | 
						|
    Extends :class:`TrafficLightsBuzzer` for the `Ryanteck Traffic HAT`_: 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 ``True``, construct :class:`PWMLED` instances to represent each
 | 
						|
        LED. If ``False`` (the default), construct regular :class:`LED`
 | 
						|
        instances.
 | 
						|
 | 
						|
    .. _Ryanteck Traffic HAT: https://ryanteck.uk/hats/1-traffichat-0635648607122.html
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, pwm=False):
 | 
						|
        super(TrafficHat, self).__init__(
 | 
						|
            TrafficLights(24, 23, 22, pwm=pwm),
 | 
						|
            Buzzer(5),
 | 
						|
            Button(25),
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class Robot(SourceMixin, CompositeDevice):
 | 
						|
    """
 | 
						|
    Extends :class:`CompositeDevice` to represent a generic dual-motor robot.
 | 
						|
 | 
						|
    This class is constructed with two tuples representing the forward and
 | 
						|
    backward pins of the left and right controllers 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=(4, 14), right=(17, 18))
 | 
						|
        robot.forward()
 | 
						|
 | 
						|
    :param tuple left:
 | 
						|
        A tuple of two GPIO pins representing the forward and backward inputs
 | 
						|
        of the left motor's controller.
 | 
						|
 | 
						|
    :param tuple right:
 | 
						|
        A tuple of two GPIO pins representing the forward and backward inputs
 | 
						|
        of the right motor's controller.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, left=None, right=None):
 | 
						|
        super(Robot, self).__init__(
 | 
						|
                left_motor=Motor(*left),
 | 
						|
                right_motor=Motor(*right),
 | 
						|
                _order=('left_motor', 'right_motor'))
 | 
						|
 | 
						|
    @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(Robot, self).value
 | 
						|
 | 
						|
    @value.setter
 | 
						|
    def value(self, value):
 | 
						|
        self.left_motor.value, self.right_motor.value = value
 | 
						|
 | 
						|
    def forward(self, speed=1):
 | 
						|
        """
 | 
						|
        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.
 | 
						|
        """
 | 
						|
        self.left_motor.forward(speed)
 | 
						|
        self.right_motor.forward(speed)
 | 
						|
 | 
						|
    def backward(self, speed=1):
 | 
						|
        """
 | 
						|
        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.
 | 
						|
        """
 | 
						|
        self.left_motor.backward(speed)
 | 
						|
        self.right_motor.backward(speed)
 | 
						|
 | 
						|
    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 MCB`_ robot.
 | 
						|
 | 
						|
    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()
 | 
						|
 | 
						|
    .. _Ryanteck MCB: https://ryanteck.uk/add-ons/6-ryanteck-rpi-motor-controller-board-0635648607160.html
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        super(RyanteckRobot, self).__init__((17, 18), (22, 23))
 | 
						|
 | 
						|
 | 
						|
class CamJamKitRobot(Robot):
 | 
						|
    """
 | 
						|
    Extends :class:`Robot` for the `CamJam #3 EduKit`_ robot controller.
 | 
						|
 | 
						|
    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()
 | 
						|
 | 
						|
    .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        super(CamJamKitRobot, self).__init__((9, 10), (7, 8))
 | 
						|
 | 
						|
 | 
						|
class _EnergenieMaster(SharedMixin, CompositeOutputDevice):
 | 
						|
    def __init__(self):
 | 
						|
        self._lock = Lock()
 | 
						|
        super(_EnergenieMaster, self).__init__(
 | 
						|
                *(OutputDevice(pin) for pin in (17, 22, 23, 27)),
 | 
						|
                mode=OutputDevice(24), enable=OutputDevice(25),
 | 
						|
                _order=('mode', 'enable'))
 | 
						|
 | 
						|
    def close(self):
 | 
						|
        if self._lock:
 | 
						|
            with self._lock:
 | 
						|
                super(_EnergenieMaster, self).close()
 | 
						|
            self._lock = None
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def _shared_key(cls):
 | 
						|
        # There's only one Energenie master
 | 
						|
        return None
 | 
						|
 | 
						|
    def transmit(self, socket, enable):
 | 
						|
        with self._lock:
 | 
						|
            try:
 | 
						|
                code = (8 * bool(enable)) + (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 ``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.
 | 
						|
 | 
						|
    :param bool initial_value:
 | 
						|
        The initial state of the socket. As Energenie sockets provide no
 | 
						|
        means of reading their state, you must provide an initial state for
 | 
						|
        the socket, which will be set upon construction. This defaults to
 | 
						|
        ``False`` which will switch the socket off.
 | 
						|
 | 
						|
    .. _Energenie socket: https://energenie4u.co.uk/index.php/catalogue/product/ENER002-2PI
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, socket=None, initial_value=False):
 | 
						|
        if socket is None:
 | 
						|
            raise EnergenieSocketMissing('socket number must be provided')
 | 
						|
        if not (1 <= socket <= 4):
 | 
						|
            raise EnergenieBadSocket('socket number must be between 1 and 4')
 | 
						|
        self._value = None
 | 
						|
        super(Energenie, self).__init__()
 | 
						|
        self._socket = socket
 | 
						|
        self._master = _EnergenieMaster()
 | 
						|
        if initial_value:
 | 
						|
            self.on()
 | 
						|
        else:
 | 
						|
            self.off()
 | 
						|
 | 
						|
    def close(self):
 | 
						|
        if self._master:
 | 
						|
            m = self._master
 | 
						|
            self._master = None
 | 
						|
            m.close()
 | 
						|
 | 
						|
    @property
 | 
						|
    def closed(self):
 | 
						|
        return self._master is None
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        try:
 | 
						|
            self._check_open()
 | 
						|
            return "<gpiozero.Energenie object on socket %d>" % self._socket
 | 
						|
        except DeviceClosed:
 | 
						|
            return "<gpiozero.Energenie object closed>"
 | 
						|
 | 
						|
    @property
 | 
						|
    def value(self):
 | 
						|
        return self._value
 | 
						|
 | 
						|
    @value.setter
 | 
						|
    def value(self, value):
 | 
						|
        value = bool(value)
 | 
						|
        self._master.transmit(self._socket, value)
 | 
						|
        self._value = value
 | 
						|
 | 
						|
    def on(self):
 | 
						|
        self.value = True
 | 
						|
 | 
						|
    def off(self):
 | 
						|
        self.value = False
 |