[ROS2 How-to] Install third-party Python packages using ROS2 #5

[ROS2 How-to] Install third-party Python packages using ROS2 #5

What we are going to learn

  1. How to use rosdep
  2. How to use apt and apt-get
  3. How to create a virtual environment and install packages inside

List of resources used in this post

  1. Use this rosject: https://app.theconstructsim.com/l/4fefbbab/
  2. The Construct: https://app.theconstructsim.com/
  3. ROS2 Guide: https://docs.ros.org/en/humble/How-To-Guides/Using-Python-Packages.html
    1. https://github.com/ros/rosdistro/blob/master/rosdep/base.yaml
    2. https://github.com/ros/rosdistro/blob/master/rosdep/python.yaml
  4. ROS2 Courses –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61
    3. ROS2 Navigation training: https://www.theconstruct.ai/ros2-navigation-training/

Overview

Software usually builds on other software. When you are creating a ROS2 Package, for example, you have to specify the packages that the created package depends on.

In this post, we are going to talk about different methods that you can use to properly handle package dependencies in ROS2.

ROS Inside!

ROS inside

ROS inside

Before anything else, in case you want to use the logo above on your own robot or laptop, feel free to download it for free and attach it to your robot. It is really free. Find it in the link below:

ROS Inside logo

 

Opening the rosject

In order to learn how to install third-party Python packages using ROS2, we need to have ROS2 installed in our system, and it is also useful to have some simulations. To make your life easier, we already prepared a rosject for that: https://app.theconstructsim.com/l/4fefbbab/.

You can download the rosject on your own computer if you want to work locally, but just by copying the rosject (clicking the link), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject (below you have a rosject example).

Learn ROS2 Parameters - Run rosject

Installing Third-Party Python packages in ROS2 – Run rosject (example of the RUN button)

After pressing the Run button, you should have the rosject loaded. Let’s now head to the next section to really get some real practice.

Installing packages via rosdep

The rosdep command helps you install external dependencies in an OS-independent manner.

In order to use rosdep, we need a terminal. Let’s open one:

Open a new Terminal

Open a new Terminal

Ok, in this rosject, we have already some packages. They were created with the following commands (you don’t need to run those commands since the package already exists):

cd ~/ros2_ws/src

ros2 pkg create --build-type ament_python my_new_ros2_numpy_pkg --dependencies rclpy std_msgs geometry_msgs python3-numpy

In the command above, we created the my_new_ros2_numpy_pkg package that depends on rclpy, std_msgs geometry_msgs, and python3-numpy.

You can see the command by running the commands below:

cd ~/ros2_ws/src

ls

 

You can now see the content of the package.xml file of that package using the command below:

cat ~/ros2_ws/src/my_new_ros2_numpy_pkg/package.xml

 

After checking that file, let’s pay attention to the following line:

<depend>python3-numpy</depend>

Do you remember that we specific python3-numpy as a dependency of our package when we created it?

You can also add a new dependency to an existing package by manually editing the package.xml file.

All right, now that our package is created, let’s install its dependencies using rosdep. For that, let’s run the following commands in the first terminal:

cd ~/ros2_ws/src

source ~/ros2_ws/install/setup.bash

sudo apt-get update

rosdep install --from-paths ~/ros2_ws/src --ignore-src -r -y

 

The last command above installs all the packages that are not yet installed in our system and are required by the packages in your workspace.

If the message says no package was installed, that is because all dependencies are already installed in the system, but you should have seen at least the package python3-websockets installed.

That python3-websockets package is a dependency of our second package called my_new_ros2_websockets_pkg in our workspace, which was created using the following commands

cd ~/ros2_ws/src

ros2 pkg create --build-type ament_python my_new_ros2_websockets_pkg --dependencies rclpy python3-websockets

 

Packages that can be installed with rosdep

The list of packages that can be installed using rosdep can be found in the following links:

    1. https://github.com/ros/rosdistro/blob/master/rosdep/base.yaml
    2. https://github.com/ros/rosdistro/blob/master/rosdep/python.yaml

 

Installing packages using a package manager (apt, apt-get)

We just learned how to install packages using rosdep. Another way of installing dependencies is by using a package manager directly. If you are using Linux Ubuntu, for example, you can use apt or apt-get, which are command-line tools that can be used to install and manage packages.

You can use the following command to list the packages installed with apt;

apt list --installed

 

The output would be something similar to the following:

...
xxd/jammy,now 2:8.2.3995-1ubuntu2 amd64 [installed,automatic]
xz-utils/jammy,now 5.2.5-2ubuntu1 amd64 [installed,automatic]
yaru-theme-gnome-shell/now 21.10.2 all [installed,upgradable to: 22.04.4]
yaru-theme-gtk/jammy,jammy,now 22.04.4 all [installed,automatic]
yaru-theme-icon/jammy,jammy,now 22.04.4 all [installed,automatic]
yaru-theme-sound/jammy,jammy,now 22.04.4 all [installed,automatic]
yelp-xsl/jammy,jammy,now 42.0-1 all [installed,automatic]
yelp/jammy,now 42.1-1 amd64 [installed,automatic]
zenity-common/jammy-updates,jammy-updates,now 3.42.1-0ubuntu1 all [installed,automatic]
zenity/jammy-updates,now 3.42.1-0ubuntu1 amd64 [installed,automatic]
zip/jammy,now 3.0-12build2 amd64 [installed,automatic]
zlib1g-dev/jammy,now 1:1.2.11.dfsg-2ubuntu9 amd64 [installed,automatic]
zlib1g/jammy,now 1:1.2.11.dfsg-2ubuntu9 amd64 [installed,automatic]
zstd/jammy,now 1.4.8+dfsg-3build1 amd64 [installed,automatic]

 

If you want to see if a given package named xml is installed, you would run the command below:

apt list --installed| grep libxml

which would should packages that contain the word xml:

libxml-parser-perl/jammy,now 2.46-3build1 amd64 [installed,automatic]
libxml-twig-perl/jammy,jammy,now 1:3.52-1 all [installed,automatic]
libxml-xpathengine-perl/jammy,jammy,now 0.14-1 all [installed,automatic]
libxml2/jammy-updates,jammy-security,now 2.9.13+dfsg-1ubuntu0.1 amd64 [installed,automatic]
libxmlb2/jammy,now 0.3.6-2build1 amd64 [installed,automatic]

 

Another way of finding installed packages is by calling dpkg directly.

sudo dpkg -l | grep xml

 

If you know the name of the package you wish to install, you can install it by using this syntax:

sudo apt-get install <package1> <package2> <package3>

 

Replace the <package> with your desired package name. Example:

sudo apt-get install -y python3-pandas

 

Sometimes, it may happen that apt or apt-get install will fail and you will get this error message:

Package 'python3-pandas' has no installation candidate

when this happens, usually, running sudo apt update before the apt install command can solve the issue:

sudo apt-get update

sudo apt-get install python3-pandas

 

Installing packages using pip and virtualenv

Instead of installing python dependencies system-wide, you can install them in a virtual environment, so that you can have different versions of a given package being used by different projects, yet they do not conflict. For that, we need to use virtualenv.

To check if virtualenv is installed, we can use the following command:

virtualenv --version

If you have an error like the following, it means virtualenv is not installed:

bash: virtualenv: command not found

 

In order to install virtualenv, we would use the following commands:

sudo apt update

sudo apt install python3-virtualenv

 

Once virtualenv is installed, we can create our virtual environment this way:

cd ~

mkdir -p ~/ros2_ws_venv/src

cd ~/ros2_ws_venv

virtualenv -p python3 ./venv

 

If everything went well, you should now have a python virtual environment in the ~/ros2_ws_venv folder.

You can now activate your environment with the following command:

source ./venv/bin/activate

After you activate the environment, your command prompt will change to show that you are in a Python virtual environment, so, instead of having user:~/ros2_ws_venv$  in your terminal, you would see something like this:

(venv) user:~/ros2_ws_venv$

 

Ignoring the venv folder  (virtual environment) when compiling our ros2 workspace

In order to tell ROS2 not to consider our venv folder when compiling our workspace, we just create a file named COLCON_IGNORE in the folder that we want to ignore.

Example:

touch ~/ros2_ws_venv/venv/COLCON_IGNORE

 

All right. In the previous section we activated our environment with:

cd ~/ros2_ws_venv

source ./venv/bin/activate

Now, we can install Python packages in this environment just like we did before. Example:

python3 -m pip install torch

Now you can build your workspace and run your python node that depends on packages installed in your virtual environment.

Let’s compile our workspace:

cd ~/ros2_ws_venv

colcon build

 

Now your packages can use the Python packages installed in your virtual environment, just like if they were installed system-wide.

As long as your virtual environment is activated, you can install packages into that specific environment and you will be able to import and use those packages in your application.

Bear in mind that you always need to have the virtual environment activated when you want o use it.

To exit/deactivate the virtual environment, you can run the following command:

deactivate

 

Congratulations on reaching the end of this post. I hope it was useful for you. If you want a live version of this post, please check the video in the next section.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

How to pass ROS2 arguments to a ROS2 Node via the command line

How to pass ROS2 arguments to a ROS2 Node via the command line

What we are going to learn

  1. How to put a node inside a namespace
  2. How to remap a topic
  3. How to set a node parameter
  4. How to set the logger level

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/#/l/4e79d8dd/
  2. The Construct: https://app.theconstructsim.com/
  3. ROS2 Guide: https://docs.ros.org/en/humble/How-To-Guides/Node-arguments.html
  4. ROS2 Courses –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61

Overview

In this post we are going to show are ROS Arguments are, how to use them from the command line with the ros-args flag, and how they can modify some aspects of a ROS Node in a ROS Network.

ROS Inside!

ROS inside

ROS inside

Before anything else, in case you want to use the logo above on your own robot or laptop, feel free to download it for free and attach it to your robot. It is really free. Find it in the link below:

ROS Inside logo

 

Opening the rosject

In order to learn how to pass ROS2 arguments to a ROS2 Node via the command line, we need to have ROS2 installed in our system, and it is also useful to have some simulations. To make your life easier, we already prepared a rosject with a simulation for that: https://app.theconstructsim.com/#/l/4e79d8dd/

You can download the rosject to your own computer if you want to work locally, but just by copying the rosject (clicking the link), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject (below you have a rosject example).

Learn ROS2 Parameters - Run rosject

ROS2 Arguments – Run rosject (example of the RUN button)

After pressing the Run button, you should have the rosject loaded. Let’s now head to the next section to really get some real practice.

Launching the simulation

The rosject we provided contains the packages needed to run a simulation in ROS2. The ros2_ws (ROS2 Workspace) is already compiled. Let’s compile it again just in case. For that, let’s first open a terminal:

Open a new Terminal

Open a new Terminal

Now, let’s compile the ros2_ws folder:

cd ~/ros2_ws

colcon build

 

Once the workspace is compiled, let’s source it.

source install/setup.bash

 

Now that the workspace is compiled and sourced, let’s start the simulation by running the following commands:

cd ~/ros2_ws/src/t3_humble

./start_sim_house.sh

The simulation should have been loaded, as we can see on the left side of the image below:

ROS2 --ros-args: Simulation launched

ROS2 –ros-args: Simulation launched

Starting a normal obstacle avoidance node (without –ros-args)

Now that we have the simulation running, let’s run the obstacle avoidance node. For that, let’s open a new terminal, let’s call it second terminal and type the following command on it:

ros2 run rule_based_obstacle_avoidance obstacle_avoidance

Let’s now to go to a third terminal and list the ROS2 nodes there:

ros2 node list

The expected list of nodes should be similar to the following:

/ObstacleAvoidance
/camera_driver
/gazebo
/robot_state_publisher
/turtlebot3_diff_drive
/turtlebot3_imu
/turtlebot3_joint_state
/turtlebot3_laserscan

 

Let’s take the chance and also check the list of ROS2 topics still in the third terminal:

ros2 topic list

We should have something like this:

/camera/camera_info
/camera/image_raw
/camera/image_raw/compressed
/camera/image_raw/compressedDepth
/camera/image_raw/theora
/clock
/cmd_vel
/imu
/joint_states
/odom
/parameter_events
/performance_metrics
/robot_description
/rosout
/scan
/tf
/tf_static

 

Starting the obstacle avoidance node in a specific namespace (using –ros-args)

Now that we saw how to launch a normal node in ROS2, let’s see how to launch it in a specific namespace. For that, we are going to use the –ros-args parameter when running ros2 run.

Let’s go to the second terminal where we launched the node, and stop the node by pressing CTRL+C.

After the node has stopped, let’s launch it again in the /robot1 namespace.

ros2 run rule_based_obstacle_avoidance obstacle_avoidance --ros-args -r __ns:=/robot1

If we now list the nodes again, just as we did before in the third terminal, we should see something different:

ros2 node list

The output must be something like this:

/camera_driver
/gazebo
/robot1/ObstacleAvoidance
/robot_state_publisher
/turtlebot3_diff_drive
/turtlebot3_imu
/turtlebot3_joint_state
/turtlebot3_laserscan

As we can see, now the ObstacleAvoidance node that we just launched is under the /robot1 namespace. The reason why the other nodes are not under a namespace is that those nodes were launched in the first terminal, where we launched the simulation.

We can also check the topics now, in order to find whether the Obstacle Avoidance-related topics are under the namespace. Let’s run the following command in the third terminal:

ros2 topic list

 

We should have something similar to this:

/camera/camera_info
/camera/image_raw
/camera/image_raw/compressed
/camera/image_raw/compressedDepth
/camera/image_raw/theora
/clock
/cmd_vel
/imu
/joint_states
/odom
/parameter_events
/performance_metrics
/robot1/cmd_vel
/robot1/scan
/robot_description
/rosout
/scan
/tf
/tf_static

 

The important difference in the list of topics here are the cmd_vel and scan topics, which are now under the /robot1 namespace, yet, some topics with the same name without the namespace:

...
/cmd_vel
...
/robot1/cmd_vel
/robot1/scan
...
/scan

 

The topics under the /robot1 namespace are the ones that the  Obstacle Avoidance node subscribes to. The topics without the namespace are watched by other nodes in the Gazebo Simulation, not by the Obstacle Avoidance.

Why have a namespace for ROS2 Nodes?

One of the reasons why Namespaces are useful is because this way, you can run the same node multiple times, once for each robot. This way, you will not have two robots processing the same messages, nor will you have to change your code to consider whether the code is running on robot1 or robot2, for example.

You can run multiple instances of a node with the same name, as long as they are in different namespaces.

Examples:

ros2 run rule_based_obstacle_avoidance obstacle_avoidance --ros-args -r __ns:=/robot1

 

ros2 run rule_based_obstacle_avoidance obstacle_avoidance --ros-args -r __ns:=/robot2

Assigning a different name to a ROS2 Node using –ros-args

In order to change the name of a given ROS2 Node, we use the __node variable.

In order to run this example, please remember to kill the node that we launched in the second terminal by pressing CTRL+C.

Once the old node is terminated, we can launch it again with a different name using the following command:

ros2 run rule_based_obstacle_avoidance obstacle_avoidance --ros-args --remap __node:=collision_avoidance

The above command will cause the node to be started under the node collision_avoidance name.

Let’s check it by listing the nodes using the third terminal:

ros2 node list

You should now see the /collision_avoidance. instead of /obstacle_avoidance that we had before.

Changing topic names in ROS2 (Remapping)

Remapping means changing topics’ names.

So far we have learned how to add a namespace to a node, and how to change the node name. Time now has come to learn how to change the topic that a given node publishes or subscribes to.

Let’s go again to the second terminal , kill the node we launched earlier by pressing CTRL+C, then launch the node again using the command below:

ros2 run rule_based_obstacle_avoidance obstacle_avoidance --ros-args -r scan:=laser_scan

The command above will make the node subscribe to the topic named laser_scan instead of the default scan.

If you now check the topics again in the third terminal, you should see the /laser_scan topic, which is the one that the Obstacle Avoidance subscribes to:

ros2 topic list

 

Setting parameters directly from the command line when the node starts up

If you pay attention carefully, you will see that up to now we have been using the ros-args –remap parameters. Now, in order to pass parameters to the node, we use –ros-args -p instead.

In order to set the “safety_distance” parameter to 0.5, for example, we would use the following command in the second  terminal:

ros2 run rule_based_obstacle_avoidance obstacle_avoidance --ros-args -p safety_distance:=0.5

 

To check of the parameter was set, we can start by listing the parameters in the third terminal:

ros2 param list

With the command above you will see that we have a node called ObstacleAvoidance and there is a parameter called safety_distance associated with the node.

We can check the value of the node with the following command:

ros2 param get ObstacleAvoidance safety_distance

 

After retrieving the value, you will see that it matches exactly with the value we set in the second terminal when we ran the node.

Setting Logger Level using –ros-args

Now we will see that we can control log severity levels printed out to the terminal, using the command line.

Let’s go again to our second terminal and kill the node we launched previously by pressing CTRL+C.

After the node has stopped, let’s run the following command:

ros2 run rule_based_obstacle_avoidance obstacle_avoidance --ros-args --log-level debug

The command above specifies that our logger level is DEBUG, which means we will see basically all log messages, given that DEBUG is the lowest level.

We set the level to debug, but you could also use info, warn or error, for example.

After having launched the node, you should have seen a lot of log messages coming out, logs that we didn’t have the previous times.

Using different parameters, you can control which logs you are going to see, without having to recompile your nodes, in case the nodes are in C++.

Congratulations on reaching the end of this post. We really hope it was of great use to you.

If case you want a live version of this post, please check the video in the next section.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

How to Replay ros2 bags with changed Quality of Service

How to Replay ros2 bags with changed Quality of Service

In this post, you will learn how to replay ros2 bags with changed Quality of Service (QoS) setting. You’ll discover how to set the QoS before recording the bag, and how to change the QoS when playing back the bag file.

Step 1: Get a Copy of the ROS package containing the code used in the post

Click here to copy the project. It would be copied to your cloud account at The Construct. That done, open the project using the Run button. This might take a few moments, please be patient.

Run rosject

PS: If you don’t have an account on the The Construct, you would need to create one. Once you create an account or log in, you will be able to follow the steps to read and write parameters in ros1 and ros2.

You might also want to try this on a local PC if you have ROS installed. In that case you need to read on and duplicate the source code of the package in your own local workspace. However, please note that we cannot support local PCs and you will have to fix any errors you run into on your own.

Step 2: Start a simulation and the Quality of Service publisher

Open Code Editor
Open a web shell

Open a web shell (1) and run the following commands to start a simulation:

export GAZEBO_RESOURCE_PATH=/home/user/ros2_ws/src/turtlebot/turtlebot3_simulations/turtlebot3_gazebo:${GAZEBO_RESOURCE_PATH}
export GAZEBO_MODEL_PATH=/home/user/ros2_ws/src/turtlebot/turtlebot3_simulations/turtlebot3_gazebo/models:${GAZEBO_MODEL_PATH}
export TURTLEBOT3_MODEL=waffle
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py

You should see a simulation like this. If the robot does not show up, shutdown using Ctrl+C and run the above command again.

TurtleBot3 simulation
TurtleBot3 Simulation

Now make the robot move by publishing to the /cmd_vel topic, in another web shell (2). You should see the robot move.

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

Now start the QoS publisher in the same web shell (2) where you published to /cmd_vel. Note that we set the reliability to reliable. The QoS publisher publishes to the /robot_pose topic.

ros2 run qos_pose_publisher qos_pose_publisher -reliability reliable

Now let’s look at the reliability of the /robot_pose topic in a new web shell (3):

ros2 topic info /robot_pose --verbose

You should see something like this as part of the output:

QoS profile:
  Reliability: RELIABLE
  Durability: VOLATILE
  Lifespan: 9223372036854775807 nanoseconds
  Deadline: 9223372036854775807 nanoseconds
  Liveliness: AUTOMATIC
  Liveliness lease duration: 9223372036854775807 nanoseconds

Step 3: Record and play back a ros2 bag file of a topic

Now let’s record a ros2 bag file of a topic in web shell (3). We are using the /robot_pose topic.

ros2 bag record -o ros2bag_qos_as_published /robot_pose

Let it run for 5 seconds and then kill it, in web shell (3). You should have a new file created:

ros2bag_qos_as_published

Now let’s play the ros2 bag. But before that kill the qos_pose_publisher node in web shell (2) by pressing Ctrl+C. In the same web shell (2), run the following command:

ros2 bag play ros2bag_qos_as_published

Now let’s examine the QoS of the /robot_pose topic (now being published to from the ros2 bag), in web shell (3):

ros2 topic info /robot_pose --verbose

You should see something like we saw before:

QoS profile:
  Reliability: RELIABLE
  Durability: VOLATILE
  Lifespan: 9223372036854775807 nanoseconds
  Deadline: 9223372036854775807 nanoseconds
  Liveliness: AUTOMATIC
  Liveliness lease duration: 9223372036854775807 nanoseconds

Step 4: Change the Quality of Service profile for ros2 bag playback

Create a profile file for ros2 bag playback. In web shell (3):

touch override.yaml

Open override.yaml in the code editor and paste in the following content:

/robot_pose:
  history: keep_last
  depth: 10
  reliability: best_effort
  durability: volatile
  deadline:
    # unspecified/infinity
    sec: 0
    nsec: 0
  lifespan:
    # unspecified/infinity
    sec: 0
    nsec: 0
  liveliness: system_default
  liveliness_lease_duration:
    # unspecified/infinity
    sec: 0
    nsec: 0
  avoid_ros_namespace_conventions: false

Now go to web shell (2), stop the ros2 bag playback (if it’s still running), and run the following command instead:

ros2 bag play --qos-profile-overrides-path override.yaml ros2bag_qos_as_published 

Now, let’s see the quality of service of the /robot_pose topic. In web shell (3):

ros2 topic info /robot_pose --verbose

You should now see something similar to the following in the output:

QoS profile:
  Reliability: BEST_EFFORT
  Durability: VOLATILE
  Lifespan: 9223372036854775807 nanoseconds
  Deadline: 9223372036854775807 nanoseconds
  Liveliness: AUTOMATIC
  Liveliness lease duration: 9223372036854775807 nanoseconds

And that’s it! That’s how to replay ros2 bags with changed quality of service setting.

Step 5: Check your learning

  1. Do you understand how to set the QoS for the topic before recording a bag?
  2. Do you understand how to replay ros2 bags with changed quality of service setting?

If you didn’t get any of the points above, please go over the post again, more carefully this time.

(Extra) Step 6: Watch the video to understand how to create ros2 XML launch files

Here you go:

Feedback

Did you like this post? Do you have any questions about how to read and write parameters in ros1 and ros2? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS topics, please let us know in the comments area and we will do a video or post about it.

[ROS2 How-to] #2 – Create a ROS2 action server

[ROS2 How-to] #2 – Create a ROS2 action server

What we are going to learn

  1. – How to create a custom action message
  2. How to create an action server

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/#/l/4a1c58c5/
  2. The Construct: https://app.theconstructsim.com/
  3. ROS2 Courses –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61
    3. ROS2 Navigation training: https://www.theconstruct.ai/ros2-navigation-training/

What is a ROS2 Action

Let’s assume you wish to wash your clothing. There are two possible ways you could go about it:

  1. Go to the Laundry service provider
    1. Put your clothes to wash.
    2. Wait until the clothes are washed.
    3. Get your clothes.
  2. If you have a washing machine at home:
    1. Put your clothes to wash
    2. Instead of waiting, you can do other things and leave the watching machine doing its jobs
    3. Check once in a while if the clothes are finished
    4. Do other things.
    5. Clothes are washed.

Option 1 is a blocking activity because you have to wait (in theory not able to do anything else) for the clothes to be washed, while option 2 is non-blocking because you can do some other things while your clothes are being washed.

This non-blocking is what defines an Action. If ROS2 Services are for instant request-responses, an Action is a task that may take a lot of time to be finished, and in the meantime, a robot (or you) is free to do other things and is also able to constantly check the status of the action.

Opening the rosject

In order to learn how to create an and use an Action Server in ROS2, we need to have ROS2 installed in our system, and it is also useful to have some simulations. To make your life easier, we already prepared a rosject with a simulation for that: https://app.theconstructsim.com/#/l/4a1c58c5/.

You can download the rosject on your own computer if you want to work locally, but just by copying the rosject (clicking the link), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject (below you have a rosject example).

Learn ROS2 Parameters - Run rosject

Learn ROS2 – Run rosject (example of the RUN button)

After pressing the Run button, you should have the rosject loaded. Let’s now head to the next section to really get some real practice.

Launching the simulation

The rosject we provided contains the packages needed to run a TurtleBot3 simulation in ROS2. The TurtleBot3 has the following sensors:

  • Lidar
  • IMU

Feel free to use this rosject to test your mobile robot programs.

The rosject is structured the following way:

  • turtlebot3_ws: this workspace contains the TurtleBot3 packages provided by ROBOTIS. Don’t modify this unless you know what you are doing and want to change something from the simulation
  • Use this workspace to develop your programs

Assuming you have opened the rosject by clicking the Run button, we can launch the simulation with:

cd
source turtlebot3_ws/install/setup.bash
source turtlebot3_ws/install/setup.bash
export TURTLEBOT3_MODEL=burger
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py
After a few seconds, the simulation should have opened automatically:

[ROS2 How-to] #2 - Create a ROS2 action server - Simulation running

[ROS2 How-to] #2 – Create a ROS2 action server – Simulation running

 

In case the simulation does not pop up automatically, you can easily click the Open Gazebo button like in the example below (bear in mind that the simulation below is not the one used in this tutorial. Its main purpose is to show the Open Gazebo button):

Open Gazebo by clicking Open Gazebo

Open Gazebo by clicking Open Gazebo

 

Creating our ROS2 package (later used to create our Action Server)

Let’s create our ROS2 Package. For that, let’s start by opening a new terminal:

Open a new Terminal

Open a new Terminal

 

In the terminal that was just open, by running the “ls”  command you can see that we have at least the following folders:

ros2_ws  turtlebot3_ws

 

The turtlebot3_ws contains the simulation, and the ros2_ws is where we are going to place our code.

Before you continue, it is worth mentioning that in the rosject that we shared with you, the custom_interfaces package that we are going to create here already exists. We are going to create it here basically for learning purposes. You would actually not need it since the package was already created for you:

Let’s create a package named custom_interfaces with the action_msgs std_msgs rosids_default_generators  packages as dependencies:

cd ~/ros2_ws/src/

ros2 pkg create custom_interfaces2 --build-type ament_cmake --dependencies action_msgs std_msgs rosidl_default_generators

If everything went ok, you should see the following:

going to create a new package
package name: custom_interfaces
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user <user@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: ['action_msgs', 'std_msgs', 'rosidl_default_generators']
creating folder ./custom_interfaces
creating ./custom_interfaces/package.xml
creating source and include folder
creating folder ./custom_interfaces/src
creating folder ./custom_interfaces/include/custom_interfaces2
creating ./custom_interfaces/CMakeLists.txt

 

After the package was created, let’s create a folder called action:

mkdir -p custom_interfaces/action/

and also create the action/Patrol.action file.

touch custom_interfaces/action/Patrol.action

This is the file/Interface that we will use in our Action Server for patrolling.

Let’s now open that Patrol.action file. You can open it in the Code Editor. If you do not know how to open the Code Editor, please check the image below:

Open the IDE - Code Editor

Open the IDE – Code Editor

You can now open the custom_interfaces/action/Patrol.action file and paste the following content on it:

#Goal
float32 radius
---
#Result
bool success
---
#Feedback
float32 time_left

 

Now, to be able to compile our message file, we have to open the custom_interfaces/CMakeLists.txt file and paste the following content around line 14:

set(action_files
    "action/Patrol.action"
)

rosidl_generate_interfaces(${PROJECT_NAME}
    ${action_files}
    DEPENDENCIES action_msgs std_msgs
)
In the end, the final CMakeLiss.txt file would look like the following:
cmake_minimum_required(VERSION 3.8)
project(custom_interfaces)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(action_msgs REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

set(action_files
  "action/Patrol.action"
)

rosidl_generate_interfaces(${PROJECT_NAME}
  ${action_files}
  DEPENDENCIES action_msgs std_msgs
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

 

And for the file custom_interfaces/package.xml we also have to add the following code before the <export> tag:

<depend>builtin_interfaces</depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

In the end, our package.xml would look like the following:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>custom_interfaces</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="user@todo.todo">user</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>action_msgs</depend>
  <depend>std_msgs</depend>
  <depend>rosidl_default_generators</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <depend>builtin_interfaces</depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

Please make sure you save the files with Ctrl+S after making the modifications.

Compiling our custom Action interface

Now that we defined our Custom Action interface, let’s compile it.

Let’s go to the first terminal we opened previously and run the following commands:

cd ~/ros2_ws/

colcon build

The package should have been compiled with no errors:

Starting >>> custom_interfaces
Finished <<< custom_interfaces [9.53s]

Summary: 1 package finished [9.53s]

 

Let’s now make sure ROS2 can find our Action interface:

source install/setup.bash

ros2 interface show custom_interfaces/action/Patrol

It should show:

#Goal
float32 radius
---
#Result
bool success
---
#Feedback
float32 time_left

 

So far so good. ROS is able to find our custom interface.

The time has now come for us to create the Action Server.

Creating our ROS2 Action Server

Let’s create a different package for the Action Server, just to keep things separated. Since we are not doing to create Interfaces in this new package, just use existing interfaces, let’s use the ament_python build type. Again, bear in mind that if you are using the rosject that we provided, the package already exists in the ~/ros2_ws/src folder:

cd ~/ros2_ws/src/
ros2 pkg create --build-type ament_python patrol_action_server --dependencies rclpy geometry_mgs custom_interfaces

The logs should be similar to the following:

going to create a new package
package name: patrol_action_server
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user <user@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: ['rclpy', 'geometry_mgs', 'custom_interfaces']
creating folder ./patrol_action_server
creating ./patrol_action_server/package.xml
creating source folder
creating folder ./patrol_action_server/patrol_action_server2
creating ./patrol_action_server/setup.py
creating ./patrol_action_server/setup.cfg
creating folder ./patrol_action_server/resource
creating ./patrol_action_server/resource/patrol_action_server
creating ./patrol_action_server/patrol_action_server/__init__.py
creating folder ./patrol_action_server/test
creating ./patrol_action_server/test/test_copyright.py
creating ./patrol_action_server/test/test_flake8.py
creating ./patrol_action_server/test/test_pep257.py

Now that our package is created, let’s create a file patrol_action_server.py that will have the code of our Action Server:

touch patrol_action_server/patrol_action_server/patrol_action_server.py

Let’s now open that file using the Code Editor, and paste the following content to it:

#!/usr/bin/env python3
#
# Copyright 2019 ROBOTIS CO., LTD.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Authors: Ryan Shim, Gilbert

import math
import time

import rclpy

from geometry_msgs.msg import Twist

from rclpy.action import ActionServer
from rclpy.action import CancelResponse
from rclpy.action import GoalResponse
from rclpy.callback_groups import ReentrantCallbackGroup
from rclpy.duration import Duration
from rclpy.node import Node
from rclpy.qos import QoSProfile

from rclpy.executors import MultiThreadedExecutor

from custom_interfaces.action import Patrol


class Turtlebot3PatrolServer(Node):

    def __init__(self):
        super().__init__('turtlebot3_patrol_server')


        self.goal = Patrol.Goal()


        qos = QoSProfile(depth=10)

        # Initialise publishers
        self.cmd_vel_pub = self.create_publisher(Twist, 'cmd_vel', qos)

        # Initialise servers
        self._action_server = ActionServer(
            self,
            Patrol,
            'patrol',
            execute_callback=self.execute_callback,
            callback_group=ReentrantCallbackGroup(),
            goal_callback=self.goal_callback,
            cancel_callback=self.cancel_callback)

        self.get_logger().info("Turtlebot3 patrol action server has been initialised.")


    def destroy(self):
        self._action_server.destroy()
        super().destroy_node()

    def goal_callback(self, goal_request):
        # Accepts or rejects a client request to begin an action
        self.get_logger().info('Received goal request :)')
        self.goal = goal_request
        return GoalResponse.ACCEPT

    def cancel_callback(self, goal_handle):
        # Accepts or rejects a client request to cancel an action
        self.get_logger().info('Received cancel request :(')
        return CancelResponse.ACCEPT

    async def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')

        radius = self.goal.radius  # unit: m
        speed = 0.5  # unit: m/s

        feedback_msg = Patrol.Feedback()
        total_driving_time = 2 * math.pi * radius / speed
        feedback_msg.time_left = total_driving_time
        last_time = self.get_clock().now()

        # Start executing an action
        while (feedback_msg.time_left > 0):
            if goal_handle.is_cancel_requested:
                goal_handle.canceled()
                self.get_logger().info('Goal canceled')
                return Patrol.Result()

            curr_time = self.get_clock().now()
            duration = Duration()
            duration = (curr_time - last_time).nanoseconds / 1e9  # unit: s

            feedback_msg.time_left = total_driving_time - duration
            self.get_logger().info('Time left until the robot stops: {0}'.format(feedback_msg.time_left))
            goal_handle.publish_feedback(feedback_msg)

            # Give vel_cmd to Turtlebot3
            twist = Twist()
            twist = self.drive_circle(radius, speed)
            self.cmd_vel_pub.publish(twist)

            # Process rate
            time.sleep(0.010)  # unit: s

        # When the action is completed
        twist = Twist()
        self.cmd_vel_pub.publish(twist)

        goal_handle.succeed()
        result = Patrol.Result()
        result.success = True
        self.get_logger().info('Returning result: {0}'.format(result.success))

        return result

    def drive_circle(self, radius, velocity):
        self.twist = Twist()
        self.linear_velocity = velocity  # unit: m/s
        self.angular_velocity = self.linear_velocity / radius  # unit: rad/s

        self.twist.linear.x = self.linear_velocity
        self.twist.angular.z = self.angular_velocity

        return self.twist

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

    patrol_action_server = Turtlebot3PatrolServer()

    # Use a MultiThreadedExecutor to enable processing goals concurrently
    executor = MultiThreadedExecutor()

    rclpy.spin(patrol_action_server, executor=executor)

    patrol_action_server.destroy()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

The code used above is just an adaptation of a code already provided by ROBOTIS.
Before we compile our code, we also have to open the patrol_action_server/setup.py file and modify the entry_points section to define our executable called patrol_action_server_exe in the following way:
entry_points={
        'console_scripts': [
            'patrol_action_server_exe = patrol_action_server.patrol_action_server:main',
        ],
    },

 

In the end, the complete ~/ros2_ws/src/patrol_action_server/setup.py would be as follows:

from setuptools import setup

package_name = 'patrol_action_server'

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='user',
    maintainer_email='user@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'patrol_action_server_exe = patrol_action_server.patrol_action_server:main',
        ],
    },
)

 

Compiling our ROS2 Action Server

With everything in place, we compile our package just as before:

cd ~/ros2_ws/

colcon build
Starting >>> custom_interfaces
Finished <<< custom_interfaces [9.53s]
Starting >>> patrol_action_server
Finished <<< patrol_action_server [5.33s]

Summary: 2 packages finished [15.2s]
We can now run our server with the following commands:
source install/setup.bash

ros2 run patrol_action_server patrol_action_server_exe
The server should start with no problems:
[INFO] [1651528559.914166370] [turtlebot3_patrol_server]: Turtlebot3 patrol action server has been initialised

Calling our ROS2 Action Server

Ok, if you did not kill the Action Server launched in the previous section, please open a second terminal that we will use to call the Action Server.

With “ros2 node list” we should be able to find our node running:

ros2 node list

/turtlebot3_patrol_server
And with ros2 action list, we should be able to see the /patrol action:
ros2 action list

/patrol
We can now call our Action Server. If you remember when we created the Patrol.action, we defined a radius. Let’s them call the Action Server passing a radius of 0.5. The robot will be basically rotating:
ros2 action send_goal --feedback /patrol custom_interfaces/action/Patrol radius:\ 0.5\
You should now see the feedback sent by the action server:
Waiting for an action server to become available...
Sending goal:
     radius: 0.5

Goal accepted with ID: dd32bc835d7a4ef5ae854d0bfb4b119f

Feedback:
    time_left: 6.2831525802612305

Feedback:
    time_left: 6.271763801574707

Feedback:
    time_left: 6.260392665863037

Feedback:
    time_left: 6.2484917640686035

Feedback:
    time_left: 6.237414836883545

Feedback:
    time_left: 6.2265496253967285

Feedback:
    time_left: 6.215761661529541

...
^CCanceling goal...
Feedback:
time_left: 5.634908676147461
Goal canceled.

Remember that you can easily cancel the call to the action server by pressing CTRL+C.

If you look at the simulation after sending a goal to the Action Server, you should see the robot spinning around 0.5 meters.

Congratulations. You now know how to create a ROS2 Action Server from scratch. If you want more details about the code of the Action Server, please check the video in the next section.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

ROS2 Concepts in Practice #4 – Interfaces

ROS2 Concepts in Practice #4 – Interfaces

What we are going to learn

In this video, you’ll understand what is a ROS2 interface, the common language behind ROS2 messages, services, and actions.

You will also learn:

  • How to create and compile your own ROS2 Message Interfaces
  • How to create and compile your own ROS2 Service Interfaces

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/#/l/4a5c5215/
  2. ROS Development Studio (ROSDS) —▸ http://rosds.online
  3. ROS2 Courses –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61

Opening the rosject

In order to better understand ROS2 Interfaces, we need to have ROS2 installed in our system, and sometimes it is also useful to have some simulations. To make your life easier, we already prepared a rosject that you can use, with ROS2 already installed: https://app.theconstructsim.com/#/l/4a5c5215/.

You can download the rosject on your own computer if you want to work locally, but just by copying the rosject (clicking the link), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject (below you have a rosject example).

Learn ROS2 Parameters - Run rosject

Learn ROS2 – Run rosject (example of the RUN button)

 

After pressing the Run button, you should have the rosject loaded. Let’s now head to the next section to really get some real practice.

ROS2 Interfaces overview

ROS2 Interfaces is a common language behind ROS2 messages, services, and actions.

Let’s start listing the interfaces. For that, let’s first open a terminal:

Open a new Terminal

Open a new Terminal

After the terminal is open, you can list the interfaces with the command ros2 interface list. The output would be something similar to the following:

ros2 interface list

Messages:
    action_msgs/msg/GoalInfo
    action_msgs/msg/GoalStatus
    action_msgs/msg/GoalStatusArray
    actionlib_msgs/msg/GoalID
    actionlib_msgs/msg/GoalStatus
    actionlib_msgs/msg/GoalStatusArray
    bond/msg/Constants
    bond/msg/Status
    builtin_interfaces/msg/Duration
    builtin_interfaces/msg/Time
    cartographer_ros_msgs/msg/LandmarkEntry
    cartographer_ros_msgs/msg/LandmarkList
    cartographer_ros_msgs/msg/SensorTopics
    cartographer_ros_msgs/msg/StatusCode
    cartographer_ros_msgs/msg/StatusResponse
    cartographer_ros_msgs/msg/SubmapEntry
    cartographer_ros_msgs/msg/SubmapList
    cartographer_ros_msgs/msg/SubmapTexture
    cartographer_ros_msgs/msg/TrajectoryOptions
    checking_interfaces/msg/NewMsg
    control_msgs/msg/DynamicJointState
    control_msgs/msg/GripperCommand
    control_msgs/msg/InterfaceValue
    control_msgs/msg/JointComponentTolerance
    control_msgs/msg/JointControllerState
    control_msgs/msg/JointJog
    control_msgs/msg/JointTolerance
    control_msgs/msg/JointTrajectoryControllerState
    control_msgs/msg/PidState
    controller_manager_msgs/msg/ControllerState
    controller_manager_msgs/msg/HardwareInterface
    diagnostic_msgs/msg/DiagnosticArray
    diagnostic_msgs/msg/DiagnosticStatus
    diagnostic_msgs/msg/KeyValue
    dwb_msgs/msg/CriticScore
    dwb_msgs/msg/LocalPlanEvaluation
    dwb_msgs/msg/Trajectory2D
    dwb_msgs/msg/TrajectoryScore
    example_interfaces/msg/Bool
    example_interfaces/msg/Byte
    example_interfaces/msg/ByteMultiArray
    example_interfaces/msg/Char
    example_interfaces/msg/Empty
    example_interfaces/msg/Float32
    example_interfaces/msg/Float32MultiArray
    example_interfaces/msg/Float64
    example_interfaces/msg/Float64MultiArray
    example_interfaces/msg/Int16
    example_interfaces/msg/Int16MultiArray
    example_interfaces/msg/Int32
    example_interfaces/msg/Int32MultiArray
    example_interfaces/msg/Int64
    example_interfaces/msg/Int64MultiArray
    example_interfaces/msg/Int8
    example_interfaces/msg/Int8MultiArray
    example_interfaces/msg/MultiArrayDimension
    example_interfaces/msg/MultiArrayLayout
    example_interfaces/msg/String
    example_interfaces/msg/UInt16
    example_interfaces/msg/UInt16MultiArray
    example_interfaces/msg/UInt32
    example_interfaces/msg/UInt32MultiArray
    example_interfaces/msg/UInt64
    example_interfaces/msg/UInt64MultiArray
    example_interfaces/msg/UInt8
    example_interfaces/msg/UInt8MultiArray
    example_interfaces/msg/WString
    gazebo_msgs/msg/ContactState
    gazebo_msgs/msg/ContactsState
    gazebo_msgs/msg/EntityState
    gazebo_msgs/msg/LinkState
    gazebo_msgs/msg/LinkStates
    gazebo_msgs/msg/ModelState
    gazebo_msgs/msg/ModelStates
    gazebo_msgs/msg/ODEJointProperties
    gazebo_msgs/msg/ODEPhysics
    gazebo_msgs/msg/PerformanceMetrics
    gazebo_msgs/msg/SensorPerformanceMetric
    gazebo_msgs/msg/WorldState
    geometry_msgs/msg/Accel
    geometry_msgs/msg/AccelStamped
    geometry_msgs/msg/AccelWithCovariance
    geometry_msgs/msg/AccelWithCovarianceStamped
    geometry_msgs/msg/Inertia
    geometry_msgs/msg/InertiaStamped
    geometry_msgs/msg/Point
    geometry_msgs/msg/Point32
    geometry_msgs/msg/PointStamped
    geometry_msgs/msg/Polygon
    geometry_msgs/msg/PolygonStamped
    geometry_msgs/msg/Pose
    geometry_msgs/msg/Pose2D
    geometry_msgs/msg/PoseArray
    geometry_msgs/msg/PoseStamped
    geometry_msgs/msg/PoseWithCovariance
    geometry_msgs/msg/PoseWithCovarianceStamped
    geometry_msgs/msg/Quaternion
    geometry_msgs/msg/QuaternionStamped
    geometry_msgs/msg/Transform
    geometry_msgs/msg/TransformStamped
    geometry_msgs/msg/Twist
    geometry_msgs/msg/TwistStamped
    geometry_msgs/msg/TwistWithCovariance
    geometry_msgs/msg/TwistWithCovarianceStamped
    geometry_msgs/msg/Vector3
    geometry_msgs/msg/Vector3Stamped
    geometry_msgs/msg/Wrench
    geometry_msgs/msg/WrenchStamped
    libstatistics_collector/msg/DummyMessage
    lifecycle_msgs/msg/State
    lifecycle_msgs/msg/Transition
    lifecycle_msgs/msg/TransitionDescription
    lifecycle_msgs/msg/TransitionEvent
    map_msgs/msg/OccupancyGridUpdate
    map_msgs/msg/PointCloud2Update
    map_msgs/msg/ProjectedMap
    map_msgs/msg/ProjectedMapInfo
    nav2_msgs/msg/BehaviorTreeLog
    nav2_msgs/msg/BehaviorTreeStatusChange
    nav2_msgs/msg/Costmap
    nav2_msgs/msg/CostmapFilterInfo
    nav2_msgs/msg/CostmapMetaData
    nav2_msgs/msg/Particle
    nav2_msgs/msg/ParticleCloud
    nav2_msgs/msg/SpeedLimit
    nav2_msgs/msg/VoxelGrid
    nav_2d_msgs/msg/Path2D
    nav_2d_msgs/msg/Pose2D32
    nav_2d_msgs/msg/Pose2DStamped
    nav_2d_msgs/msg/Twist2D
    nav_2d_msgs/msg/Twist2D32
    nav_2d_msgs/msg/Twist2DStamped
    nav_msgs/msg/GridCells
    nav_msgs/msg/MapMetaData
    nav_msgs/msg/OccupancyGrid
    nav_msgs/msg/Odometry
    nav_msgs/msg/Path
    pcl_msgs/msg/ModelCoefficients
    pcl_msgs/msg/PointIndices
    pcl_msgs/msg/PolygonMesh
    pcl_msgs/msg/Vertices
    pendulum_msgs/msg/JointCommand
    pendulum_msgs/msg/JointState
    pendulum_msgs/msg/RttestResults
    rcl_interfaces/msg/FloatingPointRange
    rcl_interfaces/msg/IntegerRange
    rcl_interfaces/msg/ListParametersResult
    rcl_interfaces/msg/Log
    rcl_interfaces/msg/Parameter
    rcl_interfaces/msg/ParameterDescriptor
    rcl_interfaces/msg/ParameterEvent
    rcl_interfaces/msg/ParameterEventDescriptors
    rcl_interfaces/msg/ParameterType
    rcl_interfaces/msg/ParameterValue
    rcl_interfaces/msg/SetParametersResult
    rmw_dds_common/msg/Gid
    rmw_dds_common/msg/NodeEntitiesInfo
    rmw_dds_common/msg/ParticipantEntitiesInfo
    rosgraph_msgs/msg/Clock
    sensor_msgs/msg/BatteryState
    sensor_msgs/msg/CameraInfo
    sensor_msgs/msg/ChannelFloat32
    sensor_msgs/msg/CompressedImage
    sensor_msgs/msg/FluidPressure
    sensor_msgs/msg/Illuminance
    sensor_msgs/msg/Image
    sensor_msgs/msg/Imu
    sensor_msgs/msg/JointState
    sensor_msgs/msg/Joy
    sensor_msgs/msg/JoyFeedback
    sensor_msgs/msg/JoyFeedbackArray
    sensor_msgs/msg/LaserEcho
    sensor_msgs/msg/LaserScan
    sensor_msgs/msg/MagneticField
    sensor_msgs/msg/MultiDOFJointState
    sensor_msgs/msg/MultiEchoLaserScan
    sensor_msgs/msg/NavSatFix
    sensor_msgs/msg/NavSatStatus
    sensor_msgs/msg/PointCloud
    sensor_msgs/msg/PointCloud2
    sensor_msgs/msg/PointField
    sensor_msgs/msg/Range
    sensor_msgs/msg/RegionOfInterest
    sensor_msgs/msg/RelativeHumidity
    sensor_msgs/msg/Temperature
    sensor_msgs/msg/TimeReference
    shape_msgs/msg/Mesh
    shape_msgs/msg/MeshTriangle
    shape_msgs/msg/Plane
    shape_msgs/msg/SolidPrimitive
    statistics_msgs/msg/MetricsMessage
    statistics_msgs/msg/StatisticDataPoint
    statistics_msgs/msg/StatisticDataType
    std_msgs/msg/Bool
    std_msgs/msg/Byte
    std_msgs/msg/ByteMultiArray
    std_msgs/msg/Char
    std_msgs/msg/ColorRGBA
    std_msgs/msg/Empty
    std_msgs/msg/Float32
    std_msgs/msg/Float32MultiArray
    std_msgs/msg/Float64
    std_msgs/msg/Float64MultiArray
    std_msgs/msg/Header
    std_msgs/msg/Int16
    std_msgs/msg/Int16MultiArray
    std_msgs/msg/Int32
    std_msgs/msg/Int32MultiArray
    std_msgs/msg/Int64
    std_msgs/msg/Int64MultiArray
    std_msgs/msg/Int8
    std_msgs/msg/Int8MultiArray
    std_msgs/msg/MultiArrayDimension
    std_msgs/msg/MultiArrayLayout
    std_msgs/msg/String
    std_msgs/msg/UInt16
    std_msgs/msg/UInt16MultiArray
    std_msgs/msg/UInt32
    std_msgs/msg/UInt32MultiArray
    std_msgs/msg/UInt64
    std_msgs/msg/UInt64MultiArray
    std_msgs/msg/UInt8
    std_msgs/msg/UInt8MultiArray
    stereo_msgs/msg/DisparityImage
    test_msgs/msg/Arrays
    test_msgs/msg/BasicTypes
    test_msgs/msg/BoundedSequences
    test_msgs/msg/Builtins
    test_msgs/msg/Constants
    test_msgs/msg/Defaults
    test_msgs/msg/Empty
    test_msgs/msg/MultiNested
    test_msgs/msg/Nested
    test_msgs/msg/Strings
    test_msgs/msg/UnboundedSequences
    test_msgs/msg/WStrings
    tf2_msgs/msg/TF2Error
    tf2_msgs/msg/TFMessage
    trajectory_msgs/msg/JointTrajectory
    trajectory_msgs/msg/JointTrajectoryPoint
    trajectory_msgs/msg/MultiDOFJointTrajectory
    trajectory_msgs/msg/MultiDOFJointTrajectoryPoint
    turtlesim/msg/Color
    turtlesim/msg/Pose
    unique_identifier_msgs/msg/UUID
    visualization_msgs/msg/ImageMarker
    visualization_msgs/msg/InteractiveMarker
    visualization_msgs/msg/InteractiveMarkerControl
    visualization_msgs/msg/InteractiveMarkerFeedback
    visualization_msgs/msg/InteractiveMarkerInit
    visualization_msgs/msg/InteractiveMarkerPose
    visualization_msgs/msg/InteractiveMarkerUpdate
    visualization_msgs/msg/Marker
    visualization_msgs/msg/MarkerArray
    visualization_msgs/msg/MenuEntry
Services:
    action_msgs/srv/CancelGoal
    cartographer_ros_msgs/srv/FinishTrajectory
    cartographer_ros_msgs/srv/StartTrajectory
    cartographer_ros_msgs/srv/SubmapQuery
    cartographer_ros_msgs/srv/WriteState
    checking_interfaces/srv/NewServiceMessage
    composition_interfaces/srv/ListNodes
    composition_interfaces/srv/LoadNode
    composition_interfaces/srv/UnloadNode
    control_msgs/srv/QueryCalibrationState
    control_msgs/srv/QueryTrajectoryState
    controller_manager_msgs/srv/ConfigureController
    controller_manager_msgs/srv/ConfigureStartController
    controller_manager_msgs/srv/ListControllerTypes
    controller_manager_msgs/srv/ListControllers
    controller_manager_msgs/srv/ListHardwareInterfaces
    controller_manager_msgs/srv/LoadConfigureController
    controller_manager_msgs/srv/LoadController
    controller_manager_msgs/srv/LoadStartController
    controller_manager_msgs/srv/ReloadControllerLibraries
    controller_manager_msgs/srv/SwitchController
    controller_manager_msgs/srv/UnloadController
    diagnostic_msgs/srv/AddDiagnostics
    diagnostic_msgs/srv/SelfTest
    dwb_msgs/srv/DebugLocalPlan
    dwb_msgs/srv/GenerateTrajectory
    dwb_msgs/srv/GenerateTwists
    dwb_msgs/srv/GetCriticScore
    dwb_msgs/srv/ScoreTrajectory
    example_interfaces/srv/AddTwoInts
    example_interfaces/srv/SetBool
    example_interfaces/srv/Trigger
    gazebo_msgs/srv/ApplyBodyWrench
    gazebo_msgs/srv/ApplyJointEffort
    gazebo_msgs/srv/ApplyLinkWrench
    gazebo_msgs/srv/BodyRequest
    gazebo_msgs/srv/DeleteEntity
    gazebo_msgs/srv/DeleteLight
    gazebo_msgs/srv/DeleteModel
    gazebo_msgs/srv/GetEntityState
    gazebo_msgs/srv/GetJointProperties
    gazebo_msgs/srv/GetLightProperties
    gazebo_msgs/srv/GetLinkProperties
    gazebo_msgs/srv/GetLinkState
    gazebo_msgs/srv/GetModelList
    gazebo_msgs/srv/GetModelProperties
    gazebo_msgs/srv/GetModelState
    gazebo_msgs/srv/GetPhysicsProperties
    gazebo_msgs/srv/GetWorldProperties
    gazebo_msgs/srv/JointRequest
    gazebo_msgs/srv/LinkRequest
    gazebo_msgs/srv/SetEntityState
    gazebo_msgs/srv/SetJointProperties
    gazebo_msgs/srv/SetJointTrajectory
    gazebo_msgs/srv/SetLightProperties
    gazebo_msgs/srv/SetLinkProperties
    gazebo_msgs/srv/SetLinkState
    gazebo_msgs/srv/SetModelConfiguration
    gazebo_msgs/srv/SetModelState
    gazebo_msgs/srv/SetPhysicsProperties
    gazebo_msgs/srv/SpawnEntity
    gazebo_msgs/srv/SpawnModel
    lifecycle_msgs/srv/ChangeState
    lifecycle_msgs/srv/GetAvailableStates
    lifecycle_msgs/srv/GetAvailableTransitions
    lifecycle_msgs/srv/GetState
    logging_demo/srv/ConfigLogger
    map_msgs/srv/GetMapROI
    map_msgs/srv/GetPointMap
    map_msgs/srv/GetPointMapROI
    map_msgs/srv/ProjectedMapsInfo
    map_msgs/srv/SaveMap
    map_msgs/srv/SetMapProjections
    nav2_msgs/srv/ClearCostmapAroundRobot
    nav2_msgs/srv/ClearCostmapExceptRegion
    nav2_msgs/srv/ClearEntireCostmap
    nav2_msgs/srv/GetCostmap
    nav2_msgs/srv/LoadMap
    nav2_msgs/srv/ManageLifecycleNodes
    nav2_msgs/srv/SaveMap
    nav_msgs/srv/GetMap
    nav_msgs/srv/GetPlan
    nav_msgs/srv/LoadMap
    nav_msgs/srv/SetMap
    pcl_msgs/srv/UpdateFilename
    rcl_interfaces/srv/DescribeParameters
    rcl_interfaces/srv/GetParameterTypes
    rcl_interfaces/srv/GetParameters
    rcl_interfaces/srv/ListParameters
    rcl_interfaces/srv/SetParameters
    rcl_interfaces/srv/SetParametersAtomically
    rosbag2_interfaces/srv/GetRate
    rosbag2_interfaces/srv/IsPaused
    rosbag2_interfaces/srv/Pause
    rosbag2_interfaces/srv/PlayNext
    rosbag2_interfaces/srv/Resume
    rosbag2_interfaces/srv/Seek
    rosbag2_interfaces/srv/SetRate
    rosbag2_interfaces/srv/TogglePaused
    sensor_msgs/srv/SetCameraInfo
    slam_toolbox/srv/AddSubmap
    slam_toolbox/srv/Clear
    slam_toolbox/srv/ClearQueue
    slam_toolbox/srv/DeserializePoseGraph
    slam_toolbox/srv/LoopClosure
    slam_toolbox/srv/MergeMaps
    slam_toolbox/srv/Pause
    slam_toolbox/srv/SaveMap
    slam_toolbox/srv/SerializePoseGraph
    slam_toolbox/srv/ToggleInteractive
    std_srvs/srv/Empty
    std_srvs/srv/SetBool
    std_srvs/srv/Trigger
    test_bond/srv/TestBond
    test_msgs/srv/Arrays
    test_msgs/srv/BasicTypes
    test_msgs/srv/Empty
    tf2_msgs/srv/FrameGraph
    turtlesim/srv/Kill
    turtlesim/srv/SetPen
    turtlesim/srv/Spawn
    turtlesim/srv/TeleportAbsolute
    turtlesim/srv/TeleportRelative
    visualization_msgs/srv/GetInteractiveMarkers
Actions:
    action_tutorials_interfaces/action/Fibonacci
    control_msgs/action/FollowJointTrajectory
    control_msgs/action/GripperCommand
    control_msgs/action/JointTrajectory
    control_msgs/action/PointHead
    control_msgs/action/SingleJointPosition
    example_interfaces/action/Fibonacci
    nav2_msgs/action/BackUp
    nav2_msgs/action/ComputePathThroughPoses
    nav2_msgs/action/ComputePathToPose
    nav2_msgs/action/DummyRecovery
    nav2_msgs/action/FollowPath
    nav2_msgs/action/FollowWaypoints
    nav2_msgs/action/NavigateThroughPoses
    nav2_msgs/action/NavigateToPose
    nav2_msgs/action/Spin
    nav2_msgs/action/Wait
    test_msgs/action/Fibonacci
    test_msgs/action/NestedMessage
    tf2_msgs/action/LookupTransform
    turtlesim/action/RotateAbsolute

 

As you can see in the output above, the same command returned Messages, Actions, and Services.

Creating our first ROS2 Interface (a message)

If you are using the rosject provided at the beginning of this post, there is already a ROS2 Package called checking_interfaces on the /home/user/ros2_ws/src/checking_interfaces path. The package also already contains a message on the ~/ros2_ws/src/checking_interfaces/msg/NewMsg.msg path.

If you are not using the provided rosject, you can create a package with:

mkdir -p /home/user/ros2_ws/src

cd /home/user/ros2_ws/src

ros2 pkg create --build-type ament_cmake checking_interfaces

The output would be similar to the following:

going to create a new package
package name: checking_interfaces
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user <user@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
creating folder ./checking_interfaces
creating ./checking_interfaces/package.xml
creating source and include folder
creating folder ./checking_interfaces/src
creating folder ./checking_interfaces/include/checking_interfaces
creating ./checking_interfaces/CMakeLists.txt

 

If you are using the rosject you do not need to create the message msg/NewMsg.msg, but for learning purposes, let’s see how to create it.

~/ros2_ws/src/checking_interfaces$ ls ~/ros2_ws/src/checking_interfaces$ ls msg/NewMsg.msg

cd ~/ros2_ws/src/checking_interfaces

mkdir msg

cd msg

touch NewMsg.msg

Then, paste the following content on the NewMsg.msg file:

bool check
int16 number
string text

If you are wondering how to know which types are accepted for the properties of the new message, the types available are:

ROS2 build-in-types

Type name C++ Python DDS type
bool bool builtins.bool boolean
byte uint8_t builtins.bytes* octet
char char builtins.str* char
float32 float builtins.float* float
float64 double builtins.float* double
int8 int8_t builtins.int* octet
uint8 uint8_t builtins.int* octet
int16 int16_t builtins.int* short
uint16 uint16_t builtins.int* unsigned short
int32 int32_t builtins.int* long
uint32 uint32_t builtins.int* unsigned long
int64 int64_t builtins.int* long long
uint64 uint64_t builtins.int* unsigned long long
string std::string builtins.str string
wstring std::u16string builtins.str string

Compiling our ROS2 Interface

In order to compile our ROS2 Interface, we have to touch the ~/ros2_ws/src/checking_interfaces/CMakeLists.txt file. Again, if you are using the rosject we provided, everything is already prepared for you. For learning purposes, let’s open the file to modify it. Let’s start by opening the Code Editor:

Open the IDE - Code Editor

Open the IDE – Code Editor

 

After the Code Editor is open, feel free to open the checking_interfaces/CMakeListst.txt file.

Around line 13 of the CMakeListst.txt file, we have to add the following lines:

find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
	"msg/NewMsg.msg"
)

The first line mentioned above includes the rosidl_default_generators package, which will be used to “compile” our message to make it available for Python and C++, so that our nodes can use the interface.

In the rosidl_generate_interfaces we add the name of the interface (message, service, or action) that we want to compile, which in this case is “msg/NewMsg.msg”. 

After setting up the CMakeLists.txt file, you also have to set up the checking_interfaces/package.xml file. We have to add rosidl_default_generators as a build dependency, we also need to add the rosidl_default_runtime execution dependency, and we also need the rosidl_interface_packages “member of group”. In the end, the lines you would need to add are:

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

In the end, the final package.xml file would be as follows:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
  <package format="3">
  <name>checking_interfaces</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintaineremail="user@todo.todo">user</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  <build_depend>rosidl_default_generators</build_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>
After saving the files, you can compile the interface with:
cd ~/ros2_ws/

colcon build --packages-select checking_interfaces

 

The package should compile with no errors:

...
This may be promoted to an error in a future release of colcon-core.
Starting >>> checking_interfaces
Finished <<< checking_interfaces [5.27s]

Summary: 1 package finished [5.87s]

 

After the package is compiled, we can now source the installation folder so that ROS can find what we have compiled so far:

source install/setup.bash

 

If we now list the interfaces again and search for our NewMsg, we should be able to find it:

ros2 interface list | grep New

# ...
checking_interfaces/msg/NewMsg
# ...

As we can see in the output above, we have a message called NewMsg.

Using the interface we just created

If we have just created the checking_interfaces/msg/NewMsg, we should be able to use it. We can check its definition with:

ros2 interface show checking_interfaces/msg/NewMsg

which shows the exact thing we defined in the NewMsg.msg file:

bool check
int16 number
string text

 

Let’s now create a publisher in a topic called /testing using this message:

ros2 topic pub /testing checking_interfaces/msg/NewMsg

After pressing ENTER, we should see the message being published with default values:

 

publisher: beginning loop
publishing #1: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #2: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #3: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #4: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #5: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #6: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #7: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
...

You can of course set the values of the message:

ros2 topic pub /testing checking_interfaces/msg/NewMsg "{check: true, number: 7, text: 'Be perfect' }"

# WHICH OUTPUTS

publisher: beginning loop
publishing #1: checking_interfaces.msg.NewMsg(check=True, number=7, text='Be perfect')
publishing #2: checking_interfaces.msg.NewMsg(check=True, number=7, text='Be perfect')
publishing #3: checking_interfaces.msg.NewMsg(check=True, number=7, text='Be perfect')
...

 

Creating our second ROS2 Interface (a service)

Similar to when defining our message, we already have a service defined on ~/ros2_ws/src/checking_interfaces/srv/NewServiceMessage.srv, but we will be following the process for learning purposes in case you are not using the rosject we provided, or wants to learn with hands-on.

cd ~/ros2_ws/src/checking_interfaces

mkdir srv

cd srv

touch NewServiceMessage.srv

You can now open the NewServiceMessage.srv file with the Code Editor and paste the following content on it:

bool check
---
int16 number
string text

In order to compile the service interface, you have to open the CMakeLists.txt file again and add “srv/NewServiceMessage.srv” right after “msg/NewMsg.msg” that we added earlier around line 15 of our CMakeLists.txt file. The rosidl_generate_interfaces section of the file would be like:

find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
    "msg/NewMsg.msg"
    "srv/NewServiceMessage.srv"
)
As you can see so far, the same rosidl_generate_interfaces is used to compile Messages and Services.
You may have noticed that we have a line containing “—“ in the NewServiceMessage.srv file. These three “—” basically separate the Service Request from the Service Response.
The request will be “bool check“, and the fields used in a response will be “int16 number” and “string text“.
After saving the file changes, we can just compile the service interface in the same way that we compiled the message interface:
cd ~/ros2_ws/

colcon build --packages-select checking_interfaces

 source install/setup.bash

If you now list the interfaces, you should be able to easily find our NewServiceMessage.

ros2 interface list | grep New


checking_interfaces/msg/NewMsg
checking_interfaces/srv/NewServiceMessage

 

Congratulations. You now know how to easily create ROS2 Interfaces. Please go ahead and try to create ROS2 Action Interfaces yourself, or you can also check the courses that we have listed at the end of this post.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

ROS2 Concepts in Practice #3 – DDS/RTPS

ROS2 Concepts in Practice #3 – DDS/RTPS

What we are going to learn

  1. How to set up the DDS middleware for your ROS2 programs
  2. What are DDS and RTPS used by ROS2
  3. How to avoid errors when working with multiple DDS providers

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/#/l/4a5ab758/
  2. ROS Development Studio (ROSDS) —▸ http://rosds.online
  3. ROS2 Courses –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61

Opening the rosject

In order to better understand DDS/RTTPS used in ROS2, we need to have ROS2 installed in our system, and it is also useful to have some simulations. To make your life easier, we already prepared a rosject with a simulation for that: https://app.theconstructsim.com/#/l/4a5ab758/ .

You can download the rosject on your own computer if you want to work locally, but just by copying the rosject (clicking the link), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject (below you have a rosject example).

Learn ROS2 Parameters - Run rosject

Learn ROS2 – Run rosject (example of the RUN button)

 

After pressing the Run button, you should have the rosject loaded. Let’s now head to the next section to really get some real practice.

DDS Overview

DDS stands for Data Distribution Service, it is an industry standard for distributed object management groups. More info on https://www.dds-foundation.org/what-is-dds-3/

ROS2 is built on top of DDS/RTPS Middleware implementations to handle DDS/RTPS communication. Right now, we have 4 DDS implementations available for ROS2. The different implementations implement the ROS Middleware Interface (RMW).

In this POST we will be using the ROS2 Galactic version. For ROS2 Galactic, the default DDS Implementation is Cyclone DDS.

RTTPS Overview

RTPS (Real-Time Transport Protocol) is the protocol used by DDS. https://www.omg.org/spec/DDSI-RTPS/2.3/Beta1/PDF

The DDS War

Up until ROS2 Foxy, the default DDS Implementation used by ROS was eProsima’s Fast DDS (previous default RMW)
Eclipse Cyclone DDS (default one since Galactic)

For ROS2 Galactic, as we already mentioned earlier,  the default DDS Implementation is Cyclone DDS. The Galactic version was released on May 23rd, 2021, and is supported until November 2022.

For ROS2 Humble, an LTS (Long Term Support) version with support until May 2027,  eProsima’s Fast DDS is again the default middleware.

Running publishers and subscribers with default DDS

Assuming you have opened the rosject by clicking the Run button mentioned in earlier sections, let’s now Open a Terminal in order to launch ROS nodes.

Open a new Terminal

Open a new Terminal

 

In the first terminal that is open, let’s start a publisher by running the command below:

ros2 run demo_nodes_cpp talker

 

Please open a second terminal and run a subscriber there:

ros2 run demo_nodes_cpp listener

 

If everything went well, the publisher and subscriber should be working with no problems:

ROS2 Concepts in Practice #3 - DDS_RTPS - publisher and subscriber

ROS2 Concepts in Practice #3 – DDS_RTPS – publisher and subscriber

Since we did not inform the DDS Implementation that we are using, they are using the default one, which for ROS Galactic is Cyclone DDS.

You may be asking: How do I know which implementation I’m really using?

That is quite easy. After running ros2 topic list, a ROS2 Daemon will be launched and in the command line, you will see the DDS Implementation used. If you then list the process and search for rmw using grep, you will find the implementation being used.

Let’s search the DDS with the command “ps faux | grep rmw“:

ps faux | grep rmw

# The output
/usr/bin/python3 /opt/ros/galactic/bin/_ros2_daemon --rmw-implementation rmw_cyclonedds_cpp --ros-domain-id 0

As we can see in the output “–rmw-implementation rmw_cyclonedds_cpp“, the implementation used for ROS Galactic is Cyclone DDS.

RMW_IMPLEMENTATION

If you want to use different DDS Implementations, you can make usage of the environment variable called RMW_IMPLEMENTATION that ROS uses.

Let’s put it into practice in the next sections.

Using eProsima’s Fast DDS

In order to use FastDDS, you have to export the following variable:

export RMW_IMPLEMENTATION=rmw_fastrtps_cpp

 

On The Construct we already have ros-galactic-rmw-fastrtps-cpp installed, but if running the nodes in your own computer, you may need to install it with:

sudo apt-get update

sudo apt-get install -y ros-galactic-rmw-fastrtps-cpp

 

You can also set the variable when starting each node, for example. Please go to the first terminal, kill the current publisher by pressing CTRL-C, and then let’s launch the publisher with FastDDS with the command below:

RMW_IMPLEMENTATION=rmw_fastrtps_cpp ros2 run demo_nodes_cpp talker

And to run the subscriber using FastDDS, you can just set RMW_IMPLEMENTATION:

RMW_IMPLEMENTATION=rmw_fastrtps_cpp ros2 run demo_nodes_cpp listener

 

You can go even further and use different DDS Implementations for different ROS Nodes. To test this, please open a third terminal and do not kill the current publisher running with FastDDS. You can then run the listener in a third terminal using CyCloneDDS with:

RMW_IMPLEMENTATION=rmw_cyclonedds_cpp ros2 run demo_nodes_cpp listener

The listener should be working with no problems.

Using CycloneDDS

In order to use CycloneDDS, you just have to define RMW_IMPLEMENTATION=rmw_cyclonedds_cpp, and you will also need to have the ros-galactic-rmw-cyclonedds-cpp package installed.

On The Construct, this package is already installed, but if you are running the ROS2 nodes on your  computer, you may need to install the package ros-galactic-rmw-cyclonedds-cpp with:

sudo apt-get update

sudo apt-get install -y ros-galactic-rmw-cyclonedds-cpp

 

You can then run the talker/publisher and listener/subscriber using CycloneDDS with:

RMW_IMPLEMENTATION=rmw_cyclonedds_cpp ros2 run demo_nodes_cpp talker

RMW_IMPLEMENTATION=rmw_cyclonedds_cpp ros2 run demo_nodes_cpp listener

 

Using GurumNetworks GurumDDS

In order to use GurumDDS, you have to export RMW_IMPLEMENTATION=rmw_gurumdds_cpp.

Let’s try to run the publisher:

RMW_IMPLEMENTATION=rmw_gurumdds_cpp ros2 run demo_nodes_cpp talker

Since we do not have that DDS implementation installed, you should have an error similar to the following:

 

[ERROR] [1651517328.801031797] [rcl]: Error getting RMW implementation identifier / 
RMW implementation not installed (expected identifier of 'rmw_gurumdds_cpp'), 
with error message 'failed to load shared library 'librmw_gurumdds_cpp.so' due to dlopen error: 
librmw_gurumdds_cpp.so: cannot open shared object file: No such file or directory, 
at /tmp/binarydeb/ros-galactic-rcutils-4.0.2/src/shared_library.c:99, 
at /tmp/binarydeb/ros-galactic-rmw-implementation-2.4.1/src/functions.cpp:75', exiting with 1., 
at /tmp/binarydeb/ros-galactic-rcl-3.1.2/src/rcl/rmw_implementation_identifier_check.c:139

 

The problem happens because we do not have the GurumDDS installed by default on The Construct. You have to install the ros-galactic-rmw-gurumdds-cpp package with:

sudo apt-get update

sudo apt-get install -y ros-galactic-rmw-gurumdds-cpp

 

After that, running the publisher and subscriber should work with no problems:

RMW_IMPLEMENTATION=rmw_gurumdds_cpp ros2 run demo_nodes_cpp talker

 

Hm, now you probably have a new error similar to the following:

user:~$ RMW_IMPLEMENTATION=rmw_gurumdds_cpp ros2 run demo_nodes_cpp talker
22-05-02 18:52:44.719 2804 2804 E [License] Invalid license
22-05-02 18:52:44.719 2804 2804 E [License] Reason: No license for feature(gurumdds-x86_64-linux)
22-05-02 18:52:44.719 2804 2804 E [License] Please contact us via email below for license extension and inquiries.
22-05-02 18:52:44.719 2804 2804 E [License] email: contact@gurum.cc

 

Well, I think it is clear to you that this DDS implementation needs a special license. If you want to use this implementation, it would be nice to reach the creators through the email contact@gurum.cc.

Using Connext DDS

In order to use Connext DDS to run a ROS2 node, for example, you can just:

RMW_IMPLEMENTATION=rmw_connext_cpp ros2 run demo_nodes_cpp talker

But we will have an error also because this DDS implementation is not installed. The error is the following:

[ERROR] [1651517890.844612937] [rcl]: Error getting RMW implementation identifier / 
RMW implementation not installed (expected ide ntifier of 'rmw_connext_cpp'), 
with error message 'failed to load shared library 'librmw_connext_cpp.so' due to dlopen error: 
librmw_connext_cpp.so: cannot open shared object file: 
No such file or directory, at /tmp/binarydeb/ros-galactic-rcutils-4.0.2/src/sha
red_library.c:99, 
at /tmp/binarydeb/ros-galactic-rmw-implementation-2.4.1/src/functions.cpp:75', 
exiting with 1., 
at /tmp/binarydeb/ros-galactic-rcl-3.1.2/src/rcl/rmw_implementation_identifier_check.c:139

 

If you want to install this DDS implementation, I would ask you to follow the instructions in the link below:

https://docs.ros.org/en/foxy/Installation/DDS-Implementations/Install-Connext-Security-Plugins.html

Limitations when mixing DDS Implementations

We saw in the Using FastDDS section that we can use different DDSs for the publisher and subscriber.

We cannot use, however, any combinations of DDS implementations because there are some limitations. The know limitations are:

Fast DDS and Connext limitations

  • WString published by Fast DDS can’t be received correctly by Connext on macOS

Connext and Cyclone DDS

  • Does not support pub/sub communication for WString

Connext Dynamic and Connext Dynamic

  • Does not support C services

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

Pin It on Pinterest