From 208882f701423fc4d78d78637bd90c6894796f7a Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 6 May 2022 18:30:18 +0100 Subject: [PATCH 01/10] fix issue 51 --- picozero/picozero.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 371ae21..d520ed8 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -497,8 +497,8 @@ def close(self): del PWMOutputDevice._channels_used[ PWMOutputDevice.PIN_TO_PWM_CHANNEL[self._pin_num] ] - self._pin.deinit() - self._pin = None + self._pwm.deinit() + self._pwm = None class PWMLED(PWMOutputDevice): """ From 911d7835dcb1cafc99796cb7b0d47fd00c62d893 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 6 May 2022 18:32:32 +0100 Subject: [PATCH 02/10] fix issue AnalogueInputDevice's should have a close method #52 --- picozero/picozero.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/picozero/picozero.py b/picozero/picozero.py index d520ed8..17062aa 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -1366,6 +1366,9 @@ def voltage(self): """ return self.value * 3.3 + def close(self): + self._adc = None + class Potentiometer(AnalogInputDevice): """ Represents a Potentiometer which outputs with a variable voltage From 2f321638d932df89f5e228565671fc2f59975acb Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 6 May 2022 18:36:09 +0100 Subject: [PATCH 03/10] fix Speaker should have a close method #53 --- picozero/picozero.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/picozero/picozero.py b/picozero/picozero.py index 17062aa..9dee158 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -807,6 +807,9 @@ def tune_generator(): self._start_change(tune_generator, n, wait) + def close(self): + self._pwm_buzzer.close() + class RGBLED(OutputDevice, PinsMixin): """ Extends :class:`OutputDevice` and represents a full color LED component (composed From 4943b41e5bc3d4c93a4de939ed56b6c3bd4a9353 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 6 May 2022 19:19:07 +0100 Subject: [PATCH 04/10] Fix #55 --- picozero/picozero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 9dee158..68d742b 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -370,7 +370,7 @@ def _check_pwm_channel(self, pin_num): PWMOutputDevice._channels_used[channel] = pin_num def _state_to_value(self, state): - return (state if self.active_high else 1 - state) / self._duty_factor + return (state if self.active_high else self._duty_factor - state) / self._duty_factor def _value_to_state(self, value): return int(self._duty_factor * (value if self.active_high else 1 - value)) From 08937b794b1fc539c27252b39184e91176e34291 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Fri, 3 Jun 2022 09:34:58 +0100 Subject: [PATCH 05/10] resolve AnalogueInputDevice active_state #56 --- picozero/picozero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 68d742b..016946e 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -1335,7 +1335,7 @@ def __init__(self, pin, active_state=True, threshold=0.5): self._threshold = float(threshold) def _state_to_value(self, state): - return (state if self.active_state else 1 - state) / 65535 + return (state if self.active_state else 65535 - state) / 65535 def _value_to_state(self, value): return int(65535 * (value if self.active_state else 1 - value)) From 4fc0e801d321a494d03ef8c4d0245b8d80a6e4e5 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Sat, 4 Jun 2022 08:38:08 +0100 Subject: [PATCH 06/10] Adding tests (#57) * test experiments * added tests * added tests * migrated test to unittest * added test * added tests readme * added digital input test * added ADC tests * updated tests docs --- docs/developing.rst | 15 +- tests/README.rst | 30 ++++ tests/test_picozero.py | 379 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 423 insertions(+), 1 deletion(-) create mode 100644 tests/README.rst create mode 100644 tests/test_picozero.py diff --git a/docs/developing.rst b/docs/developing.rst index c31e1c9..10b67ca 100644 --- a/docs/developing.rst +++ b/docs/developing.rst @@ -39,4 +39,17 @@ The website will be built in the directory docs/_build/html. Documentation can be viewed at `picozero.readthedocs.io`_. -.. _picozero.readthedocs.io: https://picozero.readthedocs.io \ No newline at end of file +.. _picozero.readthedocs.io: https://picozero.readthedocs.io + +Tests +----- + +The tests are design to be run on a Raspberry Pi Pico. + +1. Install the `picozero `_ package. + +2. Install the `micropython-unittest `_ package. + +3. Copy the ``test_picozero.py`` to the pico. + +4. Run the ``test_picozero.py`` file. \ No newline at end of file diff --git a/tests/README.rst b/tests/README.rst new file mode 100644 index 0000000..241566b --- /dev/null +++ b/tests/README.rst @@ -0,0 +1,30 @@ +Tests +===== + +The tests are design to be run on a Raspberry Pi Pico. + +Setup +----- + +1. Install the `picozero `_ package. + +2. Install the `micropython-unittest `_ package. + +3. Copy the ``test_picozero.py`` to the pico. + +4. Run the ``test_picozero.py`` file. + +Error messsages +--------------- + +If a test fails it is helpful to be able to see verbose error messages. To see error messages you need to modify the ``lib/unittest.py`` file on the pico. + +Locate the following code in the ``run_class`` function:: + + # Uncomment to investigate failure in detail + #raise + +Uncomment ``raise``:: + + # Uncomment to investigate failure in detail + raise \ No newline at end of file diff --git a/tests/test_picozero.py b/tests/test_picozero.py new file mode 100644 index 0000000..8da37dd --- /dev/null +++ b/tests/test_picozero.py @@ -0,0 +1,379 @@ +import unittest +from picozero import * +from time import ticks_ms + +def log_device_values(d, timeout): + values = [d.value] + + timeout_ms = ticks_ms() + (timeout * 1000) + + while ticks_ms() < timeout_ms: + if values[-1] != d.value: + values.append(d.value) + + return values + +class MockPin: + def __init__(self, initial_state=0, irq_handler=None): + self._state = initial_state + self._irq_handler = irq_handler + + def read(self): + return self._state + + def write(self, state): + self._state = state + if self._irq_handler is not None: + self._irq_handler(self) + + def value(self, state=None): + if state is None: + return self._state + else: + self.write(state) + + def irq(self, handler, args=None): + self._irq_handler = handler + +class MockADC: + def __init__(self, initial_state=0): + self._state = initial_state + + def read(self): + return self._state + + def write(self, state): + self._state = state + + def read_u16(self): + return self._state + +class MockEvent: + def __init__(self): + self._is_set = False + + def set(self): + self._is_set = True + + def is_set(self): + return self._is_set + + def reset(self): + self._is_set = False + +class Testpicozero(unittest.TestCase): + + def test_digital_output_device_default_values(self): + d = DigitalOutputDevice(1) + + self.assertTrue(d.active_high) + self.assertEqual(d.value, 0) + self.assertFalse(d.is_active) + + d.on() + self.assertTrue(d.value) + self.assertEqual(d._pin.value(), 1) + self.assertTrue(d.is_active) + + d.off() + self.assertFalse(d.value) + self.assertEqual(d._pin.value(), 0) + self.assertFalse(d.is_active) + + d.value = True + self.assertEqual(d.value, 1) + d.value = False + self.assertEqual(d.value, 0) + + d.close() + self.assertIsNone(d._pin) + + def test_digital_output_device_alt_values(self): + d = DigitalOutputDevice(1, active_high=False, initial_value=True) + + self.assertFalse(d.active_high) + self.assertTrue(d.value) + + d.off() + self.assertEqual(d._pin.value(), 1) + + d.on() + self.assertEqual(d._pin.value(), 0) + + d.close() + + def test_digital_output_device_blink(self): + d = DigitalOutputDevice(1) + + d.blink() + values = log_device_values(d, 1.1) + self.assertEqual(values, [1,0]) + d.off() + self.assertFalse(d.value) + + d.blink(on_time=0.1, off_time=0.1, n=2) + values = log_device_values(d, 0.5) + self.assertEqual(values, [1,0,1,0]) + self.assertFalse(d.value) + + d.close() + + def test_digital_LED(self): + d = DigitalLED(1) + self.assertFalse(d.is_lit) + d.close() + + def test_pwm_output_device_default_values(self): + d = PWMOutputDevice(1) + + self.assertTrue(d.active_high) + self.assertEqual(d.value, 0) + self.assertFalse(d.is_active) + self.assertEqual(d.freq, 100) + + d.on() + self.assertTrue(d.value) + self.assertTrue(d.is_active) + self.assertEqual(d._pwm.duty_u16(), 65535) + self.assertTrue(d.is_active) + + d.off() + self.assertFalse(d.value) + self.assertEqual(d._pwm.duty_u16(),0) + self.assertFalse(d.is_active) + + d.value = True + self.assertEqual(d.value, 1) + d.value = False + self.assertEqual(d.value, 0) + + d.value = 0.5 + self.assertAlmostEqual(d.value, 0.5, places=2) + self.assertTrue(d.is_active) + + d.close() + self.assertIsNone(d._pwm) + + def test_pwm_output_device_alt_values(self): + d = PWMOutputDevice(1, freq=200, duty_factor=10000, active_high=False, initial_value=True) + + self.assertFalse(d.active_high) + self.assertTrue(d.value) + self.assertEqual(d.freq, 200) + + d.off() + # pwm returns 1 less than the duty_factor unless the duty is set to the maximum 65535 + self.assertEqual(d._pwm.duty_u16(), 9999) + self.assertAlmostEqual(d.value, 0, places=2) + + d.on() + self.assertEqual(d._pwm.duty_u16(), 0) + self.assertEqual(d.value, 1) + + d.off() + + d.close() + + def test_pwm_output_device_blink(self): + d = PWMOutputDevice(1) + + d.blink() + values = log_device_values(d, 1.1) + self.assertEqual(values, [1,0]) + d.off() + self.assertFalse(d.value) + + d.blink(on_time=0.1, off_time=0.1, n=2) + values = log_device_values(d, 0.5) + self.assertEqual(values, [1,0,1,0]) + self.assertFalse(d.value) + + d.close() + + def test_pwm_output_device_pulse(self): + d = PWMOutputDevice(1) + + d.pulse(n=1) + values = log_device_values(d, 2.1) + + expected = [ + 0.0, 0.04, 0.08, 0.12, 0.16, 0.2, 0.24, 0.28, 0.32, 0.36, 0.4, + 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.84, + 0.88, 0.92, 0.96, 1.0, 0.96, 0.92, 0.88, 0.84, 0.8, 0.76, + 0.72, 0.68, 0.64, 0.6, 0.56, 0.52, 0.48, 0.44, 0.4, 0.36, 0.32, + 0.28, 0.24, 0.2, 0.16, 0.12, 0.08, 0.04, 0.0] + + if len(values) == len(expected): + for i in range(len(values)): + self.assertAlmostEqual(values[i], expected[i], places=2) + else: + self.fail(f"{len(values)} were generated, {len(expected)} were expected.") + + d.pulse(fade_in_time=0.5, fade_out_time=1, n=1, fps=4) + values = log_device_values(d, 2.1) + + expected = [0.0, 0.5, 1.0, 0.75, 0.5, 0.25, 0] + + if len(values) == len(expected): + for i in range(len(values)): + self.assertAlmostEqual(values[i], expected[i], places=2) + else: + self.fail(f"{len(values)} values were generated, {len(expected)} were expected.") + + d.close() + + def test_LED_factory(self): + d = LED(1) + self.assertIsInstance(d, PWMLED) + d.close() + + d = LED(1, use_pwm=False) + self.assertIsInstance(d, DigitalLED) + d.close() + + def test_digital_input_device_default_values(self): + d = DigitalInputDevice(1) + + pin = MockPin(irq_handler=d._pin_change) + d._pin = pin + + self.assertTrue(d.active_state) + self.assertFalse(d.is_active) + self.assertEqual(d.value, 0) + + pin.write(1) + + self.assertTrue(d.is_active) + self.assertEqual(d.value, 1) + + pin.write(0) + + self.assertFalse(d.is_active) + self.assertEqual(d.value, 0) + + d.close() + + def test_digital_input_device_alt_values(self): + d = DigitalInputDevice(1, pull_up=False, active_state=False) + + pin = MockPin(irq_handler=d._pin_change) + d._pin = pin + + self.assertFalse(d.active_state) + self.assertTrue(d.is_active) + self.assertEqual(d.value, 1) + + pin.write(1) + + self.assertFalse(d.is_active) + self.assertEqual(d.value, 0) + + pin.write(0) + + self.assertTrue(d.is_active) + self.assertEqual(d.value, 1) + + d.close() + + def test_digital_input_device_activated_deactivated(self): + d = DigitalInputDevice(1) + + pin = MockPin(irq_handler=d._pin_change) + d._pin = pin + + event_activated = MockEvent() + event_deactivated = MockEvent() + + d.when_activated = event_activated.set + d.when_deactivated = event_deactivated.set + + self.assertFalse(event_activated.is_set()) + pin.write(1) + self.assertTrue(event_activated.is_set()) + + self.assertFalse(event_deactivated.is_set()) + pin.write(0) + self.assertTrue(event_deactivated.is_set()) + + d.close() + + def test_adc_input_device_default_values(self): + d = AnalogInputDevice(1) + + adc = MockADC() + d._adc = adc + + self.assertTrue(d.active_state) + self.assertFalse(d.is_active) + self.assertEqual(d.value, 0) + + adc.write(65535) + self.assertTrue(d.is_active) + self.assertEqual(d.value, 1) + self.assertEqual(d.voltage, 3.3) + + adc.write(0) + self.assertFalse(d.is_active) + self.assertEqual(d.value, 0) + self.assertEqual(d.voltage, 0) + + # mid point + adc.write(32767) + self.assertAlmostEqual(d.value, 0.5, places=2) + self.assertAlmostEqual(d.voltage, 1.65, places=2) + + d.close() + + def test_adc_input_device_alt_values(self): + d = AnalogInputDevice(1, active_state=False, threshold=0.1) + + adc = MockADC() + d._adc = adc + + self.assertFalse(d.active_state) + self.assertTrue(d.is_active) + self.assertEqual(d.value, 1) + + adc.write(65535) + self.assertFalse(d.is_active) + self.assertEqual(d.value, 0) + self.assertEqual(d.voltage, 0) + + adc.write(0) + self.assertTrue(d.is_active) + self.assertEqual(d.value, 1) + self.assertEqual(d.voltage, 3.3) + + d.close() + + def test_adc_input_device_threshold(self): + d = AnalogInputDevice(1) + + adc = MockADC() + d._adc = adc + + self.assertFalse(d.is_active) + + # mid point + adc.write(32767) + self.assertFalse(d.is_active) + + # above threshold + adc.write(32768) + self.assertTrue(d.is_active) + + # below threshold + adc.write(32766) + self.assertFalse(d.is_active) + + d.threshold = 0.1 + + self.assertTrue(d.is_active) + + adc.write(6553) + self.assertFalse(d.is_active) + + d.close() + + +unittest.main() From de4ee007d3b6d0713f3568981a39d6dc50b6ef54 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Tue, 7 Jun 2022 08:57:35 +0100 Subject: [PATCH 07/10] added colour alias --- picozero/picozero.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/picozero/picozero.py b/picozero/picozero.py index 016946e..8eb75b9 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -1,3 +1,4 @@ +from ctypes.wintypes import RGB from machine import Pin, PWM, Timer, ADC from micropython import schedule from time import ticks_ms, sleep @@ -1080,6 +1081,8 @@ 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) +RGBLED.colour = RGBLED.color + ############################################################################### # INPUT DEVICES ############################################################################### From e35bbdabd81681cd0dc154e09efb0b70464905fb Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Wed, 8 Jun 2022 17:23:41 +0100 Subject: [PATCH 08/10] fix random import! --- picozero/picozero.py | 1 - 1 file changed, 1 deletion(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index 8eb75b9..a71946a 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -1,4 +1,3 @@ -from ctypes.wintypes import RGB from machine import Pin, PWM, Timer, ADC from micropython import schedule from time import ticks_ms, sleep From 7ecaafe59cb402a686052948ba9bf11b252b94cd Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Wed, 8 Jun 2022 17:40:50 +0100 Subject: [PATCH 09/10] PWM channel error msg --- picozero/picozero.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/picozero/picozero.py b/picozero/picozero.py index a71946a..11c1bef 100644 --- a/picozero/picozero.py +++ b/picozero/picozero.py @@ -364,10 +364,13 @@ 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]}. Use a different pin" + "PWM channel {} is already in use by {}. Use a different pin".format( + channel, + str(PWMOutputDevice._channels_used[channel]) + ) ) else: - PWMOutputDevice._channels_used[channel] = pin_num + PWMOutputDevice._channels_used[channel] = self def _state_to_value(self, state): return (state if self.active_high else self._duty_factor - state) / self._duty_factor From 0acaa00ef56ab67e807b7b458059e2d77aff8801 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Wed, 8 Jun 2022 17:48:56 +0100 Subject: [PATCH 10/10] v0.1.1 --- docs/changelog.rst | 11 ++++++++++- docs/conf.py | 2 +- picozero/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 85e2ab3..bc78257 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,11 +3,20 @@ Change log .. currentmodule:: picozero +0.1.1 - 2022-06-08 +~~~~~~~~~~~~~~~~~~ + ++ Minor bug fixes found during testing ++ Small improvements to exception messages ++ Added close methods to Speaker and PWMOutputDevice ++ Added unit tests ++ Added RGBLED.colour as an alias to RGBLED.color + 0.1.0 - 2022-04-08 ~~~~~~~~~~~~~~~~~~ + Beta release -+ Documentation updates ++ Documentation update + Minor bug fixes and refactoring 0.0.2 - 2022-03-31 diff --git a/docs/conf.py b/docs/conf.py index 255f14e..86469b3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,7 +46,7 @@ def __getattr__(cls, name): author = 'Raspberry Pi Foundation' # The full version, including alpha/beta/rc tags -release = '0.1.0' +release = '0.1.1' # -- General configuration --------------------------------------------------- diff --git a/picozero/__init__.py b/picozero/__init__.py index 72c4bae..6f0fa0f 100644 --- a/picozero/__init__.py +++ b/picozero/__init__.py @@ -1,6 +1,6 @@ __name__ = "picozero" __package__ = "picozero" -__version__ = '0.1.0' +__version__ = '0.1.1' __author__ = "Raspberry Pi Foundation" from .picozero import ( diff --git a/setup.py b/setup.py index af1c3d4..a59b48c 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ __project__ = 'picozero' __packages__ = ['picozero'] __desc__ = 'A beginner-friendly library for using common electronics components with the Raspberry Pi Pico. ' -__version__ = '0.1.0' +__version__ = '0.1.1' __author__ = "Raspberry Pi Foundation" __author_email__ = 'learning@raspberrypi.org' __license__ = 'MIT'