Introduction to ROS2 Launch Files for both Python and C++ Packages

Introduction to ROS2

Last updated: December 22, 2024

1. Introduction

In our previous lessons, we built ROS2 packages that each contained two nodes. To run these nodes, we had to source the overlay in separate terminals and manually start each node. While this works for simple projects, it quickly becomes inefficient as the complexity of the system grows. This manual approach not only requires extra effort but also introduces the risk of human error, especially in large-scale applications with multiple interdependent nodes.

To address this challenge, ROS2 introduces launch files, a powerful mechanism that simplifies and automates the process of starting nodes. Written in Python, launch files enable you to start multiple nodes simultaneously, manage parameters, load configuration files, and perform other advanced setup tasks with ease.

In this lesson, you will learn the fundamentals of ROS2 launch files. We’ll begin by creating simple launch files to understand their structure and usage. As we progress, you’ll see how launch files can handle increasingly complex scenarios, making them indispensable for real-world applications.

Complete codeComplete code: talker_listener on Edreate GitHub

1a. Why Use ROS2 Launch Files?

Launch files streamline and enhance your workflow in several key ways:

  1. Simplify Execution: Launch multiple nodes and configure them with a single command.
  2. Automation: Automate the startup of complex systems, including nodes with interdependencies and pre-set parameters.
  3. Reusability: Create reusable configurations for different scenarios, projects, or deployments.
  4. Scalability: Manage large-scale systems with centralized control over all nodes and configurations.
  5. Consistency: Reduce human errors by ensuring the same nodes and parameters are launched every time.

Mastering launch files equips you to efficiently deploy and manage ROS2 systems, whether for prototypes, simulations, or fully integrated robots. They save time, reduce effort, and ensure consistency.

1b. General Structure of a ROS2 Launch File

In ROS2, launch files are used to start nodes, configure parameters, and automate complex setups. Typically written in Python, the general structure of a ROS2 launch file is as follows:

# Import necessary modules
from launch import LaunchDescription
from launch_ros.actions import Node

# Define the launch description function
def generate_launch_description():
    # Create and return the LaunchDescription object
    return LaunchDescription([
        Node(
            package='<package_name>',
            executable='<executable_name>',
            name='<node_name>',
            namespace='<namespace>',  # Optional
            output='screen',          # Optional, 'screen' or 'log'
            parameters=[{'param_name': 'param_value'}],  # Optional
            remappings=[('/old_topic', '/new_topic')]    # Optional
        ),
        # Add more Node entries here if required
    ])

Key Components

  1. Imports:

    • LaunchDescription: Encapsulates all launch configurations.
    • Node: Specifies the ROS2 nodes to launch.
  2. Function generate_launch_description:

    • Must return a LaunchDescription object containing configurations for one or more nodes.
  3. Node Configuration:

    • package: Name of the ROS2 package containing the node.
    • executable: Node executable within the package.
    • name: Desired name for the node.
    • namespace: Grouping or scoping for the node (optional).
    • output: Determines whether logs are displayed on the screen or saved to a file.
    • parameters: List of dictionaries defining parameters to be passed to the node.
    • remappings: List of tuples for topic remapping.
  4. Additional Configurations:

    • You can include timers, conditions, and groupings for more advanced setups.

Let’s dive in and create your first ROS2 launch file!

2. Brief Overview of How It Was Done Previously

  1. Build the ROS2 Packages
    We created ROS2 packages containing nodes written in Python or C++. These packages were built using the colcon build command.

  2. Source the Overlay
    After building, we sourced the setup script in a terminal to overlay the newly built packages:

    source install/setup.bash
  3. Run Each Node Manually
    For each node, we opened a new terminal, sourced the overlay again, and ran the node:

    • In one terminal:
      ros2 run <package_name> talker
      # e.g;
      ros2 run talker_listener_py talker
    • In another terminal:
      ros2 run <package_name> listener
      
      #e.g;
      ros2 run talker_listener_py listener

3. Create Launch File

Steps to Create Launch Files for talker_listener_py and talker_listener_cpp

The steps outlined below apply to both packages. You can follow these instructions for either package or both, depending on your needs.

3a. Setup a Launch File Directory and Create Python Launch Files

Inside each package, create a launch directory if it doesn’t already exist:

# Create launch directory in the package directory

mkdir -p src/talker_listener_py/launch
mkdir -p src/talker_listener_cpp/launch

Launch files in ROS2 are typically written in Python although writing them in XML and YAML is also possible. Create talker_listener_py.launch.py and talker_listener_cpp.launch.py in the respective launch directory.

3b. Define the Launch File Structure

Each launch file should define the nodes to be started. Here's the content for the launch files:

talker_listener_py.launch.py

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='talker_listener_py',
            executable='talker',
            name='py_talker'
        ),
        Node(
            package='talker_listener_py',
            executable='listener',
            name='py_listener'
        ),
    ])

talker_listener_cpp.launch.py

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='talker_listener_cpp',
            executable='talker',
            name='cpp_talker'
        ),
        Node(
            package='talker_listener_cpp',
            executable='listener',
            name='cpp_listener'
        ),
    ])

3c. Install Launch Files

For Python Packages

  1. Open the setup.py file in your Python package directory (talker_listener_py package).
  2. Update the data_files section to include the launch directory:
    'data_files': [ 
            ('share/talker_listener_py/launch',
            ['launch/talker_listener_py.launch.py']), 
            ],
  3. Rebuild the Python package:
    colcon build

For C++ Packages

  1. Open the CMakeLists.txt file in your C++ package directory (talker_listener_cpp package).
  2. Add the following lines to install the launch directory:
    install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch)
  3. Rebuild the C++ package:
    colcon build

3d. Run the Launch Files

After sucessfull build, source the overlay and use the ros2 launch command to execute the nodes:

For talker_listener_py package:

ros2 launch talker_listener_py talker_listener_py.launch.py

Output:

[INFO] [launch]: All log files can be found below /home/edreate/.ros/log/2024-12-22-12-25-13-491524-edreate-19975
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [talker-1]: process started with pid [19976]
[INFO] [listener-2]: process started with pid [19978]
[talker-1] [INFO] [1734866714.795842846] [py_talker]: Published message: Hello from talker: 0
[listener-2] [INFO] [1734866714.797289861] [py_listener]: Listener received messaged: Hello from talker: 0
[talker-1] [INFO] [1734866715.755148025] [py_talker]: Published message: Hello from talker: 1
[listener-2] [INFO] [1734866715.755930962] [py_listener]: Listener received messaged: Hello from talker: 1

For talker_listener_cpp package:

ros2 launch talker_listener_cpp talker_listener_cpp.launch.py

Output:

[INFO] [launch]: All log files can be found below /home/edreate/.ros/log/2024-12-22-12-48-30-966088-edreate-28232
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [talker-1]: process started with pid [28233]
[INFO] [listener-2]: process started with pid [28235]
[talker-1] [INFO] [1734868112.048737833] [cpp_talker]: Published: Hello from talker_cpp: 0
[listener-2] [INFO] [1734868112.048917045] [cpp_listener]: Received: Hello from talker_cpp: 0
[talker-1] [INFO] [1734868113.048759472] [cpp_talker]: Published: Hello from talker_cpp: 1
[listener-2] [INFO] [1734868113.048967468] [cpp_listener]: Received: Hello from talker_cpp: 1
[talker-1] [INFO] [1734868114.048838436] [cpp_talker]: Published: Hello from talker_cpp: 2
[listener-2] [INFO] [1734868114.049076223] [cpp_listener]: Received: Hello from talker_cpp: 2

3e. ROS2 CLI Output

After setting up and running the Python package, we can use ROS2 CLI tools to observe the active nodes and topics. Here's what we get:

Listing Active Nodes

Run the following command to list all active nodes:

ros2 node list

Output:

/py_listener
/py_talker

The output shows the two nodes, /py_listener and /py_talker, created by the Python package.

Listing Active Topics

Next, run the command to list all active topics:

ros2 topic list

Output:

/py_listener
/py_talker

Even though we executed the ros2 topic list command, the output remains unchanged because no additional topics were created or modified during this process.

These results demonstrate that the nodes and topics defined in the Python package are correctly launched and active. If you run the equivalent commands while using the C++ package, you would see similar output with the node and topic names corresponding to the C++ implementation.

4. Complete Files

src/talker_listener_py/setup.py

from setuptools import find_packages, setup

package_name = "talker_listener_py"

setup(
    name=package_name,
    version="0.0.1",
    packages=find_packages(exclude=["test"]),
    data_files=[
        (
            "share/ament_index/resource_index/packages",
            ["resource/" + package_name],
        ),
        (
            "share/" + package_name,
            ["package.xml"],
        ),
        # Launch file
        (
            "share/talker_listener_py/launch",
            ["launch/talker_listener_py.launch.py"],
        ),
    ],
    install_requires=["setuptools"],
    zip_safe=True,
    maintainer="edreate",
    maintainer_email="edreate.dev@gmail.com",
    description="Basic talker listener node in python.",
    license="MIT",
    tests_require=["pytest"],
    entry_points={
        "console_scripts": [
            "talker = talker_listener_py.talker:main",
            "listener = talker_listener_py.listener:main",
        ],
    },
)

src/talker_listener_cpp/CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
project(talker_listener_cpp)

# Enable strict compiler warnings for GCC or Clang
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# Find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

# Define talker executable
add_executable(talker src/talker.cpp)
target_include_directories(talker PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
target_compile_features(talker PUBLIC cxx_std_17)  # Require C++17
ament_target_dependencies(talker rclcpp std_msgs)

# Define listener executable
add_executable(listener src/listener.cpp)
target_include_directories(listener PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
target_compile_features(listener PUBLIC cxx_std_17)  # Require C++17
ament_target_dependencies(listener rclcpp std_msgs)

# Install executables
install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

install(DIRECTORY 
  launch/ DESTINATION share/${PROJECT_NAME}/launch)

# Enable linting and testing (optional)
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # Skip copyright and cpplint checks (adjust as needed)
  set(ament_cmake_copyright_FOUND TRUE)
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

# Mark package complete
ament_package()

5. Summary

ROS2 launch files provide a structured and efficient way to automate the startup of multiple nodes, manage configurations, and scale systems with minimal manual intervention.

Complete codeComplete code: talker_listener on Edreate GitHub

The typical launch file is written in Python and follows a clear structure:

  1. Import necessary modules.
  2. Define a function (generate_launch_description) that returns a LaunchDescription object.
  3. Use Node objects to specify nodes, parameters, and remappings.

This mechanism simplifies workflow, reduces human error, and ensures consistency in deploying ROS2 applications. The flexibility of launch files makes them indispensable for both development and production environments.

Previous Lesson