0% found this document useful (0 votes)
289 views58 pages

03 ROS Client Libraries

This document provides instructions for creating a ROS 2 workspace and overlay. It explains how to source the ROS 2 environment, create a new directory for the workspace, clone a sample repository, resolve dependencies, build the workspace, source the overlay, and modify packages in the overlay. The goal is to demonstrate how to set up an overlay for development and testing by creating a workspace with a sample package and then modifying that package to show that changes only affect the overlay and not the underlying ROS installation.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
289 views58 pages

03 ROS Client Libraries

This document provides instructions for creating a ROS 2 workspace and overlay. It explains how to source the ROS 2 environment, create a new directory for the workspace, clone a sample repository, resolve dependencies, build the workspace, source the overlay, and modify packages in the overlay. The goal is to demonstrate how to set up an overlay for development and testing by creating a workspace with a sample package and then modifying that package to show that changes only affect the overlay and not the underlying ROS installation.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 58

Creating a workspace

Goal: Create a workspace and learn how to set up an overlay for development and testing.
Tutorial level: Beginner
Time: 20 minutes
Contents
 Background
 Prerequisites
 Tasks
 1 Source ROS 2 environment
 2 Create a new directory
 3 Clone a sample repo
 4 Resolve dependencies
 5 Build the workspace with colcon
 6 Source the overlay
 7 Modify the overlay
o Summary
o Next steps
Background
A workspace is a directory containing ROS 2 packages. Before using ROS 2, it’s necessary to
source your ROS 2 installation workspace in the terminal you plan to work in. This makes ROS 2’s
packages available for you to use in that terminal.
You also have the option of sourcing an “overlay” – a secondary workspace where you can add
new packages without interfering with the existing ROS 2 workspace that you’re extending, or
“underlay”. Your underlay must contain the dependencies of all the packages in your overlay.
Packages in your overlay will override packages in the underlay. It’s also possible to have several
layers of underlays and overlays, with each successive overlay using the packages of its parent
underlays.
Prerequisites
 ROS 2 installation
 colcon installation
 git installation
 turtlesim installation
 Have rosdep installed
 Understanding of basic terminal commands (here’s a guide for Linux)
 Text editor of your choice
Tasks
1 Source ROS 2 environment
Your main ROS 2 installation will be your underlay for this tutorial. (Keep in mind that an underlay
does not necessarily have to be the main ROS 2 installation.)
Depending on how you installed ROS 2 (from source or binaries), and which platform you’re on,
your exact source command will vary:
LinuxmacOSWindows
source /opt/ros/galactic/setup.bash

Consult the installation guide you followed if these commands don’t work for you.
2 Create a new directory
Best practice is to create a new directory for every new workspace. The name doesn’t matter, but
it is helpful to have it indicate the purpose of the workspace. Let’s choose the directory
name  dev_ws , for “development workspace”:
LinuxmacOSWindows
mkdir -p ~/dev_ws/src
cd ~/dev_ws/src

Another best practice is to put any packages in your workspace into the  src  directory. The above
code creates a  src  directory inside  dev_ws  and then navigates into it.
3 Clone a sample repo
Ensure you’re still in the  dev_ws/src  directory before you clone.
In the rest of the beginner developer tutorials, you will create your own packages, but for now you
will practice putting a workspace together using existing packages.
The existing packages you will use are from the  ros_tutorials  repository (repo). If you went
through the “Beginner: CLI Tools” tutorials, you’ll be familiar with  turtlesim , one of the packages in
this repo.
You can see the repo on GitHub.
Notice the “Branch” drop down list to the left above the directories list. When you clone this repo,
add the  -b  argument followed by the branch that corresponds with your ROS 2 distro.
In the  dev_ws/src  directory, run the following command for the distro you’re using:
git clone https://github.com/ros/ros_tutorials.git -b galactic-devel

Now  ros_tutorials  is cloned in your workspace. The  ros_tutorials  repository contains
the  turtlesim  package, which we’ll use in the rest of this tutorial. The other packages in this
repository are not built because they contain a  COLCON_IGNORE  file.
Now you have populated your workspace with a sample package, but it isn’t a fully-functional
workspace yet. You need to resolve dependencies and build the workspace first.
4 Resolve dependencies
Before building the workspace, you need to resolve package dependencies. You may have all the
dependencies already, but best practice is to check for dependencies every time you clone. You
wouldn’t want a build to fail after a long wait because of missing dependencies.
From the root of your workspace ( dev_ws ), run the following command:
LinuxmacOSWindows
# cd if you're still in the ``src`` directory with the ``ros_tutorials`` clone
cd ..
rosdep install -i --from-path src --rosdistro galactic -y

If you installed ROS 2 on Linux from source or the “fat” archive, you will need to use the rosdep
command from their installation instructions. Here are the from-source rosdep section and the “fat”
archive rosdep section.
If you already have all your dependencies, the console will return:
#All required rosdeps installed successfully

Packages declare their dependencies in the package.xml file (you will learn more about packages
in the next tutorial). This command walks through those declarations and installs the ones that are
missing. You can learn more about  rosdep  in another tutorial (coming soon).
5 Build the workspace with colcon
From the root of your workspace ( dev_ws ), you can now build your packages using the command:
LinuxmacOSWindows
colcon build

The console will return the following message:


Starting >>> turtlesim
Finished <<< turtlesim [5.49s]

Summary: 1 package finished [5.58s]

Note
Other useful arguments for  colcon build :
 --packages-up-to  builds the package you want, plus all its dependencies, but not the whole
workspace (saves time)
 --symlink-install  saves you from having to rebuild every time you tweak python scripts

 --event-handlers console_direct+  shows console output while building (can otherwise be found


in the  log  directory)
Once the build is finished, enter  ls  in the workspace root ( ~/dev_ws ) and you will see that colcon
has created new directories:
build install log src

The  install  directory is where your workspace’s setup files are, which you can use to source your
overlay.
6 Source the overlay
Before sourcing the overlay, it is very important that you open a new terminal, separate from the
one where you built the workspace. Sourcing an overlay in the same terminal where you built, or
likewise building where an overlay is sourced, may create complex issues.
In the new terminal, source your main ROS 2 environment as the “underlay”, so you can build the
overlay “on top of” it:
LinuxmacOSWindows
source /opt/ros/galactic/setup.bash

Go into the root of your workspace:


LinuxmacOSWindows
cd ~/dev_ws

In the root, source your overlay:


LinuxmacOSWindows
. install/local_setup.bash

Note
Sourcing the  local_setup  of the overlay will only add the packages available in the overlay to your
environment.  setup  sources the overlay as well as the underlay it was created in, allowing you to
utilize both workspaces.
So, sourcing your main ROS 2 installation’s  setup  and then the  dev_ws  overlay’s  local_setup , like
you just did, is the same as just sourcing  dev_ws ’s  setup , because that includes the environment of
the underlay it was created in.
Now you can run the  turtlesim  package from the overlay:
ros2 run turtlesim turtlesim_node

But how can you tell that this is the overlay turtlesim running, and not your main installation’s
turtlesim?
Let’s modify turtlesim in the overlay so you can see the effects:
 You can modify and rebuild packages in the overlay separately from the underlay.
 The overlay takes precedence over the underlay.
7 Modify the overlay
You can modify  turtlesim  in your overlay by editing the title bar on the turtlesim window. To do
this, locate the  turtle_frame.cpp  file in  ~/dev_ws/src/ros_tutorials/turtlesim/src .
Open  turtle_frame.cpp  with your preferred text editor.
On line 52 you will see the function  setWindowTitle("TurtleSim"); . Change the
value  ”TurtleSim”  to  ”MyTurtleSim” , and save the file.
Return to first terminal where you ran  colcon build  earlier and run it again.
Return to the second terminal (where the overlay is sourced) and run turtlesim again:
ros2 run turtlesim turtlesim_node

You will see the title bar on the turtlesim window now says “MyTurtleSim”.
Even though your main ROS 2 environment was sourced in this terminal earlier, the overlay of
your  dev_ws  environment takes precedence over the contents of the underlay.
To see that your underlay is still intact, open a brand new terminal and source only your ROS 2
installation. Run turtlesim again:
ros2 run turtlesim turtlesim_node
You can see that modifications in the overlay did not actually affect anything in the underlay.
Summary
In this tutorial, you sourced your main ROS 2 distro install as your underlay, and created an
overlay by cloning and building packages in a new workspace. The overlay gets prepended to the
path, and takes precedence over the underlay, as you saw with your modified turtlesim.
Using overlays is recommended for working on a small number of packages, so you don’t have to
put everything in the same workspace and rebuild a huge workspace on every iteration.
Next steps
Now that you understand the details behind creating, building and sourcing your own workspace,
you can learn how to create your own packages.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Creating your first ROS 2 package
Goal: Create a new package using either CMake or Python, and run its executable.
Tutorial level: Beginner
Time: 15 minutes
Contents
 Background
 1 What is a ROS 2 package?
 2 What makes up a ROS 2 package?
 3 Packages in a workspace
o Prerequisites
o Tasks
 1 Create a package
 2 Build a package
 3 Source the setup file
 4 Use the package
 5 Examine package contents
 6 Customize package.xml
o Summary
o Next steps
Background
1 What is a ROS 2 package?
A package can be considered a container for your ROS 2 code. If you want to be able to install
your code or share it with others, then you’ll need it organized in a package. With packages, you
can release your ROS 2 work and allow others to build and use it easily.
Package creation in ROS 2 uses ament as its build system and colcon as its build tool. You can
create a package using either CMake or Python, which are officially supported, though other build
types do exist.
2 What makes up a ROS 2 package?
ROS 2 Python and CMake packages each have their own minimum required contents:
CMakePython
 package.xml  file containing meta information about the package
 CMakeLists.txt  file that describes how to build the code within the package

The simplest possible package may have a file structure that looks like:
CMakePython
my_package/
CMakeLists.txt
package.xml

3 Packages in a workspace
A single workspace can contain as many packages as you want, each in their own folder. You can
also have packages of different build types in one workspace (CMake, Python, etc.). You cannot
have nested packages.
Best practice is to have a  src  folder within your workspace, and to create your packages in there.
This keeps the top level of the workspace “clean”.
A trivial workspace might look like:
workspace_folder/
src/
package_1/
CMakeLists.txt
package.xml

package_2/
setup.py
package.xml
resource/package_2
...
package_n/
CMakeLists.txt
package.xml

Prerequisites
You should have a ROS 2 workspace after following the instructions in the previous tutorial. You
will create your package in this workspace.
Tasks
1 Create a package
First, source your ROS 2 installation.
Let’s use the workspace you created in the previous tutorial,  dev_ws , for your new package.`
Make sure you are in the  src  folder before running the package creation command.
LinuxmacOSWindows
cd ~/dev_ws/src

The command syntax for creating a new package in ROS 2 is:


CMakePython
ros2 pkg create --build-type ament_cmake <package_name>

For this tutorial, you will use the optional argument  --node-name  which creates a simple Hello World
type executable in the package.
Enter the following command in your terminal:
CMakePython
ros2 pkg create --build-type ament_cmake --node-name my_node my_package

You will now have a new folder within your workspace’s  src  directory called  my_package .
After running the command, your terminal will return the message:
CMakePython
going to create a new package
package name: my_package
destination directory: /home/user/dev_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['<name> <email>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source and include folder
creating folder ./my_package/src
creating folder ./my_package/include/my_package
creating ./my_package/CMakeLists.txt
creating ./my_package/src/my_node.cpp

You can see the automatically generated files for the new package.
2 Build a package
Putting packages in a workspace is especially valuable because you can build many packages at
once by running  colcon build  in the workspace root. Otherwise, you would have to build each
package individually.
Return to the root of your workspace:
LinuxmacOSWindows
cd ~/dev_ws

Now you can build your packages:


LinuxmacOSWindows
colcon build

Recall from the last tutorial that you also have the  ros_tutorials  packages in your  dev_ws . You
might’ve noticed that running  colcon build  also built the  turtlesim  package. That’s fine when you
only have a few packages in your workspace, but when there are many
packages,  colcon build  can take a long time.
To build only the  my_package  package next time, you can run:
colcon build --packages-select my_package

3 Source the setup file


To use your new package and executable, first open a new terminal and source your main ROS 2
installation.
Then, from inside the  dev_ws  directory, run the following command to source your workspace:
LinuxmacOSWindows
. install/local_setup.bash

Now that your workspace has been added to your path, you will be able to use your new
package’s executables.
4 Use the package
To run the executable you created using the  --node-name  argument during package creation, enter
the command:
ros2 run my_package my_node

Which will return a message to your terminal:


CMakePython
hello world my_package package

5 Examine package contents


Inside  dev_ws/src/my_package , you will see the files and folders that  ros2 pkg create  automatically
generated:
CMakePython
CMakeLists.txt include package.xml src

my_node.cpp  is inside the  src  directory. This is where all your custom C++ nodes will go in the
future.
6 Customize package.xml
You may have noticed in the return message after creating your package that the
fields  description  and  license  contain  TODO  notes. That’s because the package description and
license declaration are not automatically set, but are required if you ever want to release your
package. The  maintainer  field may also need to be filled in.
From  dev_ws/src/my_package , open  package.xml  using your preferred text editor:
CMakePython
<?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>my_package</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="[email protected]">user</maintainer>
<license>TODO: License declaration</license>

<buildtool_depend>ament_cmake</buildtool_depend>

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

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

Input your name and email on the  maintainer  line if it hasn’t been automatically populated for you.
Then, edit the  description  line to summarize the package:
<description>Beginner client libraries tutorials practice package</description>

Then update the  license  line. You can read more about open source licenses here. Since this
package is only for practice, it’s safe to use any license. We use  Apache License 2.0 :
<license>Apache License 2.0</license>

Don’t forget to save once you’re done editing.


Below the license tag, you will see some tag names ending with  _depend . This is where
your  package.xml  would list its dependencies on other packages, for colcon to search
for.  my_package  is simple and doesn’t have any dependencies, but you will see this space being
utilized in upcoming tutorials.
CMakePython
You’re all done for now!
Summary
You’ve created a package to organize your code and make it easy to use for others.
Your package was automatically populated with the necessary files, and then you used colcon to
build it so you can use its executables in your local environment.
Next steps
Next, let’s add something meaningful to a package. You’ll start with a simple publisher/subscriber
system, which you can choose to write in either C++ or Python.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Writing a simple publisher and subscriber (C++)
Goal: Create and run a publisher and subscriber node using C++.
Tutorial level: Beginner
Time: 20 minutes
Contents
 Background
 Prerequisites
 Tasks
 1 Create a package
 2 Write the publisher node
 3 Write the subscriber node
 4 Build and run
o Summary
o Next steps
o Related content
Background
Nodes are executable processes that communicate over the ROS graph. In this tutorial, the nodes
will pass information in the form of string messages to each other over a topic. The example used
here is a simple “talker” and “listener” system; one node publishes data and the other subscribes
to the topic so it can receive that data.
The code used in these examples can be found here.
Prerequisites
In previous tutorials, you learned how to create a workspace and create a package.
Tasks
1 Create a package
Open a new terminal and source your ROS 2 installation so that  ros2  commands will work.
Navigate into the  dev_ws  directory created in a previous tutorial.
Recall that packages should be created in the  src  directory, not the root of the workspace. So,
navigate into  dev_ws/src , and run the package creation command:
ros2 pkg create --build-type ament_cmake cpp_pubsub

Your terminal will return a message verifying the creation of your package  cpp_pubsub  and all its
necessary files and folders.
Navigate into  dev_ws/src/cpp_pubsub/src . Recall that this is the directory in any CMake package
where the source files containing executables belong.
2 Write the publisher node
Download the example talker code by entering the following command:
LinuxmacOSWindows
wget -O publisher_member_function.cpp
https://raw.githubusercontent.com/ros2/examples/master/rclcpp/topics/minimal_publisher/member_function.cp
p

Now there will be a new file named  publisher_member_function.cpp . Open the file using your
preferred text editor.
#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a


* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node


{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}

private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};

int main(int argc, char * argv[])


{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}

2.1 Examine the code


The top of the code includes the standard C++ headers you will be using. After the standard C++
headers is the  rclcpp/rclcpp.hpp  include which allows you to use the most common pieces of the
ROS 2 system. Last is  std_msgs/msg/string.hpp , which includes the built-in message type you will
use to publish data.
These lines represent the node’s dependencies. Recall that dependencies have to be added
to  package.xml  and  CMakeLists.txt , which you’ll do in the next section.
#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

The next line creates the node class  MinimalPublisher  by inheriting from  rclcpp::Node . Every  this  in
the code is referring to the node.
class MinimalPublisher : public rclcpp::Node
The public constructor names the node  minimal_publisher  and initializes  count_  to 0. Inside the
constructor, the publisher is initialized with the  String  message type, the topic name  topic , and
the required queue size to limit messages in the event of a backup. Next,  timer_  is initialized,
which causes the  timer_callback  function to be executed twice a second.
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}

The  timer_callback  function is where the message data is set and the messages are actually
published. The  RCLCPP_INFO  macro ensures every published message is printed to the console.
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}

Last is the declaration of the timer, publisher, and counter fields.


rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;

Following the  MinimalPublisher  class is  main , where the node actually
executes.  rclcpp::init  initializes ROS 2, and  rclcpp::spin  starts processing data from the node,
including callbacks from the timer.
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}

2.2 Add dependencies


Navigate one level back to the  dev_ws/src/cpp_pubsub  directory, where
the  CMakeLists.txt  and  package.xml  files have been created for you.
Open  package.xml  with your text editor.
As mentioned in the previous tutorial, make sure to fill in
the  <description> ,  <maintainer>  and  <license>  tags:
<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>
Add a new line after the  ament_cmake  buildtool dependency and paste the following dependencies
corresponding to your node’s include statements:
<depend>rclcpp</depend>
<depend>std_msgs</depend>

This declares the package needs  rclcpp  and  std_msgs  when its code is executed.
Make sure to save the file.
2.3 CMakeLists.txt
Now open the  CMakeLists.txt  file. Below the existing
dependency  find_package(ament_cmake REQUIRED) , add the lines:
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

After that, add the executable and name it  talker  so you can run your node using  ros2 run :
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

Finally, add the  install(TARGETS…)  section so  ros2 run  can find your executable:
install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})

You can clean up your  CMakeLists.txt  by removing some unnecessary sections and comments, so
it looks like this:
cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")


add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})

ament_package()

You could build your package now, source the local setup files, and run it, but let’s create the
subscriber node first so you can see the full system at work.
3 Write the subscriber node
Return to  dev_ws/src/cpp_pubsub/src  to create the next node. Enter the following code in your
terminal:
LinuxmacOSWindows
wget -O subscriber_member_function.cpp
https://raw.githubusercontent.com/ros2/examples/master/rclcpp/topics/minimal_subscriber/member_function.c
pp

Entering  ls  in the console will now return:


publisher_member_function.cpp subscriber_member_function.cpp

Open the  subscriber_member_function.cpp  with your text editor.


#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node


{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}

private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])


{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}

3.1 Examine the code


The subscriber node’s code is nearly identical to the publisher’s. Now the node is
named  minimal_subscriber , and the constructor uses the node’s  create_subscription  class to
execute the callback.
There is no timer because the subscriber simply responds whenever data is published to
the  topic  topic.
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}

Recall from the topic tutorial that the topic name and message type used by the publisher and
subscriber must match to allow them to communicate.
The  topic_callback  function receives the string message data published over the topic, and simply
writes it to the console using the  RCLCPP_INFO  macro.
The only field declaration in this class is the subscription.
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;

The  main  function is exactly the same, except now it spins the  MinimalSubscriber  node. For the
publisher node, spinning meant starting the timer, but for the subscriber it simply means preparing
to receive messages whenever they come.
Since this node has the same dependencies as the publisher node, there’s nothing new to add
to  package.xml .
3.2 CMakeLists.txt
Reopen  CMakeLists.txt  and add the executable and target for the subscriber node below the
publisher’s entries.
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})

Make sure to save the file, and then your pub/sub system should be ready for use.
4 Build and run
You likely already have the  rclcpp  and  std_msgs  packages installed as part of your ROS 2 system.
It’s good practice to run  rosdep  in the root of your workspace ( dev_ws ) to check for missing
dependencies before building:
LinuxmacOSWindows
rosdep install -i --from-path src --rosdistro galactic -y

Still in the root of your workspace,  dev_ws , build your new package:
LinuxmacOSWindows
colcon build --packages-select cpp_pubsub

Open a new terminal, navigate to  dev_ws , and source the setup files:
LinuxmacOSWindows
. install/setup.bash

Now run the talker node:


ros2 run cpp_pubsub talker

The terminal should start publishing info messages every 0.5 seconds, like so:
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [minimal_publisher]: Publishing: "Hello World: 4"

Open another terminal, source the setup files from inside  dev_ws  again, and then start the listener
node:
ros2 run cpp_pubsub listener

The listener will start printing messages to the console, starting at whatever message count the
publisher is on at that time, like so:
[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"

Enter  Ctrl+C  in each terminal to stop the nodes from spinning.
Summary
You created two nodes to publish and subscribe to data over a topic. Before compiling and
running them, you added their dependencies and executables to the package configuration files.
Next steps
Next you’ll create another simple ROS 2 package using the service/client model. Again, you can
choose to write it in either C++ or Python.
Related content
There are several ways you could write a publisher and subscriber in C++; check out
the  minimal_publisher  and  minimal_subscriber  packages in the ros2/examples repo.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Writing a simple service and client (C++)
Goal: Create and run service and client nodes using C++.
Tutorial level: Beginner
Time: 20 minutes
Contents
 Background
 Prerequisites
 Tasks
 1 Create a package
 2 Write the service node
 3 Write the client node
 4 Build and run
o Summary
o Next steps
o Related content
Background
When nodes communicate using services, the node that sends a request for data is called the
client node, and the one that responds to the request is the service node. The structure of the
request and response is determined by a  .srv  file.
The example used here is a simple integer addition system; one node requests the sum of two
integers, and the other responds with the result.
Prerequisites
In previous tutorials, you learned how to create a workspace and create a package.
Tasks
1 Create a package
Open a new terminal and source your ROS 2 installation so that  ros2  commands will work.
Navigate into the  dev_ws  directory created in a previous tutorial.
Recall that packages should be created in the  src  directory, not the root of the workspace.
Navigate into  dev_ws/src  and create a new package:
ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces

Your terminal will return a message verifying the creation of your package  cpp_srvcli  and all its
necessary files and folders.
The  --dependencies  argument will automatically add the necessary dependency lines
to  package.xml  and  CMakeLists.txt .  example_interfaces  is the package that includes the .srv file you
will need to structure your requests and responses:
int64 a
int64 b
---
int64 sum

The first two lines are the parameters of the request, and below the dashes is the response.
1.1 Update  package.xml 
Because you used the  --dependencies  option during package creation, you don’t have to manually
add dependencies to  package.xml  or  CMakeLists.txt .
As always, though, make sure to add the description, maintainer email and name, and license
information to  package.xml .
<description>C++ client server tutorial</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>

2 Write the service node


Inside the  dev_ws/src/cpp_srvcli/src  directory, create a new file called  add_two_ints_server.cpp  and
paste the following code within:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"

#include <memory>

void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,


std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
response->sum = request->a + request->b;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
request->a, request->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}

int main(int argc, char **argv)


{
rclcpp::init(argc, argv);

std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");

rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);

RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");

rclcpp::spin(node);
rclcpp::shutdown();
}

2.1 Examine the code


The first two  #include  statements are your package dependencies.
The  add  function adds two integers from the request and gives the sum to the response, while
notifying the console of its status using logs.
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
response->sum = request->a + request->b;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
request->a, request->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}

The  main  function accomplishes the following, line by line:


 Initializes ROS 2 C++ client library:
 rclcpp::init(argc, argv);

 Creates a node named  add_two_ints_server :


 std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");

 Creates a service named  add_two_ints  for that node and automatically advertises it over the
networks with the  &add  method:
 rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
 node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);

 Prints a log message when it’s ready:


 RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");

 Spins the node, making the service available.


 rclcpp::spin(node);

2.2 Add executable


The  add_executable  macro generates an executable you can run using  ros2 run . Add the following
code block to  CMakeLists.txt  to create an executable named  server :
add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)

So  ros2 run  can find the executable, add the following lines to the end of the file, right
before  ament_package() :
install(TARGETS
server
DESTINATION lib/${PROJECT_NAME})

You could build your package now, source the local setup files, and run it, but let’s create the
client node first so you can see the full system at work.
3 Write the client node
Inside the  dev_ws/src/cpp_srvcli/src  directory, create a new file called  add_two_ints_client.cpp  and
paste the following code within:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"

#include <chrono>
#include <cstdlib>
#include <memory>

using namespace std::chrono_literals;

int main(int argc, char **argv)


{
rclcpp::init(argc, argv);

if (argc != 3) {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_two_ints_client X Y");
return 1;
}

std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");


rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();


request->a = atoll(argv[1]);
request->b = atoll(argv[2]);

while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return 0;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}

auto result = client->async_send_request(request);


// Wait for the result.
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
} else {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_two_ints");
}

rclcpp::shutdown();
return 0;
}
3.1 Examine the code
Similar to the service node, the following lines of code create the node and then create the client
for that node:
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

Next, the request is created. Its structure is defined by the  .srv  file mentioned earlier.
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);

The  while  loop gives the client 1 second to search for service nodes in the network. If it can’t find
any, it will continue waiting.
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");

If the client is canceled (e.g. by you entering  Ctrl+C  into the terminal), it will return an error log
message stating it was interrupted.
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return 0;

Then the client sends its request, and the node spins until it receives its response, or fails.
3.2 Add executable
Return to  CMakeLists.txt  to add the executable and target for the new node. After removing some
unnecessary boilerplate from the automatically generated file, your  CMakeLists.txt  should look like
this:
cmake_minimum_required(VERSION 3.5)
project(cpp_srvcli)

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)

add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)

add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
rclcpp example_interfaces)

install(TARGETS
server
client
DESTINATION lib/${PROJECT_NAME})

ament_package()

4 Build and run


It’s good practice to run  rosdep  in the root of your workspace ( dev_ws ) to check for missing
dependencies before building:
LinuxmacOSWindows
rosdep install -i --from-path src --rosdistro galactic -y
Navigate back to the root of your workspace,  dev_ws , and build your new package:
LinuxmacOSWindows
colcon build --packages-select cpp_srvcli

Open a new terminal, navigate to  dev_ws , and source the setup files:
LinuxmacOSWindows
. install/setup.bash

Now run the service node:


ros2 run cpp_srvcli server

The terminal should return the following message, and then wait:
[INFO] [rclcpp]: Ready to add two ints.

Open another terminal, source the setup files from inside  dev_ws  again. Start the client node,
followed by any two integers separated by a space:
ros2 run cpp_srvcli client 2 3

If you chose  2  and  3 , for example, the client would receive a response like this:
[INFO] [rclcpp]: Sum: 5

Return to the terminal where your service node is running. You will see that it published log
messages when it received the request and the data it received, and the response it sent back:
[INFO] [rclcpp]: Incoming request
a: 2 b: 3
[INFO] [rclcpp]: sending back response: [5]

Enter  Ctrl+C  in the server terminal to stop the node from spinning.
Summary
You created two nodes to request and respond to data over a service. You added their
dependencies and executables to the package configuration files so that you could build and run
them, and see a service/client system at work
Next steps
In the last few tutorials you’ve been utilizing interfaces to pass data across topics and services.
Next, you’ll learn how to create custom interfaces.
Related content
 There are several ways you could write a service and client in C++; check out
the  minimal_service  and  minimal_client  packages in the ros2/examples repo.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Writing a simple service and client (Python) 


Goal: Create and run service and client nodes using Python.
Tutorial level: Beginner
Time: 20 minutes
Contents
 Background
 Prerequisites
 Tasks
 1 Create a package
 2 Write the service node
 3 Write the client node
 4 Build and run
o Summary
o Next steps
o Related content
Background
When nodes communicate using services, the node that sends a request for data is called the
client node, and the one that responds to the request is the service node. The structure of the
request and response is determined by a  .srv  file.
The example used here is a simple integer addition system; one node requests the sum of two
integers, and the other responds with the result.
Prerequisites
In previous tutorials, you learned how to create a workspace and create a package.
Tasks
1 Create a package
Open a new terminal and source your ROS 2 installation so that  ros2  commands will work.
Navigate into the  dev_ws  directory created in a previous tutorial.
Recall that packages should be created in the  src  directory, not the root of the workspace.
Navigate into  dev_ws/src  and create a new package:
ros2 pkg create --build-type ament_python py_srvcli --dependencies rclpy example_interfaces

Your terminal will return a message verifying the creation of your package  py_srvcli  and all its
necessary files and folders.
The  --dependencies  argument will automatically add the necessary dependency lines
to  package.xml .  example_interfaces  is the package that includes the .srv file you will need to
structure your requests and responses:
int64 a
int64 b
---
int64 sum

The first two lines are the parameters of the request, and below the dashes is the response.
1.1 Update  package.xml 
Because you used the  --dependencies  option during package creation, you don’t have to manually
add dependencies to  package.xml .
As always, though, make sure to add the description, maintainer email and name, and license
information to  package.xml .
<description>Python client server tutorial</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>

1.2 Update  setup.py 


Add the same information to the  setup.py  file for
the  maintainer ,  maintainer_email ,  description  and  license  fields:
maintainer='Your Name',
maintainer_email='[email protected]',
description='Python client server tutorial',
license='Apache License 2.0',

2 Write the service node


Inside the  dev_ws/src/py_srvcli/py_srvcli  directory, create a new file
called  service_member_function.py  and paste the following code within:
from example_interfaces.srv import AddTwoInts

import rclpy
from rclpy.node import Node

class MinimalService(Node):

def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

def add_two_ints_callback(self, request, response):


response.sum = request.a + request.b
self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))

return response

def main():
rclpy.init()

minimal_service = MinimalService()

rclpy.spin(minimal_service)

rclpy.shutdown()

if __name__ == '__main__':
main()

2.1 Examine the code


The first  import  statement imports the  AddTwoInts  service type from
the  example_interfaces  package. The following  import  statement imports the ROS 2 Python client
library, and specifically the  Node  class.
from example_interfaces.srv import AddTwoInts

import rclpy
from rclpy.node import Node
The  MinimalService  class constructor initializes the node with the name  minimal_service . Then, it
creates a service and defines the type, name, and callback.
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

The definition of the service callback receives the request data, sums it, and returns the sum as a
response.
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))

return response

Finally, the main class initializes the ROS 2 Python client library, instantiates
the  MinimalService  class to create the service node and spins the node to handle callbacks.
2.2 Add an entry point
To allow the  ros2 run  command to run your node, you must add the entry point
to  setup.py  (located in the  dev_ws/src/py_srvcli  directory).
Add the following line between the  'console_scripts':  brackets:
'service = py_srvcli.service_member_function:main',

3 Write the client node


Inside the  dev_ws/src/py_srvcli/py_srvcli  directory, create a new file
called  client_member_function.py  and paste the following code within:
import sys

from example_interfaces.srv import AddTwoInts


import rclpy
from rclpy.node import Node

class MinimalClientAsync(Node):

def __init__(self):
super().__init__('minimal_client_async')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = AddTwoInts.Request()

def send_request(self):
self.req.a = int(sys.argv[1])
self.req.b = int(sys.argv[2])
self.future = self.cli.call_async(self.req)

def main():
rclpy.init()

minimal_client = MinimalClientAsync()
minimal_client.send_request()

while rclpy.ok():
rclpy.spin_once(minimal_client)
if minimal_client.future.done():
try:
response = minimal_client.future.result()
except Exception as e:
minimal_client.get_logger().info(
'Service call failed %r' % (e,))
else:
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(minimal_client.req.a, minimal_client.req.b, response.sum))
break

minimal_client.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

3.1 Examine the code


The only different  import  statement for the client is  import sys . The client node code
uses sys.argv to get access to command line input arguments for the request.
The constructor definition creates a client with the same type and name as the service node. The
type and name must match for the client and service to be able to communicate.
The  while  loop in the constructor checks if a service matching the type and name of the client is
available once a second.
Below the constructor is the request definition, followed by  main .
The only significant difference in the client’s  main  is the  while  loop. The loop checks the  future  to
see if there is a response from the service, as long as the system is running. If the service has
sent a response, the result will be written in a log message.
3.2 Add an entry point
Like the service node, you also have to add an entry point to be able to run the client node.
The  entry_points  field of your  setup.py  file should look like this:
entry_points={
'console_scripts': [
'service = py_srvcli.service_member_function:main',
'client = py_srvcli.client_member_function:main',
],
},

4 Build and run


It’s good practice to run  rosdep  in the root of your workspace ( dev_ws ) to check for missing
dependencies before building:
LinuxmacOSWindows
rosdep install -i --from-path src --rosdistro galactic -y

Navigate back to the root of your workspace,  dev_ws , and build your new package:
colcon build --packages-select py_srvcli

Open a new terminal, navigate to  dev_ws , and source the setup files:
LinuxmacOSWindows
. install/setup.bash

Now run the service node:


ros2 run py_srvcli service
The node will wait for the client’s request.
Open another terminal and source the setup files from inside  dev_ws  again. Start the client node,
followed by any two integers separated by a space:
ros2 run py_srvcli client 2 3

If you chose  2  and  3 , for example, the client would receive a response like this:
[INFO] [minimal_client_async]: Result of add_two_ints: for 2 + 3 = 5

Return to the terminal where your service node is running. You will see that it published log
messages when it received the request:
[INFO] [minimal_service]: Incoming request
a: 2 b: 3

Enter  Ctrl+C  in the server terminal to stop the node from spinning.
Summary
You created two nodes to request and respond to data over a service. You added their
dependencies and executables to the package configuration files so that you could build and run
them, allowing you to see a service/client system at work.
Next steps
In the last few tutorials you’ve been utilizing interfaces to pass data across topics and services.
Next, you’ll learn how to create custom interfaces.
Related content
 There are several ways you could write a service and client in Python; check out
the  minimal_client  and  minimal_service  packages in the ros2/examples repo.
 In this tutorial, you used the  call_async()  API in your client node to call the service. There is
another service call API available for Python called synchronous calls. We do not recommend
using synchronous calls, but if you’d like to learn more about them, read the guide
to Synchronous vs. asynchronous clients.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Creating custom ROS 2 msg and srv files


Goal: Define custom interface files ( .msg  and  .srv ) and use them with Python and C++ nodes.
Tutorial level: Beginner
Time: 20 minutes
Contents
 Background
 Prerequisites
 Tasks
 1 Create a new package
 2 Create custom definitions
 3 CMakeLists.txt

 4 package.xml
 5 Build the  tutorial_interfaces  package
 6 Confirm msg and srv creation
 7 Test the new interfaces
o Summary
o Next steps
Background
In previous tutorials you utilized message and service interfaces to learn about topics, services,
and simple publisher/subscriber (C++/Python) and service/client (C++/Python) nodes. The
interfaces you used were predefined in those cases.
While it’s good practice to use predefined interface definitions, you will probably need to define
your own messages and services sometimes as well. This tutorial will introduce you to the
simplest method of creating custom interface definitions.
Prerequisites
You should have a ROS 2 workspace.
This tutorial also uses the packages created in the publisher/subscriber (C++ and Python) and
service/client (C++ and Python) tutorials to try out the new custom messages.
Tasks
1 Create a new package
For this tutorial you will be creating custom  .msg  and  .srv  files in their own package, and then
utilizing them in a separate package. Both packages should be in the same workspace.
Since we will use the pub/sub and service/client packages created in earlier tutorials, make sure
you are in the same workspace as those packages ( dev_ws/src ), and then run the following
command to create a new package:
ros2 pkg create --build-type ament_cmake tutorial_interfaces

tutorial_interfaces  is the name of the new package. Note that it is a CMake package; there
currently isn’t a way to generate a  .msg  or  .srv  file in a pure Python package. You can create a
custom interface in a CMake package, and then use it in a Python node, which will be covered in
the last section.
It is good practice to keep  .msg  and  .srv  files in their own directories within a package. Create the
directories in  dev_ws/src/tutorial_interfaces :
mkdir msg

mkdir srv

2 Create custom definitions


2.1 msg definition
In the  tutorial_interfaces/msg  directory you just created, make a new file called  Num.msg  with one
line of code declaring its data structure:
int64 num

This is your custom message that transfers a single 64-bit integer called  num .
2.2 srv definition
Back in the  tutorial_interfaces/srv  directory you just created, make a new file
called  AddThreeInts.srv  with the following request and response structure:
int64 a
int64 b
int64 c
---
int64 sum

This is your custom service that requests three integers named  a ,  b , and  c , and responds with
an integer called  sum .
3  CMakeLists.txt 
To convert the interfaces you defined into language-specific code (like C++ and Python) so that
they can be used in those languages, add the following lines to  CMakeLists.txt :
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Num.msg"
"srv/AddThreeInts.srv"
)

4  package.xml 
Because the interfaces rely on  rosidl_default_generators  for generating language-specific code,
you need to declare a dependency on it. Add the following lines to  package.xml
<build_depend>rosidl_default_generators</build_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

5 Build the  tutorial_interfaces  package


Now that all the parts of your custom interfaces package are in place, you can build the package.
In the root of your workspace ( ~/dev_ws ), run the following command:
LinuxmacOSWindows
colcon build --packages-select tutorial_interfaces

Now the interfaces will be discoverable by other ROS 2 packages.


6 Confirm msg and srv creation
In a new terminal, run the following command from within your workspace ( dev_ws ) to source it:
LinuxmacOSWindows
. install/setup.bash

Now you can confirm that your interface creation worked by using
the  ros2 interface show  command:
ros2 interface show tutorial_interfaces/msg/Num

should return:
int64 num
And
ros2 interface show tutorial_interfaces/srv/AddThreeInts

should return:
int64 a
int64 b
int64 c
---
int64 sum

7 Test the new interfaces


For this step you can use the packages you created in previous tutorials. A few simple
modifications to the nodes,  CMakeLists  and  package  files will allow you to use your new interfaces.
7.1 Testing  Num.msg  with pub/sub
With some slight modifications to the publisher/subscriber package created in a previous tutorial
(C++ or Python), you can see  Num.msg  in action. Since you’ll be changing the standard string msg
to a numerical one, the output will be slightly different.
Publisher:
C++Python
#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp" // CHANGE

using namespace std::chrono_literals;

class MinimalPublisher : public rclcpp::Node


{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<tutorial_interfaces::msg::Num>("topic", 10); // CHANGE
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}

private:
void timer_callback()
{
auto message = tutorial_interfaces::msg::Num(); // CHANGE
message.num = this->count_++; // CHANGE
RCLCPP_INFO_STREAM(this->get_logger(), "Publishing: '" << message.num << "'"); // CHANGE
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<tutorial_interfaces::msg::Num>::SharedPtr publisher_; // CHANGE
size_t count_;
};

int main(int argc, char * argv[])


{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}
Subscriber:
C++Python
#include <functional>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp" // CHANGE

using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node


{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<tutorial_interfaces::msg::Num>( // CHANGE
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}

private:
void topic_callback(const tutorial_interfaces::msg::Num::SharedPtr msg) const // CHANGE
{
RCLCPP_INFO_STREAM(this->get_logger(), "I heard: '" << msg->num << "'"); // CHANGE
}
rclcpp::Subscription<tutorial_interfaces::msg::Num>::SharedPtr subscription_; // CHANGE
};

int main(int argc, char * argv[])


{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}

CMakeLists.txt:
Add the following lines (C++ only):
#...

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED) # CHANGE

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces) # CHANGE

add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces) # CHANGE

install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})

ament_package()

package.xml:
Add the following line:
C++Python
<depend>tutorial_interfaces</depend>

After making the above edits and saving all the changes, build the package:
C++Python
colcon build --packages-select cpp_pubsub

On Windows:
colcon build --merge-install --packages-select cpp_pubsub

Then open two new terminals, source  dev_ws  in each, and run:
C++Python
ros2 run cpp_pubsub talker

ros2 run cpp_pubsub listener

Since  Num.msg  relays only an integer, the talker should only be publishing integer values, as
opposed to the string it published previously:
[INFO] [minimal_publisher]: Publishing: '0'
[INFO] [minimal_publisher]: Publishing: '1'
[INFO] [minimal_publisher]: Publishing: '2'

7.2 Testing  AddThreeInts.srv  with service/client


With some slight modifications to the service/client package created in a previous tutorial (C+
+ or Python), you can see  AddThreeInts.srv  in action. Since you’ll be changing the original two
integer request srv to a three integer request srv, the output will be slightly different.
Service:
C++Python
#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp" // CHANGE

#include <memory>

void add(const std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Request> request, // CHANGE


std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Response> response) // CHANGE
{
response->sum = request->a + request->b + request->c; // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld" " c: %ld", // CHANGE
request->a, request->b, request->c); // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}

int main(int argc, char **argv)


{
rclcpp::init(argc, argv);

std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_server"); // CHANGE

rclcpp::Service<tutorial_interfaces::srv::AddThreeInts>::SharedPtr service = // CHANGE


node->create_service<tutorial_interfaces::srv::AddThreeInts>("add_three_ints", &add); // CHANGE

RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add three ints."); // CHANGE

rclcpp::spin(node);
rclcpp::shutdown();
}

Client:
C++Python
#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp" // CHANGE

#include <chrono>
#include <cstdlib>
#include <memory>

using namespace std::chrono_literals;

int main(int argc, char **argv)


{
rclcpp::init(argc, argv);

if (argc != 4) { // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_three_ints_client X Y Z"); // CHANGE
return 1;
}

std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_client"); // CHANGE


rclcpp::Client<tutorial_interfaces::srv::AddThreeInts>::SharedPtr client = // CHANGE
node->create_client<tutorial_interfaces::srv::AddThreeInts>("add_three_ints"); // CHANGE

auto request = std::make_shared<tutorial_interfaces::srv::AddThreeInts::Request>(); // CHANGE


request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
request->c = atoll(argv[3]); // CHANGE

while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return 0;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}

auto result = client->async_send_request(request);


// Wait for the result.
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
} else {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_three_ints"); // CHANGE
}

rclcpp::shutdown();
return 0;
}

CMakeLists.txt:
Add the following lines (C++ only):
#...

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED) # CHANGE

add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp tutorial_interfaces) # CHANGE

add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
rclcpp tutorial_interfaces) # CHANGE

install(TARGETS
server
client
DESTINATION lib/${PROJECT_NAME})

ament_package()
package.xml:
Add the following line:
C++Python
<depend>tutorial_interfaces</depend>

After making the above edits and saving all the changes, build the package:
C++Python
colcon build --packages-select cpp_srvcli

On Windows:
colcon build --merge-install --packages-select cpp_srvcli

Then open two new terminals, source  dev_ws  in each, and run:
C++Python
ros2 run cpp_srvcli server

ros2 run cpp_srvcli client 2 3 1

Summary
In this tutorial, you learned how to create custom interfaces in their own package and how to utilize
those interfaces from within other packages.
This is a simple method of interface creation and utilization. You can learn more about
interfaces here.
Next steps
The next tutorial covers more ways to use interfaces in ROS 2.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Expanding on ROS 2 interfaces


Goal: Learn more ways to implement custom interfaces in ROS 2
Tutorial level: Beginner
Time: 15 minutes
Contents
 Background
 Prerequisites
 Tasks
 1 Create a package
 2 Create a msg file
 3 Use an interface from the same package
 4 Try it out
 5 (Extra) Use an existing interface definition
o Summary
o Next steps
o Related content
Background
In a previous tutorial, you learned how to create custom msg and srv interfaces.
While best practice is to declare interfaces in dedicated interface packages, sometimes it can be
convenient to declare, create and use an interface all in one package.
Recall that interfaces can currently only be defined in CMake packages. It is possible, however, to
have Python libraries and nodes in CMake packages (using ament_cmake_python), so you could
define interfaces and Python nodes together in one package. We’ll use a CMake package and C+
+ nodes here for the sake of simplicity.
This tutorial will focus on the msg interface type, but the steps here are applicable to all interface
types.
Prerequisites
We assume you’ve reviewed the basics in the Creating custom ROS 2 msg and srv files tutorial
before working through this one.
You should have ROS 2 installed, a workspace, and an understanding of creating packages.
As always, don’t forget to source ROS 2 in every new terminal you open.
Tasks
1 Create a package
In your workspace  src  directory, create a package  more_interfaces  and make a folder within it for
msg files:
ros2 pkg create --build-type ament_cmake more_interfaces
mkdir more_interfaces/msg

2 Create a msg file


Inside  more_interfaces/msg , create a new file  AddressBook.msg
Paste the following code to create a message meant to carry information about an individual:
bool FEMALE=true
bool MALE=false

string first_name
string last_name
bool gender
uint8 age
string address

This message is composed of 5 fields:


 first_name: of type string
 last_name: of type string
 gender: of type bool, that can be either MALE or FEMALE
 age: of type uint8
 address: of type string
Notice that it’s possible to set default values for fields within the message definition. See About
ROS 2 interfaces for more ways you can customize interfaces.
Next, we need to make sure that the msg file is turned into source code for C++, Python, and other
languages.
2.1 Build a msg file
Open  package.xml , and add the following lines:
<buildtool_depend>rosidl_default_generators</buildtool_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

Note that at build time, we need  rosidl_default_generators , while at runtime, we only


need  rosidl_default_runtime .
Open  CMakeLists.txt  and add the following lines:
Find the package that generates message code from msg/srv files:
find_package(rosidl_default_generators REQUIRED)

Declare the list of messages you want to generate:


set(msg_files
"msg/AddressBook.msg"
)

By adding the .msg files manually, we make sure that CMake knows when it has to reconfigure the
project after you add other .msg files.
Generate the messages:
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
)

Also make sure you export the message runtime dependency:


ament_export_dependencies(rosidl_default_runtime)

Now you’re ready to generate source files from your msg definition. We’ll skip the compile step for
now as we do it all together below in step 4.
2.2 (Extra) Set multiple interfaces
Note
You can use  set  to neatly list all of your interfaces:
set(msg_files
"msg/Message1.msg"
"msg/Message2.msg"
# etc
)

set(srv_files
"srv/Service1.srv"
"srv/Service2.srv"
# etc
)

And generate all lists at once like so:


rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
${srv_files}
)
3 Use an interface from the same package 
Now we can start writing code that uses this message.
In  more_interfaces/src  create a file called  publish_address_book.cpp  and paste the following code:
#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "more_interfaces/msg/address_book.hpp"

using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node


{
public:
AddressBookPublisher()
: Node("address_book_publisher")
{
address_book_publisher_ =
this->create_publisher<more_interfaces::msg::AddressBook>("address_book", 10);

auto publish_msg = [this]() -> void {


auto message = more_interfaces::msg::AddressBook();

message.first_name = "John";
message.last_name = "Doe";
message.age = 30;
message.gender = message.MALE;
message.address = "unknown";

std::cout << "Publishing Contact\nFirst:" << message.first_name <<


" Last:" << message.last_name << std::endl;

this->address_book_publisher_->publish(message);
};
timer_ = this->create_wall_timer(1s, publish_msg);
}

private:
rclcpp::Publisher<more_interfaces::msg::AddressBook>::SharedPtr address_book_publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char * argv[])


{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<AddressBookPublisher>());
rclcpp::shutdown();

return 0;
}

3.1 The code explained


#include "more_interfaces/msg/address_book.hpp"

Include the header of our newly created  AddressBook.msg .


using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node


{
public:
AddressBookPublisher()
: Node("address_book_publisher")
{
address_book_publisher_ =
this->create_publisher<more_interfaces::msg::AddressBook>("address_book");
Create a node and an  AddressBook  publisher.
auto publish_msg = [this]() -> void {

Create a callback to publish the messages periodically.


auto message = more_interfaces::msg::AddressBook();

Create an  AddressBook  message instance that we will later publish.


message.first_name = "John";
message.last_name = "Doe";
message.age = 30;
message.gender = message.MALE;
message.address = "unknown";

Populate  AddressBook  fields.


std::cout << "Publishing Contact\nFirst:" << message.first_name <<
" Last:" << message.last_name << std::endl;

this->address_book_publisher_->publish(message);

Finally send the message periodically.


timer_ = this->create_wall_timer(1s, publish_msg);

Create a 1 second timer to call our  publish_msg  function every second.


3.2 Build the publisher
We need to create a new target for this node in the  CMakeLists.txt :
find_package(rclcpp REQUIRED)

add_executable(publish_address_book
src/publish_address_book.cpp
)

ament_target_dependencies(publish_address_book
"rclcpp"
)

install(TARGETS publish_address_book
DESTINATION lib/${PROJECT_NAME})

3.3 Link against the interface


In order to use the messages generated in the same package we need to use the following CMake
code:
rosidl_target_interfaces(publish_address_book
${PROJECT_NAME} "rosidl_typesupport_cpp")

This finds the relevant generated C++ code from  AddressBook.msg  and allows your target to link
against it.
You may have noticed that this step was not necessary when the interfaces being used were from
a package that was built separately. This CMake code is only required when you want to use
interfaces in the same package as the one in which they are used.
4 Try it out
Return to the root of the workspace to build the package:
LinuxmacOSWindows
cd ~/dev_ws
colcon build --packages-up-to more_interfaces

Then source the workspace and run the publisher:


LinuxmacOSWindows
. install/local_setup.bash
ros2 run more_interfaces publish_address_book

You should see the publisher relaying the msg you defined, including the values you set
in  publish_address_book.cpp .
To confirm the message is being published on the  address_book  topic, open another terminal,
source the workspace, and call  topic echo :
LinuxmacOSWindows
. install/setup.bash
ros2 topic echo /address_book

We won’t create a subscriber in this tutorial, but you can try to write one yourself for practice
(use Writing a simple publisher and subscriber (C++) to help).
5 (Extra) Use an existing interface definition 
Note
You can use an existing interface definition in a new interface definition. For example, let’s say
there is a message named  Contact.msg  that belongs to an existing ROS 2 package
named  rosidl_tutorials_msgs . Assume that its definition is identical to our custom-
made  AddressBook.msg  interface from earlier.
In that case you could have defined  AddressBook.msg  (an interface in the package with your nodes)
as type  Contact  (an interface in a separate package). You could even define  AddressBook.msg  as
an array of type  Contact , like so:
rosidl_tutorials_msgs/Contact[] address_book

To generate this message you would need to declare a dependency


on  Contact.msg's  package,  rosidl_tutorials_msgs , in  package.xml :
<build_depend>rosidl_tutorials_msgs</build_depend>

<exec_depend>rosidl_tutorials_msgs</exec_depend>

And in  CMakeLists.txt :


find_package(rosidl_tutorials_msgs REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
DEPENDENCIES rosidl_tutorials_msgs
)

You would also need to include the header of  Contact.msg  in you publisher node in order to be able
to add  contacts  to your  address_book .
#include "rosidl_tutorials_msgs/msg/contact.hpp"

You could change the call back to something like this:


auto publish_msg = [this]() -> void {
auto msg = std::make_shared<more_interfaces::msg::AddressBook>();
{
rosidl_tutorials_msgs::msg::Contact contact;
contact.first_name = "John";
contact.last_name = "Doe";
contact.age = 30;
contact.gender = contact.MALE;
contact.address = "unknown";
msg->address_book.push_back(contact);
}
{
rosidl_tutorials_msgs::msg::Contact contact;
contact.first_name = "Jane";
contact.last_name = "Doe";
contact.age = 20;
contact.gender = contact.FEMALE;
contact.address = "unknown";
msg->address_book.push_back(contact);
}

std::cout << "Publishing address book:" << std::endl;


for (auto contact : msg->address_book) {
std::cout << "First:" << contact.first_name << " Last:" << contact.last_name <<
std::endl;
}

address_book_publisher_->publish(*msg);
};

Building and running these changes would show the msg defined as expected, as well as the
array of msgs defined above.
Summary
In this tutorial, you tried out different field types for defining interfaces, then built an interface in the
same package where it’s being used.
You also learned how to use another interface as a field type, as well as
the  package.xml ,  CMakeLists.txt , and  #include  statements necessary for utilizing that feature.
Next steps
Next you will create a simple ROS 2 package with a custom parameter that you will learn to set
from a launch file. Again, you can choose to write it in either C++ or Python.
Related content
There are several design articles on ROS 2 interfaces and the IDL (interface definition language).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Using parameters in a class (C++)


Goal: Create and run a class with ROS parameters using C++.
Tutorial level: Beginner
Time: 20 minutes
Contents
 Background
 Prerequisites
 Tasks
 1 Create a package
 2 Write the C++ node
 3 Build and run
o Summary
o Next steps
Background
When making your own nodes you will sometimes need to add parameters that can be set from
the launch file.
This tutorial will show you how to create those parameters in a C++ class, and how to set them in
a launch file.
Prerequisites
In previous tutorials, you learned how to create a workspace and create a package. You have also
learned about parameters and their function in a ROS 2 system.
Tasks
1 Create a package
Open a new terminal and source your ROS 2 installation so that  ros2  commands will work.
Navigate into the  dev_ws  directory created in a previous tutorial.
Recall that packages should be created in the  src  directory, not the root of the workspace.
Navigate into  dev_ws/src  and create a new package:
ros2 pkg create --build-type ament_cmake cpp_parameters --dependencies rclcpp

Your terminal will return a message verifying the creation of your package  cpp_parameters  and all its
necessary files and folders.
The  --dependencies  argument will automatically add the necessary dependency lines
to  package.xml  and  CMakeLists.txt .
1.1 Update  package.xml 
Because you used the  --dependencies  option during package creation, you don’t have to manually
add dependencies to  package.xml  or  CMakeLists.txt .
As always, though, make sure to add the description, maintainer email and name, and license
information to  package.xml .
<description>C++ parameter tutorial</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>

2 Write the C++ node


Inside the  dev_ws/src/cpp_parameters/src  directory, create a new file
called  cpp_parameters_node.cpp  and paste the following code within:
#include <rclcpp/rclcpp.hpp>
#include <chrono>
#include <string>
#include <functional>

using namespace std::chrono_literals;

class ParametersClass: public rclcpp::Node


{
public:
ParametersClass()
: Node("parameter_node")
{
this->declare_parameter<std::string>("my_parameter", "world");
timer_ = this->create_wall_timer(
1000ms, std::bind(&ParametersClass::respond, this));
}
void respond()
{
this->get_parameter("my_parameter", parameter_string_);
RCLCPP_INFO(this->get_logger(), "Hello %s", parameter_string_.c_str());
}
private:
std::string parameter_string_;
rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char** argv)


{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<ParametersClass>());
rclcpp::shutdown();
return 0;
}

2.1 Examine the code


The  #include  statements at the top are the package dependencies.
The next piece of code creates the class and the constructor. The first line of this constructor
creates our parameter. Our parameter has the name  my_parameter  and is assigned the default
value  world . Next,  timer_  is initialized, which causes the  respond  function to be executed once a
second.
class ParametersClass: public rclcpp::Node
{
public:
ParametersClass()
: Node("parameter_node")
{
this->declare_parameter<std::string>("my_parameter", "world");
timer_ = this->create_wall_timer(
1000ms, std::bind(&ParametersClass::respond, this));
}

The first line of our  respond  function gets the parameter  my_parameter  from the node, and stores it
in  parameter_string_ . The  RCLCPP_INFO  function ensures the message is logged.
void respond()
{
this->get_parameter("my_parameter", parameter_string_);
RCLCPP_INFO(this->get_logger(), "Hello %s", parameter_string_.c_str());
}

Last is the declaration of  timer_  and  parameter_string_


private:
std::string parameter_string_;
rclcpp::TimerBase::SharedPtr timer_;

Following our  ParametersClass  is our  main . Here ROS 2 is initialized, and  rclcpp::spin  starts
processing data from the node.
int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<ParametersClass>());
rclcpp::shutdown();
return 0;
}

2.2 Add executable


Now open the  CMakeLists.txt  file. Below the dependency  find_package(rclcpp REQUIRED)  add the
following lines of code.
add_executable(parameter_node src/cpp_parameters_node.cpp)
ament_target_dependencies(parameter_node rclcpp)

install(TARGETS
parameter_node
DESTINATION lib/${PROJECT_NAME}
)

3 Build and run


It’s good practice to run  rosdep  in the root of your workspace ( dev_ws ) to check for missing
dependencies before building:
LinuxmacOSWindows
rosdep install -i --from-path src --rosdistro galactic -y

Navigate back to the root of your workspace,  dev_ws , and build your new package:
LinuxmacOSWindows
colcon build --packages-select cpp_parameters

Open a new terminal, navigate to  dev_ws , and source the setup files:
LinuxmacOSWindows
. install/setup.bash

Now run the node:


ros2 run cpp_parameters parameter_node

The terminal should return the following message every second:


[INFO] [parameter_node]: Hello world

Now you can see the default value of your parameter, but you want to be able to set it yourself.
There are two ways to accomplish this.
3.1 Change via the console
This part will use the knowledge you have gained from the tutorial about parameters and apply it
to the node you have just created.
Make sure the node is running:
ros2 run cpp_parameters parameter_node
Open another terminal, source the setup files from inside  dev_ws  again, and enter the following
line:
ros2 param list

There you will see the custom parameter  my_parameter . To change it simply run the following line in
the console:
ros2 param set /parameter_node my_parameter earth

You know it went well if you get the output  Set parameter successful . If you look at the other
terminal, you should see the output change to  [INFO] [parameter_node]: Hello earth
3.2 Change via a launch file
You can also set the parameter in a launch file, but first you will need to add the launch directory.
Inside the  dev_ws/src/cpp_parameters/  directory, create a new directory called  launch . In there,
create a new file called  cpp_parameters_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
return LaunchDescription([
Node(
package="cpp_parameters",
executable="parameter_node",
name="custom_parameter_node",
output="screen",
emulate_tty=True,
parameters=[
{"my_parameter": "earth"}
]
)
])

Here you can see that we set  my_parameter  to  earth  when we launch our node  parameter_node . By
adding the two lines below, we ensure our output is printed in our console.
output="screen",
emulate_tty=True,

Now open the  CMakeLists.txt  file. Below the lines you added earlier, add the following lines of
code.
install(
DIRECTORY launch
DESTINATION share/${PROJECT_NAME}
)

Open a console and navigate to the root of your workspace,  dev_ws , and build your new package:
LinuxmacOSWindows
colcon build --packages-select cpp_parameters

Then source the setup files in a new terminal:


LinuxmacOSWindows
. install/setup.bash
Now run the node using the launch file we have just created:
ros2 launch cpp_parameters cpp_parameters_launch.py

The terminal should return the following message every second:


[parameter_node-1] [INFO] [custom_parameter_node]: Hello earth

Summary
You created a node with a custom parameter, that can be set either from a launch file or the
command line. You added the dependencies, executables, and a launch file to the package
configuration files so that you could build and run them, and see the parameter in action.
Next steps
Now that you have some packages and ROS 2 systems of your own, the next tutorial will show
you how to examine issues in your environment and systems in case you have problems.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Using parameters in a class (Python)


Goal: Create and run a class with ROS parameters using Python (rclpy).
Tutorial level: Beginner
Time: 20 minutes
Contents
 Background
 Prerequisites
 Tasks
 1 Create a package
 2 Write the Python node
 3 Build and run
o Summary
o Next steps
Background
When making your own nodes you will sometimes need to add parameters that can be set from
the launch file.
This tutorial will show you how to create those parameters in a Python class, and how to set them
in a launch file.
Prerequisites
In previous tutorials, you learned how to create a workspace and create a package. You have also
learned about parameters and their function in a ROS 2 system.
Tasks
1 Create a package
Open a new terminal and source your ROS 2 installation so that  ros2  commands will work.
Navigate into the  dev_ws  directory created in a previous tutorial.
Recall that packages should be created in the  src  directory, not the root of the workspace.
Navigate into  dev_ws/src  and create a new package:
ros2 pkg create --build-type ament_python python_parameters --dependencies rclpy
Your terminal will return a message verifying the creation of your package  python_parameters  and all
its necessary files and folders.
The  --dependencies  argument will automatically add the necessary dependency lines
to  package.xml  and  CMakeLists.txt .
1.1 Update  package.xml 
Because you used the  --dependencies  option during package creation, you don’t have to manually
add dependencies to  package.xml  or  CMakeLists.txt .
As always, though, make sure to add the description, maintainer email and name, and license
information to  package.xml .
<description>Python parameter tutorial</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>

2 Write the Python node


Inside the  dev_ws/src/python_parameters/python_parameters  directory, create a new file
called  python_parameters_node.py  and paste the following code within:
import rclpy
import rclpy.node
from rclpy.exceptions import ParameterNotDeclaredException
from rcl_interfaces.msg import ParameterType

class MinimalParam(rclpy.node.Node):
def __init__(self):
super().__init__('minimal_param_node')
timer_period = 2 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)

self.declare_parameter('my_parameter', 'world')

def timer_callback(self):
my_param = self.get_parameter('my_parameter').get_parameter_value().string_value

self.get_logger().info('Hello %s!' % my_param)

my_new_param = rclpy.parameter.Parameter(
'my_parameter',
rclpy.Parameter.Type.STRING,
'world'
)
all_new_parameters = [my_new_param]
self.set_parameters(all_new_parameters)

def main():
rclpy.init()
node = MinimalParam()
rclpy.spin(node)

if __name__ == '__main__':
main()

2.1 Examine the code


Note: Declaring a parameter before getting or setting it is compulsory, or
a  ParameterNotDeclaredException  exception will be raised.
The  import  statements below are used to import the package dependencies.
import rclpy
import rclpy.node
from rclpy.exceptions import ParameterNotDeclaredException
from rcl_interfaces.msg import ParameterType

The next piece of code creates the class and the constructor.  timer  is initialized (with timer_period
set as 2 seconds), which causes the  timer_callback  function to be executed once every two
seconds. The line  self.declare_parameter('my_parameter', 'world')  of the constructor creates a
parameter with the name  my_parameter  and a default value of  world .
class MinimalParam(rclpy.node.Node):
def __init__(self):
super().__init__('minimal_param_node')
timer_period = 2 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)

self.declare_parameter('my_parameter', 'world')

The first line of our  timer_callback  function gets the parameter  my_parameter  from the node, and
stores it in  my_param . Next,the  get_logger  function ensures the message is logged. Then, we set the
parameter ‘my_parameter’ back to the default string value ‘world’.
def timer_callback(self):
my_param = self.get_parameter('my_parameter').get_parameter_value().string_value

self.get_logger().info('Hello %s!' % my_param)

my_new_param = rclpy.parameter.Parameter(
'my_parameter',
rclpy.Parameter.Type.STRING,
'world'
)
all_new_parameters = [my_new_param]
self.set_parameters(all_new_parameters)

Following the  timer_callback  is the  main  function where ROS 2 is initialized. Then an instance of
the class  MinimalParam  named  node  is defined. Finally,  rclpy.spin  starts processing data from the
node.
def main():
rclpy.init()
node = MinimalParam()
rclpy.spin(node)

if __name__ == '__main__':
main()

2.1.1 (Optional) Add ParameterDescriptor


Optionally, you can set a descriptor for the parameter. Descriptors allow you to specify a text
description of the parameter and parameters constraints, like making it read-only, specifying a
range, etc. For that to work, the  __init__  code has to be changed to:
# ...

class MinimalParam(rclpy.node.Node):
def __init__(self):
super().__init__('minimal_param_node')
timer_period = 2 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)

from rcl_interfaces.msg import ParameterDescriptor


my_parameter_descriptor = ParameterDescriptor(description='This parameter is mine!')
self.declare_parameter('my_parameter',
'default value for my_parameter',
my_parameter_descriptor)

The rest of the code remains the same. Once you run the node, you can then
run  ros2 param describe /minimal_param_node my_parameter  to see the type and description.
2.2 Add an entry point
Open the  setup.py  file. Again, match
the  maintainer ,  maintainer_email ,  description  and  license  fields to your  package.xml :
maintainer='YourName',
maintainer_email='[email protected]',
description='Python parameter tutorial',
license='Apache License 2.0',

Add the following line within the  console_scripts  brackets of the  entry_points  field:
entry_points={
'console_scripts': [
'param_talker = python_parameters.python_parameters_node:main',
],
},

Don’t forget to save.


3 Build and run
It’s good practice to run  rosdep  in the root of your workspace ( dev_ws ) to check for missing
dependencies before building:
LinuxmacOSWindows
rosdep install -i --from-path src --rosdistro galactic -y

Navigate back to the root of your workspace,  dev_ws , and build your new package:
LinuxmacOSWindows
colcon build --packages-select python_parameters

Open a new terminal, navigate to  dev_ws , and source the setup files:
LinuxmacOSWindows
. install/setup.bash

Now run the node:


ros2 run python_parameters param_talker

Except the first message where the parameter had a default value (an empty string), the terminal
should return the following message every 2 seconds:
[INFO] [parameter_node]: Hello world!

There are two ways to change the parameter:


3.1 Change via the console
This part will use the knowledge you have gained from the tutoral about parameters and apply it to
the node you have just created.
Make sure the node is running:
ros2 run python_parameters param_talker

Open another terminal, source the setup files from inside  dev_ws  again, and enter the following
line:
ros2 param list

There you will see the custom parameter  my_parameter . To change it simply run the following line in
the console:
ros2 param set /minimal_param_node my_parameter earth

You know it went well if you get the output  Set parameter successful . If you look at the other
terminal, you should see the output change to  [INFO] [minimal_param_node]: Hello earth!
Since the Python talker then set the parameter back to  world , further outputs
show  [INFO] [minimal_param_node]: Hello world!
3.2 Change via a launch file
You can also set parameters in a launch file, but first you will need to add a launch directory.
Inside the  dev_ws/src/python_parameters/  directory, create a new directory called  launch . In there,
create a new file called  python_parameters_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
return LaunchDescription([
Node(
package='python_parameters',
executable='param_talker',
name='custom_parameter_node',
output='screen',
emulate_tty=True,
parameters=[
{'my_parameter': 'earth'}
]
)
])

Here you can see that we set  my_parameter  to  earth  when we launch our node  parameter_node . By
adding the two lines below, we ensure our output is printed in our console.
output="screen",
emulate_tty=True,

Now open the  setup.py  file. Add the  import  statements to the top of the file, and the other new
statement to the  data_files  parameter to include all launch files:
import os
from glob import glob
# ...

setup(
# ...
data_files=[
# ...
(os.path.join('share', package_name), glob('launch/*_launch.py')),
]
)
Open a console and navigate to the root of your workspace,  dev_ws , and build your new package:
LinuxmacOSWindows
colcon build --packages-select python_parameters

Then source the setup files in a new terminal:


LinuxmacOSWindows
. install/setup.bash

Now run the node using the launch file we have just created:
ros2 launch python_parameters python_parameters_launch.py

The terminal should return the following message:


[parameter_node-1] [INFO] [custom_parameter_node]: Hello earth!

Summary
You created a node with a custom parameter, that can be set either from the launch file or the
command line. You wrote the code of a parameter talker: a Python node that declares, and then
loops getting and setting a string parameter. You added the entry point so that you could build and
run it, and used  ros2 param  to interact with the parameter talker.
Next steps
Now that you have some packages and ROS 2 systems of your own, the next tutorial will show
you how to examine issues in your environment and systems in case you have problems.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Getting started with ros2doctor


Goal: Identify issues in your ROS 2 setup using the  ros2doctor  tool.
Tutorial level: Beginner
Time: 10 minutes
Contents
 Background
 Prerequisites
 Tasks
 1 Check your setup
 2 Check a system
 3 Get a full report
o Summary
o Related content
o Next steps
Background
When your ROS 2 setup is not running as expected, you can check its settings with
the  ros2doctor  tool.
ros2doctor  checks all aspects of ROS 2, including platform, version, network, environment, running
systems and more, and warns you about possible errors and reasons for issues.
Prerequisites
ros2doctor  is part of the  ros2cli  package. As long as you have  ros2cli  installed (which any normal
install should have), you will be able to use  ros2doctor .
This tutorial uses turtlesim to illustrate some of the examples.
Tasks
1 Check your setup
Let’s examine your general ROS 2 setup as a whole with  ros2doctor . First, source ROS 2 in a new
terminal, then enter the command:
ros2 doctor

This will conduct checks over all your setup modules and return warnings and errors.
If your ROS 2 setup is in perfect shape, you’ll see a message similar to this:
All <n> checks passed

However, it’s not unusual to have a few warnings returned. A  UserWarning  doesn’t mean your setup
is unusable; it’s more likely just an indication that something is configured in a way that’s not ideal.
If you do receive a warning, it will look something like this:
<path>: <line>: UserWarning: <message>

For example,  ros2doctor  will find this warning if you’re using an unstable ROS 2 distribution:
UserWarning: Distribution <distro> is not fully supported or tested. To get more consistent features,
download a stable version at https://index.ros.org/doc/ros2/Installation/

If  ros2doctor  only finds warnings in your system, you will still receive
the  All <n> checks passed  message.
Most checks are categorized as warnings as opposed to errors. It’s mostly up to you, the user, to
determine the importance of the feedback  ros2doctor  returns. If it does find a rare error in your
setup, indicated by  UserWarning: ERROR: , the check is considered failed.
You will see a message similar to this following the list of issue feedback:
1/3 checks failed

Failed modules: network

An error indicates the system is missing important settings or functions that are crucial to ROS 2.
Errors should be addressed to ensure the system functions properly.
2 Check a system
You can also examine a running ROS 2 system to identify possible causes for issues. To
see  ros2doctor  working on a running system, let’s run Turtlesim, which has nodes actively
communicating with each other.
Start up the system by opening a new terminal, sourcing ROS 2, and entering the command:
ros2 run turtlesim turtlesim_node
Open another terminal and source ROS 2 to run the teleop controls:
ros2 run turtlesim turtle_teleop_key

Now run  ros2doctor  again in its own terminal. You will see the warnings and errors you had the
last time you ran  ros2doctor  on your setup, if you had any. Following those will be a couple new
warnings relating to the system itself:
UserWarning: Publisher without subscriber detected on /turtle1/color_sensor.
UserWarning: Publisher without subscriber detected on /turtle1/pose.

It seems that the  /turtlesim  node publishes data to two topics that aren’t being subscribed to,
and  ros2doctor  thinks this could possibly lead to issues.
If you run commands to echo the  /color_sensor  and  /pose  topics, those warnings will disappear
because the publishers will have subscribers.
You can try this by opening two new terminals while turtlesim is still running, sourcing ROS 2 in
each, and running each of the following commands in their own terminal:
ros2 topic echo /turtle1/color_sensor

ros2 topic echo /turtle1/pose

Then run  ros2doctor  in its terminal again. The  publisher without subscriber  warnings will be gone.
(Make sure to enter  Ctrl+C  in the terminals where you ran  echo ).
Now try exiting either the turtlesim window or quitting the teleop and running  ros2doctor  again.
You’ll see more warnings indicating  publisher without subscriber  or  subscriber without publisher  for
different topics, now that one node in the system isn’t available.
In a complex system with many nodes,  ros2doctor  would be invaluable for identifying possible
reasons for communication issues.
3 Get a full report
While  ros2doctor  will let you know warnings about your network, system, etc., running it with
the  --report  argument will give you much more detail to help you analyze issues.
You might want to use  --report  if you get a warning about your network setup and want to find out
exactly what part of your configuration is causing the warning.
It’s also very helpful when you need to open a support ticket to get help with ROS 2. You can copy
and paste the relevant parts of your report into the ticket so the people helping you can better
understand your environment and provide better assistance.
To get a full report, enter the following command in the terminal:
ros2 doctor --report

Which will return a list of information categorized into five groups:


NETWORK CONFIGURATION
...

PLATFORM INFORMATION
...

RMW MIDDLEWARE
...
ROS 2 INFORMATION
...

TOPIC LIST
...

You can crosscheck the information here against the warnings you get from running  ros2 doctor .
For example, if  ros2doctor  returned the warning (mentioned earlier) that your distribution is “not
fully supported or tested”, you might take a look at the  ROS 2 INFORMATION  section of the report:
distribution name : <distro>
distribution type : ros2
distribution status : prerelease
release platforms : {'<platform>': ['<version>']}

Here you can see the  distribution status  is  prerelease , which explains why it’s not fully supported.
Summary
ros2doctor will inform you of problems in your ROS 2 setup and running systems. You can get a
deeper look at information behind those warnings by using the  --report  argument.
Keep in mind,  ros2doctor  is not a debug tool; it won’t help with errors in your code or on the
implementation side of your system.
Related content
ros2doctor’s README will tell you more about different arguments. You might want to take a look
around the  ros2doctor  repo as well, since it’s fairly beginner friendly and a great place to get
started with contributing.
Next steps
You’ve completed the beginner level tutorials!
 PreviousNext 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Creating and Using Plugins (C++)


Goal: Learn to create and load a simple plugin using pluginlib
Tutorial level: Beginner
Minimum Platform: Ardent
Contents
 Background
 Prerequisites
 Tasks
 1 Create the Base Class Package
 2 Create the Plugin Package
 2.1 Source code for the plugins
 2.2 Plugin Declaration XML
 2.3 CMake Plugin Declaration
 3 Use the Plugins
 4 Build and run
o Summary
Background
This tutorial is derived from http://wiki.ros.org/pluginlib and Writing and Using a Simple Plugin
Tutorial.
pluginlib is a C++ library for loading and unloading plugins from within a ROS package. Plugins
are dynamically loadable classes that are loaded from a runtime library (i.e. shared object,
dynamically linked library). With pluginlib, one does not have to explicitly link their application
against the library containing the classes – instead pluginlib can open a library containing exported
classes at any point without the application having any prior awareness of the library or the header
file containing the class definition. Plugins are useful for extending/modifying application behavior
without needing the application source code.
Prerequisites
This tutorial assumes basic C++ knowledge and that you have  pluginlib  installed.
sudo apt-get install ros-galactic-pluginlib

Tasks
In this tutorial, you will create a two new packages, one that defines the base class, and the other
that provides the plugins. The base class will define a generic polygon class, and then our plugins
will define specific shapes.
1 Create the Base Class Package
Create a new empty package in your  dev_ws/src  folder with the following terminal command.
ros2 pkg create --build-type ament_cmake polygon_base --dependencies pluginlib --node-name area_node

Open your favorite editor, edit  dev_ws/src/polygon_base/include/polygon_base/regular_polygon.hpp , and


paste the following inside of it:
#ifndef POLYGON_BASE_REGULAR_POLYGON_HPP
#define POLYGON_BASE_REGULAR_POLYGON_HPP

namespace polygon_base
{
class RegularPolygon
{
public:
virtual void initialize(double side_length) = 0;
virtual double area() = 0;
virtual ~RegularPolygon(){}

protected:
RegularPolygon(){}
};
} // namespace polygon_base

#endif // POLYGON_BASE_REGULAR_POLYGON_HPP

This code above should be pretty self explanatory… we’re creating an abstract class
called  RegularPolygon . One thing to notice is the presence of the initialize method. With  pluginlib , a
constructor without parameters is required for classes so, if any parameters are required, we use
the initialize method to initialize the object.
We need to make this header available to other classes, so
open  dev_ws/src/polygon_base/CMakeLists.txt  for editing. Add the following lines after
the  ament_target_dependencies  command.
install(
DIRECTORY include/
DESTINATION include
)

And add this command before the  ament_package  command


ament_export_include_directories(
include
)

We will return to this package later to write our test node.


2 Create the Plugin Package
Now we’re going to write two non-virtual implementations of our abstract class. Create a second
empty package in your  dev_ws/src  folder with the following terminal command.
ros2 pkg create --build-type ament_cmake polygon_plugins --dependencies polygon_base pluginlib --library-
name polygon_plugins

2.1 Source code for the plugins


Open  dev_ws/src/polygon_plugins/src/polygon_plugins.cpp  for editing, and paste the following inside of
it:
#include <polygon_base/regular_polygon.hpp>
#include <cmath>

namespace polygon_plugins
{
class Square : public polygon_base::RegularPolygon
{
public:
void initialize(double side_length) override
{
side_length_ = side_length;
}

double area() override


{
return side_length_ * side_length_;
}

protected:
double side_length_;
};

class Triangle : public polygon_base::RegularPolygon


{
public:
void initialize(double side_length) override
{
side_length_ = side_length;
}

double area() override


{
return 0.5 * side_length_ * getHeight();
}

double getHeight()
{
return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
}

protected:
double side_length_;
};
}

#include <pluginlib/class_list_macros.hpp>

PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)

The implementation of the Square and Triangle classes should be fairly straightforward: save the
side length, and use it to calculate the area. The only piece that is pluginlib specific is the last
three lines, which invokes some magical macros that register the classes as actual plugins. Let’s
go through the arguments to the  PLUGINLIB_EXPORT_CLASS  macro:
1. The fully-qualified type of the plugin class, in this case,  polygon_plugins::Square .
2. The fully-qualified type of the base class, in this case,  polygon_base::RegularPolygon .
2.2 Plugin Declaration XML
The steps above make it so that instances of our plugins can be created once the library they exist
in is loaded, but the plugin loader still needs a way to find that library and to know what to
reference within that library. To this end, we’ll also create an XML file that, along with a special
export line in the package manifest, makes all the necessary information about our plugins
available to the ROS toolchain.
Create  dev_ws/src/polygon_plugins/plugins.xml  with the following code:
<library path="polygon_plugins">
<class type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
<description>This is a square plugin.</description>
</class>
<class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
<description>This is a triangle plugin.</description>
</class>
</library>

A couple things to note:


1. The  library  tag gives the relative path to a library that contains the plugins that we want to
export. In ROS 2, that is just the name of the library. In ROS 1 it contained the prefix  lib  or
sometimes  lib/lib  (i.e.  lib/libpolygon_plugins ) but here it is simpler.
2. The  class  tag declares a plugin that we want to export from our library. Let’s go through its
parameters:
 type : The fully qualified type of the plugin. For us, that’s  polygon_plugins::Square .

 : The fully qualified base class type for the plugin. For us,
base_class
that’s  polygon_base::RegularPolygon .
 description : A description of the plugin and what it does.
 name : There used to be a name attribute, but it is no longer required.
2.3 CMake Plugin Declaration
The last step is to export your plugins via  CMakeLists.txt . This is a change from ROS 1, where the
exporting was done via  package.xml . Add the following block to
your  dev_ws/src/polygon_plugins/CMakeLists.txt  after the line reading  find_package(pluginlib REQUIRED)
add_library(polygon_plugins src/polygon_plugins.cpp)
target_include_directories(polygon_plugins PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
ament_target_dependencies(
polygon_plugins
polygon_base
pluginlib
)

pluginlib_export_plugin_description_file(polygon_base plugins.xml)

install(
TARGETS polygon_plugins
EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)

And before the  ament_package  command, add


ament_export_libraries(
polygon_plugins
)
ament_export_targets(
export_${PROJECT_NAME}
)

The arguments to this CMake command are


1. The package for the base class, i.e.  polygon_base
2. The relative path to the Plugin Declaration xml, i.e.  plugins.xml
3 Use the Plugins
Now its time to use the plugins. This can be done in any package, but here we’re going to do it in
the base package. Edit  dev_ws/src/polygon_base/src/area_node.cpp  to contain the following:
#include <pluginlib/class_loader.hpp>
#include <polygon_base/regular_polygon.hpp>

int main(int argc, char** argv)


{
// To avoid unused parameter warnings
(void) argc;
(void) argv;

pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("polygon_base",
"polygon_base::RegularPolygon");

try
{
std::shared_ptr<polygon_base::RegularPolygon> triangle =
poly_loader.createSharedInstance("polygon_plugins::Triangle");
triangle->initialize(10.0);

std::shared_ptr<polygon_base::RegularPolygon> square =
poly_loader.createSharedInstance("polygon_plugins::Square");
square->initialize(10.0);

printf("Triangle area: %.2f\n", triangle->area());


printf("Square area: %.2f\n", square->area());
}
catch(pluginlib::PluginlibException& ex)
{
printf("The plugin failed to load for some reason. Error: %s\n", ex.what());
}

return 0;
}
The  ClassLoader  is the key class to understand, defined in the  class_loader.hpp  header.
 It is templated with the base class, i.e.  polygon_base::RegularPolygon
 The first argument is a string for the package name of the base class, i.e.  polygon_base
 The second argument is a string with the fully qualified base class type for the plugin,
i.e.  polygon_base::RegularPolygon
There are a number of ways to instantiate an instance of the class. In this example, we’re using
shared pointers. We just need to call  createSharedInstance  with the fully-qualified type of the plugin
class, in this case,  polygon_plugins::Square .
Important note: the  polygon_base  package in which this node is defined does NOT depend on
the  polygon_plugins  class. The plugins will be loaded dynamically without any dependency needing
to be declared. Furthermore, we’re instantiating the classes with hardcoded plugin names, but you
can also do so dynamically with parameters, etc.
4 Build and run
Navigate back to the root of your workspace,  dev_ws , and build your new packages:
colcon build --packages-select polygon_base polygon_plugins

From  dev_ws , be sure to source the setup files:


LinuxmacOSWindows
. install/setup.bash

Now run the node:


ros2 run polygon_base area_node

It should print
Triangle area: 43.30
Square area: 100.00

Summary
Congratulations! You’ve just written and used your first plugins.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

You might also like