1 An introduction to programming robots

In the first week’s activities, you were introduced to the basic anatomy of a Python program. You also saw how simple Python programs could be downloaded to, and run in, the RoboLab simulator. The objective of this RoboLab session is to explore in a little more detail some of the programming constructs that will allow you to control the simulated robot within the RoboLab simulator.

It is assumed that you are completely new to computer programming and robotics. We’ll try to avoid jargon and go at a pace that makes it easy for you to understand what is going on. Feel free to experiment and use the notebooks to take notes. If you wonder what running a particular line of code might do, try it and see.

You are welcome to explore your own robot programs in this notebook BUT I recommend you work through the whole notebook before going off piste.

1.1 Getting started with the simulator

As you learned in last week’s notebooks, the simulator we will be using must be loaded into each notebook that wants to use it, once per notebook.

Run the following code cell to load the simulator in the normal way:

from nbev3devsim.load_nbev3devwidget import roboSim, eds

%load_ext nbev3devsim

At the moment, there is no simple way of getting back the ‘Robot Simulator’ dialogue if you accidentally delete it or if you see an Uninitialized Proxy Widget message when you try to display it.

Instead, you will have to go to the Kernel menu, select Restart and then rerun the notebook code cells.

1.1.1 Passing code into the simulator

You should recall from the first week’s RoboLab activities that the program code intended for the simulator should be written in a code cell prefixed with some simulator magic. Running the magicked code cell downloads the program to the simulator. The downloaded program code can then be run in the simulator to control the simulated robot.

Switches passed to the magic allow us to automate how the simulator is set up for any particular code experiment. The available switches can be previewed by running the following code cell:

%sim_magic --help

For example, running the following cell will set up the simulator to display just the Output panel (-O), hiding the Simulator world (-W) and Simulator controls (-H) panels, which would otherwise be displayed by default, and then autorunning the program on download (-R).

%%sim_magic -OHW -R
print('hello world')

1.1.2 Debugging error messages in the simulator

Further recapping the first week’s activities, you should also recall that if you make an error in a program sent to the simulator, then when you try to run the program in the simulator a message will be displayed in the simulator console.

Screenshot of the error message: TypeError: on() missing 1 required argument: speed on line 6.

The line number specified relates to the line number of the program actually run by the simulated robot.

You can see the full program listing as loaded into the simulated robot from the Code display panel in the simulator, which can be raised from the Simulator controls panel, the D simulator keyboard shortcut, or the -D magic switch:

%%sim_magic_preloaded -OHW --code -R
print("This works")
print This doesn't

1.2 An introduction to sequential programming

For beginners, computer programming can appear to be a very mysterious process. Programming a robot may seem even more daunting, but I hope to show that the basics are quite straightforward.

Throughout the practical activities associated with this block, you will have the opportunity to create and run your own code, as well as run code provided for you. In some cases, the provided code may look quite complex. You are not expected to be able to write programs as complex as some of the ones provided. However, we have included some code in the notebooks, rather than hiding it elsewhere and referring to it via single-line function calls, so that you can see how short programs or code blocks may be constructed in order to perform particular tasks.

There are various ways of programming computers to control robots. One of these is the sequential approach, which is the main approach used in RoboLab. This method of writing computer programs, as lists or sequences of commands, produces what are called sequential programs.

In the sequential program model, the computer executes commands in sequence.

The program is structured in a particular way that allows it to operate correctly (a necessary requirement) as well as making it ‘readable’ (a desirable requirement).

In terms of correctness, the program requires that we define things within our program before we try to call on them and make use of them within our program.

1.2.1 A simple sequential program

As you may have gleaned from last week’s overview activities, a computer program is a sequence of instructions or commands, written using words, symbols and numbers.

For example, you might want a robot to go forwards for five seconds. If we control the motors separately, this involves turning each motor on separately, waiting for 5 seconds, and then terminating the program, at which point the motors in our robot simulation are automatically switched off.

The ‘stop the motors at the end of the program’ behaviour is a specific feature of the simulator we are using. This behaviour is not guaranteed in other environments that can be used to program either simulated or real robots.

To be safe, it’s often worth making sure you turn off the motors at the end of a program so you know for sure what state they are in when the program terminates.

You may recall from the notebook 01.6 Working with simulators that different sorts of time are at work in the simulator: natural wall clock time, as well as the ‘clock time’ given by the number of animation frame / event loop steps iterated through by the simulator.

In this notebook, you will turn motors on and off for specified periods of time. Generally, this is not encouraged; you may even see some examples why!

In our simulated robot environment, to drive a robot forwards for 5 seconds, we need to:

  • turn each motor on with a particular speed, which also sets the direction: positive speed values are assumed to mean ‘go forwards’, negative ones ‘go backwards’

  • wait for 5 seconds

  • (program ends, motors turned off automatically).

This could be coded in RoboLab using Python instructions of the form:

left_motor.on(SpeedPercent(50))
right_motor.on(SpeedPercent(50))

import time
time.sleep(5)

for suitable configurations of left_motor and right_motor.

Perhaps confusingly, the time.sleep() command, rather than saying ‘do nothing for 5 seconds’, means ‘continue to do what you’re already doing for 5 seconds’. For our current example, this means ‘keep your motors on and running for 5 seconds’.

The time.sleep() command is known as a blocking operation. The way the program execution progresses is temporarily blocked by the time.sleep() command until it has finished executing – in this case, for dawdling on the current line for a specified amount of time.

Some of the motor commands are blocking in a similar way: if you tell the motors to turn on for a specified amount of time or a specified number of rotations, that’s exactly what the robot will do before moving on to the next step of the program.

Blocking commands can often have undesirable consequences if you want to be able to react to events in the world. They might be thought of as a bit like really obsessive behaviours, because you aren’t allowed to do anything else until the blocking action has completed.

In the above example, we explicitly import the time package which provides access to the time.sleep() command. But how does the program know what the left_motor and right_motor are? Our program requires that we have defined these items earlier in the program using things it does know about. In particular, we would need to use a construction of the form:

left_motor = Motor(OUTPUT_B)
right_motor = Motor(OUTPUT_C)

Here, the Motor(), OUTPUT_B and OUTPUT_C statements, as well as the previously seen SpeedPercent, are provided as predefined building blocks to use in our own simulated robot control programs.

The Motor() element refers to program elements elsewhere that define a Motor object. This computational object provides an abstract representation of a physical (or simulated) motor along with a set of operations or methods that can be enacted on it. For example, we may turn a motor on in a particular direction and with a particular speed for a particular time or for a specified number of rotations.

The Motor() object is created with an argument that identifies an output port that the physical motor in a real robot, and the simulated motor in a simulated robot, would be connected to. Output ports are used to identify power and/or control lines that a software controller can use to control the behaviour of a physical (or simulated) device, such as a motor, LED display, or speaker. In our simulated robot case, two output ports are defined: OUTPUT_B and OUTPUT_C. By convention, we associate OUTPUT_B with the motor on the left-hand side of the robot as it travels in a forwards direction, and OUTPUT_C with the right-hand motor.

To simplify matters, other ‘higher level’ predefined building blocks are also provided to make writing our programs simpler.

For example, the MoveTank building block allows us to create a ‘tank’ drive comprising a left motor and right motor. We can instruct the tank drive to turn each motor on with its own specified speed and direction, and for a certain amount of time, using a single command:

tank_drive = MoveTank(OUTPUT_B, OUTPUT_C)
tank_drive.on_for_seconds(SpeedPercent(50), SpeedPercent(50), 5)

1.2.2 Importing predefined components from Python packages

One way of defining things is to import them from a Python package. A package is essentially a collection of predefined program elements that are useful for a particular programming task.

%%sim_magic

# Import statements
from ev3dev2.motor import Motor, SpeedPercent, OUTPUT_B, OUTPUT_C
import time

# Definitions
left_motor = Motor(OUTPUT_B)
right_motor = Motor(OUTPUT_C)

# Program actions
left_motor.on(SpeedPercent(75))
right_motor.on(SpeedPercent(75))

# Idle here for 1 second
# with the motors still running
time.sleep(1)

# Program ends

Remember, lines prefixed by a # are comment lines that are not executed as program code. Instead, they are intended as notes to human readers that can be used to help make a program more readable. Writing good comments can help others make sense of your program, and help you maintain and debug your own code in the future. (For an excellent guide to writing comments, see this blog post on Writing system software: code comments.)

The following program turns the left and right motors on at a quarter (25%) of their full speed, program control flow waits for a short period (3 seconds) with the motors continuing to run, then the program ends and the motors are automatically switched off.

Note that turning motors on for fixed periods of time is not best practice and can lead to unwanted or unexpected robot behaviours. It is sometimes useful for simple test bench activities, such as the following where we are simply testing whether can we drive the robot forwards.

%%sim_magic --background Empty_Map

# Import statements
from ev3dev2.motor import Motor, SpeedPercent, OUTPUT_B, OUTPUT_C
import time

# Definitions
left_motor = Motor(OUTPUT_B)
right_motor = Motor(OUTPUT_C)

# Program actions
left_motor.on(SpeedPercent(25))
right_motor.on(SpeedPercent(25))

# Wait here for 3 seconds...
time.sleep(3)

# Program ends

Run the above code cell to download the code to the simulator, and then run the program using the simulator interface.

When you run the program in the simulator, the robot should move forwards quickly for three seconds and then stop. Try increasing the ‘sleep’ time in seconds, rerun the code code cell to download the program to the simulator, and then rerun it in the simulator. Does the robot behave as you expect? What happens if you also change the SpeedPercent(VALUE), where VALUE is a numerical value that can range from -100 to 100?

1.2.3 Using predefined code building blocks

Writing programs at such a low level is possible, but we often find it more convenient to program at a higher level of abstraction or generalisation. In the following example, we can configure and use a predefined motor drive that allows us to control both motors from a single command. The %%sim_magic_preloaded magic prepends our code with some common set-up code that makes our programs easier to write.

%sim_magic_preloaded --preview

In particular, the MoveTank() function from the ev3dev3.motor Python package allows us to define a simple tank drive comprising two motors, one on the left-hand side of the robot and one on the right-hand side. The configuration associates a controllable motor output with a particular motor.

tank_drive = MoveTank(OUTPUT_B, OUTPUT_C)

The tank drive is used to power two motors simultaneously in various ways. For example, we can turn the motors on at desired speeds:

tank_drive.on(LEFT_SPEED, RIGHT_SPEED)

We can also turn the motors on for a specified time and then automatically turn them off at the end of that period:

tank_drive.on_for_seconds(LEFT_SPEED, RIGHT_SPEED, TIME)

1.3 Making the simulated robot move

One of the defining characteristics of mobile robots is, trivially, that they move. So in the following set of activities, you will explore various ways of controlling just how the robot moves in the simulated environment, such as moving forwards and backwards for a specified distance or a particular duration, and turning in various ways.

Remember, when a robot program running in the simulator completes or terminates, the robot’s motors are turned off by default.

This means that if you have a one-step program where you turn the motors on, and that’s all, they will turn on for a fraction of a second, the program will terminate, and the motors will turn off.

On a real robot, when the program terminates, the motors may continue doing whatever they were doing at the end of the program. So best practice suggests making sure you turn the motors off as the last line of a program.

1.3.1 Activity – Driving the motors at different speeds

You can complete and submit this activity as part of your ePortfolio.

What do you think will happen if the motors are turning in the same direction but at different speeds?

What will happen if the motors turn in different directions?

Run the following code cell to download the program to the simulator and then run the program in the simulator. Experiment using different values for the motor speeds. To compare different configurations, use the Pen Down feature to leave a trace showing where the robot has been. Use the Clear Trace button to remove the trace and in the Positioning panel the Move button to reset the starting position of the robot between each run. Remember to download updated configurations to the simulator by running the updated code cell before rerunning the program in the simulator.

Note that the simulated robot may not behave as a real robot would. It all depends on how well the simulated robot and the simulator physics have been implemented.

%%sim_magic_preloaded -b Empty_Map -a 180

TIME_IN_S = 2

LEFT_MOTOR_SPEED_PC = -50
RIGHT_MOTOR_SPEED_PC = -50

tank_drive.on_for_seconds(SpeedPercent(LEFT_MOTOR_SPEED_PC),
                          SpeedPercent(RIGHT_MOTOR_SPEED_PC),
                          TIME_IN_S)

Example solution

Click the arrow in the sidebar or run this cell to reveal an example solution.

With both motor speeds in reverse, the robot drives backwards.

With both motor speeds set in the forward direction, if the left motor speed is slightly faster than the right motor speed, then the robot curves towards the right; if the right motor is slightly faster than the left motor, then the robot curves to the left. The greater difference between the speeds, the tighter the curve.

If one of the motors is set at a forward speed and one is in reverse, then the robot turns in a tight circle centred on the reverse turning wheel.

1.3.2 Turning the motors on for a specified number of wheel rotations

As well as turning the motors on for a specified period of time, we can also turn them on for a specified number of rotations of the wheels:

tank_drive.on_for_rotations(LEFT_SPEED, RIGHT_SPEED, ROTATIONS)

This is easy to imagine for the case where the wheels are turning at the same speed, but if one wheel turns faster than the other, then the robot will follow a curving path and the outside wheel will travel further than the inside wheel (assuming that the inside wheel doesn’t slip).

From the documentation, ‘if the left speed is not equal to the right speed (i.e., the robot will turn), the motor on the outside of the turn will rotate for the full rotations while the motor on the inside will have its requested distance calculated according to the expected turn.’

The following code cell provides code for exploring the use of the .on_for_rotations() command.

Run the following code cell to download the program to the simulator and then run the program in the simulator. Experiment using different values for the motor speeds and number of rotations. To compare different configurations, use the Pen Down feature to leave a trace showing where the robot has been. Use the Clear Trace button to remove the trace and the Move button to reset the starting position of the robot between each run. Remember to download updated configurations to the simulator by running the updated code cell before rerunning the program in the simulator.

%%sim_magic_preloaded -b Empty_Map

LEFT_MOTOR_SPEED = SpeedPercent(50)
RIGHT_MOTOR_SPEED = SpeedPercent(55)

ROTATIONS = 4

tank_drive.on_for_rotations(LEFT_MOTOR_SPEED, RIGHT_MOTOR_SPEED,
                          ROTATIONS)

1.3.3 Steering the robot – MoveSteering

As well as the MoveTank() configuration, a MoveSteering() configuration is also available that again is based on the presence of two motors connected to the same controllable outputs:

tank_turn = MoveSteering(OUTPUT_B, OUTPUT_C)

The MoveSteering() configuration again drives both motors simultaneously, although this time at the same speed but in different directions.

To turn the robot, we use the command:

tank_turn.on(STEERING, SPEED)

where STEERING is a numerical value between -100 and 100 and where:

  • -100 means turn left on the spot (right motor at 100% forward, left motor at 100% backward)

  • 0 means drive in a straight line

  • 100 means turn right on the spot (left motor at 100% forward, right motor at 100% backward).

As well as turning the steering drive on, we can turn it on for a specified time using .on_for_seconds(STEERING, SPEED, TIME) as in the case of the tank drive.

Alternatively, we can turn the steering drive on for a specified number of rotations of one of the wheels:

on_for_rotations(STEERING, SPEED, ROTATIONS)

The following program gives a simple example of how to turn the robot using the MoveSteering() motor configuration.

Run the following code cell to download the program to the simulator and then run the program in the simulator. Experiment with various settings for the motor speeds to get a feel for how the relative left and right motor speeds determine the robot’s behaviour.

%%sim_magic_preloaded -b Empty_Map

# the first two parameters can be unit classes or percentages.
tank_drive.on_for_rotations(SpeedPercent(50),
                            SpeedPercent(50), 4)

# drive in a turn for 2 rotations of the outer motor
tank_turn = MoveSteering(OUTPUT_B, OUTPUT_C)

tank_turn.on_for_rotations(-100,
                           SpeedPercent(75), 2)

1.3.4 Activity – Predicting a robot’s behaviour from its program

Suppose that a simulated robot starts pointing towards the top of the screen. If you download and run the program in the code cell below, will the robot turn towards the right or left while executing the sequence of commands shown?

Before you run the code, make a prediction about what you think the robot will do when the code is executed by the simulated robot.

To monitor what the robot does, you may find it convenient to enable the Pen Down feature in the simulator that will trace out the path taken by the simulator robot as the program runs.

Double-click this cell to edit it.

When the following code is executed by the robot simulator, I predict …YOUR ANSWER HERE… .

%%sim_magic_preloaded

time_1s = 1

# Set the left and right motors
# in a forward direction and run for 1 second
tank_drive.on_for_seconds(SpeedPercent(50),
                          SpeedPercent(50),
                          time_1s)


# Set the left motor forwards
# and the right motor backwards
# and run for 1 second
tank_drive.on_for_seconds(SpeedPercent(50),
                          SpeedPercent(-50),
                          time_1s)

If a simulated robot starts pointing towards the right-hand side of the screen, will it turn towards the top or bottom of the screen while executing the same sequence of commands?

Double-click this cell to edit it.

When the following code is executed by the robot simulator, I predict …YOUR ANSWER HERE… .

Example solution

Click on the arrow in the sidebar or run this cell to reveal an example solution.

When the program is run, the first tank_drive motor command moves the robot forwards for one second:

tank_drive.on_for_seconds(SpeedPercent(50), SpeedPercent(50), time_1s)

The next command turns the robot towards the right (the left wheel goes forwards and the right wheel goes backwards) for one second:

tank_drive.on_for_seconds(SpeedPercent(50), SpeedPercent(-50), time_1s)

The combined result is that the robot goes forwards for one second and turns for one second.

The trace left as the robot turns on the spot does not appear to be center simulated robot doesn’t turn exactly on the spot because the robot is turning about one of the wheels.

1.3.6 Recapping simple motor control

If a program ends after a single step in which the motors are on, as in the following example:

# ...boilerplate code...
# Start the robot driving forwards
tank_drive.on(SpeedPercent(50), SpeedPercent(50))
# program end

then the robot will not move forwards.

If you reread the program aloud and put yourself in the robot’s shoes, you’ll get a more direct sense of what happens:

  • the motors turn on

  • fraction of a second to get to next step

  • end of program and the motor is turned off by default.

What you’ll observe is the robot not moving: the robot hasn’t had time to move between the motors turning on and the program ending and the motors being turned off by default. It’s like me giving you a box of chocolates and then immediately taking them back again before you have time to take one.

If the next line of the program consumes time while it executes:

time.sleep(10)

then the first line of the program would turn the motors on, and the robot would carry on what it was doing (driving forwards) before reaching the end of the program and by default turning the motors off. Running such a program, you’d observe the robot moving with motors on for 10 seconds.

The .on_for_X() commands (.on_for_seconds(), .on_for_rotations()) work differently: they turn the motors on and then stay on_ at that command _for_ a number of seconds or rotations, as specified.

1.4 And just when it was all going so well…

The following example looks as if it should let us drive the tank:

  1. forwards in a straight line (both motors at the same speed) for a specified time using the tank drive

  2. turn on the spot for the same period (one motor forwards, the other backwards, at the same speed)

  3. reverse in a straight line for the same period of time (both motors backwards at the same speed.

Run the following code cell to download the program to the simulator and then run the program in the simulator.

%%sim_magic_preloaded --background Empty_Map

from time import sleep

time_1s = 1

# Go forwards...
# Set the left and right motors in a
# forward direction at the same speed
# and run for 1 second
tank_drive.on_for_seconds(SpeedPercent(50),
                          SpeedPercent(50),
                          time_1s)

# Pause further program execution for a moment
sleep(1)

# Turn on the spot...
# Set the left motor forwards
# and the right motor backwards
# and run for 1 second
tank_drive.on_for_seconds(SpeedPercent(50),
                          SpeedPercent(-50),
                          time_1s)

# Pause further program execution for a moment
sleep(1)

# Go backwards...
# Set the left and right motors in a
# backwards direction at the same speed
# and run for 1 second
tank_drive.on_for_seconds(SpeedPercent(-50),
                          SpeedPercent(-50),
                          time_1s)

Observe how the robot behaves when you run the program. Does the sequence of program commands appear to match the sequence of the robot’s actions? Set the pen to be down to record the path.

If things appear to behave oddly, DON’T PANIC, just try to describe what you see.

Add your observations here about what the robot actually did.

Did it match what you expected from how the program was written? If not, how did it differ from your expectations?

1.4.1 Making sense of the robot’s behaviour

From many years of working at the OU level 1 engineering residential school robotics activity, I have seen even (often!) the most capable students tell me forcefully and repeatedly that their robot is doing one thing when it very obviously isn’t. Saying what you see can be really hard particularly when it goes against expectations.

When I ran the program, I saw the robot:

  • go forwards

  • sit awhile

  • turn

  • sit awhile

  • turn again.

It did not seem to go straight backwards.

Try this simpler example which should just drive the robot forwards and then backwards, surely?

%%sim_magic_preloaded --background Empty_Map
from time import sleep
time_1s = 1
# Go forwards...
# Set the left and right motors in a
# forward direction at the same speed
# and run for 1 second
tank_drive.on_for_seconds(SpeedPercent(50),
                          SpeedPercent(50),
                          time_1s)
sleep(2)
# Go backwards...
# Set the left and right motors in a
# backwards direction at the same speed
# and run for 1 second
tank_drive.on_for_seconds(SpeedPercent(-50),
                          SpeedPercent(-50),
                          time_1s)
sleep(2)

If you download and run that code in the simulator, the robot:

  • moves forwards for a second

  • stops awhile

  • moves forwards a small amount

  • reverses.

What’s going on? Why does the robot move forwards a small amount before going backwards?

What do you think could possibly be going on?

1.4.2 Don’t forget the physics…

The motor physics actually explains the robot behaviour. When you set the motor speed, this is actually a speed request.

As mentioned previously, the simulator runs a bit like a movie, at so many animation frames per second. At each animation frame, the state of the robot and the state of the world is updated. As far as the motors go, at each animation step, the acceleration model looks at the requested speed (set by the motor commands) and at the current motor speed, and if they aren’t equal updates the current speed closer to the desired speed.

Elsewhere in the simulator, the current motor speed is multiplied by the frame duration to see how far the robot has travelled in that time step and the robot moved accordingly. (If we make the robot weightless, then the acceleration is infinite and speed should reach its new value in a single animation step.)

Now let’s consider the first part of the program: the robot moves forward as the motors are accelerated up to the desired/requested speed; then the first sleep() command pauses everything (time literally stops…). So far so good.

After the pause, the next command requests the robot to go in reverse. But it moves forwards slightly before reversing. So what’s going on?

The answer is that the motor speeds at the point the first sleep() command was called were positive, so when the motor reverse command is issued, the robot must spend time decelerating from the positive motor values (still going forwards while they are still positive) before the motor value goes negative and actually puts the robot into moving in the backwards direction and reverses the robot’s direction of travel.

The robot essentially had forward momentum that was put into suspended animation at the point of the sleep() command.

A significant part of the one second reverse time is actually spent decelerating (the robot still moves forwards as the motor speed values are positive) before the motor speed reaches the negative values, at which point the robot goes backwards.

If you run the program and look at the motor speeds in the instrumentation panel (note that the ±% motor speed values map onto the actual motor speed values in the range ±1050), then you can see this happening.

%sim_magic -iR

If you open the settings panel in the simulator, you will see a checkbox option that allows you to set the robot to Weightless which gives it an infinite acceleration rate up (or down) to the desired motor speed value.

Set the robot to weightless and run the program again. What happens now?

%sim_magic -Z

Given the above explanation, can you now explain why the robot turned, rather than reversed, in the earlier program? (Think about the motor speeds…)

What happens if you run that program with a weightless robot?

Whenever you download a robot program to the simulator, the Weightless setting may be unticked and the robot settings reverted to their default values for the selected robot configuration. If the behaviour of your program is likely to be affected by acceleration terms, or your robot appears to be behaving oddly, check the Weightless parameter setting.

1.4.3 It doesn’t get harder than that…

At this point you may think this is all just too confusing, but take heart: that was a really tricky problem, but now we understand it, we can move on with that knowledge in our toolkit.

Writing effective robot programs, like many other engineering or mathematics problems, often boils down to finding simple solutions to simple problems whilst at the same time stopping yourself from attempting to create complicated and convoluted solutions to problems that appear incomprehensible and beyond explanation because you’re looking at them the wrong way.

If you find yourself stuck on a robot program, go for a walk and let your subconscious churn on it for a bit while you think of other things. Banging your head against the keyboard and shouting ‘why doesn’t it work?’ over and over again doesn’t help. I know. I’ve tried. Repeatedly.

1.5 Summary

In the notebook, you have seen how we can construct programs as a set of sequential operations that are followed in turn. Some operations are ‘blocking’ in the sense that rather than being evaluated quickly and passing control on to the next line more or less immediately, they hog control until they have finished executing. If you turn the motors on for 15 seconds, then control will remain at that line of the program for 15 seconds.

You should also have started to get a feel for how we can drive the robot around, getting it to move forwards, backwards, turn and so on.

In the next notebook, you’ll have a chance to create some of your own programs to get the robot to move in a particular way.