ROS 2 and middleware
How ROS 2 programs are built and connected. The node graph, parameters, launch and tf2, the rmw layer with DDS and Zenoh, QoS rules that silently block messages, ros2_control, micro-ROS and the current distributions.
Overview
ROS 2 is not an operating system. It is a set of libraries, tools and conventions for building robot software as a graph of processes that exchange typed messages. Underneath sits a middleware layer, DDS by default, that handles discovery and transport. Most open robotics software, from drivers to MoveIt 2 and Nav2, assumes it. This hub is for engineers writing their first nodes or deciding how to structure a larger system. Code on this site is tested on ROS 2 Jazzy Jalisco (LTS, May 2024).
Key ideas
The graph: nodes, topics, services and actions
A node is one unit of computation, such as a camera driver or a planner. Nodes talk in three ways. A topic is anonymous publish and subscribe: any number of publishers, any number of subscribers, one message type. It suits sensor streams and state. A service is a request and a response, for short calls such as “load this map”. An action is a long-running goal with feedback and cancellation, such as “drive to this pose”, built from topics and services underneath. Each node also has typed parameters that can be set at start-up or changed while it runs. Launch files, written in Python, XML or YAML, start and configure many nodes together. tf2 keeps a time-stamped tree of coordinate frames, so any node can ask for the transform between two frames at a given time.
Middleware, discovery and QoS
ROS 2 reaches the middleware through the rmw interface. In ROS 2 Lyrical Luth the default implementation is rmw_fastrtps_cpp (eProsima Fast DDS), and Cyclone DDS, RTI Connext and Zenoh are also Tier 1, according to the Lyrical supported platforms page. With DDS there is no central master: nodes discover each other on the network, and ROS_DOMAIN_ID separates groups that share one network. Zenoh uses a router for discovery instead.
Every publisher and subscriber carries a QoS profile: reliability (reliable or best effort), durability (volatile or transient local), history depth, deadline and liveliness. When the two ends are incompatible, they never connect. You get a warning in the log and no messages. A reliable subscriber will not accept data from a best-effort publisher, which is the classic reason a node “receives nothing” from a sensor driver. The block below runs in the Jazzy container and shows it.
# ros2
import rclpy
from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy
from std_msgs.msg import Int32
def count_received(pub_rel: ReliabilityPolicy, sub_rel: ReliabilityPolicy) -> int:
node = rclpy.create_node("qos_demo")
pub_qos = QoSProfile(depth=10, history=HistoryPolicy.KEEP_LAST, reliability=pub_rel)
sub_qos = QoSProfile(depth=10, history=HistoryPolicy.KEEP_LAST, reliability=sub_rel)
received = []
pub = node.create_publisher(Int32, "counter", pub_qos)
node.create_subscription(Int32, "counter", lambda msg: received.append(msg.data), sub_qos)
for i in range(40):
pub.publish(Int32(data=i))
rclpy.spin_once(node, timeout_sec=0.05)
node.destroy_node()
return len(received)
rclpy.init()
matched = count_received(ReliabilityPolicy.BEST_EFFORT, ReliabilityPolicy.BEST_EFFORT)
mismatched = count_received(ReliabilityPolicy.BEST_EFFORT, ReliabilityPolicy.RELIABLE)
rclpy.shutdown()
print(f"best effort -> best effort: received {matched > 0}")
print(f"best effort -> reliable: received {mismatched} messages")
The second case prints 0 and logs an incompatible QoS warning for the reliability policy. Durability matters too. The tf2 static broadcaster in tf2_ros publishes /tf_static with transient local durability, so a node that starts late still receives every static transform.
ros2_control and micro-ROS
ros2_control separates controllers from hardware. Its controller manager matches the command and state interfaces that each controller needs with those that each hardware component provides, and reports an error on a conflict. The hardware is described in the robot’s URDF. The same controllers, from the ros2_controllers repository, then run against a real arm, a simulator or a mock. micro-ROS brings nodes, publish and subscribe, services and lifecycle onto microcontrollers through a C API based on rcl. It uses eProsima Micro XRCE-DDS, with an agent on a larger computer bridging to the rest of the ROS 2 graph, and supports FreeRTOS, Zephyr and NuttX.
Distributions
A new ROS 2 distribution comes out each May. Releases in even years are LTS and are supported for about five years, and releases in odd years are supported for 1.5 years, according to the ROS 2 release schedule. The ROS 2 distributions page listed these supported releases on 14 November 2026.
| Distribution | Released | End of life | Notes |
|---|---|---|---|
| Lyrical Luth | 22 May 2026 | May 2031 | LTS and the latest release. Tier 1 on Ubuntu 26.04 and Windows 11 |
| Kilted Kaiju | 23 May 2025 | December 2026 | Non-LTS |
| Jazzy Jalisco | 23 May 2024 | May 2029 | LTS. Tier 1 on Ubuntu 24.04. Used for code on this site |
| Humble Hawksbill | 23 May 2022 | May 2027 | LTS |
| Rolling Ridley | Continuous | None | Development distribution |
macOS is a Tier 3 platform for Lyrical, which means building from source. On a Mac, run ROS 2 in a Linux container or virtual machine.
Where to start
Hello ROS 2 writes a publisher and subscriber and runs them in the Jazzy container. The ROS 2 talker, listener and tf2 project adds parameters, launch and transforms. Keep the ROS 2 CLI cheat sheet open while you work, and see the URDF and MJCF reference for robot descriptions. The ROS platform page lists the main packages.
Tools and platforms
| Tool | What for | Licence |
|---|---|---|
| rclpy and rclcpp | Python and C++ client libraries | Apache-2.0 |
| Fast DDS | Default DDS implementation | Apache-2.0 |
| Cyclone DDS | Alternative DDS implementation | EPL-2.0 or EDL-1.0 |
| rmw_zenoh | Zenoh-based middleware for ROS 2 | Apache-2.0 |
| ros2_control | Controller manager and hardware abstraction | Apache-2.0 |
| micro-ROS | ROS 2 on microcontrollers | Apache-2.0 |
| RViz and rosbag2 | 3D visualisation, and recording and replay | BSD-3-Clause-Clear, Apache-2.0 |
| SROS2 | Security tooling: keys, certificates, access control | Apache-2.0 |
Common mistakes
- Mismatched QoS between a driver and your subscriber. Check with
ros2 topic info -v. - Two robots on one network with the same
ROS_DOMAIN_ID, each seeing the other’s topics. - Blocking inside a callback, which stalls every other callback on a single-threaded executor.
- Publishing transforms with the wrong timestamp, so tf2 lookups fail or extrapolate.
- Mixing distributions across machines. Messages may not match.
- Building on a non-LTS release for a product that must run for years.
Going deeper
- Macenski, Foote, Gerkey, Lalancette and Woodall, Robot Operating System 2: Design, architecture, and uses in the wild, Science Robotics, 2022.
- The ROS 2 distributions page, release schedule source and REP 2000, ROS 2 Releases and Target Platforms.
- The Object Management Group DDS specification, version 1.4.
- The ros2_control documentation and the micro-ROS documentation.
- The ROS 2 design articles, which explain why the system looks the way it does.