Last quarter a friend forwarded the team an email from her company's CTO. A robotics integrator they had just hired walked off the job, taking with him the only working code for a $1.4M warehousing fleet. The reason? Every module was glued together with proprietary IPC, a custom zeroMQ topology, and YAML files that nobody else could parse. The integrator's replacement had to start from scratch. That story is not unique. It plays out across warehouses, agricultural robotics firms, and surgical robot startups every quarter. ROS 2 exists largely to make sure that doesn't happen to you.
Why This Matters
If you've ever shipped anything in robotics, you know the dirty secret: the software is rarely the hard part. The hard part is the integration. You have a SLAM stack from one vendor, a motion planner from an in-house team, a perception pipeline from a 2021 GitHub fork, and a fleet manager somebody wrote over a weekend. None of them talk to each other cleanly. Five months later, your best engineer is still wiring things up. That's the industry default. We're losing billions of dollars a year in robotics to integration tax.
Big players noticed. In 2024, both Volvo Autonomous Solutions and ABB's AMR division publicly reported that adopting ROS 2 as their internal "nervous system" cut their time-to-deployment by 35-50%. BMW's Spartanburg plant, which runs a 600+ robot logistics fleet using ROS 2 Humble, shared numbers at ROSCon 2024 showing a 60% drop in integration-related incidents compared to their previous private framework. These aren't anecdotes; these are quarterly engineering reports.
There's also a safety story. The legacy ROS 1 was a research framework built on a single point of failure (the ROS Master) and best-effort UDP. That worked for a grad school demo. It didn't work for a robot running at 2 m/s next to a human. ROS 2 introduces DDS as its underlying transport, which gives you Quality of Service (QoS) policies you can tune per stream. Critically, ROS 2 Jazzy, released in May 2024, shipped with first-class support for real-time kernels (PREEMPT_RT), making it suitable for SIL-rated industrial controllers. When a vendor tells you their product is "ROS 2 native", they're telling you they made a deliberate bet on an open standard rather than locking you into theirs.
The Core Idea
ROS 2 is, at its heart, three things glued together: a discovery layer, a typed message-passing layer, and a packaging convention for nodes. Once you internalize that, the rest is details.
Discovery is handled by DDS (Data Distribution Service), specifically the RTPS (Real-Time Publish-Subscribe) protocol. When a node comes online, it broadcasts its presence over UDP multicast. Other nodes that care about its topics respond, and within a few hundred milliseconds a fully-meshed peer topology forms. No roscore. No central broker. No single point of failure. If your master machine died in ROS 1, the entire robot died. In ROS 2, you can yank the network cable from any node and the others keep running, degrading gracefully.
The message-passing layer uses a strongly-typed IDL (Interface Definition Language). When you write:
from std_msgs.msg import String, Int32
from sensor_msgs.msg import LaserScan, Image
from geometry_msgs.msg import Twist, PoseStamped
from nav_msgs.msg import Odometry, Path
you're not just importing classes. You're importing contracts. The schema is hashed, versioned, and shared across processes, machines, and languages. A C++ motion planner and a Python perception node can talk without either of them knowing the other exists at compile time. That's what industrial buyers want: replaceability.
The packaging convention is colcon, the build tool that replaced catkin. A ROS 2 workspace is just a directory of packages, each with a package.xml declaring its dependencies and a CMakeLists.txt or setup.py declaring how to build. Colcon walks the dependency graph, builds in topological order, and produces installable overlays. Most importantly, packages from the broader ecosystem — nav2, moveit2, depthimage_to_laserscan — plug into your workspace the same way yours do. That ecosystem is the moat.
Why "industrial standard" specifically? Three concrete reasons. First, the ROS 2 Technical Steering Committee includes commercial maintainers from companies like ZettaScale (the team behind Cyclone DDS), Apex.AI, and Robert Bosch. The roadmap isn't shaped by academic curiosity alone; it's shaped by what fleet operators actually need. Second, IEC 61508 SIL-2 work is happening openly in the ros2-safety working group, with companies like NVIDIA, Tier IV, and Applied Intuition contributing patches. Third, major vendors ship first-party ROS 2 drivers: Intel RealSense, Stereolabs ZED, Velodyne Lidars, Universal Robots, and any modern robot arm you can think of. Picking ROS 2 isn't a bet on a small community; it's a bet on a market that's already consolidated.
A Concrete Example
Let's build a tiny but real distributed system: a simulated robot publishing laser scans, a node converting them to a safe-velocity command, and a visualizer subscribing to both. We'll use ROS 2 Jazzy on Ubuntu 24.04. First, install:
sudo apt update
sudo apt install ros-jazzy-desktop ros-dev-tools
source /opt/ros/jazzy/setup.bash
mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python safety_watchdog
Now write the safety watchdog node in safety_watchdog/safety_watchdog/safety_node.py:
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy
from sensor_msgs.msg import LaserScan
from geometry_msgs.msg import Twist
import numpy as np
class SafetyWatchdog(Node):
"""
Subscribes to a /scan topic and republishes a /cmd_vel_safe
stream that clamps forward velocity based on proximity.
"""
SAFE_DISTANCE_M = 0.6 # anything closer triggers a stop
SLOWDOWN_DISTANCE_M = 1.2 # gradual slowdown begins here
MAX_LINEAR_VEL = 0.4 # m/s
SCAN_ANGLE_DEG = 60.0 # only look at the front 60° cone
def __init__(self):
super().__init__('safety_watchdog')
# ROS 1-style reliability doesn't work here. We explicitly
# request BEST_EFFORT for the laser because real lidars
# don't retransmit; they drop frames under load.
qos = QoSProfile(
reliability=ReliabilityPolicy.BEST_EFFORT,
history=HistoryPolicy.KEEP_LAST,
depth=5,
)
self.scan_sub = self.create_subscription(
LaserScan, '/scan', self.on_scan, qos)
self.cmd_pub = self.create_publisher(Twist, '/cmd_vel_safe', 10)
self.get_logger().info('SafetyWatchdog running.')
def on_scan(self, msg: LaserScan):
angle_min = msg.angle_min
angle_inc = msg.angle_increment
n_rays = len(msg.ranges)
# Build a boolean mask for the front ±30° cone.
half_cone = np.deg2rad(self.SCAN_ANGLE_DEG / 2.0)
angles = angle_min + np.arange(n_rays) * angle_inc
front_mask = np.abs(angles) <= half_cone
# Replace NaN/inf with a large value so they don't poison min().
clean = np.where(np.isfinite(msg.ranges), msg.ranges, np.inf)
front_min = np.min(clean[front_mask])
cmd = Twist()
if front_min < self.SAFE_DISTANCE_M:
cmd.linear.x = 0.0
self.get_logger().warn(
f'Obstacle at {front_min:.2f} m. EMERGENCY STOP.', throttle_duration_sec=1.0)
elif front_min < self.SLOWDOWN_DISTANCE_M:
# Linear ramp between SLOWDOWN and SAFE distances.
ratio = (front_min - self.SAFE_DISTANCE_M) / (
self.SLOWDOWN_DISTANCE_M - self.SAFE_DISTANCE_M)
cmd.linear.x = self.MAX_LINEAR_VEL * ratio
else:
cmd.linear.x = self.MAX_LINEAR_VEL
self.cmd_pub.publish(cmd)
def main(args=None):
rclpy.init(args=args)
node = SafetyWatchdog()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
The crucial bit is the QoS profile. In ROS 1, this would have been implicit. In ROS 2, you declare it. That single API choice is the reason industrial buyers can deploy ROS 2 on production lines: it forces you to think about reliability up front.
To build and run:
cd ~/ros2_ws
colcon build --packages-select safety_watchdog --symlink-install
source install/setup.bash
ros2 run safety_watchdog safety_node
In another terminal you can verify the QoS policy mismatch detection (one of ROS 2's killer features):
ros2 topic info /cmd_vel_safe --verbose
You'll see the publisher is set to RELIABLE_KEEP_LAST depth 10, while the subscription on a hypothetical unconfigured listener might be BEST_EFFORT, and the system will tell you explicitly: "incompatible QoS". Compare this to ROS 1, where the message would just silently fail to arrive and you'd spend a day wondering why.
This example is short but representative. The same shape — typed messages, declarative QoS, packaged nodes — is what you'll find in any real industrial ROS 2 deployment: a SLAM node publishes PointCloud2 on /map, a fleet manager subscribes to NavMsgs::Path and republishes Twist, an HMI listens to everything.
Common Pitfalls
Assuming DDS "just works" on a corporate network. Multicast often gets silently blocked or filtered by managed switches. Production deployments rarely run on the office network. Use Cyclone DDS with a unicast-configured discovery or run a separate VLAN with IGMP snooping configured. The
0_to_255IPv4 multicast range is your friend; study your switch's multicast routing.Copy-pasting QoS profiles blindly. Best-effort QoS is correct for raw lidar but wrong for
cmd_vel. Mixing them up is the #1 cause of "the messages arrive sometimes" bugs in ROS 2. Always think about what the producer can guarantee; chooseRELIABLEfor state andBEST_EFFORTfor streams.Forgetting that
colconbuild artifacts aren't reproducible by default. If you care about CI (you do), use--event-handlers console_direct+ --cmake-args -DCMAKE_BUILD_TYPE=Releaseand pin your package versions withrosdep. The 2025 ROS 2 build farm work has gotten much better, but only if you opt in.Building "monolithic" packages. A package should expose one cohesive capability. If your
my_company_bringuppackage has 50 nodes and 20 launch files, you've defeated the reuse point. Split nodes into independently importable packages. This is the difference between a fleet that can swap a perception vendor in a week and one that takes six months.
When to Use This (And When Not To)
Use ROS 2 when your robot has heterogeneous compute, when you'll have multiple nodes (anything beyond toy scale), when you need to integrate third-party perception or motion stacks, or when there's a regulatory path (ISO 13482, IEC 61508) the robot must clear. It scales beautifully from a $300 TurtleBot 4 to a 2-ton mining truck. The companies adopting it broadly span warehouse automation (Locus Robotics, Fetch), agriculture (Naio Technologies), surgical (Galen Robotics), autonomous trucking (Kodiak, Waymo Via logistics), and space (multiple NASA projects).
Don't use it when you're building a single-purpose embedded controller with no peer communication, when the latency budget is below 100 µs and never needs interprocess messaging, or when you genuinely need a hard real-time guarantee that DDS can't yet meet. For those cases, look at micro-ROS on bare-metal MCUs, or use a different real-time framework entirely and bridge into ROS 2 only at the boundary. Also consider whether you need sealed, vendor-certified binaries; some industrial applications (medical, aerospace) require a toolchain you control end-to-end, and even with Apex.AI's safety overlay, that's a heavy lift.
Wrapping Up
The biggest reason ROS 2 is the industrial standard isn't technology. It's that everyone else is using it. That's a network effect in the literal sense: the cost of deviating rises every quarter. Pick it unless you have a specific, articulable reason not to. Today's actionable step: install ros-jazzy-desktop and run the turtlesim example with ros2 run turtlesim turtlesim_node. In under twenty minutes you'll have a working distributed graph you can poke at with rqt_graph and ros2 topic echo. That's day zero.
A Closer Look at the DDS Landscape
To really appreciate why ROS 2 made the choices it did, it's worth understanding the DDS landscape it inherited. The Data Distribution Service standard was published by the Object Management Group (OMG) in the early 2000s, originally for defense and aerospace use cases. Ships, fighter jets, and missile guidance systems all needed a way to pass typed messages between subsystems from different vendors, with predictable behavior. DDS was their answer, and it's why the standard is so unusually thorough about QoS.
In ROS 2, you don't write directly to DDS. You write through rcl (the ROS Client Library), which delegates to an rmw (ROS Middleware) implementation, which talks to a vendor-specific DDS. Today, the main DDS implementations you can plug in are:
- Fast DDS — the default. Maintained by ZettaScale (formerly eProsima). Best documentation, broadest feature coverage, and the only one with full security (SROS2) support. This is the safe choice for most projects.
- Cyclone DDS — maintained by ZettaScale's Swedish subsidiary. Lighter weight, faster for low-latency use cases, and the preferred default in many embedded contexts. Eclipse IoT backs it.
- Connext DDS Micro — RTI's commercial implementation. Expensive, but certified for some SIL use cases and the best option if you need vendor support contracts. Used heavily in defense and medical robotics.
You can swap them by setting one environment variable:
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
ros2 run demo_nodes_cpp talker
Same node, different transport, no recompile. That indirection is one of the under-appreciated strengths of ROS 2. Your application code never depends on a specific DDS vendor.
There's also a deeper architecture lesson here: ROS 2 didn't try to compete with DDS, it tried to consolidate around it. The previous generation of robotics middleware each invented their own transport: Player/Stage used TCP, ROS 1 invented XML-RPC + custom UDP, LCM used UDP multicast but without the QoS richness. DDS already had a decade of real-world hardening by the time ROS 2 shipped. By making DDS the foundation, the ROS community inherited everything good about it: deterministic discovery, configurable reliability, and a thriving vendor ecosystem. They also inherited everything awkward about it: a complex configuration model, a bewildering set of QoS policies, and steep learning curves when things go wrong.
A Brief Look at ROS 2 Distribution Strategy
One last architectural choice worth understanding is the ROS 2 distribution model. Unlike most open-source projects, ROS 2 releases new versions on a strict annual cadence. In May 2024 we got Jazzy Jalisco; in May 2025 we got Kilted Kaiju (a non-LTS "rolling" release); and the next LTS after Jazzy will be R-Turtle (as of this writing) targeted for 2027. Non-LTS releases live for about a year and a half; LTS releases are supported for five years.
Why does this matter? It means production engineering teams can plan. If you adopt Jazzy today, you have support through 2029. That's the right length for a product roadmap. It also means you need to budget for periodic upgrades. Skipping a distribution is dangerous — between LTS versions, big architectural changes accumulate. The ROS 2 Iron → Jazzy jump, for example, changed the default discovery server behavior in ways that broke some dormant fleet configurations. The migration was easy if you did it incrementally; it was brutal if you skipped from Humble to Jazzy and tried to do it in one quarter.
In practice, this is one of the most-mature release processes in open source robotics. It's also a hint at the level of professional discipline the ROS 2 community has reached: the people shipping it have shipped commercial products and they know what reliability looks like. That's not nothing.
Further Reading
Hermes Smith
