What ROS 2 is

ROS 2 (the Robot Operating System, version 2) is an open-source framework for writing robot software: a communication layer, client libraries for Python and C++, build tools, command-line tools and a large ecosystem of ready-made packages for drivers, navigation, manipulation and simulation. Despite the name it is not an operating system; it runs on top of one, normally Ubuntu Linux.

The idea is division of labor. A robot needs a camera driver, a motor driver, a localization module, a planner and a user interface. In ROS 2 each is a separate program that publishes and receives messages in agreed formats, so a lidar driver from one vendor works with a navigation stack from another, and a team can replace one part without touching the rest. Universities, start-ups and industrial vendors share packages this way, which is the main reason to use ROS at all.

ROS is developed in the open under Open Robotics. Since 18 March 2024 the Open Source Robotics Alliance (OSRA), set up by Open Robotics, governs ROS, the Gazebo simulator and related projects.

The ROS 2 graph

All running ROS 2 programs and the connections between them form the ROS graph. A ROS 2 node is one participant in that graph: a process (or part of one) that does one job and communicates through topics, services, actions and parameters. The ROS 2 documentation puts the design rule plainly: "each node should do one logical thing". A camera driver node, a detector node and a motor controller node are three nodes, even if they run on the same computer.

A ROS 2 graph with five nodes. camera_driver publishes the topic /image_raw to detector; detector publishes /detections to planner; planner publishes /cmd_vel to base_controller. operator_ui calls the service /set_mode on detector and sends an action goal to planner. camera_driver detector planner operator_ui base_controller /image_raw /detections /cmd_vel /set_mode goal topic service (request and reply) action
Nodes (boxes) connected by topics (one-way streams), a service (request and reply) and an action (a goal with feedback). Each node knows only the names and message types, not who is on the other side.

Topics

A ROS 2 topic is a named channel on which nodes publish messages of one fixed type, and to which any number of nodes can subscribe. The documentation calls it "a strongly-typed, anonymous publish/subscribe system": strongly typed because every message on /cmd_vel is a geometry_msgs/msg/Twist; anonymous because a subscriber does not know or care which node published it. It is the publish-subscribe pattern, used for continuous streams: camera images, lidar scans, wheel speeds, velocity commands.

Here is a minimal node in Python (rclpy, the Python client library) that tells a mobile robot to drive slowly forward. It follows the form of the current tutorials for ROS 2 Lyrical and Rolling. ROS 2 is not installed on the 64-bit Linux machine (ARM64) that checks this site's examples, so it is not run here:

import rclpy
from rclpy.executors import ExternalShutdownException
from rclpy.node import Node

from geometry_msgs.msg import Twist


class SlowForward(Node):
    """Drive forward slowly: publish a velocity command twice a second."""

    def __init__(self):
        super().__init__('slow_forward')
        self.publisher_ = self.create_publisher(Twist, 'cmd_vel', 10)
        self.timer = self.create_timer(0.5, self.timer_callback)

    def timer_callback(self):
        msg = Twist()
        msg.linear.x = 0.10      # meters per second, forward
        msg.angular.z = 0.0      # radians per second, no turning
        self.publisher_.publish(msg)


def main(args=None):
    try:
        with rclpy.init(args=args):
            node = SlowForward()
            rclpy.spin(node)
    except (KeyboardInterrupt, ExternalShutdownException):
        pass


if __name__ == '__main__':
    main()

Line by line:

  • The node is a class that inherits from Node (see inheritance) and gives itself the name slow_forward.
  • create_publisher(Twist, 'cmd_vel', 10) announces a topic, its message type, and a queue depth of 10 — "a required Quality of Service (QoS) setting", explained below.
  • A timer calls timer_callback every 0.5 seconds; ROS uses meters, radians and seconds throughout.
  • rclpy.spin keeps the node alive and runs its callbacks until Ctrl+C. The with rclpy.init(...) form shuts ROS down cleanly; tutorials for older distributions call rclpy.init(), destroy_node() and rclpy.shutdown() by hand instead.

Services

A ROS 2 service is a named request-and-reply call between nodes: one node offers it, another sends a request and waits for exactly one response. Services suit short questions and commands — "switch to calibration mode", "what is the current map?" — and the documentation says they are "expected to return quickly". Anything that takes seconds or longer belongs in an action. With the turtlesim demo simulator running, this command calls one of its services from the terminal:

ros2 service call /turtle1/teleport_absolute turtlesim/srv/TeleportAbsolute "{x: 2.0, y: 2.0, theta: 0.0}"

Actions

A ROS 2 action is a long-running task that a client sends to a server as a goal; the server reports progress as feedback, sends a result at the end, and can be told to cancel. The documentation describes it as "a long-running remote procedure call with feedback and the ability to cancel or preempt the goal". Driving to a position, moving an arm along a planned path, or docking at a charger are actions: they take time, the operator wants to see progress, and they must be stoppable. Under the hood an action is built from three services (goal, cancel, result) and two topics (feedback, status).

ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}" --feedback

Parameters

Parameters are named settings of a node — a controller gain, a maximum speed, a camera's frame rate. In the documentation's words they "configure nodes at startup (and during runtime), without changing the code". They are usually kept in a YAML file per robot:

line_follower:
  ros__parameters:
    kp: 4.0
    ki: 0.5
    kd: 0.2
    max_speed: 0.20   # meters per second
ros2 run my_robot line_follower --ros-args --params-file line_follower.yaml

Keeping tuning values out of the code means the same program runs on two robots with different motors, and a change of gain is a reviewed configuration change rather than a new build. Where those gains come from is on Motion Control and PID.

Launch files

A real robot starts dozens of nodes with their parameters, names and connections. A launch file is a file that describes which nodes to start, with which parameters, names and remappings, so that one command brings up a whole system. Launch files "can be written in XML, YAML, or Python" and are run with ros2 launch. A YAML launch file that starts the turtlesim simulator and a line follower with one parameter:

launch:
  - node:
      pkg: "turtlesim"
      exec: "turtlesim_node"
      name: "sim"
  - node:
      pkg: "my_robot"
      exec: "line_follower"
      name: "line_follower"
      param:
        - name: "kp"
          value: 4.0
ros2 launch my_robot bringup.launch.yaml

XML and YAML launch files are short and declarative; Python launch files can contain logic (start this node only on the real robot, not in simulation) at the cost of being harder to read. Many projects use XML or YAML for simple bring-up and Python only where they need the logic.

Building and running

ROS 2 code lives in packages inside a workspace: a folder with a src subfolder. The build tool colcon builds every package in it and writes a setup script that makes them available in the terminal:

ros2_ws/
├── src/
│   └── my_robot/          ← your package (Python or C++)
├── build/                 ← created by colcon
├── install/               ← created by colcon; contains setup.bash
└── log/
cd ~/ros2_ws
colcon build --symlink-install
source install/setup.bash

--symlink-install links Python files instead of copying them, so edits take effect without rebuilding. The ros2 command-line tool then inspects the running graph. With the turtlesim simulator started by ros2 run turtlesim turtlesim_node, a second terminal shows:

ros2 node list
ros2 topic list
/turtlesim
/parameter_events
/rosout
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose
ros2 topic echo /turtle1/pose
x: 5.544444561004639
y: 5.544444561004639
theta: 0.0
linear_velocity: 0.0
angular_velocity: 0.0
---

tf2: keeping track of frames

A robot has many coordinate frames: the map, the robot base, each wheel, the lidar, the camera, the gripper. tf2 is ROS 2's transform library, "which lets the user keep track of multiple coordinate frames over time". Nodes publish the transform between pairs of frames (a parent and a child); tf2 keeps them in a tree, buffered in time, and any node can ask for the transform between any two frames at a given moment. The camera example on Robot Vision — a detected part, converted from the camera frame to the robot base — is exactly the question tf2 answers.

For mobile robots, the convention REP 105 names the main frames: map (a fixed world frame), odom (smooth but drifting, from wheel odometry) and base_link (attached to the robot). Localization publishes map → odom, odometry publishes odom → base_link, and tf2 chains them. The arithmetic is a composition of poses, here in 2D:

import math


def compose(a, b):
    """Pose b, given in frame a, expressed in a's parent frame (2D: x, y, theta)."""
    ax, ay, at = a
    bx, by, bt = b
    x = ax + math.cos(at) * bx - math.sin(at) * by
    y = ay + math.sin(at) * bx + math.cos(at) * by
    return x, y, at + bt


def show(name, pose):
    x, y, t = pose
    print(f"{name:20} x = {x:6.3f}  y = {y:6.3f}  heading = {math.degrees(t):5.1f} deg")


map_to_odom = (0.20, -0.10, math.radians(5))      # from localization
odom_to_base = (3.00, 1.50, math.radians(90))     # from wheel odometry

show("odom -> base_link", odom_to_base)
show("map -> base_link", compose(map_to_odom, odom_to_base))
odom -> base_link    x =  3.000  y =  1.500  heading =  90.0 deg
map -> base_link     x =  3.058  y =  1.656  heading =  95.0 deg

The 5-degree part of the correction alone shifts the robot by about 29 cm, because it rotates the whole 3.35 m odometry path around the odom origin; together with the 22 cm offset, the robot ends up 17 cm from where odometry alone would put it. The matrix form of the same operation is on Coordinate Frames and Transforms; why odom drifts is on Mobile Robots and Odometry.

DDS and quality of service

ROS 2 does not move messages itself. Underneath sits a middleware, plugged in through a layer called the RMW (ROS middleware interface). The default is an implementation of the Data Distribution Service (DDS): an industrial publish-subscribe standard from the Object Management Group in which participants discover each other on the network and exchange typed data directly, with configurable delivery guarantees. As of October 2026 the default RMW is eProsima's Fast DDS; Cyclone DDS and RTI Connext are alternatives, and Eclipse Zenoh (rmw_zenoh_cpp), which uses a small router instead of DDS discovery, has been a Tier 1 option since the Kilted release.

Discovery is automatic: by default every ROS 2 node on the same network segment and with the same domain number (ROS_DOMAIN_ID) sees every other. That is convenient on a lab bench and a risk on a shared network — two teams can steer each other's robots. Give each robot or test setup its own domain ID, and put robots on their own network segment (see segmentation in the OT topic). ROS 2 can also use the DDS security plugins for authentication, encryption and access control; they are off by default.

Quality of service

Each publisher and subscriber has a QoS (quality of service) profile. According to the documentation, ROS 2 can be "as reliable as TCP or as best-effort as UDP" — here TCP means the network protocol (see TCP versus UDP), not a robot's tool center point. The main settings:

Setting Choices Typical use
Reliability reliable · best effort commands and maps reliable; camera and lidar streams best effort (a late image is useless anyway)
History and depth keep last N · keep all keep last 10 is the common default
Durability volatile · transient local transient local lets a late subscriber still get the last message, for example a static map

With "keep last 10", a slow subscriber does not get an ever-growing backlog; the oldest messages are dropped. Python's collections.deque with a maximum length behaves the same way:

from collections import deque

depth = 10                       # QoS history: keep last 10
queue = deque(maxlen=depth)

# A camera node publishes 25 images while the subscriber is busy.
for number in range(1, 26):
    queue.append(f"image #{number}")

print(f"waiting in the queue: {len(queue)}")
print(f"oldest: {queue[0]}, newest: {queue[-1]}")
print(f"dropped: {25 - len(queue)}")
waiting in the queue: 10
oldest: image #16, newest: image #25
dropped: 15

Reliability is the other trade-off. A simplified model of a lossy Wi-Fi link: best effort sends each message once, reliable resends until it gets through. Reliable delivers everything, but resends cost bandwidth and add delay — on a busy link, retrying old camera frames can make every new frame late.

import random

random.seed(7)
LOSS = 0.05          # 5% of packets get lost on a busy Wi-Fi link
MESSAGES = 200

best_effort = 0
reliable_sends = 0
for _ in range(MESSAGES):
    if random.random() > LOSS:       # best effort: one try only
        best_effort += 1
    while True:                       # reliable: resend until it arrives
        reliable_sends += 1
        if random.random() > LOSS:
            break

print(f"best effort: {best_effort} of {MESSAGES} arrived")
print(f"reliable:    {MESSAGES} of {MESSAGES} arrived, "
      f"{reliable_sends - MESSAGES} resends")
best effort: 190 of 200 arrived
reliable:    200 of 200 arrived, 9 resends

Distributions and support dates

ROS 2 is released as named distributions, a matched set of packages. According to the documentation, "there is a new ROS 2 distribution released yearly on May 23rd (World Turtle Day)". Since Humble (2022), the releases of even years (2022, 2024, 2026) have been long-term support (LTS) releases with five years of support; the others are supported for about a year and a half. Rolling Ridley is the continuously updated development line, for people who build ROS itself — it may break at any time.

Distribution Released End of support Type
Humble Hawksbill 23 May 2022 May 2027 LTS
Jazzy Jalisco 23 May 2024 May 2029 LTS
Kilted Kaiju 23 May 2025 end of 2026 non-LTS
Lyrical Luth 22 May 2026 May 2031 LTS (newest)
Rolling Ridley continuous — development

Distributions and dates as of October 2026.

For a project that must run for years, the support window matters more than the newest features:

# ROS 2 distributions and end of support, as of October 2026
distros = [
    ("Humble Hawksbill", "LTS", 2027, 5),
    ("Jazzy Jalisco", "LTS", 2029, 5),
    ("Kilted Kaiju", "non-LTS", 2026, 12),   # end of 2026
    ("Lyrical Luth", "LTS", 2031, 5),
]
today = (2026, 10)


def months_between(start, end):
    return (end[0] - start[0]) * 12 + (end[1] - start[1])


for name, kind, year, month in sorted(distros, key=lambda d: (d[2], d[3])):
    left = months_between(today, (year, month))
    print(f"{name:17} {kind:8} ends {year}-{month:02d}  {left:3d} months left")
Kilted Kaiju      non-LTS  ends 2026-12    2 months left
Humble Hawksbill  LTS      ends 2027-05    7 months left
Jazzy Jalisco     LTS      ends 2029-05   31 months left
Lyrical Luth      LTS      ends 2031-05   55 months left

In practice a team also checks that the drivers it needs exist for the chosen distribution: a vendor driver that only supports Jazzy can be a good reason to start on Jazzy rather than Lyrical.

Supported platforms

Each distribution names its platforms in tiers. Tier 1 platforms are fully built and tested: for Humble, Jazzy, Kilted and Lyrical that is Ubuntu (on both amd64 and arm64, so also on many ARM boards) and Windows. Lyrical's Tier 1 platforms are Ubuntu 26.04 and Windows 11. Debian appears only as Tier 3 (community-tested, without guarantees), and so do macOS and 32-bit ARM. Lyrical needs Python 3.12 to 3.14, C++20 and C17.

MoveIt, Nav2 and micro-ROS

Three large projects built on ROS 2 cover much of what a new robot needs:

  • MoveIt — "a motion planning, manipulation, and kinematics framework for ROS" (its website moved to moveit.ai). It plans collision-free arm motions using the kinematics of the robot's description and the obstacles around it. As of October 2026, MoveIt 2.15 is the LTS version for Lyrical, 2.12 for Jazzy.
  • Nav2 — the navigation stack for mobile robots, which calls itself a "production-grade autonomy framework": maps, localization, path planning, obstacle avoidance and recovery behaviors. A navigation goal is an action. As of October 2026 it is actively supported for Lyrical and Jazzy.
  • micro-ROS — ROS 2 on microcontrollers. A small board such as an ESP32 (with ESP-IDF and FreeRTOS) or a Raspberry Pi Pico runs a micro-ROS client and appears in the ROS graph through an agent on the main computer — useful for motor controllers and sensors that need hard timing. As of October 2026 its ESP-IDF component has branches for Humble, Jazzy, Kilted and Rolling, but not yet for Lyrical. It is an optional next step on Building Your First Robot.

Industrial robot vendors increasingly ship ROS 2 drivers of their own; which ones, and how they fit next to the vendor's own language, is on Industrial Robots and Cobots in Practice. Testing a ROS 2 system without hardware is on Simulation and Digital Twins.