# Source code for gpiozero.output_devices

    # vim: set fileencoding=utf-8:
    #
    # GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
    #
    # Copyright (c) 2015-2023 Dave Jones <dave@waveform.org.uk>
    # Copyright (c) 2022 gnicki2000 <89583687+gnicki2000@users.noreply.github.com>
    # Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
    # Copyright (c) 2015-2020 Ben Nuttall <ben@bennuttall.com>
    # Copyright (c) 2019 tuftii <3215045+tuftii@users.noreply.github.com>
    # Copyright (c) 2019 tuftii <pi@raspberrypi>
    # Copyright (c) 2019 Yisrael Dov Lebow 🐻 <lebow@lebowtech.com>
    # Copyright (c) 2019 Kosovan Sofiia <sofiia.kosovan@gmail.com>
    # Copyright (c) 2016-2019 Andrew Scheller <github@loowis.durge.org>
    # Copyright (c) 2016 Ian Harcombe <ian.harcombe@gmail.com>
    #
    # SPDX-License-Identifier: BSD-3-Clause

    import warnings
    from threading import Lock
    from itertools import repeat, cycle, chain
    from colorzero import Color
    from collections import OrderedDict
    from math import log2

    from .exc import (
        OutputDeviceBadValue,
        GPIOPinMissing,
        PWMSoftwareFallback,
        DeviceClosed,
    )
    from .devices import GPIODevice, Device, CompositeDevice
    from .mixins import SourceMixin
    from .threads import GPIOThread
    from .tones import Tone
    try:
        from .pins.pigpio import PiGPIOFactory
    except ImportError:
        PiGPIOFactory = None

    class OutputDevice(SourceMixin, GPIODevice):
        """
        Represents a generic GPIO output device.

        This class extends :class:`GPIODevice` to add facilities common to GPIO
        output devices: an :meth:`on` method to switch the device on, a
        corresponding :meth:`off` method, and a :meth:`toggle` method.

        :type pin: int or str
        :param pin:
            The GPIO pin that the device is connected to. See :ref:`pin-numbering`
            for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
            will be raised.

        :param bool active_high:
            If :data:`True` (the default), the :meth:`on` method will set the GPIO
            to HIGH. If :data:`False`, the :meth:`on` method will set the GPIO to
            LOW (the :meth:`off` method always does the opposite).

        :type initial_value: bool or None
        :param initial_value:
            If :data:`False` (the default), the device will be off initially.  If
            :data:`None`, the 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).
        """
        def __init__(self, pin=None, *, active_high=True, initial_value=False,
                     pin_factory=None):
            super().__init__(pin, pin_factory=pin_factory)
            self._lock = Lock()
            self.active_high = active_high
            if initial_value is None:
                self.pin.function = 'output'
            else:
                self.pin.output_with_state(self._value_to_state(initial_value))

        def _value_to_state(self, value):
            return bool(self._active_state if value else self._inactive_state)

        def _write(self, value):
            try:
                self.pin.state = self._value_to_state(value)
            except AttributeError:
                self._check_open()
                raise

        def on(self):
            """
            Turns the device on.
            """
            self._write(True)

        def off(self):
            """
            Turns the device off.
            """
            self._write(False)

        def toggle(self):
            """
            Reverse the state of the device. If it's on, turn it off; if it's off,
            turn it on.
            """
            with self._lock:
                if self.is_active:
                    self.off()
                else:
                    self.on()

        @property
        def value(self):
            """
            Returns 1 if the device is currently active and 0 otherwise. Setting
            this property changes the state of the device.
            """
            return super().value

        @value.setter
        def value(self, value):
            self._write(value)

        @property
        def active_high(self):
            """
            When :data:`True`, the :attr:`value` property is :data:`True` when the
            device's :attr:`~GPIODevice.pin` is high. When :data:`False` the
            :attr:`value` property is :data:`True` when the device's pin is low
            (i.e. the value is inverted).

            This property can be set after construction; be warned that changing it
            will invert :attr:`value` (i.e. changing this property doesn't change
            the device's pin state - it just changes how that state is
            interpreted).
            """
            return self._active_state

        @active_high.setter
        def active_high(self, value):
            self._active_state = True if value else False
            self._inactive_state = False if value else True

        def __repr__(self):
            try:
                return (
                    f'<gpiozero.{self.__class__.__name__} object on pin '
                    f'{self.pin!r}, active_high={self.active_high}, '
                    f'is_active={self.is_active}>')
            except:
                return super().__repr__()

    class DigitalOutputDevice(OutputDevice):
        """
        Represents a generic output device with typical on/off behaviour.

        This class extends :class:`OutputDevice` with a :meth:`blink` method which
        uses an optional background thread to handle toggling the device state
        without further interaction.

        :type pin: int or str
        :param pin:
            The GPIO pin that the device is connected to. See :ref:`pin-numbering`
            for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
            will be raised.

        :param bool active_high:
            If :data:`True` (the default), the :meth:`on` method will set the GPIO
            to HIGH. If :data:`False`, the :meth:`on` method will set the GPIO to
            LOW (the :meth:`off` method always does the opposite).

        :type initial_value: bool or None
        :param initial_value:
            If :data:`False` (the default), the device will be off initially.  If
            :data:`None`, the 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).
        """
        def __init__(self, pin=None, *, active_high=True, initial_value=False,
                     pin_factory=None):
            self._blink_thread = None
            self._controller = None
            super().__init__(pin, active_high=active_high,
                             initial_value=initial_value, pin_factory=pin_factory)

        @property
        def value(self):
            return super().value

        @value.setter
        def value(self, value):
            self._stop_blink()
            self._write(value)

        def close(self):
            self._stop_blink()
            super().close()

        def on(self):
            self._stop_blink()
            self._write(True)

        def off(self):
            self._stop_blink()
            self._write(False)

        def blink(self, on_time=1, off_time=1, n=None, background=True):
            """
            Make the device 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.

            :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).
            """
            self._stop_blink()
            self._blink_thread = GPIOThread(
                self._blink_device, (on_time, off_time, n))
            self._blink_thread.start()
            if not background:
                self._blink_thread.join()
                self._blink_thread = None

        def _stop_blink(self):
            if getattr(self, '_controller', None):
                self._controller._stop_blink(self)
            self._controller = None
            if getattr(self, '_blink_thread', None):
                self._blink_thread.stop()
            self._blink_thread = None

        def _blink_device(self, on_time, off_time, n):
            iterable = repeat(0) if n is None else repeat(0, n)
            for _ in iterable:
                self._write(True)
                if self._blink_thread.stopping.wait(on_time):
                    break
                self._write(False)
                if self._blink_thread.stopping.wait(off_time):
                    break

    class LED(DigitalOutputDevice):
        """
        Extends :class:`DigitalOutputDevice` and represents a light emitting diode
        (LED).

        Connect the cathode (short leg, flat side) of the LED to a ground pin;
        connect the anode (longer leg) to a limiting resistor; connect the other
        side of the limiting resistor to a GPIO pin (the limiting resistor can be
        placed either side of the LED).

        The following example will light the LED::

            from gpiozero import LED

            led = LED(17)
            led.on()

        :type pin: int or str
        :param pin:
            The GPIO pin which the LED is connected to. See :ref:`pin-numbering`
            for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
            will be raised.

        :param bool active_high:
            If :data:`True` (the default), the LED will operate normally with the
            circuit described above. If :data:`False` you should wire the cathode
            to the GPIO pin, and the anode to a 3V3 pin (via a limiting resistor).

        :type initial_value: bool or None
        :param initial_value:
            If :data:`False` (the default), the LED will be off initially.  If
            :data:`None`, the 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 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).
        """
        pass

    LED.is_lit = LED.is_active

    class Buzzer(DigitalOutputDevice):
        """
        Extends :class:`DigitalOutputDevice` and represents a digital buzzer
        component.

        .. note::

            This interface is only capable of simple on/off commands, and is not
            capable of playing a variety of tones (see :class:`TonalBuzzer`).

        Connect the cathode (negative pin) of the buzzer to a ground pin; connect
        the other side to any GPIO pin.

        The following example will sound the buzzer::

            from gpiozero import Buzzer

            bz = Buzzer(3)
            bz.on()

        :type pin: int or str
        :param pin:
            The GPIO pin which the buzzer is connected to. See :ref:`pin-numbering`
            for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
            will be raised.

        :param bool active_high:
            If :data:`True` (the default), the buzzer will operate normally with
            the circuit described above. If :data:`False` you should wire the
            cathode to the GPIO pin, and the anode to a 3V3 pin.

        :type initial_value: bool or None
        :param initial_value:
            If :data:`False` (the default), the buzzer will be silent initially. If
            :data:`None`, the buzzer will be left in whatever state the pin is
            found in when configured for output (warning: this can be on). If
            :data:`True`, the buzzer 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).
        """
        pass

    Buzzer.beep = Buzzer.blink

    class PWMOutputDevice(OutputDevice):
        """
        Generic output device configured for pulse-width modulation (PWM).

        :type pin: int or str
        :param pin:
            The GPIO pin that the device is connected to. See :ref:`pin-numbering`
            for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
            will be raised.

        :param bool active_high:
            If :data:`True` (the default), the :meth:`on` method will set the GPIO
            to HIGH. If :data:`False`, the :meth:`on` method will set the GPIO to
            LOW (the :meth:`off` method always does the opposite).

        :param float initial_value:
            If 0 (the default), the device's duty cycle will be 0 initially.
            Other values between 0 and 1 can be specified as an initial duty cycle.
            Note that :data:`None` cannot be specified (unlike the parent class) as
            there is no way to tell PWM not to alter the state of the pin.

        :param int frequency:
            The frequency (in Hz) of pulses emitted to drive the device. Defaults
            to 100Hz.

        :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, pin=None, *, active_high=True, initial_value=0,
                     frequency=100, pin_factory=None):
            self._blink_thread = None
            self._controller = None
            if not 0 <= initial_value <= 1:
                raise OutputDeviceBadValue("initial_value must be between 0 and 1")
            super().__init__(pin, active_high=active_high, initial_value=None,
                             pin_factory=pin_factory)
            try:
                # XXX need a way of setting these together
                self.pin.frequency = frequency
                self.value = initial_value
            except:
                self.close()
                raise

        def close(self):
            try:
                self._stop_blink()
            except AttributeError:
                pass
            try:
                self.pin.frequency = None
            except AttributeError:
                # If the pin's already None, ignore the exception
                pass
            super().close()

        def _state_to_value(self, state):
            return float(state if self.active_high else 1 - state)

        def _value_to_state(self, value):
            return float(value if self.active_high else 1 - value)

        def _write(self, value):
            if not 0 <= value <= 1:
                raise OutputDeviceBadValue("PWM value must be between 0 and 1")
            super()._write(value)

        @property
        def value(self):
            """
            The duty cycle of the PWM device. 0.0 is off, 1.0 is fully on. Values
            in between may be specified for varying levels of power in the device.
            """
            return super().value

        @value.setter
        def value(self, value):
            self._stop_blink()
            self._write(value)

        def on(self):
            self._stop_blink()
            self._write(1)

        def off(self):
            self._stop_blink()
            self._write(0)

        def toggle(self):
            """
            Toggle the state of the device. If the device is currently off
            (:attr:`value` is 0.0), this changes it to "fully" on (:attr:`value` is
            1.0).  If the device has a duty cycle (:attr:`value`) of 0.1, this will
            toggle it to 0.9, and so on.
            """
            self._stop_blink()
            self.value = 1 - self.value

        @property
        def is_active(self):
            """
            Returns :data:`True` if the device is currently active (:attr:`value`
            is non-zero) and :data:`False` otherwise.
            """
            return self.value != 0

        @property
        def frequency(self):
            """
            The frequency of the pulses used with the PWM device, in Hz. The
            default is 100Hz.
            """
            return self.pin.frequency

        @frequency.setter
        def frequency(self, value):
            self.pin.frequency = value

        def blink(
                self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0,
                n=None, background=True):
            """
            Make the device 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.

            :param float fade_out_time:
                Number of seconds to spend fading out. Defaults to 0.

            :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).
            """
            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 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.

            :type n: int or None
            :param n:
                Number of times to pulse; :data:`None` (the default) means forever.

            :param bool background:
                If :data:`True` (the default), start a background thread to
                continue pulsing and return immediately. If :data:`False`, only
                return when the pulse 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 _stop_blink(self):
            if self._controller:
                self._controller._stop_blink(self)
                self._controller = None
            if self._blink_thread:
                self._blink_thread.stop()
                self._blink_thread = None

        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))
                    )
            for value, delay in sequence:
                self._write(value)
                if self._blink_thread.stopping.wait(delay):
                    break

    class TonalBuzzer(SourceMixin, CompositeDevice):
        """
        Extends :class:`CompositeDevice` and represents a tonal buzzer.

        :type pin: int or str
        :param pin:
            The GPIO pin which the buzzer is connected to. See :ref:`pin-numbering`
            for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
            will be raised.

        :param float initial_value:
            If :data:`None` (the default), the buzzer will be off initially. Values
            between -1 and 1 can be specified as an initial value for the buzzer.

        :type mid_tone: int or str
        :param mid_tone:
            The tone which is represented the device's middle value (0). The
            default is "A4" (MIDI note 69).

        :param int octaves:
            The number of octaves to allow away from the base note. The default is
            1, meaning a value of -1 goes one octave below the base note, and one
            above, i.e. from A3 to A5 with the default base note of A4.

        :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).

        .. note::

            Note that this class does not currently work with
            :class:`~gpiozero.pins.pigpio.PiGPIOFactory`.
        """

        def __init__(self, pin=None, *, initial_value=None, mid_tone=Tone("A4"),
                     octaves=1, pin_factory=None):
            self._mid_tone = None
            super().__init__(
                pwm_device=PWMOutputDevice(pin=pin, pin_factory=pin_factory),
                pin_factory=pin_factory)
            try:
                self._mid_tone = Tone(mid_tone)
                if not (0 < octaves <= 9):
                    raise ValueError('octaves must be between 1 and 9')
                self._octaves = octaves
                try:
                    self.min_tone.note
                except ValueError:
                    raise ValueError(
                        f'{self._mid_tone!r} is too low for {self._octaves} '
                        f'octaves')
                try:
                    self.max_tone.note
                except ValueError:
                    raise ValueError(
                        f'{self._mid_tone!r} is too high for {self._octaves} '
                        f'octaves')
                self.value = initial_value
            except:
                self.close()
                raise

        def __repr__(self):
            try:
                self._check_open()
                if self.value is None:
                    return (
                        f'<gpiozero.{self.__class__.__name__} object on pin '
                        f'{self.pwm_device.pin!r}, silent>')
                else:
                    return (
                        f'<gpiozero.{self.__class__.__name__} object on pin '
                        f'{self.pwm_device.pin!r}, playing {self.tone.note}>')
            except DeviceClosed:
                return super().__repr__()

        def play(self, tone):
            """
            Play the given *tone*. This can either be an instance of
            :class:`~gpiozero.tones.Tone` or can be anything that could be used to
            construct an instance of :class:`~gpiozero.tones.Tone`.

            For example::

                >>> from gpiozero import TonalBuzzer
                >>> from gpiozero.tones import Tone
                >>> b = TonalBuzzer(17)
                >>> b.play(Tone("A4"))
                >>> b.play(Tone(220.0)) # Hz
                >>> b.play(Tone(60)) # middle C in MIDI notation
                >>> b.play("A4")
                >>> b.play(220.0)
                >>> b.play(60)
            """
            if tone is None:
                self.value = None
            else:
                if not isinstance(tone, Tone):
                    tone = Tone(tone)
                freq = tone.frequency
                if self.min_tone.frequency <= tone <= self.max_tone.frequency:
                    self.pwm_device.pin.frequency = freq
                    self.pwm_device.value = 0.5
                else:
                    raise ValueError("tone is out of the device's range")

        def stop(self):
            """
            Turn the buzzer off. This is equivalent to setting :attr:`value` to
            :data:`None`.
            """
            self.value = None

        @property
        def tone(self):
            """
            Returns the :class:`~gpiozero.tones.Tone` that the buzzer is currently
            playing, or :data:`None` if the buzzer is silent. This property can
            also be set to play the specified tone.
            """
            if self.pwm_device.pin.frequency is None:
                return None
            else:
                return Tone.from_frequency(self.pwm_device.pin.frequency)

        @tone.setter
        def tone(self, value):
            self.play(value)

        @property
        def value(self):
            """
            Represents the state of the buzzer as a value between -1 (representing
            the minimum tone) and 1 (representing the maximum tone). This can also
            be the special value :data:`None` indicating that the buzzer is
            currently silent.
            """
            if self.pwm_device.pin.frequency is None:
                return None
            else:
                # Can't have zero-division here; zero-frequency Tone cannot be
                # generated and self.octaves cannot be zero either
                return log2(
                    self.pwm_device.pin.frequency / self.mid_tone.frequency
                ) / self.octaves

        @value.setter
        def value(self, value):
            if value is None:
                self.pwm_device.pin.frequency = None
            elif -1 <= value <= 1:
                freq = self.mid_tone.frequency * 2 ** (self.octaves * value)
                self.pwm_device.pin.frequency = freq
                self.pwm_device.value = 0.5
            else:
                raise OutputDeviceBadValue(
                    'TonalBuzzer value must be between -1 and 1, or None')

        @property
        def is_active(self):
            """
            Returns :data:`True` if the buzzer is currently playing, otherwise
            :data:`False`.
            """
            return self.value is not None

        @property
        def octaves(self):
            """
            The number of octaves available (above and below mid_tone).
            """
            return self._octaves

        @property
        def min_tone(self):
            """
            The lowest tone that the buzzer can play, i.e. the tone played
            when :attr:`value` is -1.
            """
            return self._mid_tone.down(12 * self.octaves)

        @property
        def mid_tone(self):
            """
            The middle tone available, i.e. the tone played when :attr:`value` is
            0.
            """
            return self._mid_tone

        @property
        def max_tone(self):
            """
            The highest tone that the buzzer can play, i.e. the tone played when
            :attr:`value` is 1.
            """
            return self._mid_tone.up(12 * self.octaves)

    class PWMLED(PWMOutputDevice):
        """
        Extends :class:`PWMOutputDevice` and represents a light emitting diode
        (LED) with variable brightness.

        A typical configuration of such a device is to connect a GPIO pin to the
        anode (long leg) of the LED, and the cathode (short leg) to ground, with
        an optional resistor to prevent the LED from burning out.

        :type pin: int or str
        :param pin:
            The GPIO pin which the LED is connected to. See :ref:`pin-numbering`
            for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
            will be raised.

        :param bool active_high:
            If :data:`True` (the default), the :meth:`on` method will set the GPIO
            to HIGH. If :data:`False`, the :meth:`on` method will set the GPIO to
            LOW (the :meth:`off` method always does the opposite).

        :param float initial_value:
            If ``0`` (the default), the LED will be off initially. Other values
            between 0 and 1 can be specified as an initial brightness for the LED.
            Note that :data:`None` cannot be specified (unlike the parent class) as
            there is no way to tell PWM not to alter the state of the pin.

        :param int frequency:
            The frequency (in Hz) of pulses emitted to drive the LED. Defaults
            to 100Hz.

        :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).
        """
        pass

    PWMLED.is_lit = PWMLED.is_active

    class RGBLED(SourceMixin, Device):
        """
        Extends :class:`Device` and represents a full color LED component (composed
        of red, green, and blue LEDs).

        Connect the common cathode (longest leg) to a ground pin; connect each of
        the other legs (representing the red, green, and blue anodes) to any GPIO
        pins.  You should use three limiting resistors (one per anode).

        The following code will make the LED yellow::

            from gpiozero import RGBLED

            led = RGBLED(2, 3, 4)
            led.color = (1, 1, 0)

        The `colorzero`_ library is also supported::

            from gpiozero import RGBLED
            from colorzero import Color

            led = RGBLED(2, 3, 4)
            led.color = Color('yellow')

        :type red: int or str
        :param red:
            The GPIO pin that controls the red component of the RGB LED. See
            :ref:`pin-numbering` for valid pin numbers. If this is :data:`None` a
            :exc:`GPIODeviceError` will be raised.

        :type green: int or str
        :param green:
            The GPIO pin that controls the green component of the RGB LED.

        :type blue: int or str
        :param blue:
            The GPIO pin that controls the blue component of the RGB LED.

        :param bool active_high:
            Set to :data:`True` (the default) for common cathode RGB LEDs. If you
            are using a common anode RGB LED, set this to :data:`False`.

        :type initial_value: ~colorzero.Color or tuple
        :param initial_value:
            The initial color for the RGB LED. Defaults to black ``(0, 0, 0)``.

        :param bool pwm:
            If :data:`True` (the default), construct :class:`PWMLED` instances for
            each component of the RGBLED. 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).

        .. _colorzero: https://colorzero.readthedocs.io/
        """
        def __init__(self, red=None, green=None, blue=None, *, active_high=True,
                     initial_value=(0, 0, 0), pwm=True, pin_factory=None):
            self._leds = ()
            self._blink_thread = None
            if not all(p is not None for p in [red, green, blue]):
                raise GPIOPinMissing('red, green, and blue pins must be provided')
            LEDClass = PWMLED if pwm else LED
            super().__init__(pin_factory=pin_factory)
            self._leds = tuple(
                LEDClass(pin, active_high=active_high, pin_factory=pin_factory)
                for pin in (red, green, blue))
            self.value = initial_value

        def close(self):
            if getattr(self, '_leds', None):
                self._stop_blink()
                for led in self._leds:
                    led.close()
            self._leds = ()
            super().close()

        @property
        def closed(self):
            return len(self._leds) == 0

        @property
        def value(self):
            """
            Represents the color of the LED as an RGB 3-tuple of ``(red, green,
            blue)`` where each value is between 0 and 1 if *pwm* was :data:`True`
            when the class was constructed (and only 0 or 1 if not).

            For example, red would be ``(1, 0, 0)`` and yellow would be ``(1, 1,
            0)``, while orange would be ``(1, 0.5, 0)``.
            """
            return tuple(led.value for led in self._leds)

        @value.setter
        def value(self, value):
            for component in value:
                if not 0 <= component <= 1:
                    raise OutputDeviceBadValue(
                        'each RGB color component must be between 0 and 1')
                if isinstance(self._leds[0], LED):
                    if component not in (0, 1):
                        raise OutputDeviceBadValue(
                            'each RGB color component must be 0 or 1 with non-PWM '
                            'RGBLEDs')
            self._stop_blink()
            for led, v in zip(self._leds, value):
                led.value = v

        @property
        def is_active(self):
            """
            Returns :data:`True` if the LED is currently active (not black) and
            :data:`False` otherwise.
            """
            return self.value != (0, 0, 0)

        is_lit = is_active

        @property
        def color(self):
            """
            Represents the color of the LED as a :class:`~colorzero.Color` object.
            """
            return Color(*self.value)

        @color.setter
        def color(self, value):
            self.value = value

        @property
        def red(self):
            """
            Represents the red element of the LED as a :class:`~colorzero.Red`
            object.
            """
            return self.color.red

        @red.setter
        def red(self, value):
            self._stop_blink()
            r, g, b = self.value
            self.value = value, g, b

        @property
        def green(self):
            """
            Represents the green element of the LED as a :class:`~colorzero.Green`
            object.
            """
            return self.color.green

        @green.setter
        def green(self, value):
            self._stop_blink()
            r, g, b = self.value
            self.value = r, value, b

        @property
        def blue(self):
            """
            Represents the blue element of the LED as a :class:`~colorzero.Blue`
            object.
            """
            return self.color.blue

        @blue.setter
        def blue(self, value):
            self._stop_blink()
            r, g, b = self.value
            self.value = r, g, value

        def on(self):
            """
            Turn the LED on. This equivalent to setting the LED color to white
            ``(1, 1, 1)``.
            """
            self.value = (1, 1, 1)

        def off(self):
            """
            Turn the LED off. This is equivalent to setting the LED color to black
            ``(0, 0, 0)``.
            """
            self.value = (0, 0, 0)

        def toggle(self):
            """
            Toggle the state of the device. If the device is currently off
            (:attr:`value` is ``(0, 0, 0)``), this changes it to "fully" on
            (:attr:`value` is ``(1, 1, 1)``).  If the device has a specific color,
            this method inverts the color.
            """
            r, g, b = self.value
            self.value = (1 - r, 1 - g, 1 - b)

        def blink(
                self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0,
                on_color=(1, 1, 1), off_color=(0, 0, 0), n=None, background=True):
            """
            Make the device 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 on_color: ~colorzero.Color or tuple
            :param on_color:
                The color to use when the LED is "on". Defaults to white.

            :type off_color: ~colorzero.Color or tuple
            :param off_color:
                The color to use when the LED is "off". Defaults to black.

            :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).
            """
            if isinstance(self._leds[0], LED):
                if fade_in_time:
                    raise ValueError('fade_in_time must be 0 with non-PWM RGBLEDs')
                if fade_out_time:
                    raise ValueError('fade_out_time must be 0 with non-PWM RGBLEDs')
            self._stop_blink()
            self._blink_thread = GPIOThread(
                self._blink_device,
                (
                    on_time, off_time, fade_in_time, fade_out_time,
                    on_color, off_color, n
                )
            )
            self._blink_thread.start()
            if not background:
                self._blink_thread.join()
                self._blink_thread = None

        def pulse(
                self, fade_in_time=1, fade_out_time=1,
                on_color=(1, 1, 1), off_color=(0, 0, 0), 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.

            :type on_color: ~colorzero.Color or tuple
            :param on_color:
                The color to use when the LED is "on". Defaults to white.

            :type off_color: ~colorzero.Color or tuple
            :param off_color:
                The color to use when the LED is "off". Defaults to black.

            :type n: int or None
            :param n:
                Number of times to pulse; :data:`None` (the default) means forever.

            :param bool background:
                If :data:`True` (the default), start a background thread to
                continue pulsing and return immediately. If :data:`False`, only
                return when the pulse 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,
                on_color, off_color, n, background
            )

        def _stop_blink(self, led=None):
            # If this is called with a single led, we stop all blinking anyway
            if self._blink_thread:
                self._blink_thread.stop()
                self._blink_thread = None

        def _blink_device(
                self, on_time, off_time, fade_in_time, fade_out_time, on_color,
                off_color, n, fps=25):
            # Define a simple lambda to perform linear interpolation between
            # off_color and on_color
            lerp = lambda t, fade_in: tuple(
                (1 - t) * off + t * on
                if fade_in else
                (1 - t) * on + t * off
                for off, on in zip(off_color, on_color)
                )
            sequence = []
            if fade_in_time > 0:
                sequence += [
                    (lerp(i * (1 / fps) / fade_in_time, True), 1 / fps)
                    for i in range(int(fps * fade_in_time))
                    ]
            sequence.append((on_color, on_time))
            if fade_out_time > 0:
                sequence += [
                    (lerp(i * (1 / fps) / fade_out_time, False), 1 / fps)
                    for i in range(int(fps * fade_out_time))
                    ]
            sequence.append((off_color, off_time))
            sequence = (
                    cycle(sequence) if n is None else
                    chain.from_iterable(repeat(sequence, n))
                    )
            for l in self._leds:
                l._controller = self
            for value, delay in sequence:
                for l, v in zip(self._leds, value):
                    l._write(v)
                if self._blink_thread.stopping.wait(delay):
                    break

    class Motor(SourceMixin, CompositeDevice):
        """
        Extends :class:`CompositeDevice` and represents a generic motor
        connected to a bi-directional motor driver circuit (i.e. an `H-bridge`_).

        Attach an `H-bridge`_ motor controller to your Pi; connect a power source
        (e.g. a battery pack or the 5V pin) to the controller; connect the outputs
        of the controller board to the two terminals of the motor; connect the
        inputs of the controller board to two GPIO pins.

        .. _H-bridge: https://en.wikipedia.org/wiki/H_bridge

        The following code will make the motor turn "forwards"::

            from gpiozero import Motor

            motor = Motor(17, 18)
            motor.forward()

        :type forward: int or str
        :param forward:
            The GPIO pin that the forward input of the motor driver chip is
            connected to. See :ref:`pin-numbering` for valid pin numbers. If this
            is :data:`None` a :exc:`GPIODeviceError` will be raised.

        :type backward: int or str
        :param backward:
            The GPIO pin that the backward input of the motor driver chip is
            connected to. See :ref:`pin-numbering` for valid pin numbers. If this
            is :data:`None` a :exc:`GPIODeviceError` will be raised.

        :type enable: int or str or None
        :param enable:
            The GPIO pin that enables the motor. Required for *some* motor
            controller boards. See :ref:`pin-numbering` for valid pin numbers.

        :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).
        """
        def __init__(self, forward, backward, *, enable=None, pwm=True,
                     pin_factory=None):
            PinClass = PWMOutputDevice if pwm else DigitalOutputDevice
            devices = OrderedDict((
                ('forward_device', PinClass(forward, pin_factory=pin_factory)),
                ('backward_device', PinClass(backward, pin_factory=pin_factory)),
            ))
            if enable is not None:
                devices['enable_device'] = DigitalOutputDevice(
                    enable,
                    initial_value=True,
                    pin_factory=pin_factory
                )
            super().__init__(_order=devices.keys(), pin_factory=pin_factory, **devices)

        @property
        def value(self):
            """
            Represents the speed of the motor as a floating point value between -1
            (full speed backward) and 1 (full speed forward), with 0 representing
            stopped.
            """
            return self.forward_device.value - self.backward_device.value

        @value.setter
        def value(self, value):
            if not -1 <= value <= 1:
                raise OutputDeviceBadValue("Motor value must be between -1 and 1")
            if value > 0:
                try:
                    self.forward(value)
                except ValueError as e:
                    raise OutputDeviceBadValue(e)
            elif value < 0:
                try:
                   self.backward(-value)
                except ValueError as e:
                    raise OutputDeviceBadValue(e)
            else:
                self.stop()

        @property
        def is_active(self):
            """
            Returns :data:`True` if the motor is currently running and
            :data:`False` otherwise.
            """
            return self.value != 0

        def forward(self, speed=1):
            """
            Drive the motor forwards.

            :param float speed:
                The speed at which the motor should turn. Can be any value between
                0 (stopped) and the default 1 (maximum speed) if *pwm* was
                :data:`True` when the class was constructed (and only 0 or 1 if
                not).
            """
            if not 0 <= speed <= 1:
                raise ValueError('forward speed must be between 0 and 1')
            if isinstance(self.forward_device, DigitalOutputDevice):
                if speed not in (0, 1):
                    raise ValueError(
                        'forward speed must be 0 or 1 with non-PWM Motors')
            self.backward_device.off()
            self.forward_device.value = speed

        def backward(self, speed=1):
            """
            Drive the motor backwards.

            :param float speed:
                The speed at which the motor should turn. Can be any value between
                0 (stopped) and the default 1 (maximum speed) if *pwm* was
                :data:`True` when the class was constructed (and only 0 or 1 if
                not).
            """
            if not 0 <= speed <= 1:
                raise ValueError('backward speed must be between 0 and 1')
            if isinstance(self.backward_device, DigitalOutputDevice):
                if speed not in (0, 1):
                    raise ValueError(
                        'backward speed must be 0 or 1 with non-PWM Motors')
            self.forward_device.off()
            self.backward_device.value = speed

        def reverse(self):
            """
            Reverse the current direction of the motor. If the motor is currently
            idle this does nothing. Otherwise, the motor's direction will be
            reversed at the current speed.
            """
            self.value = -self.value

        def stop(self):
            """
            Stop the motor.
            """
            self.forward_device.off()
            self.backward_device.off()

    class PhaseEnableMotor(SourceMixin, CompositeDevice):
        """
        Extends :class:`CompositeDevice` and represents a generic motor connected
        to a Phase/Enable motor driver circuit; the phase of the driver controls
        whether the motor turns forwards or backwards, while enable controls the
        speed with PWM.

        The following code will make the motor turn "forwards"::

            from gpiozero import PhaseEnableMotor
            motor = PhaseEnableMotor(12, 5)
            motor.forward()

        :type phase: int or str
        :param phase:
            The GPIO pin that the phase (direction) input of the motor driver chip
            is connected to. See :ref:`pin-numbering` for valid pin numbers. If
            this is :data:`None` a :exc:`GPIODeviceError` will be raised.

        :type enable: int or str
        :param enable:
            The GPIO pin that the enable (speed) input of the motor driver chip
            is connected to. See :ref:`pin-numbering` for valid pin numbers. If
            this is :data:`None` a :exc:`GPIODeviceError` will be raised.

        :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).
        """
        def __init__(self, phase, enable, *, pwm=True, pin_factory=None):
            PinClass = PWMOutputDevice if pwm else DigitalOutputDevice
            super().__init__(
                phase_device=DigitalOutputDevice(phase, pin_factory=pin_factory),
                enable_device=PinClass(enable, pin_factory=pin_factory),
                _order=('phase_device', 'enable_device'), pin_factory=pin_factory)

        @property
        def value(self):
            """
            Represents the speed of the motor as a floating point value between -1
            (full speed backward) and 1 (full speed forward).
            """
            return (
                -self.enable_device.value
                if self.phase_device.is_active else
                self.enable_device.value
            )

        @value.setter
        def value(self, value):
            if not -1 <= value <= 1:
                raise OutputDeviceBadValue("Motor value must be between -1 and 1")
            if value > 0:
                self.forward(value)
            elif value < 0:
                self.backward(-value)
            else:
                self.stop()

        @property
        def is_active(self):
            """
            Returns :data:`True` if the motor is currently running and
            :data:`False` otherwise.
            """
            return self.value != 0

        def forward(self, speed=1):
            """
            Drive the motor forwards.

            :param float speed:
                The speed at which the motor should turn. Can be any value between
                0 (stopped) and the default 1 (maximum speed).
            """
            if isinstance(self.enable_device, DigitalOutputDevice):
                if speed not in (0, 1):
                    raise ValueError(
                        'forward speed must be 0 or 1 with non-PWM Motors')
            self.enable_device.off()
            self.phase_device.off()
            self.enable_device.value = speed

        def backward(self, speed=1):
            """
            Drive the motor backwards.

            :param float speed:
                The speed at which the motor should turn. Can be any value between
                0 (stopped) and the default 1 (maximum speed).
            """
            if isinstance(self.enable_device, DigitalOutputDevice):
                if speed not in (0, 1):
                    raise ValueError(
                        'backward speed must be 0 or 1 with non-PWM Motors')
            self.enable_device.off()
            self.phase_device.on()
            self.enable_device.value = speed

        def reverse(self):
            """
            Reverse the current direction of the motor. If the motor is currently
            idle this does nothing. Otherwise, the motor's direction will be
            reversed at the current speed.
            """
            self.value = -self.value

        def stop(self):
            """
            Stop the motor.
            """
            self.enable_device.off()

    class Servo(SourceMixin, CompositeDevice):
        """
        Extends :class:`CompositeDevice` and represents a PWM-controlled servo
        motor connected to a GPIO pin.

        Connect a power source (e.g. a battery pack or the 5V pin) to the power
        cable of the servo (this is typically colored red); connect the ground
        cable of the servo (typically colored black or brown) to the negative of
        your battery pack, or a GND pin; connect the final cable (typically colored
        white or orange) to the GPIO pin you wish to use for controlling the servo.

        The following code will make the servo move between its minimum, maximum,
        and mid-point positions with a pause between each::

            from gpiozero import Servo
            from time import sleep

            servo = Servo(17)

            while True:
                servo.min()
                sleep(1)
                servo.mid()
                sleep(1)
                servo.max()
                sleep(1)

        You can also use the :attr:`value` property to move the servo to a
        particular position, on a scale from -1 (min) to 1 (max) where 0 is the
        mid-point::

            from gpiozero import Servo

            servo = Servo(17)

            servo.value = 0.5

        .. note::

            To reduce servo jitter, use the pigpio pin driver rather than the default
            RPi.GPIO driver (pigpio uses DMA sampling for much more precise edge
            timing). See :ref:`changing-pin-factory` for further information.

        :type pin: int or str
        :param pin:
            The GPIO pin that the servo is connected to. See :ref:`pin-numbering`
            for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
            will be raised.

        :param float initial_value:
            If ``0`` (the default), the device's mid-point will be set initially.
            Other values between -1 and +1 can be specified as an initial position.
            :data:`None` means to start the servo un-controlled (see
            :attr:`value`).

        :param float min_pulse_width:
            The pulse width corresponding to the servo's minimum position. This
            defaults to 1ms.

        :param float max_pulse_width:
            The pulse width corresponding to the servo's maximum position. This
            defaults to 2ms.

        :param float frame_width:
            The length of time between servo control pulses measured in seconds.
            This defaults to 20ms which is a common value for servos.

        :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, pin=None, *, initial_value=0.0, min_pulse_width=1/1000,
                     max_pulse_width=2/1000, frame_width=20/1000,
                     pin_factory=None):
            if min_pulse_width >= max_pulse_width:
                raise ValueError('min_pulse_width must be less than max_pulse_width')
            if max_pulse_width >= frame_width:
                raise ValueError('max_pulse_width must be less than frame_width')
            self._frame_width = frame_width
            self._min_dc = min_pulse_width / frame_width
            self._dc_range = (max_pulse_width - min_pulse_width) / frame_width
            self._min_value = -1
            self._value_range = 2
            super().__init__(
                pwm_device=PWMOutputDevice(
                    pin, frequency=int(1 / frame_width), pin_factory=pin_factory),
                pin_factory=pin_factory
            )

            if PiGPIOFactory is None or not isinstance(self.pin_factory, PiGPIOFactory):
                warnings.warn(PWMSoftwareFallback(
                    'To reduce servo jitter, use the pigpio pin factory.'
                    'See https://gpiozero.readthedocs.io/en/stable/api_output.html#servo for more info'
                ))

            try:
                self.value = initial_value
            except:
                self.close()
                raise

        @property
        def frame_width(self):
            """
            The time between control pulses, measured in seconds.
            """
            return self._frame_width

        @property
        def min_pulse_width(self):
            """
            The control pulse width corresponding to the servo's minimum position,
            measured in seconds.
            """
            return self._min_dc * self.frame_width

        @property
        def max_pulse_width(self):
            """
            The control pulse width corresponding to the servo's maximum position,
            measured in seconds.
            """
            return (self._dc_range * self.frame_width) + self.min_pulse_width

        @property
        def pulse_width(self):
            """
            Returns the current pulse width controlling the servo.
            """
            if self.pwm_device.pin.frequency is None:
                return None
            else:
                return self.pwm_device.pin.state * self.frame_width

        @pulse_width.setter
        def pulse_width(self, value):
            self.pwm_device.pin.state = value / self.frame_width

        def min(self):
            """
            Set the servo to its minimum position.
            """
            self.value = -1

        def mid(self):
            """
            Set the servo to its mid-point position.
            """
            self.value = 0

        def max(self):
            """
            Set the servo to its maximum position.
            """
            self.value = 1

        def detach(self):
            """
            Temporarily disable control of the servo. This is equivalent to
            setting :attr:`value` to :data:`None`.
            """
            self.value = None

        def _get_value(self):
            if self.pwm_device.pin.frequency is None:
                return None
            else:
                return (
                    ((self.pwm_device.pin.state - self._min_dc) / self._dc_range) *
                    self._value_range + self._min_value)

        @property
        def value(self):
            """
            Represents the position of the servo as a value between -1 (the minimum
            position) and +1 (the maximum position). This can also be the special
            value :data:`None` indicating that the servo is currently
            "uncontrolled", i.e. that no control signal is being sent. Typically
            this means the servo's position remains unchanged, but that it can be
            moved by hand.
            """
            result = self._get_value()
            if result is None:
                return result
            else:
                # NOTE: This round() only exists to ensure we don't confuse people
                # by returning 2.220446049250313e-16 as the default initial value
                # instead of 0. The reason _get_value and _set_value are split
                # out is for descendents that require the un-rounded values for
                # accuracy
                return round(result, 14)

        @value.setter
        def value(self, value):
            if value is None:
                self.pwm_device.pin.frequency = None
            elif -1 <= value <= 1:
                self.pwm_device.pin.frequency = int(1 / self.frame_width)
                self.pwm_device.pin.state = (
                    self._min_dc + self._dc_range *
                    ((value - self._min_value) / self._value_range)
                    )
            else:
                raise OutputDeviceBadValue(
                    "Servo value must be between -1 and 1, or None")

        @property
        def is_active(self):
            return self.value is not None

    class AngularServo(Servo):
        """
        Extends :class:`Servo` and represents a rotational PWM-controlled servo
        motor which can be set to particular angles (assuming valid minimum and
        maximum angles are provided to the constructor).

        Connect a power source (e.g. a battery pack or the 5V pin) to the power
        cable of the servo (this is typically colored red); connect the ground
        cable of the servo (typically colored black or brown) to the negative of
        your battery pack, or a GND pin; connect the final cable (typically colored
        white or orange) to the GPIO pin you wish to use for controlling the servo.

        Next, calibrate the angles that the servo can rotate to. In an interactive
        Python session, construct a :class:`Servo` instance. The servo should move
        to its mid-point by default. Set the servo to its minimum value, and
        measure the angle from the mid-point. Set the servo to its maximum value,
        and again measure the angle::

            >>> from gpiozero import Servo
            >>> s = Servo(17)
            >>> s.min() # measure the angle
            >>> s.max() # measure the angle

        You should now be able to construct an :class:`AngularServo` instance
        with the correct bounds::

            >>> from gpiozero import AngularServo
            >>> s = AngularServo(17, min_angle=-42, max_angle=44)
            >>> s.angle = 0.0
            >>> s.angle
            0.0
            >>> s.angle = 15
            >>> s.angle
            15.0

        .. note::

            You can set *min_angle* greater than *max_angle* if you wish to reverse
            the sense of the angles (e.g. ``min_angle=45, max_angle=-45``). This
            can be useful with servos that rotate in the opposite direction to your
            expectations of minimum and maximum.

        :type pin: int or str
        :param pin:
            The GPIO pin that the servo is connected to. See :ref:`pin-numbering`
            for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
            will be raised.

        :param float initial_angle:
            Sets the servo's initial angle to the specified value. The default is
            0. The value specified must be between *min_angle* and *max_angle*
            inclusive. :data:`None` means to start the servo un-controlled (see
            :attr:`value`).

        :param float min_angle:
            Sets the minimum angle that the servo can rotate to. This defaults to
            -90, but should be set to whatever you measure from your servo during
            calibration.

        :param float max_angle:
            Sets the maximum angle that the servo can rotate to. This defaults to
            90, but should be set to whatever you measure from your servo during
            calibration.

        :param float min_pulse_width:
            The pulse width corresponding to the servo's minimum position. This
            defaults to 1ms.

        :param float max_pulse_width:
            The pulse width corresponding to the servo's maximum position. This
            defaults to 2ms.

        :param float frame_width:
            The length of time between servo control pulses measured in seconds.
            This defaults to 20ms which is a common value for servos.

        :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, pin=None, *, initial_angle=0.0, min_angle=-90,
                     max_angle=90, min_pulse_width=1/1000, max_pulse_width=2/1000,
                     frame_width=20/1000, pin_factory=None):
            self._min_angle = min_angle
            self._angular_range = max_angle - min_angle
            if initial_angle is None:
                initial_value = None
            elif ((min_angle <= initial_angle <= max_angle) or
                (max_angle <= initial_angle <= min_angle)):
                initial_value = 2 * ((initial_angle - min_angle) / self._angular_range) - 1
            else:
                raise OutputDeviceBadValue(
                    f"AngularServo angle must be between {min_angle} and "
                    f"{max_angle}, or None")
            super().__init__(pin, initial_value=initial_value,
                             min_pulse_width=min_pulse_width,
                             max_pulse_width=max_pulse_width,
                             frame_width=frame_width, pin_factory=pin_factory)

        @property
        def min_angle(self):
            """
            The minimum angle that the servo will rotate to when :meth:`min` is
            called.
            """
            return self._min_angle

        @property
        def max_angle(self):
            """
            The maximum angle that the servo will rotate to when :meth:`max` is
            called.
            """
            return self._min_angle + self._angular_range

        @property
        def angle(self):
            """
            The position of the servo as an angle measured in degrees. This will
            only be accurate if :attr:`min_angle` and :attr:`max_angle` have been
            set appropriately in the constructor.

            This can also be the special value :data:`None` indicating that the
            servo is currently "uncontrolled", i.e. that no control signal is being
            sent.  Typically this means the servo's position remains unchanged, but
            that it can be moved by hand.
            """
            result = self._get_value()
            if result is None:
                return None
            else:
                # NOTE: Why round(n, 12) here instead of 14? Angle ranges can be
                # much larger than -1..1 so we need a little more rounding to
                # smooth off the rough corners!
                return round(
                    self._angular_range *
                    ((result - self._min_value) / self._value_range) +
                    self._min_angle, 12)

        @angle.setter
        def angle(self, angle):
            if angle is None:
                self.value = None
            elif ((self.min_angle <= angle <= self.max_angle) or
                  (self.max_angle <= angle <= self.min_angle)):
                self.value = (
                    self._value_range *
                    ((angle - self._min_angle) / self._angular_range) +
                    self._min_value)
            else:
                raise OutputDeviceBadValue(
                    f"AngularServo angle must be between {self.min_angle} and "
                    f"{self.max_angle}, or None")