ROS-II Fundamentals: Middleware Architecture
Overview
Robot Operating System 2 (ROS 2) is a flexible framework for writing robot software that provides the services you would expect from an operating system, including hardware abstraction, device drivers, libraries, visualizers, message-passing, package management, and more. Unlike traditional operating systems, ROS 2 is not a single monolithic piece of software but a collection of tools, libraries, and conventions that facilitate the creation of complex and robust robot behavior across a heterogeneous cluster of processors.
Architecture and Design Philosophy
Client Library Architecture
ROS 2 uses a client library architecture that abstracts the underlying middleware implementation. This design allows for different middleware implementations while maintaining a consistent API for users:
┌─────────────────┐ ┌─────────────────┐
│ Application │ │ Application │
├─────────────────┤ ├─────────────────┤
│ rclcpp/rclpy │ │ rclcpp/rclpy │
├─────────────────┤ ├─────────────────┤
│ rmw layer │ │ rmw layer │
├─────────────────┼────┼─────────────────┤
│ DDS Impl. │ │ DDS Impl. │
└─────────────────┘ └─ ────────────────┘
The architecture consists of:
- Client Libraries (rclcpp, rclpy): C++ and Python client libraries that provide the ROS 2 API
- ROS Middleware (rmw): Abstraction layer that interfaces with the underlying middleware
- DDS Implementation: Data Distribution Service that handles the actual message passing
DDS-Based Communication
ROS 2 uses Data Distribution Service (DDS) as its underlying communication middleware, providing:
- Real-time Performance: Deterministic message delivery with bounded latency
- Scalability: Support for large numbers of nodes and topics
- Reliability: Quality of Service (QoS) policies for message delivery guarantees
- Distributed Architecture: Nodes can run on different machines without special configuration
Core Concepts
Nodes
A node is an executable that uses ROS 2 to communicate with other nodes. In ROS 2, nodes are more isolated than in ROS 1:
#include "rclcpp/rclcpp.hpp"
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};
Topics and Messages
Topics are named buses over which nodes exchange messages. ROS 2 uses a more robust topic discovery mechanism:
// Publisher with QoS configuration
auto publisher = this->create_publisher<std_msgs::msg::String>(
"topic_name",
rclcpp::QoS(rclcpp::KeepLast(10)).reliability(RMW_QOS_POLICY_RELIABILITY_RELIABLE)
);
// Subscriber with matching QoS
auto subscription = this->create_subscription<std_msgs::msg::String>(
"topic_name",
rclcpp::QoS(rclcpp::KeepLast(10)).reliability(RMW_QOS_POLICY_RELIABILITY_RELIABLE),
[this](const std_msgs::msg::String::SharedPtr msg) {
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
});
Services and Actions
ROS 2 introduces Actions as a new communication pattern for long-running tasks:
// Action server
class FibonacciActionServer : public rclcpp::Node
{
public:
FibonacciActionServer()
: Node("fibonacci_action_server")
{
using namespace std::placeholders;
this->action_server_ = rclcpp_action::create_server<Fibonacci>(
this->get_node_base_interface(),
this->get_node_clock_interface(),
this->get_node_logging_interface(),
this->get_node_waitables_interface(),
"fibonacci",
std::bind(&FibonacciActionServer::handle_goal, this, _1, _2),
std::bind(&FibonacciActionServer::handle_cancel, this, _1),
std::bind(&FibonacciActionServer::handle_accepted, this, _1));
}
private:
rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;
};
Quality of Service (QoS) Policies
QoS policies allow fine-tuning of communication behavior:
Reliability Policy
- Reliable: All messages are delivered (at the cost of latency)
- Best Effort: Messages may be dropped (lower latency)
Durability Policy
- Transient Local: Publishers send recent messages to new subscribers
- Volatile: No message persistence for late-joining subscribers
History Policy
- Keep Last: Maintain a fixed number of most recent messages
- Keep All: Maintain all messages (use with caution)
Depth Policy
Controls the number of messages in the queue when using Keep Last history policy.
Lifecycle Management
ROS 2 introduces lifecycle nodes for better system management:
class LifecyclePublisher : public rclcpp_lifecycle::LifecycleNode
{
public:
LifecyclePublisher() : rclcpp_lifecycle::LifecycleNode("lifecycle_publisher") {}
private:
rclcpp_lifecycle::LifecyclePublisher<std_msgs::msg::String>::SharedPtr pub_;
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_configure(const rclcpp_lifecycle::State &)
{
pub_ = this->create_publisher<std_msgs::msg::String>("lifecycle_chatter", 10);
RCLCPP_INFO(get_logger(), "Publisher created");
return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
}
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_activate(const rclcpp_lifecycle::State &)
{
pub_->on_activate();
RCLCPP_INFO(get_logger(), "Publisher activated");
return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
}
};
Parameter System
ROS 2 has an improved parameter system with dynamic reconfiguration:
// Declare parameters with default values
this->declare_parameter("param_name", "default_value");
// Get parameter value
std::string param_value;
this->get_parameter("param_name", param_value);
// Set parameters from command line or launch files
auto parameters_client = std::make_shared<rclcpp::AsyncParametersClient>(this);
parameters_client->set_parameters({rclcpp::Parameter("param_name", "new_value")});
Launch System
ROS 2 uses Python-based launch files for complex system management:
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='demo_nodes_cpp',
executable='talker',
name='talker',
parameters=[
{'param_name': 'param_value'}
],
remappings=[
('original_topic', 'remapped_topic')
]
),
Node(
package='demo_nodes_cpp',
executable='listener',
name='listener'
)
])
Security Features
ROS 2 includes built-in security capabilities:
Authentication
- Identity verification of nodes
- Certificate-based authentication
Authorization
- Access control lists for topics and services
- Role-based permissions
Encryption
- Message payload encryption
- Secure communication channels
Migration from ROS 1
Key differences from ROS 1:
- Middleware: DDS-based instead of custom ROS 1 transport
- API Changes: New client libraries with improved design
- Quality of Service: Configurable communication behavior
- Lifecycle Management: Built-in node lifecycle management
- Security: Native security features
- Real-time Support: Better real-time capabilities
- Cross-platform: Improved Windows and macOS support
Best Practices
Node Design
- Keep nodes focused on single responsibilities
- Use composition for complex systems
- Implement proper error handling
- Follow naming conventions
Communication
- Choose appropriate QoS policies
- Use services for request-response patterns
- Use actions for long-running tasks
- Avoid large message sizes
Performance
- Use intra-process communication when possible
- Optimize message serialization
- Consider message frequency and size
- Monitor network utilization
ROS 2 provides the robust middleware foundation needed for complex physical AI systems, enabling reliable communication between diverse hardware and software components in robotic applications.