ROS2Lab Chapter 06
Ch.06

Launch 파일

여러 노드를 한번에

⏱ 20분 #Launch

🎯 이 챕터의 목표

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는 다릅니다.

ROS 1
  • XML 전용 (.launch)
  • 로직 표현 어려움
  • 조건/반복문 X
ROS 2
  • 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 — 어느 패키지에서
    • executablesetup.py의 entry_point 이름
    • name — 노드의 실행 시 이름 (생략 가능)
    • output='screen' — 로그를 터미널에 표시 (안 쓰면 안 보임)

⚙ STEP 3 — setup.py에 launch 파일 포함

중요: launch 파일을 만들었다고 자동으로 설치되지 않아요. setup.pydata_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 액션들

액션
용도
Node
노드 1개 실행
가장 기본
ExecuteProcess
아무 쉘 명령 실행
ros2 service call 호출
IncludeLaunchDescription
다른 launch 파일 포함
대형 프로젝트 모듈화
DeclareLaunchArgument
실행 시 인자 받기
--use_sim_time=true 같은 옵션
GroupAction
노드 묶음
네임스페이스로 그룹화
TimerAction
딜레이 후 실행
3초 뒤 노드 시작

🧪 실용 예시 — 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" = 로봇 켜기.
✏️ 퀴즈 1

ROS2의 기본 launch 파일 언어는?

✏️ 퀴즈 2

launch 파일을 만들었는데 ros2 launch ...가 "no launch file"이라고 합니다. 가장 먼저 확인할 것은?

✏️ 퀴즈 3

같은 노드 코드를 두 번 띄우면서 토픽 이름만 다르게 하고 싶다면 launch에서 무엇을 쓰나요?

✏️ 퀴즈 4

ROS2에서 launch 파일이 반드시 구현해야 하는 함수 이름은?

🎁 정리

  • Launch = 노드 여러 개를 한 번에 띄우는 자동화 스크립트
  • ROS2는 Python launch (.launch.py)가 기본 — ROS1의 XML과 다름
  • 위치: <패키지>/launch/, 등록: setup.pydata_files
  • 핵심 함수: generate_launch_description()
  • 핵심 액션: Node, IncludeLaunchDescription, DeclareLaunchArgument, TimerAction
  • remappings로 토픽 이름 우회, parameters로 노드 설정 주입
  • 실행: ros2 launch <패키지> <파일>.launch.py <인자>:=<값>

다음은 launch에서 잠깐 언급한 파라미터를 본격적으로 다뤄요. YAML 파일로 설정값 관리하는 법. ⚙