<commit_before># GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
# Copyright (c) 2015-2019 <NAME> <<EMAIL>>
# Copyright (c) 2015-2019 <NAME> <<EMAIL>>
# Copyright (c) 2019 tuftii <pi<EMAIL>>
# Copyright (c) 2019 tuftii <321504<EMAIL>>
# Copyright (c) 2016 <NAME> <<EMAIL>>
# Copyright (c) 2016 <NAME> <<EMAIL>>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from __future__ import (
    unicode_literals,
    print_function,
    absolute_import,
    division,
)
str = type('')

from threading import Lock
from itertools import repeat, cycle, chain
from colorzero import Color
from collections import OrderedDict
try:
    from math import log2
except ImportError:
    from .compat import log2

from .exc import OutputDeviceBadValue, GPIOPinMissing
from .devices import GPIODevice, Device, CompositeDevice
from .mixins import SourceMixin
from .threads import GPIOThread
from .tones import Tone


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(OutputDevice, self).__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(OutputDevice, self).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 '<gpiozero.%s object on pin %r, active_high=%s, is_active=%s>' % (
                self.__class__.__name__, self.pin, self.active_high, self.is_active)
        except:
            return super(OutputDevice, self).__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(DigitalOutputDevice, self).__init__(
            pin, active_high, initial_value, pin_factory=pin_factory
        )

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

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

    def close(self):
        self._stop_blink()
        super(DigitalOutputDevice, self).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(
            target=self._blink_device, args=(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(PWMOutputDevice, self).__init__(
            pin, 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(PWMOutputDevice, self).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(PWMOutputDevice, self)._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(PWMOutputDevice, self).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(
            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 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_note: int or str
    :param mid_note:
        The note 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(TonalBuzzer, self).__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(
                    '%r is too low for %d octaves' %
                    (self._mid_tone, self._octaves))
            try:
                self.max_tone.note
            except ValueError:
                raise ValueError(
                    '%r is too high for %d octaves' %
                    (self._mid_tone, self._octaves))
            self.value = initial_value
        except:
            self.close()
            raise

    def __repr__(self):
        try:
            if self.value is None:
                return '<gpiozero.TonalBuzzer object on pin %r, silent>' % (
                    self.pwm_device.pin,)
            else:
                return '<gpiozero.TonalBuzzer object on pin %r, playing %s>' % (
                    self.pwm_device.pin, self.tone.note)
        except:
            return super(TonalBuzzer, self).__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 note) and 1 (representing the maximum note). 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:
            try:
                return log2(
                    self.pwm_device.pin.frequency / self.mid_tone.frequency
                ) / self.octaves
            except ZeroDivisionError:
                return 0.0

    @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_note).
        """
        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.
        """<commit_msg>Fix all references to notes with tones<commit_after># GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
# Copyright (c) 2015-2019 <NAME> <<EMAIL>>
# Copyright (c) 2015-2019 <NAME> <<EMAIL>>
# Copyright (c) 2019 tuftii <<EMAIL>>
# Copyright (c) 2019 tuftii <32<EMAIL>>
# Copyright (c) 2016 <NAME> <<EMAIL>>
# Copyright (c) 2016 <NAME> <<EMAIL>>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from __future__ import (
    unicode_literals,
    print_function,
    absolute_import,
    division,
)
str = type('')

from threading import Lock
from itertools import repeat, cycle, chain
from colorzero import Color
from collections import OrderedDict
try:
    from math import log2
except ImportError:
    from .compat import log2

from .exc import OutputDeviceBadValue, GPIOPinMissing
from .devices import GPIODevice, Device, CompositeDevice
from .mixins import SourceMixin
from .threads import GPIOThread
from .tones import Tone


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(OutputDevice, self).__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(OutputDevice, self).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 '<gpiozero.%s object on pin %r, active_high=%s, is_active=%s>' % (
                self.__class__.__name__, self.pin, self.active_high, self.is_active)
        except:
            return super(OutputDevice, self).__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(DigitalOutputDevice, self).__init__(
            pin, active_high, initial_value, pin_factory=pin_factory
        )

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

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

    def close(self):
        self._stop_blink()
        super(DigitalOutputDevice, self).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(
            target=self._blink_device, args=(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(PWMOutputDevice, self).__init__(
            pin, 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(PWMOutputDevice, self).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(PWMOutputDevice, self)._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(PWMOutputDevice, self).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(
            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 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(TonalBuzzer, self).__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(
                    '%r is too low for %d octaves' %
                    (self._mid_tone, self._octaves))
            try:
                self.max_tone.note
            except ValueError:
                raise ValueError(
                    '%r is too high for %d octaves' %
                    (self._mid_tone, self._octaves))
            self.value = initial_value
        except:
            self.close()
            raise

    def __repr__(self):
        try:
            if self.value is None:
                return '<gpiozero.TonalBuzzer object on pin %r, silent>' % (
                    self.pwm_device.pin,)
            else:
                return '<gpiozero.TonalBuzzer object on pin %r, playing %s>' % (
                    self.pwm_device.pin, self.tone.note)
        except:
            return super(TonalBuzzer, self).__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:
            try:
                return log2(
                    self.pwm_device.pin.frequency / self.mid_tone.frequency
                ) / self.octaves
            except ZeroDivisionError:
                return 0.0

    @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.
        """
{'fraction_non_alphanumeric': 0.08419555821655098, 'fraction_numerical': 0.006174340935880405, 'mean_word_length': 2.137540358086293, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 99, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7360406', 'n_tokens_mistral': 15407, 'n_tokens_neox': 14576, 'n_words': 5582}