ROS 2 Launch Files & Parameters
Launching one node at a time gets old fast. Learn to write Python launch files that start your entire robot stack with a single command, then use parameters to tune behavior without touching code.
Starting nodes one at a time is fine when you have two nodes. But a real robot might run a camera node, a LiDAR node, a localization node, a planner, and several controllers at the same time. Typing ros2 run ten times every time you want to test something gets old fast — and the first time you forget one node, your robot breaks in a mysterious and confusing way.
Launch files are ROS 2’s answer: a single script that brings up your entire stack with one command. Parameters are the knobs that let you tune behavior at runtime — changing a value in a config file instead of editing and recompiling code every time you want to adjust a speed limit or a sensor rate.
What You’ll Learn
- How to write a Python launch file that starts multiple nodes at once
- How to pass arguments to a launch file from the command line
- What parameters are and how to set them inline or from a YAML config file
- The
ros2 paramCLI commands for inspecting a running system - The common pitfalls that catch beginners
Why Launch Files Exist
When you wrote your first ROS 2 node, you ran it directly with ros2 run. That works for a single node, but it puts you in charge of remembering what to start, in what order, in which terminals, with which arguments. Forget one node and something breaks — often somewhere completely unrelated, because a missing publisher means another node’s subscriber never fires.
A launch file delegates that job to a script you check into your repository. Anyone (or any CI system) can then bring up your entire robot with one command:
ros2 launch my_robot my_robot.launch.py
Launch files also handle topic remapping, conditional node startup, loading config files, and including other launch files — so you can compose a large system from reusable pieces.
Your First Launch File
A ROS 2 Python launch file has one required ingredient: a function called generate_launch_description() that returns a LaunchDescription. Everything goes inside that LaunchDescription.
# launch/my_robot.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='my_robot',
executable='camera_node',
name='camera',
),
Node(
package='my_robot',
executable='drive_node',
name='drive',
),
])
Three things to notice:
- Each
Nodeentry needs apackage(where to find the executable), anexecutable(the name from yoursetup.py), and aname(what ROS 2 will call it on the network). - You can have as many
Nodeentries as you like — they all start roughly in parallel. - The file conventionally ends in
.launch.pyand lives in alaunch/directory inside your package.
Place the file at my_robot/launch/my_robot.launch.py, run a colcon build, then:
ros2 launch my_robot my_robot.launch.py
All nodes start in one terminal with their output interleaved. Hit Ctrl-C once and ROS 2 shuts them all down cleanly — you don’t have to hunt across multiple terminal windows.
How the Launch System Works
Here’s what happens from the moment you type that command to the moment your nodes are alive:
Adding Launch Arguments
A launch argument is a value you can override from the command line, with a sensible default baked in. Declare it with DeclareLaunchArgument and read it with LaunchConfiguration:
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
DeclareLaunchArgument(
'max_speed',
default_value='0.5',
description='Maximum drive speed in m/s'
),
Node(
package='my_robot',
executable='drive_node',
name='drive',
parameters=[{
'max_speed': LaunchConfiguration('max_speed'),
}]
),
])
Now you can tune at launch time:
# Use the default (0.5 m/s)
ros2 launch my_robot my_robot.launch.py
# Override for a faster test run
ros2 launch my_robot my_robot.launch.py max_speed:=1.0
This pattern — default in the launch file, overridable from the command line — lets you ship one launch file for all environments: slow for indoor testing, fast for outdoor trials, without touching a line of code.
What Are Parameters?
A parameter in ROS 2 is a named configuration value that belongs to a specific node. Think of it as a variable the node exposes so you can adjust it from outside. Unlike a constant baked into the source code, a parameter can be changed while the system is running (if the node supports it).
Common examples:
- Maximum speed or turn rate
- Sensor update frequencies
- PID gains (proportional, integral, derivative — see the PID tutorial)
- Safety thresholds and timeouts
Declaring and Reading Parameters in a Node
Before a node can use a parameter, it must declare it with a default value. Here’s a minimal drive node:
# drive_node.py
import rclpy
from rclpy.node import Node
class DriveNode(Node):
def __init__(self):
super().__init__('drive')
self.declare_parameter('max_speed', 0.5)
self.declare_parameter('max_turn_rate', 1.0)
speed = self.get_parameter('max_speed').value
turn = self.get_parameter('max_turn_rate').value
self.get_logger().info(f'Ready: max speed {speed} m/s, max turn {turn} rad/s')
def main():
rclpy.init()
rclpy.spin(DriveNode())
rclpy.shutdown()
declare_parameter('name', default) registers the parameter and provides a fallback if no value is supplied. get_parameter('name').value reads whatever value the launch system (or command line) provided.
Setting Parameters Inline
The simplest way to supply values is directly in the Node() call:
Node(
package='my_robot',
executable='drive_node',
name='drive',
parameters=[{
'max_speed': 0.5,
'max_turn_rate': 1.0,
'safe_stop_distance': 0.3,
}]
)
The parameters argument takes a list; each entry can be a dictionary (for inline values) or a file path (for YAML files — see below).
Loading Parameters from a YAML File
When you have many parameters, or want to share a config across multiple launch files, put them in a YAML file. The convention is a config/ directory inside your package:
# config/drive_params.yaml
drive: # must match the node's `name` in the launch file
ros__parameters: # always this exact key — two underscores
max_speed: 0.5
max_turn_rate: 1.0
safe_stop_distance: 0.3
Then load it in the launch file:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
config = os.path.join(
get_package_share_directory('my_robot'),
'config',
'drive_params.yaml'
)
return LaunchDescription([
Node(
package='my_robot',
executable='drive_node',
name='drive',
parameters=[config]
)
])
get_package_share_directory finds the installed location of your package (inside install/) — it’s the right way to reference files that travel with your package.
Inspecting Parameters at Runtime
While your robot is running, the ros2 param commands let you see and change parameters without restarting anything. This is invaluable during tuning:
# List all parameters on a node
ros2 param list /drive
# Read one value
ros2 param get /drive max_speed
# Change a value live (useful for tuning PID gains, speed limits, etc.)
ros2 param set /drive max_speed 0.8
# Dump all parameters to a YAML file you can save and reload
ros2 param dump /drive
The typical workflow: start with default values, run the robot, use ros2 param set to tweak until behavior is correct, then copy the values into your YAML config so they’re preserved for the next run.
Note:
ros2 param setonly changes the value in the current session. If you restart the node, it reloads from the launch file. Only values you save to YAML (and load at launch) persist across restarts.
Common Pitfalls
Forgetting to declare parameters in the node. If your launch file sets a parameter the node never declared, the value is silently ignored. Always call declare_parameter before get_parameter.
The ros__parameters double underscore. YAML parameter files require the key ros__parameters with two underscores. A single underscore won’t raise an error — it’ll just load nothing, and you’ll wonder why your values aren’t applied.
Node name mismatch. The top-level key in your YAML file must exactly match the name you give the node in Node(). If the YAML says drive_controller but the launch file says name='drive', the parameters go unloaded.
Launch file not installed. If you put your launch file in launch/ but don’t tell setup.py to install it, ros2 launch can’t find it. Add these lines to setup.py:
import os
from glob import glob
data_files=[
...
(os.path.join('share', package_name, 'launch'), glob('launch/*.py')),
(os.path.join('share', package_name, 'config'), glob('config/*.yaml')),
],
Then rebuild with colcon build.
Using a bare string for the config path. Don’t write parameters=['config/drive_params.yaml']. That’s a relative path and it will break the moment you run from a different directory. Always use get_package_share_directory to build an absolute path.
Recap
Launch files are the tool that turns a multi-node robot into a single command. Parameters are the tuning layer between hard-coded values and full configuration management. Together they make your robot software shareable, testable, and repeatable:
| What you want | How to do it |
|---|---|
| Start all nodes at once | ros2 launch pkg launch_file.launch.py |
| Override a value at launch | ros2 launch ... key:=value |
| Set a parameter inline | parameters=[{'key': value}] in Node() |
| Load many parameters from file | parameters=[config_path] where config_path is a .yaml |
| Inspect a running node’s parameters | ros2 param list /node_name |
| Tweak a parameter live | ros2 param set /node_name key value |
| Save current parameters | ros2 param dump /node_name |
If this is the first time you’ve touched launch files, the Nav2 bringup command from Week 18 — ros2 launch nav2_bringup tb3_simulation_launch.py — makes a lot more sense now: that one command starts dozens of nodes with hundreds of parameters, all defined in launch files exactly like the ones you just wrote.
Next week we go deeper into how nodes talk to each other: ROS 2 Services and Actions — the request/response and long-running-task patterns that complement the publish/subscribe topics you already know.