Section 3: Parameters & Launch Files
Introduction
So far we've built individual nodes that communicate via topics, services, and actions. Real robotics applications require:
- Runtime configuration: Adjust robot speed, sensor thresholds, algorithm parameters without recompiling
- Multi-node orchestration: Launch 10+ nodes simultaneously with correct dependencies and namespaces
- Environment management: Different configs for simulation vs hardware, development vs production
ROS 2 provides two mechanisms for this:
- Parameters: Runtime configuration values (robot speed, sensor topic names, PID gains)
- Launch files: Python scripts that start multiple nodes with coordinated configuration
What are Parameters?
A parameter is a named value associated with a node that can be:
- Declared in node code with default values
- Set from command line, launch files, or parameter files
- Changed at runtime without restarting the node
Analogy: Parameters are like configuration settings in an app:
# Traditional config file
config = {
'max_speed': 1.0,
'sensor_topic': '/camera/image',
'enable_debug': False
}
# ROS 2 parameters (similar concept, but dynamic)
self.declare_parameter('max_speed', 1.0)
self.declare_parameter('sensor_topic', '/camera/image')
self.declare_parameter('enable_debug', False)
Parameter Types
ROS 2 supports these parameter types:
bool- True/False flagsint- Integer valuesdouble- Floating-point numbersstring- Text valuesbyte_array- Binary databool_array,int_array,double_array,string_array- Arrays of values
Node with Parameters Example
Let's create a configurable velocity publisher:
# File: configurable_velocity.py
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
class ConfigurableVelocity(Node):
def __init__(self):
super().__init__('configurable_velocity')
# Declare parameters with default values
self.declare_parameter('linear_speed', 0.5) # m/s
self.declare_parameter('angular_speed', 0.3) # rad/s
self.declare_parameter('publish_rate', 10.0) # Hz
self.declare_parameter('topic_name', '/cmd_vel')
self.declare_parameter('enable_rotation', True)
# Get parameter values
linear = self.get_parameter('linear_speed').value
angular = self.get_parameter('angular_speed').value
rate = self.get_parameter('publish_rate').value
topic = self.get_parameter('topic_name').value
enable_rot = self.get_parameter('enable_rotation').value
# Create publisher with configured topic
self.publisher = self.create_publisher(Twist, topic, 10)
# Create timer with configured rate
timer_period = 1.0 / rate
self.timer = self.create_timer(timer_period, self.timer_callback)
# Store configuration
self.linear_speed = linear
self.angular_speed = angular if enable_rot else 0.0
self.get_logger().info(f'Started with: linear={linear}, angular={angular}, rate={rate}')
def timer_callback(self):
msg = Twist()
msg.linear.x = self.linear_speed
msg.angular.z = self.angular_speed
self.publisher.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = ConfigurableVelocity()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Key concepts:
declare_parameter(name, default_value)- Register parameter with fallbackget_parameter(name).value- Retrieve current value- Parameters are read during
__init__(), so changes require node restart (for this example)
Running with custom parameters:
# Use defaults
python3 configurable_velocity.py
# Override parameters from command line
ros2 run my_package configurable_velocity --ros-args \
-p linear_speed:=1.0 \
-p angular_speed:=0.5 \
-p publish_rate:=20.0 \
-p enable_rotation:=false
Parameter Introspection with CLI
While node is running, use these commands:
# List all nodes
ros2 node list
# List parameters for a node
ros2 param list /configurable_velocity
# Output:
# enable_rotation
# angular_speed
# linear_speed
# publish_rate
# topic_name
# Get parameter value
ros2 param get /configurable_velocity linear_speed
# Output: Double value is: 0.5
# Set parameter at runtime (if node supports dynamic updates)
ros2 param set /configurable_velocity linear_speed 1.0
# Output: Set parameter successful
# Dump all parameters to file
ros2 param dump /configurable_velocity > my_params.yaml
Parameter Files (YAML)
Instead of passing parameters on command line, use YAML files:
# File: robot_config.yaml
configurable_velocity:
ros__parameters:
linear_speed: 0.8
angular_speed: 0.4
publish_rate: 15.0
topic_name: '/mobile_base/cmd_vel'
enable_rotation: true
Load parameters from file:
ros2 run my_package configurable_velocity --ros-args \
--params-file robot_config.yaml
YAML structure:
- Top level: Node name (
configurable_velocity) - Second level:
ros__parameters(double underscore) - Third level: Parameter names and values
Dynamic Parameter Updates
To allow runtime parameter changes without restarting, add a parameter callback:
from rcl_interfaces.msg import ParameterDescriptor, SetParametersResult
class DynamicConfigNode(Node):
def __init__(self):
super().__init__('dynamic_config_node')
# Declare parameter with descriptor (optional constraints)
speed_descriptor = ParameterDescriptor(
description='Maximum linear speed in m/s',
floating_point_range=[
{'from_value': 0.0, 'to_value': 2.0, 'step': 0.1}
]
)
self.declare_parameter('max_speed', 0.5, speed_descriptor)
# Register callback for parameter changes
self.add_on_set_parameters_callback(self.parameters_callback)
self.max_speed = self.get_parameter('max_speed').value
self.get_logger().info(f'Initial max_speed: {self.max_speed}')
def parameters_callback(self, params):
"""Called when parameters are set at runtime."""
for param in params:
if param.name == 'max_speed':
# Validate value
if 0.0 <= param.value <= 2.0:
self.max_speed = param.value
self.get_logger().info(f'Updated max_speed to {param.value}')
return SetParametersResult(successful=True)
else:
self.get_logger().error(f'Invalid speed: {param.value}')
return SetParametersResult(successful=False)
return SetParametersResult(successful=True)
Benefits:
- Change parameters without restarting node
- Validate new values before applying
- Log configuration changes
Testing dynamic updates:
# Terminal 1: Run node
python3 dynamic_config_node.py
# Terminal 2: Change parameter
ros2 param set /dynamic_config_node max_speed 1.2
# Node logs: "Updated max_speed to 1.2"
# Try invalid value
ros2 param set /dynamic_config_node max_speed 3.0
# Node logs: "Invalid speed: 3.0"
# Output: Setting parameter failed
What are Launch Files?
A launch file is a Python script that:
- Starts multiple nodes simultaneously
- Sets parameters for each node
- Configures remappings (redirect topic names)
- Manages node lifecycle (start order, restart on crash)
- Loads parameter files
Why launch files?
- Manually starting 15 nodes is error-prone
- Ensures correct startup order (e.g., sensor driver before SLAM)
- Consistent configuration across team members
Basic Launch File Example
Let's create a launch file that starts a publisher and subscriber together:
# File: simple_demo.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
# Launch publisher node
Node(
package='demo_nodes_cpp',
executable='talker',
name='my_publisher',
output='screen' # Print logs to terminal
),
# Launch subscriber node
Node(
package='demo_nodes_py',
executable='listener',
name='my_subscriber',
output='screen'
),
])
Running the launch file:
ros2 launch my_package simple_demo.launch.py
Output: Both nodes start, talker publishes, listener receives messages
Launch File with Parameters
Let's launch our configurable velocity node with custom parameters:
# File: velocity_launch.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='my_package',
executable='configurable_velocity',
name='robot_velocity_controller',
output='screen',
parameters=[{
'linear_speed': 1.0,
'angular_speed': 0.5,
'publish_rate': 20.0,
'topic_name': '/robot/cmd_vel',
'enable_rotation': True
}]
),
])
Or load from YAML 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():
# Get path to parameter file
config_dir = get_package_share_directory('my_package')
params_file = os.path.join(config_dir, 'config', 'robot_config.yaml')
return LaunchDescription([
Node(
package='my_package',
executable='configurable_velocity',
name='robot_velocity_controller',
output='screen',
parameters=[params_file] # Load from YAML
),
])
Multi-Node Launch File Example
Let's launch a complete robot system with multiple nodes:
# File: robot_system.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument, LogInfo
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
# Declare launch arguments (command-line overrides)
use_sim_time_arg = DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation time if true'
)
robot_name_arg = DeclareLaunchArgument(
'robot_name',
default_value='robot1',
description='Robot namespace'
)
# Get launch argument values
use_sim_time = LaunchConfiguration('use_sim_time')
robot_name = LaunchConfiguration('robot_name')
return LaunchDescription([
# Declare arguments
use_sim_time_arg,
robot_name_arg,
# Log launch info
LogInfo(msg=['Launching robot system for: ', robot_name]),
# 1. Sensor driver node
Node(
package='sensor_driver',
executable='lidar_node',
name='lidar_driver',
namespace=robot_name,
parameters=[{
'use_sim_time': use_sim_time,
'scan_topic': '/scan',
'frame_id': 'lidar_link'
}],
output='screen'
),
# 2. Localization node
Node(
package='localization',
executable='amcl',
name='localizer',
namespace=robot_name,
parameters=[{
'use_sim_time': use_sim_time,
'odom_topic': '/odom',
'scan_topic': '/scan'
}],
output='screen'
),
# 3. Navigation planner
Node(
package='navigation',
executable='path_planner',
name='planner',
namespace=robot_name,
parameters=[{
'use_sim_time': use_sim_time,
'max_speed': 1.0,
'safety_distance': 0.3
}],
output='screen'
),
# 4. Velocity controller
Node(
package='control',
executable='velocity_controller',
name='controller',
namespace=robot_name,
parameters=[{
'use_sim_time': use_sim_time,
'cmd_vel_topic': '/cmd_vel'
}],
remappings=[
('/cmd_vel', '/mobile_base/cmd_vel') # Remap topic
],
output='screen'
),
])
Running with custom arguments:
# Use defaults
ros2 launch my_package robot_system.launch.py
# Override arguments
ros2 launch my_package robot_system.launch.py \
use_sim_time:=true \
robot_name:=robot2
Key features:
- DeclareLaunchArgument: Command-line parameters for launch file
- namespace: Prefix all node names and topics (multi-robot support)
- remappings: Redirect topic names (e.g.,
/cmd_vel→/mobile_base/cmd_vel) - output='screen': Show node logs in terminal
Launch File with Conditionals
Start nodes conditionally based on arguments:
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch.conditions import IfCondition
def generate_launch_description():
use_rviz_arg = DeclareLaunchArgument(
'use_rviz',
default_value='true',
description='Launch RViz for visualization'
)
use_rviz = LaunchConfiguration('use_rviz')
return LaunchDescription([
use_rviz_arg,
# Always launch robot nodes
Node(
package='my_package',
executable='robot_controller',
name='controller',
output='screen'
),
# Conditionally launch RViz
Node(
package='rviz2',
executable='rviz2',
name='rviz',
condition=IfCondition(use_rviz), # Only if use_rviz=true
output='screen'
),
])
Usage:
# Launch with RViz
ros2 launch my_package robot.launch.py use_rviz:=true
# Launch without RViz
ros2 launch my_package robot.launch.py use_rviz:=false
Including Other Launch Files
Modular launch files can include others:
# File: simulation.launch.py
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
# Get paths to other launch files
gazebo_pkg = get_package_share_directory('gazebo_ros')
robot_pkg = get_package_share_directory('my_robot_description')
gazebo_launch = os.path.join(gazebo_pkg, 'launch', 'gazebo.launch.py')
robot_launch = os.path.join(robot_pkg, 'launch', 'robot_state_publisher.launch.py')
return LaunchDescription([
# Include Gazebo simulator launch file
IncludeLaunchDescription(
PythonLaunchDescriptionSource(gazebo_launch),
launch_arguments={'world': 'empty_world'}.items()
),
# Include robot description launch file
IncludeLaunchDescription(
PythonLaunchDescriptionSource(robot_launch),
launch_arguments={'use_sim_time': 'true'}.items()
),
])
Benefits:
- Reuse existing launch files
- Compose complex systems from smaller modules
- Override included launch file arguments
Launch File Introspection
# List all launch files in a package
ros2 launch my_package <TAB>
# See launch file arguments
ros2 launch my_package robot_system.launch.py --show-args
# Output:
# Arguments (pass arguments as '<name>:=<value>'):
# 'use_sim_time':
# Use simulation time if true
# (default: 'false')
# 'robot_name':
# Robot namespace
# (default: 'robot1')
# See what nodes would be launched (dry run)
ros2 launch my_package robot_system.launch.py --show-nodes
Best Practices
Parameters
- Always declare parameters with defaults in
__init__() - Use descriptive names:
max_linear_velocitynotmax_vel - Add ParameterDescriptor with value ranges and descriptions
- Validate in callback before applying dynamic updates
- Log parameter changes for debugging
Launch Files
- Use DeclareLaunchArgument for flexibility (sim vs real, robot names)
- Organize by system:
sensors.launch.py,navigation.launch.py,main.launch.py - Use namespaces for multi-robot systems
- Load parameters from YAML files, not hardcoded in launch file
- Document required arguments with descriptions
Common Pitfalls and Solutions
1. "Parameter not declared"
Symptom: ParameterNotDeclaredException when calling get_parameter()
Cause: Forgot to call declare_parameter() first
Solution:
# Wrong: get before declare
self.speed = self.get_parameter('speed').value # ERROR
# Correct: declare then get
self.declare_parameter('speed', 1.0)
self.speed = self.get_parameter('speed').value
2. "Launch file not found"
Symptom: launch file '...' not found
Cause: Launch file not in correct directory or not installed
Solution:
# Correct directory structure:
my_package/
launch/
my_launch.launch.py # Must end with .launch.py
# In setup.py:
from glob import glob
data_files=[
('share/my_package/launch', glob('launch/*.launch.py')),
]
3. "Parameter file not loaded"
Symptom: Node uses default parameters, ignoring YAML file
Cause: Incorrect YAML structure or file path
Solution:
# Wrong: Missing ros__parameters
my_node:
speed: 1.0
# Correct: Double underscore
my_node:
ros__parameters:
speed: 1.0
4. "Nodes start in wrong order"
Symptom: Subscriber starts before publisher, misses messages
Solution: Use event handlers to wait for nodes:
from launch.actions import RegisterEventHandler
from launch.event_handlers import OnProcessStart
# Wait for sensor node to start before starting planner
sensor_node = Node(...)
planner_node = Node(...)
wait_for_sensor = RegisterEventHandler(
event_handler=OnProcessStart(
target_action=sensor_node,
on_start=[planner_node]
)
)
Practical Example: Multi-Robot System
Let's launch 3 robots with unique namespaces and configurations:
# File: multi_robot.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
nodes = []
# Launch 3 robots with different configurations
for i in range(1, 4):
robot_name = f'robot{i}'
# Each robot gets velocity controller
velocity_node = Node(
package='my_package',
executable='configurable_velocity',
name='velocity_controller',
namespace=robot_name,
parameters=[{
'linear_speed': 0.5 + (i * 0.2), # robot1: 0.7, robot2: 0.9, robot3: 1.1
'angular_speed': 0.3,
'publish_rate': 10.0,
'topic_name': '/cmd_vel'
}],
output='screen'
)
# Each robot gets obstacle detector
detector_node = Node(
package='sensors',
executable='obstacle_detector',
name='obstacle_detector',
namespace=robot_name,
parameters=[{
'safety_distance': 0.5,
'sensor_topic': '/scan'
}],
output='screen'
)
nodes.extend([velocity_node, detector_node])
return LaunchDescription(nodes)
Result: Launches 6 nodes total (2 per robot) with unique topics:
/robot1/cmd_vel,/robot1/scan/robot2/cmd_vel,/robot2/scan/robot3/cmd_vel,/robot3/scan
Key Takeaways
- Parameters enable runtime configuration without recompiling
- Parameter files (YAML) separate configuration from code
- Dynamic parameters allow updates without restarting nodes
- Launch files orchestrate multi-node systems with coordinated configuration
- Namespaces enable multi-robot systems on same network
- DeclareLaunchArgument makes launch files flexible and reusable
Exercises
Try these hands-on challenges:
-
Parameterized node: Modify
simple_publisher.pyto accept parameters for message content and publish rate- Test with
ros2 param setat runtime - Save parameters to YAML file with
ros2 param dump
- Test with
-
Launch file: Create a launch file that starts publisher and subscriber from Chapter 03.1
- Add parameters to configure topic name
- Test with different topic names
-
Multi-instance: Create a launch file that starts 5 publishers with unique names and different rates
- Use loop to generate nodes dynamically
- Verify with
ros2 node listandros2 topic hz
-
Conditional launch: Create a launch file with arguments to enable/disable specific nodes
- Add
use_visualizationargument to launch RViz - Add
robot_countargument to launch N robots
- Add
Next Section
Ready to debug your ROS 2 applications? Continue to Section 4: Debugging Tools to learn troubleshooting techniques and visualization tools.
Further Reading
- ROS 2 Parameters: <https://docs.ros.org/en/jazzy/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Parameters.html>
- ROS 2 Launch: <https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Launch/Launch-Main.html>
- Launch File Architecture: <https://design.ros2.org/articles/roslaunch.html>
- Parameter YAML Files: <https://docs.ros.org/en/jazzy/Concepts/About-ROS-2-Parameters.html>