Chapter 5: Launch Files and Package Management
You've learned to create nodes, communicate via topics/services/actions, and model robots in URDF. But how do you launch 10+ nodes at once? How do you organize code into reusable, distributable packages?
This final chapter teaches you professional ROS 2 development workflows:
- Launch files to start multi-node systems with one command
- Package structure for organizing code, dependencies, and resources
- colcon build system for compiling and installing packages
- Best practices for maintainable, shareable robot software
By the end, you'll have a complete ROS 2 package ready for deployment and distribution.
5.1 Launch Files: Managing Multi-Node Systemsβ
5.1.1 Basic Python Launch Fileβ
1from launch import LaunchDescription2from launch_ros.actions import Node34def generate_launch_description():5 return LaunchDescription([6 # Node 1: Temperature sensor7 Node(8 package='my_robot_pkg',9 executable='temperature_sensor',10 name='temp_sensor_node',11 output='screen' # Print logs to console12 ),1314 # Node 2: Temperature monitor15 Node(16 package='my_robot_pkg',17 executable='temperature_monitor',18 name='temp_monitor_node',19 output='screen'20 ),21 ])
Running the launch file:
# Launch both nodes with one commandros2 launch my_robot_pkg simple_launch.py# Both nodes start automatically with configured names
Key Components:
package: ROS 2 package containing the executableexecutable: Name of the Python script or binary (from setup.py entry_points)name: Node name (overrides the name set in Node() constructor)output: Where to send logs ('screen'or'log')
5.1.2 Passing Parameters via Launch Filesβ
1from launch import LaunchDescription2from launch_ros.actions import Node34def generate_launch_description():5 return LaunchDescription([6 Node(7 package='my_robot_pkg',8 executable='parameter_node',9 name='configured_node',10 parameters=[{11 'robot_name': 'atlas',12 'max_speed': 2.5,13 'debug_mode': True,14 'sensor_topics': ['/camera', '/lidar', '/imu']15 }],16 output='screen'17 ),18 ])
Parameters can also be loaded from YAML files:
1import os2from ament_index_python.packages import get_package_share_directory3from launch import LaunchDescription4from launch_ros.actions import Node56def generate_launch_description():7 # Get path to config file8 config = os.path.join(9 get_package_share_directory('my_robot_pkg'),10 'config',11 'robot_params.yaml'12 )1314 return LaunchDescription([15 Node(16 package='my_robot_pkg',17 executable='configurable_node',18 name='robot_controller',19 parameters=[config], # Load from YAML file20 output='screen'21 ),22 ])
YAML parameter file (config/robot_params.yaml):
1robot_controller:2ros__parameters:3 max_linear_velocity: 1.04 max_angular_velocity: 0.55 control_frequency: 50.06 pid_gains:7 kp: 1.28 ki: 0.19 kd: 0.05
5.1.3 Topic and Service Remappingβ
Remapping allows you to connect nodes without modifying code:
1from launch import LaunchDescription2from launch_ros.actions import Node34def generate_launch_description():5 return LaunchDescription([6 # Camera node publishes to /camera/image_raw7 Node(8 package='usb_cam',9 executable='usb_cam_node',10 name='front_camera'11 ),1213 # Object detector expects /image topic14 # Remap: /image β /camera/image_raw15 Node(16 package='vision_pkg',17 executable='object_detector',18 name='detector',19 remappings=[20 ('/image', '/camera/image_raw'),21 ('/detections', '/robot/detected_objects')22 ]23 ),24 ])
Why Remapping?
- β Integrate third-party packages without code changes
- β Run multiple instances of same node with different topics
- β
Create clear topic hierarchies (
/robot/sensors/camera1)
5.1.4 Launch Arguments for Configurationβ
Make launch files configurable with arguments:
1from launch import LaunchDescription2from launch.actions import DeclareLaunchArgument3from launch.substitutions import LaunchConfiguration4from launch_ros.actions import Node56def generate_launch_description():7 # Declare launch arguments8 use_sim_time_arg = DeclareLaunchArgument(9 'use_sim_time',10 default_value='false',11 description='Use simulation time if true'12 )1314 robot_name_arg = DeclareLaunchArgument(15 'robot_name',16 default_value='robot1',17 description='Name of the robot instance'18 )1920 # Access argument values with LaunchConfiguration21 use_sim_time = LaunchConfiguration('use_sim_time')22 robot_name = LaunchConfiguration('robot_name')2324 return LaunchDescription([25 use_sim_time_arg,26 robot_name_arg,2728 Node(29 package='nav2_bringup',30 executable='navigation_node',31 name='navigator',32 parameters=[{33 'use_sim_time': use_sim_time,34 'robot_name': robot_name35 }],36 output='screen'37 ),38 ])
Using Launch Arguments:
# Use default valuesros2 launch my_robot_pkg configurable_launch.py# Override argumentsros2 launch my_robot_pkg configurable_launch.py use_sim_time:=true robot_name:=atlas# View available argumentsros2 launch my_robot_pkg configurable_launch.py --show-args
5.2 ROS 2 Package Structureβ
A ROS 2 package is an organizational unit containing code, dependencies, and resources.
5.2.1 Standard Package Layoutβ
my_robot_pkg/βββ package.xml # Package metadata and dependenciesβββ setup.py # Python package configurationβββ setup.cfg # Python build configurationβββ resource/ # Package marker fileβ βββ my_robot_pkgβββ my_robot_pkg/ # Python source codeβ βββ __init__.pyβ βββ temperature_sensor.pyβ βββ temperature_monitor.pyβ βββ utils.pyβββ launch/ # Launch filesβ βββ sensors.launch.pyβ βββ full_system.launch.pyβββ config/ # YAML parameter filesβ βββ robot_params.yamlβ βββ sensor_config.yamlβββ urdf/ # Robot description filesβ βββ robot.urdfβ βββ robot.urdf.xacroβββ rviz/ # RViz configurationβ βββ default.rvizβββ test/ # Unit testsβββ test_sensor.pyβββ test_monitor.py
5.2.2 package.xml: Package Metadataβ
1<?xml version="1.0"?>2<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>3<package format="3">4<name>my_robot_pkg</name>5<version>1.0.0</version>6<description>Temperature monitoring system for robotics</description>7<maintainer email="your.email@example.com">Your Name</maintainer>8<license>MIT</license>910<!-- Build tool -->11<buildtool_depend>ament_python</buildtool_depend>1213<!-- Runtime dependencies -->14<depend>rclpy</depend>15<depend>std_msgs</depend>16<depend>sensor_msgs</depend>17<depend>geometry_msgs</depend>1819<!-- Test dependencies -->20<test_depend>ament_copyright</test_depend>21<test_depend>ament_flake8</test_depend>22<test_depend>ament_pep257</test_depend>23<test_depend>python3-pytest</test_depend>2425<export>26 <build_type>ament_python</build_type>27</export>28</package>
Dependency Types:
<buildtool_depend>: Build tools (ament_python, ament_cmake)<depend>: Build + runtime dependencies (rclpy, sensor_msgs)<build_depend>: Build-only dependencies (code generators)<exec_depend>: Runtime-only dependencies (launch files)<test_depend>: Testing dependencies (pytest, linters)
5.2.3 setup.py: Python Package Configurationβ
1from setuptools import setup2import os3from glob import glob45package_name = 'my_robot_pkg'67setup(8 name=package_name,9 version='1.0.0',10 packages=[package_name],11 data_files=[12 # Install package marker13 ('share/ament_index/resource_index/packages',14 ['resource/' + package_name]),1516 # Install package.xml17 ('share/' + package_name, ['package.xml']),1819 # Install launch files20 (os.path.join('share', package_name, 'launch'),21 glob('launch/*.launch.py')),2223 # Install config files24 (os.path.join('share', package_name, 'config'),25 glob('config/*.yaml')),2627 # Install URDF files28 (os.path.join('share', package_name, 'urdf'),29 glob('urdf/*.urdf') + glob('urdf/*.xacro')),30 ],31 install_requires=['setuptools'],32 zip_safe=True,33 maintainer='Your Name',34 maintainer_email='your.email@example.com',35 description='Temperature monitoring system for robotics',36 license='MIT',37 tests_require=['pytest'],38 entry_points={39 'console_scripts': [40 # Executable name = package.module:function41 'temperature_sensor = my_robot_pkg.temperature_sensor:main',42 'temperature_monitor = my_robot_pkg.temperature_monitor:main',43 ],44 },45)
entry_points Explained:
- Maps executable names to Python functions
'temperature_sensor'becomes a command you can run withros2 run- Points to
main()function inmy_robot_pkg/temperature_sensor.py
5.3 Colcon: The ROS 2 Build Systemβ
Colcon (collective construction) is the build tool for ROS 2 packages.
5.3.1 Workspace Structureβ
ros2_ws/ # Workspace rootβββ src/ # Source space (your packages)β βββ my_robot_pkg/β βββ vision_pkg/βββ build/ # Build artifacts (auto-generated)βββ install/ # Installed files (auto-generated)βββ log/ # Build logs (auto-generated)
5.3.2 Building Packages with Colconβ
# Navigate to workspace rootcd ~/ros2_ws# Build all packagescolcon build# Build specific packagecolcon build --packages-select my_robot_pkg# Build with debug symbols (for gdb debugging)colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debug# Build with verbose outputcolcon build --event-handlers console_direct+# Clean build (remove build/install directories)rm -rf build install logcolcon build
After building, source the workspace:
# Source install spacesource ~/ros2_ws/install/setup.bash# Verify package is foundros2 pkg list | grep my_robot_pkg# Run nodes from packageros2 run my_robot_pkg temperature_sensorros2 launch my_robot_pkg sensors.launch.py
5.3.3 Dependency Management with rosdepβ
rosdep installs package dependencies automatically:
# Initialize rosdep (once per system)sudo rosdep initrosdep update# Install dependencies for all packages in workspacecd ~/ros2_wsrosdep install --from-paths src --ignore-src -r -y# Explanation:# --from-paths src: Search src/ for package.xml files# --ignore-src: Don't try to install packages from src/# -r: Continue despite errors# -y: Auto-confirm installations
5.4 Complete Package Exampleβ
Let's create a full package with multiple nodes, launch files, and configuration.
5.4.1 Creating a New Packageβ
# Create workspacemkdir -p ~/ros2_ws/srccd ~/ros2_ws/src# Create packageros2 pkg create --build-type ament_python --dependencies rclpy std_msgs sensor_msgs robot_monitor# Directory structure created:# robot_monitor/# βββ package.xml# βββ setup.py# βββ setup.cfg# βββ resource/robot_monitor# βββ robot_monitor/__init__.py
5.4.2 Adding Nodesβ
Create robot_monitor/sensor_node.py:
1import rclpy2from rclpy.node import Node3from std_msgs.msg import Float324import random56class SensorNode(Node):7 def __init__(self):8 super().__init__('sensor_node')910 self.declare_parameter('sensor_name', 'default_sensor')11 self.declare_parameter('publish_rate', 1.0)1213 sensor_name = self.get_parameter('sensor_name').value14 rate = self.get_parameter('publish_rate').value1516 self.publisher_ = self.create_publisher(Float32, 'sensor_data', 10)17 self.timer = self.create_timer(1.0 / rate, self.timer_callback)1819 self.get_logger().info(f'Sensor "{sensor_name}" started at {rate} Hz')2021 def timer_callback(self):22 msg = Float32()23 msg.data = 20.0 + random.uniform(-5.0, 5.0)24 self.publisher_.publish(msg)2526def main(args=None):27 rclpy.init(args=args)28 node = SensorNode()29 rclpy.spin(node)30 node.destroy_node()31 rclpy.shutdown()3233if __name__ == '__main__':34 main()
5.4.3 Updating setup.pyβ
1# Add to entry_points:2entry_points={3 'console_scripts': [4 'sensor_node = robot_monitor.sensor_node:main',5 'monitor_node = robot_monitor.monitor_node:main',6 ],7},
5.4.4 Creating Launch Fileβ
Create launch/system.launch.py:
1import os2from ament_index_python.packages import get_package_share_directory3from launch import LaunchDescription4from launch.actions import DeclareLaunchArgument5from launch.substitutions import LaunchConfiguration6from launch_ros.actions import Node78def generate_launch_description():9 # Get config file path10 config_dir = os.path.join(11 get_package_share_directory('robot_monitor'),12 'config'13 )1415 return LaunchDescription([16 # Launch argument17 DeclareLaunchArgument(18 'use_sim_time',19 default_value='false',20 description='Use simulation time'21 ),2223 # Sensor node24 Node(25 package='robot_monitor',26 executable='sensor_node',27 name='temperature_sensor',28 parameters=[{29 'sensor_name': 'temp_sensor_1',30 'publish_rate': 2.0,31 'use_sim_time': LaunchConfiguration('use_sim_time')32 }],33 output='screen'34 ),3536 # Monitor node37 Node(38 package='robot_monitor',39 executable='monitor_node',40 name='data_monitor',41 parameters=[{42 'threshold': 30.0,43 'use_sim_time': LaunchConfiguration('use_sim_time')44 }],45 output='screen'46 ),47 ])
5.4.5 Building and Testingβ
# Build packagecd ~/ros2_wscolcon build --packages-select robot_monitor# Source workspacesource install/setup.bash# Test individual noderos2 run robot_monitor sensor_node --ros-args -p sensor_name:=test# Launch full systemros2 launch robot_monitor system.launch.py# Override launch argumentros2 launch robot_monitor system.launch.py use_sim_time:=true
5.5 Hands-On Exercise: Complete ROS 2 Packageβ
Multi-Node Robot Monitor Package
Learning Objectives
Problem Statementβ
Create a complete ROS 2 package for a robot monitoring system with:
- Sensor node: Publishes simulated battery, temperature, and CPU metrics
- Aggregator node: Subscribes to all sensors, computes statistics
- Alert node: Triggers warnings when metrics exceed thresholds
Requirementsβ
Package Structure:
robot_health_monitor/
βββ package.xml (dependencies: rclpy, std_msgs, diagnostic_msgs)
βββ setup.py (3 entry points)
βββ robot_health_monitor/
β βββ battery_sensor.py
β βββ aggregator.py
β βββ alert_system.py
βββ launch/
β βββ health_monitor.launch.py
βββ config/
β βββ thresholds.yaml
Node Specifications:
battery_sensor: Publish Float32 to/battery_level(0-100%) at 1 Hzaggregator: Subscribe to/battery_level,/temperature,/cpu_usage, publish average to/system_healthalert_system: Subscribe to metrics, log WARN if battery < 20%, temperature > 80Β°C
Launch File:
- Start all 3 nodes
- Load
thresholds.yamlfor alert_system - Accept
log_levelargument (default: INFO)
Testingβ
# Build
colcon build --packages-select robot_health_monitor
source install/setup.bash
# Launch system
ros2 launch robot_health_monitor health_monitor.launch.py
# Verify topics
ros2 topic list # Should show /battery_level, /system_health, etc.
ros2 topic hz /battery_level # Should show ~1 Hz
# Check alerts appear when battery drops below 20%
Deliverableβ
- Complete package with all files
- Screenshot of
colcon buildsuccess - Screenshot of all 3 nodes running via launch file with visible log output
Key Takeawaysβ
β Core Concepts Masteredβ
- Launch files orchestrate multi-node systems using Python syntax with Node(), parameters, remapping, and arguments
- Python packages follow standard structure with package.xml (dependencies), setup.py (entry points), and organized directories (launch, config, urdf)
- colcon builds ROS 2 workspaces compiling packages and creating install spaces that extend the ROS 2 underlay
- rosdep installs package dependencies automatically from package.xml declarations
- Overlay workspaces extend underlays allowing custom packages to override system packages when sourced
- Launch arguments enable configuration through DeclareLaunchArgument and LaunchConfiguration for runtime flexibility
- Best practices separate code and configuration using YAML files, modular launch includes, and proper dependency declarations
Module 1 Complete! πβ
Congratulations! You've completed Module 1: The Robotic Nervous System (ROS 2).
What You've Learned:
- Chapter 1: ROS 2 architecture, computational graphs, DDS middleware
- Chapter 2: Publishers, subscribers, topics, QoS policies
- Chapter 3: Services, actions, parameters, AI-ROS integration
- Chapter 4: URDF robot modeling, links, joints, RViz2 visualization
- Chapter 5: Launch files, packages, colcon build system
Total Time: ~475 minutes (7.9 hours)
Next Steps:
- Review: Revisit key takeaways from each chapter
- Practice: Complete any unfinished exercises
- Module 2 Preview: Digital Twin and Gazebo Simulation (Weeks 6-7)
Assessment: Before continuing, ensure you can:
- β Create a ROS 2 package with multiple nodes
- β Write launch files with parameters and remapping
- β Build and deploy packages using colcon
- β Debug multi-node systems using ROS 2 CLI tools
Next Module: Module 2: Digital Twin Simulation (coming soon)