LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

ROS and ROS2 Explained

roboticsrosros2ddsmiddlewaresoftware-architecture

The Robot Operating System is not an operating system. It does not have a kernel, a scheduler, or a process model of its own; it runs on top of plain Linux. What ROS actually is — and what makes it the convention nearly every modern robot speaks — is a publish-subscribe message bus, a set of conventions for naming, packaging, and discovering software modules, and a sprawling ecosystem of robot-specific libraries (sensor drivers, motion planners, perception stacks, simulators) built around that bus. Whether your robot is a research-lab manipulator arm, a Roomba, an industrial AGV, a quadruped, or a custom hobby project, the odds that someone has already written half the software you need as a ROS package are uncomfortably high. The cost of that ecosystem is real — a learning curve that takes weeks, a build system that is its own discipline, and a runtime model that does interesting things at scale — and the version split between ROS1 and ROS2 has spent the better part of a decade fragmenting that ecosystem in ways that matter for what you actually build. This post walks what ROS is, what changed when ROS2 replaced the central master with DDS, how the build-and-launch story actually fits together, and an honest take on the learning curve for someone trying to use it for the first time.


ROS Is a Message Bus With Strong Conventions

The most useful frame for ROS is “ROS is a publish-subscribe message bus that comes with conventions, packaging, and tooling, plus an enormous library of robot-domain code.” Everything else follows from that.

Your robot has many independent pieces of software running concurrently — a driver that reads a lidar at 10 Hz, a perception module that converts lidar scans into obstacle maps, a planner that consumes obstacle maps and produces velocity commands, a motor controller that consumes velocity commands and writes to the wheels, a state estimator that consumes wheel odometry and IMU data and publishes pose. Each of these is a separate process (in ROS terminology, a node), and they communicate by passing messages through named channels (topics) on the bus.

        TYPICAL ROS GRAPH (mobile robot, simplified)

   /lidar_driver  ─publishes──> /scan
                                  │
                                  ▼
   /imu_driver    ─publishes──> /imu_data ──> /state_estimator ──> /odom
                                                                     │
                                                                     ▼
   /scan         ────────────────────────> /local_planner ──> /cmd_vel
   /odom         ────────────────────────────────^                  │
   /map_server   ─publishes──> /map ─────────────^                  │
                                                                    ▼
                                                          /motor_controller
                                                                    │
                                                                    ▼
                                                              physical motors

A node publish-es to a topic it owns; any number of other nodes subscribe to that topic and receive every message that gets published. The publisher does not know who is listening; the subscriber does not know who is sending. This is the fundamental ROS decoupling — and the reason swapping out a lidar driver, a planner, or a perception stack is as easy as launching a different node that publishes to the same topic name. The graph is dynamic: nodes come and go at runtime, the bus rewires.

On top of the topic primitive, ROS adds two patterns the topic itself does not handle well:

  • Services: synchronous request/response calls between two nodes. Use these for short, infrequent commands like “save the map” or “calibrate the camera.” Bad for high-rate streams.
  • Actions: long-running goals with progress feedback and cancellation. Use these for “navigate to coordinate (3.4, 2.1)” or “pick up object.” An action server runs the goal asynchronously, sends progress updates back, and reports success or failure.

The messages themselves are not free-form bytes. ROS has its own interface definition language (IDL) — .msg and .srv files — that declares strongly-typed structs. Standard message types like sensor_msgs/Image, geometry_msgs/Twist, and nav_msgs/Odometry come with the distribution; you define your own when you have custom data.

This whole architecture is what makes ROS feel like an operating system even though it is not one. The bus is the shared substrate that all the components plug into. Once you have it, swapping the implementation of any one node — different lidar, different planner, different motor controller — is almost free. The conventions are the real product, not the runtime.


ROS1 vs ROS2: The Master Goes Away

ROS1 — the original, which dominated academic robotics from 2007 to roughly 2020 — had a single architectural decision that became its undoing at scale: a central master process (roscore) that every node had to register with at startup to discover its peers. The master maintained a directory of which node was publishing or subscribing to which topic, and helped each node find the others. It was simple, it worked, and it was a single point of failure. Lose the master and every connection in the system dropped.

ROS2, which has been the active development branch since around 2020, replaces the master with DDS — the Data Distribution Service, an industry-standard publish-subscribe middleware used in aerospace, defense, and industrial systems for decades. DDS does distributed discovery: every node, on startup, broadcasts its existence on the local network (typically via UDP multicast), and every other node listens. Two nodes that want to talk find each other directly, without any central coordinator. The master is gone. Lose any one node and the rest of the system continues; there is no single failure point.

The other architectural wins ROS2 brought over ROS1:

  • Quality of Service (QoS) settings. DDS lets each publisher and subscriber declare what they expect — reliable versus best-effort delivery, history depth, deadline guarantees, durability — and the middleware enforces it. ROS1 was effectively best-effort TCP with no controls. This is what makes ROS2 viable for safety-critical and real-time applications.
  • Cross-platform support. ROS1 was Linux-only in practice. ROS2 runs on Linux, macOS, Windows, and embedded targets via micro-ROS, which is a meaningful expansion of where you can deploy.
  • Native multi-machine. ROS2 nodes on different machines on the same network discover each other automatically; ROS1 needed extra configuration and DNS dances.
  • Real-time support. ROS2 was designed for soft real-time use from the start, where ROS1 was not. There are real-time kernel patches and DDS implementations that make hard-real-time loops possible.

The cost of moving from ROS1 to ROS2 is real. The API is similar but not identical, message types changed slightly, the build system moved from catkin to colcon, and most ROS1 packages had to be ported. By mid-2026, the ROS1 ecosystem is essentially deprecated (Noetic, the last LTS, ended support in May 2025), the academic robotics world has finished migrating, and new projects should start on ROS2 without exception. The current LTS releases in 2026 are Humble (Ubuntu 22.04, supported until 2027) and Jazzy (Ubuntu 24.04, supported until 2029), with rolling releases and shorter-lived editions like Iron and Kilted in between.

Property ROS1 (Noetic) ROS2 (Jazzy/Humble)
Discovery Central rosmaster DDS distributed multicast
Wire protocol Custom TCP/UDP DDS over UDP (RTPS)
QoS controls None Per-publisher/subscriber
Real-time No Yes (with appropriate setup)
Multi-machine Manual config Automatic via DDS
Build system catkin colcon
Status (2026) EOL May 2025 Active, two LTS lines
Platforms Linux only Linux, macOS, Windows, embedded

DDS, RMW, and Why It Matters

DDS is its own piece of infrastructure worth understanding because it shapes how ROS2 behaves in interesting ways. The OMG (Object Management Group) published DDS in 2004 as a portable spec, and several vendors implement it: eProsima Fast DDS (the default in Humble, Jazzy, and Kilted), Eclipse Cyclone DDS (the default in older Iron), RTI Connext DDS (commercial, used in defense and aerospace), and a few others. ROS2 abstracts the underlying DDS implementation behind a layer called the RMW (ROS Middleware) so that you can swap one for another by setting an environment variable.

1
2
3
# Switch ROS2 to a different DDS implementation at startup
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
ros2 node list

Why would you swap? Performance differs, particularly at scale — under high node counts or on lossy wireless networks, one vendor will outperform another. Cyclone DDS has historically been preferred for very-large-node multi-robot fleets; Fast DDS is the standard default and broadly fine for most setups. The fact that you can choose without changing application code is part of the architectural win.

The trade-off DDS brings is complexity. Discovery via multicast does not always cross network segments cleanly. Wi-Fi networks that filter multicast, Docker networks with bridge mode, VPNs, and home routers all do interesting things to DDS discovery. Many beginner ROS2 frustrations resolve to “the nodes cannot see each other because multicast does not reach them,” and the standard fix is to use the Discovery Server mode (a small process that mimics a central directory for discovery only) on networks where multicast is unreliable. The newer Zenoh middleware, becoming an option in recent ROS2 releases, is specifically designed for diverse network topologies including WANs and cellular and is a sign of where the bus might go next.

For someone running a robot on a normally configured home network, DDS just works once you stop fighting your router’s multicast settings. For someone deploying to fleets across networks, the discovery story is the first thing that breaks.


The Build System, the Workspace, and colcon

If ROS feels heavyweight, the build system is a big reason. A ROS2 workspace is a directory containing a collection of source packages (ROS-shaped C++ or Python libraries with metadata), built and installed using colcon (the successor to catkin). The pattern looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
git clone https://github.com/ros-perception/vision_opencv.git
git clone https://github.com/my-robot/my_robot_pkgs.git
cd ~/ros2_ws

# resolve dependencies declared in each package's package.xml
rosdep install --from-paths src --ignore-src -r -y

# build all packages in src/
colcon build --symlink-install

# overlay this workspace on top of the installed ROS2 distro
source install/setup.bash

# now your shell sees the workspace's packages and you can launch them
ros2 launch my_robot_pkgs robot.launch.py

Two things to know about this. First, every shell that wants to use a workspace must source install/setup.bash. Forgetting this is the #1 source of “command not found” frustration. Second, the build system has a lot of moving pieces (CMake under the hood for C++, setuptools for Python, plus colcon orchestration on top, plus rosdep for system deps). When it works, it is a surprisingly clean way to ship a multi-package robot stack to colleagues with one command. When it breaks, the error messages are not famous for being friendly.

The runtime side uses launch files — Python scripts that describe a set of nodes to start, their parameters, their topic remappings, and any conditional logic (“start the simulator node if use_sim is true, else start the real driver”). A ros2 launch command brings up an entire robot subsystem from one file, the way docker compose up brings up a multi-container application. The whole pattern is similar in spirit to running a homelab via docker compose — declarative composition of services with shared infrastructure — except ROS does it for robot software and the “shared infrastructure” is the DDS bus.


What ROS Is For (and What It Is Not For)

ROS earns its complexity when you actually need the ecosystem it brings. For these cases it is the right tool by a wide margin:

  • Research robotics. Almost every robotics paper out of an academic lab in the last decade has reference code that is a ROS package. If you want to reproduce work, you are running ROS.
  • Building a robot from existing parts. A lidar, a depth camera, a stereo pair, an IMU, a motor controller, a navigation stack, a SLAM library, a manipulation planner — each is a ROS package. Wiring them together is mostly configuration, not custom code.
  • Multi-robot systems. ROS2’s DDS-based discovery handles multi-machine and multi-robot setups well.
  • Industrial deployment. ROS2’s QoS controls, real-time support, and security framework (SROS2) make it viable for production robotics, where ROS1 was research-only.
  • Simulation. Gazebo Sim integrates natively, and the same nodes that drive a real robot drive a simulated one with no code changes. The sim-to-real workflow is one of ROS’s most underappreciated strengths.

ROS is the wrong tool for:

  • Single-purpose embedded systems. A line-follower robot with one sensor and one motor does not need ROS. A custom C or PID-driven firmware on a microcontroller is faster, smaller, and simpler.
  • Hard real-time loops. ROS2 is soft real-time. The 1 kHz inner loop of a force-controlled robot arm typically runs on the motor controller or a real-time co-processor; ROS2 lives at the higher-rate planner level around it.
  • You-just-need-a-bus situations. If you wanted a message bus you would use MQTT, NATS, or Redis. ROS is a message bus plus a robotics ecosystem; you pay for both even if you only use one.
  • Resource-constrained microcontrollers. Bare ROS2 is too heavy for an MCU. micro-ROS is the embedded variant that uses a lightweight client library and bridges to a full ROS2 node on a Linux machine, but it is its own learning curve.

The Honest Learning Curve

The first hard thing about ROS is that it is not one thing. To use it productively you eventually need to know: the bus model (publishers, subscribers, services, actions); the message types and how to define your own; the build system (colcon, CMake under it); the launch system (Python launch files); URDF (the robot-description XML format that drives Rviz, Gazebo, and planners); TF2 (the transform-tracking library that lets you ask “where is the gripper in the map frame?”); Rviz (the 3D visualizer); Gazebo Sim (the simulator); and at least one of the navigation, manipulation, or perception stacks (Nav2, MoveIt2, ros2_control, image_pipeline). Each is its own subsystem with its own documentation.

The realistic onramp:

Week Goal What to do
1 Bus mechanics ROS2 tutorials: publish/subscribe in Python, then C++, then services, actions
2 Build system Build your own package; write your own message types; understand colcon and source/setup
3 Simulation Bring up a simulated robot in Gazebo, drive it with cmd_vel, visualize in Rviz
4 TF and URDF Get the robot model right so Rviz, TF2, and Gazebo agree on where everything is
5 Navigation Bring up Nav2 on the simulated robot, send goals, watch it plan and execute
6 Real robot Bridge to actual hardware (or a TurtleBot, Create3, or similar starter platform)

By the end of a focused six weeks most people can drive a research-grade robot through simulated environments and onto real hardware. The first week is the most frustrating — half-correct documentation, environment variables that need to be set, multicast that does not work on your Wi-Fi — and quitting before week three is common. The payoff for staying past it is the ability to consume the rest of the robotics literature directly.

For a homelab hobbyist who just wants to play with a robot, the honest advice is to use a setup where the build and deploy story is reliable: a dedicated Ubuntu 22.04 or 24.04 machine for development, a TurtleBot4 or similar reference platform for first hardware, Docker-based ROS2 images when you need to keep environments clean, and the ros2 launch files from the platform’s own examples as the starting point for your own. Avoid the temptation to start by writing your own URDF for a robot you also designed yourself — the URDF/TF debugging path eats months.


Verdict

ROS is the Linux of robotics — not an operating system in any kernel sense, but the convention-and-message-bus stack and ecosystem that the field has converged on. The bus itself is conceptually simple: nodes publish messages onto named topics, other nodes subscribe and consume them, and the whole thing forms a dataflow graph that the system architecture lives inside. ROS1’s central master made discovery simple at the cost of a single point of failure; ROS2 replaced it with DDS, an industry-standard pub-sub middleware that does distributed discovery, gives you per-channel QoS controls, and enables real-time and cross-platform deployment, at the cost of multicast-discovery quirks that bite beginners. The build system (colcon over CMake/setuptools) and launch system (Python launch files) are powerful and not friendly; you learn them or you do not ship a robot. The ecosystem is the real product — Nav2 for mobile robots, MoveIt2 for manipulators, ros2_control for hardware abstraction, perception_pcl and image_pipeline for sensor processing, Gazebo Sim for simulation, micro-ROS for embedded targets — and consuming it is the leverage ROS gives you over rolling your own. The wrong situations to reach for ROS are single-purpose embedded controllers and any case where you just want a message bus without the robotics framework; the right situations are research robotics, building from off-the-shelf parts, multi-robot systems, industrial deployment, and any workflow where the sim-to-real path through Gazebo is part of the win. Plan on a six-week learning curve, start on ROS2 Humble or Jazzy in 2026, accept that the first frustrations are environment and discovery quirks rather than your code, and the field will open up around you.


Sources

Comments