Learn EV3 Python‎ > ‎Robot Educator‎ > ‎

Beyond Basics Ex 1-6

To see how each of these exercises can be solved in the standard Lego EV3 programming environment (EV3-G) click HERE.

Exercise 1: Multitasking (multithreading)

What some languages call 'multitasking' is called 'multithreading' in Python. The interest of the exercise is of course that it demonstrates multitasking i.e. the running of multiple branches or tasks or 'threads' simultaneously. A thread is a part of program code that can run independently and at the same time as other parts of the program. Unlike EV3-G, Python does not have a command that can be run in one thread to instantly stop another thread running. Multithreading in EV3 Python is more awkward than it is in EV3-G so you may want to seek alternative solutions that do not involve multithreading - this is often possible.

In the script below the play_sound() function definition contains a loop that stops running when the loop variable is set to False, which happens in the last line of the main code block.

You should know about the important concept of 'scope' already - this site won't teach it to you. If you know about scope you may wonder why it's not necessary to make loop 'global' in the play_sound() function definition. Without 'global loop', shouldn't the function create a local variable (with local scope) that is not the same as the loop variable in the main code block? And shouldn't that mean that when loop is set to False in the main code block it won't affect the independent loopvariable in the function definition? In fact the function uses the same loop variable that is in the main code block because the variable 'loop' is created in the main code block before the play_sound() function is called. Making variables global should generally be avoided if possible - check the special multithreading page for alternative methods.

Objective:
The robot will continuously play the Lego 'Motor idle' sound until the robot has moved straight forward for two wheel rotations at 50% of the rated maximum speed (speed=50). The Lego EV3 software includes a collection of sounds in Lego's own RSF sound file format. EV3 Python cannot play sounds in that format but it can play sounds in WAV format. You can find the same sounds in WAV format at the bottom of the 'Sound' page. All the sounds are in a single folder, unlike the sounds used by EV3-G. For compatibility with the scripts in this site you should place all the WAV sound files in a folder called 'sounds' inside the 'robot' folder on the EV3dev SD card. This is very easy if you are using VS Code with the EV3 extension - see the Sound page.

Solution:

#!/usr/bin/env python3
from ev3dev2.motor import MoveSteering, OUTPUT_B, OUTPUT_C
from ev3dev2.sound import Sound
from threading import Thread

steer_pair = MoveSteering(OUTPUT_B, OUTPUT_C)
sound = Sound()

def play_sound():
    while loop==True: # repeat until 'loop' is set to False in the main thread.
        sound.play_file('/home/robot/sounds/Motor idle.wav')
    
loop = True
# Start the play_sound thread.
t = Thread(target=play_sound)
t.start()

steer_pair.on_for_rotations(steering=0, speed=50, rotations=2)

loop = False # set 'loop' to False so that the loop in the play_sound thread
# stops repeating

Notes:
  1. With only two rotations you don't really have time to see what's going on so I suggest you change to 20 rotations - then you will clearly hear that the sound stops repeating after the motor completes its motion.
  2. As the sound repeats there is a pause between each playing of the sound. This is annoying but probably inevitable.
  3. If we had used the command
    sound.play_file('/home/robot/sounds/Motor idle.wav', play_type=2)
    then play_type=2 would have made the sound loop without needing a while loop but we must use the while loop so that we can stop the loop by changing loop to False.

Exercise 2: Loop

Objective:
The program first makes the brick's status light glow red for two seconds without pulsing, then the robot beeps twice and moves forwards one wheel rotation, repeatedly beeping and moving until the touch sensor is pressed. This script uses beeps rather than clicks because beeps (and tones) are built into the EV3 Python whereas clicks are not.

Solution:

This is perhaps the simplest solution, but it's not very faithful to the EV3-G solution and it requires that you stop the program with a LONG press on the touch sensor since it does not continually monitor the touch sensor for presses.

#!/usr/bin/env python3
from ev3dev2.motor import OUTPUT_B, OUTPUT_C, MoveSteering 
from ev3dev2.sensor.lego import TouchSensor
from ev3dev2.led import Leds
from ev3dev2.sound import Sound
from time import sleep

steer_pair = MoveSteering(OUTPUT_B, OUTPUT_C)
ts = TouchSensor()
leds = Leds()
sound = Sound()

leds.all_off() # Turn all LEDs off and also turns off the flashing.

# Set both pairs of LEDs to red for 2s then green
leds.set_color('LEFT', 'RED')
leds.set_color('RIGHT', 'RED')
sleep(2)
leds.set_color('LEFT', 'GREEN')
leds.set_color('RIGHT', 'GREEN')

while not ts.is_pressed: # Stop the program with a LONG press
    sound.beep()
    sound.beep()
    steer_pair.on_for_rotations(steering=0, speed=50, rotations=2)

The above script doesn't use multithreading like the EV3-G solution and therefore won't react instantly to the press of the touch sensor - you need to do a long press. See the multithreading page for a discussion of multithreading and then check out the script below. As you read on the multithreading page, a daemon thread will stop running as soon as the main thread stops. In the script below, the main thread waits for the touch sensor to be bumped and then terminates, causing the daemon thread to also terminate. Changes relative to the script above are in bold.

#!/usr/bin/env python3
from ev3dev2.motor import OUTPUT_B, OUTPUT_C, MoveSteering 
from ev3dev2.sensor.lego import TouchSensor
from ev3dev2.led import Leds
from ev3dev2.sound import Sound
from time import sleep
from threading import Thread

steer_pair = MoveSteering(OUTPUT_B, OUTPUT_C)
ts = TouchSensor()
leds = Leds()
sound = Sound()

leds.all_off() # Turn all LEDs off. This also turns off the flashing.

# Set both pairs of LEDs to red
leds.set_color('LEFT', 'RED')
leds.set_color('RIGHT', 'RED')
sleep(2)
leds.set_color('LEFT', 'GREEN')
leds.set_color('RIGHT', 'GREEN')

def daemon_thread():
    while True:
        sound.beep()
        sound.beep()
        steer_pair.on_for_rotations(steering=0, speed=50, rotations=2)

t = Thread(target=daemon_thread)
t.setDaemon(True) # make the thread called 't' into a daemon thread
t.start()

ts.wait_for_bump()

Exercise 3: IF structure (called 'Switch' in the Lego software)

Objective:
This will be a simple 'line follower' program using a single color sensor. The intention is that the robot will follow a black line that has been drawn on a white mat. This is a little difficult to do using only a single color sensor. Imagine that the robot is moving along the line with the sensor over the black line, then the sensor will detect very little light being reflected. If the robot wanders off the line then the sensor will detect a strong reflected light intensity, but how useful is that information? The robot won't know whether it has wandered off the left side of the line or the right, so it won't know how to turn back towards the line. So the trick is not to try to follow 'the line', it is to follow one edge of the black line, let's say the right edge. Now if the sensor detects that it is over white then we know the robot should turn left to find the edge again and if the sensor detects that it is over black then the robot should turn right to find the edge again. In other words...
  • If the color sensor detects weak reflected light (i.e. over black surface) then the robot should make a medium right turn (motor B speed = 50%, motor C speed = 0%). 
  • If the color sensor detects strong reflected light (i.e. over white surface) then the robot should make a medium left turn (motor B speed = 0%, motor C speed = 50%).
Solution:

#!/usr/bin/env python3
from ev3dev2.motor import MoveTank, OUTPUT_B, OUTPUT_C
from ev3dev2.sensor.lego import ColorSensor
from ev3dev2.button import Button
from time import sleep

btn = Button() # we will use any button to stop script
tank = MoveTank(OUTPUT_B, OUTPUT_C)

# Connect an EV3 color sensor to any sensor port.
cl = ColorSensor()

while not btn.any():    # exit loop when any button pressed
    # if we are over the black line (weak reflection)
    if cl.reflected_light_intensity<30:   
        # medium turn right
        tank.on(left_speed=50, right_speed=0)
    
    else:   # strong reflection (>=30) so over white surface
        # medium turn left
        tank.on(left_speed=0, right_speed=50)
    
    sleep(0.1) # wait for 0.1 seconds

Notes:
  1. This solution works very well because there are only two options and therefore a simple if structure is neatly appropriate.
  2. The above script includes the neat code while not btn.any(): which allows for the loop to be exited by pressing any button on the EV3.
  3. In this solution there is never more than one motor moving at any one time so it could just as well have been written with commands for a single motor rather than using tank commands.

Exercise 4: Multiple Switch

Note: this exercise assumes that the color sensor on port 3 is aligned horizontally or pointing upwards so you can present differently colored sides of the 'multi-color cuboid' to it.

Objective:
  • If the color sensor detects no color or a color other than yellow or blue then the robot should continuously move straight forward with a speed of 40%. 
  • If a yellow object is presented to the color sensor then the robot should continuously turn gently right (with the left motor at 40% speed and the right motor at 10% speed). 
  • If a blue object is presented to the color sensor then the robot should continuously turn gently left (with the left motor at 10% speed and the right motor at 40% speed).
Solution:

#!/usr/bin/env python3
from ev3dev2.motor import MoveTank, OUTPUT_B, OUTPUT_C
from ev3dev2.sensor.lego import ColorSensor
from ev3dev2.button import Button
from time import sleep

btn = Button() # will use any button to stop script
cl = ColorSensor()

tank_pair = MoveTank(OUTPUT_B, OUTPUT_C)

while not btn.any():    # exit loop when any button pressed
    color_str = cl.color_name # Returns an integer, e.g. 'Blue'
    if color_str == 'Yellow':
        tank_pair.on(left_speed=40, right_speed=10)
    elif color_str == 'Blue':
        tank_pair.on(left_speed=10, right_speed=40)
    else:
        # no color detected or a color other than yellow or blue
        tank_pair.on(left_speed=40, right_speed=40)
    sleep(0.01) # Give the CPU a rest

Notes:
  1. Since there are three options in this exercise a simple if...else... statement is insufficient. Instead we must use if...elif...else..., as above. elif is short for 'else if'.
  2. It would have been complicated to test for 'no color or any color other than yellow or blue' so I simply put it last and used 'else'. This correctly runs if the blue or yellow color has not been responded to in previous lines.
  3. Recall that to test for equality you must use the == operator. The = sign is used to assign values to variables.

Exercise 5: 'Data wires'

In the icon-based Lego EV3 programming environment it is often necessary to use 'data wires' to transfer data between programming blocks. Fortunately, this is not meaningful or necessary in a textual programming environment like EV3 Python.

Objective:
In this program the robot does not move and it is assumed that an object such as your hand will be brought towards the ultrasound sensor on the robot. A loop continuously measures the distance to an object (assumed to be initially more than 8 cm away), and displays the measured distance on the brick's screen. The loop exits when the program detects that the distance to the reflecting object has become less than 8 cm, and an image of two eyes called 'Up' is then displayed for two seconds.

Solution:

This program assumes that have you have downloaded all the standard Lego image files in BMP format to the brick in accordance with the instructions in the 'Display an image file' section on this page. This script uses a font (helvB24).

#!/usr/bin/env python3
from ev3dev2.sensor.lego import UltrasonicSensor
from ev3dev2.display import Display
from time import sleep
from PIL import Image, ImageDraw, ImageFont

lcd = Display()
us = UltrasonicSensor() 

while us.distance_centimeters > 8:
    my_text = str(us.distance_centimeters)+' cm'
    lcd.text_pixels(my_text, x=40, y=50, font='helvB24')
    lcd.update()
    sleep(0.1) # so the display doesn't change too frequently

pic = Image.open('/home/robot/pics/Up.bmp')
lcd.image.paste(pic, (0,0))
lcd.update()

sleep(5)  # need time to admire image

Exercise 6: Random

Objective:
The program will first generate a random integer between -70 and 70 inclusive. The robot should drive forward at this speed for one second, then pause for one second, then generate a new random number and repeat forever.

Solution:
In Python, random integers between a and b inclusive can be generated with randint(a, b). The randint() function is part of the random library so the random library must be imported before the function can be used. We will include code such that the script can be terminated with a long press on any EV3 button.

#!/usr/bin/env python3
from ev3dev2.motor import MoveSteering, OUTPUT_B, OUTPUT_C
from time import sleep
from ev3dev2.button import Button
import random

btn = Button() # press any button to stop the program

steer_pair = MoveSteering(OUTPUT_B, OUTPUT_C)

# exit loop with a long press on any EV3 button
while not btn.any():
    # generate a random integer between -70 and +70 inclusive
    rand=random.randint(-70,70)
    steer_pair.on_for_seconds(steering=0, speed=rand, seconds=1)
    sleep(1)

Notes:
Let's compare this solution with the official EV3-G solution to the same problem. Bear in mind that the official EV3-G solution has no comments.

You are now ready to tackle Beyond Basics exercises 7-11.
Comments