개발하는 핑구
article thumbnail
Published 2023. 2. 23. 13:29
[ROS2 시작하기 - 3] Topic Robotics/ROS2

ROS2 Topics

  • Topic is a message
  • If publisher send FM signal over a topic, and if you are trying to decode AM signal on your phone, it will not work. publisher and subscriber must have same data structure
  • we can have many publishers on a topic, and also have many subscribers on a topic
  • On a view of a publisher or subscriber, a publisher is not aware of the other publishers and is not aware of who is receiving the data on the topic. conversion is same.
  • if you have three subscribers and no publisher, they just receive nothing, but it’s working
  • if you have two publishers on the topic and no subscriber, the data is just sent, but no one receives, but it’s working
  • a node can have many subscribers and publishers for many different topics simultaneously.

Publish

In Python

#!/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")

        self.robot_name = "C3PO"
        self.publisher_ = self.create_publisher(String, "robot_news", 10)
																							#(data_type, topic_name, queue_size) 
        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 = "Hi, this is " + str(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()

You can write node name, file name, executable name identically.

  • Show interface
ros2 interface show example_interfaces/msg/String
"
# This is an example message of using a primitive datatype, string.
# If you want to test with this that's fine, but if you are deploying
# it into a system you should create a semantically meaningful message type.
# If you want to embed it in another message, use the primitive data type instead.
string data
"

Your packages always depends on another package, you will need to add a dependancy.

  • show topic list
ros2 topic list
  • get the data of topic
ros2 topic echo /robot_news

In C++

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/msg/string.hpp"

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(std::chrono::milliseconds(500),
                                         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;
}

To able to link this package to any node we create, you should add lines in CMakeLists.txt

And to run your node, you should write more lines in CMakeLists.txt whenever you create node.

Subscriber

In Python

#!/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", self.callback_robot_news, 10)
        self.get_logger().info("Smartphone has been started.")

    def callback_robot_news(self, msg):
        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()

If you run only smartphone node, it doesn’t receive any topic but still working.

And if you run robot_news_station node, you can see the topic smartphone node is getting.

They communicate only with topic.

In C++

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/msg/string.hpp"

class Smartphoneode : public rclcpp::Node
{
public:
    Smartphoneode() : Node("smartphone")
    {
        subscriber_ = this->create_subscription<example_interfaces::msg::String>(
            "robot_news", 10,
            std::bind(&Smartphoneode::callbackRobotNews, this, std::placeholders::_1));
            //std::bind(&Smartphoneode::callbackRobotNews, this, the number of parameters...));
        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<Smartphoneode>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

And you write some codes to use this node in CMakeLists.txt, as you create publisher

Debug Topics with Cammand Line Tools

ros2 topic info /robot_news
"
Type: example_interfaces/msg/String
Publisher count: 1
Subscription count: 0
"

we can create a subscriber inside the terminal, but not node

ros2 topic echo /robot_news

then you will see that the Subscription count is increased to 1

ros2 topic info /robot_news
"
Type: example_interfaces/msg/String
Publisher count: 1
Subscription count: 1
"

we can see the publish rate. If the node has a very high publish rate, you can drop the hz.

(4 frequency for one second = 4Hz)

ros2 topic hz /robot_news
"
average rate: 2.008
	min: 0.491s max: 0.502s std dev: 0.00447s window: 4
average rate: 2.006
	min: 0.491s max: 0.502s std dev: 0.00360s window: 7
average rate: 2.003
	min: 0.491s max: 0.504s std dev: 0.00352s window: 10
average rate: 2.003
	min: 0.491s max: 0.504s std dev: 0.00324s window: 13
"

we can see the bandwidth.

(Bandwidth is the amount of data that can be delivered in a unit of time)

ros2 topic bw /robot_news
"
Subscribed to [/robot_news]
182 B/s from 2 messages
	Message size mean: 56 B min: 56 B max: 56 B
139 B/s from 4 messages
	Message size mean: 56 B min: 56 B max: 56 B
129 B/s from 6 messages
	Message size mean: 56 B min: 56 B max: 56 B
"

we can publish a topic in terminal

ros2 topic pub -r 10 /robot_news example_interfaces/msg/String "{data: 'hello from terminal'}"

Remap a topic name

ros2 run my_py_pkg robot_news_station --ros-args -r robot_news:=my_news
# or you can remap both of node's name and topic's name
ros2 run my_py_pkg robot_news_station --ros-args -r __node:=my_station -r robot_news:=my_news

and if you run smartphone node, the subscriber can’t receive the topic robot_news.

To receive the topic you can use this command.

ros2 run my_py_pkg smartphone --ros-args -r robot_news:=my_news

Moniter Topics With rqt and rqt_graph

# run publisher has same topic
ros2 run my_cpp_pkg robot_news_station
ros2 run my_py_pkg robot_news_station --ros-args -r __node:=my_station
ros2 run my_py_pkg robot_news_station --ros-args -r __node:=my_station2
# and has other topic
ros2 run my_py_pkg robot_news_station --ros-args -r __node:=other_station -r robot_news:=my_news

# run subscriber has same topic
ros2 run my_py_pkg smartphone
ros2 run my_py_pkg smartphone --ros-args -r __node:=smartphone2
# and has other topic
ros2 run my_py_pkg smartphone --ros-args -r __node:=other_smartphone -r robot_news:=my_news

Experiment on Topics with Turtlesim

ros2 run turtlesim turtlesim_node
ros2 run turtlesim turtle_teleop_key

Two nodes have same topic.

Let’s see the topic’s information

ros2 interface show geometry_msgs/msg/Twist
"
# This expresses velocity in free space broken into its linear and angular parts.

Vector3  linear
Vector3  angular
"

It has linear field and angular field. And These are another message.

Then what is Vector3?

ros2 interface show geometry_msgs/msg/Vector3
"
# This represents a vector in free space.

# This is semantically different than a point.
# A vector is always anchored at the origin.
# When a transform is applied to a vector, only the rotational component is applied.

float64 x
float64 y
float64 z
"

The Vector3 contains three field X, Y, and Z which are float64. These are primitive data type.

In turtle_teleop_key terminal, if you press the key, the node sends a message on a topic.

So you can monitor the topic

Activity

It’s time for a new activity on ROS2 topics.

Here is a representation of the graph you should get at the end.

The blue boxes are nodes and the green boxes are topics.

And here’s the final result you should get in the terminal (of course the numbers may be different):


ros2 topic echo /number_count

data: 38

---

data: 40

---

data: 42

---


You’ll create 2 nodes from scratch. In the first one you’ll have 1 publisher, and in the second one, 1 publisher & 1 subscriber.

  • The number_publisher node publishes a number (always the same) on the “/number” topic, with the existing type example_interfaces/msg/Int64.
  • The number_counter node subscribes to the “/number” topic. It keeps a counter variable. Every time a new number is received, it’s added to the counter. The node also has a publisher on the “/number_count” topic. When the counter is updated, the publisher directly publishes the new value on the topic.

A few hints:

  • Check what to put into the example_interfaces/msg/Int64 with the “ros2 interface show” command line tool.
  • It may be easier to do the activity in this order: first create the number_publisher node, check that the publisher is working with “ros2 topic”. Then create the number_counter, focus on the subscriber. And finally create the last publisher.
  • In the number_counter node, the publisher will publish messages directly from the subscriber callback.

And now, let’s get to work! :)

Make sure you really spend some time on this activity, even if you can’t finish it. That’s the only way you will truly progress.

You can do this activity in both Python and Cpp. In fact, you could write the first node in Python, and the second in Cpp, and vice versa!

I’ll see you in the next lecture for the solution.

Solution

  • number_publisher.py (publisher)
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node

from example_interfaces.msg import Int64

class NumberPublisher(Node):
    def __init__(self):
        super().__init__("number_publisher")

        self.number_ = 2
        self.publisher_ = self.create_publisher(Int64, "number", 10)
        self.timer_ = self.create_timer(1.0, self.publish_number)
        self.get_logger().info("Number Publisher has been started.")

    def publish_number(self):
        msg = Int64()
        msg.data = self.number_
        self.publisher_.publish(msg)
 
def main(args=None):
    rclpy.init(args=args)
    node = NumberPublisher()
    rclpy.spin(node)
    rclpy.shutdown()
 
 
if __name__ == "__main__":
    main()
  • number_counter.py (publisher/subscriber)
#!/usr/bin/env python3
from ast import In
import rclpy
from rclpy.node import Node

from example_interfaces.msg import Int64

class NumberCounter(Node):      
    def __init__(self):
        super().__init__("number_counter")
        self.counter_ = 0
        self.number_count_publisher_ = self.create_publisher(
            Int64, "number_count", 10)
        self.subscriber_ = self.create_subscription(
            Int64, "number", self.callback_number, 10)
        self.get_logger().info("Number Counter has been started.")

    def callback_number(self, msg):
        self.counter_ += msg.data
        new_msg = Int64()
        new_msg.data = self.counter_
        self.number_count_publisher_.publish(new_msg)

        
def main(args=None):
    rclpy.init(args=args)
    node = NumberCounter()
    rclpy.spin(node)
    rclpy.shutdown()
 
 
if __name__ == "__main__":
    main()
  • number_publisher.cpp (publisher)
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/msg/int64.hpp"

class NumberPublisherNode : public rclcpp::Node
{
public:
    NumberPublisherNode() : Node("number_publisher"), number_(2)
    {
        number_publisher_ = this->create_publisher<example_interfaces::msg::Int64>("number", 10);
        number_timer_ = this->create_wall_timer(std::chrono::seconds(1),
                                                std::bind(&NumberPublisherNode::publishNumber, this));
        RCLCPP_INFO(this->get_logger(), "Number publisher has been started.");
    }

private:
    void publishNumber()
    {
        auto msg = example_interfaces::msg::Int64();
        msg.data = number_;
        number_publisher_->publish(msg);
    }

    int number_;
    rclcpp::Publisher<example_interfaces::msg::Int64>::SharedPtr number_publisher_;
    rclcpp::TimerBase::SharedPtr number_timer_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared<NumberPublisherNode>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}
  • number_counter.cpp (publisher/subscriber)
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/msg/int64.hpp"

class NumberCounterNode : public rclcpp::Node
{
public:
    NumberCounterNode() : Node("number_counter"), counter_(0)
    {
        counter_publisher_ = this->create_publisher<example_interfaces::msg::Int64>("number_count", 10);
        number_subscriber_ = this->create_subscription<example_interfaces::msg::Int64>(
            "number", 10, std::bind(&NumberCounterNode::callbackNumber, this, std::placeholders::_1));
    }

private:
    void callbackNumber(const example_interfaces::msg::Int64::SharedPtr msg)
    {
        counter_ += msg->data;
        auto newMsg = example_interfaces::msg::Int64();
        newMsg.data = counter_;
        counter_publisher_->publish(newMsg);
    }

    int counter_;
    rclcpp::Publisher<example_interfaces::msg::Int64>::SharedPtr counter_publisher_;
    rclcpp::Subscription<example_interfaces::msg::Int64>::SharedPtr number_subscriber_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared<NumberCounterNode>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

Section Conclusion

In this section you have seen how to use ROS2 Topics to communicate between your nodes.

Now, you can already create more complex applications!

To recap, a topic is:

  • A named bus over which nodes exchange messages
  • Used for unidirectional data streams
  • Anonymous: publishers don’t know who is subscribing, and subscribers don’t know who is publishing.

To implement topics in your ROS2 application:

  • First create a node (or start from an existing one), then inside your node you can create any number of publishers/subscribers.
  • A publisher and subscriber must publish/subscribe to the same topic name, and use the same data type. Those are the 2 conditions for successful topic communication.
  • Then, once you’ve added some publishers/subscribers in your nodes, just launch your nodes, and the communication starts! You can debug them using the “ros2” command line tool, as well as rqt.

Note: your topic names should start with a letter (“98.7” is not a valid topic name, it was just used to introduce the concept with a real life analogy). By using nodes, you can easily separate your code into different independent modules, one for each part of your application. And with topics, you can make them communicate. And now, after Topics, the next logical step is to look at ROS2 Services. That’s what we’ll see in the next section.

'Robotics > ROS2' 카테고리의 다른 글

[ROS2 시작하기 - 5] Interface  (0) 2023.02.27
[ROS2 시작하기 - 4] Service  (0) 2023.02.24
[ROS2 시작하기 - 2] Tools  (0) 2023.02.21
[ROS2 시작하기 - 1] Node  (0) 2023.02.20
[ROS2 시작하기 - 0] 환경 셋팅  (0) 2023.02.19
profile

개발하는 핑구

@sinq

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!