A Topic is a receiver of a signal from a publisher (node). The publisher is able to send data to the topic while not knowing which subscribers(nodes) receive this data. Similarly, subscribers do not know which nodes send the data to the topic. On top of that, Nodes’ capability of sending data is not restricted to sending to single topic but sending to multiple topics to different topics. In addition to that, the data stream is unidirectional. Data can be sent to subscriber but cannot be returned to the publisher.
Technically, ROS 2 messages are transferred using middleware named DDS. However, users do not need to handle DDS as libraries such as RCL provide abstraction.
Simple Python code
Publisher
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.msg import String
class RobotNewsStationNode(Node):
def __init__(self):
super().__init__("robot_news_station") # Choosing the same node name with file name is quite common.
self.robot_name = "C3PO"
self.publisher_ = self.create_publisher(
String,
"robot_news",
10
)
# 0.5 means twice per seconds
self.timer_ = self.create_timer(0.5, self.publish_news)
self.get_logger().info("Robot News Station has been started.")
def publish_news(self):
msg = String()
msg.data = f"Hi, this is {self.robot_name} from the robot news station."
self.publisher_.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = RobotNewsStationNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
NOTE: Do not forget to add “example_interfaces” library in the package.xml file for String message type and install the node in the setup.py.
Subscriber
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.msg import String
class SmartphoneNode(Node):
def __init__(self):
super().__init__("smartphone")
self.subscriber_ = self.create_subscription(
String,
"robot_news",
# When the subscriber receives the message
self.callback_robot_news,
# queue size
10
)
self.get_logger().info("Smartphone has been started.")
def callback_robot_news(self, msg: String):
self.get_logger().info(msg.data)
def main(args=None):
rclpy.init(args=args)
node = SmartphoneNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
Simple C++ code
Publisher
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/msg/string.hpp"
using namespace std::chrono_literals;
class RobotNewsStationNode : public rclcpp::Node{
public:
RobotNewsStationNode() : Node("robot_news_station"), robot_name_("R2D2")
{
publisher_ = this->create_publisher<example_interfaces::msg::String>("robot_news", 10);
timer_ = this->create_wall_timer(0.5s, std::bind(&RobotNewsStationNode::publishNews, this));
RCLCPP_INFO(this->get_logger(), "Robot News Station has been started");
}
private:
void publishNews(){
auto msg = example_interfaces::msg::String();
msg.data = std::string("Hi, this is ") + robot_name_ + std::string(" from the robot news station.");
publisher_->publish(msg);
}
std::string robot_name_;
rclcpp::Publisher<example_interfaces::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char **argv){
rclcpp::init(argc, argv);
auto node = std::make_shared<RobotNewsStationNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Subscriber
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/msg/string.hpp"
using namespace std::placeholders;
class SmartphoneNode : public rclcpp::Node{
public:
SmartphoneNode() : Node("smartphone")
{
subscriber_ = this->create_subscription<example_interfaces::msg::String>(
"robot_news",
10,
std::bind(&SmartphoneNode::callbackRobotNews, this, _1)
);
RCLCPP_INFO(this->get_logger(), "Smartphone has been started.");
}
private:
void callbackRobotNews(const example_interfaces::msg::String::SharedPtr msg){
RCLCPP_INFO(this->get_logger(), "%s", msg->data.c_str());
}
rclcpp::Subscription<example_interfaces::msg::String>::SharedPtr subscriber_;
};
int main(int argc, char **argv){
rclcpp::init(argc, argv);
auto node = std::make_shared<SmartphoneNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Bags
Suppose you are building robot software with ROS 2 and a robot. Then you need the robot to code and test with. But “Bag” provides very handy features in this case. ROS 2 Bag can save data from topic with any amount of time, then can replay these data as many times as you want.
# Help
ros2 bag -h
# Record topics
ros2 bag record <topic name 1> <topic name 2> ...
# Record topics with custom record name
ros2 bag record -o <record name> <topic name 1> <topic name 2> ...
# Record all topics
ros2 bag record -a
# Play a record
ros2 bag play <record name>
# Print record Information
ros2 bag info <record name>