ROS 2 is an open-source middleware framework that gives robot software a standard way to exchange data, schedule computation, and manage hardware drivers. It is not an operating system. It runs on top of Linux, Windows, or macOS and provides the communication layer, tooling, and libraries that connect a LiDAR, an IMU, a motion planner, and a motor controller into one coherent system. The hook is simple: you write small, isolated programs, and ROS 2 routes typed messages between them over a real-time-capable transport.
Quick Takeaways
- ROS 2 replaces the single-master design of ROS 1 with a decentralized DDS (Data Distribution Service) middleware, removing the single point of failure.
- Communication uses four primitives:
topics(streams),services(request/response),actions(long-running goals), andparameters(runtime configuration). - Quality of Service (QoS) profiles let you tune reliability and latency per connection, which matters on lossy Wi-Fi links and 1 kHz control loops.
- The recommended long-term support (LTS) distributions as of late 2026 are Jazzy Jalisco (Ubuntu 24.04, supported to May 2029) and the newer Kilted Kaiju non-LTS release. Verify the current LTS on the official ROS 2 release page before starting a new project.
| Feature | ROS 1 | ROS 2 |
|---|---|---|
| Architecture | Central roscore master |
Decentralized DDS discovery |
| Real-time support | Limited | Designed for it (with RT kernel) |
| QoS control | None (TCP/UDP only) | Per-endpoint QoS profiles |
| Platforms | Linux primarily | Linux, Windows, macOS |
| Multi-robot | Awkward | Native via DDS domains |
| Languages | Python 2/3, C++ | Python 3, C++ (rclpy, rclcpp), others |
| Build system | catkin | colcon + ament |
| Status | End of life (Noetic EOL May 2025) | Actively developed |
Why ROS 2 Exists
ROS 1 grew out of research labs around 2007. It worked well for prototypes. Three limitations blocked industrial adoption:
- Single master. If
roscoredied, node discovery died with it. - No real-time path. Message passing relied on TCP-based
TCPROS, with no deadline guarantees. - Weak security. No authentication or encryption was built in.
ROS 2 addresses each. DDS provides peer-to-peer discovery. Executors and lock-free paths enable deterministic timing. SROS 2 adds authentication, access control, and encrypted transport.
Core Architecture of ROS 2
The Layered Stack
Application layer -> your nodes (Python / C++)
Client libraries -> rclpy, rclcpp
ROS Client Library -> rcl (C API)
Middleware iface -> rmw (swappable)
DDS implementation -> Fast DDS, Cyclone DDS, Connext
Transport -> UDP, shared memory, TCP
The rmw abstraction lets you swap DDS vendors without changing application code. Set the vendor with an environment variable:
# Switch to Cyclone DDS for this shell session
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
Nodes
A node is one process (or component) with a single responsibility: read a LiDAR, estimate pose, or drive a BLDC motor controller. Small nodes are testable and replaceable.
Topics
A topic is a named, typed, many-to-many data stream. A publisher sends sensor_msgs/msg/LaserScan on /scan. Any number of subscribers receive it. Topics suit continuous data such as sensor streams at 10 to 1000 Hz.
Services and Actions
- Services are synchronous-style request/response calls, such as
/reset_odometry. - Actions handle long-running goals with feedback and cancellation, such as
NavigateToPosefor a mobile base.
Parameters
Parameters are per-node configuration values (PID gains, frame IDs, speed limits) that you can change at runtime without recompiling.
Comparison of Communication Primitives
| Primitive | Pattern | Typical Rate | Use Case | Blocking |
|---|---|---|---|---|
| Topic | Pub/Sub | 10 to 1000 Hz | Sensor data, velocity commands | No |
| Service | Request/Reply | On demand | Reset, calibrate, toggle | Caller waits |
| Action | Goal/Feedback/Result | On demand | Navigation, arm trajectories | No (async) |
| Parameter | Key/Value | On change | Gains, thresholds | No |
Quality of Service (QoS): The Feature Beginners Miss
QoS profiles decide how DDS delivers each message. A publisher and subscriber must have compatible profiles, or no data flows. This is the most common beginner bug.
| QoS Policy | Options | Effect |
|---|---|---|
| Reliability | RELIABLE / BEST_EFFORT |
Retransmit lost packets vs. drop them |
| Durability | VOLATILE / TRANSIENT_LOCAL |
Late joiners get last message or not |
| History | KEEP_LAST(n) / KEEP_ALL |
Queue depth |
| Deadline | Duration | Alert if no message within the period |
Rule of thumb: use BEST_EFFORT with shallow depth for high-rate camera and LiDAR streams. Use RELIABLE for commands and configuration. ROS 2 ships qos_profile_sensor_data for the first case.
Installing ROS 2
On Ubuntu 24.04, install Jazzy Jalisco:
# Add the ROS 2 apt repository (follow the official docs for key setup)
sudo apt update && sudo apt install -y ros-jazzy-desktop
# Source the environment in every new shell
echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc
source ~/.bashrc
# Verify: run the demo talker in one terminal...
ros2 run demo_nodes_cpp talker
# ...and the listener in another
ros2 run demo_nodes_py listener
If the listener prints the talker’s messages, DDS discovery works.
Your First ROS 2 Node: Publisher and Subscriber
Create a Workspace and Package
mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python robot_basics \
--dependencies rclpy std_msgs
Publisher Node (Python)
# robot_basics/robot_basics/speed_publisher.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32
class SpeedPublisher(Node):
def __init__(self):
super().__init__('speed_publisher')
# Topic '/wheel_speed', queue depth 10 (KEEP_LAST(10))
self.pub = self.create_publisher(Float32, 'wheel_speed', 10)
# Timer fires at 10 Hz (period = 0.1 s)
self.timer = self.create_timer(0.1, self.tick)
self.speed = 0.0
def tick(self):
msg = Float32()
msg.data = self.speed # field 'data': speed in m/s
self.pub.publish(msg)
self.speed += 0.01 # ramp speed upward
def main():
rclpy.init()
node = SpeedPublisher()
rclpy.spin(node) # run the executor until shutdown
node.destroy_node()
rclpy.shutdown()
Subscriber Node (Python)
# robot_basics/robot_basics/speed_subscriber.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32
class SpeedSubscriber(Node):
def __init__(self):
super().__init__('speed_subscriber')
# Callback runs once per received message
self.sub = self.create_subscription(
Float32, 'wheel_speed', self.on_speed, 10)
def on_speed(self, msg: Float32):
self.get_logger().info(f'Wheel speed: {msg.data:.2f} m/s')
def main():
rclpy.init()
rclpy.spin(SpeedSubscriber())
rclpy.shutdown()
Register both in setup.py under entry_points:
entry_points={
'console_scripts': [
'speed_pub = robot_basics.speed_publisher:main',
'speed_sub = robot_basics.speed_subscriber:main',
],
},
Build and run:
cd ~/ros2_ws
colcon build --packages-select robot_basics
source install/setup.bash
ros2 run robot_basics speed_pub # terminal 1
ros2 run robot_basics speed_sub # terminal 2
Inspect the Graph
ros2 node list # active nodes
ros2 topic list -t # topics with message types
ros2 topic hz /wheel_speed # measure actual publish rate
ros2 topic echo /wheel_speed # print live messages
rqt_graph # visualize node/topic connections
Launch Files: Starting Multiple Nodes
A launch file starts and configures many nodes at once. ROS 2 launch files are Python scripts.
# launch/basics.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(package='robot_basics', executable='speed_pub',
name='speed_publisher'),
Node(package='robot_basics', executable='speed_sub',
name='speed_subscriber',
output='screen'), # print logs to the terminal
])
Control Loop Foundations in ROS 2
Most robots run a feedback loop between topics. A discrete PID controller for wheel velocity is the standard starting point.
e[k] = r[k] - y[k]
u[k] = Kp * e[k] + Ki * T * sum(e[0..k]) + Kd * (e[k] - e[k-1]) / T
Where:
r= setpoint (target velocity)y= measured velocity from the encoderT= sample period (0.01 s at 100 Hz)Kp, Ki, Kd= tuning gains
PID Node Skeleton
class PidNode(Node):
def __init__(self):
super().__init__('pid_node')
# Gains exposed as parameters, tunable at runtime
self.declare_parameter('kp', 1.2)
self.declare_parameter('ki', 0.4)
self.declare_parameter('kd', 0.05)
self.T = 0.01 # 100 Hz loop
self.integral = 0.0
self.prev_err = 0.0
self.setpoint = 0.0
self.measured = 0.0
self.cmd_pub = self.create_publisher(Float32, 'motor_cmd', 10)
self.create_subscription(Float32, 'wheel_speed', self.on_meas, 10)
self.create_timer(self.T, self.update)
def on_meas(self, msg):
self.measured = msg.data
def update(self):
kp = self.get_parameter('kp').value
ki = self.get_parameter('ki').value
kd = self.get_parameter('kd').value
err = self.setpoint - self.measured
self.integral += err * self.T
self.integral = max(min(self.integral, 5.0), -5.0) # anti-windup clamp
deriv = (err - self.prev_err) / self.T
u = kp * err + ki * self.integral + kd * deriv
self.prev_err = err
out = Float32(); out.data = float(max(min(u, 1.0), -1.0)) # clamp to +/-1
self.cmd_pub.publish(out)
Tune live without restarting:
ros2 param set /pid_node kp 1.8
A caveat: Python timers jitter by milliseconds under load. For loops above roughly 200 Hz or with hard deadlines, use rclcpp and the ros2_control framework, which runs a real-time controller manager.
Essential Tools and Ecosystem
| Tool | Purpose |
|---|---|
| colcon | Build system for workspaces |
| rviz2 | 3D visualization of sensors, TF, maps |
| rqt | GUI plugins (graph, plots, parameters) |
| ros2 bag | Record and replay topic data |
| Gazebo | Physics simulation |
| Nav2 | Navigation stack (planning, costmaps, behavior trees) |
| MoveIt 2 | Manipulator motion planning |
| ros2_control | Hardware abstraction and real-time controllers |
| micro-ROS | ROS 2 on microcontrollers (e.g., STM32, ESP32) |
| tf2 | Coordinate frame transforms |
Coordinate Frames with tf2
Robots need consistent frames: map -> odom -> base_link -> laser. A point seen by the LiDAR transforms to the world frame through:
p_map = T_map_odom * T_odom_base * T_base_laser * p_laser
Each T is a 4×4 homogeneous transform. tf2 maintains and queries this tree for you.
Real-World Workflow: Bringing Up a Differential-Drive Robot
A practical sequence for a small mobile robot:
- Describe the robot. Write a URDF or Xacro file with links, joints, and sensor frames. Check it in
rviz2. - Wire hardware. Connect two BLDC or geared DC motors with encoders to a motor driver. Connect the IMU over I2C (400 kHz) and a LiDAR over USB or UART.
- Write or install drivers. Many sensors ship ROS 2 drivers. Otherwise, wrap the vendor SDK in a node that publishes
sensor_msgs. - Configure
ros2_control. Use thediff_drive_controller, which converts/cmd_vel(geometry_msgs/Twist) into wheel velocities and publishes odometry. - Fuse sensors. Run
robot_localization(an EKF) to merge wheel odometry and IMU data. - Map and navigate. Run
slam_toolboxto build a map, then launch Nav2 with that map. - Record and test. Capture runs with
ros2 bag record -aand replay them to debug offline.
Differential-drive kinematics, which the controller implements:
v = (v_r + v_l) / 2 # linear velocity (m/s)
omega = (v_r - v_l) / L # angular velocity (rad/s), L = wheel separation
v_r = v + omega * L / 2
v_l = v - omega * L / 2
Common Beginner Mistakes
- Forgetting to source
install/setup.bashafter each build. - QoS mismatch between a
RELIABLEsubscriber and aBEST_EFFORTpublisher. - Mismatched
ROS_DOMAIN_IDbetween machines (set the same value, 0 to 101, to share a graph). - Blocking inside callbacks, which stalls the executor. Offload slow work to threads or use a
MultiThreadedExecutor. - Ignoring
use_sim_timewhen replaying bags or running Gazebo.
Is ROS 2 Right for Your Project?
| Scenario | Recommendation |
|---|---|
| Hobby rover, learning | ROS 2 with Python, simulation first |
| Research arm with MoveIt | ROS 2, C++ for latency-critical parts |
| Commercial AMR fleet | ROS 2 + Nav2, DDS tuning, SROS 2 |
| Tiny single-MCU robot | Skip ROS; consider micro-ROS only if networking is needed |
FAQ
Is ROS 2 an operating system?
No. ROS 2 is middleware and a toolset that runs on Ubuntu, Windows, or macOS. It supplies communication, drivers, and libraries, while the host OS manages processes and hardware.
Should I learn ROS 1 or ROS 2 in 2026?
Learn ROS 2. ROS 1 Noetic reached end of life in May 2025, and active development, new drivers, and Nav2 and MoveIt 2 improvements target ROS 2.
Which programming language should I use for ROS 2?
Use Python (rclpy) for prototyping, scripting, and high-level logic. Use C++ (rclcpp) for latency-sensitive nodes, real-time controllers, and heavy perception pipelines.
Can ROS 2 run in real time?
Yes, with conditions. You need a real-time kernel (PREEMPT_RT), a C++ node design that avoids dynamic allocation in the control loop, and an appropriate executor. Standard Python nodes on stock Linux are not hard real-time.




