Launch Files and Parameter Management
This chapter delves into the sophisticated configuration and system management capabilities of ROS 2. You'll learn how to create complex launch files that start multiple nodes simultaneously, manage parameters for configurable behavior, and orchestrate complete robot systems.
Learning Objectives
By the end of this chapter, you will be able to:
- Create complex launch files for multi-node robot systems
- Manage parameters using YAML configuration files
- Use launch arguments for runtime configuration
- Implement parameter remapping and namespace management
- Create conditional launch logic
- Debug launch file issues and parameter conflicts
Understanding Launch Files
Launch files are Python scripts that define how to start a collection of ROS 2 nodes and other processes. They provide a convenient way to bring up complex robot systems with a single command.
Basic Launch File Structure
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
# Define launch arguments
# Create nodes
# Return LaunchDescription
pass
Simple Launch Example
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='turtlesim',
executable='turtlesim_node',
name='sim'
),
Node(
package='turtlesim',
executable='turtle_teleop_key',
name='teleop',
prefix='xterm -e' # Run in xterm terminal
)
])
Launch Arguments
Launch arguments allow runtime configuration of launch files:
Declaring Launch Arguments
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
# Declare launch arguments
robot_name_arg = DeclareLaunchArgument(
'robot_name',
default_value='default_robot',
description='Name of the robot'
)
use_sim_time_arg = DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation time'
)
# Use launch configurations in node definitions
robot_node = Node(
package='my_robot_package',
executable='robot_node',
name=[LaunchConfiguration('robot_name'), '_node'],
parameters=[
{'use_sim_time': LaunchConfiguration('use_sim_time')}
]
)
return LaunchDescription([
robot_name_arg,
use_sim_time_arg,
robot_node
])
Using Launch Arguments
# Use default values
ros2 launch my_robot_package robot.launch.py
# Override specific arguments
ros2 launch my_robot_package robot.launch.py robot_name:=my_robot
# Set multiple arguments
ros2 launch my_robot_package robot.launch.py robot_name:=my_robot use_sim_time:=true
Parameter Management
Parameter Sources
Parameters can come from multiple sources, with the following precedence (highest to lowest):
- Command line:
--param param_name:=value - Launch file: Explicit parameter values in Node definition
- Configuration file: YAML parameter files
- Node defaults: Default values declared in the node
YAML Parameter Files
# config/robot_params.yaml
my_robot_node:
ros__parameters:
robot_name: "my_robot"
max_velocity: 1.0
safety_mode: true
sensor_config:
camera_enabled: true
lidar_range: 10.0
control_params:
kp: 1.0
ki: 0.1
kd: 0.05
Loading Parameters in Launch Files
from launch import LaunchDescription
from launch_ros.actions import Node
import os
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# Get path to parameter file
config = os.path.join(
get_package_share_directory('my_robot_package'),
'config',
'robot_params.yaml'
)
robot_node = Node(
package='my_robot_package',
executable='robot_node',
name='robot_node',
parameters=[config] # Load from YAML file
)
return LaunchDescription([robot_node])
Combining Parameter Sources
def generate_launch_description():
config = os.path.join(
get_package_share_directory('my_robot_package'),
'config',
'robot_params.yaml'
)
# Combine YAML config with launch configuration
robot_node = Node(
package='my_robot_package',
executable='robot_node',
name='robot_node',
parameters=[
config, # From YAML file
{'robot_name': LaunchConfiguration('robot_name')}, # From launch arg
{'use_sim_time': True} # Direct value
]
)
return LaunchDescription([robot_node])
Advanced Launch Concepts
Conditional Launch Logic
from launch import LaunchDescription, LaunchCondition
from launch.actions import IncludeLaunchDescription, SetLaunchConfiguration
from launch.conditions import IfCondition, UnlessCondition
from launch.substitutions import LaunchConfiguration
from launch.launch_description_sources import PythonLaunchDescriptionSource
import os
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# Declare launch arguments
use_sim_arg = DeclareLaunchArgument(
'use_sim',
default_value='false',
description='Use simulation'
)
# Conditional inclusion of other launch files
sim_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(get_package_share_directory('gazebo_ros'), 'launch', 'gazebo.launch.py')
),
condition=IfCondition(LaunchConfiguration('use_sim'))
)
real_robot_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(get_package_share_directory('my_robot_bringup'), 'launch', 'real_robot.launch.py')
),
condition=UnlessCondition(LaunchConfiguration('use_sim'))
)
return LaunchDescription([
use_sim_arg,
sim_launch,
real_robot_launch
])
Remapping Topics and Services
def generate_launch_description():
robot_node = Node(
package='my_robot_package',
executable='robot_node',
name='robot_node',
remappings=[
('/cmd_vel', '/my_robot/cmd_vel'), # Remap to namespaced topic
('/odom', '/my_robot/odom'),
('/scan', '/my_robot/laser_scan')
]
)
return LaunchDescription([robot_node])
Namespaces
def generate_launch_description():
# Nodes in namespace
robot_node = Node(
package='my_robot_package',
executable='robot_node',
name='robot_node',
namespace='my_robot' # All topics/services will be in /my_robot namespace
)
# Multiple robots in different namespaces
robot1_node = Node(
package='my_robot_package',
executable='robot_node',
name='robot_node',
namespace='robot1'
)
robot2_node = Node(
package='my_robot_package',
executable='robot_node',
name='robot_node',
namespace='robot2'
)
return LaunchDescription([robot1_node, robot2_node])
Complex Launch File Example
Here's a comprehensive launch file for a humanoid robot system:
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, GroupAction, SetRemap
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
from launch_ros.actions import Node, PushRosNamespace
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
# Declare launch arguments
use_sim_time_arg = DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation time'
)
robot_name_arg = DeclareLaunchArgument(
'robot_name',
default_value='humanoid_robot',
description='Name of the robot'
)
use_rviz_arg = DeclareLaunchArgument(
'use_rviz',
default_value='true',
description='Launch RViz'
)
# Robot description parameter
robot_description_path = PathJoinSubstitution([
FindPackageShare('my_robot_description'),
'urdf',
'humanoid_robot.urdf.xacro'
])
# Robot state publisher node
robot_state_publisher_node = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
name='robot_state_publisher',
parameters=[
{'robot_description': robot_description_path},
{'use_sim_time': LaunchConfiguration('use_sim_time')}
]
)
# Joint state publisher node
joint_state_publisher_node = Node(
package='joint_state_publisher',
executable='joint_state_publisher',
name='joint_state_publisher',
parameters=[
{'use_sim_time': LaunchConfiguration('use_sim_time')}
]
)
# Navigation nodes group
navigation_group = GroupAction(
condition=IfCondition(LaunchConfiguration('navigation_enabled')),
actions=[
SetRemap(src='/cmd_vel', dst='/humanoid_robot/cmd_vel'),
Node(
package='nav2_map_server',
executable='map_server',
name='map_server',
parameters=[
{'use_sim_time': LaunchConfiguration('use_sim_time')}
]
),
Node(
package='nav2_planner',
executable='planner_server',
name='planner_server',
parameters=[
{'use_sim_time': LaunchConfiguration('use_sim_time')}
]
)
]
)
# RViz node
rviz_node = Node(
package='rviz2',
executable='rviz2',
name='rviz',
arguments=['-d', PathJoinSubstitution([
FindPackageShare('my_robot_bringup'),
'rviz',
'humanoid_robot.rviz'
])],
condition=IfCondition(LaunchConfiguration('use_rviz'))
)
return LaunchDescription([
use_sim_time_arg,
robot_name_arg,
use_rviz_arg,
robot_state_publisher_node,
joint_state_publisher_node,
navigation_group,
rviz_node
])
Parameter Management Best Practices
Organizing Parameters
# config/humanoid_robot_params.yaml
# Sensor parameters
sensor_fusion_node:
ros__parameters:
camera_timeout: 0.1
lidar_frequency: 10.0
imu_filter_enabled: true
# Control parameters
controller_manager:
ros__parameters:
update_rate: 100
use_sim_time: false
# Navigation parameters
local_costmap:
ros__parameters:
update_frequency: 5.0
publish_frequency: 2.0
resolution: 0.05
global_costmap:
ros__parameters:
update_frequency: 1.0
resolution: 0.1
Parameter Validation
import rclpy
from rclpy.node import Node
from rcl_interfaces.msg import ParameterDescriptor, ParameterType
class ParameterValidatedNode(Node):
def __init__(self):
super().__init__('parameter_validated_node')
# Declare parameters with validation
self.declare_parameter(
'max_velocity',
1.0,
ParameterDescriptor(
name='max_velocity',
type=ParameterType.PARAMETER_DOUBLE,
description='Maximum linear velocity',
additional_constraints='Must be positive',
floating_point_range=[rclpy.Parameter.FloatingPointRange(from_value=0.0, to_value=10.0)]
)
)
# Validate parameter values
max_vel = self.get_parameter('max_velocity').value
if max_vel <= 0:
self.get_logger().error('max_velocity must be positive')
raise ValueError('Invalid max_velocity parameter')
Launch File Debugging
Useful Debugging Commands
# Check launch file syntax
python3 -m py_compile path/to/launch/file.py
# Verbose launch output
ros2 launch --log-level debug my_package my_launch_file.py
# Check what was launched
ros2 node list
ros2 topic list
ros2 param list
# Get parameter values
ros2 param get /node_name param_name
Common Launch Issues and Solutions
Issue: Nodes not starting
Solution: Check package names, executable names, and installation
Issue: Parameters not loading
Solution: Verify YAML syntax and file paths
Issue: Namespace conflicts
Solution: Use proper namespace separation and remapping
Issue: Dependencies not found
Solution: Check package.xml dependencies and installation
Dynamic Parameter Management
Parameter Callbacks
from rcl_interfaces.msg import SetParametersResult
class DynamicParameterNode(Node):
def __init__(self):
super().__init__('dynamic_parameter_node')
# Declare parameters
self.declare_parameter('sensitivity', 1.0)
self.declare_parameter('threshold', 0.5)
# Add parameter callback
self.add_on_set_parameters_callback(self.parameter_callback)
# Store current values
self.sensitivity = self.get_parameter('sensitivity').value
self.threshold = self.get_parameter('threshold').value
def parameter_callback(self, params):
for param in params:
if param.name == 'sensitivity':
if param.value <= 0:
return SetParametersResult(successful=False, reason='Sensitivity must be positive')
self.sensitivity = param.value
elif param.name == 'threshold':
if param.value < 0 or param.value > 1:
return SetParametersResult(successful=False, reason='Threshold must be between 0 and 1')
self.threshold = param.value
return SetParametersResult(successful=True)
Launch File Organization
Multi-File Launch Structure
my_robot_bringup/
├── launch/
│ ├── robot.launch.py # Main robot bringup
│ ├── sensors.launch.py # Sensor system
│ ├── controllers.launch.py # Control system
│ ├── navigation.launch.py # Navigation stack
│ └── perception.launch.py # Perception stack
├── config/
│ ├── robot.yaml # Main robot config
│ ├── sensors.yaml # Sensor configs
│ └── controllers.yaml # Controller configs
└── rviz/
└── robot.rviz # RViz configuration
Including Other Launch Files
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():
# Include other launch files
sensors_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(
get_package_share_directory('my_robot_bringup'),
'launch',
'sensors.launch.py'
)
)
)
controllers_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(
get_package_share_directory('my_robot_control'),
'launch',
'controllers.launch.py'
)
)
)
return LaunchDescription([
sensors_launch,
controllers_launch
])
ROS 2 in Humanoid Robotics Context
Launch File Applications
- Robot bringup: Start all essential nodes for robot operation
- Mode switching: Different launch files for different operational modes
- Multi-robot systems: Coordinate multiple humanoid robots
- Simulation vs real: Different configurations for sim and real hardware
- Calibration: Special launch files for sensor calibration procedures
Parameter Strategies for Humanoid Robots
- Safety parameters: Emergency stop thresholds, velocity limits
- Calibration parameters: Sensor offsets, joint zero positions
- Behavior parameters: Walking gait parameters, balance thresholds
- Adaptive parameters: Learning-based parameter adjustments
Summary
Launch files and parameter management are crucial for configuring and starting complex robot systems. They enable the orchestration of multiple nodes with proper configuration, namespace management, and conditional logic. Effective parameter management allows for flexible, configurable robot systems that can adapt to different hardware configurations and operational requirements.
Next Steps
In the next chapter, we'll explore URDF (Unified Robot Description Format) for describing humanoid robots in ROS 2, including kinematics, dynamics, and visualization.
Estimated Reading Time: 24 minutes