Note: This is part of a series where I follow along with the ROS 2 Humble and Gazebo Fortress tutorials and demos. This is not original content.

WARNING: Today’s blog post focuses exclusively on ROS2 Humble. Future posts may cover ROS 1 Noetic and/or ROS 2 Foxy, as well as (several different versions of) Gazebo. Make sure you have installed ROS 2 Humble and Gazebo Fortress before starting. If you’re an Ubuntu user, you can find instructions in this blog post.

Welcome! I intend this to be the first in a series of blog posts talking about how to get started working with ROS (for controlling robots) and Gazebo (for simulating them).

ROS and Gazebo (and the many layers of “version hell”) Link to heading

If you try to learn ROS and/or Gazebo, you’ll discover almost immediately that they are very complex pieces of software that have evolved and changed significantly over the years. Being independently developed, not all versions of ROS will work with all versions of Gazebo. Furthermore, ROS and Gazebo themselves are built and tested against a specific environment (usually a long-term support release of Ubuntu Linux). You should try to use that same environment to run them, if you can.

The Gazebo page has a detailed guide on which versions of ROS work with which versions of Gazebo, but I don’t want to go into a lot of detail here.

I’m going to cover two different software stacks in this and future blog posts, because I have to. This is because ROS 1 and ROS 2 are so different. They use different commands to launch and run things. They use different build systems to build packages. If you are thinking about starting a ROS project today, you want to use ROS 2. However, a lot of example code, blog posts, tutorials, and robot hardware are still floating around out there using ROS 1, so it’s still necessary to have a working understanding of ROS 1. To make sure you can still read, understand, and use that older code, we will cover ROS 1, too.

Good? Good.

So, the two stacks we’ll use are:

Stack Ubuntu Version ROS Version Gazebo Version
“Old” ROS/Gazebo Ubuntu 20.04 LTS ROS 1 Noetic Gazebo 11
“New” ROS/Gazebo Ubuntu 22.04 LTS ROS 2 Humble Gazebo Fortress

The “new” stack Link to heading

Let’s start out by taking a look at our new stack. I’ll be summarizing notes I took when going through the relevant tutorials:

I assume you have already installed everything. If you haven’t, check out this earlier blog post which will walk you through the process.

ROS 2 Humble (basics) Link to heading

First off, ROS uses the concept of workspaces to isolate ROS projects from each other. This does a few nice things:

  1. It allows you to install and use multiple versions of ROS on the same computer without issues.
  2. It enables you to develop multiple ROS projects using different versions of ROS or conflicting sets of ROS packages.

You access a workspace by sourceing it in your shell, like this:

source /opt/ros/humble/setup.bash 

Note that I assume you’re running Ubuntu. If you are running Windows or macOS, see this page.

Note that sourceing the packages from /opt/ros/<rosversion> sets up what is known as the underlay workspace. You then run separate source commands from inside your own ROS project, to add one or more overlays. Taken together, the underlay plus one or more overlays makes up your ROS workspace.

Pitfall: Forgetting to run source to set up your workspace when opening a new shell is a common source of errors. Always check that you have done this. If you’re working with a project pretty much all the time, you can add the source commands inside your .bashrc or .bash_profile file so they are run each time you open a shell.

Protip: Can’t remember if you ran source, or just want to check what ROS environment variables are set? Just run: printenv | grep -i ROS

Here’s an example of what printenv | grep -i ROS should print after running source /opt/ros/humble/setup.bash on Ubuntu 22.04 with ROS 2 Humble installed:

ROS_VERSION=2
ROS_PYTHON_VERSION=3
AMENT_PREFIX_PATH=/opt/ros/humble
PYTHONPATH=/opt/ros/humble/lib/python3.10/site-packages:/opt/ros/humble/local/lib/python3.10/dist-packages
LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/gazebo-11/plugins:/opt/ros/humble/opt/rviz_ogre_vendor/lib:/opt/ros/humble/lib/x86_64-linux-gnu:/opt/ros/humble/lib
ROS_LOCALHOST_ONLY=0
PATH=/opt/ros/humble/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin
ROS_DISTRO=humble

Turtlesim Link to heading

Base ROS 2 packages can be installed directly using Ubuntu’s package manager. Let’s start by installing ROS 2’s turtlesim, which gives us a simple “virtual turtlebot” we can control to learn more about ROS:

sudo apt update
sudo apt install ros-humble-turtlesim

Now we make sure it installed correctly using the ros2 pkg command:

ros2 pkg executables turtlesim

This will return a list of the executables that make up the turtlesim package, which are:

turtlesim draw_square
turtlesim mimic
turtlesim turtle_teleop_key
turtlesim turtlesim_node

To run turtlesim, we need to use turtlesim_node, which we can launch like this:

ros2 run turtlesim turtlesim_node

This will open up a window where we can see our little turtle buddy:

The turtle bot GUI

We can run a different piece of code to control the turtle. We’ll need to open a new terminal and re-run source /opt/ros/humble/setup.bash first, to make this work:

source /opt/ros/humble/setup.bash
ros2 run turtlesim turtle_teleop_key

This second program will not open a new window; rather, it will start polling the terminal for keyboard inputs, which we can use to “drive” the turtle around, drawing a line on the screen. Like this:

The turtle, drawing lines on the screen

Protip: You have to press and hold the arrow keys to make the turtle keep moving (or rotating). Pressing once and letting go immediately won’t do much!

Installing and using rqt Link to heading

Later on, we’ll get into more detail about how turtle_teleop_key is talking to the turtle. For now, all you need to know is that ROS nodes talk to each other by sending messages through topics.

To make debugging easier, ROS provides a tool called rqt that lets us interact with ROS components through a handy GUI. This allows us to manually send messages to ROS services or inspect the output of sensors.

Let’s install it:

sudo apt update
sudo apt install '~nros-humble-rqt*'

Awesome. Now, we run it with:

rqt

Pitfall: Again, don’t forget to run source /opt/ros/humble/setup.bash first.

This will open up the rqt GUI:

rqt GUI

By default, rqt should open up to the Service Caller interface, like you see here. If it does not, you can open it yourself from the menus. It’s under Plugins > Services > Service Caller.

Here you can see that the service called /clear is selected. Hmmm….I wonder what that does. Maybe I’ll just click on “Call” and find out…

rqt GUI, after calling the “clear” service

Well, would you look at that! It cleared my turtle’s path. If you play around with the Service Caller a little more, you’ll see there are other services like /spawn running, which lets me create additional turtles. /spawn is a more complex service call that requires me to include some parameters in the call, such as the new turtle’s name and (X,Y) position.

The official tutorials have a more in-depth example.

Re-mapping inputs Link to heading

Ok, so let’s say you have played around with /spawn and you now have two happy little turtles on your screen. You want to control your new turtle. What do you do?

If you run turtle_teleop_key again, you’ll discover it still controls your original turtle.

This is because, by default, messages from turtle_teleop_key are going to the cmd_vel topic that belongs to the first turtle.

However, you can remap the output of your new turtle_teleop_key to go to a specific turtle, like this:

ros2 run turtlesim turtle_teleop_key --ros-args --remap turtle1/cmd_vel:=newturt/cmd_vel

Note that my second turtle is named newturt. Change the command above to match your turtle’s name. The first turtle will probably still be called turtle (the default name), assuming you are following along with this post (or the official tutorial).

Awesome, now I can switch back and forth between my two turtle_teleop_key console windows and control each turtle separately:

Controlling two turtles at once

Cleaning up Link to heading

Ok, we did it! To clean up, hit q in each of the terminals where turtle_teleop_key is running, and Ctrl + C in the terminal where you launched turtlesim_node.

If you are curious about how turtlesim itself was built, the code is available on GitHub. If you’re feeling adventurous, you could even try building it yourself!

Download the entire set of ROS tutorials with:

git clone https://github.com/ros/ros_tutorials.git

Ok, now let’s look at some basic ROS concepts we need to understand before moving on to Gazebo. Like much of the content here, I am summarizing based on the official tutorial.

ROS Nodes Link to heading

ROS applications are made up of nodes. Each node should have one job (such as reading data from a particular sensor or controlling a motor). Nodes send and receive data in several ways:

  1. Topics
  2. Services
  3. Actions
  4. Parameters

Here, take a look at this graphic I shamelessly borrowed from the official tutorial:

The ROS Graph

Anatomy of a ros2 run command Link to heading

ROS 2 projects consist of one or more executables inside a package. Let’s say we run our turtle:

ros2 run turtlesim turtlesim_node

That first turtlesim is the package, and turtlesim_node is one of the executables inside that package.

Running the turtlesim_node executable should have created one (or possibly more) nodes. Let’s list those out:

ros2 node list

We see:

/turtlesim

Great. We have a single running node now, called /turtlesim. If we also started up turtle_teleop_key, we’d see two nodes:

/turtlesim
/teleop_turtle

Remember the remapping feature from earlier? We can use that to rename nodes, too. Let’s say we want to run turtlesim_node again but change the name of the node it creates to my_turtle. We would run:

ros2 run turtlesim turtlesim_node --ros-args --remap __node:=my_turtle

Re-running ros2 node list would now show 3 nodes:

/my_turtle
/turtlesim
/teleop_turtle

You can get more info about a node with ros2 node info, like this:

ros2 node info /turtlesim

Which prints out:

/turtlesim
  Subscribers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /turtle1/cmd_vel: geometry_msgs/msg/Twist
  Publishers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /rosout: rcl_interfaces/msg/Log
    /turtle1/color_sensor: turtlesim/msg/Color
    /turtle1/pose: turtlesim/msg/Pose
  Service Servers:
    /clear: std_srvs/srv/Empty
    /kill: turtlesim/srv/Kill
    /reset: std_srvs/srv/Empty
    /spawn: turtlesim/srv/Spawn
    /turtle1/set_pen: turtlesim/srv/SetPen
    /turtle1/teleport_absolute: turtlesim/srv/TeleportAbsolute
    /turtle1/teleport_relative: turtlesim/srv/TeleportRelative
    /turtlesim/describe_parameters: rcl_interfaces/srv/DescribeParameters
    /turtlesim/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
    /turtlesim/get_parameters: rcl_interfaces/srv/GetParameters
    /turtlesim/list_parameters: rcl_interfaces/srv/ListParameters
    /turtlesim/set_parameters: rcl_interfaces/srv/SetParameters
    /turtlesim/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
  Service Clients:

  Action Servers:
    /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute
  Action Clients:

Let’s take a look at teleop_turtle:

ros2 node info /teleop_turtle

You’ll see it has very different output:

/teleop_turtle
  Subscribers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
  Publishers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /rosout: rcl_interfaces/msg/Log
    /turtle1/cmd_vel: geometry_msgs/msg/Twist
  Service Servers:
    /teleop_turtle/describe_parameters: rcl_interfaces/srv/DescribeParameters
    /teleop_turtle/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
    /teleop_turtle/get_parameters: rcl_interfaces/srv/GetParameters
    /teleop_turtle/list_parameters: rcl_interfaces/srv/ListParameters
    /teleop_turtle/set_parameters: rcl_interfaces/srv/SetParameters
    /teleop_turtle/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
  Service Clients:

  Action Servers:

  Action Clients:
    /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute

Now let’s look at topics.

ROS Topics Link to heading

In the simplest terms, topics allow ROS nodes to communicate with each other by sending messages.

A node can publish to one or more topics, and be subscribed to (receive messages from) one or more topics.

Therefore, messages in ROS can be:

  • one-to-one
  • many-to-one
  • one-to-many
  • many-to-many

Here’s another diagram shamelessly stolen from the official tutorial to illustrate the point:

ROS Topics and Messages

Inspecting the graph Link to heading

Let’s re-launch turtlesim_node:

ros2 run turtlesim turtlesim_node

And also turtle_teleop_key:

ros2 run turtlesim turtle_teleop_key

How can we see the nodes and the topics connecting them? Easy, we just use rqt_graph. Open a new terminal and execute rqt_graph there:

rqt_graph

Protip: You can also open the graph view from the menus in rqt via Plugins > Introspection > Node Graph.

In the screenshot below, I have opened the graph view directly from within rqt:

rqt_graph ROS graph viewer

Note that the color highlighting will change based on which part of the graph is beneath your cursor.

So, /teleop_turtle is publishing messages to /turtle1/cmd_vel, which /turtlesim subscribes to. These messages direct the turtle to move or turn.

We can get additional information about the type of each topic with:

/parameter_events [rcl_interfaces/msg/ParameterEvent]
/rosout [rcl_interfaces/msg/Log]
/turtle1/cmd_vel [geometry_msgs/msg/Twist]
/turtle1/color_sensor [turtlesim/msg/Color]
/turtle1/pose [turtlesim/msg/Pose]

We can watch the messages being published to a topic with:

ros2 topic echo <name_of_topic>

For instance, we can use this to watch the teleop commands which are being sent to the turtle, like this:

ros2 topic echo /turtle1/cmd_vel

If we now open up the terminal window where teleop is running and move the turtle around a bit, we’ll see output like this:

linear:
  x: 2.0
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.0
---
linear:
  x: 2.0
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.0
---

If you open rqt_graph again (and remember to uncheck all the boxes to the right of the word “hide”), you’ll notice something interesting:

The rqt graph, with the new node created by the echo command

A new node has appeared! The node ros2cli_3864 was created by the echo command. That’s right…the echo command prints messages to our terminal by subscribing to the topic where the messages are being sent, just like any other ROS node would.

We can also learn more about a topic (for instance, how many active subscriptions there are) by running:

ros2 topic info /turtle1/cmd_vel

Which (assuming the echo command and teleop commands are still running) should return:

Type: geometry_msgs/msg/Twist
Publisher count: 1
Subscription count: 2

Nodes communicate with each other over topics using messages. The node sending a message is a publisher. The node receiving the message is a subscriber.

In order to communicate, the publisher and subscriber must send the same types of messages.

Remember, we can see the topics by adding -t to the ros2 topic command like this:

ros2 topic list -t

Which should return:

/parameter_events [rcl_interfaces/msg/ParameterEvent]
/rosout [rcl_interfaces/msg/Log]
/turtle1/cmd_vel [geometry_msgs/msg/Twist]
/turtle1/color_sensor [turtlesim/msg/Color]
/turtle1/pose [turtlesim/msg/Pose]

So the cmd_vel topic is expecting messages of type geometry_msgs/msg/Twist.

What this means is that inside the ROS package geometry_msgs there is a msg called Twist. The ros2 interface show command can tell us exactly what the content of a Twist message should look like:

ros2 interface show geometry_msgs/msg/Twist

We see:

# This expresses velocity in free space, broken into its linear and angular parts.

Vector3  linear
    float64 x
    float64 y
    float64 z
Vector3  angular
    float64 x
    float64 y
    float64 z

So this message consists of two vectors with three 64-bit floating point elements each. One (X,Y,Z) pair representing linear motion, and another (X,Y,Z) pair representing angular motion.

Just looking at this, you can probably guess that Twist messages are designed for two-wheeled robots which can pivot about their wheelbase without moving forward or backward. We would need a different type of control message for a car-like robot with four wheels.

Guess what? Now that we know how the messages are formatted, we can send them ourselves from the command line with:

ros2 topic pub <topic_name> <msg_type> '<args>'

Where <args> is the actual data (message) we want to send.

The ros2 topic command expects <args> to be formatted like a YAML document, so keep that in mind. Here’s an example taken straight from the docs:

ros2 topic pub --once /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"

Protip: --once makes sure we send a single message and then exit. This is optional.

This will make the turtle drive along an arc, like so:

The turtle, driving along an arc

We can append a different flag (the --rate flag) to tell ROS to continuously send a command to our robot. For instance, to send a message at 1 Hz (once a second), you would use --rate 1 like this:

ros2 topic pub --rate 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"

The turtle should now be driving around in a circle, like so (sorry if the .gif doesn’t loop perfectly):

Our turtle, going in happy circles

Protip: You can hit Ctrl + C in the terminal where ros2 topic pub is running, to stop sending commands to the turtle.

Opening up rqt_graph again (and fiddling with what nodes are hidden, pay attention to that part), we now see:

New ROS topic graph

Notice how we have /teleop_turtle and /_ros2cli_4434 both publishing to /turtle1/cmd_vel. Those would be our teleop and ros2 topic pub commands, which are both sending commands to control the robot. We have the turtle /turtlesim receiving the commands, and also /_ros2cli_3864, which is our echo command. Cool!

We can watch the turtle’s position change by watching the /turtle1/pose topic, which records the robot’s motion.

ros2 topic echo /turtle1/pose

This will continuously print large numbers of messages like the ones below:

---
x: 5.353726863861084
y: 6.314544677734375
theta: 0.7420223951339722
linear_velocity: 2.0
angular_velocity: 1.7999999523162842
---
x: 5.376681804656982
y: 6.33683967590332
theta: 0.7708224058151245
linear_velocity: 2.0
angular_velocity: 1.7999999523162842
---
x: 5.398984909057617
y: 6.3597869873046875
theta: 0.7996224164962769
linear_velocity: 2.0
angular_velocity: 1.7999999523162842
---

These are generated at a much higher rate than we are sending commands to the robot because the robot continues to move between the times when we are sending it commands (no robot can stop instantly, after all).

Looking again at rqt_graph we now see the /turtle1/pose topic receiving messages from /turtlesim and sending them to /_ros2cli_4905, which is our new echo command that we just started up:

Updated ROS graph, showing a new topic for the robot’s pose

A quick note on time Link to heading

Although we don’t need it yet, it’s possible to include a timestamp with the current time in the header field of messages. Here are two different approaches.

1. For messages with a std_msgs/msg/Header header type Link to heading

We can use auto to fill in a stamp value with the current time, like this:

ros2 topic pub /pose geometry_msgs/msg/PoseStamped '{header: "auto", pose: {position: {x: 1.0, y: 2.0, z: 3.0}}}'

2. For messages with a field type of builtin_interfaces/msg/Time Link to heading

For these messages, we can set the time to now, like so:

ros2 topic pub /reference sensor_msgs/msg/TimeReference '{header: "auto", time_ref: "now", source: "dummy"}'

We can also view the rate at which data is being published (relevant to the messages we are publishing with the --rate 1 flag), like this:

ros2 topic hz /turtle1/pose

Which should return something like this:

WARNING: topic [/turtle1/pose] does not appear to be published yet
average rate: 62.512
    min: 0.016s max: 0.017s std dev: 0.00045s window: 64
average rate: 62.502
    min: 0.015s max: 0.017s std dev: 0.00047s window: 127
average rate: 62.506
    min: 0.015s max: 0.017s std dev: 0.00047s window: 190
average rate: 62.503
    min: 0.015s max: 0.017s std dev: 0.00048s window: 253

You can see messages are being published at approximately 62 cycles per second (Hz).

Awesome! You can now close all the terminal windows where you ran ROS commands, before we move on to exploring parameters.

ROS Services Link to heading

Services are another way for nodes in the ROS graph to communicate.

Whereas topics use a publish and subscribe model, services use a call and response model.

Services provide data only when they are called by a client.

Here, take a look at these two images ripped straight from the ROS 2 Humble docs:

ROS service with one client

And here we have an example with multiple clients:

ROS service with multiple clients

Again, you’ll need to set up turtlesim and teleop_key again. Open two separate terminal windows, and execute the commands below (one in each terminal):

ros2 run turtlesim turtlesim_node
ros2 run turtlesim turtle_teleop_key

Awesome. This time, let’s take a look at the running services:

ros2 service list

This should print out a list like the one below. Some of these may look familiar (we called at least a few of these services earlier with rqt):

/clear
/kill
/reset
/spawn
/teleop_turtle/describe_parameters
/teleop_turtle/get_parameter_types
/teleop_turtle/get_parameters
/teleop_turtle/list_parameters
/teleop_turtle/set_parameters
/teleop_turtle/set_parameters_atomically
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/describe_parameters
/turtlesim/get_parameter_types
/turtlesim/get_parameters
/turtlesim/list_parameters
/turtlesim/set_parameters
/turtlesim/set_parameters_atomically

Notice how the /teleop_turtle and /turtlesim nodes both have the same six parameters services. Most ROS nodes will have these (more on that in the next section).

Like topics, services also have types that define the service requests and responses.

Let’s take a look at the type for the /clear service, as an example:

ros2 service type /clear

This should return:

std_srvs/srv/Empty

This means no data needs to be sent alongside a request to /clear, and the /clear service returns no data when it is called.

We can list the types of all services with:

ros2 service list -t

Which in our case should return:

/clear [std_srvs/srv/Empty]
/kill [turtlesim/srv/Kill]
/reset [std_srvs/srv/Empty]
/spawn [turtlesim/srv/Spawn]
/teleop_turtle/describe_parameters [rcl_interfaces/srv/DescribeParameters]
/teleop_turtle/get_parameter_types [rcl_interfaces/srv/GetParameterTypes]
/teleop_turtle/get_parameters [rcl_interfaces/srv/GetParameters]
/teleop_turtle/list_parameters [rcl_interfaces/srv/ListParameters]
/teleop_turtle/set_parameters [rcl_interfaces/srv/SetParameters]
/teleop_turtle/set_parameters_atomically [rcl_interfaces/srv/SetParametersAtomically]
/turtle1/set_pen [turtlesim/srv/SetPen]
/turtle1/teleport_absolute [turtlesim/srv/TeleportAbsolute]
/turtle1/teleport_relative [turtlesim/srv/TeleportRelative]
/turtlesim/describe_parameters [rcl_interfaces/srv/DescribeParameters]
/turtlesim/get_parameter_types [rcl_interfaces/srv/GetParameterTypes]
/turtlesim/get_parameters [rcl_interfaces/srv/GetParameters]
/turtlesim/list_parameters [rcl_interfaces/srv/ListParameters]
/turtlesim/set_parameters [rcl_interfaces/srv/SetParameters]
/turtlesim/set_parameters_atomically [rcl_interfaces/srv/SetParametersAtomically]

You can also search for all services of a specific type. For instance, if we want to find all the services with a type of Empty, we would run:

ros2 service find std_srvs/srv/Empty

Which returns:

/clear
/reset

Earlier we saw how we can publish messages to a topic from the CLI. Well, we can also call services from the CLI, but we need to know the structure of the input arguments first. We can get those with:

ros2 interface show <type_name>

For the Empty services, that would be:

ros2 interface show std_srvs/srv/Empty

Which unsurprisingly returns an empty set of input arguments:

---

Kinda boring. Let’s try with a service type that does have some input parameters, like the /spawn service we played with earlier:

ros2 interface show turtlesim/srv/Spawn

Here, we get some more interesting output:

float32 x
float32 y
float32 theta
string name # Optional.  A unique name will be created and returned if this is empty
---
string name

It appears the turtle needs an (optional) name, an (X,Y) position, and a “theta” (heading angle).

The information above the line indicates the arguments we need to include when calling the service, and the information below the line indicates any fields that will be included in the response.

Great! Now that we know how to determine the type (and arguments) for a service, let’s try calling /clear from the command line:

ros2 service call /clear std_srvs/srv/Empty

Which (of course) returns an empty response:

requester: making request: std_srvs.srv.Empty_Request()

response:
std_srvs.srv.Empty_Response()

If you had driven your turtle around a bit (creating a line), it should be gone now, after calling /clear.

Let’s also try calling /spawn, which does require some input arguments:

ros2 service call /spawn turtlesim/srv/Spawn "{x: 2, y: 2, theta: 0.2, name: 'turtle2'}"

Note: Again, we need to format the parameters using YAML syntax when calling a service that requires input arguments. We should see the following output:

requester: making request: turtlesim.srv.Spawn_Request(x=2.0, y=2.0, theta=0.2, name='turtle2')

response:
turtlesim.srv.Spawn_Response(name='turtle2')

And a new turtle should appear, like the cute little green one I see in the lower left-hand corner of the screen here:

New turtle in the turtlebot window

That’s it for services! Next, let’s take a look at parameters, which control a node’s settings.

ROS Parameters Link to heading

Parameters are configuration values associated with ROS nodes. Each node maintains its own set of parameters, which can be integers, floating-point values, strings, boolean values, and lists. We can use /turtlesim and /teleop_turtle to explore parameters. Start them both up with:

ros2 run turtlesim turtlesim_node

and

ros2 run turtlesim turtle_teleop_key

To see the parameters associated with all running nodes, execute this command:

ros2 param list

This will print out a list of the /turtlesim and /teleop_turtle parameters:

/teleop_turtle:
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  scale_angular
  scale_linear
  use_sim_time
/turtlesim:
  background_b
  background_g
  background_r
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  use_sim_time

Note: Every ROS node will have the parameter use_sim_time.

We can get more information about a parameter (including its datatype) with the get command. Its general format is:

ros2 param get <node_name> <parameter_name>

Since /turtlesim appears to have RGB parameters (one each for red, blue, and green), let’s try learning more about how one of these color parameters is defined:

ros2 param get /turtlesim background_b

This will return:

Integer value is: 255

So it’s an integer value. Unsurprisingly, it is set to 255 (maximum blue), as the background is currently blue (see screenshot below):

ROS “param get” command output

Using the CLI, it’s also possible to change parameter values at runtime, like this:

ros2 param set <node_name> <parameter_name> <value>

Let’s change the value for background_b by setting it to zero, which will remove all of the blue:

ros2 param set /turtlesim background_b 0

This will return Set parameter successful, and the background color will now change to something else (in my case mostly green… it depends on the current values for background_r and background_g):

New background color after running “ros2 param set”

Protip: Changing parameters in this way does not save them permanently, but it is possible to export your current configuration for later use. Read on.

Dumping parameters Link to heading

You can dump (output) all of a node’s current parameters with:

ros2 param dump <node_name>

Let’s say we really like our new /turtlesim background color and want to save it for later. We can dump it to a file with:

ros2 param dump /turtlesim > turtlesim.yaml

If we open up turtlesim.yaml in a text editor, we’ll see something like this:

/turtlesim:
  ros__parameters:
    background_b: 0
    background_g: 86
    background_r: 69
    qos_overrides:
      /parameter_events:
        publisher:
          depth: 1000
          durability: volatile
          history: keep_last
          reliability: reliable
    use_sim_time: false

We now have a copy of our parameters which we can load back in at a later time.

We can reload these parameters in the future with the load command (see the next section).

Loading parameters Link to heading

In the future, if we want to load our beautiful green background again, it’s easy to do:

ros2 param load /turtlesim turtlesim.yaml

The param load command is very “chatty”, and will happily tell us all about the parameters it is loading back in, like so:

Set parameter background_b successful
Set parameter background_g successful
Set parameter background_r successful
Set parameter qos_overrides./parameter_events.publisher.depth failed: parameter 'qos_overrides./parameter_events.publisher.depth' cannot be set because it is read-only
Set parameter qos_overrides./parameter_events.publisher.durability failed: parameter 'qos_overrides./parameter_events.publisher.durability' cannot be set because it is read-only
Set parameter qos_overrides./parameter_events.publisher.history failed: parameter 'qos_overrides./parameter_events.publisher.history' cannot be set because it is read-only
Set parameter qos_overrides./parameter_events.publisher.reliability failed: parameter 'qos_overrides./parameter_events.publisher.reliability' cannot be set because it is read-only
Set parameter use_sim_time successful

Note that it even provides warnings for parameters which cannot be set (read-only parameters).

Protip: Read-only parameters aren’t completely immutable. They cannot be changed after startup, but they can be set on startup. We’re unable to alter them here because the node is already running.

Loading parameters on startup Link to heading

If we were to stop the /turtlesim node and load the parameters at startup time, we would not get the warnings about “read-only” parameters (as they are modifiable at startup time). Let’s stop /turtlesim (with Ctrl + C) and then restart it. The general syntax for doing this is:

ros2 run <package_name> <executable_name> --ros-args --params-file <file_name>

The specific command to restart turtlesim in our case is therefore:

ros2 run turtlesim turtlesim_node --ros-args --params-file turtlesim.yaml

Behold! Our turtlesim window reopens, with our gorgeous green background:

Turtlesim window after “ros2 run” command with re-loaded parameters

Great! Now let’s go back to looking at communication between nodes, with actions.

ROS Actions Link to heading

Actions are for long-running tasks. They have 3 parts:

  1. Goal
  2. Feedback
  3. Result

Actions are built on top of topics and services. They work like services, except that an action can be canceled. From the docs:

An “action client” node sends a goal to an “action server” node that acknowledges the goal and returns a stream of feedback and a result.

Here’s a handy illustration (again stolen straight from the docs):

Action client and action server diagram

As usual, turtlesim can help us explore actions. Restart turtlesim and turtle_teleop_key with:

ros2 run turtlesim turtlesim_node

and

ros2 run turtlesim turtle_teleop_key

Notice how after starting turtle_teleop_key, the following message prints to the terminal:

Reading from keyboard
---------------------------
Use arrow keys to move the turtle.
Use G|B|V|C|D|E|R|T keys to rotate to absolute orientations. 'F' to cancel a rotation.
'Q' to quit.

The message in the middle corresponds to actions we can perform (i.e., commands that take time and return feedback while they are executing):

Use G|B|V|C|D|E|R|T keys to rotate to absolute orientations. ‘F’ to cancel a rotation.

Look at your keyboard. Notice how the letters G, B, V, C, D, E, R, and T form a “box” around F.

Each of these keys represents a particular heading (direction). Pressing one of these keys will cause the turtle to rotate until it is facing in the direction that matches the key’s position relative to ‘F’. For instance, to make our turtle face “up” (or “north”), we would hit R. Let’s try that. You’ll notice a message has printed out in the terminal where the /turtlesim node is running:

ROS action completed message

Also, the turtle is now facing “up”, as expected. You can also cancel an action before it is complete. Try pressing V, waiting for the turtle to start rotating, and then pressing F to cancel the action. You should see a message showing that the rotation was canceled, like this:

ROS action canceled message

What happens if you try to take another action, while a previous action is in progress? It depends. Action servers get to choose how to handle this situation. The action server could cancel (abort) the action that is in progress and immediately start the new action. It could also choose to complete the current action and then start the new action. Or it could abort the current action and do nothing. Or something else entirely! Be aware that the action server gets to choose what to do if multiple actions are requested.

Here, let’s try rotating the turtle to face “up” again by pressing R then immediately pressing V to get the turtle to rotate to face “down”. What happens?

ROS action aborted message

Here we can see that the server chose to abort the first action and immediately start executing the new action. I let that action run to completion, so I also see a “goal completed successfully” message.

Learning what actions we can take Link to heading

Each node provides a list of actions it supports. We can learn more about the actions /turtlesim supports with ros2 node info, like this:

ros2 node info /turtlesim

/turtlesim supports quite a few actions, so a fairly long list is printed out:

/turtlesim
  Subscribers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /turtle1/cmd_vel: geometry_msgs/msg/Twist
  Publishers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /rosout: rcl_interfaces/msg/Log
    /turtle1/color_sensor: turtlesim/msg/Color
    /turtle1/pose: turtlesim/msg/Pose
  Service Servers:
    /clear: std_srvs/srv/Empty
    /kill: turtlesim/srv/Kill
    /reset: std_srvs/srv/Empty
    /spawn: turtlesim/srv/Spawn
    /turtle1/set_pen: turtlesim/srv/SetPen
    /turtle1/teleport_absolute: turtlesim/srv/TeleportAbsolute
    /turtle1/teleport_relative: turtlesim/srv/TeleportRelative
    /turtlesim/describe_parameters: rcl_interfaces/srv/DescribeParameters
    /turtlesim/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
    /turtlesim/get_parameters: rcl_interfaces/srv/GetParameters
    /turtlesim/list_parameters: rcl_interfaces/srv/ListParameters
    /turtlesim/set_parameters: rcl_interfaces/srv/SetParameters
    /turtlesim/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
  Service Clients:

  Action Servers:
    /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute
  Action Clients:

The rotation actions we just tried are possible thanks to the action server /turtle1/rotate_absolute, near the end of the list above.

Of course, /teleop_turtle also provides information about actions:

ros2 node info /teleop_turtle

Which will return:

/teleop_turtle
  Subscribers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
  Publishers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /rosout: rcl_interfaces/msg/Log
    /turtle1/cmd_vel: geometry_msgs/msg/Twist
  Service Servers:
    /teleop_turtle/describe_parameters: rcl_interfaces/srv/DescribeParameters
    /teleop_turtle/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
    /teleop_turtle/get_parameters: rcl_interfaces/srv/GetParameters
    /teleop_turtle/list_parameters: rcl_interfaces/srv/ListParameters
    /teleop_turtle/set_parameters: rcl_interfaces/srv/SetParameters
    /teleop_turtle/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
  Service Clients:

  Action Servers:

  Action Clients:
    /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute

Near the end, we can see that /teleop_turtle provides a client with the same name: /turtle1/rotate_absolute. This action server calls turtlesim’s matching action client, also called /turtle1/rotate_absolute.

Listing all actions in the ROS graph Link to heading

We can get a full list of all available actions with:

ros2 action list

Which outputs:

/turtle1/rotate_absolute

It’s a short list, as we only provide one action between the /turtlesim and /teleop_turtle nodes (the rotate_absolute action).

Listing action types Link to heading

As with topics, we can learn about each action’s type with the -t flag, like this:

ros2 action list -t

Which will return:

/turtle1/rotate_absolute [turtlesim/action/RotateAbsolute]

Learning even more about a particular action Link to heading

We can get even more info about an action with the action info command, like so:

ros2 action info /turtle1/rotate_absolute

Which returns:

Action: /turtle1/rotate_absolute
Action clients: 1
    /teleop_turtle
Action servers: 1
    /turtlesim

Learning how to call an action server with interface show Link to heading

We can learn what parameters a call to an action server requires with the interface show command, like this:

ros2 interface show turtlesim/action/RotateAbsolute

Which will return:

# The desired heading in radians
float32 theta
---
# The angular displacement in radians to the starting position
float32 delta
---
# The remaining rotation in radians
float32 remaining

See how there are three sections delineated with ---? The first section is the structure of the goal request. The second section is the structure of the result. The last section is the structure of the feedback.

Sending a goal Link to heading

We can manually send a goal from the console. Let’s try that now. The command syntax is:

ros2 action send_goal <action_name> <action_type> <values>

Let’s try this now with the rotate_absolute action:

ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}"

Again, values should be passed as YAML-formatted strings, as above.

Our turtle will rotate to its new heading, and we will see the following output (or similar, it depends on your turtle’s initial heading):

Waiting for an action server to become available...
Sending goal:
     theta: 1.57

Goal accepted with ID: 2fa0c2c893674d1ba6ca4021f3aa7c58

Result:
    delta: -1.5520000457763672

Goal finished with status: SUCCEEDED

Note: All goals will have a unique ID.

If you want to see the feedback while the action is executing, add the --feedback flag:

ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: -1.57}" --feedback

We now get a much, much longer output (I have removed a chunk of the feedback for brevity):

Waiting for an action server to become available...
Sending goal:
     theta: -1.57

Goal accepted with ID: 0f473f00bdfa4dbbaf6cd227348c693f

Feedback:
    remaining: -3.122000217437744

Feedback:
    remaining: -3.1059999465942383


....Lots more of this (removed for brevity).....

Feedback:
    remaining: -0.03400003910064697

Feedback:
    remaining: -0.018000006675720215

Result:
    delta: 3.1040000915527344

Goal finished with status: SUCCEEDED

Again, actions are like services for long-running tasks, which provide feedback while the task is being executed and which can be canceled.

Viewing logs with rqt_console Link to heading

The rqt_console tool gives you a convenient way to collect, view, and filter log messages output by ROS nodes. It can also save collected logs to disk and reload them for inspection later.

Open a new terminal and run source /opt/ros/humble/setup.bash, then run:

ros2 run rqt_console rqt_console

The rqt_console GUI should load:

rqt_console GUI window

The topmost window is where new log messages will appear.

The middle window lets you filter messages based on the severity level (or by adding a custom filter with the “+” sign to the right).

The bottom window allows you to search through the logs by highlighting log entries that contain a specific string you have entered.

Open a new terminal and start up turtlesim, like so:

ros2 run turtlesim turtlesim_node

You should now have both turtlesim and rqt_console windows open:

rqt_console and turtlesim windows open

To start generating logs, we need to have our turtle do something. Let’s use pub to command the turtle to move, using the command below (taken straight from the docs):

ros2 topic pub -r 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

Let the turtle hit the wall, wait a few seconds, and then hit Ctrl+C in the terminal where you just ran ros2 topic pub. You should now see a series of log messages in the top window in the rqt_console GUI:

rqt_console log messages

ROS log messages are categorized according to their “severity”. The default severity levels are (from most to least severe):

Fatal
Error
Warn
Info
Debug

From the official docs:

  • Fatal messages indicate the system is going to terminate to try to protect itself from detriment.

  • Error messages indicate significant issues that won’t necessarily damage the system, but are preventing it from functioning properly.

  • Warn messages indicate unexpected activity or non-ideal results that might represent a deeper issue, but don’t harm functionality outright.

  • Info messages indicate event and status updates that serve as visual verification that the system is running as expected.

  • Debug messages detail the entire step-by-step process of the system execution.

In rqt_console, by default, only messages at a severity level of “Info” or above are printed. This means “Debug” messages will not be shown because they have a lower severity than “Info”.

We can exclude messages at one or more severity levels using the middle window. Click on “Warn” as shown in the screenshot below, to exclude all messages at the “Warn” severity level. The top window should now look much cleaner, because all the warnings about hitting the wall have been removed:

rqt_console warnings removed

It’s also possible to change the default logger level when you first run a node. Go ahead and close all your existing terminal windows, then re-open three new windows and run the commands below (each in its own window, and don’t forget to source /opt/ros/humble/setup.bash first!):

ros2 run rqt_console rqt_console

Then:

ros2 run turtlesim turtlesim_node --ros-args --log-level WARN

And finally:

ros2 topic pub -r 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

If you look at the output in rqt_console, you’ll notice the initial “Info” messages from when the turtle first launched are no longer there. That’s because turtlesim is now only logging messages at a severity of “Warn” or higher.

Launching multiple nodes at once Link to heading

Larger systems will often involve running and configuring multiple nodes. Starting them one at a time from separate terminals is tedious.

Enter the ros2 launch command. With the launch command, we can run multiple nodes at once.

Let’s continue using turtlesim, but we’ll launch multiple turtles at once:

ros2 launch turtlesim multisim.launch.py

What this actually does is run a Python script (the file multisim.launch.py). Here’s a copy of that code (taken from the docs):

# turtlesim/launch/multisim.launch.py

from launch import LaunchDescription
import launch_ros.actions

def generate_launch_description():
    return LaunchDescription([
        launch_ros.actions.Node(
            namespace="turtlesim1", package='turtlesim', executable='turtlesim_node', output='screen'),
        launch_ros.actions.Node(
            namespace="turtlesim2", package='turtlesim', executable='turtlesim_node', output='screen'),
    ])

You should see that two turtlesim windows open at once:

Two turtlesim windows

Pro-tip: Launch files can be written in Python, XML, or YAML format.

Let’s set both turtles in motion. For this part, we’ll still need two terminal windows (one for each of the two ros2 pub commands below):

ros2 topic pub /turtlesim1/turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"
ros2 topic pub /turtlesim2/turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -1.8}}"

Great! Our turtles are now making nice little circles, like so:

Circling turtles

Record and play back data with ros2 bag Link to heading

The ros2 bag command gives you a way to read data from ROS topics.

Following along with this page in the official ROS docs, we’ll start by setting up turtlesim and sending some keyboard commands to move the turtle around. We will use ros2 bag to see the commands sent by turtle_teleop_key. In two new terminal windows, run:

ros2 run turtlesim turtlesim_node

and then:

ros2 run turtlesim turtle_teleop_key

We also need a new folder to hold our bag files. In yet another terminal window, run:

mkdir bag_files
cd bag_files

Now we’ll list out our topics and choose one from which to record data:

ros2 topic list

This should return:

/parameter_events
/rosout
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose

We already know that turtle_teleop_key is going to publish to /turtle1/cmd_vel, so let’s use ros2 bag to monitor that topic:

ros2 bag record /turtle1/cmd_vel

The bag command should print out some INFO messages, like you see here:

ROS2 bag info messages

Return to the terminal where turtle_teleop_key is running and move the turtle around a little bit.

Because I want to play with the “replay” pattern later, I have tried to make a recognizable shape (a triangle). We’ll see later on whether or not we’re able to successfully replay the messages to /cmd_vel to get the same shape:

ROS2 bag info messages

Go back to the terminal where ros2 bag is running, and hit Ctrl+C to stop recording.

Note: The bag command will set up a new directory named using the pattern rosbag2_year_month_day-hour_minute_second. That directory will contain one or more .db3 files containing the messages recorded by bag, as well as a metadata.yaml file, like this:

ubuntu@ip-10-0-3-184:~/bag_files$ ls
rosbag2_2024_07_31-10_11_16
ubuntu@ip-10-0-3-184:~/bag_files$ ls rosbag2_2024_07_31-10_11_16/
metadata.yaml  rosbag2_2024_07_31-10_11_16_0.db3
ubuntu@ip-10-0-3-184:~/bag_files$ 

If you examine the metadata.yaml file, you’ll find something like this:

rosbag2_bagfile_information:
  version: 5
  storage_identifier: sqlite3
  duration:
    nanoseconds: 41450007446
  starting_time:
    nanoseconds_since_epoch: 1722420796324899867
  message_count: 6
  topics_with_message_count:
    - topic_metadata:
        name: /turtle1/cmd_vel
        type: geometry_msgs/msg/Twist
        serialization_format: cdr
        offered_qos_profiles: "- history: 3\n  depth: 0\n  reliability: 1\n  durability: 2\n  deadline:\n    sec: 9223372036\n    nsec: 854775807\n  lifespan:\n    sec: 9223372036\n    nsec: 854775807\n  liveliness: 1\n  liveliness_lease_duration:\n    sec: 9223372036\n    nsec: 854775807\n  avoid_ros_namespace_conventions: false"
      message_count: 6
  compression_format: ""
  compression_mode: ""
  relative_file_paths:
    - rosbag2_2024_07_31-10_11_16_0.db3
  files:
    - path: rosbag2_2024_07_31-10_11_16_0.db3
      starting_time:
        nanoseconds_since_epoch: 1722420796324899867
      duration:
        nanoseconds: 41450007446
      message_count: 6

Recording multiple topics Link to heading

It’s possible for bag to record multiple topics. From your bag_files directory, run the command below:

ros2 bag record -o subset /turtle1/cmd_vel /turtle1/pose

Move the turtle around some more, then return to the window where you ran ros2 bag and hit Ctrl + C. I chose to make another triangle:

Because we used -o subset, this time the directory where our bag files are stored will have the name subset, like this:

ubuntu@ip-10-0-3-184:~/bag_files$ ls -1
rosbag2_2024_07_31-10_11_16
subset
ubuntu@ip-10-0-3-184:~/bag_files$ ls -1 subset/
metadata.yaml
subset_0.db3
ubuntu@ip-10-0-3-184:~/bag_files$ 

Learn about your bag files with bag info Link to heading

We can learn about the contents of a bag file with bag info. Let’s ask the bag info command to tell us about subset:

ros2 bag info subset

Note: You need to be in the directory where the subset directory is located (which should be bag_files, if you have been following along).

The bag info command should print out something like this:

Files:             subset_0.db3
Bag size:          493.4 KiB
Storage id:        sqlite3
Duration:          126.206s
Start:             Jul 31 2024 10:29:31.451 (1722421771.451)
End:               Jul 31 2024 10:31:37.657 (1722421897.657)
Messages:          7895
Topic information: Topic: /turtle1/cmd_vel | Type: geometry_msgs/msg/Twist | Count: 6 | Serialization Format: cdr
                   Topic: /turtle1/pose | Type: turtlesim/msg/Pose | Count: 7889 | Serialization Format: cdr

Replay messages with bag play Link to heading

Close the terminal where teleop_key is running (or hit Ctrl + C in that terminal window). Next, return to the window where you’ve been running your ros2 bag commands, and run:

ros2 bag play subset

You might notice a significant delay before your turtle starts moving, and the play command might not exit for some time after the turtle stops moving.

This is because we also recorded the pose topic, which publishes constantly, even when the turtle is not moving.

Take a look at the output from bag info above. Notice that only 6 movement messages were sent, but 7895 pose messages were sent. How much time does that represent? How long will it take for bag play to finish replaying all the messages? First, we need to work out how often the pose topic publishes messages:

ros2 topic hz /turtle1/pose

This will return something like:

average rate: 62.465
	min: 0.015s max: 0.016s std dev: 0.00039s window: 64

So this topic is publishing roughly 60 messages a second. At 7895 messages, our bag play command is going to need about 130 seconds (just over 2 minutes) to finish.

If you wait, you’ll see your robot move again, as mine has done here:

The turtle, moving again

Next up Link to heading

Great! We’ve covered all the ROS 2 CLI basics. You should now be able to:

  • Understand how to set up the ROS 2 Humble environment
  • Understand nodes, topics, services, parameters, and actions at a high level
  • View the ROS graph with rqt_graph
  • View logs with rqt_console
  • Interact with ROS nodes using rqt
  • Launch nodes with ros2 launch
  • Record and play back data with ros2 bag

In future blog posts, we’ll cover how to do these things in ROS 1 Noetic, as well as talk about how to set up simulated worlds (and simulated robots) with Gazebo.