How to Create a Service and Client (C++) | ROS2 Foxy Fitzroy

In this tutorial, we will learn how to create a service and a client in ROS 2 Foxy Fitzroy using C++. The client-service relationship in ROS 2 is a request-reply relationship. A client node sends a request for data to the service node. The service node then sends a reply to the client node.

A real-world example of this is a client node requesting sensor data from a service node. The service node then responds to the client node with the requested sensor data. 

A file called a .srv file defines the structure of the service-client node interaction.

service_client

The example we will use here is an addition system. We will create a client node that requests the sum of two integers. We will then create a service node that will respond to the client node with the sum of those two integers.

The official tutorial is located in the ROS 2 Foxy documentation, but we’ll run through the entire process step-by-step below.

You Will Need

In order to complete this tutorial, you will need:

Prerequisites

You have already created a workspace.

Create a Package

Open a new terminal window, and navigate to the src directory of your workspace:

cd ~/dev_ws/src

Now let’s create a package named cpp_srvcli.

Type this command (this is a single command):

ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces

Your package named cpp_srvcli has now been created.

1-create-a-packageJPG

You will note that we added the –dependencies command after the package creation command. Doing this automatically adds the dependencies to your package.xml and CMakeLists.txt files.

Modify Package.xml

Go to the dev_ws/src/cpp_srvcli folder.

cd ~/dev_ws/src/cpp_srvcli

Make sure you have a text editor installed. I like to use gedit.

sudo apt-get install gedit

Open the package.xml file. 

gedit package.xml

Fill in the description of the cpp_srvcli package, your email address and name on the maintainer line, and the license you desire (e.g. Apache License 2.0).

<description>A minimal C++ client and service node</description>
<maintainer email="automaticaddison@todo.todo">automaticaddison</maintainer>
<license>Apache License 2.0</license>

Save and close the file.

Write a Service Node

Move to the dev_ws/src/cpp_srvcli/src folder.

cd ~/dev_ws/src/cpp_srvcli/src

Create a new C++ file named add_two_ints_server.cpp

gedit add_two_ints_server.cpp

Write the following code. Don’t be intimidated. Just go one line at a time and read the comments to understand what each line does.

This service node adds two integers together.

#include "rclcpp/rclcpp.hpp" // ROS 2 C++ Client Library
#include "example_interfaces/srv/add_two_ints.hpp" // Package dependency

#include <memory>

void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
          std::shared_ptr<example_interfaces::srv::AddTwoInts::Response>      response)
{
  // Adds two integers from the request and gives the sum to the response.
  response->sum = request->a + request->b;
  
  // Notifies the console of its status using logs.
  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)
{
  // Initialize the ROS 2 C++ Client Library
  rclcpp::init(argc, argv);

  // Create a node named add_two_ints_server
  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");

  // Create a service named add_two_ints and advertise it over the network (i.e. &add method)
  rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
    node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);

  // Display a log message when the service is ready
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");

  // Make the service available.
  rclcpp::spin(node);
  
  // Call shutdown procedure when we are done
  rclcpp::shutdown();
}

Save the file, and close it.

Modify CMakeLists.txt

Go to the following directory.

cd ~/dev_ws/src/cpp_srvcli/

Open CMakeLists.txt.

gedit CMakeLists.txt

Add the following lines above the # find dependencies block:

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

Right before the ament_package() line, add the following lines.

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

Save the file, and close it.

Write a Client Node

Move to the dev_ws/src/cpp_srvcli/src folder.

cd ~/dev_ws/src/cpp_srvcli/src

Create a new C++ file named add_two_ints_client.cpp

gedit add_two_ints_client.cpp

Write the following code. Don’t be intimidated. Just go one line at a time and read the comments to understand what each line does.

#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;
  }
  
  // Create the node
  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
  
  // Create the client for the node
  rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
    node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

  // Make the request
  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()) {
      // Show an error if the user types CTRL + C
      RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
      return 0;
    }
    // Search for service nodes in the network
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
  }

  // Send a request
  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;
}

Save the file, and close it.

Modify CMakeLists.txt

Go to the following directory.

cd ~/dev_ws/src/cpp_srvcli/

Open CMakeLists.txt.

gedit CMakeLists.txt

Add the following lines after the server’s add_executable block:

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

Add client inside the install target.

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

Here is what your CMakeLists.txt file should look like:

cmake_minimum_required(VERSION 3.5)
project(cpp_srvcli)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# 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 dependencies
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)

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

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

ament_package()

Save the file, and close it.

Build the Package 

Return to the root of your workspace:

cd ~/dev_ws/

We need to double check that all the dependencies needed are already installed.

rosdep install -i --from-path src --rosdistro foxy -y

Build all packages in the workspace.

colcon build

Run the Nodes

To run the nodes, open a new terminal window.

Make sure you are in the root of your workspace:

cd ~/dev_ws/

Run the service node. 

ros2 run cpp_srvcli server
3-ready-to-addJPG

Open a new terminal, and run the client node. At the end of the command, put the two integers you would like to add.

ros2 run cpp_srvcli client 5 3
4-run-clientJPG

Go back to the service node terminal. Here is what I see:

5-server-responseJPG

When you’re done, press CTRL + C in all terminal windows to shut everything down.

That’s it. Keep building!

Create a Basic Publisher and Subscriber (Python) | ROS2 Foxy

In this post, we will learn how to create a basic publisher node and a subscriber node in ROS 2 Foxy Fitzroy using Python. You can think of a node as a small single-purpose program within a larger robotic system. Publisher nodes publish data, subscriber nodes receive data, and a publishing subscriber node receives data and publishes data.

The official tutorial is located in the ROS 2 Foxy documentation, but we’ll run through the entire process step-by-step below.

You Will Need

In order to complete this tutorial, you will need:

Prerequisites

You have already created a workspace.

Create a Package

Open a new terminal window, and navigate to the src directory of your workspace:

cd ~/dev_ws/src

Now let’s create a package named py_pubsub.

Type this command:

ros2 pkg create --build-type ament_python py_pubsub
1-python-pubsubJPG

Your package named py_pubsub has now been created.

Write a Publisher Node

Move to the /dev_ws/src/py_pubsub/py_pubsub folder. This is where your Python code will go for your publisher and subscriber.

cd py_pubsub/py_pubsub

Make sure you have a text editor installed. I like to use gedit.

sudo apt-get install gedit

Create a blank Python file called my_publisher_node.py.

gedit my_publisher_node.py

Write the following code. Don’t be intimidated. Just go one line at a time and read the comments to understand what each line does.

This publisher node publishes the message “Hi Automatic Addison!” every 500 milliseconds (i.e. two times per second) to a topic named addison.

# ROS Client Library for Python
import rclpy

# Handles the creation of nodes
from rclpy.node import Node

# Enables usage of the String message type
from std_msgs.msg import String

class MinimalPublisher(Node):
  """
  Create a MinimalPublisher class, which is a subclass of the Node class.
  """
  def __init__(self):
    """
    Class constructor to set up the node
    """
    # Initiate the Node class's constructor and give it a name
    super().__init__('minimal_publisher')
    
    # Create the publisher. This publisher will publish a String message
    # to the addison topic. The queue size is 10 messages.
    self.publisher_ = self.create_publisher(String, 'addison', 10)
    
    # We will publish a message every 0.5 seconds
    timer_period = 0.5  # seconds
    
    # Create the timer
    self.timer = self.create_timer(timer_period, self.timer_callback)
 
    # Initialize a counter variable
    self.i = 0

  def timer_callback(self):
    """
    Callback function.
    This function gets called every 0.5 seconds.
    """
    # Create a String message
    msg = String()

    # Set the String message's data
    msg.data = 'Hi Automatic Addison: %d' % self.i
    
    # Publish the message to the topic
    self.publisher_.publish(msg)
    
    # Display the message on the console
    self.get_logger().info('Publishing: "%s"' % msg.data)

    # Increment the counter by 1    
    self.i += 1

def main(args=None):

  # Initialize the rclpy library
  rclpy.init(args=args)

  # Create the node
  minimal_publisher = MinimalPublisher()

  # Spin the node so the callback function is called.
  rclpy.spin(minimal_publisher)

  # Destroy the node explicitly
  # (optional - otherwise it will be done automatically
  # when the garbage collector destroys the node object)
  minimal_publisher.destroy_node()

  # Shutdown the ROS client library for Python
  rclpy.shutdown()

if __name__ == '__main__':
  main()

Note that in this tutorial, we are publishing a string to a topic. In a robotics project, you’ll typically be publishing numerical values. We’re just using strings in this example as a demonstration.

Add Dependencies

Now that we’ve written our publisher node, we need to let our system know what libraries our node needs in order to execute properly. These libraries are our node’s dependencies.

Go to the dev_ws/src/py_pubsub folder, and see what files are in there.

cd ~/dev_ws/src/py_pubsub
dir

Here are the files you should see.

2-dirJPG

The package.xml file contains key information about the py_pubsub package. 

Open the package.xml file. 

gedit package.xml

Fill in the description of the cpp_pubsub package, your email address and name on the maintainer line, and the license you desire (e.g. Apache License 2.0).

<description>A minimal publisher/subscriber that uses the ROS Python Client Library</description>
<maintainer email="automaticaddison@todo.todo">automaticaddison</maintainer>
<license>Apache License 2.0</license>

Now, after the <build_type>ament_cmake</build_type> line, add the two dependencies your node needs in order to compile.

<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>

Save the file and close it to return to the terminal window.

Modify Setup.py

Open setup.py.

gedit setup.py

Set the following fields.

maintainer='automaticaddison',
maintainer_email='automaticaddison@todo.todo',
description='A minimal publisher/subscriber that uses the ROS Python Client Library',
license='Apache License 2.0',

Add the following line within the console_scripts brackets of the entry_points field:

entry_points={
        'console_scripts': [
                'my_publisher = py_pubsub.my_publisher_node:main',
        ],
},

Save and close the file.

Write a Subscriber Node

Now let’s write a subscriber node. This node will subscribe to String messages that are published by the publisher node to the addison topic.

Navigate to the dev_ws/src/py_pubsub/py_pubsub directory.

cd ~/dev_ws/src/py_pubsub/py_pubsub

Create a blank Python file called my_subscriber_node.py.

gedit my_subscriber_node.py

Write the following code.

# ROS Client Library for Python
import rclpy

# Handles the creation of nodes
from rclpy.node import Node

# Handles string messages
from std_msgs.msg import String

class MinimalSubscriber(Node):
  """
  Create a subscriber node
  """
  def __init__(self):

    # Initiate the Node class's constructor and give it a name
    super().__init__('minimal_subscriber')

    # The node subscribes to messages of type std_msgs/String, 
    # over a topic named: /addison
    # The callback function is called as soon as a message is received.
    # The maximum number of queued messages is 10.
    self.subscription = self.create_subscription(
      String,
      'addison',
      self.listener_callback,
      10)
    self.subscription  # prevent unused variable warning

  def listener_callback(self, msg):
    # Display a message on the console every time a message is received on the
    # addison topic
    self.get_logger().info('I heard: "%s"' % msg.data)

def main(args=None):

  # Initialize the rclpy library
  rclpy.init(args=args)

  # Create a subscriber
  minimal_subscriber = MinimalSubscriber()

  # Spin the node so the callback function is called.
  # Pull messages from any topics this node is subscribed to.
  rclpy.spin(minimal_subscriber)

  # Destroy the node explicitly
  # (optional - otherwise it will be done automatically
  # when the garbage collector destroys the node object)
  minimal_subscriber.destroy_node()
  
  # Shutdown the ROS client library for Python
  rclpy.shutdown()

if __name__ == '__main__':
  main()

Save the file, and close it.

Modify Setup.py

Go to the dev_ws/src/py_pubsub folder.

cd ~/dev_ws/src/py_pubsub

Open setup.py.

gedit setup.py

Add the following line within the console_scripts brackets of the entry_points field:

entry_points={
        'console_scripts': [
                 'my_publisher = py_pubsub.my_publisher_node:main',
                 'my_subscriber = py_pubsub.my_subscriber_node:main',
        ],
},

Save and close the file.

Build the Package 

Return to the root of your workspace:

cd ~/dev_ws/

We need to double check that all the dependencies needed (rclpy and std_msgs) are already installed.

rosdep install -i --from-path src --rosdistro foxy -y
3-dependenciesJPG

Build all packages in the workspace.

colcon build

Alternatively, if you only want to build py_pubsub and no other package in the workspace, you can type:

colcon build --packages-select py_pubsub

The build was successful.

Run the Nodes

To run the nodes, open a new terminal window.

Make sure you are in the root of your workspace:

cd ~/dev_ws/

Run the publisher node. If you recall, its name is my_publisher.

ros2 run py_pubsub my_publisher

You will see a message published every 500 milliseconds.

4-publisherJPG

Open a new terminal, and run the subscriber node.

ros2 run py_pubsub my_subscriber
5-subscriberJPG

Let’s see what topics are currently active. Open a new terminal, and type:

ros2 topic list -t
6-ros2topiclistJPG

Let’s listen to the addison topic.

ros2 topic echo /addison
7-ros2-topic-echoJPG

Press CTRL + C

Now let’s see the node graph.

rqt_graph

You can see the relationship between the nodes. 

8-rqt-graphJPG

minimal_publisher publishes data to the /addison topic. minimal_subscriber subscribes to data published on that topic.

Press CTRL + C in all terminals to shutdown the programs.

Create a Launch File

Now let’s create a ROS 2 launch file. This launch file will enable us to launch the publisher and subscriber nodes simultaneously with a single command.

Go to the following directory:

cd ~/dev_ws/src/py_pubsub/

Type the following command to create a new folder:

mkdir launch

Move inside that folder.

cd launch

Open up a new launch file.

gedit py_pubsub_launch.py

Write the following code inside the launch file.

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='py_pubsub',
            namespace='ns1',
            executable='my_publisher',
            name='my_publisher_node'
        ),
        Node(
            package='py_pubsub',
            namespace='ns1',
            executable='my_subscriber',
            name='my_subscriber_node'
        )
    ])

Click Save and close the file to return to the terminal.

Launch the Launch File

To launch the launch file, open a new terminal window, and move to the launch folder.

cd ~/dev_ws/src/py_pubsub/launch

Type:

ros2 launch py_pubsub_launch.py

Here was my output:

9-run-launch-fileJPG

Click Save and close the file to return to the terminal.

Zip the Package for Distribution

If you ever want to zip the package and send it to someone, open a new terminal window.

Move to the directory containing your package.

cd ~/dev_ws/src
zip -r py_pubsub.zip py_pubsub

The syntax is:

zip -r <filename.zip> <foldername>

Final Words

That’s it for creating a basic Python Subscriber and Publisher in ROS 2. 

You should also know that you can have a node that both subscribes and publishes. I call this kind of node a publishing subscriber node. I show you how to create this kind of node in this post.

Keep building!

Create a Publisher and Subscriber in C++ | ROS 2 Foxy Fitzroy

In this post, we will learn how to create a publisher node, subscriber node, and a publishing subscriber node in ROS 2 Foxy Fitzroy using C++. You can think of a node as a small single-purpose program within a larger robotic system. Publisher nodes publish data to topic(s), subscriber nodes receive data from topic(s), and a publishing subscriber node can do both receive data from topic(s) and publish data to topic(s).

A good analogy is YouTube. YouTubers (publisher nodes) publish videos (messages) to a channel (topic), and you (subscriber node) can subscribe to that channel (topic) so that you receive all the videos (messages) on that channel (topic). 

Robotic systems (mobile robots, robotic arms, and aeriel robots) pass a lot of data around, which is why the ROS 2 framework for developing robots is so useful and popular.

The official tutorial is located in the ROS 2 Foxy documentation, but we’ll run through the entire process step-by-step below.

You Will Need

In order to complete this tutorial, you will need:

Prerequisites

You have already created a workspace.

Create a Package

Open a new terminal window, and navigate to the src directory of your workspace:

cd ~/dev_ws/src

Now let’s create a package named cpp_pubsub.

Type this command:

ros2 pkg create --build-type ament_cmake cpp_pubsub

Your package named cpp_pubsub has now been created.

1-create-a-new-packageJPG

Write a Publisher Node

Move to the /dev_ws/src/cpp_pubsub/src folder

cd cpp_pubsub/src

This folder is where our source code (i.e. publisher and subscriber nodes) will reside.

Make sure you have a text editor installed. I like to use gedit.

sudo apt-get install gedit

Create a blank C++ file called my_publisher_node.cpp.

gedit my_publisher_node.cpp

Write the following code. Don’t be intimidated. C++ code can look kind of scary. Just go one line at a time, and read the comments to understand what each line does.

This publisher node publishes the message “Hi Automatic Addison!” every 500 milliseconds (i.e. two times per second) to a topic named addison.

Note that in this tutorial, we are publishing a string to a topic. In a real robotics project, you’ll typically be publishing numerical values. We’re just using strings in this example as a demonstration.

// Include important C++ header files that provide class
// templates for useful operations.
#include <chrono> // Date and time
#include <functional> // Arithmetic, comparisons, and logical operations
#include <memory> // Dynamic memory management
#include <string> // String functions

// ROS Client Library for C++
// Allows use of the most common elements of ROS 2
#include "rclcpp/rclcpp.hpp"

// Built-in message type that will be used to publish data
#include "std_msgs/msg/string.hpp"

// chrono_literals handles user-defined time durations (e.g. 500ms) 
using namespace std::chrono_literals;

// Create the node class named MinimalPublisher which inherits the attributes
// and methods of the rclcpp::Node class.
class MinimalPublisher : public rclcpp::Node
{
  public:
    // Constructor creates a node named minimal_publisher. 
    // The published message count is initialized to 0.
    MinimalPublisher()
    : Node("minimal_publisher"), count_(0)
    {
      // Publisher publishes String messages to a topic named "addison". 
      // The size of the queue is 10 messages.
      publisher_ = this->create_publisher<std_msgs::msg::String>("addison",10);
      
      // Initialize the timer. The timer_callback function will execute every
      // 500 milliseconds.
      timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
    }

  private:
    // This method executes every 500 milliseconds
    void timer_callback()
    {
      // Create a new message of type String
      auto message = std_msgs::msg::String();
      
      // Set our message's data attribute and increment the message count by 1
      message.data = "Hi Automatic Addison! " + std::to_string(count_++);

      // Print every message to the terminal window      
      RCLCPP_INFO(this->get_logger(),"Publishing: '%s'", message.data.c_str());
      
      // Publish the message to the topic named "addison"
      publisher_->publish(message);
    }
    
    // Declaration of the timer_ attribute
    rclcpp::TimerBase::SharedPtr timer_;
 
    // Declaration of the publisher_ attribute
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  
    // Declaration of the count_ attribute
    size_t count_;
};

// Node execution starts here
int main(int argc, char * argv[])
{
  // Initialize ROS 2
  rclcpp::init(argc, argv);
 
  // Start processing data from the node as well as the callbacks and the timer
  rclcpp::spin(std::make_shared<MinimalPublisher>());

  // Shutdown the node when finished
  rclcpp::shutdown();
  return 0;
}

Add Dependencies

Now that we’ve written our publisher node, we need to let our system know what libraries our node needs in order to execute properly. These libraries are our node’s dependencies.

Go to the dev_ws/src/cpp_pubsub folder, and see what files are in there.

cd ~/dev_ws/src/cpp_pubsub
dir
2-what-files-in-thereJPG

You should see that this folder contains a file named package.xml and a file named CMakeLists.txt.

The package.xml file contains key information about the cpp_pubsub package. CMakeLists.txt contains important information needed to compile the C++ source code you wrote in the previous section of this tutorial.

Open the package.xml file. 

gedit package.xml

Fill in the description of the cpp_pubsub package, your email address and name on the maintainer line, and the license you desire (e.g. Apache License 2.0).

<description>A minimal publisher/subscriber that uses the ROS C++ Client Library</description>
<maintainer email="automaticaddison@todo.todo">automaticaddison</maintainer>
<license>Apache License 2.0</license>

Now, after the <build_type>ament_cmake</build_type> line, add the two dependencies your node needs in order to compile. These properties were included at the top of the C++ code of your publisher node (the other dependencies are part of the C++ Standard Library, so they don’t need to be added here).

<depend>rclcpp</depend>
<depend>std_msgs</depend>
3-package-xml-fileJPG

Save the file and close it to return to the terminal window.

Modify CMakeLists.txt

Open CMakeLists.txt.

gedit CMakeLists.txt

Under this line (find_package(ament_cmake REQUIRED)), add the dependencies:

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

Below that, add the executable. We will name it my_publisher.

add_executable(my_publisher src/my_publisher_node.cpp)
ament_target_dependencies(my_publisher rclcpp std_msgs)

Below that, add the install section so that ROS 2 will be able to find the executable.

install(TARGETS
  my_publisher
  DESTINATION lib/${PROJECT_NAME})
4-cmakelistsJPG

Save the file and close it.

Write a Subscriber Node

Now let’s write a subscriber node. This node will subscribe to String messages that are published by the publisher node to the addison topic.

Navigate to the dev_ws/src/cpp_pubsub/src directory.

cd ~/dev_ws/src/cpp_pubsub/src

Create a blank C++ file called my_subscriber_node.cpp.

gedit my_subscriber_node.cpp

Write the following code.

// Include the C++ standard library headers
#include <memory> // Dynamic memory management

// Dependencies
#include "rclcpp/rclcpp.hpp" // ROS Clienty Library for C++
#include "std_msgs/msg/string.hpp" // Handles String messages in ROS 2
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
  public:
    // Constructor
    // The name of the node is minimal_subscriber
    MinimalSubscriber()
    : Node("minimal_subscriber")
    {
      // Create the subscription.
      // The topic_callback function executes whenever data is published
      // to the 'addison' topic.
      subscription_ = this->create_subscription<std_msgs::msg::String>(
      "addison", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
    }

  private:
    // Receives the String message that is published over the topic
    void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
    {
      // Write the message that was received on the console window
      RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
    }
    // Declare the subscription attribute
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  // Launch ROS 2
  rclcpp::init(argc, argv);
  
  // Prepare to receive messages that arrive on the topic
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  
  // Shutdown routine for ROS2
  rclcpp::shutdown();
  return 0;
}

Save the file, and close it.

Add Dependencies

We don’t need to add anything to our package.xml file since the dependencies are the same as the publisher node’s dependencies.

Modify CMakeLists.txt

Go to the dev_ws/src/cpp_pubsub folder, and see what files are in there.

cd ~/dev_ws/src/cpp_pubsub

Open CMakeLists.txt.

gedit CMakeLists.txt

Under this line (ament_target_dependencies(my_publisher rclcpp std_msgs)), add the executable path and the dependencies for the subscriber node you just created. We will name the executable my_subscriber

add_executable(my_subscriber src/my_subscriber_node.cpp)
ament_target_dependencies(my_subscriber rclcpp std_msgs)

Now add this after my_publisher inside the install section

my_subscriber
5-new-cmakelistsJPG

Save the file, and close it.

Build the Package 

Return to the root of your workspace:

cd ~/dev_ws/

We need to double check that all the dependencies needed (rclcpp and std_msgs) are already installed.

rosdep install -i --from-path src --rosdistro foxy -y
6-all-dependencies-installedJPG

Open your bash file, and make sure these two lines are at the bottom of your file.

7-these-two-linesJPG

Build all packages in the workspace.

colcon build

Alternatively, if you only want to build cpp_pubsub and no other package in the workspace, you can type:

colcon build --packages-select cpp_pubsub

The build was successful.

8-build-successfulJPG

The executable is located inside the dev_ws/install/cpp_pubsub/lib/cpp_pubsub folder.

Run the Nodes

To run the nodes, open a new terminal window.

Make sure you are in the root of your workspace:

cd ~/dev_ws/

Run the publisher node. If you recall, its name is my_publisher.

ros2 run cpp_pubsub my_publisher

You will see a message published every 500 milliseconds.

9-run-publisherJPG

Open a new terminal, and run the subscriber node.

ros2 run cpp_pubsub my_subscriber
10-run-subscriberJPG

Let’s see what topics are currently active. Open a new terminal, and type:

ros2 topic list -t
11-active-topicsJPG

Let’s listen to the addison topic.

ros2 topic echo /addison
14d-listen-to-addison2-topic

Press CTRL + C

Now let’s see the node graph.

rqt_graph

You can see the relationship between the nodes. 

13-relationship-between-nodesJPG

minimal_publisher publishes data to the /addison topic. minimal_subscriber subscribes to data published on that topic.

Press CTRL + C in all terminals to shutdown the programs.

Create a Launch File

Now let’s create a ROS 2 launch file. This launch file will enable us to launch the publisher and subscriber nodes simultaneously with a single command.

Go to the following directory

cd ~/dev_ws/src/cpp_pubsub/

Type the following command to create a new folder:

mkdir launch

Move inside that folder.

cd launch

Open up a new launch file.

gedit cpp_pubsub_launch.py

Write the following code inside the launch file.

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
       Node(
         package='cpp_pubsub',
         namespace='ns1', # Make sure this matches the subscriber's namespace
         executable='my_publisher', # Name of the executable
         name='minimal_publisher' # Any name is fine
       ),
       Node(
         package='cpp_pubsub',
         namespace='ns1', # Make sure this matches the publisher's namespace
         executable='my_subscriber', # Name of the executable
         name='minimal_subscriber' # Any name is fine
       )
    ])

Click Save and close the file to return to the terminal.

Launch the Launch File

To launch the launch file, open a new terminal window, and move to the launch folder.

cd ~/dev_ws/src/cpp_pubsub/launch

Type:

ros2 launch cpp_pubsub_launch.py

Here was my output:

14-launch-fileJPG

Click Save and close the file to return to the terminal.

Create a Publishing Subscriber Node

Finally, let’s create a publishing subscriber node. This kind of node subscribes to a topic and publishes to another topic. 

A publishing subscriber node is common in robotics. Consider a robotic arm for example. You might have a node that reads camera data (i.e. subscribes), does some calculations, and then publishes servo motor angles to a topic.

Move to the /dev_ws/src/cpp_pubsub/src folder

cd ~/dev_ws/src/cpp_pubsub/src

Create a blank C++ file called pubsub_node.cpp.

gedit pubsub_node.cpp

Write the following code. 

This node subscribes to the addison topic and then publishes messages to the addison2 topic.

// Include important C++ header files that provide class
// templates for useful operations.
#include <functional> // Arithmetic, comparisons, and logical operations
#include <memory> // Dynamic memory management
#include <string> // String functions

// ROS Client Library for C++
// Allows use of the most common elements of ROS 2
#include "rclcpp/rclcpp.hpp"

// Built-in message type that will be used to publish data
#include "std_msgs/msg/string.hpp"

using std::placeholders::_1;

// Create the node class named PublishingSubscriber which inherits the attributes
// and methods of the rclcpp::Node class.
class PublishingSubscriber : public rclcpp::Node
{
  public:
    // Constructor creates a node named publishing_subscriber. 
    // The published message count is initialized to 0.
    PublishingSubscriber()
    : Node("publishing_subscriber")
    {
      // Create the subscription.
      // The topic_callback function executes whenever data is published
      // to the 'addison' topic.
      subscription_ = this->create_subscription<std_msgs::msg::String>(
      "addison", 10, std::bind(&PublishingSubscriber::topic_callback, this, _1));
			
      // Publisher publishes String messages to a topic named "addison2". 
      // The size of the queue is 10 messages.
      publisher_ = this->create_publisher<std_msgs::msg::String>("addison2",10);
      
    }

  private:
    // Receives the String message that is published over the topic
    void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
    {
      // Create a new message of type String
      auto message = std_msgs::msg::String();
      
      // Set our message's data attribute
      message.data = "I heard " + msg->data;

      // Publish the message to the topic named "addison2"
      publisher_->publish(message);
    }
    // Declare the subscription attribute
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
		
    // Declaration of the publisher_ attribute		
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  
};

// Node execution starts here
int main(int argc, char * argv[])
{
  // Initialize ROS 2
  rclcpp::init(argc, argv);
 
  // Start processing data from the node as well as the callbacks
  rclcpp::spin(std::make_shared<PublishingSubscriber>());

  // Shutdown the node when finished
  rclcpp::shutdown();
  return 0;
}

Go to the dev_ws/src/cpp_pubsub folder.

cd ~/dev_ws/src/cpp_pubsub

Open CMakeLists.txt.

gedit CMakeLists.txt

Add the executable path underneath the ament_target_dependencies(my_subscriber…) line.

add_executable(pubsub_node src/pubsub_node.cpp)
ament_target_dependencies(pubsub_node rclcpp std_msgs)

Now add this after my_subscriber inside the install section

pubsub_node
14b-pubsub-1

Save the file, and close it.

Return to the root of your workspace:

cd ~/dev_ws/

We need to double check that all the dependencies needed (rclcpp and std_msgs) are already installed.

rosdep install -i --from-path src --rosdistro foxy -y

Build all packages in the workspace.

colcon build

The build was successful.

Open a new terminal window.

Make sure you are in the root of your workspace:

cd ~/dev_ws/

Run the publisher node. 

ros2 run cpp_pubsub my_publisher

Open a new terminal, and run the publishing subscriber node.

ros2 run cpp_pubsub pubsub_node

You will not see any output from running this node, but we can see it if we look at the addison2 topic.

Let’s see what topics are currently active. Open a new terminal, and type:

ros2 topic list -t
14c-active-topics

Let’s listen to the addison2 topic.

ros2 topic echo /addison2
14d-listen-to-addison2-topic-1

Press CTRL + C in all terminals to shutdown the programs.

Zip the Package for Distribution

If you ever want to zip the package and send it to someone, open a new terminal window.

Move to the directory containing your package.

cd ~/dev_ws/src
zip -r cpp_pubsub.zip cpp_pubsub
15-zip-fileJPG

The syntax is:

zip -r <filename.zip> <foldername>

Final Words

Creating C++ publishers and subscribers is a lot more tedious than creating Python publishers and subscribers. But while C++ publishers and subscribers are more complex to get up and running, they tend to have much faster execution time compared to their Python counterparts.

In the next post, we’ll take a look at how to create a basic publisher and subscriber for ROS 2 using Python.