From 8b21599257cbf64e912ab139c7d9fc26ab3e614f Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Sun, 31 Jan 2016 17:17:52 +0000 Subject: [PATCH] Fix #118 ... and fix up active_high values while we're at it ... --- gpiozero/output_devices.py | 84 +++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 70d9b1b..cf9cfab 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -32,8 +32,14 @@ class OutputDevice(SourceMixin, GPIODevice): If ``True`` (the default), the :meth:`on` method will set the GPIO to HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the :meth:`off` method always does the opposite). + + :param bool initial_value: + If ``False`` (the default), the device will be off initially. If + ``None``, the device will be left in whatever state the pin is found in + when configured for output (warning: this can be on). If ``True``, the + device will be switched on initially. """ - def __init__(self, pin=None, active_high=True): + def __init__(self, pin=None, active_high=True, initial_value=False): self._active_high = active_high super(OutputDevice, self).__init__(pin) self._active_state = GPIO.HIGH if active_high else GPIO.LOW @@ -42,6 +48,12 @@ class OutputDevice(SourceMixin, GPIODevice): # NOTE: catch_warnings isn't thread-safe but hopefully no-one's # messing around with GPIO init within background threads... with warnings.catch_warnings(record=True) as w: + # This is horrid, but we can't specify initial=None with setup + if initial_value is None: + GPIO.setup(pin, GPIO.OUT) + else: + GPIO.setup(pin, GPIO.OUT, initial= + [self._inactive_state, self._active_state][bool(initial_value)]) GPIO.setup(pin, GPIO.OUT) # The only warning we want to squash is a RuntimeWarning that is # thrown when setting pins 2 or 3. Anything else should be replayed @@ -56,6 +68,8 @@ class OutputDevice(SourceMixin, GPIODevice): raise def _write(self, value): + if not self.active_high: + value = not value try: GPIO.output(self.pin, bool(value)) except ValueError: @@ -66,13 +80,13 @@ class OutputDevice(SourceMixin, GPIODevice): """ Turns the device on. """ - self._write(self._active_state) + self._write(True) def off(self): """ Turns the device off. """ - self._write(self._inactive_state) + self._write(False) @property def value(self): @@ -103,9 +117,9 @@ class DigitalOutputDevice(OutputDevice): which uses an optional background thread to handle toggling the device state without further interaction. """ - def __init__(self, pin=None, active_high=True): + def __init__(self, pin=None, active_high=True, initial_value=False): self._blink_thread = None - super(DigitalOutputDevice, self).__init__(pin, active_high) + super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value) self._lock = Lock() def close(self): @@ -114,11 +128,11 @@ class DigitalOutputDevice(OutputDevice): def on(self): self._stop_blink() - self._write(self._active_state) + self._write(True) def off(self): self._stop_blink() - self._write(self._inactive_state) + self._write(False) def toggle(self): """ @@ -167,10 +181,10 @@ class DigitalOutputDevice(OutputDevice): def _blink_led(self, on_time, off_time, n): iterable = repeat(0) if n is None else repeat(0, n) for i in iterable: - self._write(self._active_state) + self._write(True) if self._blink_thread.stopping.wait(on_time): break - self._write(self._inactive_state) + self._write(False) if self._blink_thread.stopping.wait(off_time): break @@ -241,19 +255,34 @@ class PWMOutputDevice(OutputDevice): The GPIO pin which the device is attached to. See :doc:`notes` for valid pin numbers. + :param bool active_high: + If ``True`` (the default), the :meth:`on` method will set the GPIO to + HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the + :meth:`off` method always does the opposite). + + :param bool 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 ``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. """ - def __init__(self, pin=None, active_high=True, frequency=100): + def __init__(self, pin=None, active_high=True, initial_value=0, frequency=100): self._pwm = None self._blink_thread = None + if not 0 <= initial_value <= 1: + raise OutputDeviceError("initial_value must be between 0 and 1") super(PWMOutputDevice, self).__init__(pin, active_high) try: self._pwm = GPIO.PWM(self.pin, frequency) - self._pwm.start(0.0) + self._value = initial_value + if not active_high: + initial_value = 1 - initial_value + self._pwm.start(100 * initial_value) self._frequency = frequency - self._value = 0.0 except: self.close() raise @@ -271,9 +300,14 @@ class PWMOutputDevice(OutputDevice): def _read(self): self._check_open() - return self._value + if self.active_high: + return self._value + else: + return 1 - self._value def _write(self, value): + if not self.active_high: + value = 1 - value if not 0 <= value <= 1: raise OutputDeviceError("PWM value must be between 0 and 1") try: @@ -289,26 +323,20 @@ class PWMOutputDevice(OutputDevice): 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. """ - if self.active_high: - return self._read() - else: - return 1 - self._read() + return self._read() @value.setter def value(self, value): self._stop_blink() - if self._active_high: - self._write(value) - else: - self._write(1 - value) + self._write(value) def on(self): self._stop_blink() - self._write(self._active_state) + self._write(1) def off(self): self._stop_blink() - self._write(self._inactive_state) + self._write(0) def toggle(self): """ @@ -386,20 +414,18 @@ class PWMOutputDevice(OutputDevice): def _blink_led( self, on_time, off_time, fade_in_time, fade_out_time, n, fps=50): sequence = [] - if fade_in_time > 0.0: + 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.0, on_time)) - if fade_out_time > 0.0: + 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.0, off_time)) - if not self.active_high: - sequence = [(1 - value, delay) for (value, delay) in sequence] + sequence.append((0, off_time)) sequence = ( cycle(sequence) if n is None else chain.from_iterable(repeat(sequence, n))