Skip to content

ROS 2 Introduction

1. What is ROS 2 and Why Does It Exist?

ROS 1 (Robot Operating System 1) was a groundbreaking framework for robot software development, but as the robotics industry matured, several fundamental limitations became apparent:

  • No real-time support: ROS 1's TCP/UDP transport layer was not designed for real-time or safety-critical systems.
  • Single-master architecture: The central roscore was a single point of failure.
  • Poor security: No built-in authentication, encryption, or access control.
  • Limited multi-robot support: Running multiple robots required complex namespace workarounds.
  • Weak cross-platform support: Primarily Linux-only.
  • Python 2 legacy: ROS Noetic finally moved to Python 3, but the transition was slow.

ROS 2 was designed from the ground up to address these issues. It uses the DDS (Data Distribution Service) middleware standard — a proven pub/sub protocol used in aerospace, defense, and financial systems — as its communication backbone.

Key motivations for ROS 2:

Goal How ROS 2 Achieves It
Real-time control DDS supports real-time QoS (Quality of Service) policies
Multi-robot Decentralized discovery; no single master
Security DDS-Security (SROS2) provides authentication and encryption
Embedded systems Micro-ROS targets microcontrollers
Production-ready Designed for commercial deployment, not just research
Cross-platform Linux, macOS, Windows supported equally

2. ROS 1 vs ROS 2 Comparison

Feature ROS 1 (Noetic) ROS 2 (Humble+)
Middleware Custom TCP/UDP (roscomm) DDS (Data Distribution Service)
Discovery Central roscore (single master) Decentralized (DDS discovery)
Real-time Not supported Supported via DDS QoS policies
Security None built-in DDS-Security (SROS2)
Multi-robot Manual namespace tricks Native support
Build system catkin colcon + ament (CMake / Python setuptools)
Launch files XML (.launch) Python (.launch.py)
Language support C++ (roscpp), Python (rospy) C++ (rclcpp), Python (rclpy)
Parameters Global parameter server Per-node parameters
Action servers actionlib Built-in rclpy/rclcpp actions
Lifecycle None Managed node lifecycle (LifecycleNode)
Message IDL .msg files Same .msg + .srv + .action files
Platform Linux (primarily) Linux, macOS, Windows
DDS vendor N/A Fast DDS, Cyclone DDS, Connext, etc.

3. ROS 2 Architecture

ROS 2 uses a layered architecture that abstracts the middleware via an RMW (ROS Middleware Interface).

+-------------------------------------------------------+
|                  User Application                     |
|          (your ROS 2 nodes, launch files)             |
+-------------------------------------------------------+
|                 RCL  (ROS Client Library)              |
|         rclcpp (C++)  /  rclpy (Python)               |
+-------------------------------------------------------+
|               RMW  (ROS Middleware Interface)          |
|        rmw_fastrtps / rmw_cyclonedds / rmw_connext    |
+-------------------------------------------------------+
|                 DDS Implementation                     |
|         eProsima Fast DDS / Eclipse Cyclone DDS        |
+-------------------------------------------------------+
|              OS Network Stack (UDP/IP)                 |
+-------------------------------------------------------+

Layer breakdown:

  • Application layer: Your nodes, topics, services, actions.
  • RCL (ROS Client Library): Language-specific APIs. rclcpp for C++, rclpy for Python. Provides the familiar ROS API (Node, Publisher, Subscriber, etc.).
  • RMW (ROS Middleware Interface): An abstraction layer that allows swapping DDS vendors without changing application code. You choose rmw_fastrtps (default), rmw_cyclonedds, or others.
  • DDS layer: Handles actual pub/sub transport, discovery, serialization, QoS, and (optionally) security.

DDS Quality of Service (QoS)

QoS policies let you tune communication behavior:

+---------------------+------------------------------------------+
| Policy              | Options                                  |
+---------------------+------------------------------------------+
| Reliability         | RELIABLE  /  BEST_EFFORT                 |
| Durability          | TRANSIENT_LOCAL  /  VOLATILE             |
| History             | KEEP_LAST(n)  /  KEEP_ALL                |
| Deadline            | (period)                                 |
| Lifespan            | (duration)                               |
| Liveliness          | AUTOMATIC  /  MANUAL_BY_TOPIC            |
+---------------------+------------------------------------------+

4. Core Concepts

4.1 Node (节点)

A node is the fundamental unit of computation. Each node performs a single, well-defined function (e.g., camera driver, path planner).

import rclpy
from rclpy.node import Node

class MyNode(Node):
    def __init__(self):
        super().__init__('my_node')
        self.get_logger().info('Hello from ROS 2!')

4.2 Topic (话题)

Topics are named buses for asynchronous publish/subscribe communication. Multiple publishers and subscribers can connect to the same topic.

  +----------+         /camera/image         +------------+
  | Publisher | -------> Topic -------> | Subscriber |
  | (camera)  |                          | (detector) |
  +----------+                          +------------+
                                               |
                                          +------------+
                                          | Subscriber |
                                          | (display)  |
                                          +------------+

4.3 Service (服务)

Services are synchronous request/response communication (like RPC). A client sends a request and blocks until a response arrives.

  +----------+    request    +----------+    response
  | Client   | ------------> |  Server  | ----------->
  +----------+               +----------+

4.4 Action (动作)

Actions combine a goal, feedback, and result. They are suitable for long-running tasks (e.g., "navigate to waypoint") with periodic progress updates.

  +----------+   goal    +----------+   feedback (periodic)
  |  Client  | --------> |  Server  | ------------------->
  +----------+           +----------+
       ^                      |
       |     result           |
       +----------------------+

4.5 Parameter (参数)

Parameters are node-level configuration values (integers, strings, booleans, etc.) that can be queried or set at runtime.

4.6 Launch (启动)

Launch files start multiple nodes with configurable arguments, remappings, and conditions. ROS 2 uses Python-based launch files (.launch.py).

5. ROS 2 CLI Commands vs ROS 1

Task ROS 1 ROS 2
Run a node rosrun pkg node ros2 run pkg node
List nodes rosnode list ros2 node list
List topics rostopic list ros2 topic list
Echo a topic rostopic echo /topic ros2 topic echo /topic
Publish to topic rostopic pub /topic ... ros2 topic pub /topic ...
Inspect message type rosmsg show MsgType ros2 interface show MsgType
Call a service rosservice call /srv ... ros2 service call /srv ...
List parameters rosparam list ros2 param list
Get parameter rosparam get /p ros2 param get /node p
List packages rospack list ros2 pkg list
Create package catkin_create_pkg ros2 pkg create --build-type ament_python
Build workspace catkin_make colcon build
Launch file roslaunch pkg file.launch ros2 launch pkg file.launch.py
Record bag rosbag record -a ros2 bag record -a
Play bag rosbag play file.bag ros2 bag play file/

6. ROS 2 Distributions

Distribution Ubuntu EOL (End of Life) Notes
Humble Hawksbill 22.04 May 2027 LTS; most widely used as of 2025
Iron Irwini 22.04 Nov 2024 Non-LTS; already EOL
Jazzy Jalisco 24.04 May 2029 LTS; latest stable
Rolling Latest N/A Continuous delivery; always up-to-date

Recommendation: Use Humble (if on Ubuntu 22.04) or Jazzy (if on Ubuntu 24.04) for production. Use Rolling if you want the latest features and can tolerate occasional breakage.

7. Writing a Simple Publisher/Subscriber in ROS 2 (Python)

7.1 Creating a Package

# Source ROS 2
source /opt/ros/humble/setup.bash

# Create a workspace
mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src

# Create a Python package
ros2 pkg create --build-type ament_python my_pubsub --dependencies rclpy std_msgs

7.2 Publisher (my_publisher.py)

Create file my_pubsub/my_pubsub/my_publisher.py:

import rclpy
from rclpy.node import Node
from std_msgs.msg import String


class MinimalPublisher(Node):
    """A minimal ROS 2 publisher that sends 'Hello World' messages."""

    def __init__(self):
        super().__init__('minimal_publisher')
        # Create a publisher on topic 'chatter' with queue size 10
        self.publisher_ = self.create_publisher(String, 'chatter', 10)
        # Create a timer that calls publish_callback every 0.5 seconds
        self.timer = self.create_timer(0.5, self.publish_callback)
        self.count = 0

    def publish_callback(self):
        msg = String()
        msg.data = f'Hello World: {self.count}'
        self.publisher_.publish(msg)
        self.get_logger().info(f'Publishing: "{msg.data}"')
        self.count += 1


def main(args=None):
    rclpy.init(args=args)
    node = MinimalPublisher()
    try:
        rclpy.spin(node)  # Keep the node alive
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()

7.3 Subscriber (my_subscriber.py)

Create file my_pubsub/my_pubsub/my_subscriber.py:

import rclpy
from rclpy.node import Node
from std_msgs.msg import String


class MinimalSubscriber(Node):
    """A minimal ROS 2 subscriber that listens to 'chatter' topic."""

    def __init__(self):
        super().__init__('minimal_subscriber')
        # Subscribe to 'chatter' topic, call listener_callback on each message
        self.subscription = self.create_subscription(
            String,
            'chatter',
            self.listener_callback,
            10  # QoS queue depth
        )

    def listener_callback(self, msg: String):
        self.get_logger().info(f'I heard: "{msg.data}"')


def main(args=None):
    rclpy.init(args=args)
    node = MinimalSubscriber()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()

7.4 Register Entry Points

Edit setup.py to add console script entry points:

entry_points={
    'console_scripts': [
        'talker = my_pubsub.my_publisher:main',
        'listener = my_pubsub.my_subscriber:main',
    ],
},

7.5 Build and Run

cd ~/ros2_ws
colcon build --packages-select my_pubsub
source install/setup.bash

# Terminal 1: run publisher
ros2 run my_pubsub talker

# Terminal 2: run subscriber
ros2 run my_pubsub listener

Expected output:

# Terminal 1 (talker)
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
...

# Terminal 2 (listener)
[INFO] [minimal_subscriber]: I heard: "Hello World: 0"
[INFO] [minimal_subscriber]: I heard: "Hello World: 1"
...

8. Launch Files in ROS 2 (Python-based)

ROS 2 launch files are written in Python. Create my_pubsub/launch/demo.launch.py:

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
    return LaunchDescription([
        Node(
            package='my_pubsub',
            executable='talker',
            name='my_talker',
            output='screen',
            parameters=[],       # list of param YAML files or dicts
            remappings=[],       # topic remappings
        ),
        Node(
            package='my_pubsub',
            executable='listener',
            name='my_listener',
            output='screen',
        ),
    ])

Run the launch file:

ros2 launch my_pubsub demo.launch.py

Advanced Launch Features

ROS 2 launch supports:

  • Arguments: DeclareLaunchArgument('robot_name', default_value='robot1')
  • Conditions: IfCondition(LaunchConfiguration('use_camera'))
  • Substitutions: ${} style variable expansion
  • Including other launch files: IncludeLaunchDescription(...)
  • Group actions: GroupAction([...]) with namespace/remapping

9. Migration Tips from ROS 1

If you are migrating existing ROS 1 code to ROS 2, here are practical guidelines:

  1. Message types are mostly the same: .msg, .srv, .action files have the same syntax. Package names may differ (e.g., geometry_msgs works in both).

  2. Replace rospy with rclpy:

  3. rospy.init_node()rclpy.init() + Node('name')
  4. rospy.Publisher()node.create_publisher()
  5. rospy.Subscriber()node.create_subscription()
  6. rospy.spin()rclpy.spin(node)

  7. Replace rosrun with ros2 run and roslaunch with ros2 launch.

  8. Replace catkin with colcon:

    # Old
    catkin_make
    # New
    colcon build
    

  9. Port launch files: Convert XML .launch to Python .launch.py. Tools like ros2 launch support both, but Python is the standard.

  10. Parameters: ROS 2 parameters are per-node (no global parameter server). Use ros2 param CLI or declare parameters in code with self.declare_parameter('name', default_value).

  11. Logging: Replace rospy.loginfo() with self.get_logger().info().

  12. Time: Replace rospy.Time.now() with node.get_clock().now().

  13. Use ros1_bridge: For mixed ROS 1 / ROS 2 systems during transition, the ros1_bridge package bridges topics and services.

  14. Incremental migration: You don't have to port everything at once. Use ros1_bridge to connect old and new nodes.

10. References