🎯 이 챕터의 목표
Ch.04~05에서 우리는 노드를 띄울 때마다 터미널을 따로 열어서 ros2 run을 쳤어요. 노드가 2개라 그럭저럭 견딜만했죠.
근데 실제 로봇은 노드가 보통 10~30개예요. 매번 터미널 30개 열고 명령어 30번 칠 수는 없잖아요.
Launch 파일이 그 문제를 해결해요. 노드 여러 개를 한 줄 명령으로 동시에 띄우는 자동화 스크립트예요.
🧠 비유 — Launch = 연극 큐시트
· 노드 = 배우
· ros2 run = 배우 한 명한테 "지금 무대로 나가" 하고 손짓하는 것
· launch 파일 = "1번 배우 등장, 5초 뒤 2번 배우 등장, 조명은 빨강..." 적어둔 큐시트
공연이 시작되면 큐시트만 실행하면 알아서 다 나옴.
📝 ROS1 vs ROS2 — Launch 언어가 바뀌었다
ROS1을 써본 분이라면 알 텐데, ROS1의 launch는 XML이 표준이었어요. ROS2는 다릅니다.
- XML 전용 (
.launch) - 로직 표현 어려움
- 조건/반복문 X
- Python 기본 (
.launch.py) - XML도 가능 (
.launch.xml) - YAML도 가능 (
.launch.yaml) - 조건/반복/함수 다 됨
이 챕터는 Python launch를 기준으로 합니다.
📂 STEP 1 — launch 폴더 만들기
Ch.04에서 만든 my_first_pkg 안에 launch 폴더를 추가해요.
cd ~/ros2_ws/src/my_first_pkg
mkdir launch
위치 정리:
my_first_pkg/
├── package.xml
├── setup.py
├── my_first_pkg/ ← 노드 .py 파일들
│ ├── chatter_pub.py
│ └── chatter_sub.py
└── launch/ ← 🌟 여기에 launch 파일
└── chatter.launch.py
🚀 STEP 2 — 첫 Launch 파일 작성
nano launch/chatter.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='my_first_pkg',
executable='chatter_pub',
name='chatter_pub',
output='screen',
),
Node(
package='my_first_pkg',
executable='chatter_sub',
name='chatter_sub',
output='screen',
),
])
한 줄씩 풀어보기
generate_launch_description()— launch 시스템이 호출하는 약속된 함수 이름. 반드시 이 이름.LaunchDescription([...])— 실행할 액션들의 리스트.Node(package=, executable=, name=, output=)— 노드 한 개를 실행하는 액션.package— 어느 패키지에서executable—setup.py의 entry_point 이름name— 노드의 실행 시 이름 (생략 가능)output='screen'— 로그를 터미널에 표시 (안 쓰면 안 보임)
⚙ STEP 3 — setup.py에 launch 파일 포함
중요: launch 파일을 만들었다고 자동으로 설치되지 않아요. setup.py의 data_files에 등록해야 빌드 시 같이 복사돼요.
cd ~/ros2_ws/src/my_first_pkg
nano setup.py
아래 부분을 찾아서 추가/수정.
import os
from glob import glob
from setuptools import setup
package_name = 'my_first_pkg'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
# 🌟 launch 폴더의 모든 .launch.py 파일 포함
(os.path.join('share', package_name, 'launch'),
glob('launch/*.launch.py')),
],
# ... 이하 생략
)
config/ 폴더에 YAML 파라미터 파일 두는 것도 같은 방식으로:(os.path.join('share', package_name, 'config'), glob('config/*.yaml')),
🔨 STEP 4 — 빌드 & 실행
cd ~/ros2_ws
colcon build --packages-select my_first_pkg
source ~/ros2_ws/install/setup.bash
# 실행
ros2 launch my_first_pkg chatter.launch.py
[INFO] [launch]: All log files can be found below /home/...
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [chatter_pub-1]: process started with pid [12345]
[INFO] [chatter_sub-2]: process started with pid [12346]
[chatter_pub-1] [INFO] [chatter_pub]: chatter_pub 시작!
[chatter_sub-2] [INFO] [chatter_sub]: chatter_sub 시작! 메시지 기다리는 중...
[chatter_pub-1] [INFO] [chatter_pub]: 발행: "Hello ROS2 0"
[chatter_sub-2] [INFO] [chatter_sub]: 수신: "Hello ROS2 0"
...
🎉 노드 2개가 한 번에 떴고, 로그가 한 터미널에 같이 흐릅니다. Ctrl+C 한 번이면 둘 다 종료.
🎚 파라미터와 리매핑 — Launch의 진짜 매력
같은 노드를 "이름만 다르게, 토픽도 다르게" 두 번 띄우는 시나리오. 거북이 2마리 동시에 키우기:
nano launch/two_turtles.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
# 거북이 시뮬레이터 (첫번째 거북이는 자동으로 turtle1)
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtlesim',
output='screen',
),
# 거북이 자동 운전기 — turtle1 조종
Node(
package='my_first_pkg',
executable='turtle_driver',
name='driver_for_turtle1',
output='screen',
),
# 거북이 자동 운전기 — turtle2 조종 (토픽 리매핑!)
Node(
package='my_first_pkg',
executable='turtle_driver',
name='driver_for_turtle2',
remappings=[
('/turtle1/cmd_vel', '/turtle2/cmd_vel'),
],
output='screen',
),
])
remappings — 토픽 이름 바꾸기
같은 코드를 가진 노드가 다른 토픽으로 발행하게 만들 수 있어요. 위 예시는 turtle_driver가 원래 /turtle1/cmd_vel에 발행하지만, 두 번째 인스턴스는 /turtle2/cmd_vel로 보내도록 우회.
/spawn 서비스로 미리 만들어둬야 해요. launch 안에서 서비스를 호출하려면 ExecuteProcess 같은 추가 액션이 필요한데, 그건 좀 복잡해요. 우선 첫 거북이만 따로 봐도 OK.
parameters — 파라미터 주입
Node(
package='my_first_pkg',
executable='temperature_pub',
name='temp_living',
parameters=[{
'sensor_id': 'living_room_01',
'publish_rate': 2.0,
}],
output='screen',
),
노드 안에서 self.declare_parameter()로 받아쓸 수 있어요. (Ch.07에서 더 다룸)
🎁 더 쓸 만한 launch 액션들
NodeExecuteProcessIncludeLaunchDescriptionDeclareLaunchArgumentGroupActionTimerAction🧪 실용 예시 — launch 인자로 동작 바꾸기
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
# 사용자가 ros2 launch 호출 시 인자로 줄 수 있음
use_sub = DeclareLaunchArgument(
'use_sub',
default_value='true',
description='Listener 노드도 같이 띄울지 여부',
)
return LaunchDescription([
use_sub,
Node(
package='my_first_pkg',
executable='chatter_pub',
output='screen',
),
Node(
package='my_first_pkg',
executable='chatter_sub',
output='screen',
condition=..., # LaunchConfiguration('use_sub')가 true일 때만
),
])
실행 시:
ros2 launch my_first_pkg chatter.launch.py use_sub:=false
이렇게 하면 Pub만 띄우고 Sub는 안 뜨도록 제어할 수 있어요.
📝 명명 관례
- launch 파일 확장자:
.launch.py/.launch.xml/.launch.yaml - 위치:
<패키지>/launch/폴더 - 이름: 보통
<목적>.launch.py형식. 예:bringup.launch.py,simulation.launch.py - 관행: 로봇 전체를 한 번에 띄우는 launch는
bringup.launch.py라 부름. "bring up the robot" = 로봇 켜기.
ROS2의 기본 launch 파일 언어는?
launch 파일을 만들었는데 ros2 launch ...가 "no launch file"이라고 합니다. 가장 먼저 확인할 것은?
같은 노드 코드를 두 번 띄우면서 토픽 이름만 다르게 하고 싶다면 launch에서 무엇을 쓰나요?
ROS2에서 launch 파일이 반드시 구현해야 하는 함수 이름은?
🎁 정리
- Launch = 노드 여러 개를 한 번에 띄우는 자동화 스크립트
- ROS2는 Python launch (
.launch.py)가 기본 — ROS1의 XML과 다름 - 위치:
<패키지>/launch/, 등록:setup.py의data_files - 핵심 함수:
generate_launch_description() - 핵심 액션:
Node,IncludeLaunchDescription,DeclareLaunchArgument,TimerAction remappings로 토픽 이름 우회,parameters로 노드 설정 주입- 실행:
ros2 launch <패키지> <파일>.launch.py <인자>:=<값>
다음은 launch에서 잠깐 언급한 파라미터를 본격적으로 다뤄요. YAML 파일로 설정값 관리하는 법. ⚙