Jaehyun Jeong

Jaehyun Jeong

Applied Mathematics Graduate

A graduate of the Department of Applied Mathematics at Kongju National University. Fascinated by natural intelligence and trying to develop it through RL.
9 Posts
0 Topics
3 Years

Basic Guide to build and run ROS 2 Topics (Python & C++)

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>