TIKI-MINI로 시작하는 ROS2 실무
목차

1. 이론

1.7런치 파일

왜 런치 파일인가

지금까지는 노드마다 터미널을 하나씩 열어 ros2 run을 실행했습니다. 로봇 하나를 움직이려면 구동 노드, 센서 노드, 제어 노드, 시각화 도구까지 수십 개에 이르기도 합니다. 이를 매번 손으로 실행하면 번거롭고 실수하기 쉽습니다.

런치 파일(launch file) 은 "어떤 노드를 어떤 설정으로 실행할지"를 파일 하나에 적어 두는 방법입니다. 적어 둔 내용은 한 번에 실행됩니다.

  • 여러 노드를 명령 한 줄로 실행합니다.
  • 파라미터, 토픽 이름 바꾸기(리맵), 네임스페이스 같은 설정을 코드 수정 없이 파일에 모아 둡니다.
  • 같은 로봇을 두 대 띄우는 등 구성을 바꿔 재사용하기가 간편합니다.
ros2 run과 ros2 launch 비교지금까지: ros2 run터미널 1ros2 run노드 A터미널 2ros2 run노드 B터미널 3ros2 run노드 C런치: 명령 한 줄터미널 1ros2 launch런치 파일어떤 노드를어떤 설정으로노드 A노드 B노드 C
왼쪽은 노드마다 터미널을 열어 ros2 run을 실행하는 방식이고, 오른쪽은 터미널 하나에서 런치 파일로 노드 여럿을 띄우는 방식입니다.

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}] 처럼 적습니다.

네임스페이스와 리맵은 이름을 어떻게 바꾸는가

이름 규칙은 자주 헷갈리는 부분이라 직접 만져 보면서 익히는 것이 좋습니다. 먼저 규칙을 정리합니다.

  1. 노드 이름 앞에 네임스페이스가 붙어 완전한 이름이 됩니다. 이름 talker, 네임스페이스 robot1이면 /robot1/talker입니다.
  2. 노드가 코드에서 쓰는 상대 이름(chatter)은 네임스페이스가 앞에 붙어 /robot1/chatter가 됩니다. /로 시작하는 절대 이름(/chatter)은 네임스페이스의 영향을 받지 않습니다.
  3. 리맵 규칙은 양쪽 이름(from, to)을 같은 방식으로 확장한 뒤 비교하고 바꿉니다. 네임스페이스 robot1에서 ('chatter', 'news') 규칙은 /robot1/chatter를 /robot1/news로 바꿉니다.
  4. 리맵 결과로 /news 처럼 절대 이름을 쓰면 네임스페이스를 벗어납니다.
  5. 노드 이름과 네임스페이스 변경이 토픽 리맵보다 먼저 적용됩니다.

아래 도구에서 옵션을 바꿔 보세요. 표의 실제 이름, 그래프, 맨 아래의 런치 파일이 함께 바뀝니다.

namespace
remappings

바꿀 이름 (from)

새 이름 (to)

listener 노드 수

결과: 실제 이름

노드 (완전한 이름)코드 속 토픽실제 토픽
/robot1/talkerchatter/robot1/chatter
/robot1/listenerchatter/robot1/chatter
/robot1/talker/robot1/listener/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을 쓰면 다른 런치 파일을 한 줄로 불러와 재사용합니다.
런치 인자가 전달되는 순서1234터미널ros2 launchmy_pkg my.launch.pyns:=robot2인자 선언DeclareLaunchArgument이름 ns기본값 robot1값 가져오기LaunchConfiguration실행 시점에 정해짐Node 액션talker 실행namespace=ns/robot2 아래에 둠ns:=값을 생략하면 기본값 robot1이 들어갑니다
터미널에서 넘긴 ns:=robot2가 인자 선언과 LaunchConfiguration을 거쳐 Node의 namespace로 들어가는 순서입니다.

런치 파일 형식

형식확장자 예특징
Python.launch.py조건, 반복, 계산이 가능해 가장 유연하다. 이 교육의 기본 형식
XML.launch.xml단순한 구성에 읽기 쉽다
YAML.launch.yaml단순한 구성에 간결하다

Humble에서 ros2 launch는 세 형식을 모두 실행합니다. 모든 기능을 쓰려면 Python이 가장 편합니다.

한눈에 정리

  1. 런치 파일은 여러 노드와 설정을 한 번에 실행하기 위한 파일이다.
  2. Python 런치 파일은 generate_launch_description()에서 LaunchDescription([...])을 반환하고, 노드는 Node 액션으로 지정한다.
  3. Node의 name, namespace, parameters, remappings로 코드 수정 없이 이름과 설정을 바꾼다.
  4. 상대 토픽 이름은 네임스페이스가 앞에 붙고, 절대 이름(/로 시작)은 붙지 않는다. 리맵 규칙도 같은 방식으로 확장해서 비교한다.
  5. 실행은 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를 다룹니다. 이들은 노드들이 서로를 찾고 통신하는 바탕입니다.