ROS 2 Introduction¶
1. What is ROS 2 and Why Does It Exist?¶
ROS 1 (Robot Operating System 1) was a groundbreaking framework for robot software development, but as the robotics industry matured, several fundamental limitations became apparent:
- No real-time support: ROS 1's TCP/UDP transport layer was not designed for real-time or safety-critical systems.
- Single-master architecture: The central
roscorewas a single point of failure. - Poor security: No built-in authentication, encryption, or access control.
- Limited multi-robot support: Running multiple robots required complex namespace workarounds.
- Weak cross-platform support: Primarily Linux-only.
- Python 2 legacy: ROS Noetic finally moved to Python 3, but the transition was slow.
ROS 2 was designed from the ground up to address these issues. It uses the DDS (Data Distribution Service) middleware standard — a proven pub/sub protocol used in aerospace, defense, and financial systems — as its communication backbone.
Key motivations for ROS 2:
| Goal | How ROS 2 Achieves It |
|---|---|
| Real-time control | DDS supports real-time QoS (Quality of Service) policies |
| Multi-robot | Decentralized discovery; no single master |
| Security | DDS-Security (SROS2) provides authentication and encryption |
| Embedded systems | Micro-ROS targets microcontrollers |
| Production-ready | Designed for commercial deployment, not just research |
| Cross-platform | Linux, macOS, Windows supported equally |
2. ROS 1 vs ROS 2 Comparison¶
| Feature | ROS 1 (Noetic) | ROS 2 (Humble+) |
|---|---|---|
| Middleware | Custom TCP/UDP (roscomm) | DDS (Data Distribution Service) |
| Discovery | Central roscore (single master) |
Decentralized (DDS discovery) |
| Real-time | Not supported | Supported via DDS QoS policies |
| Security | None built-in | DDS-Security (SROS2) |
| Multi-robot | Manual namespace tricks | Native support |
| Build system | catkin | colcon + ament (CMake / Python setuptools) |
| Launch files | XML (.launch) |
Python (.launch.py) |
| Language support | C++ (roscpp), Python (rospy) | C++ (rclcpp), Python (rclpy) |
| Parameters | Global parameter server | Per-node parameters |
| Action servers | actionlib |
Built-in rclpy/rclcpp actions |
| Lifecycle | None | Managed node lifecycle (LifecycleNode) |
| Message IDL | .msg files |
Same .msg + .srv + .action files |
| Platform | Linux (primarily) | Linux, macOS, Windows |
| DDS vendor | N/A | Fast DDS, Cyclone DDS, Connext, etc. |
3. ROS 2 Architecture¶
ROS 2 uses a layered architecture that abstracts the middleware via an RMW (ROS Middleware Interface).
+-------------------------------------------------------+
| User Application |
| (your ROS 2 nodes, launch files) |
+-------------------------------------------------------+
| RCL (ROS Client Library) |
| rclcpp (C++) / rclpy (Python) |
+-------------------------------------------------------+
| RMW (ROS Middleware Interface) |
| rmw_fastrtps / rmw_cyclonedds / rmw_connext |
+-------------------------------------------------------+
| DDS Implementation |
| eProsima Fast DDS / Eclipse Cyclone DDS |
+-------------------------------------------------------+
| OS Network Stack (UDP/IP) |
+-------------------------------------------------------+
Layer breakdown:
- Application layer: Your nodes, topics, services, actions.
- RCL (ROS Client Library): Language-specific APIs.
rclcppfor C++,rclpyfor Python. Provides the familiar ROS API (Node,Publisher,Subscriber, etc.). - RMW (ROS Middleware Interface): An abstraction layer that allows swapping DDS vendors without changing application code. You choose
rmw_fastrtps(default),rmw_cyclonedds, or others. - DDS layer: Handles actual pub/sub transport, discovery, serialization, QoS, and (optionally) security.
DDS Quality of Service (QoS)¶
QoS policies let you tune communication behavior:
+---------------------+------------------------------------------+
| Policy | Options |
+---------------------+------------------------------------------+
| Reliability | RELIABLE / BEST_EFFORT |
| Durability | TRANSIENT_LOCAL / VOLATILE |
| History | KEEP_LAST(n) / KEEP_ALL |
| Deadline | (period) |
| Lifespan | (duration) |
| Liveliness | AUTOMATIC / MANUAL_BY_TOPIC |
+---------------------+------------------------------------------+
4. Core Concepts¶
4.1 Node (节点)¶
A node is the fundamental unit of computation. Each node performs a single, well-defined function (e.g., camera driver, path planner).
import rclpy
from rclpy.node import Node
class MyNode(Node):
def __init__(self):
super().__init__('my_node')
self.get_logger().info('Hello from ROS 2!')
4.2 Topic (话题)¶
Topics are named buses for asynchronous publish/subscribe communication. Multiple publishers and subscribers can connect to the same topic.
+----------+ /camera/image +------------+
| Publisher | -------> Topic -------> | Subscriber |
| (camera) | | (detector) |
+----------+ +------------+
|
+------------+
| Subscriber |
| (display) |
+------------+
4.3 Service (服务)¶
Services are synchronous request/response communication (like RPC). A client sends a request and blocks until a response arrives.
+----------+ request +----------+ response
| Client | ------------> | Server | ----------->
+----------+ +----------+
4.4 Action (动作)¶
Actions combine a goal, feedback, and result. They are suitable for long-running tasks (e.g., "navigate to waypoint") with periodic progress updates.
+----------+ goal +----------+ feedback (periodic)
| Client | --------> | Server | ------------------->
+----------+ +----------+
^ |
| result |
+----------------------+
4.5 Parameter (参数)¶
Parameters are node-level configuration values (integers, strings, booleans, etc.) that can be queried or set at runtime.
4.6 Launch (启动)¶
Launch files start multiple nodes with configurable arguments, remappings, and conditions. ROS 2 uses Python-based launch files (.launch.py).
5. ROS 2 CLI Commands vs ROS 1¶
| Task | ROS 1 | ROS 2 |
|---|---|---|
| Run a node | rosrun pkg node |
ros2 run pkg node |
| List nodes | rosnode list |
ros2 node list |
| List topics | rostopic list |
ros2 topic list |
| Echo a topic | rostopic echo /topic |
ros2 topic echo /topic |
| Publish to topic | rostopic pub /topic ... |
ros2 topic pub /topic ... |
| Inspect message type | rosmsg show MsgType |
ros2 interface show MsgType |
| Call a service | rosservice call /srv ... |
ros2 service call /srv ... |
| List parameters | rosparam list |
ros2 param list |
| Get parameter | rosparam get /p |
ros2 param get /node p |
| List packages | rospack list |
ros2 pkg list |
| Create package | catkin_create_pkg |
ros2 pkg create --build-type ament_python |
| Build workspace | catkin_make |
colcon build |
| Launch file | roslaunch pkg file.launch |
ros2 launch pkg file.launch.py |
| Record bag | rosbag record -a |
ros2 bag record -a |
| Play bag | rosbag play file.bag |
ros2 bag play file/ |
6. ROS 2 Distributions¶
| Distribution | Ubuntu | EOL (End of Life) | Notes |
|---|---|---|---|
| Humble Hawksbill | 22.04 | May 2027 | LTS; most widely used as of 2025 |
| Iron Irwini | 22.04 | Nov 2024 | Non-LTS; already EOL |
| Jazzy Jalisco | 24.04 | May 2029 | LTS; latest stable |
| Rolling | Latest | N/A | Continuous delivery; always up-to-date |
Recommendation: Use Humble (if on Ubuntu 22.04) or Jazzy (if on Ubuntu 24.04) for production. Use Rolling if you want the latest features and can tolerate occasional breakage.
7. Writing a Simple Publisher/Subscriber in ROS 2 (Python)¶
7.1 Creating a Package¶
# Source ROS 2
source /opt/ros/humble/setup.bash
# Create a workspace
mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src
# Create a Python package
ros2 pkg create --build-type ament_python my_pubsub --dependencies rclpy std_msgs
7.2 Publisher (my_publisher.py)¶
Create file my_pubsub/my_pubsub/my_publisher.py:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalPublisher(Node):
"""A minimal ROS 2 publisher that sends 'Hello World' messages."""
def __init__(self):
super().__init__('minimal_publisher')
# Create a publisher on topic 'chatter' with queue size 10
self.publisher_ = self.create_publisher(String, 'chatter', 10)
# Create a timer that calls publish_callback every 0.5 seconds
self.timer = self.create_timer(0.5, self.publish_callback)
self.count = 0
def publish_callback(self):
msg = String()
msg.data = f'Hello World: {self.count}'
self.publisher_.publish(msg)
self.get_logger().info(f'Publishing: "{msg.data}"')
self.count += 1
def main(args=None):
rclpy.init(args=args)
node = MinimalPublisher()
try:
rclpy.spin(node) # Keep the node alive
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
7.3 Subscriber (my_subscriber.py)¶
Create file my_pubsub/my_pubsub/my_subscriber.py:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalSubscriber(Node):
"""A minimal ROS 2 subscriber that listens to 'chatter' topic."""
def __init__(self):
super().__init__('minimal_subscriber')
# Subscribe to 'chatter' topic, call listener_callback on each message
self.subscription = self.create_subscription(
String,
'chatter',
self.listener_callback,
10 # QoS queue depth
)
def listener_callback(self, msg: String):
self.get_logger().info(f'I heard: "{msg.data}"')
def main(args=None):
rclpy.init(args=args)
node = MinimalSubscriber()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
7.4 Register Entry Points¶
Edit setup.py to add console script entry points:
entry_points={
'console_scripts': [
'talker = my_pubsub.my_publisher:main',
'listener = my_pubsub.my_subscriber:main',
],
},
7.5 Build and Run¶
cd ~/ros2_ws
colcon build --packages-select my_pubsub
source install/setup.bash
# Terminal 1: run publisher
ros2 run my_pubsub talker
# Terminal 2: run subscriber
ros2 run my_pubsub listener
Expected output:
# Terminal 1 (talker)
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
...
# Terminal 2 (listener)
[INFO] [minimal_subscriber]: I heard: "Hello World: 0"
[INFO] [minimal_subscriber]: I heard: "Hello World: 1"
...
8. Launch Files in ROS 2 (Python-based)¶
ROS 2 launch files are written in Python. Create my_pubsub/launch/demo.launch.py:
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='my_pubsub',
executable='talker',
name='my_talker',
output='screen',
parameters=[], # list of param YAML files or dicts
remappings=[], # topic remappings
),
Node(
package='my_pubsub',
executable='listener',
name='my_listener',
output='screen',
),
])
Run the launch file:
Advanced Launch Features¶
ROS 2 launch supports:
- Arguments:
DeclareLaunchArgument('robot_name', default_value='robot1') - Conditions:
IfCondition(LaunchConfiguration('use_camera')) - Substitutions:
${}style variable expansion - Including other launch files:
IncludeLaunchDescription(...) - Group actions:
GroupAction([...])with namespace/remapping
9. Migration Tips from ROS 1¶
If you are migrating existing ROS 1 code to ROS 2, here are practical guidelines:
-
Message types are mostly the same:
.msg,.srv,.actionfiles have the same syntax. Package names may differ (e.g.,geometry_msgsworks in both). -
Replace
rospywithrclpy: rospy.init_node()→rclpy.init()+Node('name')rospy.Publisher()→node.create_publisher()rospy.Subscriber()→node.create_subscription()-
rospy.spin()→rclpy.spin(node) -
Replace
rosrunwithros2 runandroslaunchwithros2 launch. -
Replace catkin with colcon:
-
Port launch files: Convert XML
.launchto Python.launch.py. Tools likeros2 launchsupport both, but Python is the standard. -
Parameters: ROS 2 parameters are per-node (no global parameter server). Use
ros2 paramCLI or declare parameters in code withself.declare_parameter('name', default_value). -
Logging: Replace
rospy.loginfo()withself.get_logger().info(). -
Time: Replace
rospy.Time.now()withnode.get_clock().now(). -
Use
ros1_bridge: For mixed ROS 1 / ROS 2 systems during transition, theros1_bridgepackage bridges topics and services. -
Incremental migration: You don't have to port everything at once. Use
ros1_bridgeto connect old and new nodes.