ROS2 topics

You learned how to use ROS2 packages to start one or several nodes. You also learned how to create your own ROS2 programs with Python. In this article, you will learn how to subscribe to a Topic and how to publish to a Topic.

There are many sources covering ROS2 Topics such as the official ROS2 documentation.

What are Topics?

As already mentioned earlier, a Topic is a way of communication between ROS2 nodes. This protocol created a data stream from a Publisher to a Subscriber. It is possible that several Publishers are sending data to a Topic at the same time and several Subscribers can listen to a Topic simultaneously.

Each Topic consists of a Topic name and a message type. The name is used to refer to a specific Topic while the message type defines the actual structure of the content. A fairly common Topic name is /cmd_vel which contains a Twist message. Twist messages describe the three velocity parameters for the translation and rotation of a robot. Twist belongs to a category of ROS2 messages called geometry_msgs. This is simply the ROS2 package that contains these message definitions. Twist is defined as follows:

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

Note: You can find more references to the geometry_msgs Twist messages here and here.

This means that you can access the properties of a Twist object in the following way in Python:

my_message = Twist()

my_message.linear.x = 0
my_message.linear.y = 0
my_message.linear.z = 0
my_message.angular.x = 0
my_message.angular.y = 0
my_message.angular.z = 0

First, you define the name of the variable and set it to the variable type of Twist() which is a constructor that creates a Twist object. It initializes all the values to zero.

Topics can also be less complex data types such as Int or String which then only contain a simple integer or string value. These message types belong to the ROS package called std_msgs. Another very common type is sensor_msgs for IMU data, camera data or laser scanner data.

Using Topics in Terminal

In case you only want to see the content of a topic or see what topics are available, you don’t need to write a ROS2 program to listen to a Topic. You can do this in the terminal as well. You can even publish some data into a Topic. This is mainly used for testing purposes and not really used for actual robot control.

Starting TurtleSim

Open a terminal and start the turtlesim node with the following command:

$ ros2 run turtlesim turtlesim_node

A new window with the turtlesim application will appear.

Starting turtlesim node

The first thing you need to know is, how to find which Topics are already used by a robot. This is useful as you can use the Topics that are already available rather then creating a new Topic even though, it is not necessary.

Finding Information about the Topics

The following command (again in a new terminal) will show you a list of the Topics that are either being published or subscribed to by a node:

$ ros2 topic list

The output in your terminal should look like this:

Now, you know which topics are currently available. You can get more information about these Topics with the following command:

$ ros2 topic info /turtle1/pose

This command will provide the following information:

The information you get is that this topic is of type turtlesim/msg/Pose which means it is a message type inside the package called turtlesim. The message type is Pose and it contains the following information:

float32 x
float32 y
float32 theta

float32 linear_velocity
float32 angular_velocity

The Pose messages contain information about the current position and orientation of the turtle and the linear and angular velocity. This block of information is published by the turtlesim regularly.

Subscriber

So the first thing you want to learn is how to see what is inside a Topic. Let’s take /turtle1/pose for example. You can listen to this Topic by using your terminal with the following command:

$ ros2 topic echo /turtle1/pose

Now, you will see something like the following:

You can stop incoming messages by hitting CRTL+c on your keyboard. The Topic that you are looking at is showing you the position of the little turtle on the canvas.

Publisher

Just like you can listen to a Topic through the terminal, you can also write messages to a Topic through the terminal. Therefore, you can use the following command to write to /turtle1/cmd_vel:

$ ros2 topic pub /turtle1/cmd_vel geometry_msgs/Twist "{linear: {x: 0.25, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.25}}" -r 10

Your terminal will look like this:

TOS2 topic pub to publish cmd_vel messages

You will see that the terminal is sending messages to the turtlesim. In this case, it tells the turtle to move forward and to the left. As a result, the turtle is making a circle. The units in the command are given in m/s and rad/s. The general command is:

$ ros2 topic pub <topic_name> <message_type> "<topic_message>"

You might have noticed that we added a -r 10 at the end of the message. Many ROS2 commands have additional arguments that you can provide. The argument -r 10 instructs the command to repeat the message at a rate of 10 Hz.

Using the terminal is mainly used for quick verification or testing of a system or for a single event that doesn’t need repetition. For controlling a robot, you will probably write a program that will perform the same tasks autonomously.

More information on ROS2 message types can be found on the ROS2 overview page.

Using Topics in Python

Publisher

To start, you can go to the package you already made earlier:

$ cd ~/ros2_ws/src/my_turtlesim/my_turtlesim/

Now you can create a new file called my_simple_publisher.py in which you will write the Python code to create a Publisher node:

$ gedit my_simple_publisher.py

Add the following code to the file:

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist


class MySimplePublisher(Node):

    def __init__(self):
        super().__init__('my_simple_publisher')
        self.get_logger().info('Creating simple publisher!')
        self.publisher_ = self.create_publisher(Twist, 'turtle1/cmd_vel', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.publish_velocity_callback)
        self.i = 0

    def publish_velocity_callback(self):
        my_velocity = Twist()
        my_velocity.linear.x = 0.5
        my_velocity.angular.z = 0.5
        self.publisher_.publish(my_velocity)
        self.get_logger().info(f"Publishing velocity: \n\t linear.x: {my_velocity.linear.x}; \n\t linear.z: {my_velocity.linear.x}")
        self.i += 1


def main(args=None):
    # initiate ROS2
    rclpy.init(args=args)

    # create an instance of the node
    my_simple_publisher = MySimplePublisher()

    # keep the node alive intil pressing CTRL+C
    rclpy.spin(my_simple_publisher)
    
    # destroy the node when it is not used anymore
    my_simple_publisher.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

You start with importing the rclpy module, the Node class, and the Twist message as we will use this message to publish to the topic “/turtle1/cmd_vel”.

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist

Next, you need to create a class that inherits from the Node class object. Let’s call it MySimplePublisher. It initialises a ROS2 node to allow using ROS2 communication methods like topics. Next, we need a publisher object that we call self.publisher. It defines the topic name, the message type and the queue size. Since we want the node to publish the messages at a regular time interval, we define a timer that waits for 0.5 seconds and then we create a timer that triggers a callback function based on this 0.5 seconds.

class MySimplePublisher(Node):

    def __init__(self):
        super().__init__('my_simple_publisher')
        self.get_logger().info('Creating simple publisher!')
        self.publisher_ = self.create_publisher(Twist, 'turtle1/cmd_vel', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.publish_velocity_callback)
        self.i = 0

A callback is a function that is triggered by an event rather than a specified sequence in the program code. In this case, the timer will trigger the callback function every 0.5 seconds, or at a frequency of 2 Hz.

This callback function then creates a message of the type Twist and defines the value for the forward direction (linear.x) and the rotation to the left (angular.z). Lastly, the callback publishes the Twist message and writes a message to the terminal.

    def publish_velocity_callback(self):
        my_velocity = Twist()
        my_velocity.linear.x = 0.5
        my_velocity.angular.z = 0.5
        self.publisher_.publish(my_velocity)
        self.get_logger().info(f"Publishing velocity: \n\t linear.x: {my_velocity.linear.x}; \n\t linear.z: {my_velocity.linear.x}")
        self.i += 1

Now, in the main function of the program, you initialise the ROS2 library and create an instance of your custom node class. The rclpy.spin() function makes sure that this instance keeps running until it gets shut down.

def main(args=None):
    # initiate ROS2
    rclpy.init(args=args)

    # create an instance of the node
    my_simple_publisher = MySimplePublisher()

    # keep the node alive intil pressing CTRL+C
    rclpy.spin(my_simple_publisher)
    
    # destroy the node when it is not used anymore
    my_simple_publisher.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

You need to make the Python program executable before you can build the package again and run it:

$ chmod +x my_simple_publisher.py

Also, you need to add the new node to the setup.py file. Make sure the entry_point looks as follows:

    entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
            'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
        ],
    },

To build the package, go to the root directory of your workspace and build it:

$ cd ~/ros2_ws/
$ colcon build --packages-select my_turtlesim --symlink-install

This time, we use two additional flags compared to the last time we built the workspace. --packages-select <package_name> allows you to build only a single package. In this case, it does not affect anything, since we only have a single package in our workspace, but if you have multiple packages, it allows you to build only the ones that you are currently interested in. The flag --symlink-install allows you to modify the Python code without rebuilding the package. By default, colcon makes a copy of the source code and then runs that copy when executing the program. Since this package contains Python files, it should be possible to change the code and run it without building the package. This flag makes this possible. This allows us to quickly make changes to the code.

The package should have been built without issues.

Building the ROS2 topic publisher

Before running the new node, you need to source the workspace again:

$ source install/local_setup.bash

Make sure you have a turtlesim node running. If not, you can start one with the command:

$ ros2 run turtlesim turtlesim_node

In a separate terminal, you can start your publisher node with the following command:

$ ros2 run my_turtlesim my_simple_publisher.py

You will notice that the turtle will start to move in a circle.

Simple publisher making the turtle move

You can also verify the published messages with the following command:

$ ros2 topic echo /turtle1/cmd_vel

This command will show you the messages sent to the topic /turtle1/cmd_vel, which are the Twist messages that your my_simple_publisher.py program is publishing. The command above will show the following:

Subscriber

When making a robotic system, you are more likely to create a ROS2 node that will take the role of a Subscriber. This has the advantage that you can automatically listen to a Topic and then act depending on the data your program receives.

Let’s make a node that listens to the “/turtle1/pose” topic to get the position of the turtlesim.

To start, you can go to the package you already made earlier:

$ cd ~/ros2_ws/src/my_turtlesim/my_turtlesim/

Now you can create a new file called my_simple_subscriber.py in which you will write the Python code to create a Subscriber node:

$ gedit my_simple_subscriber.py

Now, an empty text editor window will pop up where you can type down the following code:

import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose


class MySimpleSubscriber(Node):

    def __init__(self):
        super().__init__('minimal_subscriber')
        self.my_subscriber = self.create_subscription(
            Pose,
            'turtle1/pose',
            self.listener_callback,
            10)
        # prevent warning that self.my_subscriber is not used
        self.my_subscriber

    def listener_callback(self, msg):
        self.get_logger().info(f'Turtle found at x: {msg.x}; y: {msg.y}')


def main(args=None):
    rclpy.init(args=args)

    my_simple_subscriber = MySimpleSubscriber()
    rclpy.spin(my_simple_subscriber)

     # destroy the node when it is not used anymore
    my_simple_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

First, you need to import the rclpy and its Node class. Then, you also import the Pose message type.

import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose

You have to create a new class that inherits from the Node class. Let’s call it MySimpleSubscriber. In its init function, you need to give it a node name. Also, you can create a subscriber object that defines the topic message type, the topic name, the callback function and the queue size. This callback function gets triggered every time a new message is coming in from the “/turtle1/pose” topic. Here, we also call the self.subscriber object once so that the program will not complain about an unused variable.

class MySimpleSubscriber(Node):

    def __init__(self):
        super().__init__('minimal_subscriber')
        self.my_subscriber = self.create_subscription(
            Pose,
            'turtle1/pose',
            self.listener_callback,
            10)
        # prevent warning that se.fmy_subscriber is not used
        self.my_subscriber

The callback for the subscriber is very simple as we just want to print the message in the terminal, using the self.get_logger().info() function.

    def listener_callback(self, msg):
        self.get_logger().info(f'Turtle found at x: {msg.x}; y: {msg.y}')

In the main function, we can find the initialisation of the rclpy and the instance of our subscriber node class. Then, the node is kept alive with the rclpy.spin() function. Lastly, we clean up the node when the program is finished and close the program.

def main(args=None):
    rclpy.init(args=args)

    my_simple_subscriber = MySimpleSubscriber()
    rclpy.spin(my_simple_subscriber)

     # destroy the node when it is not used anymore
    my_simple_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Save the code and then make the file executable with the following command:

$ chmod +x ~/ros2_ws/src/my_turtlesim/my_turtlesim/my_simple_subscriber.py

Add the new so it will look as followsnode to the setup.py file:

    entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
            'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
            'my_simple_subscriber = my_turtlesim.my_simple_subscriber:main',
        ],
    },

You need to return to the workspace root directory to build the package once again. This is necessary since we added a new file. Also here, we can use the --symlink-install flag to allow changing the code later if necessary:

$ cd ~/ros2_ws/
$ colcon build --symlink-install

Now, source the package and run the new node:

$ source install/local_setup.bash
$ ros2 run my_turtlesim my_simple_subscriber.py

The subscriber will print the pose data of the turtle in the terminal. If this is not the case, make sure that the turtlesim node is running in another terminal.

My simple subscriber receiving the pose messages

Currently, the message will always be the same unless you make the turtle move. For this, you could use the publisher from earlier.

When using Python to access Topics, you can have two different kinds of programs: a Subscriber or a Publisher. In addition, you can also have a program that implements several Subscribers or several Publishers or even both.

A common use case is that a publisher is being called in the callback function of a subscriber. As a result, the node will send a new message for each incoming message. This can be handy when one topic contains information related to the subscribed topic. This could be an image filter or a node that verifies the distance towards an object.

That’s it. Now you are able to write nodes that can subscribe or publish to topics. In some cases, you need to combine both in a single program. For now, you can continue with Services and how to set up a Service Server and how to create a Service Client.

Creating ROS2 Packages

After you learned how to use a ROS2 package, you will learn how to create your own ROS2 package.

To start, first, make sure ROS2 Foxy is installed. If this is the case, you can activate the ROS2 Foxy installation with:

$ source /opt/ros/humble/setup.bash

Note: You need to repeat this command for each new terminal. If you write this command into your .bashrc file, you do not have to repeat it each time.

Create a Package

Your own ROS2 packages should all be placed inside your ROS2 workspace, which you probably named ros2_ws. ROS2 has its own command to create new packages. Open a terminal and type to enter your workspace’s source code directory:

$ cd ~/ros2_ws/src/

You can create a new ROS package with the following command:

$ ros2 pkg create <package_name>

Here, the <package_name> is the name of your package. Note that you can not easily change that name after you created it as you will need to modify the CMakeLists.txt file and the package.xml file.

Next to the package name, you will also need to specify whether it will be a C++ package or a Python package. For this, you need to add --build-type ament_python to the command. Let’s create a Python package called my_turtlesim:

$ ros2 pkg create --build-type ament_python my_turtlesim

It is a convention to name ROS2 packages with lowercase names combined with an underscore. This notation is also called snake case.

The terminal will show some details of what files are being created inside the new package.

Creating a new ROS2 Python Package

Enter the folder my_turtlesim. Here, you will find several files and directories. You can enter the package and show its content with the following commands:

$ cd ~/catkin_ws/src/my_turtlesim/
$ ls

The following picture shows you the output of the list command.

Content of a new ROS2 Python Package

You can see that the my_turtlesim package has the following folders and files:

  • my_turtlesim: contains source code for the ROS2 Python programs
  • package.xml: contains package-specific elements such as dependencies and author information
  • resource: for libraries files
  • setup.cfg: contains instructions to build the ROS2 package in general
  • setup.py: contains instructions to build the ROS2 package such as which programs to build and where the launch files are located
  • test: contains test files to test the code

You can create additional folders such as a launch folder for launch files or a config folder for configuration files containing ROS2 parameters.

Create a ROS Python Program

Now, you can create your first ROS2 program with Python. Enter the my_turtlesim directory with your terminal:

$ cd my_turtlesim/

You need to create a new file with the file extension .py in order to have a Python file. You can create a new file and open it with your terminal with the following command:

$ gedit my_first_program.py

You will see that a new window will open on your screen with an empty text file. This is where you will write your first ROS2 program in Python.

Write the following code in your file and save the file. This code will simply create a ROS2 node that displays a message I n the terminal. It does nothing else than that. This node is an absolute minimum working example for a ROS2 Python node.

import rclpy
from rclpy.node import Node


class MyFirstProgram(Node):

    def __init__(self):
        super().__init__('my_first_program')
        self.get_logger().info('Hello, World')


def main(args=None):
    rclpy.init(args=args)

    my_first_program = MyFirstProgram()

    my_first_program.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

To use the ROS2 tools inside a Python script, you need to import the ROS Client Library for Python with import rclpy.

Every ROS2 program is running as one or multiple ROS2 nodes. To create these nodes, you need to import the Node class from the rclpy module and create a class that inherits from the Node class.

class MyFirstProgram(Node):

    def __init__(self):
        super().__init__('my_first_program')
        self.get_logger().info('Hello, World')

Inside the class definition, you need to specify the name of the node. In this case, it is defined as my_first_program. The node name should give clear information on what the node is doing.

The main function will initiate the ROS2 module and read any ROS2 arguments. Then, it creates an instance of our custom Node class. Then, The code makes sure the node is closed properly after finishing its job and the program exits.

def main(args=None):
    rclpy.init(args=args)

    my_first_program = MyFirstProgram()

    my_first_program.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Before you can run your program, you need to make the Python file executable, this means giving this file permissions from your system to be executed as a program. Usually, files only have permission to be read as a file or to be modified. This is also known as read-write permission. To add the permission to execute the file, make sure you are in the my_turtlesim directory and type the following command in your terminal and press ENTER:

$ chmod +x my_first_program.py

Making Python files executable is required for each Python file you create but you only need to do this process once for each Python file.

Next, you need to tel the ROS2 build system, called colcon, which Python program to build. For this, go to the package root folder and open the file setup.py. Add the following piece of code into the entry_points={} part:

entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
        ],

Open the file with the following commands:

$ cd ~/ros2_ws/src/my_turtlesim/
$ gedit setup.py

The entire file should look as follows:

from setuptools import setup

package_name = 'my_turtlesim'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='dave',
    maintainer_email='dave@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
        ],
    },
)

Save the file and close it.

Add the new node to the setup.py file

When you build the package later, this line will make sure that the Python script will be seen as a valid ROS2 node with the name my_first_node which can be found inside the my_turtlesim package and the source code is saved in the file my_first_program.py.

Note: the node name and the Python file name are not the same in this example. Many ROS2 developers actually use the same name for both to keep their packages a little bit more organised. Here, the names are different to make it clear which name refers to which element.

After that, go to the main directory of the ros2_ws and build your package:

$ cd ~/ros2_ws/
$ colcon build

The terminal will show you some details about the build process such as which package is being built and how long it takes.

Colcon build your first ROS2 Python node

Next, you need to source this workspace so that ROS2 will take into account all the packages in this workspace:

$ source install/local_setup.bash

Now, everything is ready to run your new program. For this, use the following command:

$ ros2 run my_turtlesim my_first_node

The terminal should output the following text:

[INFO] [1663744547.799484529] [my_first_program]: Hello, World!

This is it, you just grated your first Python program in ROS2! Congratulations! Of course, this program is not doing anything useful yet. But this is the absolute minimum you need to run a proper ROS2 Python program.

Tip: you don’t need to type the entire command by yourself. If you start typing a word, you can double-tap the TAB key on your keyboard to auto-complete the commands in your terminal.

Note: if your computer doesn’t show your ROS2 package, first make sure you actually have spelt the names correctly and that the package actually exists. Also, if the package is new, ROS2 might not know about it yet. Therefore, type the following to list all the packages on your system, after that, ROS2 should be able to auto-complete the name of your package as well:

$ ros2 pkg list

The above command will list all the installed ROS2 packages on your computer. As ROS2 is going through the entire system, it will probably find your package and add it to its known packages.

Next, you will learn how to create a node that can actually interact with other ROS2 nodes by using ROS2 topics.

Using ROS2 Packages

The following article will explain what a ROS2 package is and how you can use them to run programs in ROS2. Make sure you have a basic understanding of ROS2 by looking at the overview page.

What are ROS Packages?

As mentioned earlier, every program in ROS2 is delivered as a package. A package can contain various types of files where the most important files are the CMakeLists.txt file and the package.xml file. These two files are automatically generated when you create a package. These files contain information about the package so it can be built, which means the source code can be compiled so that you can run the programs. This means, that packages usually also contain the source code of the programs you want to run.

Now that you have an idea of what a package is, you can see how you can run them.

Running a ROS2 package

Before running a ROS program, you need to have ROS2 installed as explained in the installation tutorial.

Activating ROS2

When you just open a terminal, you can’t use ROS2 right away. You have to source the ROS2 installation first. This needs to be done for each terminal that you use. This is done with the following command:

$ source /opt/ros/foxy/setup.bash

In the installation tutorial, this has already been explained and also that you can add this line to the .bashrc file. This file is executed each time you open a new terminal.

Note: this step is necessary as it is possible to have multiple ROS installations installed at the same time, such as ROS1 and ROS2. The computer needs to know which version you actually want to use.

Running a ROS2 program

Now, it is time to start a real ROS program. Therefore, you need to open a new terminal and then you can run a program with the following syntax:

$ ros2 run <ros2_package> <ros2_program>

Of course, the <ros2_package> and the <ros2_program> are placeholders and need to be replaced by an actual package and program name. For example, you can run the turtlesim program which is an animated 2D turtle that can be controlled with ROS commands just like a real robot.

$ ros2 run turtlesim turtlesim_node

Now, a little window should open on your screen and you should see a little turtle in the middle of a coloured canvas.

Next, you can interact with the turtle by starting another node by opening a new terminal and typing:

$ ros2 run turtlesim draw_square

The turtle will start moving in a square shape and it will draw a line on the canvas where it is moving:

At this point, you have two ROS programs running that interact with each other. As mentioned earlier, each ROS program is running as a node. These nodes can be visualized with a program called RQT. You can show these nodes with the following command in a new terminal:

$ ros2 run rqt_graph rqt_graph

The RQT graph will look as follows:

You have two nodes running and they communicate by using Topics. In the next article, you will learn how to create your own package and how you can simplify the process of starting multiple ROS2 programs at the same time.

Overview of ROS2

After you installed ROS2, you will learn how to actually use ROS2. Therefore, this page will give you an overview of what ROS2 does for you and how it works.

The Robot Operating System (ROS) is a meta-operating system that is installed on top of your actual operating system. In this case, you installed Ubuntu as your operating system.

ROS is released in different versions that go along with the current Ubuntu versions. The version discussed on this website is ROS2 Foxy which has been released for Ubuntu 20.04.

About ROS2

ROS2 is the second major version of ROS. It features some significant changes that seek to address some of the shortcomings of ROS. The two biggest changes are that ROS2 uses a single library, called ROS client library, or rcl, to enable all the features of ROS2. The C++ and Python libraries build on top of the rcl which means they use the exact same code. In ROS, the libraries roscpp and rospy were both completely independent. This means that both libraries needed to be maintained in parallel. With this new design, ROS2 allows to easily create more client libraries for other languages, such as Rust, Java or C# without rewriting all of the ROS2 functionality. The second most significant change is that ROS2 has been developed with multi-robot systems in mind. Unlike ROS which uses a single ROS maser for the robotic system, in ROS2, every node can be seen as its own ROS master that allows communicating with other nodes. As such, multiple robotic systems can easily interact with each other, without being limited to a single ROS master.

To simplify things, you can see ROS2 as a tool-box including libraries and programs to develop and run robots. Some of the libraries are the OpenCV library for computer vision tasks and the tf library for frame transformations. Some of the tools that are included are Gazebo for robot simulations and RVIZ2 for visualization of your robot. In addition to that, there are software packages that have been created for specific tasks such as robot navigation or motion planning which you can simply install as they are openly available. For more information about ROS2 Foxy, you can have a look at the ROS2 documentation pages.

ROS2 Structure

Programs within ROS2 are not simply started like other programs. Each ROS2 program is called a ROS2 node. Each node is usually built for one specific task such as navigation, reading sensor data or managing data and tasks. The nodes can communicate with each other to provide data to another node. The nodes can run with different frequency rates and they can even be executed asynchronously, which means one node can wait for the output of another node depending on the application. Having several nodes has the advantage that a failure that may crash a single node will not affect other nodes.

ROS2 nodes

As an example, imagine the robotic arm will have an error and causes its node to crash, the navigation and the vision nodes will still be running. Furthermore, ROS can then restart the single node that crashed and send a warning to an operator and write the crash into a log file.

The implementation of having nodes run in parallel is also a very easy way to implement multi-threading which means the processor can run many tasks at the same time instead of one task after another.

Communication between Nodes

When several nodes want to exchange data, they need to communicate with each other. ROS2 has three main methods of implementing communication between nodes: Topics, Services and Actions.

Topics

Topics are the most important type of communication in a ROS2 system. They provide a constant flow of data from one node to another. This can be compared with broadcast radio where there is a constant signal with audio information. The radio doesn’t wait for the radio station to send a signal and the station doesn’t care how many radios are listening.

ROS2 publisher and subscribers

In ROS2, such a broadcaster is called a Publisher node which is publishing to a Topic. A node that is listening to that Topic is called a Subscriber which subscribes to this Topic. The Topic is the actual data stream containing a specific type of information such as camera data or movement data.

In other words, Topics send data from a Publisher to a Subscriber in form of a data stream. This is important for applications that require constant data input. These data streams create a lot of data traffic within the ROS2 network which needs to be kept in mind when creating another Topic.

The most prominent Topic used in a robot is the /cmd_vel Topic that contains the velocity commands for the robot. These commands tell the robot where to go and at what speed.

For more information about Topics, you can have a look at the ROS2 documentation page.

Services

Services are less frequent than Topics but they can be very handy to reduce the overall data flow. Services do not provide a data stream, but instead, they work with asynchronous data requests similar to a web server hosting a website. When a person is calling a website, the computer sends a request to the server for receiving the website content. After the web server has executed its task, it will send the requested data to the user and the communication is finished.

A Service is also implemented by setting up a Service Server which will wait for a call while the Service Client will call this Server. The Server will then perform its task once and send a signal back to the Client to tell if it finished successfully or failed. That’s it, there is no more communication between both sites. Also, the Client will wait for the response from the Server and will only resume its task when it received a response.

An example of when to use a Service would be a drone that is taking of, or even landing. When taking off, the drone simply needs a single signal to start the takeoff process and it doesn’t require constant input from the controller. When the drone is up in the air, it will send a quick response to the controller to say that it has succeeded.

For more information about Services, you can have a look at the ROS2 documentation page.

Actions

Actions can be seen as a middle ground between a Topic and a Service. Just like a Service, there is an Action Server and a Client. Now the difference to a normal Service is that a Service Client is waiting for a response from the Server while an Action Client will continue with another task. On the Action Server, the task will be executed and the Action Server will send regular updates to the Client. These updates are less frequent than a Topic but they occur at a user-determined frequency.

In other words, with Actions, the Client doesn’t need to wait, but similar to a Service it will get a response when the Server has finished its task.

A good example of using an Action would be the action of a mechanical gripper. While it is closing, it will provide you with information on whether it is closed or not. Also, Actions are ideal for tasks that do not need a constant data stream but yet take more time which makes it impossible to simply wait for the result. If your robot shall drive and perform another task, you cannot allow the robot to wait for the feedback of a Service, therefore, an action would be ideal in this case as the robot is still capable of observing its environment while waiting for the result of the Action.

For more information about Actions, you can have a look at the ROS2 documentation page.

Combination of Topics, Services and Actions

Usually, you take the tools that are best suited for the job. In the case of ROS2, you have to choose whether to use a Topic, a Service or an Action. In a complex system, there are usually many different ways of communication used at the same time.

For example, then you want to control an autonomous drone, you will simply use a Service for a takeoff command while you will have a Topic that gives you information on the position and height of the drone. Once you worked with some ROS2 packages, you will get a feeling of when to use which type of communication.

Software Management

In ROS1, the entire ROS system is managed by something called the roscore which observes all the active nodes and manages all the communication done through Topics, Services and Actions. The roscore manages all the tasks in the background.

Since ROS1 is limited to a single roscore in the entire system, it only allows multiple robots when they share the same roscore. If two robots wanted to have their own roscore, it was necessary to connect them through third-party packages which adds complexity and decreases the overall stability of the platform.

Instead, ROS2 does not have a single central point that oversees the ROS communication but allows every single node to act as a roscore allowing to have multiple ROS2 nodes in a single system. This makes multi-robot systems much more simple and doesn’t require third-party packages anymore.

To enable communication between nodes, ROS2 uses the Data Distribution Service, short DDS, to allow multiple nodes to share and receive data messages within a single computer but also across the entire network. There are different DDS implementations, but this is a more advanced topic. The default system works fine for most use cases.

Programs in ROS

ROS2 has been rewritten as the ROS Client Library, short rcl. The rcl has been implemented in the C programming language. There are special libraries built on top of the rcl that allow using ROS2 in other languages as well such as the rclcpp for C++ programs and the rclpy for Python programs.

Before, ROS1 had two entirely separate libraries called roscpp and rospy. As a result, if there was a change made in the C++ version, the Python version needed to be updated separately, making it a double work. ROS2 avoids this as both, C++ and Python, use the exact same underlying code.

As an additional feature, the use of the rcl also allows to easily create more libraries for other languages such as Java, Go or Rust, without rewriting the entire library.

C++ is more resource-efficient and the code runs faster as it is a rather low-level programming language and because it is compiled (this means, the source code is converted into machine code) before the execution. Python on the other hand is slower as it is an interpreted language but this makes it faster to test the code as no compilation is required. If you want to learn more about how to program with Python, have a look at this programming guide!

The examples on this website are all in Python to make it easier for beginners to get started.

Packages in ROS

ROS2 is built to be modular. This means that the software is split into several packages that can interact with each other. usually, there are packages for navigation, sensing the environment, mapping the environment, computer vision and general control of the robot. Having several packages makes it easy to share the functionality of one package across several different robots. At the same time, when you create a new package, you only need to program the parts that are not already implemented in another package. To increase reusability, it is important to keep your code robot-agnostic which means the code should work for all situations and not only for this very specific robot. If the package works despite it doesn’t know which robot it is running on, it is more modular and can be shared with colleagues across the world or simply within the robotics group that you are working with.

In the next section, you will learn how to handle ROS2 packages.

Installing ROS2 Foxy

The Robot Operating System, or ROS, is a so-called meta-operating system for robot development. Basically, it is a framework including libraries, tools, modules and complete solutions to make a robot do what you want.

ROS2 is the second major version of ROS. It features some significant changes that seek to address some of the shortcomings of ROS. More details can be found in the Overview of ROS2 page.

Installing ROS2

The easiest and most reliable way to install ROS2 on your machine is to install it on an Ubuntu based system. Make sure you have Ubuntu installed on your system before you proceed as this guide will be based on Ubuntu. The instructions provided in this guide are from the official ROS2 Foxy installation.

Start your computer and log in. You will see the Ubuntu desktop.

Ubuntu 20.04 Desktop

Open a web browser, like Firefox, and search for the ROS2 Foxy installation website.

Google search for “ROS2 Foxy installation”

Go to the ROS2 website: https://docs.ros.org/en/foxy/Installation.html.

Installation page for ROS2 Foxy

Under “binary packages”, chose the link for “Debian packages (recommended)”. This brings you to the following website: https://docs.ros.org/en/foxy/Installation/Ubuntu-Install-Debians.html.

Installation page for ROS2 Foxy binary packages

Note: The following sections will use some common commands for the bash shell. One of them is sudo which is the way to tell bash to run the given command with administrator rights. This will usually require you to type in your password. Another common command is apt which is calling the package manager. The package manager is responsible for installing, updating and removing software from the official repositories on your system.

Open a terminal by pressing the super key, also known as the Windows key, and type “terminal”. Then press enter or click on the terminal icon.

Open a new terminal

A new window should open with a terminal prompt.

New terminal prompt opened

Now, you are ready to start the installation process. Note that you can paste the code with CTRL+SHIFT+V instead of only CTRL+V.

Note: The commands that you need to copy will be shown with an $ in-front. This $ sign should not be copied when you simply copy and paste the commands. The $ shows to the user that these commands should be used as a non-root user. If you are supposed to be logged in as a root user (with administrative rights), the # sign will be placed instead.

Setting locales

$ locale # check for UTF-8

This gives the following output:

dave@roboshack:~$ locale # check for UTF-8
LANG=en_US.UTF-8
LANGUAGE=<br>LC_CTYPE="en_US.UTF-8"<br>LC_NUMERIC=de_LU.UTF-8
LC_TIME=de_LU.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=de_LU.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=de_LU.UTF-8
LC_NAME=de_LU.UTF-8
LC_ADDRESS=de_LU.UTF-8
LC_TELEPHONE=de_LU.UTF-8
LC_MEASUREMENT=de_LU.UTF-8
LC_IDENTIFICATION=de_LU.UTF-8
LC_ALL=

Then install the locales for your country:

$ sudo apt update && sudo apt install locales
$ sudo locale-gen en_US en_US.UTF-8

This gives the following output:

dave@roboshack:~$ sudo locale-gen en_US en_US.UTF-8
Generating locales (this might take a while)…
en_US.ISO-8859-1… done
en_US.UTF-8… done
Generation complete.

Then continue to set the locale settings:

$ sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
$ export LANG=en_US.UTF-8
$ locale # verify settings
Verify locale settings

Setup sources

Next, you need to set the sources for the ROS2 repositories.

$ apt-cache policy | grep universe

This gives the following output:

dave@roboshack:~$ apt-cache policy | grep universe
500 http://security.ubuntu.com/ubuntu focal-security/universe i386 Packages
release v=20.04,o=Ubuntu,a=focal-security,n=focal,l=Ubuntu,c=universe,b=i386
500 http://security.ubuntu.com/ubuntu focal-security/universe amd64 Packages
release v=20.04,o=Ubuntu,a=focal-security,n=focal,l=Ubuntu,c=universe,b=amd64
100 http://lu.archive.ubuntu.com/ubuntu focal-backports/universe i386 Packages
release v=20.04,o=Ubuntu,a=focal-backports,n=focal,l=Ubuntu,c=universe,b=i386
100 http://lu.archive.ubuntu.com/ubuntu focal-backports/universe amd64 Packages
release v=20.04,o=Ubuntu,a=focal-backports,n=focal,l=Ubuntu,c=universe,b=amd64
500 http://lu.archive.ubuntu.com/ubuntu focal-updates/universe i386 Packages
release v=20.04,o=Ubuntu,a=focal-updates,n=focal,l=Ubuntu,c=universe,b=i386
500 http://lu.archive.ubuntu.com/ubuntu focal-updates/universe amd64 Packages
release v=20.04,o=Ubuntu,a=focal-updates,n=focal,l=Ubuntu,c=universe,b=amd64
500 http://lu.archive.ubuntu.com/ubuntu focal/universe i386 Packages
release v=20.04,o=Ubuntu,a=focal,n=focal,l=Ubuntu,c=universe,b=i386
500 http://lu.archive.ubuntu.com/ubuntu focal/universe amd64 Packages
release v=20.04,o=Ubuntu,a=focal,n=focal,l=Ubuntu,c=universe,b=amd64

Here, the last line shows that the “universe” repository is already available. If this is not the case, you would need to add them. For this, have a look at the official instructions:

500 http://lu.archive.ubuntu.com/ubuntu focal/universe amd64 Packages
release v=20.04,o=Ubuntu,a=focal,n=focal,l=Ubuntu,c=universe,b=amd64

Run the following command:

$ sudo apt update && sudo apt install curl gnupg2 lsb-release

This will cause the terminal to prompt for input. Type “y” to confirm and hit enter. This will install the programs curl, gnupg2 and lsb-release.

Install curl and gpnug2

Next, use the following command which will add the gpg keys to your systems keyring. This allows your computer to make sure it only uses verified packages:

$ sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg

Now, the respective repositories will be added to the repository list so that ROS2 packages can be installed through apt install.

$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

Install ROS2 packages

First, update the systems package list and then upgrade the existing packages:

$ sudo apt update
$ sudo apt upgrade

This step can take a while, depending on the number of programs you already have installed on the system.

Then, install the ros2-foxy-desktop package:

$ sudo apt install ros-foxy-desktop python3-argcomplete

This step will take up to 20 minutes to install all the important ROS2 packages for the base installation for desktop use. The desktop version will also install the tools necessary for visualisation and for simulation.

Ros2 Foxy installation progress

Test your installation

To test if the installation was successful, use:

$ source /opt/ros/foxy/setup.bash
$ ros2 run turtlesim turtlesim_node

The turtlesim window should open up.

Now, you can close the turtlesim node with CTRL+C.

Setup workspace

If you want to create your own code, you need to create a workspace for your ROS2 projects. Create an empty folder and call it ros2_ws or give it any other name you want. At the same time, inside this new directory, you need to create a folder called src that will contain the source code of your packages:

$ mkdir -p ~/ros2_ws/src/

Before you can build your workspace, you need to install the colcon build tools:

$ sudo apt install python3-colcon-common-extensions

Colcon is a build tool that allows to build multiple packages with one command and takes care of the dependencies so that the packages are built in the correct order.

Now, you can enter the ros2_ws directory and build the workspace:

$ cd ros2_ws
$ colcon build

Now, after you have run the colcon build command, three new directories have been generated: build, install and log. The build directory contains temporary files necessary to create the binaries of the compiled packages. The install directory contains the final binaries after compilation. The log directory contains logged information about the build process.

Colcon build output

To use this workspace and its packages, you need to make it active by sourcing it:

$ source install/local_setup.bash

There is also a file called install/setup.bash which also does the same, except that it also sources the /opt/ros/foxy/setup.bash file.

For each new terminal that you open, you will need to source the workspace. This is not very convenient, therefore, add the source command to the .bashrc file:

$ echo "source /opt/ros/foxy/setup.bash && source ~/ros2_ws/install/local_setup.bash" >> ~/.bashrc

Setup ROS environment

Since ROS2 has been developed with multi-robot systems in mind, by default, you will be able to see all ROS2 nodes in the same network. If two nodes have the same name, they can create issues and unwanted behaviour. This can either be that two nodes publish to the same topic even though you do not want this, or one node makes the other crash and then a colleague might be confused why his robot stops working.

To avoid this, you can make ROS2 listen only to nodes on your computer:

$ echo "export ROS_LOCALHOST_ONLY=1" >> ~/.bashrc

Additionally, it is possible to work on different Domain IDs. This will define on which ports your ROS2 instance will look into when checking for other nodes. If two computers have different DomainIDs they will not be able to see each other. On the other hand, when the option ROS_LOCALHOST_ONLY is set to 0, two computers with the same Domain ID can see each other, as long as they are in the same network. The default Domain ID is zero:

$ echo "export ROS_DOMAIN_ID=0" >> ~/.bashrc

The next step is to create your first ROS2 node.