Waffle (& ROS) Basics¶
Having completed the steps on the previous page, your robot and laptop should now be paired, and ROS should be up and running (on the robot). You're ready to bring the robot to life!
On this page are a series of exercises for you to work through in your teams, to explore how the robots work. We'll also talk through some core ROS concepts and use some key ROS tools, in case you haven't had a chance to explore these in simulation yet.
Quick Links¶
- Exercise 1: Making the Robot Move
- Exercise 2: Seeing the Sensors in Action
- Exercise 3: Visualising the ROS Network
- Exercise 4: Exploring ROS Topics and Interfaces
- Exercise 5: Creating A Velocity Control Node (with Python)
- Exercise 6: Using SLAM to create a map of the environment
Manual Control¶
Exercise 1: Making the Robot Move¶
There's a very useful ready-made ROS application called teleop_keyboard
(from the turtlebot3_teleop
package) that we will use to drive a Waffle around. This node works in exactly the same way in both simulation and in the real-world!
-
Open up a new terminal instance on the laptop either by using the Ctrl+Alt+T keyboard shortcut, or by clicking the Terminal App icon, we'll refer to this as TERMINAL 1. In this terminal enter the following
ros2 run
command to launch theteleop_keyboard
node:
TERMINAL 1:
-
Follow the instructions provided in the terminal to drive the robot around using specific buttons on the keyboard:
Warning
Take care to avoid any obstacles or other people in the lab as you do this!
-
Once you've spent a bit of time on this, close the application down by entering Ctrl+C in TERMINAL 1.
Packages and Nodes¶
ROS applications are organised into packages. Packages are basically folders containing scripts, configurations and launch files (ways to launch those scripts and configurations).
Scripts tell the robot what to do and how to act. In ROS, these scripts are called nodes. ROS Nodes are executable programs that perform specific robot tasks and operations. These are typically written in C++ or Python, but it's possible to write ROS Nodes using other programming languages too.
There are two key ways to launch ROS applications:
ros2 launch
ros2 run
Recall that we just used the ros2 run
command in Exercise 1 to launch the teleop_keyboard
node. This command has the following structure:
Part [1] specifies the name of the ROS package containing the functionality that we want to execute. Part [2] is used to specify a single script within that package that we want to execute. We therefore use ros2 run
commands to launch single executables (aka Nodes) onto the ROS network (in Exercise 1 for example, we launched the teleop_keyboard
node).
The ros2 launch
command has a similar structure:
Here, Part [1] is the same as the ros2 run
command, but Part [2] is slightly different: {[2] Launch file}
. In this case, Part [2] is a file within that package that specifies any number of Nodes that we want to launch onto the ROS network. We can therefore launch multiple nodes at the same time from a single launch file.
Sensors & Visualisation Tools¶
Our Waffles have some pretty sophisticated sensors on them, allowing them to "see" the world around them. Let's now see what our robot sees, using some handy ROS tools.
Exercise 2: Seeing the Sensors in Action¶
-
There shouldn't be anything running in TERMINAL 1 now, after you closed down the
teleop_keyboard
node (using Ctrl+C) at the end of the previous exercise. Return to this terminal and enter the following command:
TERMINAL 1:
This will launch an application called RViz, which is a handy tool that allows us to visualise the data from all the sensors on-board our robots. When RViz opens, you should see something similar to the following:
In the bottom left-hand corner of the RViz screen there should be a Camera panel, displaying a live image feed from the robot's camera.
No camera images?
- In the top-left "Displays" panel, scroll down to the "Camera" item.
- Under "Topic", find the "Reliability Policy" option.
- From the Drop-down box, change this from "Best Effort" to "Reliable".
In the main RViz panel you should see a digital render of the robot, surrounded by lots of green dots. This is a representation of the laser displacement data coming from the LiDAR sensor (the black device on the top of the robot). The LiDAR sensor spins continuously, sending out laser pulses into the environment as it does so. When a pulse hits an object it is reflected back to the sensor, and the time it takes for this to happen is used to calculate how far away the object is.
The LiDAR sensor spins and performs this process continuously, so a full 360° scan of the environment can be generated. This data is therefore really useful for things like obstacle avoidance and mapping.
-
Place your hand in front of the robot and see if the position of the green dots change to match your hand's location. Move your hand up and down and consider at what height the LiDAR sensor is able to detect it.
-
Then, move your hand closer and further away and watch how the green dots move to match this.
-
Open up a new terminal instance (TERMINAL 2) and launch the
teleop_keyboard
node as you did in Exercise 1. Watch how the data in the RViz screen changes as you drive the robot around a bit.
Exercise 3: Visualising the ROS Network¶
Using ros2 run
and ros2 launch
, as we have done so far, it's easy to end up with a lot of different processes or ROS Nodes running on the network, some of which we will interact with, but others may just be running in the background. It is often useful to know exactly what is running on the ROS network, and there are a few ways to do this.
-
Open up a new terminal instance now (TERMINAL 3) and from here use the
ros2 node
command to list the nodes that are currently running:
TERMINAL 3:
You may notice up to 4 items in the list.
-
We can visualise the connections between the active nodes by using a ROS node called
rqt_graph
. Launch this as follows:
TERMINAL 3:
A window should then open:
-
From here, we then want to load the Node Graph plugin. From the top menu select
Plugins
>Introspection
>Node Graph
. -
In the window that opens, select
Nodes/Topics (active)
from the dropdown menu in the top left.What you should then see is a map of all the nodes in the list from above (as ovals), and arrows to illustrate the flow of information between them. This is a visual representation of the ROS network!
Items that have a rectangular border are ROS Topics. ROS Topics are essentially communication channels, and ROS Nodes can read (subscribe) or write (publish) to these topics to access sensor data, pass information around the network and make things happen.
If the
teleop_keyboard
Node is still active (in TERMINAL 2) then this graph should show us that the node is publishing messages to a topic called/cmd_vel
, which in turn is being subscribed to by thezenoh_bridge_ros2dds
Node. The Zenoh Bridge node handles all communication between the robot and the laptop, so this node is tunnelling the data from/cmd_vel
to the robot to make it move.
A ROS Robot could have hundreds of individual nodes running simultaneously to carry out all its necessary operations and actions. Each node runs independently, but uses ROS communication methods to communicate and share data with the other nodes on the ROS Network.
Publishers and Subscribers: A ROS Communication Method¶
ROS Topics are key to making things happen on a robot. Nodes can publish (write) and/or subscribe to (read) ROS Topics in order to share data around the ROS network. Data is published to topics via message interfaces.
Let's have a look at this in a bit more detail...
Exercise 4: Exploring ROS Topics and Interfaces¶
Much like the ros2 node list
command, we can use ros2 topic list
to list all the topics that are currently active on the ROS network.
-
Close down the RQT Graph window if you haven't done so already. This will release TERMINAL 3 so that we can enter commands in it again. Return to this terminal window and enter the following:
TERMINAL 3:
A much larger list of items should be printed to the terminal now. See if you can spot the
/cmd_vel
item in the list.This topic is used to control the velocity of the robot ('command velocity').
-
Let's find out more about this using the
ros2 topic info
command.
TERMINAL 3:
This should provide an output similar to the following:
This tells us that the type of data being communicated on the
/cmd_vel
topic is called:geometry_msgs/msg/Twist
.The interface description has three parts:
geometry_msgs
: The name of the ROS package that this interface belongs to.msg
: The type of interface. In this case message, but there are other types too.Twist
: The name of the message interface.
We have just learnt then, that if we want to make the robot move we need to publish
Twist
messages to the/cmd_vel
topic. -
We can use the
ros2 interface
command to find out more about theTwist
message:
TERMINAL 3:
From this, we should obtain the following:
Let's find out what it all means...
Velocity Control¶
The motion of any mobile robot can be defined in terms of its three principal axes: X
, Y
and Z
. In the context of our TurtleBot3 Waffle, these axes (and the motion about them) are defined as follows:
In theory then, a robot can move linearly or angularly about any of these three axes, as shown by the arrows in the figure. That's six Degrees of Freedom (DOFs) in total, achieved based on a robot's design and the actuators it is equipped with. Take a look back at the ros2 interface show
output above. Hopefully it's a bit clearer now that these topic messages are formatted to give a ROS Programmer the ability to ask a robot to move in any one of its six DOFs.
Vector3 linear
float64 x <-- Forwards (or Backwards)
float64 y <-- Left (or Right)
float64 z <-- Up (or Down)
Vector3 angular
float64 x <-- "Roll"
float64 y <-- "Pitch"
float64 z <-- "Yaw"
Our TurtleBot3 robot only has two motors, so it doesn't actually have six DOFs! The two motors can be controlled independently, which gives it what is called a "differential drive" configuration, but this still only allows it to move with two degrees of freedom in total, as illustrated below.
It can therefore only move linearly in the x-axis (Forwards/Backwards) and angularly in the z-axis (Yaw).
Exercise 5: Creating A Velocity Control Node (with Python)¶
Important
Before you start this, close down RViz (click the "Close without saving" button, if asked) and stop the teleop_keyboard
node by entering Ctrl+C in TERMINAL 2.
As we've seen, making a robot move with ROS is simply a case of publishing the right ROS Interface (geometry_msgs/msg/Twist
) to the right ROS Topic (/cmd_vel
). Earlier we used the teleop_keyboard
node to drive the robot around, a bit like a remote control car. In the background here all that was really happening was that the node was converting our keyboard button presses into velocity commands and publishing these to the /cmd_vel
topic.
In reality, robots need to be able to navigate complex environments autonomously, which is quite a difficult task, and requires us to build bespoke applications. We can build these applications using Python, and we'll look at the core concepts behind this now by building a simple node that will allow us to make our robot a bit more "autonomous". What we will do here forms the basis of the more complex applications that you will learn about in the lab course!
-
Above we talked about how ROS Nodes should be contained within packages, so let's create one now using a helper script that we've already put together. (This is covered in more detail in the ROS course, but for the purposes of this exercise let's just go ahead and run the script without worrying too much about it!)
In TERMINAL 1, navigate to the
tuos_ros
Course Repo, which is located in the ROS2 Workspace on the laptop:
Here you'll find the
create_pkg.sh
helper script. Run this now using the following command to create a new ROS package calledwaffle_demo
:
-
Navigate into this new package directory (using
cd
):
TERMINAL 1:
Info
..
means "go back one directory," so that command above is tellingcd
to navigate out of thetuos_ros
directory (and therefore back to~/ros2_ws/src/
), and then go into thewaffle_demo
package directory from there (and then into thescripts
directory within that).
-
Here, create a Python file called
square.py
using thetouch
command:
TERMINAL 1:
-
You'll need to change the execution permissions for this file in order to be able to run it later on. This is also covered in more depth in the ROS course but, for now, simply run the following command:
TERMINAL 1:
-
Now we need to start editing files in our package, and we'll do that using Visual Studio Code (VS Code).
First, use
cd
to navigate back one directory, to get us back to the root of our package:
TERMINAL 1:
Verify that you're in the right place by using the
pwd
command, which should provide the following output:Having confirmed that you're in the right place, open up VS Code in this directory:
Note
Don't forget to include the
.
at the end there, it's important!!
-
Next, we need to add our
square.py
file as an executable to our package'sCMakeLists.txt
.In VS Code, open the
CMakeLists.txt
file that is at the root of thewaffle_demo
package directory (/home/student/ros2_ws/src/waffle_demo/CMakeLists.txt
).Locate the lines (near the bottom of the file) that read:
# Install Python executables install(PROGRAMS scripts/minimal_node.py DESTINATION lib/${PROJECT_NAME} )
Replace
minimal_node.py
withsquare.py
to define this as an executable of your package: -
Next, in the VS Code file explorer, open up the
scripts
directory and click on thesquare.py
file to open it up in the editor. -
Paste the following content into the
square.py
file:square.py#!/usr/bin/env python3 import rclpy from rclpy.node import Node # (1)! from rclpy.signals import SignalHandlerOptions from geometry_msgs.msg import Twist # (2)! from math import sqrt, pow, pi # (3)! class Square(Node): # (4)! def __init__(self): super().__init__("square") # (5)! self.vel_pub = self.create_publisher( msg_type=Twist, topic="cmd_vel", qos_profile=10, ) # (6)! self.state = 1 self.change_state = True self.vel = Twist() # (7)! ctrl_rate = 10 self.timer = self.create_timer( timer_period_sec=1/ctrl_rate, callback=self.timer_callback, ) # (8)! self.timestamp = self.get_clock().now().nanoseconds # (9)! self.shutdown = False self.get_logger().info( f"The '{self.get_name()}' node is initialised." ) def timer_callback(self): # (10)! time_now = self.get_clock().now().nanoseconds elapsed_time = (time_now - self.timestamp) * 1e-9 # (11)! if self.change_state: # (12)! self.timestamp = self.get_clock().now().nanoseconds self.change_state = False self.vel.linear.x = 0.0 self.vel.angular.z = 0.0 self.get_logger().info( f"Changing to state: {self.state}") elif self.state == 1: # (13)! if elapsed_time > 2: self.state = 2 self.change_state = True else: self.vel.linear.x = 0.05 self.vel.angular.z = 0.0 elif self.state == 2: # (14)! if elapsed_time > 4: self.state = 1 self.change_state = True else: self.vel.angular.z = 0.2 self.vel.linear.x = 0.0 self.get_logger().info( f"Publishing Velocities:\n" f" linear.x: {self.vel.linear.x:.2f} [m/s] | angular.z: {self.vel.angular.z:.2f} [rad/s].", throttle_duration_sec=1, ) self.vel_pub.publish(self.vel) # (15)! def on_shutdown(self): # (17)! for i in range(5): self.vel_pub.publish(Twist()) self.shutdown = True def main(args=None): # (16)! rclpy.init( args=args, signal_handler_options=SignalHandlerOptions.NO ) node = Square() try: rclpy.spin(node) except KeyboardInterrupt: node.on_shutdown() finally: while not node.shutdown: continue node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()
rclpy
is the ROS client library for Python. We need this (and theNode
class from it) in order to build and run Python ROS nodes.- We know from earlier that in order to make a robot move we need to publish messages to the
/cmd_vel
topic, and that this topic uses thegeometry_msgs/msg/Twist
interface messages. This is how we import that message into the node, in order to create velocity commands in Python (which we'll get to shortly...) -
Here we're importing some mathematical operators that could be useful...
Mathematical Operation Python Syntax \(\sqrt{a+b}\) sqrt(a+b)
\(a^{2}+(bc)^{3}\) pow(a, 2) + pow(b*c, 3)
\(\pi r^2\) pi * pow(r, 2)
-
All the functionality of our Python node is contained within a Class called
Square
. - When we initialise this class, we provide a name for our node, which is the name that is used to register it on the ROS network. We're calling this one "square".
- Here we are setting up a publisher to the
/cmd_vel
topic so that the node can writeTwist
messages to this topic to make the robot move. - We're instantiating a
Twist
message here and calling itvel
(we'll assign velocity values to this later on). ATwist
message contains six different components that we can assign values to. Any idea what these six values might represent? - We want our node to run at a rate of 10 times per second (10 Hz), so we create a timer object here, set the timer period (
timer_period_sec
) and then point the object to a "callback function" that is defined later on in the code. This callback function will execute at the rate that we specify withtimer_period_sec
... - What time is it right now? (This will help us to keep track of elapsed time in our main timer callback...)
- Here we're defining our timer callback function. Everything here will execute at the rate that we specified earlier, so we can encapsulate our main control code in here and be confident that it will execute repeatedly (and indefinitely) at our desired rate.
- Here we're comparing the time now to the time the last time we checked, to tell us how much time has elapsed since then (converting from nanoseconds to seconds by multiplying by
1e-9
). We'll use that information to decide what the robot should do... - This variable is used to stop the robot (if necessary), check the time again, and then move into a new state.
- In state
1
we set velocities that will make the robot move forwards (linear-X velocity only). If the elapsed time is greater than 2 seconds however, we move on to state2
. - In state
2
we set velocities that will make the robot turn on the spot (angular-Z velocity only). In this case, if the elapsed time is greater than 4 seconds, we move back to state1
. - Regardless of what happens in the
if
statements above, we always publish a velocity command to the/cmd_vel
topic here (i.e. every time this timer callback executes). - The rest of the code here is "boilerplate": a standard approach that we'll use to instantiate our nodes and execute them. You'll learn about all of this throughout the lab course.
- We're defining a class method here that we can call when our node needs to shut down. We're controlling a robot here, so it's important to make sure the robot stops moving when this happens.
Click on the icons above to expand the code annotations. Read these carefully to ensure that you understand what's going on and how this code works.
-
Having programmed our node and defined it as an executable in our package, we're now ready to build the package so that we can run it. We use a tool called "Colcon" to do this, but this MUST be run from the root of the ROS Workspace (i.e.:
~/ros2_ws/
), so let's navigate there now usingcd
:
TERMINAL 1:
Then, use the
colcon build
command to build your package:And finally, "re-source" the environment:
-
Now, run the code.
Note
Make sure the robot is on the floor and has enough room to roam around before you do this!
TERMINAL 1:
Observe what the robot does. When you've seen enough, enter Ctrl+C in TERMINAL 1 to stop the node from running, which should also stop the robot from moving.
-
Now it's time to adapt the code:
The aim here is to make the robot follow a square motion path. What you may have observed when you actually ran the code is that the robot doesn't actually do that! We're using a time-based approach to make the robot switch between two different states continuously:
- Moving forwards
- Turning on the spot
Have a look at the code to work out how much time the robot will currently spend in each state.
We want the robot to follow a 0.5m x 0.5m square motion path. In order to properly achieve this you'll need to adjust the timings, or the robot's velocity, or both. Edit the code so that the robot actually follows a 0.5m x 0.5m square motion path!
SLAM¶
Simultaneous Localisation and Mapping (SLAM) is a sophisticated tool that is built into ROS. Using data from the robot's LiDAR sensor, plus knowledge of how far the robot has moved1 a robot is able to create a map of its environment and keep track of its location within that environment at the same time. In the exercise that follows you'll see how easy it is to implement SLAM with the Waffle.
Exercise 6: Using SLAM to create a map of the environment¶
-
In TERMINAL 1 enter the following command to launch all the necessary SLAM nodes on the laptop:
TERMINAL 1:
Tip
On the laptop, this command is also available as an alias:
tb3_slam
!
This will launch a new RViz instance, showing a top-down view of the environment, and dots of various colours representing the real-time LiDAR data. Rather than a robot model, the robot is now represented in the environment as a series of links denoted by 3-dimensional red/green/blue crosses (you can turn these off by unchecking the
TF
option in the left-hand "Displays" menu, if you want to).SLAM will already have begun processing this data to start building a map of the boundaries that are currently visible to the Waffle based on its location in the environment.
-
Return to TERMINAL 2 and launch the
teleop_keyboard
node. Start to drive the robot around slowly and carefully to build up a complete map of the area.Tip
It's best to do this slowly and perform multiple circuits of the area to build up a more accurate map.
-
Once you're happy that your robot has built up a good map of its environment, you can save this map using the
map_saver_cli
node from a package callednav2_map_server
:-
First, create a new directory within your ROS package on the laptop. Return to TERMINAL 3 and navigate to the root of the
waffle_demo
package that you created earlier. We can use thecolcon_cd
tool to do this now:
TERMINAL 3:
-
Create a directory in here called
maps
:
TERMINAL 3:
-
Navigate into this directory:
TERMINAL 3:
-
Then, use
ros2 run
to run themap_saver_cli
node and save a copy of your robot's map:
TERMINAL 3:
Replacing
MAP_NAME
with an appropriate name for your map. This will create two files:- a
MAP_NAME.pgm
- a
MAP_NAME.yaml
file
...both of which contain data related to the map that you have just created.
- a
-
The
.pgm
file can be opened using an application calledeog
on the laptop:
TERMINAL 3:
-
-
Return to TERMINAL 1 and close down SLAM by pressing Ctrl+C. The process should stop and RViz should close down.
-
Close down the
teleop_keyboard
node in TERMINAL 2 as well, if that's still running.
Next Steps¶
"Pro Tips": There are some important things to consider when working with the Real Waffles. Move onto the next page to find out more...
... and when you've done that, don't forget to power off your robot properly.
-
You'll learn much more about "Robot Odometry" in the lab course. ↩