Lesson 1.3 – Basic Publisher/Subscriber Implementation
Learning Objectives
By the end of this lesson, you will be able to:
- Write and execute a basic publisher node in Python
- Write and execute a basic subscriber node in Python
- Launch and test a ROS2 communication graph
- Understand the message flow between publisher and subscriber nodes
- Use ROS2 tools to verify communication between nodes
Concept Overview and Scope
This practical lesson teaches you to implement the most fundamental ROS2 communication pattern: publisher-subscriber. You'll write your first ROS2 nodes in Python, creating a publisher that sends messages and a subscriber that receives them. The lesson emphasizes understanding message flow and the asynchronous nature of topic-based communication.
The publisher-subscriber pattern is fundamental to Physical AI systems, enabling distributed, modular robot architectures where sensor nodes, processing nodes, and control nodes can communicate seamlessly.
Understanding Publisher-Subscriber Communication Pattern
The publisher-subscriber pattern is the backbone of ROS2 communication. This pattern enables asynchronous, decoupled communication between nodes:
- Publisher: A node that sends messages to a specific topic
- Subscriber: A node that receives messages from a specific topic
- Topic: A named channel over which messages are sent
- Message: The data structure that carries information between nodes
This creates a decoupled system where publishers don't need to know about subscribers and vice versa. This decoupling is what allows for flexible, modular robot architectures.
Creating the Publisher Node
Step 1: Setting Up the Package Structure
First, navigate to your workspace and create a new package for our publisher/subscriber example:
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python publisher_subscriber_example
cd publisher_subscriber_example
Step 2: Creating the Publisher Node
Create the publisher node file in the package directory:
mkdir -p publisher_subscriber_example
touch publisher_subscriber_example/publisher_node.py
Now edit the publisher node file with the following content:
#!/usr/bin/env python3
# publisher_subscriber_example/publisher_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(String, 'topic', 10)
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = String()
msg.data = 'Hello World: %d' % self.i
self.publisher_.publish(msg)
self.get_logger().info('Publishing: "%s"' % msg.data)
self.i += 1
def main(args=None):
rclpy.init(args=args)
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
# Destroy the node explicitly
minimal_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Step 3: Understanding the Publisher Code
Let's break down the key components of the publisher node:
- Node Initialization:
super().__init__('minimal_publisher')creates a ROS2 node named 'minimal_publisher' - Publisher Creation:
self.create_publisher(String, 'topic', 10)creates a publisher that sends String messages on the 'topic' with a queue size of 10 - Timer Callback: Creates a timer that calls the callback function every 0.5 seconds
- Message Creation: Creates a String message with the current count
- Message Publishing: Publishes the message to the topic
- Logging: Logs the published message to the console
Creating the Subscriber Node
Step 4: Creating the Subscriber Node
Create the subscriber node file:
touch publisher_subscriber_example/subscriber_node.py
Edit the subscriber node file with the following content:
#!/usr/bin/env python3
# publisher_subscriber_example/subscriber_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
String,
'topic',
self.listener_callback,
10)
self.subscription # prevent unused variable warning
def listener_callback(self, msg):
self.get_logger().info('I heard: "%s"' % msg.data)
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
# Destroy the node explicitly
minimal_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Step 5: Understanding the Subscriber Code
Key components of the subscriber node:
- Node Initialization: Creates a ROS2 node named 'minimal_subscriber'
- Subscription Creation:
self.create_subscription(String, 'topic', self.listener_callback, 10)creates a subscription to the 'topic' that receives String messages - Callback Function:
listener_callbackis called whenever a message is received on the topic - Message Processing: The callback function logs the received message to the console
Configuring the Package
Step 6: Setting Up the Entry Points
Edit the setup.py file in the package root to include entry points for our nodes:
from setuptools import find_packages, setup
package_name = 'publisher_subscriber_example'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='your_name',
maintainer_email='your_email@example.com',
description='Basic publisher subscriber example',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'publisher_node = publisher_subscriber_example.publisher_node:main',
'subscriber_node = publisher_subscriber_example.subscriber_node:main',
],
},
)
Building and Running the Nodes
Step 7: Building the Package
Navigate to your workspace and build the package:
cd ~/ros2_ws
colcon build --packages-select publisher_subscriber_example
Step 8: Sourcing the Workspace
Source your workspace to make the new nodes available:
source install/setup.bash
Step 9: Running the Publisher Node
Open a new terminal and run the publisher node:
source ~/ros2_ws/install/setup.bash
ros2 run publisher_subscriber_example publisher_node
You should see output like:
[INFO] [1620000000.123456789] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [1620000000.623456789] [minimal_publisher]: Publishing: "Hello World: 1"
Step 10: Running the Subscriber Node
Open another terminal and run the subscriber node:
source ~/ros2_ws/install/setup.bash
ros2 run publisher_subscriber_example subscriber_node
You should see output like:
[INFO] [1620000000.123456789] [minimal_subscriber]: I heard: "Hello World: 0"
[INFO] [1620000000.623456789] [minimal_subscriber]: I heard: "Hello World: 1"
Understanding Message Flow and Asynchronous Communication
The Asynchronous Nature of Topics
The publisher-subscriber pattern is asynchronous, meaning:
- Publishers don't wait: The publisher continues executing regardless of whether subscribers exist or are ready to receive messages
- Subscribers receive messages: Subscribers process messages as they arrive from publishers
- Decoupled timing: Publishers and subscribers can run at different rates without interfering with each other
Quality of Service (QoS) Considerations
The third parameter in both create_publisher and create_subscription (10 in our example) represents the queue size. This affects how many messages are buffered when there's a mismatch between publishing and subscribing rates.
Advanced Publisher-Subscriber Concepts
Multiple Subscribers
You can run multiple subscriber nodes simultaneously, and all will receive the same messages from the publisher:
# Terminal 1: Publisher
ros2 run publisher_subscriber_example publisher_node
# Terminal 2: Subscriber 1
ros2 run publisher_subscriber_example subscriber_node
# Terminal 3: Subscriber 2
ros2 run publisher_subscriber_example subscriber_node
All subscribers will receive the same messages from the publisher.
Topic Discovery
Use ROS2 command-line tools to examine the communication:
# List active topics
ros2 topic list
# Get information about a specific topic
ros2 topic info /topic
# Echo messages from a topic (without a subscriber node)
ros2 topic echo /topic std_msgs/msg/String
Physical AI Context and Application
In Physical AI systems, the publisher-subscriber pattern is fundamental to creating distributed, modular robot architectures. For example:
- Sensor nodes publish sensor data (camera images, IMU readings, joint states)
- Processing nodes subscribe to sensor data and perform computations
- Control nodes subscribe to processed information and publish motor commands
- Monitoring nodes subscribe to various topics for logging and visualization
This pattern supports the three-layer system:
- Perception Layer: Sensor nodes publish raw and processed data
- Cognition Layer: Processing nodes interpret sensor data and make decisions
- Actuation Layer: Control nodes execute motor commands
Error Handling and Best Practices
Node Lifecycle Management
Always properly manage the node lifecycle by destroying the node explicitly when done:
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
Message Validation
In production code, always validate messages before using them:
def listener_callback(self, msg):
if len(msg.data) > 0: # Validate message content
self.get_logger().info('I heard: "%s"' % msg.data)
else:
self.get_logger().warn('Received empty message')
Verification and Testing
Step 11: Testing Communication Reliability
To verify the communication is working properly:
- Run both nodes simultaneously
- Verify that messages published by the publisher are received by the subscriber
- Check that the message sequence numbers increment correctly
- Test that multiple subscribers can receive the same messages
Step 12: Using ROS2 Tools for Verification
# Check active nodes
ros2 node list
# Check active topics
ros2 topic list
# Check topic information
ros2 topic info /topic
# Monitor message rate
ros2 topic hz /topic
Lesson Summary
In this lesson, you have learned:
- Publisher Implementation: How to create a ROS2 node that publishes messages to a topic
- Subscriber Implementation: How to create a ROS2 node that subscribes to messages from a topic
- Message Flow: How messages travel from publisher to subscriber in an asynchronous manner
- Node Structure: The proper structure and lifecycle management for ROS2 nodes
- Communication Graph: How publishers and subscribers form a communication network
- Verification: How to test and verify communication between nodes
You now have practical experience with the fundamental communication pattern that underlies all ROS2-based robotic systems.
Tools / References
- Python 3.8+
- rclpy
- ROS2