---
# System prepended metadata

title: ROS2 Topics
tags: [ros2-basics]

---

# ROS2 Topics

## 0. Post Details

- Reference: [Section 5, ROS 2 Topics - Make Your Nodes Communicate Between Each Other, ROS 2 For Beginners (ROS Jazzy - 2025), Edouard Renard, Udemy.](https://www.udemy.com/course/ros2-for-beginners)
- Post by: Jedd Yang
- Date: 
    - 2025-09-19 (Started)
    - [x] 2025-09-30 (Deadline)
- Keywords: Robotics, ROS2

## 1. ROS 2 Topics - Make Your Nodes Communicate Between Each Other

### 1.1 Write a Python Publisher

```bash=
cd ~/ros2_ws/src/my_py_pkg/my_py_pkg/
touch robot_news_station.py
chmod +x robot_news_station.py 
cd ../..
code .
```

in the new file, `robot_news_station.py`, write

```py=
#!/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)
        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()
```

Remember to add this in the `entry_points` in `setup.py`,

```py=
"robot_news_station = my_py_pkg.robot_news_station:main"
```

and the dependencies in the `package.xml`,

```xml=
<depend>example_interfaces</depend>
```

Open two terminals in terminator, in the first one, build, source and run.

```bash=
cd ~/ros2_ws
colcon build --packages-select my_py_pkg
source ~/.bashrc 
ros2 run my_py_pkg robot_news_station 
```

in the second one,

```bash=
ros2 topic list
ros2 topic echo /robot_news 
# You'll see the output
# data: Hi, this is C3PO from the robot news station
# ---
# data: Hi, this is C3PO from the robot news station
# ---
# data: Hi, this is C3PO from the robot news station
# ---
```

That means your publisher is working successfully.

### 1.2 Write a Python Subscriber

```bash=
cd ~/ros2_ws/src/my_py_pkg/my_py_pkg/
touch smartphone.py
chmod +x smartphone.py 
cd ../..
code .
```

in the new file, `smartphone.py`, write

```py=
#!/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: String):
        self.get_logger().info(msg.data)

def main(args=None):
    rclpy.init(args=args)
    node = SmartphoneNode()
    rclpy.spin(node) # to keep the node alive
    rclpy.shutdown()

if __name__ == "__main__":
    main()
```

Remember to add this in the `entry_points` in `setup.py`,

```py=
"smartphone = my_py_pkg.smartphone:main"
```

Open two terminals in terminator, in the first one, build, source and run.

```bash=
cd ~/ros2_ws
colcon build --packages-select my_py_pkg
source ~/.bashrc 
ros2 run my_py_pkg smartphone # run the subscriber
```

in the second one,

```bash=
ros2 run my_py_pkg robot_news_station # run the publisher
# You'll see the output on the subscriber terminal
# [INFO] [1758088340.978817111] [smartphone]: Smartphone has been started.
# [INFO] [1758088366.559186492] [smartphone]: Hello, this is C3PO from the robot news station
# [INFO] [1758088367.061466364] [smartphone]: Hello, this is C3PO from the robot news station
# [INFO] [1758088367.560473038] [smartphone]: Hello, this is C3PO from the robot news station
# [INFO] [1758088368.059429232] [smartphone]: Hello, this is C3PO from the robot news station
```

That means your subscriber has subscibed your publisher successfully.

### 1.3 Write a C++ Publisher

```bash=
cd ~/ros2_ws/src/my_cpp_pkg/src/
touch robot_news_station.cpp
cd ../..
code .
```

In the VS Code,

```cpp=
#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 counter_;
};

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

And added this to `package.xml`,

```txt=
<depend>example_interfaces</depend>
```

and these to CMakeLists.txt

```cpp=
find_package(example_interfaces REQUIRED)

add_executable(robot_news_station src/robot_news_station.cpp)
ament_target_dependencies(robot_news_station rclcpp example_interfaces)

install(TARGETS
  //...
  robot_news_station
  //...
)
```

Now back to the bash,

```bash=
cd ~/ros2_ws/
colcon build --packages-select my_cpp_pkg 
source install/setup.bash 
```

Open terminator and open two terminals, in the first one,

```bash=
ros2 run my_cpp_pkg robot_news_station
# You will see [INFO] [1758249996.341116903] [robot_news_station]: Robot news station has been started.
```

in the second one,

```bash=
ros2 topic echo /robot_news
# You will see
# data: Hi, this is R2D2 from the robot news station.
# ---
# data: Hi, this is R2D2 from the robot news station.
# ---
# data: Hi, this is R2D2 from the robot news station.
# ---
```

Now we use the Python subscriber to subscribe the C++ publisher,

in the second terminal, `Ctrl + C` cancel it and run

```bash=
ros2 run my_py_pkg smartphone
# You will see
# [INFO] [1758250078.882947170] [smartphone]: Smartphone has been started.
# [INFO] [1758250079.345520218] [smartphone]: Hi, this is R2D2 from the robot news station.
# [INFO] [1758250079.843194320] [smartphone]: Hi, this is R2D2 from the robot news station.
# [INFO] [1758250080.343514942] [smartphone]: Hi, this is R2D2 from the robot news station.
```

### 1.4 Write a C++ Subscriber

```bash=
cd
cd ros2_ws/src/
touch smartphone.cpp
code .
```

Write the following code in the `smartphone.cpp`,

```cpp=
#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, // 10 is the queue size for the topic
                std::bind(&SmartphoneNode::callbackRobotNews, this, _1)
            );

            RCLCPP_INFO(this->get_logger(), "Smartphone has been started.");
            // timer_ = this->create_wall_timer(std::chrono::seconds(1), std::bind(&MyNode::timerCallback, this));
        }
    private:
        void callbackRobotNews(const example_interfaces::msg::String::SharedPtr msg){
            RCLCPP_INFO(this->get_logger(), "%s", msg->data.c_str());
            // counter_++;
        }
        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;
}
```

Remember to add this to the `CMakeLists.txt`,

```cmake=
# ...
add_executable(smartphone src/smartphone.cpp)
ament_target_dependencies(smartphone rclcpp example_interfaces)

install(TARGETS
  # ...
  smartphone
)
```

Now open up a terminator in two terminals, in the first one, run 

```bash
ros2 run my_cpp_pkg smartphone
# You'll see 
# [INFO] [1758540224.354945632] [smartphone]: Smartphone has been started.
```

in the second one, run the C++ publisher

```bash
ros2 run my_cpp_pkg robot_news_station
# You'll see this in the subscriber terminal
# [INFO] [1758540280.956643721] [smartphone]: Hi, this is R2D2 from the robot news station.
# [INFO] [1758540281.452004473] [smartphone]: Hi, this is R2D2 from the robot news station.
# [INFO] [1758540281.953071703] [smartphone]: Hi, this is R2D2 from the robot news station.
# ...
```

Or you can run the Python publisher

```bash
ros2 run my_py_pkg robot_news_station
# You'll see this in the subscriber terminal
# [INFO] [1758540301.478050457] [smartphone]: Hello, this is C3PO from the robot news station
# [INFO] [1758540301.981253411] [smartphone]: Hello, this is C3PO from the robot news station
# [INFO] [1758540302.482983316] [smartphone]: Hello, this is C3PO from the robot news station
# ...
```

### 1.5 Useful ROS2 Topics Commands

1. Introspect ROS2 Topics
```bash=
# list all topics
ros2 topic list
# /parameters_events
# /robot_news
# /rosout

# show publisher and subscriber counts
ros2 topic info /robot_news 

# show bandwidth
ros2 topic bw /robot_news
```

2. Remap a Topic at Runtime

```bash=
# remap publisher
ros2 run my_py_pkg robot_news_station --ros-args -r __node:=my_station -r robot_news:=abc
# remap subscriber
ros2 run my_py_pkg smartphone --ros-args -r robot_news:=abc
```
