1. 이론
1.7런치 파일
왜 런치 파일인가
지금까지는 노드마다 터미널을 하나씩 열어 ros2 run을 실행했습니다. 로봇 하나를 움직이려면 구동 노드, 센서 노드, 제어 노드, 시각화 도구까지 수십 개에 이르기도 합니다. 이를 매번 손으로 실행하면 번거롭고 실수하기 쉽습니다.
런치 파일(launch file) 은 "어떤 노드를 어떤 설정으로 실행할지"를 파일 하나에 적어 두는 방법입니다. 적어 둔 내용은 한 번에 실행됩니다.
- 여러 노드를 명령 한 줄로 실행합니다.
- 파라미터, 토픽 이름 바꾸기(리맵), 네임스페이스 같은 설정을 코드 수정 없이 파일에 모아 둡니다.
- 같은 로봇을 두 대 띄우는 등 구성을 바꿔 재사용하기가 간편합니다.
Python 런치 파일 읽기
런치 파일은 Python, XML, YAML 중 어느 형식으로 써도 됩니다. 이 교육에서는 가장 유연한 Python을 기본으로 사용합니다. 아래 예는 demo_nodes_cpp 패키지의 talker와 listener를 실행합니다. 이 패키지는 ROS2 Humble에 포함되어 있습니다. 이 코드는 참고용이며 페이지에서 실행되지 않습니다.
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='demo_nodes_cpp', # 어느 패키지의
executable='talker', # 어떤 실행 파일을
name='talker', # 이 이름의 노드로 실행
namespace='robot1', # 노드를 /robot1 아래에 둠
output='screen', # 로그를 터미널에 출력
remappings=[('chatter', 'news')], # 토픽 이름 바꾸기
),
Node(
package='demo_nodes_cpp',
executable='listener',
name='listener',
namespace='robot1',
remappings=[('chatter', 'news')],
),
])
구조는 단순합니다.
- 파일에는
generate_launch_description()함수가 있어야 하고, 이 함수가LaunchDescription을 반환합니다. LaunchDescription안에 실행할 액션을 나열합니다. 노드를 실행하는 액션이Node입니다.Node의 주요 인자는 아래와 같습니다.
| 인자 | 의미 | 예 |
|---|---|---|
package, executable | 실행할 패키지와 실행 파일 | 'demo_nodes_cpp', 'talker' |
name | 노드 이름 바꾸기 | 'talker' |
namespace | 노드를 넣을 네임스페이스 | 'robot1' |
parameters | 파라미터 지정 (딕셔너리 목록 또는 YAML 파일 경로) | [{'use_sim_time': False}] |
remappings | (원래 이름, 새 이름) 목록 | [('chatter', 'news')] |
output | 로그 출력 위치 | 'screen' |
파라미터는 1.5장에서 배운 것처럼 노드가 시작할 때 값을 정해 주는 데 쓰입니다. 예를 들어 parameters=[{'use_sim_time': True}] 처럼 적습니다.
네임스페이스와 리맵은 이름을 어떻게 바꾸는가
이름 규칙은 자주 헷갈리는 부분이라 직접 만져 보면서 익히는 것이 좋습니다. 먼저 규칙을 정리합니다.
- 노드 이름 앞에 네임스페이스가 붙어 완전한 이름이 됩니다. 이름
talker, 네임스페이스robot1이면/robot1/talker입니다. - 노드가 코드에서 쓰는 상대 이름(
chatter)은 네임스페이스가 앞에 붙어/robot1/chatter가 됩니다./로 시작하는 절대 이름(/chatter)은 네임스페이스의 영향을 받지 않습니다. - 리맵 규칙은 양쪽 이름(from, to)을 같은 방식으로 확장한 뒤 비교하고 바꿉니다. 네임스페이스
robot1에서('chatter', 'news')규칙은/robot1/chatter를/robot1/news로 바꿉니다. - 리맵 결과로
/news처럼 절대 이름을 쓰면 네임스페이스를 벗어납니다. - 노드 이름과 네임스페이스 변경이 토픽 리맵보다 먼저 적용됩니다.
아래 도구에서 옵션을 바꿔 보세요. 표의 실제 이름, 그래프, 맨 아래의 런치 파일이 함께 바뀝니다.
결과: 실제 이름
| 노드 (완전한 이름) | 코드 속 토픽 | 실제 토픽 |
|---|---|---|
| /robot1/talker | chatter | /robot1/chatter |
| /robot1/listener | chatter | /robot1/chatter |
둥근 상자 = 노드 · 사각형 = 토픽 · 화살표 = 데이터 방향
- 모든 listener가 /robot1/chatter 에 연결됩니다.
지금 설정에 해당하는 런치 파일
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='demo_nodes_cpp',
executable='talker',
name='talker',
namespace='robot1'
),
Node(
package='demo_nodes_cpp',
executable='listener',
name='listener',
namespace='robot1'
),
])표시되는 이름은 이름 규칙으로 계산한 값이며, 실제 ROS2 실행 결과(ros2 node list, ros2 topic list)와 같은 규칙을 따릅니다.
직접 해 보기
- namespace를
robot1로 두고 listener 수를 2로 바꾸면 이름이 어떻게 되나요? 두 listener가 같은 토픽을 받는지 확인하세요. - "listener에도 같은 namespace 적용"을 끄면 talker와 listener가 왜 연결되지 않을까요? 두 토픽 이름을 비교해 보세요.
- 리맵을 켜고 listener 쪽 리맵을 끄면 어떻게 되나요? 리맵이 노드마다 따로 적용된다는 것을 확인하세요.
- namespace가 있는 상태에서 from을
/chatter로 바꿔 보세요. 왜 리맵이 적용되지 않는지 설명해 보세요. (힌트: 노드가 쓰는 이름은/robot1/chatter입니다.) - to를
/news로 바꾸면 토픽이 namespace 밖으로 나가는 것을 확인하세요.
런치 파일 실행하기
런치 파일이 패키지 안에 있으면 패키지 이름과 파일 이름으로 실행합니다. 파일 경로를 직접 주어도 됩니다.
ros2 launch <패키지 이름> <런치 파일 이름>
ros2 launch my_robot_bringup robot.launch.py
# 파일 경로로 직접 실행
ros2 launch ./robot.launch.py
패키지에 런치 파일을 담으려면 보통 launch/ 폴더에 두고, 빌드 때 설치되도록 setup.py의 data_files에 등록합니다. 이 과정은 패키지와 colcon을 다루는 장에서 따로 설명합니다.
인자와 다른 런치 파일 포함
런치 파일은 인자를 받아 동작을 바꾸기도 하고 다른 런치 파일을 불러오기도 합니다. 개념만 간단히 소개합니다.
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
ns = LaunchConfiguration('ns') # 실행 시점에 값이 정해지는 자리표시자
return LaunchDescription([
DeclareLaunchArgument('ns', default_value='robot1',
description='노드 네임스페이스'),
Node(package='demo_nodes_cpp', executable='talker', namespace=ns),
# 다른 런치 파일을 포함하는 방법
# IncludeLaunchDescription(
# PythonLaunchDescriptionSource('/경로/other.launch.py')),
])
ros2 launch my_pkg my.launch.py ns:=robot2 # 인자 값 지정 (이름:=값)
ros2 launch -s my_pkg my.launch.py # 사용할 수 있는 인자 목록 보기
DeclareLaunchArgument로 인자를 선언하고LaunchConfiguration으로 값을 가져옵니다. 이 값은 파일을 읽을 때가 아니라 실행 시점에 정해집니다.- 그래서
LaunchConfiguration값은 문자열처럼 바로if로 비교하거나 연산하지 못합니다. 조건 분기가 필요하면 별도의 런치 조건 기능을 씁니다. IncludeLaunchDescription을 쓰면 다른 런치 파일을 한 줄로 불러와 재사용합니다.
런치 파일 형식
| 형식 | 확장자 예 | 특징 |
|---|---|---|
| Python | .launch.py | 조건, 반복, 계산이 가능해 가장 유연하다. 이 교육의 기본 형식 |
| XML | .launch.xml | 단순한 구성에 읽기 쉽다 |
| YAML | .launch.yaml | 단순한 구성에 간결하다 |
Humble에서 ros2 launch는 세 형식을 모두 실행합니다. 모든 기능을 쓰려면 Python이 가장 편합니다.
한눈에 정리
- 런치 파일은 여러 노드와 설정을 한 번에 실행하기 위한 파일이다.
- Python 런치 파일은
generate_launch_description()에서LaunchDescription([...])을 반환하고, 노드는Node액션으로 지정한다. Node의name,namespace,parameters,remappings로 코드 수정 없이 이름과 설정을 바꾼다.- 상대 토픽 이름은 네임스페이스가 앞에 붙고, 절대 이름(
/로 시작)은 붙지 않는다. 리맵 규칙도 같은 방식으로 확장해서 비교한다. - 실행은
ros2 launch <패키지> <파일>, 인자는이름:=값으로 넘긴다.
확인 문제
Q1. 네임스페이스가 /robot1인 노드가 코드에서 'scan'이라는 토픽을 발행합니다. 실제 토픽 이름은 무엇인가요? 코드가 '/scan'이라면 어떻게 되나요?
scan은 상대 이름이라 /robot1/scan 이 됩니다. /scan 은 절대 이름이므로 네임스페이스와 관계없이 /scan 그대로입니다.
Q2. talker에는 remappings=[('chatter', 'news')]를 주고 listener에는 주지 않았습니다. 메시지가 전달될까요?
전달되지 않습니다. 리맵은 노드마다 따로 적용됩니다. talker는 news로 발행하고 listener는 여전히 chatter를 구독하므로 이름이 달라 연결되지 않습니다.
Q3. 런치 파일 실행 시 인자 값을 바꾸고 싶습니다. 런치 파일 안과 터미널에서 각각 무엇을 쓰나요?
런치 파일 안에서는 DeclareLaunchArgument로 인자를 선언하고 LaunchConfiguration으로 값을 사용합니다. 터미널에서는 ros2 launch <패키지> <파일> 이름:=값 형태로 넘깁니다.
다음 장
다음 장에서는 DDS와 QoS, ROS_DOMAIN_ID를 다룹니다. 이들은 노드들이 서로를 찾고 통신하는 바탕입니다.