From 17a8e15f48ff234f1ffee82b68c1c83f79a55b69 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Thu, 10 Feb 2022 17:56:44 +0000 Subject: [PATCH 01/15] blink and pulse --- picozero/picozero.py | 121 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 102 insertions(+), 19 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 13bc9d9..dffbb1f 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -4,12 +4,58 @@ class PWMChannelAlreadyInUse(Exception): pass +class Cycle: + """ + Internal helper class which cycles through values in a list + """ + def __init__(self, values): + self._values = values + self._index = 0 + + def next(self): + value = self._values[self._index] + self._index += 1 + if self._index == len(self._values): + self._index = 0 + return value + +class AsyncValueChange: + """ + Internal class to control the value of an output device + asynchronously + + :param OutputDevice output_device: + The OutputDevice object you wish to change the value of + + :param sequence: + A 2d list of ((value, seconds), *). + + The output_device's value will be set for the number of + seconds. + + """ + def __init__(self, output_device, sequence): + self._output_device = output_device + + self._sequence = Cycle(sequence) + + self._timer = Timer() + self._set_value() + + def _set_value(self, timer_obj=None): + value, seconds = self._sequence.next() + self._output_device.value = value + self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) + + def stop(self): + self._timer.deinit() + class OutputDevice: def __init__(self, active_high=True, initial_value=False): self.active_high = active_high self._write(initial_value) - self._timer = None + self._async_change = None @property def active_high(self): @@ -35,14 +81,14 @@ def on(self): """ Turns the device on. """ - self._stop_blink() + self._stop_async() self.value = 1 def off(self): """ Turns the device off. """ - self._stop_blink() + self._stop_async() self.value = 0 @property @@ -61,30 +107,32 @@ def toggle(self): else: self.on() - def blink(self, time=1): + def blink(self, on_time=1, off_time=None): """ Make the device turn on and off repeatedly. - :param float time: - The length of time in seconds between on and off. Defaults to 1. + :param float on_time: + The length of time in seconds the device will be on. Defaults to 1. + + :param float off_time: + The length of time in seconds the device will be off. Defaults to 1. """ - self._stop_blink() - self._timer = Timer() - self._timer.init(period=int(time * 1000), mode=Timer.PERIODIC, callback=self._blink_callback) + if off_time is None: + off_time = on_time - def _blink_callback(self, timer_obj): - if self.is_active: - self.value = 0 - else: - self.value = 1 + self._stop_async() + self._start_async([(1,on_time), (0,off_time)]) + + def _start_async(self, sequence): + self._async_change = AsyncValueChange(self, sequence) - def _stop_blink(self): - if self._timer is not None: - self._timer.deinit() - self._timer = None + def _stop_async(self): + if self._async_change is not None: + self._async_change.stop() + self._async_change = None def __del__(self): - self._stop_blink() + self._stop_async() class DigitalOutputDevice(OutputDevice): def __init__(self, pin, active_high=True, initial_value=False): @@ -192,6 +240,41 @@ def _write(self, value): def _read(self): return 1 if super()._read() > 0 else 0 + + def pulse(self, fade_in_time=1, fade_out_time=None, fps=25): + """ + Make the device pulse on and off repeatedly. + + :param float fade_in_time: + The length of time in seconds the device will take to turn on. + Defaults to 1. + + :param float fade_out_time: + The length of time in seconds the device will take to turn off. + Defaults to 1. + + :param int fps: + The frames per second that will be used to calculate the number of + steps between off/on states. Defaults to 25. + """ + self._stop_async() + + if fade_out_time is None: + fade_out_time = fade_in_time + + sequence = [] + if fade_in_time > 0: + sequence += [ + (i * (1 / fps) / fade_in_time, 1 / fps) + for i in range(int(fps * fade_in_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)) + ] + + self._start_async(sequence) # factory for returning an LED def LED(pin, use_pwm=True, active_high=True, initial_value=False): From eb9f2255f24f6d4a1983f1e97df7933f948266c9 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Feb 2022 15:42:22 +0000 Subject: [PATCH 02/15] fix filename --- examples/fade_onboard_led.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 examples/fade_onboard_led.py diff --git a/examples/fade_onboard_led.py b/examples/fade_onboard_led.py new file mode 100644 index 0000000..26613af --- /dev/null +++ b/examples/fade_onboard_led.py @@ -0,0 +1,12 @@ +from picozero import LED +from time import sleep + +led = LED(25) + +while True: + for b in range(0, 100, 1): + led.brightness = b/100 + sleep(0.02) + for b in range(100, 0, -1): + led.brightness = b/100 + sleep(0.02) From 679f7c01e447de57723d86413517b249501e92a3 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Feb 2022 15:42:30 +0000 Subject: [PATCH 03/15] fix filenamee --- examples/fade_onboard_led | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 examples/fade_onboard_led diff --git a/examples/fade_onboard_led b/examples/fade_onboard_led deleted file mode 100644 index 26613af..0000000 --- a/examples/fade_onboard_led +++ /dev/null @@ -1,12 +0,0 @@ -from picozero import LED -from time import sleep - -led = LED(25) - -while True: - for b in range(0, 100, 1): - led.brightness = b/100 - sleep(0.02) - for b in range(100, 0, -1): - led.brightness = b/100 - sleep(0.02) From d9c04d2643c006240a0dcda27391ba9338a4cda9 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Feb 2022 15:42:48 +0000 Subject: [PATCH 04/15] add n times to pulse and blink --- picozero/picozero.py | 111 +++++++++++++++++++++++++++++++++---------- 1 file changed, 86 insertions(+), 25 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index dffbb1f..202473e 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -7,16 +7,23 @@ class PWMChannelAlreadyInUse(Exception): class Cycle: """ Internal helper class which cycles through values in a list + + Returns None when the number of cycles is reached """ - def __init__(self, values): + def __init__(self, values, number_of_cycles=None): self._values = values self._index = 0 + self._number_of_cycles = number_of_cycles def next(self): value = self._values[self._index] self._index += 1 if self._index == len(self._values): - self._index = 0 + self._index = 0 + if self._number_of_cycles is not None: + self._number_of_cycles -= 1 + if self._number_of_cycles == 0: + return None return value class AsyncValueChange: @@ -33,19 +40,27 @@ class AsyncValueChange: The output_device's value will be set for the number of seconds. + :param int n: + The number of times to repeat the sequence. If None, the + sequence will repeat forever. The default is None. """ - def __init__(self, output_device, sequence): + def __init__(self, output_device, sequence, n=None): self._output_device = output_device - self._sequence = Cycle(sequence) + self._sequence = Cycle(sequence, n) self._timer = Timer() self._set_value() def _set_value(self, timer_obj=None): - value, seconds = self._sequence.next() - self._output_device.value = value - self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) + next_seq = self._sequence.next() + if next_seq is not None: + value, seconds = next_seq + self._output_device.value = value + self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) + else: + # the sequence has finished, set the value to 0 + self._output_device.value = 0 def stop(self): self._timer.deinit() @@ -107,7 +122,7 @@ def toggle(self): else: self.on() - def blink(self, on_time=1, off_time=None): + def blink(self, on_time=1, off_time=None, n=None): """ Make the device turn on and off repeatedly. @@ -115,16 +130,21 @@ def blink(self, on_time=1, off_time=None): The length of time in seconds the device will be on. Defaults to 1. :param float off_time: - The length of time in seconds the device will be off. Defaults to 1. + The length of time in seconds the device will be off. If `None`, + it will be the same as ``on_time``. Defaults to `None`. + + :param int n: + The number of times to repeat the blink operation. If None is + specified, the device will continue blinking forever. The default + is None. """ - if off_time is None: - off_time = on_time + off_time = on_time if off_time is None else off_time self._stop_async() - self._start_async([(1,on_time), (0,off_time)]) + self._start_async([(1,on_time), (0,off_time)], n) - def _start_async(self, sequence): - self._async_change = AsyncValueChange(self, sequence) + def _start_async(self, sequence, n=None): + self._async_change = AsyncValueChange(self, sequence, n) def _stop_async(self): if self._async_change is not None: @@ -241,40 +261,80 @@ def _write(self, value): def _read(self): return 1 if super()._read() > 0 else 0 - def pulse(self, fade_in_time=1, fade_out_time=None, fps=25): + def blink(self, on_time=1, off_time=None, fade_in_time=0, fade_out_time=None, n=None, fps=25): """ - Make the device pulse on and off repeatedly. + Make the device turn on and off repeatedly. + :param float on_time: + The length of time in seconds the device will be on. Defaults to 1. + + :param float off_time: + The length of time in seconds the device will be off. If `None`, + it will be the same as ``on_time``. Defaults to `None`. + :param float fade_in_time: - The length of time in seconds the device will take to turn on. - Defaults to 1. + The length of time in seconds to spend fading in. Defaults to 0. :param float fade_out_time: - The length of time in seconds the device will take to turn off. - Defaults to 1. + The length of time in seconds to spend fading out. If `None`, + it will be the same as ``fade_in_time``. Defaults to `None`. + + :param int n: + The number of times to repeat the blink operation. If `None`, the + device will continue blinking forever. The default is `None`. :param int fps: The frames per second that will be used to calculate the number of - steps between off/on states. Defaults to 25. + steps between off/on states when fading. Defaults to 25. """ self._stop_async() - if fade_out_time is None: - fade_out_time = fade_in_time - + off_time = on_time if off_time is None else off_time + fade_out_time = fade_in_time if fade_out_time is None else fade_out_time + + # build the blink sequence sequence = [] + if fade_in_time > 0: sequence += [ (i * (1 / fps) / fade_in_time, 1 / fps) for i in range(int(fps * fade_in_time)) ] + + if on_time > 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)) ] - self._start_async(sequence) + if off_time > 0: + sequence.append((0, off_time)) + + self._start_async(sequence, n) + + def pulse(self, fade_in_time=1, fade_out_time=None, n=None, fps=25): + """ + Make the device pulse on and off repeatedly. + + :param float fade_in_time: + The length of time in seconds to spend fading in. Defaults to 0. + + :param float fade_out_time: + The length of time in seconds to spend fading out. If `None`, + it will be the same as ``fade_in_time``. Defaults to `None`. + + :param int n: + The number of times to repeat the blink operation. If `None`, the + device will continue blinking forever. The default is `None`. + + :param int fps: + The frames per second that will be used to calculate the number of + steps between off/on states when fading. Defaults to 25. + """ + self.blink(on_time=0, off_time=0, fade_in_time=fade_in_time, fade_out_time=fade_out_time, n=n, fps=fps) # factory for returning an LED def LED(pin, use_pwm=True, active_high=True, initial_value=False): @@ -635,3 +695,4 @@ def Speaker(pin, use_tones=True, active_high=True, initial_value=False): return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=initial_value) else: return Buzzer(pin, active_high=active_high, initial_value=initial_value) + From 25f3c21c998ebad97131e6771fe0e6d25256ba63 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sun, 13 Feb 2022 10:05:01 +0000 Subject: [PATCH 05/15] Update picozero.py Updated RGBLED to support blink, pulse and colour cycles, all supporting multiple colours and timings --- picozero/picozero.py | 77 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/picozero/picozero.py b/picozero/picozero.py index 202473e..5b9d6df 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -493,6 +493,7 @@ def __init__(self, red=None, green=None, blue=None, active_high=True, super().__init__(active_high, initial_value) def __del__(self): + self._stop_async() if getattr(self, '_leds', None): self._stop_blink() for led in self._leds: @@ -563,6 +564,7 @@ def on(self): self.value = (1, 1, 1) def off(self): + self._stop_async() self.value = (0, 0, 0) def invert(self): @@ -576,6 +578,81 @@ def toggle(self): self._last = self.value self.value = (0, 0, 0) + def blink(self, on_times=1, fade_times=0, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): + + self._stop_async() + if type(on_times) is not tuple: + on_times = (on_times, ) * len(colors) + if type(fade_times) is not tuple: + fade_times = (fade_times, ) * len(colors) + # If any value is above zero then treat all as 0-255 values + if any(v > 1 for v in sum(colors, ())): + colors = tuple(tuple(self._from_255(v) for v in t) for t in colors) + + # Define a linear interpolation between + # off_color and on_color + + lerp = lambda t, fade_in, color1, color2: tuple( + (1 - t) * off + t * on + if fade_in else + (1 - t) * on + t * off + for off, on in zip(color2, color1) + ) + + sequence = [] + + for c in range(len(colors)): + + if fade_times[c] > 0: + sequence += [ + (lerp(i * (1 / fps) / fade_times[c], True, colors[(c + 1) % len(colors)], colors[c]), 1 / fps) + for i in range(int(fps * fade_times[c])) + ] + + sequence.append((colors[(c + 1) % len(colors)], on_times[c])) + + self._start_async(sequence, n) + + def pulse(self, fade_times=1, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): + """ + 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. + """ + on_times = 0 + self.blink(on_times, fade_times, colors, n, fps) + + def cycle(self, fade_times=1, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, fps=25): + """ + 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. + """ + on_times = 0 + self.blink(on_times, fade_times, colors, n, fps) + class AnalogInputDevice(): def __init__(self, pin, active_high=True, threshold=0.5): self._adc = ADC(pin) From 758c5ffae4610949116e36f5010f6d8d94b16b80 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sun, 13 Feb 2022 13:59:57 +0000 Subject: [PATCH 06/15] Create picozerogen.py Prototype trying a generator (just for RGBLED to see if we like it) --- picozero/picozerogen.py | 732 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 732 insertions(+) create mode 100644 picozero/picozerogen.py diff --git a/picozero/picozerogen.py b/picozero/picozerogen.py new file mode 100644 index 0000000..b29fa73 --- /dev/null +++ b/picozero/picozerogen.py @@ -0,0 +1,732 @@ +from machine import Pin, PWM, Timer, ADC +from time import ticks_ms, sleep + +class PWMChannelAlreadyInUse(Exception): + pass + +class AsyncValueChange: + """ + Internal class to control the value of an output device + asynchronously + :param OutputDevice output_device: + The OutputDevice object you wish to change the value of + :param sequence: + A 2d list of ((value, seconds), *). + + The output_device's value will be set for the number of + seconds. + """ + def __init__(self, output_device, generator): + self._output_device = output_device + + self._generator = generator + + self._timer = Timer() + self._set_value() + + def _set_value(self, timer_obj=None): + try: + next_seq = next(self._generator) + value, seconds = next_seq + self._output_device.value = value + self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) + except StopIteration: + # the sequence has finished, set the value to 0 + self._output_device.value = 0 + + def stop(self): + self._timer.deinit() + + +class OutputDevice: + + def __init__(self, active_high=True, initial_value=False): + self.active_high = active_high + self._write(initial_value) + self._async_change = None + + @property + def active_high(self): + 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 + + @property + def value(self): + """ + Sets or returns a value representing the state of the device. 1 is on, 0 is off. + """ + return self._read() + + @value.setter + def value(self, value): + self._write(value) + + def on(self): + """ + Turns the device on. + """ + self._stop_async() + self.value = 1 + + def off(self): + """ + Turns the device off. + """ + self._stop_async() + self.value = 0 + + @property + def is_active(self): + """ + Returns :data:`True` is the device is on. + """ + return bool(self.value) + + def toggle(self): + """ + If the device is off, turn it on. If it is on, turn it off. + """ + if self.is_active: + self.off() + else: + self.on() + + def blink(self, on_time=1, off_time=None, n=None): + """ + Make the device turn on and off repeatedly. + + :param float on_time: + The length of time in seconds the device will be on. Defaults to 1. + :param float off_time: + The length of time in seconds the device will be off. If `None`, + it will be the same as ``on_time``. Defaults to `None`. + :param int n: + The number of times to repeat the blink operation. If None is + specified, the device will continue blinking forever. The default + is None. + """ + off_time = on_time if off_time is None else off_time + + self._stop_async() + self._start_async([(1,on_time), (0,off_time)], n) + + def _start_async(self, generator): + self._async_change = AsyncValueChange(self, generator) + + def _stop_async(self): + if self._async_change is not None: + self._async_change.stop() + self._async_change = None + + def __del__(self): + self._stop_async() + +class DigitalOutputDevice(OutputDevice): + def __init__(self, pin, active_high=True, initial_value=False): + self._pin = Pin(pin, Pin.OUT) + super().__init__(active_high, initial_value) + + def _value_to_state(self, value): + return int(self._active_state if value else self._inactive_state) + + def _state_to_value(self, state): + return int(bool(state) == self._active_state) + + def _read(self): + return self._state_to_value(self._pin.value()) + + def _write(self, value): + self._pin.value(self._value_to_state(value)) + + +class DigitalLED(DigitalOutputDevice): + """ + Represents a simple LED which can be switched on and off. + :param int pin: + The pin that the device is connected to. + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + :param bool initial_value: + If :data:`False` (the default), the LED will be off initially. If + :data:`True`, the LED will be switched on initially. + """ + pass + +DigitalLED.is_lit = DigitalLED.is_active + +class Buzzer(DigitalOutputDevice): + pass + +Buzzer.beep = Buzzer.blink + +class PWMOutputDevice(OutputDevice): + + PIN_TO_PWM_CHANNEL = ["0A","0B","1A","1B","2A","2B","3A","3B","4A","4B","5A","5B","6A","6B","7A","7B","0A","0B","1A","1B","2A","2B","3A","3B","4A","4B","5A","5B","6A","6B"] + _channels_used = {} + + def __init__(self, pin, freq=100, duty_factor=65025, active_high=True, initial_value=False): + self._check_pwm_channel(pin) + self._pin_num = pin + self._duty_factor = duty_factor + self._pwm = PWM(Pin(pin)) + super().__init__(active_high, initial_value) + + def _check_pwm_channel(self, pin_num): + channel = PWMOutputDevice.PIN_TO_PWM_CHANNEL[pin_num] + if channel in PWMOutputDevice._channels_used.keys(): + raise PWMChannelAlreadyInUse( + f"PWM channel {channel} is already in use by pin {PWMOutputDevice._channels_used[channel]}" + ) + else: + PWMOutputDevice._channels_used[channel] = pin_num + + def _state_to_value(self, state): + return (state if self.active_high else 1 - state) / self._duty_factor + + def _value_to_state(self, value): + return int(self._duty_factor * (value if self.active_high else 1 - value)) + + def _read(self): + return self._state_to_value(self._pwm.duty_u16()) + + def _write(self, value): + self._pwm.duty_u16(self._value_to_state(value)) + + @property + def is_active(self): + return self.value != 0 + + def __del__(self): + del PWMOutputDevice._channels_used[ + PWMOutputDevice.PIN_TO_PWM_CHANNEL[self._pin_num] + ] + +class PWMLED(PWMOutputDevice): + def __init__(self, pin, active_high=True, initial_value=False): + self._brightness = 1 + super().__init__(pin=pin, + active_high=active_high, + initial_value=initial_value) + + @property + def brightness(self): + return self._brightness + + @brightness.setter + def brightness(self, value): + self._brightness = value + self.value = 1 if self._brightness > 0 else 0 + + def _write(self, value): + super()._write(self._brightness * value) + + def _read(self): + return 1 if super()._read() > 0 else 0 + + def blink(self, on_time=1, off_time=None, fade_in_time=0, fade_out_time=None, n=None, fps=25): + """ + Make the device turn on and off repeatedly. + + :param float on_time: + The length of time in seconds the device will be on. Defaults to 1. + :param float off_time: + The length of time in seconds the device will be off. If `None`, + it will be the same as ``on_time``. Defaults to `None`. + :param float fade_in_time: + The length of time in seconds to spend fading in. Defaults to 0. + :param float fade_out_time: + The length of time in seconds to spend fading out. If `None`, + it will be the same as ``fade_in_time``. Defaults to `None`. + :param int n: + The number of times to repeat the blink operation. If `None`, the + device will continue blinking forever. The default is `None`. + :param int fps: + The frames per second that will be used to calculate the number of + steps between off/on states when fading. Defaults to 25. + """ + self._stop_async() + + off_time = on_time if off_time is None else off_time + fade_out_time = fade_in_time if fade_out_time is None else fade_out_time + + # build the blink sequence + sequence = [] + + if fade_in_time > 0: + sequence += [ + (i * (1 / fps) / fade_in_time, 1 / fps) + for i in range(int(fps * fade_in_time)) + ] + + if on_time > 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)) + ] + + if off_time > 0: + sequence.append((0, off_time)) + + self._start_async(sequence, n) + + def pulse(self, fade_in_time=1, fade_out_time=None, n=None, fps=25): + """ + Make the device pulse on and off repeatedly. + + :param float fade_in_time: + The length of time in seconds to spend fading in. Defaults to 0. + :param float fade_out_time: + The length of time in seconds to spend fading out. If `None`, + it will be the same as ``fade_in_time``. Defaults to `None`. + :param int n: + The number of times to repeat the blink operation. If `None`, the + device will continue blinking forever. The default is `None`. + :param int fps: + The frames per second that will be used to calculate the number of + steps between off/on states when fading. Defaults to 25. + """ + self.blink(on_time=0, off_time=0, fade_in_time=fade_in_time, fade_out_time=fade_out_time, n=n, fps=fps) + +# factory for returning an LED +def LED(pin, use_pwm=True, active_high=True, initial_value=False): + """ + Returns an instance of :class:`DigitalLED` or :class:`PWMLED` depending on + the value of `use_pwm` parameter. + :: + from picozero import LED + my_pwm_led = LED(1) + my_digital_led = LED(2, use_pwm=False) + :param int pin: + The pin that the device is connected to. + :param int pin: + If `use_pwm` is :data:`True` (the default), a :class:`PWMLED` will be + returned. If `use_pwm` is :data:`False`, a :class:`DigitalLED` will be + returned. A :class:`PWMLED` can control the brightness of the LED but + uses 1 PWM channel. + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + :param bool initial_value: + If :data:`False` (the default), the device will be off initially. If + :data:`True`, the device will be switched on initially. + """ + if use_pwm: + return PWMLED( + pin=pin, + active_high=active_high, + initial_value=initial_value) + else: + return DigitalLED( + pin=pin, + active_high=active_high, + initial_value=initial_value) + +pico_led = LED(25) + +class DigitalInputDevice: + def __init__(self, pin, pull_up=False, active_state=None, bounce_time=None): + self._pin = Pin( + pin, + mode=Pin.IN, + pull=Pin.PULL_UP if pull_up else Pin.PULL_DOWN) + self._bounce_time = bounce_time + + if active_state is None: + self._active_state = False if pull_up else True + else: + self._active_state = active_state + + self._value = self._state_to_value(self._pin.value()) + + self._when_activated = None + self._when_deactivated = None + + # setup interupt + self._pin.irq(self._pin_change, Pin.IRQ_RISING | Pin.IRQ_FALLING) + + def _state_to_value(self, state): + return int(bool(state) == self._active_state) + + def _pin_change(self, p): + # turn off the interupt + p.irq(handler=None) + + last_state = p.value() + + if self._bounce_time is not None: + # wait for stability + stop = ticks_ms() + (self._bounce_time * 1000) + while ticks_ms() < stop: + # keep checking, reset the stop if the value changes + if p.value() != last_state: + stop = ticks_ms() + self._bounce_time + last_state = p.value() + + # re-enable the interupt + p.irq(self._pin_change, Pin.IRQ_RISING | Pin.IRQ_FALLING) + + # did the value actually changed? + if self.value != self._state_to_value(last_state): + # set the value + self._value = self._state_to_value(self._pin.value()) + + # manage call backs + if self._value and self._when_activated is not None: + self._when_activated() + elif not self._value and self._when_deactivated is not None: + self._when_deactivated() + + @property + def value(self): + return self._value + @property + def is_active(self): + return bool(self.value) + + @property + def is_inactive(self): + return not bool(self.value) + + @property + def when_activated(self): + return self._when_activated + + @when_activated.setter + def when_activated(self, value): + self._when_activated = value + + @property + def when_deactivated(self): + return self._when_deactivated + + @when_activated.setter + def when_deactivated(self, value): + self._when_deactivated = value + + def __del__(self): + self._pin.irq(handler=None) + + +class Switch(DigitalInputDevice): + def __init__(self, pin, pull_up=True, bounce_time=0.02): + super().__init__(pin=pin, pull_up=pull_up, bounce_time=bounce_time) + +Switch.is_closed = Switch.is_active +Switch.is_open = Switch.is_inactive +Switch.when_closed = Switch.when_activated +Switch.when_opened = Switch.when_deactivated + +class Button(Switch): + pass + +Button.is_pressed = Button.is_active +Button.when_pressed = Button.when_activated +Button.when_released = Button.when_deactivated + +class RGBLED(OutputDevice): + def __init__(self, red=None, green=None, blue=None, active_high=True, + initial_value=(0, 0, 0), pwm=True): + self._leds = () + self._last = initial_value + LEDClass = PWMLED if pwm else DigitalLED + self._leds = tuple( + LEDClass(pin, active_high=active_high) + for pin in (red, green, blue)) + super().__init__(active_high, initial_value) + + def __del__(self): + self._stop_async() + if getattr(self, '_leds', None): + self._stop_blink() + for led in self._leds: + led.__del__() + self._leds = () + super().__del__() + + def _write(self, value): + if type(value) is not tuple: + value = (value, ) * 3 + for led, v in zip(self._leds, value): + led.brightness = v + + @property + def value(self): + return tuple(led.brightness for led in self._leds) + + @value.setter + def value(self, value): + self._write(value) + + @property + def is_active(self): + return self.value != (0, 0, 0) + + is_lit = is_active + + def _to_255(self, value): + return round(value * 255) + + def _from_255(self, value): + return 0 if value == 0 else value / 255 + + @property + def color(self): + return tuple(self._to_255(v) for v in self.value) + + @color.setter + def color(self, value): + self._stop_async() + self.value = tuple(self._from_255(v) for v in value) + + @property + def red(self): + return self._to_255(self.value[0]) + + @red.setter + def red(self, value): + r, g, b = self.value + self.value = self._from_255(value), g, b + + @property + def green(self): + return self._to_255(self.value[1]) + + @green.setter + def green(self, value): + r, g, b = self.value + self.value = r, self._from_255(value), b + + @property + def blue(self): + return self._to_255(self.value[2]) + + @blue.setter + def blue(self, value): + r, g, b = self.value + self.value = r, g, self._from_255(value) + + def on(self): + self.value = (1, 1, 1) + + def off(self): + self._stop_async() + self.value = (0, 0, 0) + + def invert(self): + r, g, b = self.value + self.value = (1 - r, 1 - g, 1 - b) + + def toggle(self): + if self.value == (0, 0, 0): + self.value = self._last or (1, 1, 1) + else: + self._last = self.value + self.value = (0, 0, 0) + + def blink(self, on_times=1, fade_times=0, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): + + self._stop_async() + if type(on_times) is not tuple: + on_times = (on_times, ) * len(colors) + if type(fade_times) is not tuple: + fade_times = (fade_times, ) * len(colors) + + # If any value is above zero then treat all as 0-255 values + if any(v > 1 for v in sum(colors, ())): + colors = tuple(tuple(self._from_255(v) for v in t) for t in colors) + + # Define a linear interpolation between + # off_color and on_color + + def generator(on_times, fade_times, colors, n, fps): + lerp = lambda t, fade_in, color1, color2: tuple( + (1 - t) * off + t * on + if fade_in else + (1 - t) * on + t * off + for off, on in zip(color2, color1) + ) + + while n != 0: + for c in range(len(colors)): + if fade_times[c] > 0: + for i in range(int(fps * fade_times[c])): + v = lerp(i * (1 / fps) / fade_times[c], True, colors[(c + 1) % len(colors)], colors[c]) + t = 1 / fps + yield (v, t) + + yield ((colors[(c + 1) % len(colors)], on_times[c])) + + n = n - 1 if n is not None else None + + self._start_async(generator(on_times, fade_times, colors, n, fps)) + + def pulse(self, fade_times=1, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): + """ + 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. + """ + on_times = 0 + self.blink(on_times, fade_times, colors, n, fps) + + def cycle(self, fade_times=1, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, fps=25): + """ + 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. + """ + on_times = 0 + self.blink(on_times, fade_times, colors, n, fps) + +class AnalogInputDevice(): + def __init__(self, pin, active_high=True, threshold=0.5): + self._adc = ADC(pin) + self.active_high = active_high + self.threshold = float(threshold) + + @property + def active_high(self): + 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 + + @property + def value(self): + return self._read() + + @value.setter + def value(self, value): + self._write(value) + + def _state_to_value(self, state): + return (state if self.active_high else 1 - state) / 65535 + + def _value_to_state(self, value): + return int(65535 * (value if self.active_high else 1 - value)) + + def _read(self): + return self._state_to_value(self._adc.read_u16()) + + @property + def threshold(self): + return self._threshold + + @threshold.setter + def threshold(self, value): + self._threshold = float(value) + + @property + def is_active(self): + return self.value > self.threshold + + @property + def voltage(self): + return self.value * 3.3 + + @property + def percent(self): + return int(self.value * 100) + +class Potentiometer(AnalogInputDevice): + pass + +Pot = Potentiometer + +def pico_temp_conversion(voltage): + # Formula for calculating temp from voltage for the onboard temperature sensor + return 27 - (voltage - 0.706)/0.001721 + +class TemperatureSensor(AnalogInputDevice): + def __init__(self, pin, active_high=True, threshold=0.5, conversion=None): + self._conversion = conversion + super().__init__(pin, active_high, threshold) + + @property + def temp(self): + if self._conversion is not None: + return self._conversion(self.voltage) + else: + return None + +pico_temp_sensor = TemperatureSensor(4, True, 0.5, pico_temp_conversion) +TempSensor = TemperatureSensor +Thermistor = TemperatureSensor + +class PWMBuzzer(PWMOutputDevice): + + def __init__(self, pin, freq=440, active_high=True, initial_value=False): + super().__init__( + pin, + freq=freq, + duty_factor=1023, + active_high=active_high, + initial_value=initial_value) + + def play(self, freq=440, duration=1, volume=1): + + self._pwm.freq(freq) + + if volume is not None: + self.value = volume + + if duration is not None: + sleep(duration) + self.value = 0 + + def on(self, freq=None): + if freq is not None: + self._pwm.freq(freq) + + self.value = 1 + + def stop(self): + self.value = 0 + + def __del__(self): + self.stop() + super().__del__() + +PWMBuzzer.volume = PWMBuzzer.value +PWMBuzzer.beep = PWMBuzzer.blink + +def Speaker(pin, use_tones=True, active_high=True, initial_value=False): + if use_tones: + return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=initial_value) + else: + return Buzzer(pin, active_high=active_high, initial_value=initial_value) + From 4bd900b6c9f5fea166a982c600f4dee891918160 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Mon, 14 Feb 2022 07:11:54 +0000 Subject: [PATCH 07/15] Update picozerogen.py Changed so use single instance of AsyncValueChange (avoiding some reentrancy issues when calling from an interrupt). Also used micropython.schedule to further reduce potential reentrancy/memory allocation issues. May need to do more but seems be callable from interrupts in simple scenarios. Should probably just set simple variables on the class during the interrupt. --- picozero/picozerogen.py | 54 ++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/picozero/picozerogen.py b/picozero/picozerogen.py index b29fa73..7ed41f1 100644 --- a/picozero/picozerogen.py +++ b/picozero/picozerogen.py @@ -1,5 +1,6 @@ from machine import Pin, PWM, Timer, ADC from time import ticks_ms, sleep +import micropython class PWMChannelAlreadyInUse(Exception): pass @@ -16,26 +17,30 @@ class AsyncValueChange: The output_device's value will be set for the number of seconds. """ - def __init__(self, output_device, generator): + def __init__(self, output_device): self._output_device = output_device - - self._generator = generator - self._timer = Timer() - self._set_value() + def set_generator(self, generator): + self.stop() + self._generator = generator + self._set_value() + def _set_value(self, timer_obj=None): try: next_seq = next(self._generator) + print(next_seq) value, seconds = next_seq self._output_device.value = value self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) except StopIteration: # the sequence has finished, set the value to 0 + self.stop() self._output_device.value = 0 def stop(self): self._timer.deinit() + print("Stop") class OutputDevice: @@ -43,7 +48,7 @@ class OutputDevice: def __init__(self, active_high=True, initial_value=False): self.active_high = active_high self._write(initial_value) - self._async_change = None + self._async_change = AsyncValueChange(self) @property def active_high(self): @@ -115,12 +120,10 @@ def blink(self, on_time=1, off_time=None, n=None): self._start_async([(1,on_time), (0,off_time)], n) def _start_async(self, generator): - self._async_change = AsyncValueChange(self, generator) + self._async_change.set_generator(generator) def _stop_async(self): - if self._async_change is not None: - self._async_change.stop() - self._async_change = None + self._async_change.stop() def __del__(self): self._stop_async() @@ -443,6 +446,7 @@ def __init__(self, red=None, green=None, blue=None, active_high=True, LEDClass(pin, active_high=active_high) for pin in (red, green, blue)) super().__init__(active_high, initial_value) + self._stop_async() def __del__(self): self._stop_async() @@ -533,8 +537,10 @@ def toggle(self): self._last = self.value self.value = (0, 0, 0) - def blink(self, on_times=1, fade_times=0, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): - + def _blink(self, args): + print("Blink start") + on_times, fade_times, colors, n, fps = args + print(n) self._stop_async() if type(on_times) is not tuple: on_times = (on_times, ) * len(colors) @@ -564,12 +570,23 @@ def generator(on_times, fade_times, colors, n, fps): t = 1 / fps yield (v, t) - yield ((colors[(c + 1) % len(colors)], on_times[c])) + if on_times[c] > 0: + yield ((colors[(c + 1) % len(colors)], on_times[c])) n = n - 1 if n is not None else None - + + self._stop_async() + self._start_async(generator(on_times, fade_times, colors, n, fps)) + + def blink(self, on_times=1, fade_times=0, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): + try: + micropython.schedule(self._blink, (on_times, fade_times, colors, n, fps)) + except: + print("Schedule queue full") + pass # Could raise an exception? + def pulse(self, fade_times=1, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): """ Make the device fade in and out repeatedly. @@ -689,11 +706,11 @@ def temp(self): class PWMBuzzer(PWMOutputDevice): - def __init__(self, pin, freq=440, active_high=True, initial_value=False): + def __init__(self, pin, freq=440, active_high=True, initial_value=False, duty_factor=1023): super().__init__( pin, freq=freq, - duty_factor=1023, + duty_factor=duty_factor, active_high=active_high, initial_value=initial_value) @@ -724,9 +741,8 @@ def __del__(self): PWMBuzzer.volume = PWMBuzzer.value PWMBuzzer.beep = PWMBuzzer.blink -def Speaker(pin, use_tones=True, active_high=True, initial_value=False): +def Speaker(pin, use_tones=True, active_high=True, initial_value=False, duty_factor=1023): if use_tones: - return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=initial_value) + return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=initial_value, duty_factor=duty_factor) else: return Buzzer(pin, active_high=active_high, initial_value=initial_value) - From 612a2f64874dcaf631ae01313ce74a00e61df368 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Mon, 14 Feb 2022 08:05:40 +0000 Subject: [PATCH 08/15] Update picozerogen.py Added more interesting defaults for RGB blink and pulse --- picozero/picozerogen.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/picozero/picozerogen.py b/picozero/picozerogen.py index 7ed41f1..86b9bcc 100644 --- a/picozero/picozerogen.py +++ b/picozero/picozerogen.py @@ -29,7 +29,6 @@ def set_generator(self, generator): def _set_value(self, timer_obj=None): try: next_seq = next(self._generator) - print(next_seq) value, seconds = next_seq self._output_device.value = value self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) @@ -40,7 +39,6 @@ def _set_value(self, timer_obj=None): def stop(self): self._timer.deinit() - print("Stop") class OutputDevice: @@ -538,9 +536,7 @@ def toggle(self): self.value = (0, 0, 0) def _blink(self, args): - print("Blink start") on_times, fade_times, colors, n, fps = args - print(n) self._stop_async() if type(on_times) is not tuple: on_times = (on_times, ) * len(colors) @@ -579,15 +575,14 @@ def generator(on_times, fade_times, colors, n, fps): self._start_async(generator(on_times, fade_times, colors, n, fps)) - def blink(self, on_times=1, fade_times=0, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): + def blink(self, on_times=1, fade_times=0, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, fps=25): try: micropython.schedule(self._blink, (on_times, fade_times, colors, n, fps)) except: - print("Schedule queue full") pass # Could raise an exception? - def pulse(self, fade_times=1, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): + def pulse(self, fade_times=1, colors=((1, 0, 1), (0, 0, 0)), n=None, fps=25): """ Make the device fade in and out repeatedly. :param float fade_in_time: @@ -746,3 +741,4 @@ def Speaker(pin, use_tones=True, active_high=True, initial_value=False, duty_fac return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=initial_value, duty_factor=duty_factor) else: return Buzzer(pin, active_high=active_high, initial_value=initial_value) + From 601f01862237b7288c8522cbca62bc45b66e9cef Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Mon, 14 Feb 2022 08:24:09 +0000 Subject: [PATCH 09/15] Update picozerogen.py Set generator to None on stop. --- picozero/picozerogen.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/picozero/picozerogen.py b/picozero/picozerogen.py index 86b9bcc..22ed872 100644 --- a/picozero/picozerogen.py +++ b/picozero/picozerogen.py @@ -27,18 +27,20 @@ def set_generator(self, generator): self._set_value() def _set_value(self, timer_obj=None): - try: - next_seq = next(self._generator) - value, seconds = next_seq - self._output_device.value = value - self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) - except StopIteration: - # the sequence has finished, set the value to 0 - self.stop() - self._output_device.value = 0 + if self._generator is not None: + try: + next_seq = next(self._generator) + value, seconds = next_seq + self._output_device.value = value + self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) + except StopIteration: + # the sequence has finished, set the value to 0 + self.stop() + self._output_device.value = 0 def stop(self): self._timer.deinit() + self._generator = None class OutputDevice: @@ -580,6 +582,7 @@ def blink(self, on_times=1, fade_times=0, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1 try: micropython.schedule(self._blink, (on_times, fade_times, colors, n, fps)) except: + print("Failed to schedule") pass # Could raise an exception? def pulse(self, fade_times=1, colors=((1, 0, 1), (0, 0, 0)), n=None, fps=25): @@ -741,4 +744,3 @@ def Speaker(pin, use_tones=True, active_high=True, initial_value=False, duty_fac return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=initial_value, duty_factor=duty_factor) else: return Buzzer(pin, active_high=active_high, initial_value=initial_value) - From db1d90e1114e1819f836d555621ec1d7d9802a49 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 18 Feb 2022 16:34:43 +0000 Subject: [PATCH 10/15] refactored to use generator and can wait --- picozero/picozero.py | 246 ++++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 120 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 5b9d6df..ad24d8a 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -3,74 +3,74 @@ class PWMChannelAlreadyInUse(Exception): pass - -class Cycle: - """ - Internal helper class which cycles through values in a list - - Returns None when the number of cycles is reached - """ - def __init__(self, values, number_of_cycles=None): - self._values = values - self._index = 0 - self._number_of_cycles = number_of_cycles - - def next(self): - value = self._values[self._index] - self._index += 1 - if self._index == len(self._values): - self._index = 0 - if self._number_of_cycles is not None: - self._number_of_cycles -= 1 - if self._number_of_cycles == 0: - return None - return value - -class AsyncValueChange: + +class ValueChange: """ Internal class to control the value of an output device - asynchronously :param OutputDevice output_device: The OutputDevice object you wish to change the value of - :param sequence: - A 2d list of ((value, seconds), *). + :param generator: + A generator function which yields a 2d list of + ((value, seconds), *). The output_device's value will be set for the number of seconds. :param int n: The number of times to repeat the sequence. If None, the - sequence will repeat forever. The default is None. + sequence will repeat forever. + + :param bool wait: + If True the ValueChange object will block (wait) until + the sequence has completed. """ - def __init__(self, output_device, sequence, n=None): + def __init__(self, output_device, generator, n, wait): self._output_device = output_device - - self._sequence = Cycle(sequence, n) + self._generator = generator + self._n = n + + self._gen = self._generator() self._timer = Timer() + self._running = True self._set_value() - + + while wait and self._running: + sleep(0.001) + def _set_value(self, timer_obj=None): - next_seq = self._sequence.next() - if next_seq is not None: + + try: + next_seq = next(self._gen) value, seconds = next_seq - self._output_device.value = value + + self._output_device._write(value) self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) - else: - # the sequence has finished, set the value to 0 - self._output_device.value = 0 - + + except StopIteration: + + self._n = self._n - 1 if self._n is not None else None + if self._n == 0: + # its the end, set the value to 0 and stop running + self._output_device.value = 0 + self._running = False + else: + # recreate the generator and start again + self._gen = self._generator() + self._set_value() + def stop(self): self._timer.deinit() + class OutputDevice: def __init__(self, active_high=True, initial_value=False): self.active_high = active_high self._write(initial_value) - self._async_change = None + self._value_changer = None @property def active_high(self): @@ -90,20 +90,19 @@ def value(self): @value.setter def value(self, value): + self._stop_change() self._write(value) def on(self): """ Turns the device on. """ - self._stop_async() self.value = 1 def off(self): """ Turns the device off. """ - self._stop_async() self.value = 0 @property @@ -122,7 +121,7 @@ def toggle(self): else: self.on() - def blink(self, on_time=1, off_time=None, n=None): + def blink(self, on_time=1, off_time=None, n=None, wait=False): """ Make the device turn on and off repeatedly. @@ -130,8 +129,7 @@ def blink(self, on_time=1, off_time=None, n=None): The length of time in seconds the device will be on. Defaults to 1. :param float off_time: - The length of time in seconds the device will be off. If `None`, - it will be the same as ``on_time``. Defaults to `None`. + The length of time in seconds the device will be off. Defaults to 1. :param int n: The number of times to repeat the blink operation. If None is @@ -140,19 +138,19 @@ def blink(self, on_time=1, off_time=None, n=None): """ off_time = on_time if off_time is None else off_time - self._stop_async() - self._start_async([(1,on_time), (0,off_time)], n) - - def _start_async(self, sequence, n=None): - self._async_change = AsyncValueChange(self, sequence, n) - - def _stop_async(self): - if self._async_change is not None: - self._async_change.stop() - self._async_change = None + self.off() + self._start_change(lambda : iter([(1,on_time), (0,off_time)]), n, wait) + + def _start_change(self, generator, n, wait): + self._value_changer = ValueChange(self, generator, n, wait) + + def _stop_change(self): + if self._value_changer is not None: + self._value_changer.stop() + self._value_changer = None def __del__(self): - self._stop_async() + self._stop_change() class DigitalOutputDevice(OutputDevice): def __init__(self, pin, active_high=True, initial_value=False): @@ -261,7 +259,7 @@ def _write(self, value): def _read(self): return 1 if super()._read() > 0 else 0 - def blink(self, on_time=1, off_time=None, fade_in_time=0, fade_out_time=None, n=None, fps=25): + def blink(self, on_time=1, off_time=None, fade_in_time=0, fade_out_time=None, n=None, wait=False, fps=25): """ Make the device turn on and off repeatedly. @@ -286,55 +284,62 @@ def blink(self, on_time=1, off_time=None, fade_in_time=0, fade_out_time=None, n= :param int fps: The frames per second that will be used to calculate the number of steps between off/on states when fading. Defaults to 25. - """ - self._stop_async() + """ + self.off() off_time = on_time if off_time is None else off_time fade_out_time = fade_in_time if fade_out_time is None else fade_out_time - - # build the blink sequence - sequence = [] - - if fade_in_time > 0: - sequence += [ - (i * (1 / fps) / fade_in_time, 1 / fps) - for i in range(int(fps * fade_in_time)) - ] - - if on_time > 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)) - ] - - if off_time > 0: - sequence.append((0, off_time)) + + def blink_generator(): + if fade_in_time > 0: + for s in [ + (i * (1 / fps) / fade_in_time, 1 / fps) + for i in range(int(fps * fade_in_time)) + ]: + yield s + + if on_time > 0: + yield (1, on_time) + + if fade_out_time > 0: + for s in [ + (1 - (i * (1 / fps) / fade_out_time), 1 / fps) + for i in range(int(fps * fade_out_time)) + ]: + yield s + + if off_time > 0: + yield (0, off_time) - self._start_async(sequence, n) + self._start_change(blink_generator, n, wait) - def pulse(self, fade_in_time=1, fade_out_time=None, n=None, fps=25): + def pulse(self, fade_in_time=1, fade_out_time=None, n=None, wait=False, fps=25): """ Make the device pulse on and off repeatedly. :param float fade_in_time: - The length of time in seconds to spend fading in. Defaults to 0. + The length of time in seconds the device will take to turn on. + Defaults to 1. :param float fade_out_time: - The length of time in seconds to spend fading out. If `None`, - it will be the same as ``fade_in_time``. Defaults to `None`. - - :param int n: - The number of times to repeat the blink operation. If `None`, the - device will continue blinking forever. The default is `None`. - + The length of time in seconds the device will take to turn off. + Defaults to 1. + :param int fps: The frames per second that will be used to calculate the number of - steps between off/on states when fading. Defaults to 25. + steps between off/on states. Defaults to 25. + + :param int n: + The number of times to pulse the LED. If None the LED will pulse + forever. Defaults to None. + + :param bool wait: + If True the method will block until the LED stops pulsing. If False + the method will return and the LED is will pulse in the background. + Defaults to False. + """ - self.blink(on_time=0, off_time=0, fade_in_time=fade_in_time, fade_out_time=fade_out_time, n=n, fps=fps) + self.blink(on_time=0, off_time=0, fade_in_time=fade_in_time, fade_out_time=fade_out_time, n=n, wait=wait, fps=fps) # factory for returning an LED def LED(pin, use_pwm=True, active_high=True, initial_value=False): @@ -493,7 +498,6 @@ def __init__(self, red=None, green=None, blue=None, active_high=True, super().__init__(active_high, initial_value) def __del__(self): - self._stop_async() if getattr(self, '_leds', None): self._stop_blink() for led in self._leds: @@ -564,7 +568,6 @@ def on(self): self.value = (1, 1, 1) def off(self): - self._stop_async() self.value = (0, 0, 0) def invert(self): @@ -577,10 +580,11 @@ def toggle(self): else: self._last = self.value self.value = (0, 0, 0) - - def blink(self, on_times=1, fade_times=0, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): + + def blink(self, on_times=1, fade_times=0, colors=((1, 1, 1), (0, 0, 0)), n=None, wait=False, fps=25): + + self.off() - self._stop_async() if type(on_times) is not tuple: on_times = (on_times, ) * len(colors) if type(fade_times) is not tuple: @@ -589,31 +593,32 @@ def blink(self, on_times=1, fade_times=0, colors=((1, 1, 1), (0, 0, 0)), n=None, if any(v > 1 for v in sum(colors, ())): colors = tuple(tuple(self._from_255(v) for v in t) for t in colors) - # Define a linear interpolation between - # off_color and on_color - - lerp = lambda t, fade_in, color1, color2: tuple( - (1 - t) * off + t * on - if fade_in else - (1 - t) * on + t * off - for off, on in zip(color2, color1) - ) - - sequence = [] + def blink_generator(): - for c in range(len(colors)): + # Define a linear interpolation between + # off_color and on_color - if fade_times[c] > 0: - sequence += [ - (lerp(i * (1 / fps) / fade_times[c], True, colors[(c + 1) % len(colors)], colors[c]), 1 / fps) - for i in range(int(fps * fade_times[c])) - ] + lerp = lambda t, fade_in, color1, color2: tuple( + (1 - t) * off + t * on + if fade_in else + (1 - t) * on + t * off + for off, on in zip(color2, color1) + ) - sequence.append((colors[(c + 1) % len(colors)], on_times[c])) + for c in range(len(colors)): + + if fade_times[c] > 0: + for s in [ + (lerp(i * (1 / fps) / fade_times[c], True, colors[(c + 1) % len(colors)], colors[c]), 1 / fps) + for i in range(int(fps * fade_times[c])) + ]: + yield s + + yield ((colors[(c + 1) % len(colors)], on_times[c])) - self._start_async(sequence, n) + self._start_change(sequence, n) - def pulse(self, fade_times=1, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): + def pulse(self, fade_times=1, colors=((1, 1, 1), (0, 0, 0)), n=None, wait=False, fps=25): """ Make the device fade in and out repeatedly. :param float fade_in_time: @@ -631,9 +636,9 @@ def pulse(self, fade_times=1, colors=((1, 1, 1), (0, 0, 0)), n=None, fps=25): Number of times to pulse; :data:`None` (the default) means forever. """ on_times = 0 - self.blink(on_times, fade_times, colors, n, fps) + self.blink(on_times, fade_times, colors, n, wait, fps) - def cycle(self, fade_times=1, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, fps=25): + def cycle(self, fade_times=1, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, wait=False, fps=25): """ Make the device fade in and out repeatedly. :param float fade_in_time: @@ -651,7 +656,8 @@ def cycle(self, fade_times=1, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, Number of times to pulse; :data:`None` (the default) means forever. """ on_times = 0 - self.blink(on_times, fade_times, colors, n, fps) + self.blink(on_times, fade_times, colors, n, wait, fps) + class AnalogInputDevice(): def __init__(self, pin, active_high=True, threshold=0.5): From 86cc8e974905c69227f0965d8c01d38b844ef144 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 18 Feb 2022 16:37:22 +0000 Subject: [PATCH 11/15] removed gen test file --- picozero/picozerogen.py | 746 ---------------------------------------- 1 file changed, 746 deletions(-) delete mode 100644 picozero/picozerogen.py diff --git a/picozero/picozerogen.py b/picozero/picozerogen.py deleted file mode 100644 index 22ed872..0000000 --- a/picozero/picozerogen.py +++ /dev/null @@ -1,746 +0,0 @@ -from machine import Pin, PWM, Timer, ADC -from time import ticks_ms, sleep -import micropython - -class PWMChannelAlreadyInUse(Exception): - pass - -class AsyncValueChange: - """ - Internal class to control the value of an output device - asynchronously - :param OutputDevice output_device: - The OutputDevice object you wish to change the value of - :param sequence: - A 2d list of ((value, seconds), *). - - The output_device's value will be set for the number of - seconds. - """ - def __init__(self, output_device): - self._output_device = output_device - self._timer = Timer() - - def set_generator(self, generator): - self.stop() - self._generator = generator - self._set_value() - - def _set_value(self, timer_obj=None): - if self._generator is not None: - try: - next_seq = next(self._generator) - value, seconds = next_seq - self._output_device.value = value - self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) - except StopIteration: - # the sequence has finished, set the value to 0 - self.stop() - self._output_device.value = 0 - - def stop(self): - self._timer.deinit() - self._generator = None - - -class OutputDevice: - - def __init__(self, active_high=True, initial_value=False): - self.active_high = active_high - self._write(initial_value) - self._async_change = AsyncValueChange(self) - - @property - def active_high(self): - 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 - - @property - def value(self): - """ - Sets or returns a value representing the state of the device. 1 is on, 0 is off. - """ - return self._read() - - @value.setter - def value(self, value): - self._write(value) - - def on(self): - """ - Turns the device on. - """ - self._stop_async() - self.value = 1 - - def off(self): - """ - Turns the device off. - """ - self._stop_async() - self.value = 0 - - @property - def is_active(self): - """ - Returns :data:`True` is the device is on. - """ - return bool(self.value) - - def toggle(self): - """ - If the device is off, turn it on. If it is on, turn it off. - """ - if self.is_active: - self.off() - else: - self.on() - - def blink(self, on_time=1, off_time=None, n=None): - """ - Make the device turn on and off repeatedly. - - :param float on_time: - The length of time in seconds the device will be on. Defaults to 1. - :param float off_time: - The length of time in seconds the device will be off. If `None`, - it will be the same as ``on_time``. Defaults to `None`. - :param int n: - The number of times to repeat the blink operation. If None is - specified, the device will continue blinking forever. The default - is None. - """ - off_time = on_time if off_time is None else off_time - - self._stop_async() - self._start_async([(1,on_time), (0,off_time)], n) - - def _start_async(self, generator): - self._async_change.set_generator(generator) - - def _stop_async(self): - self._async_change.stop() - - def __del__(self): - self._stop_async() - -class DigitalOutputDevice(OutputDevice): - def __init__(self, pin, active_high=True, initial_value=False): - self._pin = Pin(pin, Pin.OUT) - super().__init__(active_high, initial_value) - - def _value_to_state(self, value): - return int(self._active_state if value else self._inactive_state) - - def _state_to_value(self, state): - return int(bool(state) == self._active_state) - - def _read(self): - return self._state_to_value(self._pin.value()) - - def _write(self, value): - self._pin.value(self._value_to_state(value)) - - -class DigitalLED(DigitalOutputDevice): - """ - Represents a simple LED which can be switched on and off. - :param int pin: - The pin that the device is connected to. - :param bool active_high: - If :data:`True` (the default), the :meth:`on` method will set the Pin - to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to - LOW (the :meth:`off` method always does the opposite). - :param bool initial_value: - If :data:`False` (the default), the LED will be off initially. If - :data:`True`, the LED will be switched on initially. - """ - pass - -DigitalLED.is_lit = DigitalLED.is_active - -class Buzzer(DigitalOutputDevice): - pass - -Buzzer.beep = Buzzer.blink - -class PWMOutputDevice(OutputDevice): - - PIN_TO_PWM_CHANNEL = ["0A","0B","1A","1B","2A","2B","3A","3B","4A","4B","5A","5B","6A","6B","7A","7B","0A","0B","1A","1B","2A","2B","3A","3B","4A","4B","5A","5B","6A","6B"] - _channels_used = {} - - def __init__(self, pin, freq=100, duty_factor=65025, active_high=True, initial_value=False): - self._check_pwm_channel(pin) - self._pin_num = pin - self._duty_factor = duty_factor - self._pwm = PWM(Pin(pin)) - super().__init__(active_high, initial_value) - - def _check_pwm_channel(self, pin_num): - channel = PWMOutputDevice.PIN_TO_PWM_CHANNEL[pin_num] - if channel in PWMOutputDevice._channels_used.keys(): - raise PWMChannelAlreadyInUse( - f"PWM channel {channel} is already in use by pin {PWMOutputDevice._channels_used[channel]}" - ) - else: - PWMOutputDevice._channels_used[channel] = pin_num - - def _state_to_value(self, state): - return (state if self.active_high else 1 - state) / self._duty_factor - - def _value_to_state(self, value): - return int(self._duty_factor * (value if self.active_high else 1 - value)) - - def _read(self): - return self._state_to_value(self._pwm.duty_u16()) - - def _write(self, value): - self._pwm.duty_u16(self._value_to_state(value)) - - @property - def is_active(self): - return self.value != 0 - - def __del__(self): - del PWMOutputDevice._channels_used[ - PWMOutputDevice.PIN_TO_PWM_CHANNEL[self._pin_num] - ] - -class PWMLED(PWMOutputDevice): - def __init__(self, pin, active_high=True, initial_value=False): - self._brightness = 1 - super().__init__(pin=pin, - active_high=active_high, - initial_value=initial_value) - - @property - def brightness(self): - return self._brightness - - @brightness.setter - def brightness(self, value): - self._brightness = value - self.value = 1 if self._brightness > 0 else 0 - - def _write(self, value): - super()._write(self._brightness * value) - - def _read(self): - return 1 if super()._read() > 0 else 0 - - def blink(self, on_time=1, off_time=None, fade_in_time=0, fade_out_time=None, n=None, fps=25): - """ - Make the device turn on and off repeatedly. - - :param float on_time: - The length of time in seconds the device will be on. Defaults to 1. - :param float off_time: - The length of time in seconds the device will be off. If `None`, - it will be the same as ``on_time``. Defaults to `None`. - :param float fade_in_time: - The length of time in seconds to spend fading in. Defaults to 0. - :param float fade_out_time: - The length of time in seconds to spend fading out. If `None`, - it will be the same as ``fade_in_time``. Defaults to `None`. - :param int n: - The number of times to repeat the blink operation. If `None`, the - device will continue blinking forever. The default is `None`. - :param int fps: - The frames per second that will be used to calculate the number of - steps between off/on states when fading. Defaults to 25. - """ - self._stop_async() - - off_time = on_time if off_time is None else off_time - fade_out_time = fade_in_time if fade_out_time is None else fade_out_time - - # build the blink sequence - sequence = [] - - if fade_in_time > 0: - sequence += [ - (i * (1 / fps) / fade_in_time, 1 / fps) - for i in range(int(fps * fade_in_time)) - ] - - if on_time > 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)) - ] - - if off_time > 0: - sequence.append((0, off_time)) - - self._start_async(sequence, n) - - def pulse(self, fade_in_time=1, fade_out_time=None, n=None, fps=25): - """ - Make the device pulse on and off repeatedly. - - :param float fade_in_time: - The length of time in seconds to spend fading in. Defaults to 0. - :param float fade_out_time: - The length of time in seconds to spend fading out. If `None`, - it will be the same as ``fade_in_time``. Defaults to `None`. - :param int n: - The number of times to repeat the blink operation. If `None`, the - device will continue blinking forever. The default is `None`. - :param int fps: - The frames per second that will be used to calculate the number of - steps between off/on states when fading. Defaults to 25. - """ - self.blink(on_time=0, off_time=0, fade_in_time=fade_in_time, fade_out_time=fade_out_time, n=n, fps=fps) - -# factory for returning an LED -def LED(pin, use_pwm=True, active_high=True, initial_value=False): - """ - Returns an instance of :class:`DigitalLED` or :class:`PWMLED` depending on - the value of `use_pwm` parameter. - :: - from picozero import LED - my_pwm_led = LED(1) - my_digital_led = LED(2, use_pwm=False) - :param int pin: - The pin that the device is connected to. - :param int pin: - If `use_pwm` is :data:`True` (the default), a :class:`PWMLED` will be - returned. If `use_pwm` is :data:`False`, a :class:`DigitalLED` will be - returned. A :class:`PWMLED` can control the brightness of the LED but - uses 1 PWM channel. - :param bool active_high: - If :data:`True` (the default), the :meth:`on` method will set the Pin - to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to - LOW (the :meth:`off` method always does the opposite). - :param bool initial_value: - If :data:`False` (the default), the device will be off initially. If - :data:`True`, the device will be switched on initially. - """ - if use_pwm: - return PWMLED( - pin=pin, - active_high=active_high, - initial_value=initial_value) - else: - return DigitalLED( - pin=pin, - active_high=active_high, - initial_value=initial_value) - -pico_led = LED(25) - -class DigitalInputDevice: - def __init__(self, pin, pull_up=False, active_state=None, bounce_time=None): - self._pin = Pin( - pin, - mode=Pin.IN, - pull=Pin.PULL_UP if pull_up else Pin.PULL_DOWN) - self._bounce_time = bounce_time - - if active_state is None: - self._active_state = False if pull_up else True - else: - self._active_state = active_state - - self._value = self._state_to_value(self._pin.value()) - - self._when_activated = None - self._when_deactivated = None - - # setup interupt - self._pin.irq(self._pin_change, Pin.IRQ_RISING | Pin.IRQ_FALLING) - - def _state_to_value(self, state): - return int(bool(state) == self._active_state) - - def _pin_change(self, p): - # turn off the interupt - p.irq(handler=None) - - last_state = p.value() - - if self._bounce_time is not None: - # wait for stability - stop = ticks_ms() + (self._bounce_time * 1000) - while ticks_ms() < stop: - # keep checking, reset the stop if the value changes - if p.value() != last_state: - stop = ticks_ms() + self._bounce_time - last_state = p.value() - - # re-enable the interupt - p.irq(self._pin_change, Pin.IRQ_RISING | Pin.IRQ_FALLING) - - # did the value actually changed? - if self.value != self._state_to_value(last_state): - # set the value - self._value = self._state_to_value(self._pin.value()) - - # manage call backs - if self._value and self._when_activated is not None: - self._when_activated() - elif not self._value and self._when_deactivated is not None: - self._when_deactivated() - - @property - def value(self): - return self._value - @property - def is_active(self): - return bool(self.value) - - @property - def is_inactive(self): - return not bool(self.value) - - @property - def when_activated(self): - return self._when_activated - - @when_activated.setter - def when_activated(self, value): - self._when_activated = value - - @property - def when_deactivated(self): - return self._when_deactivated - - @when_activated.setter - def when_deactivated(self, value): - self._when_deactivated = value - - def __del__(self): - self._pin.irq(handler=None) - - -class Switch(DigitalInputDevice): - def __init__(self, pin, pull_up=True, bounce_time=0.02): - super().__init__(pin=pin, pull_up=pull_up, bounce_time=bounce_time) - -Switch.is_closed = Switch.is_active -Switch.is_open = Switch.is_inactive -Switch.when_closed = Switch.when_activated -Switch.when_opened = Switch.when_deactivated - -class Button(Switch): - pass - -Button.is_pressed = Button.is_active -Button.when_pressed = Button.when_activated -Button.when_released = Button.when_deactivated - -class RGBLED(OutputDevice): - def __init__(self, red=None, green=None, blue=None, active_high=True, - initial_value=(0, 0, 0), pwm=True): - self._leds = () - self._last = initial_value - LEDClass = PWMLED if pwm else DigitalLED - self._leds = tuple( - LEDClass(pin, active_high=active_high) - for pin in (red, green, blue)) - super().__init__(active_high, initial_value) - self._stop_async() - - def __del__(self): - self._stop_async() - if getattr(self, '_leds', None): - self._stop_blink() - for led in self._leds: - led.__del__() - self._leds = () - super().__del__() - - def _write(self, value): - if type(value) is not tuple: - value = (value, ) * 3 - for led, v in zip(self._leds, value): - led.brightness = v - - @property - def value(self): - return tuple(led.brightness for led in self._leds) - - @value.setter - def value(self, value): - self._write(value) - - @property - def is_active(self): - return self.value != (0, 0, 0) - - is_lit = is_active - - def _to_255(self, value): - return round(value * 255) - - def _from_255(self, value): - return 0 if value == 0 else value / 255 - - @property - def color(self): - return tuple(self._to_255(v) for v in self.value) - - @color.setter - def color(self, value): - self._stop_async() - self.value = tuple(self._from_255(v) for v in value) - - @property - def red(self): - return self._to_255(self.value[0]) - - @red.setter - def red(self, value): - r, g, b = self.value - self.value = self._from_255(value), g, b - - @property - def green(self): - return self._to_255(self.value[1]) - - @green.setter - def green(self, value): - r, g, b = self.value - self.value = r, self._from_255(value), b - - @property - def blue(self): - return self._to_255(self.value[2]) - - @blue.setter - def blue(self, value): - r, g, b = self.value - self.value = r, g, self._from_255(value) - - def on(self): - self.value = (1, 1, 1) - - def off(self): - self._stop_async() - self.value = (0, 0, 0) - - def invert(self): - r, g, b = self.value - self.value = (1 - r, 1 - g, 1 - b) - - def toggle(self): - if self.value == (0, 0, 0): - self.value = self._last or (1, 1, 1) - else: - self._last = self.value - self.value = (0, 0, 0) - - def _blink(self, args): - on_times, fade_times, colors, n, fps = args - self._stop_async() - if type(on_times) is not tuple: - on_times = (on_times, ) * len(colors) - if type(fade_times) is not tuple: - fade_times = (fade_times, ) * len(colors) - - # If any value is above zero then treat all as 0-255 values - if any(v > 1 for v in sum(colors, ())): - colors = tuple(tuple(self._from_255(v) for v in t) for t in colors) - - # Define a linear interpolation between - # off_color and on_color - - def generator(on_times, fade_times, colors, n, fps): - lerp = lambda t, fade_in, color1, color2: tuple( - (1 - t) * off + t * on - if fade_in else - (1 - t) * on + t * off - for off, on in zip(color2, color1) - ) - - while n != 0: - for c in range(len(colors)): - if fade_times[c] > 0: - for i in range(int(fps * fade_times[c])): - v = lerp(i * (1 / fps) / fade_times[c], True, colors[(c + 1) % len(colors)], colors[c]) - t = 1 / fps - yield (v, t) - - if on_times[c] > 0: - yield ((colors[(c + 1) % len(colors)], on_times[c])) - - n = n - 1 if n is not None else None - - self._stop_async() - - self._start_async(generator(on_times, fade_times, colors, n, fps)) - - def blink(self, on_times=1, fade_times=0, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, fps=25): - - try: - micropython.schedule(self._blink, (on_times, fade_times, colors, n, fps)) - except: - print("Failed to schedule") - pass # Could raise an exception? - - def pulse(self, fade_times=1, colors=((1, 0, 1), (0, 0, 0)), n=None, fps=25): - """ - 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. - """ - on_times = 0 - self.blink(on_times, fade_times, colors, n, fps) - - def cycle(self, fade_times=1, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, fps=25): - """ - 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. - """ - on_times = 0 - self.blink(on_times, fade_times, colors, n, fps) - -class AnalogInputDevice(): - def __init__(self, pin, active_high=True, threshold=0.5): - self._adc = ADC(pin) - self.active_high = active_high - self.threshold = float(threshold) - - @property - def active_high(self): - 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 - - @property - def value(self): - return self._read() - - @value.setter - def value(self, value): - self._write(value) - - def _state_to_value(self, state): - return (state if self.active_high else 1 - state) / 65535 - - def _value_to_state(self, value): - return int(65535 * (value if self.active_high else 1 - value)) - - def _read(self): - return self._state_to_value(self._adc.read_u16()) - - @property - def threshold(self): - return self._threshold - - @threshold.setter - def threshold(self, value): - self._threshold = float(value) - - @property - def is_active(self): - return self.value > self.threshold - - @property - def voltage(self): - return self.value * 3.3 - - @property - def percent(self): - return int(self.value * 100) - -class Potentiometer(AnalogInputDevice): - pass - -Pot = Potentiometer - -def pico_temp_conversion(voltage): - # Formula for calculating temp from voltage for the onboard temperature sensor - return 27 - (voltage - 0.706)/0.001721 - -class TemperatureSensor(AnalogInputDevice): - def __init__(self, pin, active_high=True, threshold=0.5, conversion=None): - self._conversion = conversion - super().__init__(pin, active_high, threshold) - - @property - def temp(self): - if self._conversion is not None: - return self._conversion(self.voltage) - else: - return None - -pico_temp_sensor = TemperatureSensor(4, True, 0.5, pico_temp_conversion) -TempSensor = TemperatureSensor -Thermistor = TemperatureSensor - -class PWMBuzzer(PWMOutputDevice): - - def __init__(self, pin, freq=440, active_high=True, initial_value=False, duty_factor=1023): - super().__init__( - pin, - freq=freq, - duty_factor=duty_factor, - active_high=active_high, - initial_value=initial_value) - - def play(self, freq=440, duration=1, volume=1): - - self._pwm.freq(freq) - - if volume is not None: - self.value = volume - - if duration is not None: - sleep(duration) - self.value = 0 - - def on(self, freq=None): - if freq is not None: - self._pwm.freq(freq) - - self.value = 1 - - def stop(self): - self.value = 0 - - def __del__(self): - self.stop() - super().__del__() - -PWMBuzzer.volume = PWMBuzzer.value -PWMBuzzer.beep = PWMBuzzer.blink - -def Speaker(pin, use_tones=True, active_high=True, initial_value=False, duty_factor=1023): - if use_tones: - return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=initial_value, duty_factor=duty_factor) - else: - return Buzzer(pin, active_high=active_high, initial_value=initial_value) From e58084df8d6663c4db7093cfadb3c9109edfafe8 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Fri, 18 Feb 2022 17:13:50 +0000 Subject: [PATCH 12/15] Update picozero.py Fixes to get RGBLED working. Still issues with wait=False. --- picozero/picozero.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index ad24d8a..f36f749 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -506,6 +506,8 @@ def __del__(self): super().__del__() def _write(self, value): + if type(value) is not tuple: + value = (value, ) * 3 for led, v in zip(self._leds, value): led.brightness = v @@ -606,19 +608,18 @@ def blink_generator(): ) for c in range(len(colors)): - if fade_times[c] > 0: - for s in [ - (lerp(i * (1 / fps) / fade_times[c], True, colors[(c + 1) % len(colors)], colors[c]), 1 / fps) - for i in range(int(fps * fade_times[c])) - ]: - yield s - - yield ((colors[(c + 1) % len(colors)], on_times[c])) + for i in range(int(fps * fade_times[c])): + v = lerp(i * (1 / fps) / fade_times[c], True, colors[(c + 1) % len(colors)], colors[c]) + t = 1 / fps + yield (v, t) + + if on_times[c] > 0: + yield (colors[c], on_times[c]) - self._start_change(sequence, n) + self._start_change(blink_generator, n, wait) - def pulse(self, fade_times=1, colors=((1, 1, 1), (0, 0, 0)), n=None, wait=False, fps=25): + def pulse(self, fade_times=1, colors=((0, 0, 0), (255, 0, 0), (0, 0, 0), (0, 255, 0), (0, 0, 0), (0, 0, 255)), n=None, wait=False, fps=25): """ Make the device fade in and out repeatedly. :param float fade_in_time: @@ -658,7 +659,6 @@ def cycle(self, fade_times=1, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, on_times = 0 self.blink(on_times, fade_times, colors, n, wait, fps) - class AnalogInputDevice(): def __init__(self, pin, active_high=True, threshold=0.5): self._adc = ADC(pin) From bfeead7c4305d2780d3ab534984b6448f1d2a08e Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sat, 19 Feb 2022 13:00:04 +0000 Subject: [PATCH 13/15] Update picozero.py Removed override of off and minor updates to RGBLED --- picozero/picozero.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index f36f749..cf2c731 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -517,6 +517,7 @@ def value(self): @value.setter def value(self, value): + self._stop_change() self._write(value) @property @@ -569,9 +570,6 @@ def blue(self, value): def on(self): self.value = (1, 1, 1) - def off(self): - self.value = (0, 0, 0) - def invert(self): r, g, b = self.value self.value = (1 - r, 1 - g, 1 - b) @@ -583,7 +581,7 @@ def toggle(self): self._last = self.value self.value = (0, 0, 0) - def blink(self, on_times=1, fade_times=0, colors=((1, 1, 1), (0, 0, 0)), n=None, wait=False, fps=25): + def blink(self, on_times=1, fade_times=0, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, wait=False, fps=25): self.off() @@ -617,12 +615,13 @@ def blink_generator(): if on_times[c] > 0: yield (colors[c], on_times[c]) + self.off() self._start_change(blink_generator, n, wait) - def pulse(self, fade_times=1, colors=((0, 0, 0), (255, 0, 0), (0, 0, 0), (0, 255, 0), (0, 0, 0), (0, 0, 255)), n=None, wait=False, fps=25): + def pulse(self, fade_times=1, colors=((0, 0, 0), (1, 0, 0), (0, 0, 0), (0, 1, 0), (0, 0, 0), (0, 0, 1)), n=None, wait=False, fps=25): """ Make the device fade in and out repeatedly. - :param float fade_in_time: + :param float fade_in_times: 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. From 5a2c6dafcd8f1f61d3ebfb06f8736e3cbb3134e6 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sat, 19 Feb 2022 13:05:41 +0000 Subject: [PATCH 14/15] Update picozero.py Changed ValueChange to set the timer to None and check for it before init. This seems to fix the issue where previous sequences could still be running (for LED and RGBLED) but not clear why this should be necessary. --- picozero/picozero.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index cf2c731..abb04b8 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -43,11 +43,12 @@ def __init__(self, output_device, generator, n, wait): def _set_value(self, timer_obj=None): try: - next_seq = next(self._gen) - value, seconds = next_seq + if self._timer is not None: + next_seq = next(self._gen) + value, seconds = next_seq - self._output_device._write(value) - self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) + self._output_device._write(value) + self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) except StopIteration: @@ -63,8 +64,8 @@ def _set_value(self, timer_obj=None): def stop(self): self._timer.deinit() + self._timer = None - class OutputDevice: def __init__(self, active_high=True, initial_value=False): @@ -777,4 +778,4 @@ def Speaker(pin, use_tones=True, active_high=True, initial_value=False): return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=initial_value) else: return Buzzer(pin, active_high=active_high, initial_value=initial_value) - + From e3766b0db9b96d3fdef46a6aef717bab222d6d26 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Mon, 21 Feb 2022 09:27:03 +0000 Subject: [PATCH 15/15] bug fix --- picozero/picozero.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index abb04b8..604b38b 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -43,10 +43,10 @@ def __init__(self, output_device, generator, n, wait): def _set_value(self, timer_obj=None): try: - if self._timer is not None: + if self._running: next_seq = next(self._gen) value, seconds = next_seq - + self._output_device._write(value) self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) @@ -63,8 +63,8 @@ def _set_value(self, timer_obj=None): self._set_value() def stop(self): + self._running = False self._timer.deinit() - self._timer = None class OutputDevice: