In this tutorial, we will create a C++ subscriber for ROS 2.
In ROS 2 (Robot Operating System 2), a C++ subscriber is a program (written in C++) that listens for messages being published on a specific topic.
Topics in ROS 2 are channels of communication named according to the type of information they carry, such as “/robot/speed” for speed information or “/camera/image” for vision information. Each subscriber in ROS 2 declares its interest in a particular topic and is programmed to react or process the messages received on that topic.
The official instructions for creating a subscriber are here, but I will walk you through the entire process, step by step.
We will be following the ROS 2 C++ Style Guide.
Let’s get started!
Follow along with me click by click, keystroke by keystroke.
Prerequisites
- You have completed this tutorial: How to Create a ROS 2 Python Subscriber – Jazzy. (recommended)
- I am assuming you are using Visual Studio Code, but you can use any code editor.
Write the Code
Open a terminal, and type these commands to open VS Code.
cd ~/ros2_ws && code .
Right-click on the src/ros2_fundamentals_examples/src folder to create a new file called “cpp_minimal_subscriber.cpp”.
Type the following code inside cpp_minimal_subscriber.cpp:
/**
* @file minimal_cpp_subscriber.cpp
* @brief Demonstrates subscribing to string messages on a ROS 2 topic.
*
* Description: Demonstrates the basics of subscribing to messages within
* the ROS 2 framework. The core functionality of this subscriber is to
* display output to the terminal window when a message is received over
* a topic.
*
* -------
* Subscription Topics:
* String message
* /cpp_example_topic - std_msgs/String
* -------
* Publishing Topics:
* None
* -------
* @author Addison Sears-Collins
* @date November 5, 2024
*/
#include "rclcpp/rclcpp.hpp" // ROS 2 C++ client library
#include "std_msgs/msg/string.hpp" // Handle tring messages
using std::placeholders::_1; // Placeholder for callback function argument
/**
* @class MinimalCppSubscriber
* @brief Defines a minimal ROS 2 subscriber node.
*
* This class inherits from rclcpp::Node and
* demonstrates creating a subscriber and
* subscribing to messages.
*/
class MinimalCppSubscriber : public rclcpp::Node
{
public:
/**
* @brief Constructs a MinimalCppSubscriber node.
*
* Sets up a subscriber for 'std_msgs::msg::String' messages
* on the "/cpp_example_topic" topic.
*/
MinimalCppSubscriber() : Node("minimal_cpp_subscriber")
{
// Create a subscriber object for listening to string messages on
// with a queue size of 10.
subscriber_ = create_subscription<std_msgs::msg::String>
(
"/cpp_example_topic",
10,
std::bind(
&MinimalCppSubscriber::topicCallback,
this,
_1
)
);
}
/**
* @brief This function runs every time a message is received on the topic.
*
* This is the callback function of the subscriber. It publishes a
* string message every time a message is received on the topic.
*
* @param msg The string message received on the topic
* @return void
*/
void topicCallback(const std_msgs::msg::String &msg) const
{
// Write a message every time a new message is received on the topic.
RCLCPP_INFO_STREAM(get_logger(), "I heard: " << msg.data.c_str());
}
private:
// Member variables.
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscriber_;
};
/**
* @brief Main function.
*
* Initializes the ROS 2 system and runs the minimal_cpp_subscriber node.
* It keeps the node alive until it is manually terminated.
*/
int main(int argc, char * argv[])
{
// Initialize ROS 2.
rclcpp::init(argc, argv);
// Create an instance of the MinimalCppSubscriber node and keep it running.
auto minimal_cpp_subscriber_node = std::make_shared<MinimalCppSubscriber>();
rclcpp::spin(minimal_cpp_subscriber_node);
// Shutdown ROS 2 upon node termination.
rclcpp::shutdown();
// End of program.
return 0;
}
Configure CMakeLists.txt
Now we need to modify the CMakeLists.txt file inside the package so that the ROS 2 system will be able to find the code we just wrote.
Open up the CMakeLists.txt file that is inside the package.
Make it look like this:
cmake_minimum_required(VERSION 3.8)
project(ros2_fundamentals_examples)
# Check if the compiler being used is GNU's C++ compiler (g++) or Clang.
# Add compiler flags for all targets that will be defined later in the
# CMakeLists file. These flags enable extra warnings to help catch
# potential issues in the code.
# Add options to the compilation process
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# Locate and configure packages required by the project.
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclpy REQUIRED)
find_package(std_msgs REQUIRED)
# Define a CMake variable named dependencies that lists all
# ROS 2 packages and other dependencies the project requires.
set(dependencies
rclcpp
std_msgs
)
# Add the specified directories to the list of paths that the compiler
# uses to search for header files. This is important for C++
# projects where you have custom header files that are not located
# in the standard system include paths.
include_directories(
include
)
# Tells CMake to create an executable target named cpp_minimal_publisher
# from the source file src/cpp_minimal_publisher.cpp. Also make sure CMake
# knows about the program's dependencies.
add_executable(cpp_minimal_publisher src/cpp_minimal_publisher.cpp)
ament_target_dependencies(cpp_minimal_publisher ${dependencies})
add_executable(cpp_minimal_subscriber src/cpp_minimal_subscriber.cpp)
ament_target_dependencies(cpp_minimal_subscriber ${dependencies})
# Copy necessary files to designated locations in the project
install (
DIRECTORY ros2_fundamentals_examples scripts
DESTINATION share/${PROJECT_NAME}
)
install(
DIRECTORY include/
DESTINATION include
)
# Install cpp executables
install(
TARGETS
cpp_minimal_publisher
cpp_minimal_subscriber
DESTINATION lib/${PROJECT_NAME}
)
# Install Python modules for import
ament_python_install_package(${PROJECT_NAME})
# Add this section to install Python scripts
install(
PROGRAMS
ros2_fundamentals_examples/py_minimal_publisher.py
ros2_fundamentals_examples/py_minimal_subscriber.py
DESTINATION lib/${PROJECT_NAME}
)
# Automates the process of setting up linting for the package, which
# is the process of running tools that analyze the code for potential
# errors, style issues, and other discrepancies that do not adhere to
# specified coding standards or best practices.
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
# Used to export include directories of a package so that they can be easily
# included by other packages that depend on this package.
ament_export_include_directories(include)
# Generate and install all the necessary CMake and environment hooks that
# allow other packages to find and use this package.
ament_package()
Build the Workspace
To build the workspace, open a terminal window, and type:
build
If this command doesn’t work, type these commands:
echo "alias build='cd ~/dev_ws/ && colcon build && source ~/.bashrc'" >> ~/.bashrc
build
Run the Nodes
First run your publisher node.
ros2 run ros2_fundamentals_examples cpp_minimal_publisher
Now run your subscriber node in another terminal window.
ros2 run ros2_fundamentals_examples cpp_minimal_subscriber
An Important Note on Subscribers and Publishers
In the example above, we published a string message to a topic named /cpp_example_topic using a C++ node, and we subscribed to that topic using a C++node.
ROS 2 is language agnostic, so we could have also used a Python node to do the publishing and a C++ node to do the subscribing, and vice versa.
That’s it for now. Keep building!