Skip to main content

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​

simple_launch.py
Beginner⏱ ~15 min
1from launch import LaunchDescription
2from launch_ros.actions import Node
3
4def generate_launch_description():
5 return LaunchDescription([
6 # Node 1: Temperature sensor
7 Node(
8 package='my_robot_pkg',
9 executable='temperature_sensor',
10 name='temp_sensor_node',
11 output='screen' # Print logs to console
12 ),
13
14 # Node 2: Temperature monitor
15 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 command
ros2 launch my_robot_pkg simple_launch.py
# Both nodes start automatically with configured names

Key Components:

  • package: ROS 2 package containing the executable
  • executable: 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​

params_launch.py
Intermediate
1from launch import LaunchDescription
2from launch_ros.actions import Node
3
4def 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:

yaml_params_launch.py
Intermediate
1import os
2from ament_index_python.packages import get_package_share_directory
3from launch import LaunchDescription
4from launch_ros.actions import Node
5
6def generate_launch_description():
7 # Get path to config file
8 config = os.path.join(
9 get_package_share_directory('my_robot_pkg'),
10 'config',
11 'robot_params.yaml'
12 )
13
14 return LaunchDescription([
15 Node(
16 package='my_robot_pkg',
17 executable='configurable_node',
18 name='robot_controller',
19 parameters=[config], # Load from YAML file
20 output='screen'
21 ),
22 ])

YAML parameter file (config/robot_params.yaml):

robot_params.yaml
1robot_controller:
2ros__parameters:
3 max_linear_velocity: 1.0
4 max_angular_velocity: 0.5
5 control_frequency: 50.0
6 pid_gains:
7 kp: 1.2
8 ki: 0.1
9 kd: 0.05

5.1.3 Topic and Service Remapping​

Remapping allows you to connect nodes without modifying code:

remapping_launch.py
Intermediate
1from launch import LaunchDescription
2from launch_ros.actions import Node
3
4def generate_launch_description():
5 return LaunchDescription([
6 # Camera node publishes to /camera/image_raw
7 Node(
8 package='usb_cam',
9 executable='usb_cam_node',
10 name='front_camera'
11 ),
12
13 # Object detector expects /image topic
14 # Remap: /image β†’ /camera/image_raw
15 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:

configurable_launch.py
Advanced⏱ ~20 min
1from launch import LaunchDescription
2from launch.actions import DeclareLaunchArgument
3from launch.substitutions import LaunchConfiguration
4from launch_ros.actions import Node
5
6def generate_launch_description():
7 # Declare launch arguments
8 use_sim_time_arg = DeclareLaunchArgument(
9 'use_sim_time',
10 default_value='false',
11 description='Use simulation time if true'
12 )
13
14 robot_name_arg = DeclareLaunchArgument(
15 'robot_name',
16 default_value='robot1',
17 description='Name of the robot instance'
18 )
19
20 # Access argument values with LaunchConfiguration
21 use_sim_time = LaunchConfiguration('use_sim_time')
22 robot_name = LaunchConfiguration('robot_name')
23
24 return LaunchDescription([
25 use_sim_time_arg,
26 robot_name_arg,
27
28 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_name
35 }],
36 output='screen'
37 ),
38 ])

Using Launch Arguments:

# Use default values
ros2 launch my_robot_pkg configurable_launch.py
# Override arguments
ros2 launch my_robot_pkg configurable_launch.py use_sim_time:=true robot_name:=atlas
# View available arguments
ros2 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​

package.xml
Intermediate
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>
9
10<!-- Build tool -->
11<buildtool_depend>ament_python</buildtool_depend>
12
13<!-- Runtime dependencies -->
14<depend>rclpy</depend>
15<depend>std_msgs</depend>
16<depend>sensor_msgs</depend>
17<depend>geometry_msgs</depend>
18
19<!-- 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>
24
25<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​

setup.py
Intermediate
1from setuptools import setup
2import os
3from glob import glob
4
5package_name = 'my_robot_pkg'
6
7setup(
8 name=package_name,
9 version='1.0.0',
10 packages=[package_name],
11 data_files=[
12 # Install package marker
13 ('share/ament_index/resource_index/packages',
14 ['resource/' + package_name]),
15
16 # Install package.xml
17 ('share/' + package_name, ['package.xml']),
18
19 # Install launch files
20 (os.path.join('share', package_name, 'launch'),
21 glob('launch/*.launch.py')),
22
23 # Install config files
24 (os.path.join('share', package_name, 'config'),
25 glob('config/*.yaml')),
26
27 # Install URDF files
28 (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:function
41 '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 with ros2 run
  • Points to main() function in my_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 root
cd ~/ros2_ws
# Build all packages
colcon build
# Build specific package
colcon build --packages-select my_robot_pkg
# Build with debug symbols (for gdb debugging)
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debug
# Build with verbose output
colcon build --event-handlers console_direct+
# Clean build (remove build/install directories)
rm -rf build install log
colcon build

After building, source the workspace:

# Source install space
source ~/ros2_ws/install/setup.bash
# Verify package is found
ros2 pkg list | grep my_robot_pkg
# Run nodes from package
ros2 run my_robot_pkg temperature_sensor
ros2 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 init
rosdep update
# Install dependencies for all packages in workspace
cd ~/ros2_ws
rosdep 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 workspace
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
# Create package
ros2 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:

sensor_node.py
Intermediate
1import rclpy
2from rclpy.node import Node
3from std_msgs.msg import Float32
4import random
5
6class SensorNode(Node):
7 def __init__(self):
8 super().__init__('sensor_node')
9
10 self.declare_parameter('sensor_name', 'default_sensor')
11 self.declare_parameter('publish_rate', 1.0)
12
13 sensor_name = self.get_parameter('sensor_name').value
14 rate = self.get_parameter('publish_rate').value
15
16 self.publisher_ = self.create_publisher(Float32, 'sensor_data', 10)
17 self.timer = self.create_timer(1.0 / rate, self.timer_callback)
18
19 self.get_logger().info(f'Sensor "{sensor_name}" started at {rate} Hz')
20
21 def timer_callback(self):
22 msg = Float32()
23 msg.data = 20.0 + random.uniform(-5.0, 5.0)
24 self.publisher_.publish(msg)
25
26def main(args=None):
27 rclpy.init(args=args)
28 node = SensorNode()
29 rclpy.spin(node)
30 node.destroy_node()
31 rclpy.shutdown()
32
33if __name__ == '__main__':
34 main()

5.4.3 Updating setup.py​

setup.py
Beginner
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:

system.launch.py
Advanced
1import os
2from ament_index_python.packages import get_package_share_directory
3from launch import LaunchDescription
4from launch.actions import DeclareLaunchArgument
5from launch.substitutions import LaunchConfiguration
6from launch_ros.actions import Node
7
8def generate_launch_description():
9 # Get config file path
10 config_dir = os.path.join(
11 get_package_share_directory('robot_monitor'),
12 'config'
13 )
14
15 return LaunchDescription([
16 # Launch argument
17 DeclareLaunchArgument(
18 'use_sim_time',
19 default_value='false',
20 description='Use simulation time'
21 ),
22
23 # Sensor node
24 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 ),
35
36 # Monitor node
37 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 package
cd ~/ros2_ws
colcon build --packages-select robot_monitor
# Source workspace
source install/setup.bash
# Test individual node
ros2 run robot_monitor sensor_node --ros-args -p sensor_name:=test
# Launch full system
ros2 launch robot_monitor system.launch.py
# Override launch argument
ros2 launch robot_monitor system.launch.py use_sim_time:=true

5.5 Hands-On Exercise: Complete ROS 2 Package​

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:

  1. Review: Revisit key takeaways from each chapter
  2. Practice: Complete any unfinished exercises
  3. 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)