Topic Communication in ROS2
Topic Communication in ROS2
Topics serve as a fundamental communication mechanism in ROS2, implementing a publish-subscribe model where one node publishes data to a topic and other nodes can subscribe to receive that data.
Publish-Subscribe Model
The publish-subscribe architecture supports:
- One-to-one communication
- One-to-many communication
- Many-to-one communication
- Many-to-many communication
- Nodes can subscribe to topics they themselves publish
ROS2 Topic Utilities
To visualize topic communication paths, ROS2 provides the rqt_graph tool when running the built-in demo nodes:
ros2 run demo_nodes_cpp listener
ros2 run demo_nodes_cpp talker
rqt_graph
Beyond rqt_graph, ROS2 offers CLI (Command Line Interface) tools for topic operations:
ros2 topic -h
This displays all available topic-related CLI commands.
Implementing Topics with rclcpp
Creating a Publisher Node
Step 1: Set up a new workspace and package
Create a new workspace ros2_demo_ws and a package named demo_topic_cpp, then add a source file:
mkdir -p ./ros2_demo_ws/src
cd ./ros2_demo_ws/src
ros2 pkg create demo_topic_cpp --build-type ament_cmake --license Apache-2.0 --dependencies rclcpp
touch ./demo_topic_cpp/src/publisher_node.cpp
Verify the directory structure:
.
└── demo_topic_cpp
├── CMakeLists.txt
├── include
│ └── demo_topic_cpp
├── LICENSE
├── package.xml
└── src
└── publisher_node.cpp
4 directories, 4 files
Create a basic node using an object-oriented approach:
#include "rclcpp/rclcpp.hpp"
#include <string>
class PublisherNode : public rclcpp::Node {
public:
explicit PublisherNode(const std::string& node_name) : Node(node_name) {
RCLCPP_INFO(this->get_logger(), "%s node initialized", node_name.c_str());
}
private:
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<PublisherNode>("publisher_node");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Update CMakeLists.txt to add the executable:
add_executable(publisher_node src/publisher_node.cpp)
ament_target_dependencies(publisher_node rclcpp)
install(TARGETS
publisher_node
DESTINATION lib/${PROJECT_NAME}
)
Step 2: Include message interfaces
Message interfaces are essential for ROS2 communication, enabling message serialization and deserialization. ROS2 provides standard message interfaces with auto-generated C++ and Python headers.
For ament_cmake packages, integrating message interfaces requires modifications in three places:
- In
CMakeLists.txt: usefind_packageandament_target_dependencies - In
package.xml: add<depend>tags for the message package - In source code: include the message header with
#include "message_package/xxx/xxx.hpp"
Modified CMakeLists.txt:
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
add_executable(publisher_node src/publisher_node.cpp)
ament_target_dependencies(publisher_node rclcpp std_msgs)
install(TARGETS
publisher_node
DESTINATION lib/${PROJECT_NAME}
)
Modified package.xml:
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
Alternatively, specify dependencies during package creation:
ros2 pkg create demo_topic_cpp01 --build-type ament_cmake --license Apache-2.0 --dependencies rclcpp std_msgs
Add the message header to the source file:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class PublisherNode : public rclcpp::Node
Step 3: Create the publisher
Key parameters for publisher creation:
topic_name: the topic identifier, set to"robot_cmd"Qos: queue depth usingKeepLast, specify as an integerMessageT: template parameter for message type, set tostd_msgs::msg::String
Implementation:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class PublisherNode : public rclcpp::Node {
public:
explicit PublisherNode(const std::string& node_name) : Node(node_name) {
RCLCPP_INFO(this->get_logger(), "%s node initialized", node_name.c_str());
cmd_publisher_ = this->create_publisher<std_msgs::msg::String>("robot_cmd", 10);
}
private:
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr cmd_publisher_;
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<PublisherNode>("publisher_node");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Step 4: Add a timer for periodic publishing
Timer parameters:
period: interval between callback invocationscallback: function to executegroup: callback group, defaults tonullptr
Complete publisher with timer:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include <chrono>
class PublisherNode : public rclcpp::Node {
public:
explicit PublisherNode(const std::string& node_name) : Node(node_name) {
RCLCPP_INFO(this->get_logger(), "%s node initialized", node_name.c_str());
cmd_publisher_ = this->create_publisher<std_msgs::msg::String>("robot_cmd", 10);
timer_ = this->create_wall_timer(std::chrono::milliseconds(500),
[this]() { this->publishCallback(); });
}
private:
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr cmd_publisher_;
rclcpp::TimerBase::SharedPtr timer_;
void publishCallback() {
std_msgs::msg::String msg;
msg.data = "move_forward";
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg.data.c_str());
cmd_publisher_->publish(msg);
}
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<PublisherNode>("publisher_node");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Build and test:
colcon build --packages-select demo_topic_cpp
source ./install/setup.bash
ros2 run demo_topic_cpp publisher_node
Verify with CLI tools:
ros2 topic list
ros2 topic echo /robot_cmd
Creating a Subscriber Node
Step 1: Create the subscriber source file
cd ./ros2_demo_ws/src
touch ./demo_topic_cpp/src/subscriber_node.cpp
Basic subscriber structure:
#include "rclcpp/rclcpp.hpp"
class SubscriberNode : public rclcpp::Node {
public:
explicit SubscriberNode(const std::string& node_name) : Node(node_name) {
RCLCPP_INFO(this->get_logger(), "%s node started", node_name.c_str());
}
private:
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<SubscriberNode>("subscriber_node");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Update CMakeLists.txt:
add_executable(subscriber_node src/subscriber_node.cpp)
ament_target_dependencies(subscriber_node rclcpp std_msgs)
install(TARGETS
publisher_node
subscriber_node
DESTINATION lib/${PROJECT_NAME}
)
Test the basic subscriber:
colcon build --packages-select demo_topic_cpp
source install/setup.bash
ros2 run demo_topic_cpp subscriber_node
Step 2: Implement the subscription callback
Key parameters for create_subscription:
topic_name: topic to subscribe toqos: message queue depthcallback: function to handle receievd messages
Full subscriber implementation:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class SubscriberNode : public rclcpp::Node {
public:
explicit SubscriberNode(const std::string& node_name) : Node(node_name) {
RCLCPP_INFO(this->get_logger(), "%s node started", node_name.c_str());
cmd_subscription_ = this->create_subscription<std_msgs::msg::String>(
"robot_cmd", 10,
[this](const std_msgs::msg::String::SharedPtr msg) {
this->handleMessage(msg);
});
}
private:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr cmd_subscription_;
void handleMessage(const std_msgs::msg::String::SharedPtr msg) {
double velocity = 0.0;
if (msg->data == "move_forward") {
velocity = 0.3;
}
RCLCPP_INFO(this->get_logger(), "Received command [%s], velocity [%f]",
msg->data.c_str(), velocity);
}
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<SubscriberNode>("subscriber_node");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Build and run both nodes:
colcon build --packages-select demo_topic_cpp
source install/setup.bash
ros2 run demo_topic_cpp publisher_node
ros2 run demo_topic_cpp subscriber_node
Sample output:
[INFO] [1712900021.099703358] [publisher_node]: Publishing: 'move_forward'
[INFO] [1712900021.599713885] [publisher_node]: Publishing: 'move_forward'
[INFO] [1712900022.099719858] [publisher_node]: Publishing: 'move_forward'
[INFO] [1712900022.599664745] [publisher_node]: Publishing: 'move_forward'
[INFO] [1712900027.600149013] [subscriber_node]: Received command [move_forward], velocity [0.300000]
[INFO] [1712900028.100186298] [subscriber_node]: Received command [move_forward], velocity [0.300000]
[INFO] [1712900028.600202891] [subscriber_node]: Received command [move_forward], velocity [0.300000]
Reference
d2lros2 ROS2 Tutorial