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 파일의 역할
- 여러 개의 노드를 실행
- 예: 네비게이션 스택, 센서 드라이버, 컨트롤러, 시뮬레이터 등
- 설정 파일(맵, URDF, 파라미터 파일 등)을 로드
- 예: 맵 파일, 네비게이션 설정 파일, 로봇 모델(URDF) 등
- 런치 인자를 설정하여 유동적으로 값 변경 가능
- 예: ros2 launch my_robot my_launch.py use_sim_time:=True
- 조건부 실행(IfCondition), 그룹 실행(GroupAction) 등을 활용한 구조적 관리
- 예: 특정 옵션이 True일 때만 RViz 실행
- 다른 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 파일이 포함하는 것
- 런치 인자(Launch Arguments)
- 네비게이션 시스템 실행
- LiDAR 변환 노드 실행
- 로봇별 개별 실행 그룹
- 다른 런치 파일 포함
- 실행 로그 출력
- 조건부 실행(IfCondition)
🔥 한 마디로:
"이 Launch 파일을 실행하면, 세 대의 로봇이 개별적으로 실행되고, 각각의 네비게이션 시스템이 동작하며, 센서 데이터가 처리됩니다." 🚀
'IT 지식 > IsaacSim' 카테고리의 다른 글
Isaac Sim의 QoS node property 파헤치기 (0) | 2025.02.14 |
---|---|
NVIDIA Isaac Sim: Colab vs AWS - 종합 가이드 (2) | 2025.01.26 |
docker로 ros2와 Isaac Sim 연동하기! (1) | 2025.01.23 |