diff --git a/docs/api.rst b/docs/api.rst
index 9fe63d8..214a6dc 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -56,6 +56,21 @@ Speaker
:inherited-members:
:members:
+Motor
+-----
+
+.. autoclass:: Motor
+ :show-inheritance:
+ :inherited-members:
+ :members:
+
+Robot / Rover
+-------------
+
+.. autoclass:: Robot
+ :show-inheritance:
+ :inherited-members:
+ :members:
DigitalOutputDevice
-------------------
@@ -87,22 +102,30 @@ Switch
:inherited-members:
:members:
-Potentiometer
--------------
+Potentiometer / Pot
+-------------------
.. autoclass:: Potentiometer
:show-inheritance:
:inherited-members:
:members:
-TemperatureSensor
------------------
+TemperatureSensor / TempSensor / Thermistor
+-------------------------------------------
.. autoclass:: TemperatureSensor
:show-inheritance:
:inherited-members:
:members:
+DistanceSensor
+--------------
+
+.. autoclass:: DistanceSensor
+ :show-inheritance:
+ :inherited-members:
+ :members:
+
DigitalInputDevice
------------------
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 9545d05..57416ff 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -6,7 +6,7 @@ Change log
0.2.0 - 2022-06-29
~~~~~~~~~~~~~~~~~~
-+ Compatibility fix LED
++ Pico W compatibility fix for onboard LED
0.1.1 - 2022-06-08
~~~~~~~~~~~~~~~~~~
diff --git a/docs/conf.py b/docs/conf.py
index b2531ed..939fda6 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -38,6 +38,7 @@ def __getattr__(cls, name):
# add the ticks_ms function to time (as it is in micropython)
import time
setattr(time, 'ticks_ms', lambda x: None)
+setattr(time, 'ticks_us', lambda x: None)
# -- Project information -----------------------------------------------------
diff --git a/docs/developing.rst b/docs/developing.rst
index 9117c88..37b53d7 100644
--- a/docs/developing.rst
+++ b/docs/developing.rst
@@ -38,13 +38,13 @@ Install sphinx using ::
pip3 install sphinx
-To build the documentation, run the following command from the docs directory ::
+To test the documentation build, run the following command from the docs directory ::
$ make html
The website will be built in the directory docs/_build/html.
-Documentation can be viewed at `picozero.readthedocs.io`_.
+Documentation can be viewed at `picozero.readthedocs.io`_ and is automatically built and deployed on commit.
.. _picozero.readthedocs.io: https://picozero.readthedocs.io
@@ -59,4 +59,16 @@ The tests are design to be run on a Raspberry Pi Pico.
3. Copy the ``test_picozero.py`` to the pico.
-4. Run the ``test_picozero.py`` file.
\ No newline at end of file
+4. Run the ``test_picozero.py`` file.
+
+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/docs/examples/motor_move.py b/docs/examples/motor_move.py
new file mode 100644
index 0000000..1a97ac0
--- /dev/null
+++ b/docs/examples/motor_move.py
@@ -0,0 +1,8 @@
+from picozero import Motor
+from time import sleep
+
+motor = Motor(14, 15)
+
+motor.move()
+sleep(1)
+motor.stop()
\ No newline at end of file
diff --git a/docs/examples/robot_rover_forward.py b/docs/examples/robot_rover_forward.py
new file mode 100644
index 0000000..8944ecc
--- /dev/null
+++ b/docs/examples/robot_rover_forward.py
@@ -0,0 +1,9 @@
+from picozero import Robot
+from time import sleep
+
+robot_rover = Robot(left=(14,15), right=(12,13))
+
+# move forward
+robot_rover.forward()
+sleep(1)
+robot_rover.stop()
diff --git a/docs/examples/robot_rover_square.py b/docs/examples/robot_rover_square.py
new file mode 100644
index 0000000..0b16ba7
--- /dev/null
+++ b/docs/examples/robot_rover_square.py
@@ -0,0 +1,10 @@
+from picozero import Robot
+from time import sleep
+
+robot_rover = Robot(left=(14,15), right=(12,13))
+
+for i in range(4):
+ # move forward for 1 second
+ robot_rover.forward(t=1, wait=True)
+ # rotate to the left for 1 second
+ robot_rover.left(t=1, wait=True)
diff --git a/docs/examples/ultrasonic_distance_sensor.py b/docs/examples/ultrasonic_distance_sensor.py
new file mode 100644
index 0000000..b2190df
--- /dev/null
+++ b/docs/examples/ultrasonic_distance_sensor.py
@@ -0,0 +1,8 @@
+from picozero import DistanceSensor
+from time import sleep
+
+ds = DistanceSensor(echo=4, trigger=5)
+
+while True:
+ print(ds.distance)
+ sleep(0.1)
diff --git a/docs/images/distance_sensor_bb.svg b/docs/images/distance_sensor_bb.svg
new file mode 100644
index 0000000..9aab5aa
--- /dev/null
+++ b/docs/images/distance_sensor_bb.svg
@@ -0,0 +1,817 @@
+
+
+
\ No newline at end of file
diff --git a/docs/images/robot_bb.svg b/docs/images/robot_bb.svg
new file mode 100644
index 0000000..b41b7e4
--- /dev/null
+++ b/docs/images/robot_bb.svg
@@ -0,0 +1,8150 @@
+
+
+
\ No newline at end of file
diff --git a/docs/recipes.rst b/docs/recipes.rst
index 5551529..4ceab28 100644
--- a/docs/recipes.rst
+++ b/docs/recipes.rst
@@ -154,7 +154,6 @@ Print the value, voltage and percent reported by a potentiometer:
In the Thonny Python editor, choose View->Plotter to plot the output of :meth:`print`.
-
Buzzer
------
@@ -169,6 +168,29 @@ Control a passive buzzer or speaker that can play different tones or frequencies
.. literalinclude:: examples/speaker.py
+Motor
+-----
+
+Move a motor which is connected via 2 pins (forward / backward) and a motor controller board
+
+.. literalinclude:: examples/motor_move.py
+
+Robot rover
+-------------
+
+Make a simple 2 wheeled robot rover.
+
+.. image:: images/robot_bb.svg
+ :alt: A diagram of the Raspberry Pi Pico connected to 2 motors via a motor controller board powered by a battery pack
+
+Move the rover forward for 1 second and stop.
+
+.. literalinclude:: examples/robot_rover_forward.py
+
+Move the rover *roughly* in a square:
+
+.. literalinclude:: examples/robot_rover_square.py
+
Play a tune
~~~~~~~~~~~
@@ -188,4 +210,14 @@ Internal Temperature Sensor
Check the internal temperature of the Raspberry Pi Pico in degrees Celcius:
-.. literalinclude:: examples/pico_temperature.py
\ No newline at end of file
+.. literalinclude:: examples/pico_temperature.py
+
+Ultrasonic Distance Sensor
+--------------------------
+
+Get the distance in metres from an ultrasonic distance sensor (HC-SR04).
+
+.. image:: images/distance_sensor_bb.svg
+ :alt: A diagram of the Raspberry Pi Pico connected to HC-SR04 distance sensor
+
+.. literalinclude:: examples/ultrasonic_distance_sensor.py
\ No newline at end of file
diff --git a/docs/sketches/distance_sensor.fzz b/docs/sketches/distance_sensor.fzz
new file mode 100644
index 0000000..6e26c36
Binary files /dev/null and b/docs/sketches/distance_sensor.fzz differ
diff --git a/docs/images/pico_led_14.fzz b/docs/sketches/pico_led_14.fzz
similarity index 100%
rename from docs/images/pico_led_14.fzz
rename to docs/sketches/pico_led_14.fzz
diff --git a/docs/sketches/robot.fzz b/docs/sketches/robot.fzz
new file mode 100644
index 0000000..7546c4d
Binary files /dev/null and b/docs/sketches/robot.fzz differ
diff --git a/examples/motor.py b/examples/motor.py
new file mode 100644
index 0000000..2116b54
--- /dev/null
+++ b/examples/motor.py
@@ -0,0 +1,14 @@
+from picozero import Motor
+from time import sleep
+
+m = Motor(12, 13)
+
+# turn the motor for 1 second
+print("the motor is turning")
+m.move()
+sleep(1)
+m.stop()
+
+# or alternatively turn it at half speed for 2 seconds
+m.move(speed=0.5, t=2)
+print("the motor will turn at half speed and stop after 2 seconds")
\ No newline at end of file
diff --git a/examples/ultrasonic_distance_sensor.py b/examples/ultrasonic_distance_sensor.py
new file mode 100644
index 0000000..54c3e7c
--- /dev/null
+++ b/examples/ultrasonic_distance_sensor.py
@@ -0,0 +1,8 @@
+from picozero import DistanceSensor
+from time import sleep
+
+ds = DistanceSensor(5, 4)
+
+while True:
+ print(ds.distance)
+ sleep(0.1)
\ No newline at end of file
diff --git a/picozero/__init__.py b/picozero/__init__.py
index 4847bf0..52e38f5 100644
--- a/picozero/__init__.py
+++ b/picozero/__init__.py
@@ -19,6 +19,8 @@
Speaker,
RGBLED,
+ Motor,
+ Robot,
DigitalInputDevice,
Switch,
@@ -32,4 +34,6 @@
pico_temp_sensor,
TempSensor,
Thermistor,
+
+ DistanceSensor,
)
diff --git a/picozero/picozero.py b/picozero/picozero.py
index 608f573..3e58d24 100644
--- a/picozero/picozero.py
+++ b/picozero/picozero.py
@@ -1,6 +1,6 @@
from machine import Pin, PWM, Timer, ADC
from micropython import schedule
-from time import ticks_ms, sleep
+from time import ticks_ms, ticks_us, sleep
###############################################################################
# EXCEPTIONS
@@ -41,7 +41,7 @@ def pins(self):
"""
Returns a tuple of pins used by the device
"""
- self._pin_nums
+ return self._pin_nums
def __str__(self):
return "{} (pins - {})".format(self.__class__.__name__, self._pin_nums)
@@ -170,11 +170,27 @@ def value(self, value):
self._stop_change()
self._write(value)
- def on(self):
+ def on(self, value=1, t=None, wait=False):
"""
Turns the device on.
+
+ :param float value:
+ The value to set when turning on. Defaults to 1.
+
+ :param float t:
+ The time in seconds the device should be on. If None is
+ specified, the device will stay on. The default is None.
+
+ :param bool wait:
+ If True the method will block until the time `t` has expired.
+ If False the method will return and the device will turn on in
+ the background. Defaults to False. Only effective if `t` is not
+ None.
"""
- self.value = 1
+ if t is None:
+ self.value = value
+ else:
+ self._start_change(lambda : iter([(value, t), ]), 1, wait)
def off(self):
"""
@@ -222,7 +238,10 @@ def blink(self, on_time=1, off_time=None, n=None, wait=False):
off_time = on_time if off_time is None else off_time
self.off()
- self._start_change(lambda : iter([(1,on_time), (0,off_time)]), n, wait)
+
+ # is there anything to change?
+ if on_time > 0 or off_time > 0:
+ self._start_change(lambda : iter([(1,on_time), (0,off_time)]), n, wait)
def _start_change(self, generator, n, wait):
self._value_changer = ValueChange(self, generator, n, wait)
@@ -449,7 +468,7 @@ def blink_generator():
for i in range(int(fps * fade_in_time))
]:
yield s
-
+
if on_time > 0:
yield (1, on_time)
@@ -459,11 +478,13 @@ def blink_generator():
for i in range(int(fps * fade_out_time))
]:
yield s
-
- if off_time > 0:
- yield (0, off_time)
- self._start_change(blink_generator, n, wait)
+ if off_time > 0:
+ yield (0, off_time)
+
+ # is there anything to change?
+ if on_time > 0 or off_time > 0 or fade_in_time > 0 or fade_out_time > 0:
+ self._start_change(blink_generator, n, wait)
def pulse(self, fade_in_time=1, fade_out_time=None, n=None, wait=False, fps=25):
"""
@@ -529,10 +550,10 @@ class PWMLED(PWMOutputDevice):
"""
PWMLED.brightness = PWMLED.value
-def LED(pin, use_pwm=True, active_high=True, initial_value=False):
+def LED(pin, pwm=True, active_high=True, initial_value=False):
"""
Returns an instance of :class:`DigitalLED` or :class:`PWMLED` depending on
- the value of `use_pwm` parameter.
+ the value of `pwm` parameter.
::
@@ -540,14 +561,14 @@ def LED(pin, use_pwm=True, active_high=True, initial_value=False):
my_pwm_led = LED(1)
- my_digital_led = LED(2, use_pwm=False)
+ my_digital_led = LED(2, pwm=False)
:param int pin:
The pin that the device is connected to.
:param int pin:
- If `use_pwm` is :data:`True` (the default), a :class:`PWMLED` will be
- returned. If `use_pwm` is :data:`False`, a :class:`DigitalLED` will be
+ If `pwm` is :data:`True` (the default), a :class:`PWMLED` will be
+ returned. If `pwm` is :data:`False`, a :class:`DigitalLED` will be
returned. A :class:`PWMLED` can control the brightness of the LED but
uses 1 PWM channel.
@@ -560,7 +581,7 @@ def LED(pin, use_pwm=True, active_high=True, initial_value=False):
If :data:`False` (the default), the device will be off initially. If
:data:`True`, the device will be switched on initially.
"""
- if use_pwm:
+ if pwm:
return PWMLED(
pin=pin,
active_high=active_high,
@@ -572,10 +593,10 @@ def LED(pin, use_pwm=True, active_high=True, initial_value=False):
initial_value=initial_value)
try:
- pico_led = LED("LED", use_pwm=False)
+ pico_led = LED("LED", pwm=False)
except TypeError:
# older version of micropython before "LED" was supported
- pico_led = LED(25, use_pwm=False)
+ pico_led = LED(25, pwm=False)
class PWMBuzzer(PWMOutputDevice):
"""
@@ -867,19 +888,11 @@ def __init__(self, red=None, green=None, blue=None, active_high=True,
for pin in (red, green, blue))
super().__init__(active_high, initial_value)
- def __del__(self):
- if getattr(self, '_leds', None):
- self._stop_blink()
- for led in self._leds:
- led.__del__()
- self._leds = ()
- super().__del__()
-
def _write(self, value):
if type(value) is not tuple:
value = (value, ) * 3
for led, v in zip(self._leds, value):
- led.brightness = v
+ led.value = v
@property
def value(self):
@@ -890,7 +903,7 @@ def value(self):
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)
+ return tuple(led.value for led in self._leds)
@value.setter
def value(self, value):
@@ -1088,8 +1101,306 @@ 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)
+ def close(self):
+ super().close()
+ for led in self._leds:
+ led.close()
+ self._leds = None
+
RGBLED.colour = RGBLED.color
+class Motor(PinsMixin):
+ """
+ Represents a motor connected to a motor controller which has a 2 pin
+ input. One pin drives the motor "forward", the other drives the motor
+ "backward".
+
+ :type forward: int
+ :param forward:
+ The GP pin that controls the "forward" motion of the motor.
+
+ :type backward: int
+ :param backward:
+ The GP pin that controls the "backward" motion of the motor.
+
+ :param bool pwm:
+ If :data:`True` (the default), PWM pins are used to drive the motor.
+ When using PWM pins values between 0 and 1 can be used to set the
+ speed.
+
+ """
+ def __init__(self, forward, backward, pwm=True):
+ self._pin_nums = (forward, backward)
+ self._forward = PWMOutputDevice(forward) if pwm else DigitalOutputDevice(forward)
+ self._backward = PWMOutputDevice(backward) if pwm else DigitalOutputDevice(backward)
+
+ def on(self, speed=1, t=None, wait=False):
+ """
+ Turns the motor on and makes it turn.
+
+ :param float speed:
+ The speed as a value between -1 and 1. 1 turns the motor at
+ full speed direction, -1 turns the motor at full speed in
+ the opposite direction. Defaults to 1.
+
+ :param float t:
+ The time in seconds the motor should run for. If None is
+ specified, the motor will stay on. The default is None.
+
+ :param bool wait:
+ If True the method will block until the time `t` has expired.
+ If False the method will return and the motor will turn on in
+ the background. Defaults to False. Only effective if `t` is not
+ None.
+ """
+ if speed > 0:
+ self._backward.off()
+ self._forward.on(speed, t, wait)
+
+ elif speed < 0:
+ self._forward.off()
+ self._backward.on(-speed, t, wait)
+
+ else:
+ self.off()
+
+ def off(self):
+ """
+ Stops the motor turning.
+ """
+ self._backward.off()
+ self._forward.off()
+
+ @property
+ def value(self):
+ """
+ Sets or returns the motor speed as a value between -1 and 1. -1 is full
+ speed "backward", 1 is full speed "forward", 0 is stopped.
+ """
+ return self._forward.value + (-self._backward.value)
+
+ @value.setter
+ def value(self, value):
+ if value != 0:
+ self.on(value)
+ else:
+ self.stop()
+
+ def forward(self, speed=1, t=None, wait=False):
+ """
+ Makes the motor turn "forward".
+
+ :param float speed:
+ The speed as a value between 0 and 1. 1 is full speed. Defaults to 1.
+
+ :param float t:
+ The time in seconds the motor should turn for. If None is
+ specified, the motor will stay on. The default is None.
+
+ :param bool wait:
+ If True the method will block until the time `t` has expired.
+ If False the method will return and the motor will turn on in
+ the background. Defaults to False. Only effective if `t` is not
+ None.
+ """
+ self.on(speed, t, wait)
+
+ def backward(self, speed=1, t=None, wait=False):
+ """
+ Makes the motor turn "backward".
+
+ :param float speed:
+ The speed as a value between 0 and 1. 1 is full speed. Defaults to 1.
+
+ :param float t:
+ The time in seconds the motor should turn for. If None is
+ specified, the motor will stay on. The default is None.
+
+ :param bool wait:
+ If True the method will block until the time `t` has expired.
+ If False the method will return and the motor will turn on in
+ the background. Defaults to False. Only effective if `t` is not
+ None.
+ """
+ self.on(-speed, t, wait)
+
+ def close(self):
+ """
+ Closes the device and releases any resources. Once closed, the device
+ can no longer be used.
+ """
+ self._forward.close()
+ self._backward.close()
+
+Motor.start = Motor.on
+Motor.stop = Motor.off
+
+class Robot:
+ """
+ Represent a generic dual-motor robot / rover / buggy.
+
+ Alias for :class:`Rover`
+
+ This class is constructed with two tuples representing the forward and
+ backward pins of the left and right controllers respectively. For example,
+ if the left motor's controller is connected to pins 12 and 13, while the
+ right motor's controller is connected to pins 14 and 15 then the following
+ example will drive the robot forward::
+
+ from picozero import Robot
+
+ robot = Robot(left=(12, 13), right=(14, 15))
+ robot.forward()
+
+ :param tuple left:
+ A tuple of two pins representing the forward and backward inputs of the
+ left motor's controller.
+
+ :param tuple right:
+ A tuple of two pins representing the forward and backward inputs of the
+ left motor's controller.
+
+ :param bool pwm:
+ If :data:`True` (the default), pwm pins will be used, allowing ariable
+ speed control.
+
+ """
+ def __init__(self, left, right, pwm=True):
+ self._left = Motor(left[0], left[1], pwm)
+ self._right = Motor(right[0], right[1], pwm)
+
+ @property
+ def left_motor(self):
+ """
+ Returns the left :class:`Motor`.
+ """
+ return self._left
+
+ @property
+ def right_motor(self):
+ """
+ Returns the right :class:`Motor`.
+ """
+ return self._right
+
+ @property
+ def value(self):
+ """
+ Represents the motion of the robot as a tuple of (left_motor_speed,
+ right_motor_speed) with ``(-1, -1)`` representing full speed backwards,
+ ``(1, 1)`` representing full speed forwards, and ``(0, 0)``
+ representing stopped.
+ """
+ return (self._left.value, self._right.value)
+
+ @value.setter
+ def value(self, value):
+ self._left.value, self._right.value = value
+
+ def forward(self, speed=1, t=None, wait=False):
+ """
+ Makes the robot move "forward".
+
+ :param float speed:
+ The speed as a value between 0 and 1. 1 is full speed. Defaults to 1.
+
+ :param float t:
+ The time in seconds the robot should move for. If None is
+ specified, the robot will continue to move until stopped. The default
+ is None.
+
+ :param bool wait:
+ If True the method will block until the time `t` has expired.
+ If False the method will return and the motor will turn on in
+ the background. Defaults to False. Only effective if `t` is not
+ None.
+ """
+ self._left.forward(speed, t, False)
+ self._right.forward(speed, t, wait)
+
+ def backward(self, speed=1, t=None, wait=False):
+ """
+ Makes the robot move "backward".
+
+ :param float speed:
+ The speed as a value between 0 and 1. 1 is full speed. Defaults to 1.
+
+ :param float t:
+ The time in seconds the robot should move for. If None is
+ specified, the robot will continue to move until stopped. The default
+ is None.
+
+ :param bool wait:
+ If True the method will block until the time `t` has expired.
+ If False the method will return and the motor will turn on in
+ the background. Defaults to False. Only effective if `t` is not
+ None.
+ """
+ self._left.backward(speed, t, False)
+ self._right.backward(speed, t, wait)
+
+ def left(self, speed=1, t=None, wait=False):
+ """
+ Makes the robot turn "left" by turning the left motor backward and the
+ right motor forward.
+
+ :param float speed:
+ The speed as a value between 0 and 1. 1 is full speed. Defaults to 1.
+
+ :param float t:
+ The time in seconds the robot should turn for. If None is
+ specified, the robot will continue to turn until stopped. The default
+ is None.
+
+ :param bool wait:
+ If True the method will block until the time `t` has expired.
+ If False the method will return and the motor will turn on in
+ the background. Defaults to False. Only effective if `t` is not
+ None.
+ """
+ self._left.backward(speed, t, False)
+ self._right.forward(speed, t, wait)
+
+ def right(self, speed=1, t=None, wait=False):
+ """
+ Makes the robot turn "right" by turning the left motor forward and the
+ right motor backward.
+
+ :param float speed:
+ The speed as a value between 0 and 1. 1 is full speed. Defaults to 1.
+
+ :param float t:
+ The time in seconds the robot should turn for. If None is
+ specified, the robot will continue to turn until stopped. The default
+ is None.
+
+ :param bool wait:
+ If True the method will block until the time `t` has expired.
+ If False the method will return and the motor will turn on in
+ the background. Defaults to False. Only effective if `t` is not
+ None.
+ """
+ self._left.forward(speed, t, False)
+ self._right.backward(speed, t, wait)
+
+ def stop(self):
+ """
+ Stops the robot.
+ """
+ self._left.stop()
+ self._right.stop()
+
+ def close(self):
+ """
+ Closes the device and releases any resources. Once closed, the device
+ can no longer be used.
+ """
+ self._left.close()
+ self._right.close()
+
+Rover = Robot
+
+
###############################################################################
# INPUT DEVICES
###############################################################################
@@ -1387,6 +1698,8 @@ class Potentiometer(AnalogInputDevice):
Represents a Potentiometer which outputs with a variable voltage
between 0 and 3.3V.
+ Alias for :class:`Pot`.
+
:param int pin:
The pin that the device is connected to.
@@ -1416,6 +1729,8 @@ class TemperatureSensor(AnalogInputDevice):
can be converted to a temperature using a `conversion` function passed as a
parameter.
+ Alias for :class:`Thermistor` and :class:`TempSensor`.
+
:param int pin:
The pin that the device is connected to.
@@ -1474,4 +1789,82 @@ def conversion(self, value):
TempSensor = TemperatureSensor
Thermistor = TemperatureSensor
+class DistanceSensor(PinsMixin):
+ """
+ Represents a HC-SR04 ultrasonic distance sensor.
+
+ :param int echo:
+ The pin which the ECHO pin is connected to.
+
+ :param int trigger:
+ The pin which the TRIG pin is connected to.
+
+ :param float max_distance:
+ The :attr:`value` attribute reports a normalized value between 0 (too
+ close to measure) and 1 (maximum distance). This parameter specifies
+ the maximum distance expected in meters. This defaults to 1.
+ """
+ def __init__(self, echo, trigger, max_distance=1):
+ self._pin_nums = (echo, trigger)
+ self._max_distance = max_distance
+ self._echo = Pin(echo, mode=Pin.IN, pull=Pin.PULL_DOWN)
+ self._trigger = Pin(trigger, mode=Pin.OUT, value=0)
+
+ def _read(self):
+ echo_on = None
+ echo_off = None
+ timed_out = False
+
+ self._trigger.off()
+ sleep(0.000005)
+ self._trigger.on()
+ sleep(0.00001)
+ self._trigger.off()
+
+ # if an echo isnt measured in 100 milliseconds, it should
+ # be considered out of range the maximum length of the
+ # echo is 38 milliseconds but its not known how long the
+ # transmission takes after the trigger
+ stop = ticks_ms() + 100
+ while echo_off is None and not timed_out:
+ if self._echo.value() == 1 and echo_on is None:
+ echo_on = ticks_us()
+ if echo_on is not None and self._echo.value() == 0:
+ echo_off = ticks_us()
+ if ticks_ms() > stop:
+ timed_out = True
+
+ if echo_off is None or timed_out:
+ return None
+ else:
+ distance = ((echo_off - echo_on) * 0.000343) / 2
+ distance = min(distance, self._max_distance)
+ return distance
+
+ @property
+ def value(self):
+ """
+ Returns a value between 0, indicating the reflector is either touching
+ the sensor or is sufficiently near that the sensor can’t tell the
+ difference, and 1, indicating the reflector is at or beyond the
+ specified max_distance. A return value of None indicates that the
+ echo was not received before the time out.
+ """
+ distance = self.distance
+ return distance / self._max_distance if distance is not None else None
+
+ @property
+ def distance(self):
+ """
+ Returns the current distance measured by the sensor in meters. Note
+ that this property will have a value between 0 and max_distance.
+ """
+ return self._read()
+
+ @property
+ def max_distance(self):
+ """
+ Returns the maximum distance that the sensor will measure in metres.
+ """
+ return self._max_distance
diff --git a/tests/test_picozero.py b/tests/test_picozero.py
index 8da37dd..9b216f1 100644
--- a/tests/test_picozero.py
+++ b/tests/test_picozero.py
@@ -63,6 +63,10 @@ def reset(self):
class Testpicozero(unittest.TestCase):
+ ###########################################################################
+ # OUTPUT DEVICES
+ ###########################################################################
+
def test_digital_output_device_default_values(self):
d = DigitalOutputDevice(1)
@@ -122,7 +126,7 @@ 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)
@@ -221,16 +225,148 @@ def test_pwm_output_device_pulse(self):
self.fail(f"{len(values)} values were generated, {len(expected)} were expected.")
d.close()
+
+ def test_motor_default_values(self):
+ d = Motor(1,2)
+
+ self.assertEqual(d.value, 0)
+
+ d.on()
+ self.assertEqual(d.value, 1)
+
+ d.stop()
+ self.assertEqual(d.value, 0)
+
+ d.forward()
+ self.assertEqual(d.value, 1)
+
+ d.backward()
+ self.assertEqual(d.value, -1)
+
+ d.value = 0.5
+ self.assertAlmostEqual(d.value, 0.5, places=2)
+
+ d.value = -0.5
+ self.assertAlmostEqual(d.value, -0.5, places=2)
+
+ d.forward(1, t=0.5)
+ values = log_device_values(d, 0.6)
+ self.assertEqual(values, [1,0])
+ self.assertEqual(d.value, 0)
+
+ d.backward(1, t=0.5)
+ values = log_device_values(d, 0.6)
+ self.assertEqual(values, [-1,0])
+ self.assertEqual(d.value, 0)
+
+ d.close()
+
+ def test_motor_alt_values(self):
+ d = Motor(1,2,pwm=False)
+
+ d.value = 0.5
+ self.assertEqual(d.value, 1)
+
+ d.value = -0.5
+ self.assertEqual(d.value, -1)
+
+ d.value = 0
+ self.assertEqual(d.value, 0)
+
+ d.close()
+
+ def test_robot(self):
+ d = Robot(left=(1,2), right=(3,4))
+
+ d.forward()
+ self.assertEqual(d.value, (1,1))
+
+ d.left()
+ self.assertEqual(d.value, (-1,1))
+
+ d.right()
+ self.assertEqual(d.value, (1,-1))
+
+ d.value = (0.5, -0.5)
+ self.assertAlmostEqual(d.left_motor.value, 0.5, places=2)
+ self.assertAlmostEqual(d.right_motor.value, -0.5, places=2)
+
+ d.stop()
+ self.assertEqual(d.value, (0,0))
+
+ d.close()
def test_LED_factory(self):
d = LED(1)
self.assertIsInstance(d, PWMLED)
d.close()
- d = LED(1, use_pwm=False)
+ d = LED(1, pwm=False)
self.assertIsInstance(d, DigitalLED)
d.close()
+ def test_pico_led(self):
+
+ self.assertIsInstance(pico_led, DigitalLED)
+
+ self.assertEqual(pico_led.value, 0)
+
+ pico_led.on()
+ self.assertEqual(pico_led.value, 1)
+
+ pico_led.off()
+ self.assertEqual(pico_led.value, 0)
+
+ ###########################################################################
+ # INPUT DEVICES
+ ###########################################################################
+
+ def test_rgb_led_default_values(self):
+ d = RGBLED(1,2,3)
+
+ self.assertEqual(d.value, (0,0,0))
+
+ d.on()
+ self.assertEqual(d.value, (1,1,1))
+
+ d.off()
+ self.assertEqual(d.value, (0,0,0))
+
+ d.value = (0.25, 0.5, 0.75)
+ self.assertAlmostEqual(d.value[0], 0.25, places=2)
+ self.assertAlmostEqual(d.value[1], 0.5, places=2)
+ self.assertAlmostEqual(d.value[2], 0.75, places=2)
+
+ d.red = 200
+ self.assertAlmostEqual(d.value[0], 0.78, places=2)
+
+ d.green = 100
+ self.assertAlmostEqual(d.value[1], 0.39, places=2)
+
+ d.blue = 50
+ self.assertAlmostEqual(d.value[2], 0.20, places=2)
+
+ d.close()
+
+ def test_rgb_led_alt_values(self):
+ d = RGBLED(1,2,3, initial_value=(1,1,1), pwm=False)
+
+ self.assertEqual(d.value, (1,1,1))
+
+ d.on()
+ self.assertEqual(d.value, (1,1,1))
+
+ d.off()
+ self.assertEqual(d.value, (0,0,0))
+
+ d.value = (1, 1, 1)
+ self.assertEqual(d.value, (1,1,1))
+
+ d.value = (0, 0.5, 1)
+ self.assertEqual(d.value, (0,1,1))
+
+ d.close()
+
def test_digital_input_device_default_values(self):
d = DigitalInputDevice(1)
@@ -375,5 +511,29 @@ def test_adc_input_device_threshold(self):
d.close()
+ def test_temp_sensory(self):
+
+ def temp_conversion(voltage):
+ return voltage + 2
+
+ t = TemperatureSensor(4, conversion=temp_conversion)
+
+ adc = MockADC()
+ t._adc = adc
+
+ adc.write(65535)
+ self.assertEqual(t.temp, 3.3 + 2)
+
+ adc.write(0)
+ self.assertEqual(t.temp, 2)
+
+ t.close()
+
+ def test_pico_temp_sensor(self):
+
+ self.assertIsInstance(pico_temp_sensor, TemperatureSensor)
+ self.assertEqual(pico_temp_sensor.pin, 4)
+ self.assertIsNotNone(pico_temp_sensor.temp)
unittest.main()
+