From 97af83aca765120647c58ec77c65ae71dfba5e44 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Tue, 1 Mar 2022 08:21:16 +0000 Subject: [PATCH 01/34] Create speaker_tune.py --- docs/examples/speaker_tune.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 docs/examples/speaker_tune.py diff --git a/docs/examples/speaker_tune.py b/docs/examples/speaker_tune.py new file mode 100644 index 0000000..c4b879c --- /dev/null +++ b/docs/examples/speaker_tune.py @@ -0,0 +1,12 @@ +from picozero import Speaker + +speaker = Speaker(5) + +tetris = [ ['e4', 1], ['b3', 0.5], ['c4', 0.5], ['d4', 1], ['c4', 0.5], ['b3', 0.5], ['a3', 1], ['a3', 0.5], ['c4', 0.5], ['e4', 1], ['d4', 0.5], ['c4', 0.5], + ['b3', 1.5], ['c4', 0.5], ['d4', 1], ['e4', 1], ['c4', 1], ['a3', 1], ['a3', 2]] + +try: + speaker.play(tetris) + +finally: # Turn speaker off if interrupted + speaker.off() From 5e188dfa0d8e52fc1c4472a815c216a5a1bad75c Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Tue, 1 Mar 2022 08:22:19 +0000 Subject: [PATCH 02/34] Create speaker_notes.py --- docs/examples/speaker_notes.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 docs/examples/speaker_notes.py diff --git a/docs/examples/speaker_notes.py b/docs/examples/speaker_notes.py new file mode 100644 index 0000000..46c2e09 --- /dev/null +++ b/docs/examples/speaker_notes.py @@ -0,0 +1,20 @@ +from picozero import Speaker +from time import sleep + +speaker = Speaker(5, bpm=120) + +tetris = [ ['e4', 1], ['b3', 0.5], ['c4', 0.5], ['d4', 1], ['c4', 0.5], ['b3', 0.5], ['a3', 1], ['a3', 0.5], ['c4', 0.5], ['e4', 1], ['d4', 0.5], ['c4', 0.5], + ['b3', 1.5], ['c4', 0.5], ['d4', 1], ['e4', 1], ['c4', 1], ['a3', 1], ['a3', 2]] + +# Play 'Staccato' half length notes +note_length = 0.5 # play notes for half a beat +gap_length = 1 - note_length # leave half a beat between notes + +try: + for note in tetris: + speaker.play(note, multiplier=note_length) + gap = speaker.to_seconds(note[1]) * gap_length # leave a gap between notes + sleep(gap) + +finally: # Turn speaker off if interrupted + speaker.off() From 8d2d21e74eb691c7e538e7dd9359b72ca4bbf94b Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Tue, 1 Mar 2022 08:24:06 +0000 Subject: [PATCH 03/34] Create rgb_blink.py --- docs/examples/rgb_blink.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 docs/examples/rgb_blink.py diff --git a/docs/examples/rgb_blink.py b/docs/examples/rgb_blink.py new file mode 100644 index 0000000..5c75308 --- /dev/null +++ b/docs/examples/rgb_blink.py @@ -0,0 +1,19 @@ +from picozero import RGBLED +from time import sleep + +rgb = RGBLED(1, 2, 3) + +rgb.blink() # does not wait +sleep(6) +rgb.off() +sleep(1) + +# blink purple 2 seconds, off 0.5 seconds +rgb.blink(on_times=(2, 0.5), colors=((255, 0, 255), (0, 0, 0)), wait=True, n=3) + +rgb.off() +sleep(1) + +# blink red 1 second, green 0.5 seconds, blue 0.25 seconds +rgb.blink((1, 0.5, 0.25), colors=((255, 0, 0), (0, 255, 0), (0, 0, 255)), wait=True, n=2) + From 32f3525e9a7af8d5193a7611fc147acd1675c2f4 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Tue, 1 Mar 2022 08:27:07 +0000 Subject: [PATCH 04/34] Create rgb_pulse.py --- docs/examples/rgb_pulse.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/examples/rgb_pulse.py diff --git a/docs/examples/rgb_pulse.py b/docs/examples/rgb_pulse.py new file mode 100644 index 0000000..6982e70 --- /dev/null +++ b/docs/examples/rgb_pulse.py @@ -0,0 +1,18 @@ +from picozero import RGBLED +from time import sleep + +rgb = RGBLED(1, 2, 3) + +rgb.pulse() # does not wait +sleep(6) +rgb.off() +sleep(1) + +# 2 second to fade from purple to off, 0.5 seconds to change from off to purple +rgb.pulse(fade_times=(2, 0.5), colors=((255, 0, 255), (0, 0, 0)), wait=True, n=3) + +rgb.off() +sleep(1) + +# 4 seconds to change from red to green, 2 to change from green to blue, then 1 to change from blue back to red +rgb.pulse((4, 2, 1), colors=((255, 0, 0), (0, 255, 0), (0, 0, 255)), wait=True, n=2) From 501979234b37cefe0a119bcf4f99891229b4ea1b Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Tue, 1 Mar 2022 08:28:33 +0000 Subject: [PATCH 05/34] Create rgb_cycle.py --- docs/examples/rgb_cycle.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docs/examples/rgb_cycle.py diff --git a/docs/examples/rgb_cycle.py b/docs/examples/rgb_cycle.py new file mode 100644 index 0000000..dec8e22 --- /dev/null +++ b/docs/examples/rgb_cycle.py @@ -0,0 +1,14 @@ +from picozero import RGBLED +from time import sleep + +rgb = RGBLED(1, 2, 3) + +# Gradually colour cycle through colours between red and green, green and blue then blue and red +rgb.cycle() +sleep(4) +rgb.off() +sleep(1) + +# Colour cycle slower in the opposite direction +rgb.cycle(fade_times=3, colors=((0, 0, 1), (0, 1, 0), (1, 0, 0)), wait=True, n=2) +rgb.off() From de98932f4098d23f2985c2b0bed27d6cf20d5449 Mon Sep 17 00:00:00 2001 From: Tracy Gardner Date: Tue, 1 Mar 2022 09:26:06 +0000 Subject: [PATCH 06/34] Updates to docs and examples. --- docs/examples/button_function.py | 2 +- docs/examples/button_is_pressed.py | 2 +- docs/examples/button_led.py | 2 +- docs/examples/buzzer.py | 2 +- docs/examples/rgb_blink.py | 6 ++-- docs/examples/rgb_pulse.py | 4 +-- docs/examples/speaker.py | 29 ++++++++------------ docs/recipes.rst | 44 +++++++++++++++++++++++++++--- 8 files changed, 61 insertions(+), 30 deletions(-) diff --git a/docs/examples/button_function.py b/docs/examples/button_function.py index 96dc74c..3442cba 100644 --- a/docs/examples/button_function.py +++ b/docs/examples/button_function.py @@ -1,7 +1,7 @@ from picozero import Button, pico_led from time import sleep -button = Button(17) +button = Button(18) def led_on_off(): pico_led.on() diff --git a/docs/examples/button_is_pressed.py b/docs/examples/button_is_pressed.py index e148fa3..c96ec8a 100644 --- a/docs/examples/button_is_pressed.py +++ b/docs/examples/button_is_pressed.py @@ -1,7 +1,7 @@ from picozero import Button from time import sleep -button = Button(17) +button = Button(18) while True: if button.is_pressed: diff --git a/docs/examples/button_led.py b/docs/examples/button_led.py index 5b18282..4161727 100644 --- a/docs/examples/button_led.py +++ b/docs/examples/button_led.py @@ -1,6 +1,6 @@ from picozero import Button, pico_led -button = Button(17) +button = Button(18) button.when_pressed = pico_led.on button.when_released = pico_led.off diff --git a/docs/examples/buzzer.py b/docs/examples/buzzer.py index dc0666a..81ce79f 100644 --- a/docs/examples/buzzer.py +++ b/docs/examples/buzzer.py @@ -1,4 +1,4 @@ -# Active Buzzer that plays a not when powered +# Active Buzzer that plays a note when powered from time import sleep from picozero import Buzzer diff --git a/docs/examples/rgb_blink.py b/docs/examples/rgb_blink.py index 5c75308..b7b27be 100644 --- a/docs/examples/rgb_blink.py +++ b/docs/examples/rgb_blink.py @@ -9,11 +9,11 @@ sleep(1) # blink purple 2 seconds, off 0.5 seconds -rgb.blink(on_times=(2, 0.5), colors=((255, 0, 255), (0, 0, 0)), wait=True, n=3) +rgb.blink(on_times=(2, 0.5), colors=((1, 0, 1), (0, 0, 0)), wait=True, n=3) rgb.off() sleep(1) # blink red 1 second, green 0.5 seconds, blue 0.25 seconds -rgb.blink((1, 0.5, 0.25), colors=((255, 0, 0), (0, 255, 0), (0, 0, 255)), wait=True, n=2) - +rgb.blink((1, 0.5, 0.25), colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), wait=True, n=2) + \ No newline at end of file diff --git a/docs/examples/rgb_pulse.py b/docs/examples/rgb_pulse.py index 6982e70..7345044 100644 --- a/docs/examples/rgb_pulse.py +++ b/docs/examples/rgb_pulse.py @@ -9,10 +9,10 @@ sleep(1) # 2 second to fade from purple to off, 0.5 seconds to change from off to purple -rgb.pulse(fade_times=(2, 0.5), colors=((255, 0, 255), (0, 0, 0)), wait=True, n=3) +rgb.pulse(fade_times=(2, 0.5), colors=((1, 0, 1), (0, 0, 0)), wait=True, n=3) rgb.off() sleep(1) # 4 seconds to change from red to green, 2 to change from green to blue, then 1 to change from blue back to red -rgb.pulse((4, 2, 1), colors=((255, 0, 0), (0, 255, 0), (0, 0, 255)), wait=True, n=2) +rgb.pulse((4, 2, 1), colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), wait=True, n=2) diff --git a/docs/examples/speaker.py b/docs/examples/speaker.py index 511dd4d..55a108d 100644 --- a/docs/examples/speaker.py +++ b/docs/examples/speaker.py @@ -1,30 +1,25 @@ -from time import sleep from picozero import Speaker +from time import sleep -speaker = Speaker(10) - -speaker.play() - -sleep(1) +speaker = Speaker(5) def tada(): c_note = 523 - speaker.play(freq=c_note, duration=0.1) + speaker.pitch(freq=c_note, duration=0.1) sleep(0.1) - speaker.play(c_note, 0.4) - for i in range(100, 0, -1): - speaker.play(freq=c_note, duration=0.01, volume=i/100) - -tada() -sleep(1) + speaker.pitch(c_note, 0.9) def chirp(): global speaker for _ in range(5): for i in range(5000, 2999, -100): - speaker.play(i, 0.01) + speaker.pitch(i, 0.01) sleep(0.2) -chirp() - -speaker.off() +try: + tada() + sleep(1) + chirp() + +finally: # Turn the speaker off if interrupted + speaker.off() diff --git a/docs/recipes.rst b/docs/recipes.rst index 40e4e62..5551529 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -82,7 +82,6 @@ Create a pulse effect: .. literalinclude:: examples/led_pulse.py - Button ------ @@ -99,7 +98,7 @@ Run a function every time a :class:`Button` is pressed: .. note:: - Note that the line ``button.when_pressed = led_on_off`` does not run the + The line ``button.when_pressed = led_on_off`` does not run the function ``led_on_off``, rather it creates a reference to the function to be called when the button is pressed. Accidental use of ``button.when_pressed = led_on_off()`` would set the ``when_pressed`` action to :data:`None` (the @@ -113,14 +112,37 @@ Turn the :obj:`pico_led` on when a :class:`Button` is pressed and off when it is RGBLED ------ -Setting colours with an :class:`RGBLED`: +Set colours with an :class:`RGBLED`: .. literalinclude:: examples/rgb_led.py -Using :meth:`~picozero.RGBLED.toggle` and :meth:`~picozero.RGBLED.invert`: +Use :meth:`~picozero.RGBLED.toggle` and :meth:`~picozero.RGBLED.invert`: .. literalinclude:: examples/rgb_toggle_invert.py +Blink +~~~~~ + +Use :meth:`~picozero.RGBLED.blink` blink to change between colours. You can control which colours are used and how long the LED is set to each colour. The colour `(0, 0, 0)` represents off. + +You can control whether :meth:`~picozero.RGBLED.blink` runs a fixed number of times and whether it waits until it has finished or returns immediately so other code can run. + +.. literalinclude:: examples/rgb_blink.py + +Pulse +~~~~~ + +Use :meth:`~picozero.RGBLED.pulse` to gradually change the LED between colours. The default will pulse between red and off, then green and off, then blue and off. + +.. literalinclude:: examples/rgb_pulse.py + +Cycle +~~~~~ + +The default for :meth:`~picozero.RGBLED.cycle` is to cycle from red to green, then green to blue, then blue to red. + +.. literalinclude:: examples/rgb_cycle.py + Potentiometer --------------- @@ -147,6 +169,20 @@ Control a passive buzzer or speaker that can play different tones or frequencies .. literalinclude:: examples/speaker.py +Play a tune +~~~~~~~~~~~ + +Play a tune of note names and durations in beats: + +.. literalinclude:: examples/speaker_tune.py + +Play individual notes +~~~~~~~~~~~~~~~~~~~~~ + +Play individual notes and control the timing or perform another action: + +.. literalinclude:: examples/speaker_notes.py + Internal Temperature Sensor --------------------------- From 84f3255afc9bdecce5a229058a6cc1d79faf344d Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Thu, 3 Mar 2022 08:51:06 +0000 Subject: [PATCH 07/34] Update picozero.py Single play method for PWMBuzzer #17. Still need to agree parameter names/details. And make sure value/volume are correctly handled. --- picozero/picozero.py | 43 ++++++++++++++----------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 6e51cf8..7d264df 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -840,24 +840,15 @@ class PWMBuzzer(PWMOutputDevice): 'e7': 2637, 'f7': 2794, 'f#7': 2960, 'g7': 3136, 'g#7': 3322, 'a7': 3520, 'a#7': 3729, 'b7': 3951, 'c8': 4186, 'c#8': 4435, 'd8': 4699, 'd#8': 4978 } - def __init__(self, pin, freq=440, active_high=True, initial_value=0, volume=0.5, bpm=120, duty_factor=1023): - self._bpm = bpm + def __init__(self, pin, freq=440, active_high=True, initial_value=0, volume=1, duty_factor=1023): self._volume = volume super().__init__( pin, freq=freq, duty_factor=duty_factor, active_high=active_high, - initial_value= (freq, volume), + initial_value= 0, ) - - @property - def bpm(self): - return self._bpm - - @bpm.setter - def bpm(self, value): - self._bpm = value @property def volume(self): @@ -876,7 +867,7 @@ def value(self, value): self._stop_change() self._write(value) - def _write(self, value): + def _write(self, value): if value == 0 or value is None or value == '': volume = 0 else: @@ -893,11 +884,11 @@ def _write(self, value): super()._write(volume) - def pitch(self, freq=440, duration=1, volume=1, wait=True): + def _pitch(self, freq=440, duration=1, volume=1, wait=True): + self.off() if duration is None: self.value = (freq, volume) else: - self.off() self._start_change(lambda : iter([((freq, volume), duration)]), 1, wait) def _to_freq(self, freq): @@ -912,28 +903,22 @@ def _to_freq(self, freq): else: return None - def to_seconds(self, duration): - return (duration * 60 / self._bpm) - - def play(self, tune=440, duration=4, volume=1, n=1, wait=True, multiplier=0.9): - + def play(self, tune=440, duration=1, volume=1, n=1, wait=True): if type(tune) is not list: # use note and duration, no generator - duration = self.to_seconds(duration * multiplier) - self.pitch(tune, duration, volume, wait) + self._pitch(tune, duration, volume, wait) elif type(tune[0]) is not list: # single note don't use a generator - duration = self.to_seconds(tune[1] * multiplier) - self.pitch(tune[0], duration, volume, wait) #, volume, multiplier, wait) + self._pitch(tune[0], tune[1], volume, wait) else: # tune with multiple notes def tune_generator(): for next in tune: note = next[0] if len(next) == 2: - duration = self.to_seconds(float(next[1])) + duration = float(next[1]) if note == '' or note is None: yield ((None, 0), duration) - else: - yield ((note, volume), duration * multiplier) - yield ((None, 0), duration * (1 - multiplier)) + else: # leave a gap between notes + yield ((note, volume), duration * 0.9) + yield ((None, 0), duration * 0.1) self.off() self._start_change(tune_generator, n, wait) @@ -951,8 +936,8 @@ def __del__(self): PWMBuzzer.beep = PWMBuzzer.blink -def Speaker(pin, use_tones=True, active_high=True, volume=1, initial_value=False, bpm=120): +def Speaker(pin, use_tones=True, active_high=True, volume=1, initial_value=False): if use_tones: - return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=volume, bpm=bpm) + return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=volume) else: return Buzzer(pin, active_high=active_high, initial_value=False) From 9323e4c7c00dacf228a82faac1a90b4236b64c06 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Thu, 3 Mar 2022 08:51:52 +0000 Subject: [PATCH 08/34] Update speaker.py Updated to use play --- docs/examples/speaker.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/examples/speaker.py b/docs/examples/speaker.py index 55a108d..276e75b 100644 --- a/docs/examples/speaker.py +++ b/docs/examples/speaker.py @@ -5,17 +5,18 @@ def tada(): c_note = 523 - speaker.pitch(freq=c_note, duration=0.1) + speaker.play(c_note, 0.1) sleep(0.1) - speaker.pitch(c_note, 0.9) + speaker.play(c_note, 0.9) def chirp(): global speaker for _ in range(5): for i in range(5000, 2999, -100): - speaker.pitch(i, 0.01) + speaker.play(i, 0.01) sleep(0.2) + try: tada() sleep(1) From ddb1a53686026ebdfbd38ab9e1699ebba6c71520 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Thu, 3 Mar 2022 08:53:59 +0000 Subject: [PATCH 09/34] Update speaker_notes.py Simplified --- docs/examples/speaker_notes.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/examples/speaker_notes.py b/docs/examples/speaker_notes.py index 46c2e09..77e02ba 100644 --- a/docs/examples/speaker_notes.py +++ b/docs/examples/speaker_notes.py @@ -1,20 +1,18 @@ from picozero import Speaker from time import sleep -speaker = Speaker(5, bpm=120) +speaker = Speaker(5) -tetris = [ ['e4', 1], ['b3', 0.5], ['c4', 0.5], ['d4', 1], ['c4', 0.5], ['b3', 0.5], ['a3', 1], ['a3', 0.5], ['c4', 0.5], ['e4', 1], ['d4', 0.5], ['c4', 0.5], - ['b3', 1.5], ['c4', 0.5], ['d4', 1], ['e4', 1], ['c4', 1], ['a3', 1], ['a3', 2]] +BEAT = 0.4 -# Play 'Staccato' half length notes -note_length = 0.5 # play notes for half a beat -gap_length = 1 - note_length # leave half a beat between notes +tetris = [ ['e4', BEAT], ['b3', BEAT / 2], ['c4', BEAT / 2], ['d4', BEAT], ['c4', BEAT / 2], ['b3', BEAT / 2], + ['a3', BEAT], ['a3', BEAT / 2], ['c4', BEAT / 2], ['e4', BEAT ],['d4', BEAT / 2], ['c4', BEAT / 2], + ['b3', BEAT * 1.5], ['c4', BEAT / 2], ['d4', BEAT], ['e4', BEAT], ['c4', BEAT], ['a3', BEAT], ['a3', BEAT * 2]] try: for note in tetris: - speaker.play(note, multiplier=note_length) - gap = speaker.to_seconds(note[1]) * gap_length # leave a gap between notes - sleep(gap) + speaker.play(note) + sleep(0.1) # leave a gap between notes finally: # Turn speaker off if interrupted speaker.off() From 7a6ebe4447859acef93034a7d1bda1121055c3ef Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Thu, 3 Mar 2022 08:54:31 +0000 Subject: [PATCH 10/34] Update speaker_tune.py Simplified --- docs/examples/speaker_tune.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/examples/speaker_tune.py b/docs/examples/speaker_tune.py index c4b879c..1b8cd28 100644 --- a/docs/examples/speaker_tune.py +++ b/docs/examples/speaker_tune.py @@ -2,8 +2,11 @@ speaker = Speaker(5) -tetris = [ ['e4', 1], ['b3', 0.5], ['c4', 0.5], ['d4', 1], ['c4', 0.5], ['b3', 0.5], ['a3', 1], ['a3', 0.5], ['c4', 0.5], ['e4', 1], ['d4', 0.5], ['c4', 0.5], - ['b3', 1.5], ['c4', 0.5], ['d4', 1], ['e4', 1], ['c4', 1], ['a3', 1], ['a3', 2]] +BEAT = 0.5 # 120 BPM + +tetris = [ ['e4', BEAT], ['b3', BEAT / 2], ['c4', BEAT / 2], ['d4', BEAT], ['c4', BEAT / 2], ['b3', BEAT / 2], + ['a3', BEAT], ['a3', BEAT / 2], ['c4', BEAT / 2], ['e4', BEAT ],['d4', BEAT / 2], ['c4', BEAT / 2], + ['b3', BEAT * 1.5], ['c4', BEAT / 2], ['d4', BEAT], ['e4', BEAT], ['c4', BEAT], ['a3', BEAT], ['a3', BEAT * 2]] try: speaker.play(tetris) From 985e0d381da516b00422ad32bc233c2e8866fec4 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Thu, 3 Mar 2022 17:01:04 +0000 Subject: [PATCH 11/34] resolve wait issue --- picozero/picozero.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 7d264df..e47283f 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -35,33 +35,51 @@ def __init__(self, output_device, generator, n, wait): self._timer = Timer() self._running = True - self._set_value() + self._wait = wait - while wait and self._running: - sleep(0.001) + self._set_value() def _set_value(self, timer_obj=None): - - try: - if self._running: - next_seq = next(self._gen) + if self._wait: + # wait for the exection to end + next_seq = self._get_value() + while next_seq is not None: value, seconds = next_seq - + + self._output_device._write(value) + sleep(seconds) + + next_seq = self._get_value() + + else: + # run the timer + next_seq = self._get_value() + if next_seq is not None: + value, seconds = next_seq + self._output_device._write(value) self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) + + if next_seq is None: + # the sequence has finished, set the output to 0 + self._output_device.value = 0 + self._running = False + + def _get_value(self): + try: + return next(self._gen) 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 + # its the end, return None + return None else: # recreate the generator and start again self._gen = self._generator() - self._set_value() - + return next(self._gen) + def stop(self): self._running = False self._timer.deinit() From 0c2cb91fa348f42d5f04b73b64f290fc754d6c19 Mon Sep 17 00:00:00 2001 From: beckyfranks <49150572+beckyfranks@users.noreply.github.com> Date: Tue, 8 Mar 2022 09:08:44 +0000 Subject: [PATCH 12/34] Update speaker_tune.py Changed tune to Liten Mus, a Swedish and Norwegian folk song. --- docs/examples/speaker_tune.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/examples/speaker_tune.py b/docs/examples/speaker_tune.py index 1b8cd28..92c6746 100644 --- a/docs/examples/speaker_tune.py +++ b/docs/examples/speaker_tune.py @@ -2,14 +2,20 @@ speaker = Speaker(5) -BEAT = 0.5 # 120 BPM +BEAT = 0.25 # 240 BPM -tetris = [ ['e4', BEAT], ['b3', BEAT / 2], ['c4', BEAT / 2], ['d4', BEAT], ['c4', BEAT / 2], ['b3', BEAT / 2], - ['a3', BEAT], ['a3', BEAT / 2], ['c4', BEAT / 2], ['e4', BEAT ],['d4', BEAT / 2], ['c4', BEAT / 2], - ['b3', BEAT * 1.5], ['c4', BEAT / 2], ['d4', BEAT], ['e4', BEAT], ['c4', BEAT], ['a3', BEAT], ['a3', BEAT * 2]] +liten_mus = [ ['d5', BEAT / 2], ['d#5', BEAT / 2], ['f5', BEAT], ['d6', BEAT], ['a#5', BEAT], ['d5', BEAT], + ['f5', BEAT], ['d#5', BEAT], ['d#5', BEAT], ['c5', BEAT / 2],['d5', BEAT / 2], ['d#5', BEAT], + ['c6', BEAT], ['a5', BEAT], ['d5', BEAT], ['g5', BEAT], ['f5', BEAT], ['f5', BEAT], ['d5', BEAT / 2], + ['d#5', BEAT / 2], ['f5', BEAT], ['g5', BEAT], ['a5', BEAT], ['a#5', BEAT], ['a5', BEAT], ['g5', BEAT], + ['g5', BEAT], ['', BEAT / 2], ['a#5', BEAT / 2], ['c6', BEAT / 2], ['d6', BEAT / 2], ['c6', BEAT / 2], + ['a#5', BEAT / 2], ['a5', BEAT / 2], ['g5', BEAT / 2], ['a5', BEAT / 2], ['a#5', BEAT / 2], ['c6', BEAT], + ['f5', BEAT], ['f5', BEAT], ['f5', BEAT / 2], ['d#5', BEAT / 2], ['d5', BEAT], ['f5', BEAT], ['d6', BEAT], + ['d6', BEAT / 2], ['c6', BEAT / 2], ['b5', BEAT], ['g5', BEAT], ['g5', BEAT], ['c6', BEAT / 2], + ['a#5', BEAT / 2], ['a5', BEAT], ['f5', BEAT], ['d6', BEAT], ['a5', BEAT], ['a#5', BEAT * 1.5]] try: - speaker.play(tetris) + speaker.play(liten_mus) finally: # Turn speaker off if interrupted speaker.off() From 8d6765e119e9be71edb3642e13aca042b4de2ca2 Mon Sep 17 00:00:00 2001 From: beckyfranks <49150572+beckyfranks@users.noreply.github.com> Date: Tue, 8 Mar 2022 09:12:55 +0000 Subject: [PATCH 13/34] Update speaker_notes.py Changed the tune to a Swedish and Norwegian folk song called Liten Mus. --- docs/examples/speaker_notes.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/examples/speaker_notes.py b/docs/examples/speaker_notes.py index 77e02ba..d6bb858 100644 --- a/docs/examples/speaker_notes.py +++ b/docs/examples/speaker_notes.py @@ -5,12 +5,18 @@ BEAT = 0.4 -tetris = [ ['e4', BEAT], ['b3', BEAT / 2], ['c4', BEAT / 2], ['d4', BEAT], ['c4', BEAT / 2], ['b3', BEAT / 2], - ['a3', BEAT], ['a3', BEAT / 2], ['c4', BEAT / 2], ['e4', BEAT ],['d4', BEAT / 2], ['c4', BEAT / 2], - ['b3', BEAT * 1.5], ['c4', BEAT / 2], ['d4', BEAT], ['e4', BEAT], ['c4', BEAT], ['a3', BEAT], ['a3', BEAT * 2]] +liten_mus = [ ['d5', BEAT / 2], ['d#5', BEAT / 2], ['f5', BEAT], ['d6', BEAT], ['a#5', BEAT], ['d5', BEAT], + ['f5', BEAT], ['d#5', BEAT], ['d#5', BEAT], ['c5', BEAT / 2],['d5', BEAT / 2], ['d#5', BEAT], + ['c6', BEAT], ['a5', BEAT], ['d5', BEAT], ['g5', BEAT], ['f5', BEAT], ['f5', BEAT], ['d5', BEAT / 2], + ['d#5', BEAT / 2], ['f5', BEAT], ['g5', BEAT], ['a5', BEAT], ['a#5', BEAT], ['a5', BEAT], ['g5', BEAT], + ['g5', BEAT], ['', BEAT / 2], ['a#5', BEAT / 2], ['c6', BEAT / 2], ['d6', BEAT / 2], ['c6', BEAT / 2], + ['a#5', BEAT / 2], ['a5', BEAT / 2], ['g5', BEAT / 2], ['a5', BEAT / 2], ['a#5', BEAT / 2], ['c6', BEAT], + ['f5', BEAT], ['f5', BEAT], ['f5', BEAT / 2], ['d#5', BEAT / 2], ['d5', BEAT], ['f5', BEAT], ['d6', BEAT], + ['d6', BEAT / 2], ['c6', BEAT / 2], ['b5', BEAT], ['g5', BEAT], ['g5', BEAT], ['c6', BEAT / 2], + ['a#5', BEAT / 2], ['a5', BEAT], ['f5', BEAT], ['d6', BEAT], ['a5', BEAT], ['a#5', BEAT * 1.5]] try: - for note in tetris: + for note in liten_mus: speaker.play(note) sleep(0.1) # leave a gap between notes From 08ae4f76d19d4de983b39f14c598ace96772be5f Mon Sep 17 00:00:00 2001 From: Tracy Gardner Date: Tue, 8 Mar 2022 15:53:38 +0000 Subject: [PATCH 14/34] Added docstrings for RGBLED --- docs/api.rst | 8 +++ picozero/picozero.py | 137 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 125 insertions(+), 20 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index ac5879c..25a4d60 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -24,6 +24,14 @@ PWMLED :inherited-members: :members: +RGBLED +---------- + +.. autoclass:: RGBLED + :show-inheritance: + :inherited-members: + :members: + Button ------ diff --git a/picozero/picozero.py b/picozero/picozero.py index e47283f..3d6f544 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -612,8 +612,44 @@ class Button(Switch): Button.when_pressed = Button.when_activated Button.when_released = Button.when_deactivated - class RGBLED(OutputDevice): + """ + Extends :class:`OutputDevice` and represents a full color LED component (composed + of red, green, and blue LEDs). + Connect the common cathode (longest leg) to a ground pin; connect each of + the other legs (representing the red, green, and blue anodes) to any GP + pins. You should use three limiting resistors (one per anode). + The following code will make the LED yellow:: + + from picozero import RGBLED + rgb = RGBLED(1, 2, 3) + rgb.color = (1, 1, 0) + + 0-255 colours are also supported:: + + rgb.color = (255, 255, 0) + + :type red: int + :param red: + The GP pin that controls the red component of the RGB LED. + :type green: int + :param green: + The GP pin that controls the green component of the RGB LED. + :type blue: int + :param blue: + The GP pin that controls the blue component of the RGB LED. + :param bool active_high: + Set to :data:`True` (the default) for common cathode RGB LEDs. If you + are using a common anode RGB LED, set this to :data:`False`. + :type initial_value: ~colorzero.Color or tuple + :param initial_value: + The initial color for the RGB LED. Defaults to black ``(0, 0, 0)``. + :param bool pwm: + If :data:`True` (the default), construct :class:`PWMLED` instances for + each component of the RGBLED. If :data:`False`, construct + :class:`DigitalLED` instances. + :type pin_factory: Factory or None + """ def __init__(self, red=None, green=None, blue=None, active_high=True, initial_value=(0, 0, 0), pwm=True): self._leds = () @@ -640,6 +676,13 @@ def _write(self, value): @property def value(self): + """ + Represents the color of the LED as an RGB 3-tuple of ``(red, green, + blue)`` where each value is between 0 and 1 if *pwm* was :data:`True` + when the class was constructed (and only 0 or 1 if not). + For example, red would be ``(1, 0, 0)`` and yellow would be ``(1, 1, + 0)``, while orange would be ``(1, 0.5, 0)``. + """ return tuple(led.brightness for led in self._leds) @value.setter @@ -649,6 +692,10 @@ def value(self, value): @property def is_active(self): + """ + Returns :data:`True` if the LED is currently active (not black) and + :data:`False` otherwise. + """ return self.value != (0, 0, 0) is_lit = is_active @@ -661,6 +708,13 @@ def _from_255(self, value): @property def color(self): + """ + Represents the color of the LED as an RGB 3-tuple of ``(red, green, + blue)`` where each value is between 0 and 255 if *pwm* was :data:`True` + when the class was constructed (and only 0 or 255 if not). + For example, red would be ``(255, 0, 0)`` and yellow would be ``(255, 255, + 0)``, while orange would be ``(255, 127, 0)``. + """ return tuple(self._to_255(v) for v in self.value) @color.setter @@ -669,6 +723,10 @@ def color(self, value): @property def red(self): + """ + Represents the red component of the LED as value between 0 and 255 if *pwm* was :data:`True` + when the class was constructed (and only 0 or 255 if not). + """ return self._to_255(self.value[0]) @red.setter @@ -678,6 +736,10 @@ def red(self, value): @property def green(self): + """ + Represents the green component of the LED as value between 0 and 255 if *pwm* was :data:`True` + when the class was constructed (and only 0 or 255 if not). + """ return self._to_255(self.value[1]) @green.setter @@ -687,6 +749,10 @@ def green(self, value): @property def blue(self): + """ + Represents the blue component of the LED as value between 0 and 255 if *pwm* was :data:`True` + when the class was constructed (and only 0 or 255 if not). + """ return self._to_255(self.value[2]) @blue.setter @@ -695,13 +761,27 @@ def blue(self, value): self.value = r, g, self._from_255(value) def on(self): + """ + Turn the LED on. This equivalent to setting the LED color to white + ``(1, 1, 1)``. + """ self.value = (1, 1, 1) def invert(self): + """ + Invert the state of the device. If the device is currently off + (:attr:`value` is ``(0, 0, 0)``), this changes it to "fully" on + (:attr:`value` is ``(1, 1, 1)``). If the device has a specific color, + this method inverts the color. + """ r, g, b = self.value self.value = (1 - r, 1 - g, 1 - b) def toggle(self): + """ + Toggle the state of the device. If the device has a specific colour then save that colour and turn off. + If the device is off, change it to the last colour or, if none, to fully on (:attr:`value` is ``(1, 1, 1)``). + """ if self.value == (0, 0, 0): self.value = self._last or (1, 1, 1) else: @@ -709,7 +789,27 @@ def toggle(self): self.value = (0, 0, 0) 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): - + """ + Make the device blink between colours repeatedly. + + :param float on_times: + Single value or tuple of numbers of seconds to stay on each colour. Defaults to 1 second. + :param float fade_times: + Sinlge value or tuple of times to fade between each colour. Must be 0 if + *pwm* was :data:`False` when the class was constructed. + :type colors: tuple + Tuple of colours to blink between, use ``(0, 0, 0)`` for off. + :param colors: + The colors to blink between. Defaults to red, green, blue. + :type n: int or None + :param n: + Number of times to blink; :data:`None` (the default) means forever. + :param bool wait: + If :data:`False` (the default), use a Timer to manage blinking + 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.off() if type(on_times) is not tuple: @@ -746,17 +846,16 @@ def blink_generator(): 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_times: - Number of seconds to spend fading in. Defaults to 1. + Make the device fade between colours repeatedly. + + :param float fade_times: + Single value or tuple of numbers of seconds to spend fading. 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 + :type colors: tuple :param on_color: - The color to use when the LED is "on". Defaults to white. + Tuple of colours to pulse between in order. Defaults to red, off, green, off, blue, off. :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. @@ -767,19 +866,17 @@ def pulse(self, fade_times=1, colors=((0, 0, 0), (1, 0, 0), (0, 0, 0), (0, 1, 0) 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: - Number of seconds to spend fading in. Defaults to 1. - :param float fade_out_time: + + :param float fade_times: + Single value or tuple of numbers of seconds to spend fading between colours. Defaults to 1. + :param float fade_times: Number of seconds to spend fading out. Defaults to 1. - :type on_color: ~colorzero.Color or tuple + :type colors: 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. + Tuple of colours to cycle between. Defaults to red, green, blue. :type n: int or None :param n: - Number of times to pulse; :data:`None` (the default) means forever. + Number of times to cycle; :data:`None` (the default) means forever. """ on_times = 0 self.blink(on_times, fade_times, colors, n, wait, fps) @@ -895,7 +992,7 @@ def _write(self, value): (freq, volume) = value freq = self._to_freq(freq) - if freq is not None and freq is not '' and freq !=0: + if freq is not None and freq != '' and freq != 0: self._pwm.freq(freq) else: volume = 0 @@ -910,7 +1007,7 @@ def _pitch(self, freq=440, duration=1, volume=1, wait=True): self._start_change(lambda : iter([((freq, volume), duration)]), 1, wait) def _to_freq(self, freq): - if freq is not None and freq is not '' and freq != 0: + if freq is not None and freq != '' and freq != 0: if type(freq) is str: return int(self.NOTES[freq]) elif freq <= 128 and freq > 0: # MIDI From 17d93d013444da782368d2639565fbbf4b2ae0f7 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Thu, 10 Mar 2022 12:47:16 +0000 Subject: [PATCH 15/34] Update picozero.py Quick fix for #21. Could add a parameter for setting the frequency to use. --- picozero/picozero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 3d6f544..7b7453f 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -990,7 +990,7 @@ def _write(self, value): value = (value, self.volume) (freq, volume) = value - freq = self._to_freq(freq) + freq = self._to_freq(freq) if freq != 1 else 440 if freq is not None and freq != '' and freq != 0: self._pwm.freq(freq) From 263f43e6d48553bed0f1d23dea01c2bfd7d72b4e Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Mar 2022 14:13:00 +0000 Subject: [PATCH 16/34] pwm message --- picozero/picozero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 7b7453f..e171d2e 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -245,7 +245,7 @@ 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]}" + f"PWM channel {channel} is already in use by pin {PWMOutputDevice._channels_used[channel]}. Use a different pin" ) else: PWMOutputDevice._channels_used[channel] = pin_num From 7b8f03e42a9d646db464f569fae7743a9c093abf Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Mar 2022 14:16:10 +0000 Subject: [PATCH 17/34] update PWMLED.blink parameters --- picozero/picozero.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index e171d2e..25bca90 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -302,7 +302,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, wait=False, fps=25): + def blink(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fade_out_time=None, fps=25): """ Make the device turn on and off repeatedly. @@ -313,6 +313,10 @@ def blink(self, on_time=1, off_time=None, fade_in_time=0, fade_out_time=None, n= 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`, the + device will continue blinking forever. The default is `None`. + :param float fade_in_time: The length of time in seconds to spend fading in. Defaults to 0. @@ -320,10 +324,6 @@ def blink(self, on_time=1, off_time=None, fade_in_time=0, fade_out_time=None, n= 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. From 43d294ec586570cd3b305aaee9390d117bd75ae3 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Mar 2022 14:27:33 +0000 Subject: [PATCH 18/34] schedule callbacks --- picozero/picozero.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 25bca90..bff2664 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -1,4 +1,5 @@ from machine import Pin, PWM, Timer, ADC +from micropython import schedule from time import ticks_ms, sleep class PWMChannelAlreadyInUse(Exception): @@ -530,11 +531,15 @@ def _pin_change(self, p): # set the state self._state = self._pin.value() + def schedule_callback(callback): + callback() + # manage call backs if self.value and self._when_activated is not None: - self._when_activated() + schedule(schedule_callback, self._when_activated) + elif not self.value and self._when_deactivated is not None: - self._when_deactivated() + schedule(schedule_callback, self._when_deactivated) @property def is_active(self): From a991bc9ebed30250477b50ce390ef97cc3429170 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Mar 2022 14:41:14 +0000 Subject: [PATCH 19/34] added Button.is_active --- picozero/picozero.py | 1 + 1 file changed, 1 insertion(+) diff --git a/picozero/picozero.py b/picozero/picozero.py index bff2664..3af90b2 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -614,6 +614,7 @@ class Button(Switch): pass Button.is_pressed = Button.is_active +Button.is_released = Button.is_inactive Button.when_pressed = Button.when_activated Button.when_released = Button.when_deactivated From 8c9a4e3c34e16c9bb6f9f33f7aeee1f90e0e9a03 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Mar 2022 14:41:25 +0000 Subject: [PATCH 20/34] fixed docs --- docs/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py index 67b5991..d4bb81a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,6 +32,7 @@ def __getattr__(cls, name): return Mock() sys.modules['machine'] = Mock() +sys.modules['micropython'] = Mock() # add the ticks_ms function to time (as it is in micropython) import time From afff1940da1e497b2e8f6f00646781c1528ca614 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Mar 2022 15:51:25 +0000 Subject: [PATCH 21/34] fixed urls --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 36288d9..505a0cc 100644 --- a/README.rst +++ b/README.rst @@ -18,9 +18,9 @@ A beginner-friendly library for using common electronics components with the Ras Full documentation is available at `picozero.readthedocs.io `_ : -- `Installation and getting started guide `_ -- `Recipes and how-to's `_ -- `API `_ +- `Installation and getting started guide `_ +- `Recipes and how-to's `_ +- `API `_ -picozero is inspired by `gpiozero `_ (and reuses some of its underlying struture), but is by design lighter weight and aligned with the Raspberry Pi Pico. +picozero is inspired by `gpiozero `_ (and reuses some of its underlying structure), but is by design lighter weight and aligned with the Raspberry Pi Pico. From ab97e6cab6b1409fc1295dddfab85083a5cc63e0 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Mar 2022 15:51:34 +0000 Subject: [PATCH 22/34] added license --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 475490e..431dbec 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ __version__ = '0.0.1' __author__ = "Raspberry Pi Foundation" __author_email__ = 'learning@raspberrypi.org' -__license__ = 'XXX TBC' +__license__ = 'MIT' __url__ = 'https://github.com/raspberrypilearning/picozero' __keywords__ = [ 'raspberry', @@ -30,7 +30,10 @@ button.when_pressed = led.on button.when_released = led.off -```""" +``` + +Not yet for general release. +""" setup( name=__project__, From ea3c326d6a125c9a2267f91dfb7366ebc1d896fe Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Mar 2022 15:52:11 +0000 Subject: [PATCH 23/34] organised classes by output and input --- picozero/__init__.py | 17 +- picozero/picozero.py | 586 ++++++++++++++++++++++--------------------- 2 files changed, 309 insertions(+), 294 deletions(-) diff --git a/picozero/__init__.py b/picozero/__init__.py index 5e95691..c79179c 100644 --- a/picozero/__init__.py +++ b/picozero/__init__.py @@ -1,20 +1,29 @@ from .picozero import ( + PWMChannelAlreadyInUse, + DigitalOutputDevice, DigitalLED, Buzzer, PWMOutputDevice, PWMLED, LED, + pico_led, + + PWMBuzzer, + Speaker, + + RGBLED, + DigitalInputDevice, - Button, Switch, - RGBLED, + Button, + AnalogInputDevice, Potentiometer, + Pot, + TemperatureSensor, pico_temp_sensor, TempSensor, Thermistor, - PWMBuzzer, - Speaker, ) diff --git a/picozero/picozero.py b/picozero/picozero.py index 3af90b2..f2bca61 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -84,7 +84,11 @@ def _get_value(self): def stop(self): self._running = False self._timer.deinit() - + +############################################################################### +# OUTPUT DEVICES +############################################################################### + class OutputDevice: """ Base class for output devices. @@ -384,8 +388,7 @@ def pulse(self, fade_in_time=1, fade_out_time=None, n=None, wait=False, fps=25): """ 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): """ Returns an instance of :class:`DigitalLED` or :class:`PWMLED` depending on @@ -430,193 +433,120 @@ def LED(pin, use_pwm=True, active_high=True, initial_value=False): pico_led = LED(25) -class InputDevice: - """ - Base class for input devices. - """ - def __init__(self, active_state=None): - self._active_state = active_state - +class PWMBuzzer(PWMOutputDevice): + + NOTES = {'b0': 31, 'c1': 33, 'c#1': 35, 'd1': 37, 'd#1': 39, 'e1': 41, 'f1': 44, 'f#1': 46, 'g1': 49,'g#1': 52, 'a1': 55, + 'a#1': 58, 'b1': 62, 'c2': 65, 'c#2': 69, 'd2': 73, 'd#2': 78, + 'e2': 82, 'f2': 87, 'f#2': 93, 'g2': 98, 'g#2': 104, 'a2': 110, 'a#2': 117, 'b2': 123, + 'c3': 131, 'c#3': 139, 'd3': 147, 'd#3': 156, 'e3': 165, 'f3': 175, 'f#3': 185, 'g3': 196, 'g#3': 208, 'a3': 220, 'a#3': 233, 'b3': 247, + 'c4': 262, 'c#4': 277, 'd4': 294, 'd#4': 311, 'e4': 330, 'f4': 349, 'f#4': 370, 'g4': 392, 'g#4': 415, 'a4': 440,'a#4': 466,'b4': 494, + 'c5': 523, 'c#5': 554, 'd5': 587, 'd#5': 622, 'e5': 659, 'f5': 698, 'f#5': 740, 'g5': 784, 'g#5': 831, 'a5': 880, 'a#5': 932, 'b5': 988, + 'c6': 1047, 'c#6': 1109, 'd6': 1175, 'd#6': 1245, 'e6': 1319, 'f6': 1397, 'f#6': 1480, 'g6': 1568, 'g#6': 1661, 'a6': 1760, 'a#6': 1865, 'b6': 1976, + 'c7': 2093, 'c#7': 2217, 'd7': 2349, 'd#7': 2489, + 'e7': 2637, 'f7': 2794, 'f#7': 2960, 'g7': 3136, 'g#7': 3322, 'a7': 3520, 'a#7': 3729, 'b7': 3951, + 'c8': 4186, 'c#8': 4435, 'd8': 4699, 'd#8': 4978 } + + def __init__(self, pin, freq=440, active_high=True, initial_value=0, volume=1, duty_factor=1023): + self._volume = volume + super().__init__( + pin, + freq=freq, + duty_factor=duty_factor, + active_high=active_high, + initial_value= 0, + ) + @property - def active_state(self): - """ - Sets or returns the active state of the device. If :data:`None` (the default), - the device will return the value that the pin is set to. If - :data:`True`, the device will return :data:`True` if the pin is - HIGH. If :data:`False`, the device will return :data:`False` if the - pin is LOW. - """ - return self._active_state + def volume(self): + return self._volume - @active_state.setter - def active_state(self, value): - self._active_state = True if value else False - self._inactive_state = False if value else True + @volume.setter + def volume(self, value): + self._volume = value @property def value(self): - """ - Returns the current value of the device. This is either :data:`True` - or :data:`False` depending on the value of :attr:`active_state`. - """ - return self._read() - -class DigitalInputDevice(InputDevice): - """ - :param int pin: - The pin that the device is connected to. - - :param bool pull_up: - If :data:`True` (the default), the device will be pulled up to - HIGH. If :data:`False`, the device will be pulled down to LOW. - - :param bool active_state: - If :data:`True` (the default), the device will return :data:`True` - if the pin is HIGH. If :data:`False`, the device will return - :data:`False` if the pin is LOW. + return tuple(self._pwm.freq(), self.volume) - :param float bounce_time: - The bounce time for the device. If set, the device will ignore - any button presses that happen within the bounce time after a - button release. This is useful to prevent accidental button - presses from registering as multiple presses. - """ - def __init__(self, pin, pull_up=False, active_state=None, bounce_time=None): - super().__init__(active_state) - 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 + @value.setter + def value(self, value): + self._stop_change() + self._write(value) + + def _write(self, value): + if value == 0 or value is None or value == '': + volume = 0 else: - self._active_state = active_state - - self._state = 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 _read(self): - return self._state_to_value(self._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._state != last_state: - # set the state - self._state = self._pin.value() - - def schedule_callback(callback): - callback() + if type(value) is not tuple: + value = (value, self.volume) + + (freq, volume) = value + freq = self._to_freq(freq) if freq != 1 else 440 - # manage call backs - if self.value and self._when_activated is not None: - schedule(schedule_callback, self._when_activated) + if freq is not None and freq != '' and freq != 0: + self._pwm.freq(freq) + else: + volume = 0 - elif not self.value and self._when_deactivated is not None: - schedule(schedule_callback, self._when_deactivated) + super()._write(volume) - @property - def is_active(self): - """ - Returns :data:`True` if the device is active. - """ - return bool(self.value) - - @property - def is_inactive(self): - """ - Returns :data:`True` if the device is inactive. - """ - return not bool(self.value) - - @property - def when_activated(self): - """ - Returns a :samp:`callback` that will be called when the device is activated. - """ - return self._when_activated - - @when_activated.setter - def when_activated(self, value): - self._when_activated = value - - @property - def when_deactivated(self): - """ - Returns a :samp:`callback` that will be called when the device is deactivated. - """ - return self._when_deactivated - - @when_activated.setter - def when_deactivated(self, value): - self._when_deactivated = value - - def close(self): - """ - Closes the device and releases any resources. Once closed, the device - can no longer be used. - """ - self._pin.irq(handler=None) - self._pin = None + def _pitch(self, freq=440, duration=1, volume=1, wait=True): + self.off() + if duration is None: + self.value = (freq, volume) + else: + self._start_change(lambda : iter([((freq, volume), duration)]), 1, wait) + def _to_freq(self, freq): + if freq is not None and freq != '' and freq != 0: + if type(freq) is str: + return int(self.NOTES[freq]) + elif freq <= 128 and freq > 0: # MIDI + midi_factor = 2**(1/12) + return int(440 * midi_factor ** (freq - 69)) + else: + return freq + else: + return None + + def play(self, tune=440, duration=1, volume=1, n=1, wait=True): + if type(tune) is not list: # use note and duration, no generator + self._pitch(tune, duration, volume, wait) + elif type(tune[0]) is not list: # single note don't use a generator + self._pitch(tune[0], tune[1], volume, wait) + else: # tune with multiple notes + def tune_generator(): + for next in tune: + note = next[0] + if len(next) == 2: + duration = float(next[1]) + if note == '' or note is None: + yield ((None, 0), duration) + else: # leave a gap between notes + yield ((note, volume), duration * 0.9) + yield ((None, 0), duration * 0.1) + + self.off() + self._start_change(tune_generator, n, wait) + + def _stop(self, timer_obj=None): + self.off() + + def on(self, freq=440, volume=1): + if freq is not None: + self.value = (freq, volume) -class Switch(DigitalInputDevice): - """ - :param int pin: - The pin that the device is connected to. + def __del__(self): + self.off() + super().__del__() - :param bool pull_up: - If :data:`True` (the default), the device will be pulled up to - HIGH. If :data:`False`, the device will be pulled down to LOW. +PWMBuzzer.beep = PWMBuzzer.blink - :param float bounce_time: - The bounce time for the device. If set, the device will ignore - any button presses that happen within the bounce time after a - button release. This is useful to prevent accidental button - presses from registering as multiple presses. Defaults to 0.02 - seconds. - """ - 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.is_released = Button.is_inactive -Button.when_pressed = Button.when_activated -Button.when_released = Button.when_deactivated +def Speaker(pin, use_tones=True, active_high=True, volume=1, initial_value=False): + if use_tones: + return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=volume) + else: + return Buzzer(pin, active_high=active_high, initial_value=False) class RGBLED(OutputDevice): """ @@ -887,6 +817,196 @@ 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) +############################################################################### +# INPUT DEVICES +############################################################################### + +class InputDevice: + """ + Base class for input devices. + """ + def __init__(self, active_state=None): + self._active_state = active_state + + @property + def active_state(self): + """ + Sets or returns the active state of the device. If :data:`None` (the default), + the device will return the value that the pin is set to. If + :data:`True`, the device will return :data:`True` if the pin is + HIGH. If :data:`False`, the device will return :data:`False` if the + pin is LOW. + """ + return self._active_state + + @active_state.setter + def active_state(self, value): + self._active_state = True if value else False + self._inactive_state = False if value else True + + @property + def value(self): + """ + Returns the current value of the device. This is either :data:`True` + or :data:`False` depending on the value of :attr:`active_state`. + """ + return self._read() + +class DigitalInputDevice(InputDevice): + """ + :param int pin: + The pin that the device is connected to. + + :param bool pull_up: + If :data:`True` (the default), the device will be pulled up to + HIGH. If :data:`False`, the device will be pulled down to LOW. + + :param bool active_state: + If :data:`True` (the default), the device will return :data:`True` + if the pin is HIGH. If :data:`False`, the device will return + :data:`False` if the pin is LOW. + + :param float bounce_time: + The bounce time for the device. If set, the device will ignore + any button presses that happen within the bounce time after a + button release. This is useful to prevent accidental button + presses from registering as multiple presses. + """ + def __init__(self, pin, pull_up=False, active_state=None, bounce_time=None): + super().__init__(active_state) + 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._state = 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 _read(self): + return self._state_to_value(self._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._state != last_state: + # set the state + self._state = self._pin.value() + + def schedule_callback(callback): + callback() + + # manage call backs + if self.value and self._when_activated is not None: + schedule(schedule_callback, self._when_activated) + + elif not self.value and self._when_deactivated is not None: + schedule(schedule_callback, self._when_deactivated) + + @property + def is_active(self): + """ + Returns :data:`True` if the device is active. + """ + return bool(self.value) + + @property + def is_inactive(self): + """ + Returns :data:`True` if the device is inactive. + """ + return not bool(self.value) + + @property + def when_activated(self): + """ + Returns a :samp:`callback` that will be called when the device is activated. + """ + return self._when_activated + + @when_activated.setter + def when_activated(self, value): + self._when_activated = value + + @property + def when_deactivated(self): + """ + Returns a :samp:`callback` that will be called when the device is deactivated. + """ + return self._when_deactivated + + @when_activated.setter + def when_deactivated(self, value): + self._when_deactivated = value + + def close(self): + """ + Closes the device and releases any resources. Once closed, the device + can no longer be used. + """ + self._pin.irq(handler=None) + self._pin = None + +class Switch(DigitalInputDevice): + """ + :param int pin: + The pin that the device is connected to. + + :param bool pull_up: + If :data:`True` (the default), the device will be pulled up to + HIGH. If :data:`False`, the device will be pulled down to LOW. + + :param float bounce_time: + The bounce time for the device. If set, the device will ignore + any button presses that happen within the bounce time after a + button release. This is useful to prevent accidental button + presses from registering as multiple presses. Defaults to 0.02 + seconds. + """ + 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.is_released = Button.is_inactive +Button.when_pressed = Button.when_activated +Button.when_released = Button.when_deactivated class AnalogInputDevice(InputDevice): def __init__(self, pin, active_state=True, threshold=0.5): @@ -948,117 +1068,3 @@ def temp(self): TempSensor = TemperatureSensor Thermistor = TemperatureSensor -class PWMBuzzer(PWMOutputDevice): - - NOTES = {'b0': 31, 'c1': 33, 'c#1': 35, 'd1': 37, 'd#1': 39, 'e1': 41, 'f1': 44, 'f#1': 46, 'g1': 49,'g#1': 52, 'a1': 55, - 'a#1': 58, 'b1': 62, 'c2': 65, 'c#2': 69, 'd2': 73, 'd#2': 78, - 'e2': 82, 'f2': 87, 'f#2': 93, 'g2': 98, 'g#2': 104, 'a2': 110, 'a#2': 117, 'b2': 123, - 'c3': 131, 'c#3': 139, 'd3': 147, 'd#3': 156, 'e3': 165, 'f3': 175, 'f#3': 185, 'g3': 196, 'g#3': 208, 'a3': 220, 'a#3': 233, 'b3': 247, - 'c4': 262, 'c#4': 277, 'd4': 294, 'd#4': 311, 'e4': 330, 'f4': 349, 'f#4': 370, 'g4': 392, 'g#4': 415, 'a4': 440,'a#4': 466,'b4': 494, - 'c5': 523, 'c#5': 554, 'd5': 587, 'd#5': 622, 'e5': 659, 'f5': 698, 'f#5': 740, 'g5': 784, 'g#5': 831, 'a5': 880, 'a#5': 932, 'b5': 988, - 'c6': 1047, 'c#6': 1109, 'd6': 1175, 'd#6': 1245, 'e6': 1319, 'f6': 1397, 'f#6': 1480, 'g6': 1568, 'g#6': 1661, 'a6': 1760, 'a#6': 1865, 'b6': 1976, - 'c7': 2093, 'c#7': 2217, 'd7': 2349, 'd#7': 2489, - 'e7': 2637, 'f7': 2794, 'f#7': 2960, 'g7': 3136, 'g#7': 3322, 'a7': 3520, 'a#7': 3729, 'b7': 3951, - 'c8': 4186, 'c#8': 4435, 'd8': 4699, 'd#8': 4978 } - - def __init__(self, pin, freq=440, active_high=True, initial_value=0, volume=1, duty_factor=1023): - self._volume = volume - super().__init__( - pin, - freq=freq, - duty_factor=duty_factor, - active_high=active_high, - initial_value= 0, - ) - - @property - def volume(self): - return self._volume - - @volume.setter - def volume(self, value): - self._volume = value - - @property - def value(self): - return tuple(self._pwm.freq(), self.volume) - - @value.setter - def value(self, value): - self._stop_change() - self._write(value) - - def _write(self, value): - if value == 0 or value is None or value == '': - volume = 0 - else: - if type(value) is not tuple: - value = (value, self.volume) - - (freq, volume) = value - freq = self._to_freq(freq) if freq != 1 else 440 - - if freq is not None and freq != '' and freq != 0: - self._pwm.freq(freq) - else: - volume = 0 - - super()._write(volume) - - def _pitch(self, freq=440, duration=1, volume=1, wait=True): - self.off() - if duration is None: - self.value = (freq, volume) - else: - self._start_change(lambda : iter([((freq, volume), duration)]), 1, wait) - - def _to_freq(self, freq): - if freq is not None and freq != '' and freq != 0: - if type(freq) is str: - return int(self.NOTES[freq]) - elif freq <= 128 and freq > 0: # MIDI - midi_factor = 2**(1/12) - return int(440 * midi_factor ** (freq - 69)) - else: - return freq - else: - return None - - def play(self, tune=440, duration=1, volume=1, n=1, wait=True): - if type(tune) is not list: # use note and duration, no generator - self._pitch(tune, duration, volume, wait) - elif type(tune[0]) is not list: # single note don't use a generator - self._pitch(tune[0], tune[1], volume, wait) - else: # tune with multiple notes - def tune_generator(): - for next in tune: - note = next[0] - if len(next) == 2: - duration = float(next[1]) - if note == '' or note is None: - yield ((None, 0), duration) - else: # leave a gap between notes - yield ((note, volume), duration * 0.9) - yield ((None, 0), duration * 0.1) - - self.off() - self._start_change(tune_generator, n, wait) - - def _stop(self, timer_obj=None): - self.off() - - def on(self, freq=440, volume=1): - if freq is not None: - self.value = (freq, volume) - - def __del__(self): - self.off() - super().__del__() - -PWMBuzzer.beep = PWMBuzzer.blink - -def Speaker(pin, use_tones=True, active_high=True, volume=1, initial_value=False): - if use_tones: - return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=volume) - else: - return Buzzer(pin, active_high=active_high, initial_value=False) From 1eedd1f958369d82a311f57d49e16f26a02415ce Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 11 Mar 2022 15:58:28 +0000 Subject: [PATCH 24/34] added docstrings --- picozero/picozero.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/picozero/picozero.py b/picozero/picozero.py index f2bca61..79fbdb7 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -286,6 +286,21 @@ def close(self): self._pin = None class PWMLED(PWMOutputDevice): + """ + Represents an LED driven by a PWM pin whose brightness can be changed. + + :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. + """ def __init__(self, pin, active_high=True, initial_value=False): self._brightness = 1 super().__init__(pin=pin, @@ -854,6 +869,9 @@ def value(self): class DigitalInputDevice(InputDevice): """ + Represents a generic input device with digital functionality e.g. buttons + which can be either active or inactive. + :param int pin: The pin that the device is connected to. @@ -978,6 +996,8 @@ def close(self): class Switch(DigitalInputDevice): """ + Represents a toggle switch which is either open or closed. + :param int pin: The pin that the device is connected to. @@ -1001,6 +1021,23 @@ def __init__(self, pin, pull_up=True, bounce_time=0.02): Switch.when_opened = Switch.when_deactivated class Button(Switch): + """ + Represents a push button which can be either pressed or released. + + :param int pin: + The pin that the device is connected to. + + :param bool pull_up: + If :data:`True` (the default), the device will be pulled up to + HIGH. If :data:`False`, the device will be pulled down to LOW. + + :param float bounce_time: + The bounce time for the device. If set, the device will ignore + any button presses that happen within the bounce time after a + button release. This is useful to prevent accidental button + presses from registering as multiple presses. Defaults to 0.02 + seconds. + """ pass Button.is_pressed = Button.is_active From 599f44b6520302e516600d3d9ee01b7b16a1f930 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Mon, 14 Mar 2022 10:02:25 +0000 Subject: [PATCH 25/34] Update picozero.py More flexibility in specifying lists of notes without duration. #21 --- picozero/picozero.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 79fbdb7..5626994 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -524,25 +524,31 @@ def _to_freq(self, freq): else: return None - def play(self, tune=440, duration=1, volume=1, n=1, wait=True): + def play(self, tune=440, duration=1, volume=1, n=1, wait=True): if type(tune) is not list: # use note and duration, no generator self._pitch(tune, duration, volume, wait) - elif type(tune[0]) is not list: # single note don't use a generator + elif type(tune[0]) is not list and len(tune) <= 2: # single note don't use a generator self._pitch(tune[0], tune[1], volume, wait) else: # tune with multiple notes def tune_generator(): + next_duration = duration for next in tune: - note = next[0] - if len(next) == 2: - duration = float(next[1]) + if type(next) is not list: + note = next + else: + note = next[0] + if type(next) is list and len(next) == 2: + if next[1] is not None: + next_duration = float(next[1]) if note == '' or note is None: - yield ((None, 0), duration) + yield ((None, 0), next_duration) else: # leave a gap between notes - yield ((note, volume), duration * 0.9) - yield ((None, 0), duration * 0.1) + yield ((note, volume), next_duration * 0.9) + yield ((None, 0), next_duration* 0.1) self.off() self._start_change(tune_generator, n, wait) + def _stop(self, timer_obj=None): self.off() From 85dbd6515ddeb4f5cd1a20c8b187a6eb84d0f33f Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Mon, 14 Mar 2022 16:52:46 +0000 Subject: [PATCH 26/34] refactored PWMBuzzer and Speaker --- picozero/picozero.py | 270 +++++++++++++++++++++++++++++-------------- 1 file changed, 184 insertions(+), 86 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 79fbdb7..53cbb14 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -62,8 +62,8 @@ def _set_value(self, timer_obj=None): self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) if next_seq is None: - # the sequence has finished, set the output to 0 - self._output_device.value = 0 + # the sequence has finished, turn the device off + self._output_device.off() self._running = False def _get_value(self): @@ -95,7 +95,8 @@ class OutputDevice: """ def __init__(self, active_high=True, initial_value=False): self.active_high = active_high - self._write(initial_value) + if initial_value is not None: + self._write(initial_value) self._value_changer = None @property @@ -230,6 +231,21 @@ class DigitalLED(DigitalOutputDevice): DigitalLED.is_lit = DigitalLED.is_active class Buzzer(DigitalOutputDevice): + """ + Represents an active or passive buzzer which can be turned on or 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 Buzzer will be off initially. If + :data:`True`, the Buzzer will be switched on initially. + """ pass Buzzer.beep = Buzzer.blink @@ -244,6 +260,7 @@ def __init__(self, pin, freq=100, duty_factor=65025, active_high=True, initial_v self._pin_num = pin self._duty_factor = duty_factor self._pwm = PWM(Pin(pin)) + self._pwm.freq(freq) super().__init__(active_high, initial_value) def _check_pwm_channel(self, pin_num): @@ -273,6 +290,20 @@ def is_active(self): Returns :data:`True` if the device is on. """ return self.value != 0 + + @property + def freq(self): + """ + Returns the current frequency of the device. + """ + return self._pwm.freq() + + @freq.setter + def freq(self, freq): + """ + Sets the frequency of the device. + """ + self._pwm.freq(freq) def close(self): """ @@ -283,6 +314,7 @@ def close(self): del PWMOutputDevice._channels_used[ PWMOutputDevice.PIN_TO_PWM_CHANNEL[self._pin_num] ] + self._pin.deinit() self._pin = None class PWMLED(PWMOutputDevice): @@ -449,28 +481,28 @@ def LED(pin, use_pwm=True, active_high=True, initial_value=False): pico_led = LED(25) class PWMBuzzer(PWMOutputDevice): - - NOTES = {'b0': 31, 'c1': 33, 'c#1': 35, 'd1': 37, 'd#1': 39, 'e1': 41, 'f1': 44, 'f#1': 46, 'g1': 49,'g#1': 52, 'a1': 55, - 'a#1': 58, 'b1': 62, 'c2': 65, 'c#2': 69, 'd2': 73, 'd#2': 78, - 'e2': 82, 'f2': 87, 'f#2': 93, 'g2': 98, 'g#2': 104, 'a2': 110, 'a#2': 117, 'b2': 123, - 'c3': 131, 'c#3': 139, 'd3': 147, 'd#3': 156, 'e3': 165, 'f3': 175, 'f#3': 185, 'g3': 196, 'g#3': 208, 'a3': 220, 'a#3': 233, 'b3': 247, - 'c4': 262, 'c#4': 277, 'd4': 294, 'd#4': 311, 'e4': 330, 'f4': 349, 'f#4': 370, 'g4': 392, 'g#4': 415, 'a4': 440,'a#4': 466,'b4': 494, - 'c5': 523, 'c#5': 554, 'd5': 587, 'd#5': 622, 'e5': 659, 'f5': 698, 'f#5': 740, 'g5': 784, 'g#5': 831, 'a5': 880, 'a#5': 932, 'b5': 988, - 'c6': 1047, 'c#6': 1109, 'd6': 1175, 'd#6': 1245, 'e6': 1319, 'f6': 1397, 'f#6': 1480, 'g6': 1568, 'g#6': 1661, 'a6': 1760, 'a#6': 1865, 'b6': 1976, - 'c7': 2093, 'c#7': 2217, 'd7': 2349, 'd#7': 2489, - 'e7': 2637, 'f7': 2794, 'f#7': 2960, 'g7': 3136, 'g#7': 3322, 'a7': 3520, 'a#7': 3729, 'b7': 3951, - 'c8': 4186, 'c#8': 4435, 'd8': 4699, 'd#8': 4978 } - - def __init__(self, pin, freq=440, active_high=True, initial_value=0, volume=1, duty_factor=1023): - self._volume = volume - super().__init__( - pin, - freq=freq, - duty_factor=duty_factor, - active_high=active_high, - initial_value= 0, - ) - + """ + Represents a passive buzzer driven by a PWM pin whose volume can be changed. + + :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 buzzer will be off initially. If + :data:`True`, the buzzer will be switched on initially. + """ + def __init__(self, pin, freq=100, duty_factor=1023, active_high=True, initial_value=False): + self._volume = 1 + super().__init__(pin=pin, + duty_factor=duty_factor, + active_high=active_high, + initial_value=initial_value) + @property def volume(self): return self._volume @@ -478,40 +510,89 @@ def volume(self): @volume.setter def volume(self, value): self._volume = value + self.value = 1 if self._volume > 0 else 0 + + def _write(self, value): + super()._write(self._volume * value) + + def _read(self): + return 1 if super()._read() > 0 else 0 + +class Speaker(OutputDevice): + NOTES = { + 'b0': 31, 'c1': 33, 'c#1': 35, 'd1': 37, 'd#1': 39, 'e1': 41, 'f1': 44, 'f#1': 46, 'g1': 49,'g#1': 52, 'a1': 55, + 'a#1': 58, 'b1': 62, 'c2': 65, 'c#2': 69, 'd2': 73, 'd#2': 78, + 'e2': 82, 'f2': 87, 'f#2': 93, 'g2': 98, 'g#2': 104, 'a2': 110, 'a#2': 117, 'b2': 123, + 'c3': 131, 'c#3': 139, 'd3': 147, 'd#3': 156, 'e3': 165, 'f3': 175, 'f#3': 185, 'g3': 196, 'g#3': 208, 'a3': 220, 'a#3': 233, 'b3': 247, + 'c4': 262, 'c#4': 277, 'd4': 294, 'd#4': 311, 'e4': 330, 'f4': 349, 'f#4': 370, 'g4': 392, 'g#4': 415, 'a4': 440,'a#4': 466,'b4': 494, + 'c5': 523, 'c#5': 554, 'd5': 587, 'd#5': 622, 'e5': 659, 'f5': 698, 'f#5': 740, 'g5': 784, 'g#5': 831, 'a5': 880, 'a#5': 932, 'b5': 988, + 'c6': 1047, 'c#6': 1109, 'd6': 1175, 'd#6': 1245, 'e6': 1319, 'f6': 1397, 'f#6': 1480, 'g6': 1568, 'g#6': 1661, 'a6': 1760, 'a#6': 1865, 'b6': 1976, + 'c7': 2093, 'c#7': 2217, 'd7': 2349, 'd#7': 2489, + 'e7': 2637, 'f7': 2794, 'f#7': 2960, 'g7': 3136, 'g#7': 3322, 'a7': 3520, 'a#7': 3729, 'b7': 3951, + 'c8': 4186, 'c#8': 4435, 'd8': 4699, 'd#8': 4978 + } + + def __init__(self, pin, initial_freq=440, initial_volume=0, duty_factor=1023, active_high=True): + self._pwm_buzzer = PWMBuzzer( + pin, + freq=initial_freq, + duty_factor=duty_factor, + active_high=active_high, + initial_value=None, + ) + + super().__init__(active_high, None) + self.volume = initial_volume + + def on(self, volume=1): + self.volume = volume + + def off(self): + self.volume = 0 + @property def value(self): - return tuple(self._pwm.freq(), self.volume) + """ + Sets or returns the value of the speaker. value is a tuple of (freq, volume). + """ + return tuple(self.freq, self.volume) @value.setter def value(self, value): self._stop_change() - self._write(value) - + self._write(value) + + @property + def volume(self): + """ + Sets or returns the volume of the speaker. 1 for max volume. 0 for off. + """ + return self._volume + + @volume.setter + def volume(self, value): + self._volume = value + self.value = (self.freq, self._volume) + + @property + def freq(self): + """ + Sets or returns the current frequency of the speaker. + """ + return self._pwm_buzzer.freq + + @freq.setter + def freq(self, freq): + self._pwm_buzzer.freq(freq) + def _write(self, value): - if value == 0 or value is None or value == '': - volume = 0 - else: - if type(value) is not tuple: - value = (value, self.volume) - - (freq, volume) = value - freq = self._to_freq(freq) if freq != 1 else 440 - - if freq is not None and freq != '' and freq != 0: - self._pwm.freq(freq) - else: - volume = 0 - - super()._write(volume) - - def _pitch(self, freq=440, duration=1, volume=1, wait=True): - self.off() - if duration is None: - self.value = (freq, volume) - else: - self._start_change(lambda : iter([((freq, volume), duration)]), 1, wait) + # set the frequency + self._pwm_buzzer.freq = value[0] + # write the volume value + self._pwm_buzzer.volume = value[1] + def _to_freq(self, freq): if freq is not None and freq != '' and freq != 0: if type(freq) is str: @@ -523,45 +604,61 @@ def _to_freq(self, freq): return freq else: return None - - def play(self, tune=440, duration=1, volume=1, n=1, wait=True): - if type(tune) is not list: # use note and duration, no generator - self._pitch(tune, duration, volume, wait) - elif type(tune[0]) is not list: # single note don't use a generator - self._pitch(tune[0], tune[1], volume, wait) - else: # tune with multiple notes - def tune_generator(): - for next in tune: - note = next[0] - if len(next) == 2: - duration = float(next[1]) - if note == '' or note is None: - yield ((None, 0), duration) - else: # leave a gap between notes - yield ((note, volume), duration * 0.9) - yield ((None, 0), duration * 0.1) - self.off() - self._start_change(tune_generator, n, wait) - - def _stop(self, timer_obj=None): - self.off() - - def on(self, freq=440, volume=1): - if freq is not None: - self.value = (freq, volume) - - def __del__(self): - self.off() - super().__del__() + def beep(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fade_out_time=None, fps=25): + """ + Beeps the speaker. + """ + self._pwm_buzzer.blink(on_time, off_time, n, wait, fade_in_time, fade_out_time, fps) + + def play(self, tune=440, volume=1, duration=1, n=1, wait=True): + """ + Plays a tune for a given duration. -PWMBuzzer.beep = PWMBuzzer.blink + :param int tune: + The tune can be a single frequency, note, or a list of frequencies or notes. -def Speaker(pin, use_tones=True, active_high=True, volume=1, initial_value=False): - if use_tones: - return PWMBuzzer(pin, freq=440, active_high=active_high, initial_value=volume) - else: - return Buzzer(pin, active_high=active_high, initial_value=False) + Notes should be in the format 'b0'. + + :param float duration: + The duration of the note in seconds. + + :param float volume: + The volume of the note. + + :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 tune has finished. If False + the method will return and the tune will play in the background. + Defaults to True. + """ + # tune isnt a list, so it must be a single frequency or note + if type(tune) is not list: + # make it into a list + tune = [tune] + + # tune is now a list so lets play it using a generator + def tune_generator(): + for note in tune: + + # turn the notes in frequencies + freq = self._to_freq(note) + yield ((freq, volume), duration) + + # I wasnt sure what this was doing? Maybe allowing you + # to specify timings between each note? + #if len(next) == 2: + # duration = float(next[1]) + #if note == '' or note is None: + # yield ((None, 0), duration) + #else: # leave a gap between notes + # yield ((note, volume), duration * 0.9) + # yield ((None, 0), duration * 0.1) + + self._start_change(tune_generator, n, wait) class RGBLED(OutputDevice): """ @@ -1105,3 +1202,4 @@ def temp(self): TempSensor = TemperatureSensor Thermistor = TemperatureSensor + From 9771907a130452a45a266d78501b3c4d894e464e Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Mon, 14 Mar 2022 17:08:16 +0000 Subject: [PATCH 27/34] Update picozero.py On and fade times for RGB blink #23 --- picozero/picozero.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 5626994..ab0282a 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -790,14 +790,14 @@ def blink_generator(): ) for c in range(len(colors)): + if on_times[c] > 0: + yield (colors[c], on_times[c]) + 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], on_times[c]) self._start_change(blink_generator, n, wait) From ff4c3ba23807642e75c1e19e77a825721df88717 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Tue, 15 Mar 2022 15:29:59 +0000 Subject: [PATCH 28/34] tune generator --- picozero/picozero.py | 47 ++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 53cbb14..1e49f00 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -616,16 +616,19 @@ def play(self, tune=440, volume=1, duration=1, n=1, wait=True): Plays a tune for a given duration. :param int tune: - The tune can be a single frequency, note, or a list of frequencies or notes. - Notes should be in the format 'b0'. + The tune to play which can be specified as: + + + a single "note", represented as: + + a frequency in Hz e.g. `440` + + a midi note e.g. `60` + + a note name as a string e.g `"E4"` + + a list of single notes e.g. `[440, 60, "E4"]` + + a list of 2 value tuples of (note, duration) e.g. `[(440,1), (60, 2), ("e4", 3)]` :param float duration: The duration of the note in seconds. - :param float volume: - The volume of the note. - :param int n: The number of times to pulse the LED. If None the LED will pulse forever. Defaults to None. @@ -635,29 +638,31 @@ def play(self, tune=440, volume=1, duration=1, n=1, wait=True): the method will return and the tune will play in the background. Defaults to True. """ + # tune isnt a list, so it must be a single frequency or note - if type(tune) is not list: - # make it into a list - tune = [tune] + if not isinstance(tune, (list, tuple)): + tune = [(tune, volume)] - # tune is now a list so lets play it using a generator def tune_generator(): for note in tune: + # note isnt a list or tuple, it must be a single frequency or note + if not isinstance(note, (list, tuple)): + # make it into a tuple + note = (note, duration) + # turn the notes in frequencies - freq = self._to_freq(note) - yield ((freq, volume), duration) + freq = self._to_freq(note[0]) + freq_duration = note[1] - # I wasnt sure what this was doing? Maybe allowing you - # to specify timings between each note? - #if len(next) == 2: - # duration = float(next[1]) - #if note == '' or note is None: - # yield ((None, 0), duration) - #else: # leave a gap between notes - # yield ((note, volume), duration * 0.9) - # yield ((None, 0), duration * 0.1) - + # if this is a tune of greater than 1 note, add gaps between notes + print(freq, freq_duration) + if len(tune) == 1: + yield ((freq, volume), freq_duration) + else: + yield ((freq, volume), freq_duration * 0.9) + yield ((freq, 0), freq_duration * 0.1) + self._start_change(tune_generator, n, wait) class RGBLED(OutputDevice): From f99235fc336d6bbbc40ea275843c8d91e0c37274 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Wed, 16 Mar 2022 08:46:54 +0000 Subject: [PATCH 29/34] removed debug print --- picozero/picozero.py | 1 - 1 file changed, 1 deletion(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 1e49f00..5eb1696 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -656,7 +656,6 @@ def tune_generator(): freq_duration = note[1] # if this is a tune of greater than 1 note, add gaps between notes - print(freq, freq_duration) if len(tune) == 1: yield ((freq, volume), freq_duration) else: From e4b99be7647fa08bb34594c5d4406a46ae1e603b Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Wed, 16 Mar 2022 08:58:16 +0000 Subject: [PATCH 30/34] documentation updates --- picozero/picozero.py | 60 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 5eb1696..f22948e 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -168,6 +168,11 @@ def blink(self, on_time=1, off_time=None, n=None, wait=False): The number of times to repeat the blink operation. If None is specified, the device will continue blinking forever. The default is None. + + :param bool wait: + If True the method will block until the device stops turning on and off. + If False the method will return and the device will turn on and off in + the background. Defaults to False. """ off_time = on_time if off_time is None else off_time @@ -183,6 +188,9 @@ def _stop_change(self): self._value_changer = None def close(self): + """ + Turns the device off. + """ self.value = 0 class DigitalOutputDevice(OutputDevice): @@ -369,6 +377,11 @@ def blink(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fa The number of times to repeat the blink operation. If `None`, the device will continue blinking forever. The default is `None`. + :param bool wait: + If True the method will block until the LED stops blinking. If False + the method will return and the LED is will blink in the background. + Defaults to False. + :param float fade_in_time: The length of time in seconds to spend fading in. Defaults to 0. @@ -379,7 +392,7 @@ def blink(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fa :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.off() off_time = on_time if off_time is None else off_time @@ -432,7 +445,6 @@ def pulse(self, fade_in_time=1, fade_out_time=None, n=None, wait=False, fps=25): 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, wait=wait, fps=fps) @@ -518,6 +530,8 @@ def _write(self, value): def _read(self): return 1 if super()._read() > 0 else 0 +PWMBuzzer.beep = Buzzer.blink + class Speaker(OutputDevice): NOTES = { 'b0': 31, 'c1': 33, 'c#1': 35, 'd1': 37, 'd#1': 39, 'e1': 41, 'f1': 44, 'f#1': 46, 'g1': 49,'g#1': 52, 'a1': 55, @@ -607,7 +621,34 @@ def _to_freq(self, freq): def beep(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fade_out_time=None, fps=25): """ - Beeps the speaker. + Make the buzzer 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 beep operation. If `None`, the + device will continue blinking forever. The default is `None`. + + :param bool wait: + If True the method will block until the buzzer stops beeping. If False + the method will return and the buzzer will beep in the background. + Defaults to False. + + :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 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._pwm_buzzer.blink(on_time, off_time, n, wait, fade_in_time, fade_out_time, fps) @@ -626,12 +667,17 @@ def play(self, tune=440, volume=1, duration=1, n=1, wait=True): + a list of single notes e.g. `[440, 60, "E4"]` + a list of 2 value tuples of (note, duration) e.g. `[(440,1), (60, 2), ("e4", 3)]` + Defaults to `440`. + + :param int volume: + The volume of the tune. 1 is max volume. 0 is mute. Defaults to 1. + :param float duration: - The duration of the note in seconds. + The duration of each note in seconds. Defaults to 1. :param int n: - The number of times to pulse the LED. If None the LED will pulse - forever. Defaults to None. + The number of times to play the tune. If None the tune will play + forever. Defaults to 1. :param bool wait: If True the method will block until the tune has finished. If False @@ -700,7 +746,7 @@ class RGBLED(OutputDevice): If :data:`True` (the default), construct :class:`PWMLED` instances for each component of the RGBLED. If :data:`False`, construct :class:`DigitalLED` instances. - :type pin_factory: Factory or None + """ def __init__(self, red=None, green=None, blue=None, active_high=True, initial_value=(0, 0, 0), pwm=True): From 0014b8a77d24fdef747a75cc00e9a86f28e74d16 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Wed, 16 Mar 2022 11:13:36 +0000 Subject: [PATCH 31/34] Update picozero.py Addressed parameter order for volume and duration. Fixed issue with volume being used instead of duration for single notes. #29 --- picozero/picozero.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index f22948e..63c4a2d 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -652,7 +652,7 @@ def beep(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fad """ self._pwm_buzzer.blink(on_time, off_time, n, wait, fade_in_time, fade_out_time, fps) - def play(self, tune=440, volume=1, duration=1, n=1, wait=True): + def play(self, tune=440, duration=1, volume=1, n=1, wait=True): """ Plays a tune for a given duration. @@ -687,7 +687,7 @@ def play(self, tune=440, volume=1, duration=1, n=1, wait=True): # tune isnt a list, so it must be a single frequency or note if not isinstance(tune, (list, tuple)): - tune = [(tune, volume)] + tune = [(tune, duration)] def tune_generator(): for note in tune: From 02e7b08920b0ccd6be9f47bda423271cb3416709 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Wed, 16 Mar 2022 14:08:56 +0000 Subject: [PATCH 32/34] moved blink and pulse to PWMOutputDevice --- picozero/picozero.py | 98 ++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 63c4a2d..c10acc0 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -312,55 +312,6 @@ def freq(self, freq): Sets the frequency of the device. """ self._pwm.freq(freq) - - def close(self): - """ - Closes the device and turns the device off. Once closed, the device - can no longer be used. - """ - super().close() - del PWMOutputDevice._channels_used[ - PWMOutputDevice.PIN_TO_PWM_CHANNEL[self._pin_num] - ] - self._pin.deinit() - self._pin = None - -class PWMLED(PWMOutputDevice): - """ - Represents an LED driven by a PWM pin whose brightness can be changed. - - :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. - """ - 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, n=None, wait=False, fade_in_time=0, fade_out_time=None, fps=25): """ @@ -448,6 +399,55 @@ def pulse(self, fade_in_time=1, fade_out_time=None, n=None, wait=False, fps=25): """ 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) + def close(self): + """ + Closes the device and turns the device off. Once closed, the device + can no longer be used. + """ + super().close() + del PWMOutputDevice._channels_used[ + PWMOutputDevice.PIN_TO_PWM_CHANNEL[self._pin_num] + ] + self._pin.deinit() + self._pin = None + +class PWMLED(PWMOutputDevice): + """ + Represents an LED driven by a PWM pin whose brightness can be changed. + + :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. + """ + 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 LED(pin, use_pwm=True, active_high=True, initial_value=False): """ Returns an instance of :class:`DigitalLED` or :class:`PWMLED` depending on From cb0e5c8e0822bc1ef89d70c8d17eaaf1c216b849 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Wed, 16 Mar 2022 14:33:02 +0000 Subject: [PATCH 33/34] updated duty factor --- picozero/picozero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index c10acc0..3c53717 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -263,7 +263,7 @@ 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): + def __init__(self, pin, freq=100, duty_factor=65536, active_high=True, initial_value=False): self._check_pwm_channel(pin) self._pin_num = pin self._duty_factor = duty_factor From 2932ec49746a6403d92595bba2b2e8eda6a8ca2e Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Wed, 16 Mar 2022 14:34:14 +0000 Subject: [PATCH 34/34] updated duty factor --- picozero/picozero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 3c53717..ffa351d 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -263,7 +263,7 @@ 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=65536, active_high=True, initial_value=False): + def __init__(self, pin, freq=100, duty_factor=65535, active_high=True, initial_value=False): self._check_pwm_channel(pin) self._pin_num = pin self._duty_factor = duty_factor