Nodes and Topics: The Foundation of ROS 2 Communication
Introduction
Imagine a humanoid robot walking while simultaneously processing camera data, maintaining balance, and planning its next step. Each of these tasks requires different algorithms, runs at different speeds, and may even execute on different processors. How do you coordinate all this complexity?
The answer is ROS 2 nodes and topics. This architectural pattern breaks your robot's software into independent, single-purpose processes that communicate through standardized message channels. Let's explore how this works.
What is a ROS 2 Node?
A node is an independent process that performs a specific computational task. Think of nodes as specialized workers in a factory—each has one job and does it well.
Key Characteristics of Nodes
-
Single Responsibility: Each node focuses on one task
- A camera node publishes image data
- A balance controller node processes IMU readings
- A motor driver node sends commands to actuators
-
Process Isolation: Nodes run as separate operating system processes
- If one node crashes, others continue running
- Easier to debug and test individual components
- Can be written in different programming languages (Python, C++)
-
Independent Lifecycle: Nodes can start, stop, and restart independently
- Replace a faulty perception node without restarting motor control
- Hot-swap algorithms during development
Humanoid Example: Multi-Node Architecture
A walking humanoid might use these nodes simultaneously:
vision_node → Processes camera images
imu_node → Reads balance sensors
foot_sensor_node → Monitors ground contact
balance_controller → Computes stability corrections
gait_planner → Generates walking patterns
motor_controller → Sends joint commands to hardware
state_estimator → Fuses sensor data for robot pose
Each node operates independently, but they must communicate. That's where topics come in.
What is a Topic?
A topic is a named communication channel that nodes use to exchange messages. Topics implement the publish-subscribe pattern:
- Publishers send (publish) messages to a topic
- Subscribers receive (subscribe to) messages from a topic
- Publishers and subscribers don't know about each other—they only know the topic name
Publish-Subscribe Pattern
This decoupling is powerful:
Publisher Node → /joint_states topic → Subscriber Node(s)
- One-to-Many: One publisher, multiple subscribers (broadcast sensor data)
- Many-to-One: Multiple publishers, one subscriber (aggregating data)
- Many-to-Many: Multiple publishers and subscribers (flexible architectures)
Topic Naming Convention
Topics use hierarchical names starting with /:
/camera/image_raw (camera images)
/imu/data (IMU measurements)
/joint_states (current joint positions/velocities)
/cmd_vel (velocity commands for mobile base)
/humanoid/head/pan_tilt (head motor commands)
Good names are descriptive and follow namespace conventions to avoid conflicts.
Messages: The Data on Topics
Topics carry messages—structured data packets. ROS 2 provides standard message types, and you can define custom ones.
Common Standard Message Types
# String messages
std_msgs/String → Simple text
# Numeric messages
std_msgs/Float64 → Single floating-point number
geometry_msgs/Twist → Linear and angular velocity (for mobile robots)
# Sensor messages
sensor_msgs/Image → Camera images
sensor_msgs/Imu → Accelerometer and gyroscope data
sensor_msgs/JointState → Joint positions, velocities, efforts
# Geometry messages
geometry_msgs/Pose → Position and orientation
geometry_msgs/Transform → Coordinate frame transformations
Message Structure Example
A sensor_msgs/JointState message might contain:
name: ['left_shoulder', 'left_elbow', 'right_shoulder', 'right_elbow']
position: [0.5, 1.2, 0.3, 1.1] # radians
velocity: [0.1, 0.0, 0.2, 0.0] # rad/s
effort: [2.5, 1.8, 3.0, 2.0] # torque in Nm
Asynchronous Communication
Topics are asynchronous—publishers send messages without waiting for subscribers to receive them. This is critical for robotics:
- High-frequency sensors (cameras at 30 Hz, IMUs at 100 Hz) publish continuously
- Subscribers process when ready, not on the publisher's schedule
- No blocking: A slow subscriber doesn't stall the publisher
Quality of Service (QoS)
ROS 2 introduces QoS policies to fine-tune communication reliability:
- Reliable: Guarantee delivery (important for commands)
- Best Effort: Send and forget (acceptable for high-frequency sensor streams)
- History Depth: How many messages to buffer
- Durability: Should late-joining subscribers receive old messages?
For humanoid control:
- Balance control commands: Reliable, small history depth
- Camera images: Best effort (if you miss one, next frame arrives soon)
- Joint state feedback: Reliable, keep latest value only
Code Example 1: Minimal Publisher
Let's write a simple node that publishes joint state data for a humanoid's left arm.
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState
class ArmJointPublisher(Node):
def __init__(self):
super().__init__('arm_joint_publisher')
# Create a publisher for the /arm/joint_states topic
self.publisher_ = self.create_publisher(
JointState, # Message type
'/arm/joint_states', # Topic name
10 # QoS history depth
)
# Publish at 10 Hz
self.timer = self.create_timer(0.1, self.publish_joint_state)
self.get_logger().info('Arm joint publisher started')
def publish_joint_state(self):
msg = JointState()
msg.header.stamp = self.get_clock().now().to_msg()
msg.name = ['left_shoulder', 'left_elbow']
msg.position = [0.5, 1.2] # Example positions in radians
msg.velocity = [0.0, 0.0]
msg.effort = [0.0, 0.0]
self.publisher_.publish(msg)
self.get_logger().info(f'Publishing: {msg.position}')
def main(args=None):
rclpy.init(args=args)
node = ArmJointPublisher()
rclpy.spin(node) # Keep node running
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Key Points
- Inherits from
Node: Every ROS 2 node extends theNodebase class create_publisher(): Declares this node will publish to/arm/joint_statescreate_timer(): Callspublish_joint_state()every 0.1 seconds (10 Hz)publish(): Sends the message to all subscribersrclpy.spin(): Keeps the node alive and processing callbacks
Code Example 2: Minimal Subscriber
Now let's subscribe to those joint states and monitor them:
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState
class ArmJointSubscriber(Node):
def __init__(self):
super().__init__('arm_joint_subscriber')
# Create a subscriber for the /arm/joint_states topic
self.subscription = self.create_subscription(
JointState, # Message type
'/arm/joint_states', # Topic name
self.joint_state_callback, # Callback function
10 # QoS history depth
)
self.get_logger().info('Arm joint subscriber started')
def joint_state_callback(self, msg):
# This function is called every time a message arrives
self.get_logger().info(f'Received joint states:')
for name, pos in zip(msg.name, msg.position):
self.get_logger().info(f' {name}: {pos:.2f} rad')
def main(args=None):
rclpy.init(args=args)
node = ArmJointSubscriber()
rclpy.spin(node) # Wait for messages
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Key Points
create_subscription(): Declares this node will listen to/arm/joint_states- Callback function:
joint_state_callback()runs whenever a message arrives - Automatic decoupling: Publisher and subscriber don't know about each other
- Multiple subscribers: Many nodes can subscribe to the same topic simultaneously
Running Publisher and Subscriber
To see this in action (assuming ROS 2 is installed):
# Terminal 1: Run the publisher
python3 arm_joint_publisher.py
# Terminal 2: Run the subscriber
python3 arm_joint_subscriber.py
# Terminal 3: Inspect active topics
ros2 topic list
ros2 topic echo /arm/joint_states
You'll see the subscriber receiving messages published by the publisher, even though they're separate processes that never directly communicate!
When to Use Topics
Topics are ideal for:
✅ Continuous data streams
- Sensor readings (cameras, IMUs, force sensors)
- Robot state (joint positions, velocities, pose)
- Perception outputs (detected objects, segmentation masks)
✅ One-to-many or many-to-many communication
- Broadcasting camera images to multiple vision algorithms
- Multiple nodes publishing to a shared map
✅ Asynchronous, fire-and-forget messaging
- Publisher doesn't need to know if anyone is listening
- Subscriber processes messages when ready
❌ Not ideal for:
- Request-response interactions (use services instead)
- Long-running tasks with feedback (use actions instead)
- Guaranteed synchronous execution
Architectural Pattern: Sensor → Processor → Actuator
A typical humanoid control loop using topics:
[Camera Node] → /camera/image topic → [Vision Node]
[IMU Node] → /imu/data topic → [Balance Node]
[Foot Sensors] → /foot/force topic → [Balance Node]
[Balance Node] → /cmd/joint_positions → [Motor Controller]
The Balance Node subscribes to sensor topics, computes corrections, and publishes motor commands. Each node can be developed, tested, and replaced independently.
Diagram: Node-Topic Architecture
Legend:
- Blue nodes: Sensor interfaces
- Yellow nodes: Processing/algorithms
- Red nodes: Actuator controllers
- Arrows: Topics (asynchronous message flow)
Best Practices
1. Descriptive Topic Names
# Good
'/left_arm/joint_states'
'/vision/detected_objects'
# Avoid
'/data'
'/output'
2. Choose Appropriate Publishing Rates
- Cameras: 30 Hz
- IMUs: 100-200 Hz
- Joint states: 50-100 Hz
- Path plans: 1-10 Hz
Don't publish faster than necessary—it wastes bandwidth and CPU.
3. Use Standard Message Types When Possible
ROS 2 provides messages for common robotics data. Use them for interoperability:
sensor_msgs/JointStatefor joint datageometry_msgs/Twistfor velocity commandssensor_msgs/Imagefor camera data
4. Namespace Your Topics
'/robot_1/left_arm/joint_states'
'/robot_1/right_arm/joint_states'
Namespaces prevent conflicts when running multiple robots or sensor groups.
Summary
Nodes and topics form the foundation of ROS 2's modular architecture:
- Nodes: Independent processes with single responsibilities
- Topics: Named channels for asynchronous, publish-subscribe communication
- Messages: Typed data structures sent over topics
- Decoupling: Publishers and subscribers don't know about each other
- Flexibility: Many-to-many communication, hot-swappable components
For humanoid robotics, this means:
- Sensor nodes publish continuous data streams
- Processing nodes subscribe, compute, and publish results
- Actuator nodes subscribe to commands and control motors
- All components work together without tight coupling
Next, we'll explore services and actions for request-response patterns and long-running tasks.
Continue to: Services and Actions
References
Open Robotics. (2024). ROS 2 Documentation: Humble Hawksbill - Understanding Nodes. https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html
Open Robotics. (2024). ROS 2 Documentation: Humble Hawksbill - Understanding Topics. https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Topics/Understanding-ROS2-Topics.html
Open Robotics. (2024). ROS 2 Documentation: About Quality of Service Settings. https://docs.ros.org/en/humble/Concepts/Intermediate/About-Quality-of-Service-Settings.html