Skip to main content

Chapter 1.5: ROS 2 Architecture

Learning Objectives

By the end of this chapter, students will be able to:

  • Explain the core concepts of ROS 2 architecture
  • Understand the role of nodes, topics, services, and actions
  • Describe the DDS-based communication layer
  • Compare ROS 1 and ROS 2 architectural differences
  • Implement basic ROS 2 communication patterns

Introduction

ROS 2 (Robot Operating System 2) is the foundational framework that connects all parts of a robot system, enabling different software components to communicate and coordinate effectively. Unlike traditional software systems, robot systems must handle real-time constraints, distributed computing, and safety-critical operations, making the architecture of ROS 2 particularly important for physical AI systems.

The architecture of ROS 2 is built on Data Distribution Service (DDS), which provides a middleware for real-time, scalable, and reliable communication. This foundation enables ROS 2 to support the complex requirements of humanoid robotics, including real-time performance, distributed systems, and safety-critical operations.

In this chapter, we'll explore the core architectural concepts of ROS 2, understand how they enable robot software development, and implement basic communication patterns that form the building blocks of complex robot systems.

Core Concepts of ROS 2 Architecture

Nodes

A node is a process that performs computation in a ROS 2 system. Nodes are the fundamental building blocks of ROS 2 applications:

  • Definition: A node is an executable that uses ROS 2 client libraries
  • Responsibility: Each node typically handles a specific task (e.g., sensor processing, control, planning)
  • Communication: Nodes communicate with each other through topics, services, and actions
  • Implementation: Can be written in multiple languages (C++, Python, etc.)
# Basic ROS 2 node structure
import rclpy
from rclpy.node import Node

class MinimalNode(Node):
def __init__(self):
super().__init__('minimal_node')
self.get_logger().info('Hello from minimal_node!')

def main(args=None):
rclpy.init(args=args)
node = MinimalNode()

# Keep the node running
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

Topics and Publish-Subscribe Pattern

Topics enable asynchronous, many-to-many communication between nodes:

  • Mechanism: Publisher nodes send messages to topics, subscriber nodes receive messages from topics
  • Decoupling: Publishers and subscribers don't need to know about each other
  • Asynchronous: Communication happens in real-time without blocking
  • Transport: Uses DDS for reliable message delivery
# Publisher example
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)

def timer_callback(self):
msg = String()
msg.data = 'Hello World: %d' % self.get_clock().now().nanoseconds
self.publisher.publish(msg)
self.get_logger().info('Publishing: "%s"' % msg.data)
# Subscriber example
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)

Services and Request-Reply Pattern

Services enable synchronous, request-reply communication:

  • Mechanism: Service clients send requests to service servers, which return responses
  • Synchronous: The client waits for a response before continuing
  • Use Cases: Operations that require a definitive result (e.g., navigation to goal)
# Service definition (in srv/AddTwoInts.srv file):
# int64 a
# int64 b
# ---
# int64 sum

# Service server example
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts

class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))
return response

Actions

Actions enable goal-oriented, long-running operations with feedback:

  • Components: Goal, feedback, and result messages
  • Features: Preemption, status tracking, continuous feedback
  • Use Cases: Navigation, manipulation, calibration
# Action definition (in action/Fibonacci.action file):
# int32 order
# ---
# int32[] sequence
# ---
# int32[] partial_sequence

# Action server example
import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node
from example_interfaces.action import Fibonacci

class MinimalActionServer(Node):
def __init__(self):
super().__init__('minimal_action_server')
self._action_server = ActionServer(
self,
Fibonacci,
'fibonacci',
self.execute_callback)

def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')

feedback_msg = Fibonacci.Feedback()
feedback_msg.sequence = [0, 1]

for i in range(1, goal_handle.request.order):
if goal_handle.is_cancel_requested:
goal_handle.canceled()
self.get_logger().info('Goal canceled')
return Fibonacci.Result()

feedback_msg.sequence.append(
feedback_msg.sequence[i] + feedback_msg.sequence[i-1])

goal_handle.publish_feedback(feedback_msg)
self.get_logger().info(f'Feedback: {feedback_msg.sequence}')

goal_handle.succeed()
result = Fibonacci.Result()
result.sequence = feedback_msg.sequence
return result

DDS (Data Distribution Service)

ROS 2 uses DDS as its underlying communication middleware:

Key Features of DDS

  • Real-time Performance: Deterministic behavior for time-critical applications
  • Scalability: Supports large, distributed systems
  • Reliability: Quality of Service (QoS) policies for different requirements
  • Language Independence: APIs available in multiple programming languages

Quality of Service (QoS) Policies

DDS provides QoS policies that control communication behavior:

from rclpy.qos import QoSProfile, QoSDurabilityPolicy, QoSHistoryPolicy, QoSReliabilityPolicy

# Example QoS configuration
qos_profile = QoSProfile(
history=QoSHistoryPolicy.KEEP_LAST,
depth=10,
reliability=QoSReliabilityPolicy.RELIABLE,
durability=QoSDurabilityPolicy.VOLATILE
)

ROS 1 vs ROS 2 Architecture Differences

AspectROS 1ROS 2
CommunicationMaster-basedDDS-based
Real-time SupportLimitedFirst-class
Multi-machineComplex setupNative
SecurityLimitedBuilt-in
Lifecycle ManagementManualStructured
Build Systemcatkincolcon (ament)

Hands-On Exercise: ROS 2 Communication Patterns

Objective

Implement and test different ROS 2 communication patterns.

Prerequisites

  • ROS 2 Humble installed
  • Basic understanding of ROS 2 concepts
  • Python or C++ programming skills

Steps

  1. Create a ROS 2 package called 'communication_patterns_tutorial'
  2. Implement a publisher node that sends sensor data
  3. Implement a subscriber node that processes the sensor data
  4. Create a service server that performs calculations on the data
  5. Create a service client that requests calculations
  6. Implement an action server for a long-running task
  7. Test all communication patterns together

Expected Result

Students will have working implementations of all three ROS 2 communication patterns (topics, services, actions) and understand their appropriate use cases.

Assessment Questions

Multiple Choice

Q1: What is the primary communication pattern used for real-time sensor data in ROS 2?

  • a) Services
  • b) Actions
  • c) Topics
  • d) Parameters
Details

Click to reveal answer Answer: c
Explanation: Topics use the publish-subscribe pattern which is ideal for real-time sensor data where multiple nodes may need to receive the same data simultaneously.

Short Answer

Q2: Explain the difference between services and actions in ROS 2, providing an example use case for each.

Practical Exercise

Q3: Create a ROS 2 system with three nodes: (1) a sensor node that publishes random sensor data, (2) a processing node that subscribes to the sensor data and calculates statistics, and (3) a monitoring node that uses a service to request current statistics from the processing node.

Further Reading

  1. "Programming Robots with ROS" - Comprehensive guide to ROS programming
  2. "ROS 2 for Beginners" - Introduction to ROS 2 concepts
  3. "DDS Specification" - Technical specification for Data Distribution Service

Summary

In this chapter, we've explored the core architecture of ROS 2, including nodes, topics, services, and actions. We've understood how the DDS-based communication layer enables robust, real-time communication between different components of a robot system. The architectural concepts covered in this chapter form the foundation for all robot software development in ROS 2.

Understanding ROS 2 architecture is crucial for physical AI systems because it determines how different components of the robot system communicate and coordinate. The publish-subscribe, request-reply, and action patterns provide the necessary tools to build complex, distributed robot systems that can operate effectively in the real world.

In the next chapter, we'll dive deeper into the specific communication patterns, exploring nodes, topics, services, and actions in greater detail with practical examples.