ROS, from zero
Notes for someone who has never used the Robot Operating System but needs to decide how much of it to adopt. Assumes fluency with Linux, distributed systems and Python/C++, and no ROS-specific knowledge at all.
Core take: ROS is not an operating system and not a framework so much as a distribution. It is a message-passing convention, a build system, and — the part that actually matters — several thousand packages that already implement the tedious parts of robotics. Its reputation for awkwardness is deserved and almost entirely concentrated in the build/versioning layer, not the runtime model.
Where it stands in 2026:
- ROS 1 is dead. Noetic, the last release, reached end of life on 31 May 2025. Anything new is ROS 2.
- ROS 2 is on a fixed annual cadence — a release every 23 May, long-term support in even years.
- The current long-term support release is Lyrical Luth (22 May 2026, supported to May 2031, Ubuntu 26.04). The previous one, Jazzy Jalisco (Ubuntu 24.04, to May 2029), is what most of the ecosystem actually runs today.
What it actually is
Four separable things ship under the name, and it helps to keep them apart because they fail independently:
- A communication model. Processes (“nodes”) exchange typed messages over named channels (“topics”), plus request-response (“services”) and long-running goals with feedback (“actions”). This part is small, sound, and the reason ROS works.
- An interface definition language.
.msg,.srvand.actionfiles generate C++ and Python types. Message definitions are the real contract between packages —sensor_msgs/Image,geometry_msgs/PoseStamped,nav_msgs/Odometryare effectively industry standard. - A build and packaging system —
colcon,ament,rosdep, and Debian packages per distribution. This is the part everyone complains about. - A package ecosystem. Coordinate-frame bookkeeping, visualization, logging, navigation, manipulation, drivers for most sensors that exist. This is where the value is.
What it is not: not an operating system, not a real-time system, not a scheduler, and not a framework that owns your program’s main loop in the way that, say, a game engine does. A ROS node is an ordinary process that happens to link a library.
The runtime model
A system is a graph of processes discovering each other on the network at runtime. There is no central broker and, since ROS 2, no master process — a node comes up, announces what it publishes and subscribes to, and connections form automatically.
The primitives:
| Primitive | Semantics | Use for |
|---|---|---|
| Topic | anonymous many-to-many publish/subscribe, fire-and-forget | sensor streams, poses, commands |
| Service | synchronous request/response, one server | “set this parameter”, quick queries |
| Action | goal with feedback, cancellable, long-running | “fly to this pose”, “plan a path” |
| Parameter | per-node typed key-value, settable at runtime | configuration |
The smallest useful program, in Python:
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import PoseStamped
class Planner(Node):
def __init__(self):
super().__init__('planner')
self.pub = self.create_publisher(PoseStamped, '/goal_pose', 10)
self.create_subscription(PoseStamped, '/odom_pose', self.on_pose, 10)
def on_pose(self, msg):
goal = PoseStamped()
goal.header.frame_id = 'map'
goal.pose.position.x = msg.pose.position.x + 1.0
self.pub.publish(goal)
rclpy.init()
rclpy.spin(Planner())That is the whole model. rclpy for Python, rclcpp for C++, both thin wrappers over a common C layer (rcl), so the concepts transfer exactly.
Inspection is good and worth knowing early — these are the commands that make a running system legible:
ros2 topic list # what channels exist
ros2 topic echo /odom_pose # dump messages
ros2 topic hz /camera/image_raw # actual publication rate
ros2 node info /planner # what one node is connected to
ros2 bag record -a # record everything to diskUnderneath: DDS, and why it leaks
ROS 2 does not implement its own transport. It delegates to DDS (Data Distribution Service), an industrial pub/sub middleware standard, through a pluggable abstraction (rmw). Several implementations exist — Fast DDS is the default, Cyclone DDS is the common alternative.
Consequences that show up in practice:
- Discovery is multicast and chatty. Nodes find each other by broadcasting. On a busy network, or with many nodes, discovery traffic becomes a real cost, and on networks where multicast is filtered it silently fails to work at all.
ROS_DOMAIN_IDpartitions the network. Two people running ROS on the same LAN with the same domain ID will see each other’s robots. This surprises everyone once.- Quality of Service settings must match or no data flows. Publishers and subscribers negotiate reliability (reliable vs best-effort), durability, and history depth. A mismatch is not an error — the connection simply never forms, and
ros2 topic listcheerfully shows the topic. This is the single most common way a new ROS 2 system fails, and it is worth internalizing before it costs an afternoon: sensor data is conventionally best-effort, commands and state are reliable.
Zenoh is the notable alternative: rmw_zenoh replaces DDS with a protocol designed for exactly the cases DDS handles badly — many nodes, unreliable links, routing across networks. It has been available since Jazzy, is not the default, and is worth knowing about the moment discovery misbehaves. PX4 also ships Zenoh support directly.
The build system, which is the actual learning curve
A ROS 2 workspace is a directory with a src/ folder full of packages, built by colcon into build/, install/ and log/.
mkdir -p ~/ws/src && cd ~/ws
# clone packages into src/
rosdep install --from-paths src --ignore-src -y # pull system dependencies
colcon build --symlink-install
source install/setup.bash # required, every shell
ros2 run my_package my_nodeThings that are surprising coming from ordinary software:
source install/setup.bashis mandatory and stateful. It mutatesAMENT_PREFIX_PATH,PYTHONPATH,LD_LIBRARY_PATHand more. Forgetting it produces “package not found” for a package that plainly exists. Sourcing two different workspaces in one shell produces subtler breakage.- Binary and source packages mix. Most packages install as Debian packages from a ROS apt repository, pinned to one distribution; anything modified must be built from source in a workspace that overlays them. The overlay/underlay distinction is the source of most confusing behaviour.
rosdepmaps ROS package names to system packages per OS and distribution. It works well and is invisible until it does not.- Python packaging is non-standard. ROS Python packages are built by
colcon, notpip, and mixing a virtualenv into a ROS workspace is a known source of pain. Plan to use system Python for ROS nodes.
Launch files start and configure many nodes at once. ROS 2 launch is Python (or XML/YAML), which is more powerful and considerably less readable than the ROS 1 XML it replaced:
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(package='my_pkg', executable='planner', name='planner',
parameters=[{'rate_hz': 10.0}]),
Node(package='my_pkg', executable='mapper', name='mapper'),
])Distributions, and the Ubuntu coupling
A ROS 2 distribution is a synchronized snapshot of thousands of packages pinned to one Ubuntu release. This coupling dominates practical decisions far more than any feature does.
| Distribution | Released | EOL | LTS | Ubuntu |
|---|---|---|---|---|
| Foxy Fitzroy | Jun 2020 | Jun 2023 | yes | 20.04 |
| Humble Hawksbill | May 2022 | May 2027 | yes | 22.04 |
| Iron Irwini | May 2023 | Dec 2024 | no | 22.04 |
| Jazzy Jalisco | May 2024 | May 2029 | yes | 24.04 |
| Kilted Kaiju | May 2025 | Dec 2026 | no | 24.04 |
| Lyrical Luth | May 2026 | May 2031 | yes | 26.04 |
Reading the table:
- Only pick an LTS. The odd-year releases get ~18 months and no ecosystem ever fully moves to them.
- Jazzy is the pragmatic default today — supported to 2029, and where most third-party packages currently are. Lyrical Luth is three months old; the ecosystem will take a year to follow.
- The Ubuntu pin explains a great deal of vendor behaviour. A board stuck on Ubuntu 18.04 gets Foxy, which died in 2023. A Jetson on Ubuntu 22.04 gets Humble. That is why hardware userland age propagates directly into which ROS packages can be used, and why Docker is the universal escape hatch — running a newer ROS in a container over an older host kernel is completely normal practice.
New in Lyrical Luth and worth noting for GPU work: rosidl::Buffer allows publishing and subscribing without copying between CPU and GPU memory, generalizing what NVIDIA had been doing privately in Isaac ROS.
The packages that are the real product
Nobody adopts ROS for rclpy. They adopt it for these:
| Package | What it does | Why it matters |
|---|---|---|
| tf2 | timestamped tree of coordinate frames, with interpolation | asks “where was the camera relative to the map at time t” and gets a correct answer |
| rosbag2 | record and replay any set of topics | the single most valuable debugging tool in robotics |
| RViz2 | 3D visualization of any standard message type | see the map, the pose, the point cloud, the plan |
| Nav2 | ground-robot navigation stack: costmaps, planners, controllers, recovery behaviours | the reference for how a navigation system is structured |
| MoveIt 2 | manipulator motion planning | irrelevant to aircraft, dominant elsewhere |
| robot_state_publisher / URDF | robot geometry description and joint kinematics | feeds tf2 |
| image_transport | compressed image topics | raw camera streams saturate links otherwise |
| MAVROS | MAVLink bridge to PX4 and ArduPilot | the traditional aerial entry point |
| micro-ROS | ROS 2 nodes on microcontrollers | built on the same XRCE-DDS as the PX4 bridge |
tf2 deserves special attention because it solves a problem that is genuinely hard and easy to underestimate. Every sensor reading is in some frame, every frame moves relative to others, and every measurement has a timestamp that does not line up with the others. tf2 maintains the whole tree with time history and interpolates. Systems built without it end up reimplementing it badly.
rosbag2 is the reason to use ROS even if nothing else appeals. Recording every topic during a flight and replaying it deterministically at the desk, into a modified planner, is a workflow that is hard to give up once used.
Where it disappoints
- Not real time. ROS 2 was designed with real-time in mind and can be run that way with effort — a preemptible kernel, careful executors, no allocation in the loop — but the default experience is a garbage-collected Python node scheduled by Linux. Nothing about flight-critical control belongs here; that is what the flight controller is for.
- The QoS trap, as above. Silent non-delivery is a terrible default failure mode.
- Python performance is a real ceiling.
rclpyserialization and the executor overhead make high-rate image or point-cloud handling in Python a losing proposition. Prototype in Python, move hot paths to C++. - Version fragility. A package that builds on Humble may not build on Jazzy; the ecosystem’s answer is a container per distribution, which is workable but makes “just install it” rarely true.
- Discovery does not scale gracefully to large node counts or lossy networks, which is precisely what Zenoh exists to address.
- Documentation quality varies enormously between core packages (good) and the long tail (a README and a hope).
- Security is opt-in and rarely on. SROS 2 exists; almost nobody enables it.
The general shape of the criticism: the runtime model is good, the tooling is mediocre, and the ecosystem is worth the tooling.
Relevance to indoor autonomous capture
Where ROS 2 does and does not earn its place in a drone project of this kind:
Strong reasons to use it
- It is the lingua franca of everything adjacent. Isaac Sim publishes ROS 2 topics, Pegasus supports ROS 2 launch, PX4 speaks it natively through uXRCE-DDS, ModalAI ships an MPA-to-ROS 2 bridge, and NVIDIA’s perception packages are ROS 2 nodes. Not using it means writing adapters to all of them.
- The simulator and the vehicle look identical to the planner. A node consuming
nav_msgs/Odometryand publishing goals does not know whether the pose came from Isaac Sim, a hardware-in-the-loop bridge or a real visual-inertial system. This is the property that makes simulation-first development honest rather than theatrical. - rosbag2 for capture datasets. Record a flight, replay it into successive versions of an exploration policy, compare outcomes on identical input.
- tf2 for the camera-pose bookkeeping that photogrammetry requires anyway.
Where it does not help
- It supplies no autonomy for aircraft. Nav2 is a ground-robot stack — 2D costmaps, footprints, recovery behaviours built around a differential-drive world view. It is worth reading as an architecture, not adopting as a dependency.
- It adds a distribution/Ubuntu constraint to every piece of hardware under consideration, which is a real cost when the candidate boards ship 18.04 and 22.04 userlands.
- Nothing about ROS decides where to fly. As with the flight controller, the interesting layer is the part nobody supplies.
The pragmatic position: use ROS 2 as the integration bus and the data-recording layer, keep the exploration policy as ordinary Python or C++ behind a thin node wrapper, and do not let the framework dictate the algorithm’s structure.
A concrete starting path
For someone starting from nothing, in order:
- Install Jazzy on Ubuntu 24.04, or run it in Docker on any host. Do not start on Lyrical Luth until third-party packages have caught up; do not start on anything non-LTS.
- Do the official talker/listener tutorial, then immediately do the inspection commands above against it —
topic list,topic echo,node info,bag record. Understanding the graph tooling is worth more than understanding the API. - Write one node that subscribes to something and publishes something, in Python, in a workspace built with
colcon. The build system is the actual hurdle; get it over with on a trivial program. - Learn tf2 properly, specifically
lookup_transformwith a timestamp and what the failure modes mean. Most subtle robotics bugs are frame or timing bugs. - Record and replay a bag. This closes the loop on the workflow that makes the rest worthwhile.
- Only then look at PX4’s ROS 2 integration and the
px4_ros2interface library, which assume all of the above.
Estimated time to competence for an experienced engineer: a day to be productive, a week to stop being surprised, and the remaining friction is permanent and lives in the build system.
References
Core:
- ROS 2 documentation — Jazzy branch is the practical default; the tutorials are the ones to follow.
- ROS 2 releases and support dates
- ROS 2 Lyrical Luth release announcement
- ROS Noetic end of life
Middleware:
- rmw_zenoh — the non-DDS alternative
- PX4 Zenoh support
Aerial integration:
Ecosystem:
Related notes: the Pixhawk ecosystem for what sits below ROS, and the NVIDIA Isaac ecosystem for what sits beside it.