Section 2: Topics - Publish and Subscribe
Estimated Reading Time: 8 minutes Bloom's Level: Understand, Apply Key Learning: One-to-many communication with decoupled nodes
The Problem with Direct Communication
Imagine you have a sensor node and a control node. How should they talk?
❌ Direct Connection (Bad)
Sensor Node → Control Node
Problems:
- Control node must exist before sensor starts
- Sensor knows about control (tight coupling)
- Hard to add a second control node
- If control crashes, sensor can't publish elsewhere
✅ Topic-Based (Good)
Sensor Node → [Topic: /data] ← Control Node
↗ Logger Node
↗ Visualizer Node
Benefits:
- Nodes don't know about each other (decoupled)
- Multiple nodes can listen (one-to-many)
- Nodes can start/stop independently
- Easy to add new subscribers
What is a Topic?
A topic is a named channel for publishing and subscribing to messages.
Think of it like a bulletin board:
- Publisher: Posts a message on the board
- Subscribers: Read messages from the board
- Topic Name: The bulletin board label (e.g.,
/temperature) - Message Type: What data is posted (e.g.,
Float32)
Key characteristics:
- One-to-many: One publisher, many subscribers
- Asynchronous: Publisher doesn't wait for subscribers
- Typed: Every topic has a message type
- Named: Topics have unique names starting with
/
Publishers and Subscribers
Publisher Node
# Creates a publisher
publisher = self.create_publisher(String, '/greeting', 10)
# Publishes messages
msg = String()
msg.data = 'Hello!'
publisher.publish(msg)
Subscriber Node
# Creates a subscriber with callback
subscription = self.create_subscription(
String,
'/greeting',
self.listener_callback,
10
)
# Callback handles incoming messages
def listener_callback(self, msg):
self.get_logger().info(f'Received: {msg.data}')
Code Example 2: Publisher/Subscriber System
Location: code-examples/ros2_packages/pub_sub_py/
Generated by: ros2-code-generator skill
Publisher Node
# File: pub_sub_py/pub_sub_py/publisher.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class PublisherNode(Node):
def __init__(self):
super().__init__('publisher')
# Create a publisher for String messages on /greeting topic
self.publisher = self.create_publisher(String, '/greeting', 10)
# Timer to publish every 1 second
self.timer = self.create_timer(1.0, self.timer_callback)
self.i = 0
def timer_callback(self):
# Create and publish message
msg = String()
msg.data = f'Hello from publisher: #{self.i}'
self.publisher.publish(msg)
self.get_logger().info(f'Publishing: {msg.data}')
self.i += 1
def main(args=None):
rclpy.init(args=args)
node = PublisherNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
Subscriber Node
# File: pub_sub_py/pub_sub_py/subscriber.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SubscriberNode(Node):
def __init__(self):
super().__init__('subscriber')
# Create subscriber that listens to /greeting topic
# Calls listener_callback when messages arrive
self.subscription = self.create_subscription(
String,
'/greeting',
self.listener_callback,
10
)
def listener_callback(self, msg):
# This function runs when a message arrives
self.get_logger().info(f'Received: {msg.data}')
def main(args=None):
rclpy.init(args=args)
node = SubscriberNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
Running a Pub/Sub System
Terminal 1: Publisher
ros2 run pub_sub_py publisher
Output:
[INFO] [publisher]: Publishing: Hello from publisher: #0
[INFO] [publisher]: Publishing: Hello from publisher: #1
[INFO] [publisher]: Publishing: Hello from publisher: #2
Terminal 2: Subscriber
ros2 run pub_sub_py subscriber
Output:
[INFO] [subscriber]: Received: Hello from publisher: #0
[INFO] [subscriber]: Received: Hello from publisher: #1
[INFO] [subscriber]: Received: Hello from publisher: #2
Terminal 3: Inspect the Graph
rqt_graph
Visualizes:
[publisher] --[/greeting]--> [subscriber]
Topic Naming Conventions
ROS 2 topics follow a naming pattern:
/namespace/subtopic/specific_name
Examples:
/robot/camera/image (camera images)
/robot/sensors/imu (IMU data)
/robot/navigation/goal (navigation goal)
/system/health/cpu_load (system metrics)
Best Practices:
- Use lowercase with underscores
- Start with
/ - Be specific and descriptive
- Group related topics with namespace
Message Types
Topics carry typed messages. Common types:
from std_msgs.msg import (
String, # Text
Float32, # Single floating point
Int32, # Integer
Bool, # Boolean
Header, # Timestamp + frame info
)
from geometry_msgs.msg import (
Point, # X, Y, Z coordinates
Twist, # Linear and angular velocity
Pose, # Position and orientation
)
from sensor_msgs.msg import (
Image, # Camera images
PointCloud2, # 3D point clouds
LaserScan, # LIDAR data
Imu, # Inertial measurement unit
)
Quality of Service (QoS)
The 10 parameter in create_publisher() and create_subscription() is the queue size (QoS):
# Queue size: 10 messages
self.publisher = self.create_publisher(String, '/topic', 10)
What it means:
- If subscriber is slow, publisher queues up to 10 messages
- Older messages are dropped if queue fills
- Larger queue = more memory, better for slow subscribers
- Smaller queue = faster, better for real-time systems
Key Takeaways
✅ Topics enable one-to-many communication ✅ Publishers and subscribers are decoupled ✅ Multiple subscribers can listen to one topic ✅ Publishers don't wait for subscribers ✅ Topics are typed and named ✅ QoS settings control message buffering
Try It Yourself
Try modifying the pub/sub example:
- Change the message text in publisher
- Create a second subscriber (run it in a third terminal)
- Use
rqt_graphto see multiple subscribers - Change topic name (must match in both nodes)
- Try
ros2 topic echo /greetingin another terminal
Next
Now you understand how nodes communicate via topics. The next pattern is services: synchronous request/response communication.
👉 Next: Section 3: Services - Request and Response
Or review:
Section Status: Complete ✅
Skills Used: ros2-code-generator, code-explainer