30 min

ROS 2: the honest minimum

You built a working robot without typing ros2 once. Here is exactly how much of it you owe the industry, and how much of what you will find written about it is already dead.

Where you are. You have picked a track. Everything from here is shared across all three, starting with the piece of the industry you have so far managed to avoid completely.

Nine months, one process

Everything you have built runs inside a single Python program. Read the camera, call the policy, write six joint angles down a serial bus, loop. One clock. One language. One place to put a breakpoint.

Now open the crate a commercial robot arrives in.

Inside there is an arm driver written by the arm’s manufacturer, a depth-camera driver written by the camera’s manufacturer, a battery and thermal monitor written by the integrator, a safety supervisor that somebody had to get certified, and a wheelbase controller from 2019 whose author left the company. Five programs, four organisations, two languages, written years apart. None of them import each other. None of them can.

They ship together anyway, and they work, because every one of them was written against the same agreement: named channels, typed messages, and a way for processes to find each other on a network without anyone configuring anything.

That agreement is the thing you have been skipping.

The idea in one paragraph

ROS 2 is not a framework you write your robot inside. It is four separate things wearing one name: a peer-to-peer message bus, a build and packaging convention, an interface definition language for messages, and an archive of a few thousand hardware drivers other people already wrote. You are learning it for two reasons, and neither one is that it will make your own work faster. The first is that industry deployment assumes it, so the moment your code has to sit next to somebody else’s driver, this is the socket. The second is that interviews assume it, and the questions are consistent enough that you can prepare for them precisely. Learn it as a compatibility layer, not as a runtime. Then learn which parts of it are current, because a large fraction of what you will find written about ROS is about a version that is dead.

What you have built What arrives in a crate One process obs = camera.read() action = policy(obs) arm.send(action) one clock, one language, one place to put a breakpoint arm driver vendor A depth camera vendor B safety certified, 2021 topic bus your policy unchanged inside recorder writes every stream no central broker peers discover each other; your loop becomes one node among many
On the left, one process containing a read, a policy call and a write; on the right, five separate driver and policy nodes attached to a shared topic bus

Wider than the screen; scroll it sideways.

The genuine minimum

Two tiers. The first is one or two weeks of evenings, and it is the part that is genuinely not optional.

ConceptWhere you meet itWhy it is on the list
Workspace and buildcolcon build, then source install/setup.bashNothing works before you source, and knowing why is a real interview filter
Nodes and topicsrclpy publisher and subscriber; ros2 topic echo, hz, infoThe whole model, in about forty lines
Interfaces.msg, .srv, .action files; ros2 interface showTyped contracts between processes you did not write
Quality of servicereliability, durability, historyThe most common silent failure in the system
Parametersros2 param, YAML parameter filesHow every real node is configured
Launch filesPython launch, or XML launch, both currentComposing a multi-node system with remaps and namespaces
tf2tf2_echo, view_framesThe frame maths you already did, with an API and a clock attached
rosbag2ros2 bag record and playRecordings, which for you are datasets
Debuggingros2 doctor, rqt_graph, ros2 topic hz and delayBecause nothing tells you when it is wrong

Two of those rows are cheap for you specifically. The tf2 library is the transform tree from the kinematics module with time added: every frame relationship is stamped, and you ask for the relationship as of a moment rather than now. You already own the maths; what is new is the buffering and the interpolation. And rosbag2 is a recording format, which is the subject of the next lesson.

Sourcing deserves one sentence of explanation rather than a shrug: source install/setup.bash edits environment variables in your current shell so that Python can import your packages, the CLI can find your executables, and message types resolve. It is not a build step. It is why a new terminal breaks everything, forever, in every ROS installation on earth.

The failure that produces no error

Every publisher offers a contract and every subscriber requests one. If the offer does not satisfy the request, they simply do not connect. No exception, no warning on either side, and the topic still appears in ros2 topic list because the publisher genuinely is publishing.

Publisher offers: reliable Subscriber requests: reliable Connected messages arrive Publisher offers: best effort Subscriber requests: reliable No connection and no error the subscriber demands a guarantee the publisher never offered ros2 topic list shows the topic either way
A reliable publisher and reliable subscriber connect and messages flow; a best-effort publisher and a reliable subscriber never connect and no error is reported anywhere

Wider than the screen; scroll it sideways.

The second tier

About another week, and this is where interviews go when they want to know whether you have actually used it.

Actions matter more than services. An action is a goal, a stream of feedback, a terminal result, and a cancel channel, built on top of topics and services. You have designed this exact thing before under a different name. The whole public interface of the main open-source navigation stack is actions, so if you are wiring a planner over a robot, this is the surface you meet.

Lifecycle nodes are managed nodes with explicit states: unconfigured, inactive, active, finalized, with transitions you drive from outside. They exist because a robot that brings up twelve processes in an arbitrary order will hurt someone. Know the state names.

Executors and callback groups are where the good question lives.

Single-threaded executor pending queue the service reply the next timer tick every subscription callback, running blocked, waiting for a reply request service server replies at once the reply comes back and joins the queue only the blocked executor can drain that queue, so nothing ever moves again
A single-threaded executor with one blocked callback and a reply that returns into the queue only that executor can drain

Wider than the screen; scroll it sideways.

Two more, at vocabulary depth. Composition puts several components in one process so they can pass messages without copying, which matters enormously for camera pipelines. And RMW is the pluggable middleware layer: a DDS implementation is the default, an alternative DDS is common, and a Zenoh-based option has been a supported tier-one middleware since the 2025 release without being the default anywhere. One paragraph of awareness, not a project.

Why your own stack skips all of this

Because it should. A learned policy takes images and joint states and emits joint commands at 30 to 50 Hz. There is no planner in that loop, no motion planning library, and often nothing that would benefit from being split into six processes with a discovery protocol between them. Adding a bus to a single-process control loop buys latency and a deployment problem.

So hold both. Your daily driver is a Python process. The bus is how that process gets adopted by someone whose robot already has eleven other processes on it, and how you talk about robots with people who have only ever worked that way.

What is legacy, plainly

Half the ROS material online describes a system that is gone. This table is the fastest money you will make in this module.

Dead or dyingThe current thingSince
Everything ROS 1: rospy, catkin, roslaunch, nodelets, .bag filesROS 2 equivalents throughoutFinal ROS 1 release went end of life in May 2025
Gazebo ClassicModern Gazebo: Harmonic pairs with the current teaching distro, Jetty with the newestClassic went end of life in January 2025
The 2025 non-long-term-support distroA long-term-support distro, alwaysIt goes end of life in November 2026
rosbag2 sqlite3 storageMCAP as the default containerSee the next lesson
Most of rqt beyond rqt_graphFoxglove or RerunGradually, over the last few years
Hand-tuning DDS profilesKnowing the concept existsIt is a specialist’s job, not a learner’s

Distributions are named alphabetically and released each May. Two long-term-support ones matter today.

DistroReleasedEnd of lifeUbuntuUse it?
HumbleMay 2022May 202722.04Largest installed base, but it expires next year
JazzyMay 2024May 202924.04Teach and learn on this one
KiltedMay 2025November 202624.04No. Non-LTS and nearly gone
LyricalMay 2026May 203126.04Where the industry moves through 2027

Learn on Jazzy. It is supported into 2029, it is the version named in current job postings, and every significant package has a stable release on it. The newest long-term-support distro is technically better, and one of its additions will interest you: a Python node class that lets you await ordinary asyncio operations inside callbacks, which removes a whole category of the deadlock above. But its ecosystem is a few months old, and a package having a release on it is not the same as that release being the one everyone runs. Concepts are identical across distros. Only package names and dates change.

Where it will run, honestly

There are no official macOS binaries. The platform is a source-build tier and has been fragile for years. Three approaches work, and you should pick by what you need to see.

ApproachWhat you getWhat it costs
Conda-based packages via pixiNative Apple silicon, no container, distros through the newestNever source an apt ROS environment alongside it; the Python paths collide
Docker with arm64 imagesRuns natively, clean for nodes, topics and buildsGraphical tools over X11 are slow and awkward
A Linux virtual machineEverything works, including the 3D toolsHeaviest option, and it wants real RAM

One stack, chosen for you

Two large frameworks sit on top of ROS 2: one for navigation, one for arm motion planning. Learn the navigation one.

The reason is what each one teaches, not which is healthier. Both are alive: checked against the ROS distribution index on 9 August 2026, both the navigation stack and the motion planning stack have current releases on the two newest distros. What differs is what a week with each buys you.

Navigation teaches more transferable architecture: behaviour trees, lifecycle nodes, action servers, layered costmaps and plugin-based planners. And it runs end to end in simulation on a laptop, whereas motion planning without an arm in front of you is mostly a configuration exercise.

That said, you have an arm and you are heading toward manipulation, so spend an afternoon on the vocabulary of the motion planning stack: the central planning node, the planning scene, the servo component for streaming velocity commands, and the setup assistant that generates the configuration. Enough to hold a conversation. Be clear-eyed that the learning-first labs mostly bypass sampling-based planners altogether, since a policy emits joint commands directly at 30 to 50 Hz.

One more honesty, on language. Platform and infrastructure job postings say C++17 or C++20 and they mean it. Python-only closes the robotics platform engineer door. It does not close the robot learning engineer door, or the data and simulation infrastructure doors, which is where your background points anyway. Decide that deliberately rather than finding out in a screen.

Check yourself

1. Your publisher is running, ros2 topic list shows the topic, and your subscriber’s callback never fires. What do you check first, and why is there no error message?

Quality of service compatibility on both ends, with ros2 topic info --verbose. Publishers offer a contract and subscribers request one; if the offer cannot satisfy the request the two are simply never matched, and neither side considers that an error, because from the publisher’s point of view it is publishing correctly and from the subscriber’s point of view no compatible publisher exists. The classic pair is a sensor driver offering best-effort against a hand-written subscriber requesting reliable. Durability is the other half: a late-joining subscriber gets nothing unless the publisher is transient-local.

2. Why does calling a service from inside a callback deadlock on the default executor, and what are the two fixes?

The response arrives and is placed in the executor’s queue. The only thread that drains that queue is the executor, which is currently inside your callback waiting for the response, so it never gets processed. It is the same shape as blocking on a task from inside a handler that the same event loop has to run in order to complete it. Fix one: do not block, and handle the response in a separate callback. Fix two: put the nested work in a re-entrant or separate callback group and run a multi-threaded executor.

3. Someone hands you a tutorial that uses rospy, catkin and roslaunch. What does that tell you, and what else in the same repository is suspect?

It is ROS 1, whose final release went end of life in May 2025, so the tutorial is at least a version behind and possibly many years old. Everything around it is suspect: it will likely also use Gazebo Classic, which went end of life in January 2025, sqlite-backed bags rather than the current default container, and rqt tooling that has largely been replaced by browser and notebook viewers. The concepts still transfer; none of the commands do.

4. Why does the learning-first stack bypass ROS 2, and why do you learn it anyway?

Because a learned policy is one process consuming images and joint states and emitting joint commands at 30 to 50 Hz. There is no planner in that loop and no benefit to splitting it into processes with a discovery protocol between them; adding a bus buys latency and a deployment problem. You learn it because deployment and hiring both assume it. The moment your code sits next to a driver somebody else wrote, this is the socket, and the interview questions come from this vocabulary regardless of what your own robot runs.

5. Which distribution do you learn on today, and what is the argument against the newest one?

Jazzy, released May 2024 and supported to May 2029. It is what current job postings name and every significant package has a stable release on it. The newest long-term-support release, from May 2026, is technically better and supported to 2031, but its ecosystem is only months old and thinner than Jazzy’s. Since the concepts are identical across distributions and only package names and dates change, there is no learning benefit that offsets a thin package ecosystem.

6. What is the one-sentence honest position on ROS 2 that this lesson wants you to hold?

You are learning it as an interface to the industry, not as your daily driver. Your own stack stays a single fast Python process; ROS 2 is how that process is adopted by a robot that already runs eleven others, and how you hold a conversation with people who have only ever built that way.

Do this

Two evenings. The goal is one running graph and one deliberately broken one, not a project.

1. Get an environment, then stop tuning it. Use the conda-based packages through pixi on Apple silicon, or an arm64 Docker image, and follow whichever install page you chose rather than any instructions written down here; they change more often than lessons do. Success is ros2 doctor running and ros2 run demo_nodes_py talker printing. Time-box this to ninety minutes. If it fights you, switch approach rather than debugging it.

2. Run the graph and look at it from outside. With the talker running, in other terminals:

ros2 node list
ros2 topic list
ros2 topic info /chatter --verbose
ros2 topic echo /chatter
ros2 topic hz /chatter

Read the verbose output properly. The reliability, durability and history fields it prints are the contract from the figure above.

3. Break it on purpose. Publish and subscribe on the same topic with deliberately incompatible settings, using the quality-of-service flags on ros2 topic pub and ros2 topic echo. Check --help on your distro for the exact flag spelling. Confirm two things with your own eyes: no message arrives, and nothing anywhere prints an error. Then fix it by relaxing the subscriber. This is the ten minutes that makes the interview answer yours rather than remembered.

4. Record something and read it back. ros2 bag record /chatter for a few seconds, then ros2 bag info and ros2 bag play. Notice which storage format it wrote by default. Keep that recording; the next lesson opens it in three different tools.

5. Write down the deadlock. In notes/07-ros2.md, draw the executor deadlock from memory and write the two fixes in your own words. Two minutes, and it is the thing you will actually be asked.

What you can now do

You can say what ROS 2 is in four parts rather than as one vague framework, name the concepts a working engineer is assumed to know and skip the rest without anxiety, diagnose the quality-of-service mismatch that produces no error, explain the single-threaded executor deadlock as the event-loop bug you already know, pick a distribution and a platform for the machine you actually own, and recognise ROS 1, Gazebo Classic and non-supported distributions on sight so that a decade of stale tutorials stops costing you time.

What you can now do

You can name the ROS 2 concepts a working engineer is assumed to know, explain why the learning-first stack bypasses them, and tell current practice from legacy at a glance.