본문 바로가기

IT 지식/IsaacSim

ROS2 launch 파일 구조를 carter_navigation 파일로 파헤쳐보기

728x90
반응형

 

ROS2 launch 파일은 어떤 역할을 수행하는지 IsaacSim 예제중에 하나인  multiple_robot_carter_navigation_hospital.launch.py 파일 분석을 통해 구체적으로 알아보자. 

 

전체 코드는 아래와 같다. 

## Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
## NVIDIA CORPORATION and its licensors retain all intellectual property
## and proprietary rights in and to this software, related documentation
## and any modifications thereto.  Any use, reproduction, disclosure or
## distribution of this software and related documentation without an express
## license agreement from NVIDIA CORPORATION is strictly prohibited.

"""
Example for spawing multiple robots in Gazebo.

This is an example on how to create a launch file for spawning multiple robots into Gazebo
and launch multiple instances of the navigation stack, each controlling one robot.
The robots co-exist on a shared environment and are controlled by independent nav stacks
"""

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, GroupAction, IncludeLaunchDescription, LogInfo
from launch.conditions import IfCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration, TextSubstitution
from launch_ros.actions import Node


def generate_launch_description():
    # Get the launch and rviz directories
    carter_nav2_bringup_dir = get_package_share_directory("carter_navigation")

    nav2_bringup_dir = get_package_share_directory("nav2_bringup")
    nav2_bringup_launch_dir = os.path.join(nav2_bringup_dir, "launch")

    rviz_config_dir = os.path.join(carter_nav2_bringup_dir, "rviz2", "carter_navigation_namespaced.rviz")

    # Names and poses of the robots
    robots = [{"name": "carter1"}, {"name": "carter2"}, {"name": "carter3"}]

    # Common settings
    ENV_MAP_FILE = "carter_hospital_navigation.yaml"
    use_sim_time = LaunchConfiguration("use_sim_time", default="True")
    map_yaml_file = LaunchConfiguration("map")
    default_bt_xml_filename = LaunchConfiguration("default_bt_xml_filename")
    autostart = LaunchConfiguration("autostart")
    rviz_config_file = LaunchConfiguration("rviz_config")
    use_rviz = LaunchConfiguration("use_rviz")
    log_settings = LaunchConfiguration("log_settings", default="true")

    # Declare the launch arguments
    declare_map_yaml_cmd = DeclareLaunchArgument(
        "map",
        default_value=os.path.join(carter_nav2_bringup_dir, "maps", ENV_MAP_FILE),
        description="Full path to map file to load",
    )

    declare_robot1_params_file_cmd = DeclareLaunchArgument(
        "carter1_params_file",
        default_value=os.path.join(
            carter_nav2_bringup_dir, "params", "hospital", "multi_robot_carter_navigation_params_1.yaml"
        ),
        description="Full path to the ROS2 parameters file to use for robot1 launched nodes",
    )

    declare_robot2_params_file_cmd = DeclareLaunchArgument(
        "carter2_params_file",
        default_value=os.path.join(
            carter_nav2_bringup_dir, "params", "hospital", "multi_robot_carter_navigation_params_2.yaml"
        ),
        description="Full path to the ROS2 parameters file to use for robot2 launched nodes",
    )

    declare_robot3_params_file_cmd = DeclareLaunchArgument(
        "carter3_params_file",
        default_value=os.path.join(
            carter_nav2_bringup_dir, "params", "hospital", "multi_robot_carter_navigation_params_3.yaml"
        ),
        description="Full path to the ROS2 parameters file to use for robot3 launched nodes",
    )

    declare_bt_xml_cmd = DeclareLaunchArgument(
        "default_bt_xml_filename",
        default_value=os.path.join(
            get_package_share_directory("nav2_bt_navigator"), "behavior_trees", "navigate_w_replanning_and_recovery.xml"
        ),
        description="Full path to the behavior tree xml file to use",
    )

    declare_autostart_cmd = DeclareLaunchArgument(
        "autostart", default_value="True", description="Automatically startup the stacks"
    )

    declare_rviz_config_file_cmd = DeclareLaunchArgument(
        "rviz_config", default_value=rviz_config_dir, description="Full path to the RVIZ config file to use."
    )

    declare_use_rviz_cmd = DeclareLaunchArgument("use_rviz", default_value="True", description="Whether to start RVIZ")

    # Define commands for launching the navigation instances
    nav_instances_cmds = []
    for robot in robots:
        params_file = LaunchConfiguration(robot["name"] + "_params_file")

        group = GroupAction(
            [
                IncludeLaunchDescription(
                    PythonLaunchDescriptionSource(os.path.join(nav2_bringup_launch_dir, "rviz_launch.py")),
                    condition=IfCondition(use_rviz),
                    launch_arguments={
                        "namespace": TextSubstitution(text=robot["name"]),
                        "use_namespace": "True",
                        "rviz_config": rviz_config_file,
                    }.items(),
                ),
                IncludeLaunchDescription(
                    PythonLaunchDescriptionSource(
                        os.path.join(carter_nav2_bringup_dir, "launch", "carter_navigation_individual.launch.py")
                    ),
                    launch_arguments={
                        "namespace": robot["name"],
                        "use_namespace": "True",
                        "map": map_yaml_file,
                        "use_sim_time": use_sim_time,
                        "params_file": params_file,
                        "default_bt_xml_filename": default_bt_xml_filename,
                        "autostart": autostart,
                        "use_rviz": "False",
                        "use_simulator": "False",
                        "headless": "False",
                    }.items(),
                ),
                
                Node(
                    package='pointcloud_to_laserscan', executable='pointcloud_to_laserscan_node',
                    remappings=[('cloud_in', ['front_3d_lidar/lidar_points']),
                                ('scan', ['scan'])],
                    parameters=[{
                        'target_frame': 'front_3d_lidar',
                        'transform_tolerance': 0.01,
                        'min_height': -0.4,
                        'max_height': 1.5,
                        'angle_min': -1.5708,  # -M_PI/2
                        'angle_max': 1.5708,  # M_PI/2
                        'angle_increment': 0.0087,  # M_PI/360.0
                        'scan_time': 0.3333,
                        'range_min': 0.05,
                        'range_max': 100.0,
                        'use_inf': True,
                        'inf_epsilon': 1.0,
                        # 'concurrency_level': 1,
                    }],
                    name='pointcloud_to_laserscan',
                    namespace = robot["name"]
                ),

                LogInfo(condition=IfCondition(log_settings), msg=["Launching ", robot["name"]]),
                LogInfo(condition=IfCondition(log_settings), msg=[robot["name"], " map yaml: ", map_yaml_file]),
                LogInfo(condition=IfCondition(log_settings), msg=[robot["name"], " params yaml: ", params_file]),
                LogInfo(
                    condition=IfCondition(log_settings),
                    msg=[robot["name"], " behavior tree xml: ", default_bt_xml_filename],
                ),
                LogInfo(
                    condition=IfCondition(log_settings), msg=[robot["name"], " rviz config file: ", rviz_config_file]
                ),
                LogInfo(condition=IfCondition(log_settings), msg=[robot["name"], " autostart: ", autostart]),
            ]
        )

        nav_instances_cmds.append(group)

    # Create the launch description and populate
    ld = LaunchDescription()

    # Declare the launch options

    ld.add_action(declare_map_yaml_cmd)

    ld.add_action(declare_robot1_params_file_cmd)
    ld.add_action(declare_robot2_params_file_cmd)
    ld.add_action(declare_robot3_params_file_cmd)

    ld.add_action(declare_bt_xml_cmd)
    ld.add_action(declare_use_rviz_cmd)
    ld.add_action(declare_autostart_cmd)
    ld.add_action(declare_rviz_config_file_cmd)

    for simulation_instance_cmd in nav_instances_cmds:
        ld.add_action(simulation_instance_cmd)

    return ld

 

 

우선 해당 임포트 된 패키지들의 역할을 먼저 살펴보겠다. 

 

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, GroupAction, IncludeLaunchDescription, LogInfo
from launch.conditions import IfCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration, TextSubstitution
from launch_ros.actions import Node

 

이 launch.py 파일은 여러 대의 로봇을 Gazebo에서 실행하고, 각 로봇에 독립적인 네비게이션 스택을 실행하는 역할을 합니다.
아래에서 코드에 등장하는 각 import된 함수와 클래스가 어떤 역할을 하는지 코드 내에서 설명해드리겠습니다.


📌 1. ament_index_python.packages.get_package_share_directory

📌 역할:

  • ROS2 패키지 내부의 공유 디렉토리(share directory) 경로를 찾음.
  • 네비게이션 설정 파일, 맵 파일, 파라미터 파일을 가져올 때 사용.

📌 코드에서의 사용 예시:

carter_nav2_bringup_dir = get_package_share_directory("carter_navigation")
nav2_bringup_dir = get_package_share_directory("nav2_bringup")

➡️ "carter_navigation" 패키지와 "nav2_bringup" 패키지의 설정 파일이 위치한 경로를 가져옴.
➡️ 이후 맵 파일, 파라미터 파일을 로드할 때 사용됨.


📌 2. launch.LaunchDescription

📌 역할:

  • ROS2 런치 시스템에서 런치 파일의 전체 실행 내용을 정의하는 컨테이너.
  • 모든 실행할 노드, 런치 옵션, 설정 정보가 포함됨.

📌 코드에서의 사용 예시:

ld = LaunchDescription()

➡️ ld에 모든 런치 관련 명령어를 추가한 후, 마지막에 return ld로 반환.


📌 3. launch.actions.DeclareLaunchArgument

📌 역할:

  • 런치 파일 실행 시 동적으로 설정할 인자를 선언.
  • 사용자가 런치 실행 시 ros2 launch 명령어에서 다른 값으로 변경 가능.

📌 코드에서의 사용 예시:

declare_map_yaml_cmd = DeclareLaunchArgument(
    "map",
    default_value=os.path.join(carter_nav2_bringup_dir, "maps", ENV_MAP_FILE),
    description="Full path to map file to load",
)

➡️ "map"이라는 변수를 선언하고, 기본값으로 "carter_hospital_navigation.yaml"을 사용.
➡️ 실행할 때 다른 지도 파일을 지정할 수도 있음.

ros2 launch carter_navigation multi_robot_launch.py map:=office.yaml

➡️ "map" 값이 "office.yaml"로 변경됨.


📌 4. launch.actions.IncludeLaunchDescription

📌 역할:

  • 다른 런치 파일을 포함하여 실행.
  • 예를 들어, 네비게이션 관련 런치 파일을 포함하여 실행 가능.

📌 코드에서의 사용 예시:

IncludeLaunchDescription(
    PythonLaunchDescriptionSource(
        os.path.join(carter_nav2_bringup_dir, "launch", "carter_navigation_individual.launch.py")
    ),
    launch_arguments={
        "namespace": robot["name"],
        "map": map_yaml_file,
        "use_sim_time": use_sim_time,
    }.items(),
)

➡️ 각 로봇별로 독립적인 네비게이션 런치 파일을 실행.
➡️ "namespace": robot["name"]을 설정하여 각 로봇이 개별적으로 동작하도록 만듦.


📌 5. launch.actions.GroupAction

📌 역할:

  • 여러 개의 노드를 그룹으로 묶어서 실행.
  • 같은 네임스페이스를 공유하는 노드를 그룹으로 실행할 때 유용.

📌 코드에서의 사용 예시:

group = GroupAction([
    IncludeLaunchDescription(...),  # 네비게이션 런치 포함
    Node(...),  # PointCloud 변환 노드 실행
    LogInfo(...),  # 실행 로그 출력
])

➡️ 각 로봇별로 독립적인 네비게이션 실행 그룹을 생성.
➡️ 각 그룹이 별도의 네임스페이스에서 실행되도록 설정.


📌 6. launch.actions.LogInfo

📌 역할:

  • 런치 실행 중 터미널에 로그 메시지를 출력하는 기능.

📌 코드에서의 사용 예시:

LogInfo(condition=IfCondition(log_settings), msg=["Launching ", robot["name"]])

➡️ 각 로봇이 실행될 때 로그 출력: "Launching carter1", "Launching carter2" 등.


📌 7. launch.conditions.IfCondition

📌 역할:

  • 특정 조건이 참일 때만 실행되는 런치 액션을 정의.

📌 코드에서의 사용 예시:

LogInfo(condition=IfCondition(log_settings), msg=["Launching ", robot["name"]])

➡️ log_settings가 "True"일 때만 로그 메시지를 출력.


📌 8. launch.substitutions.LaunchConfiguration

📌 역할:

  • DeclareLaunchArgument로 선언된 런치 인자의 값을 가져오는 기능.

📌 코드에서의 사용 예시:

map_yaml_file = LaunchConfiguration("map")
params_file = LaunchConfiguration(robot["name"] + "_params_file")

➡️ 런치 실행 시 전달된 "map" 값을 map_yaml_file에 저장하여 사용.


📌 9. launch.substitutions.TextSubstitution

📌 역할:

  • 문자열을 런치 파일에서 변수처럼 사용.

📌 코드에서의 사용 예시:

"namespace": TextSubstitution(text=robot["name"])

➡️ "namespace" 값을 "carter1", "carter2" 등으로 설정.


📌 10. launch_ros.actions.Node

📌 역할:

  • ROS2 노드를 실행하는 가장 기본적인 기능.

📌 코드에서의 사용 예시:

Node(
    package='pointcloud_to_laserscan', 
    executable='pointcloud_to_laserscan_node',
    remappings=[('cloud_in', ['front_3d_lidar/lidar_points']), ('scan', ['scan'])],
    parameters=[{
        'target_frame': 'front_3d_lidar',
        'transform_tolerance': 0.01,
    }],
    name='pointcloud_to_laserscan',
    namespace=robot["name"]
)

➡️ 각 로봇마다 pointcloud_to_laserscan_node 실행 (네임스페이스 carter1, carter2, carter3 적용).
➡️ Lidar 데이터 변환을 수행.


📌 11. 실행 과정 정리

1️⃣ 런치 인자 선언 (DeclareLaunchArgument)

  • map, params_file 등을 런치 인자로 선언.

2️⃣ 각 로봇별 네비게이션 그룹 생성 (GroupAction)

  • "carter1", "carter2", "carter3"을 각각 독립 실행.

3️⃣ 네비게이션 런치 실행 (IncludeLaunchDescription)

  • carter_navigation_individual.launch.py를 실행하여 로봇별 네비게이션 스택 실행.

4️⃣ PointCloud 변환 노드 실행 (Node)

  • Lidar 데이터를 2D 라이다 스캔 데이터로 변환.

5️⃣ 로그 메시지 출력 (LogInfo)

  • "Launching carter1" 등의 로그 출력.

6️⃣ LaunchDescription에 모든 액션 추가 후 반환 (return ld)

  • ld.add_action(...)으로 모든 설정을 적용 후 실행.

📌 정리

함수 역할

get_package_share_directory 특정 패키지의 공유 디렉토리 경로 가져오기
LaunchDescription 런치 파일을 정의하는 컨테이너
DeclareLaunchArgument 런치 실행 시 설정할 인자 정의
IncludeLaunchDescription 다른 런치 파일 포함 실행
GroupAction 여러 노드를 그룹으로 실행
Node ROS2 노드 실행
LogInfo 런치 실행 중 로그 출력
IfCondition 특정 조건이 참일 때만 실행

이 구조를 사용하면 여러 대의 로봇을 개별적으로 실행하면서, 네비게이션과 센서 처리를 동시에 수행할 수 있습니다! 🚀

 

따라서 이러한 복잡한 멀티 로봇 시스템을 실행하는 고급 형태의 Launch 파일의 구체적인 구조를 살펴봤을 때, ROS2 Launch 파일이란 다음으로 정리해볼 수 있다. 

ROS2 Launch 파일이란?

ROS2 Launch 파일여러 개의 노드를 동시에 실행하고, 실행 환경을 설정하는 데 사용되는 스크립트입니다.
특히, 복잡한 로봇 시스템에서 네비게이션, 시뮬레이션, 센서 데이터 처리 등을 한 번에 실행할 때 필수적입니다.

🔹 ROS2 Launch 파일의 역할

  1. 여러 개의 노드를 실행
    • 예: 네비게이션 스택, 센서 드라이버, 컨트롤러, 시뮬레이터 등
  2. 설정 파일(맵, URDF, 파라미터 파일 등)을 로드
    • 예: 맵 파일, 네비게이션 설정 파일, 로봇 모델(URDF) 등
  3. 런치 인자를 설정하여 유동적으로 값 변경 가능
    • 예: ros2 launch my_robot my_launch.py use_sim_time:=True
  4. 조건부 실행(IfCondition), 그룹 실행(GroupAction) 등을 활용한 구조적 관리
    • 예: 특정 옵션이 True일 때만 RViz 실행
  5. 다른 Launch 파일을 포함하여 구조적으로 관리
    • 예: Gazebo 실행과 네비게이션 실행을 각각 다른 Launch 파일에서 관리

🚀 위의 코드를 통해 보는 ROS2 Launch 파일의 구성 요소

위의 launch.py 코드에서 어떤 요소들이 포함되어 있는지 자세히 분석해 보겠습니다.


1️⃣ LaunchDescription을 사용하여 실행할 전체 내용을 정의

ld = LaunchDescription()
  • LaunchDescription 객체는 ROS2 런치 시스템에서 실행할 내용을 포함하는 컨테이너입니다.
  • 모든 런치 관련 명령을 이 객체에 추가한 후 실행합니다.

2️⃣ DeclareLaunchArgument를 사용한 런치 인자 설정

declare_map_yaml_cmd = DeclareLaunchArgument(
    "map",
    default_value=os.path.join(carter_nav2_bringup_dir, "maps", ENV_MAP_FILE),
    description="Full path to map file to load",
)

📌 설명:

  • 런치 파일 실행 시 동적으로 값을 변경할 수 있도록 하는 인자(Launch Argument) 선언.
  • ros2 launch my_package my_launch.py map:=office.yaml 처럼 맵 파일을 변경할 수 있도록 함.

📌 이 코드에서 선언된 주요 런치 인자들:

  • "map" → 로딩할 지도 파일
  • "use_rviz" → RViz 실행 여부
  • "autostart" → 네비게이션 자동 시작 여부
  • "default_bt_xml_filename" → 네비게이션에 사용할 행동 트리 (Behavior Tree)

➡️ 런치 인자는 실행 시 원하는 값으로 변경할 수 있도록 유연성을 제공!


3️⃣ IncludeLaunchDescription을 사용하여 다른 Launch 파일 포함

IncludeLaunchDescription(
    PythonLaunchDescriptionSource(
        os.path.join(carter_nav2_bringup_dir, "launch", "carter_navigation_individual.launch.py")
    ),
    launch_arguments={
        "namespace": robot["name"],
        "map": map_yaml_file,
        "use_sim_time": use_sim_time,
    }.items(),
)

📌 설명:

  • carter_navigation_individual.launch.py라는 별도의 Launch 파일을 실행함.
  • "namespace": robot["name"]을 추가하여 각 로봇을 독립적인 네임스페이스로 실행.

➡️ 이를 통해 네비게이션 기능을 개별적으로 실행 가능!


4️⃣ GroupAction을 사용하여 로봇별 네비게이션 그룹 실행

group = GroupAction([
    IncludeLaunchDescription(...),  # 네비게이션 런치 포함
    Node(...),  # PointCloud 변환 노드 실행
    LogInfo(...),  # 실행 로그 출력
])

📌 설명:

  • 각 로봇별로 네비게이션 스택을 개별적으로 실행.
  • 그룹을 지정하여 특정 노드들을 함께 실행.

📌 실행되는 내용:

  • 네비게이션 시스템 실행
  • LiDAR 데이터를 변환하는 노드 실행
  • 실행 로그 출력

➡️ 여러 개의 로봇을 동시에 실행하면서도 독립적으로 관리할 수 있도록 함!


5️⃣ Node를 사용하여 ROS2 노드 실행

Node(
    package='pointcloud_to_laserscan', 
    executable='pointcloud_to_laserscan_node',
    remappings=[('cloud_in', ['front_3d_lidar/lidar_points']), ('scan', ['scan'])],
    parameters=[{
        'target_frame': 'front_3d_lidar',
        'transform_tolerance': 0.01,
    }],
    name='pointcloud_to_laserscan',
    namespace=robot["name"]
)

📌 설명:

  • ROS2 노드를 실행하는 코드.
  • LiDAR 데이터를 2D 라이다 스캔으로 변환하는 노드 실행.
  • "namespace" 값을 로봇별로 다르게 설정하여 각각 독립적으로 실행 가능.

➡️ 각 로봇이 독립적인 LiDAR 데이터를 처리하도록 함!


6️⃣ LogInfo를 사용하여 실행 과정 로깅

LogInfo(condition=IfCondition(log_settings), msg=["Launching ", robot["name"]])

📌 설명:

  • 터미널에 각 로봇이 실행되는 로그 메시지를 출력.
  • "Launching carter1", "Launching carter2" 등으로 실행 중인 로봇을 알림.

📌 위의 코드에서 ROS2 Launch 파일이 수행하는 핵심 역할

역할 코드에서 수행하는 방법 예제

런치 인자 설정 DeclareLaunchArgument 지도 파일 경로, 네비게이션 설정 변경 가능
다른 런치 파일 실행 IncludeLaunchDescription carter_navigation_individual.launch.py 실행
여러 노드를 그룹으로 실행 GroupAction 각 로봇별 네비게이션 실행
ROS2 노드 실행 Node LiDAR 변환 노드 실행
조건부 실행 IfCondition 특정 옵션이 True일 때만 실행
로그 출력 LogInfo 터미널에 실행 상태 출력

🚀 최종 정리

ROS2 Launch 파일이란?

  • 여러 개의 ROS2 노드를 한 번에 실행할 수 있도록 하는 스크립트.
  • launch.py 파일을 실행하면 여러 개의 노드가 동시에 실행됨.
  • 실행 환경을 설정할 수 있도록 런치 인자(Launch Argument)를 포함함.

위의 코드에서 수행하는 것

  • 세 대의 로봇(carter1, carter2, carter3)을 실행
  • 각각 독립적인 네비게이션 시스템 실행
  • 각 로봇이 자신의 LiDAR 데이터를 처리
  • 맵 파일, 설정 파일을 불러와 동적으로 실행 가능
  • 조건부 실행 및 실행 로그 출력

이 Launch 파일이 포함하는 것

  1. 런치 인자(Launch Arguments)
  2. 네비게이션 시스템 실행
  3. LiDAR 변환 노드 실행
  4. 로봇별 개별 실행 그룹
  5. 다른 런치 파일 포함
  6. 실행 로그 출력
  7. 조건부 실행(IfCondition)

🔥 한 마디로:

"이 Launch 파일을 실행하면, 세 대의 로봇이 개별적으로 실행되고, 각각의 네비게이션 시스템이 동작하며, 센서 데이터가 처리됩니다." 🚀

728x90
반응형