본문으로 바로가기

launch 파일에서 실행 인자 사용하기 #2

category 강좌/ROS2 2026. 5. 29. 18:21

4. launch 파일에서 remapping 사용하기

ROS 2에서 토픽 이름을 바꿔 연결해야 하는 경우가 많습니다.

이때 launch 파일에서 remapping을 사용할 수 있습니다.

 

예를 들어 어떤 노드가 /cmd_vel 토픽을 구독하도록 만들어져 있는데, 실제 turtlesim은 /turtle1/cmd_vel을 사용한다고 가정해 보겠습니다.

 

이 경우 launch 파일에서 다음처럼 remapping할 수 있습니다.

 

Node(
    package='my_first_package',
    executable='my_publisher',
    output='screen',
    remappings=[
        ('/cmd_vel', '/turtle1/cmd_vel')
    ]
)
 

 

형식은 다음과 같습니다.

 
remappings=[
    ('기존_토픽_이름', '변경할_토픽_이름')
]
 

 

 

먼저 패키지 폴더로 이동하여 새로운 노드 파일을 생성합니다.

 

cd ~/ros2_study/src/my_first_packages/my_first_packages

touch cmd_vel_publisher.py

 

 

cmd_vel_publisher.py 을 열어 다음 코드를 작성합니다.

 
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist


class CmdVelPublisher(Node):

    def __init__(self):
        super().__init__('cmd_vel_publisher')

        self.publisher_ = self.create_publisher(
            Twist,
            '/cmd_vel',
            10
        )

        self.timer = self.create_timer(1.0, self.timer_callback)

    def timer_callback(self):
        msg = Twist()

        msg.linear.x = 1.0
        msg.angular.z = 0.5

        self.publisher_.publish(msg)

        self.get_logger().info(
            'Publish velocity command to /cmd_vel'
        )


def main(args=None):
    rclpy.init(args=args)

    node = CmdVelPublisher()
    rclpy.spin(node)

    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()
 

 

이 노드는 /cmd_vel 토픽으로 geometry_msgs/msg/Twist 메시지를 발행합니다.

 

코드의 주요부분은 아래와 같습니다.

 
self.publisher_ = self.create_publisher(
    Twist,
    '/cmd_vel',
    10
)
 

 

위의 소스는 /cmd_vel에 publish합니다.

나중에 launch 파일에서 이 토픽 이름을 바꿀 수 있습니다.

 

 

 

상위 패키지 폴더로 이동합니다.

 
cd ~/ros2_study/src/my_first_packages
 

 

setup.py 파일을 엽니다.

entry_points 부분에 cmd_vel_publisher를 추가합니다.

 

기존 코드가 다음과 비슷하다면,

 
아래를 추가합니다.
 
entry_points={
    'console_scripts': [
        'cmd_vel_publisher = my_first_package.cmd_vel_publisher:main',
    ],
},
 

 

이제 ROS 2에서 다음 명령으로 새 노드를 실행할 수 있습니다.

 

컴파일하고 작업공간을 활성화하면 아래와 긑은 명령으로 실행할 수 있습니다.

 
ros2 run my_first_package cmd_vel_publisher
 
 

 

5. 새로운 remapping launch 파일 생성

launch 파일을 새로 만듭니다.

 
touch launch/cmd_vel_remap.launch.py
 

 

다음 내용을 작성합니다.

 
from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():

    cmd_vel_publisher_node = Node(
        package='my_first_package',
        executable='cmd_vel_publisher',
        output='screen',
        remappings=[
            ('/cmd_vel', '/turtle1/cmd_vel')
        ]
    )

    return LaunchDescription([
        cmd_vel_publisher_node
    ])
 

 

아래의 코드가 주요 부분입니다.

 
remappings=[
    ('/cmd_vel', '/turtle1/cmd_vel')
]
 

 

뜻은 다음과 같습니다.

 
remappings=[
    ('기존_토픽_이름', '변경할_토픽_이름')
]
 

 

즉, cmd_vel_publisher 노드 안에서는 /cmd_vel을 사용하지만, 실제 실행할 때는 /turtle1/cmd_vel로 바뀝니다.

 

 

ros2 launch 명령으로 launch 파일을 실행하려면 setup.py에 launch 파일 설치 설정이 있어야 합니다.

 

setup.py 상단에 다음 import가 있는지 확인합니다.

 
import os
from glob import glob
from setuptools import setup
 

 

그리고 data_files에 다음 줄이 있어야 합니다.

 
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
 

 

예시는 다음과 같습니다.

 
data_files=[
    ('share/ament_index/resource_index/packages',
        ['resource/' + package_name]),
    ('share/' + package_name, ['package.xml']),
    (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
],
 

 

없다면 추가합니다.

 

 

워크스페이스 루트로 이동합니다.

 
cd ~/ros2_study
 

 

빌드합니다.

 
colcon build --packages-select my_first_package
 

 

환경 설정을 다시 적용합니다.

 
source install/setup.bash
 

 

 

새 터미널을 열고 다음 명령을 실행합니다.

 
ros2 run turtlesim turtlesim_node
 

 

turtlesim_node는 기본적으로 /turtle1/cmd_vel 토픽을 구독합니다.

 

 

다른 터미널에서 다음 명령을 실행합니다.

 
cd ~/ros2_study
source install/setup.bash
ros2 launch my_first_package cmd_vel_remap.launch.py
 

 

그러면 cmd_vel_publisher 노드는 코드상으로는 /cmd_vel에 publish하지만, launch 파일의 remapping 때문에 실제로는

/turtle1/cmd_vel에 publish합니다.

 

결과적으로 turtlesim의 거북이가 움직입니다.

 

 

토픽 목록을 확인합니다.

 
ros2 topic list
 

 

다음 토픽이 보이면 정상입니다.

 
/turtle1/cmd_vel
 

 

메시지를 직접 확인하려면 다음 명령을 사용합니다.

 
ros2 topic echo /turtle1/cmd_vel
 

 

출력 예시는 다음과 같습니다.

 
linear:
  x: 1.0
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.5
 

이번에는 launch 파일을 사용하지 않고 노드를 직접 실행해 봅니다.

 
ros2 run my_first_package cmd_vel_publisher
 

 

이 경우에는 remapping이 적용되지 않습니다.

 

따라서 노드는 그대로 /cmd_vel 토픽에 메시지를 발행합니다.

 

확인 명령:

 
ros2 topic echo /cmd_vel
 

 

이 경우 turtlesim은 움직이지 않습니다.

 

이유는 turtlesim이 /cmd_vel이 아니라 /turtle1/cmd_vel을 구독하기 때문입니다.

 

실무에서 remapping은 매우 자주 사용됩니다.

 

예를 들어 모바일 로봇에서는 다음처럼 토픽 이름이 달라질 수 있습니다.

/cmd_vel
/robot1/cmd_vel
/robot2/cmd_vel
/scan
/robot1/scan
/odom
/robot1/odom
 

 

코드를 매번 수정하는 것은 좋지 않습니다.

 

토픽 이름만 다른 경우에는 launch 파일에서 remapping으로 해결하는 것이 더 깔끔합니다.

 

예를 들어 로봇 제어 노드는 /cmd_vel을 사용하도록 만들고, 실제 로봇마다 launch 파일에서 다음처럼 바꿀 수 있습니다.

 
remappings=[
    ('/cmd_vel', '/robot1/cmd_vel')
]
 

 

이렇게 하면 같은 제어 노드를 여러 로봇에 재사용할 수 있습니다.

 

 

 

5. launch 파일에서 namespace를 변수로 받기

여러 대의 모바일 로봇을 운용할 때는 namespace가 중요합니다.

 

예를 들어 모바일 로봇 1번과 모바일 로봇 2번이 있다고 가정하겠습니다.

/robot1/camera/image_raw
/robot1/scan
/robot1/odom
/robot1/cmd_vel
/robot1/navigation_state

/robot2/camera/image_raw
/robot2/scan
/robot2/odom
/robot2/cmd_vel
/robot2/navigation_state
 

 

이런 구조를 만들려면 launch 파일에서 namespace를 외부 인자로 받는 방식이 좋습니다.

 

먼저 namespace_test.launch.py 파일을 생성합니다.

 

cd ros2_study/src/my_first_package/launch/

touch namespace_test.launch.py

tree
 

새롭게 생성한 파일을 열어 아래의 내요을 작성합니다

 

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
    namespace = LaunchConfiguration('namespace')

    return LaunchDescription([
        DeclareLaunchArgument(
            'namespace',
            default_value='robot1',
            description='Robot namespace'
        ),

        Node(
            package='turtlesim',
            executable='turtlesim_node',
            namespace=namespace,
            output='screen'
        ),

        Node(
            package='my_first_package',
            executable='my_publisher',
            namespace=namespace,
            output='screen'
        )
    ])
 

 

빌드합니다.

 
colcon build --packages-select my_first_package
 

 

환경 설정을 다시 적용합니다.

 
source install/setup.bash
 

 

새 터미널을 열고 실행은 다음처럼 합니다.

 
ros2 launch my_first_package namespace_test.launch.py namespace:=robot1
 

 

 

 

다른 이름으로 실행하고 싶다면 다음처럼 바꿉니다.

 
ros2 launch my_first_package namespace_test.launch.py namespace:=robot2
 
 

 

이 방식은 다중 로봇 시스템에서 거의 필수입니다.

 

특히 모바일 로봇, AMR, 물류 로봇, 서빙 로봇처럼 여러 대가 동시에 운용되는 시스템에서는 namespace 설계가 제대로 되어 있지 않으면 토픽이 섞이고 디버깅이 어려워집니다.

 

 

 

6. launch 파일에서 조건부 실행하기

항상 모든 노드를 실행해야 하는 것은 아닙니다.

 

예를 들어 카메라 노드는 필요할 때만 실행하고 싶을 수 있습니다.

 

이럴 때는 조건부 실행을 사용할 수 있습니다.

 

카메라 노드 파일을 생성합니다.

 

cd ~/ros2_study/src/my_first_package/my_first_package

touch camera_node.py
 
camera_node.py를 작성합니다.
 
import rclpy
from rclpy.node import Node


class CameraNode(Node):
    def __init__(self):
        super().__init__('camera_node')

        self.get_logger().info('camera_node has been started.')
        self.get_logger().info('This is a dummy camera node for launch condition test.')

        self.timer = self.create_timer(1.0, self.timer_callback)
        self.count = 0

    def timer_callback(self):
        self.count += 1
        self.get_logger().info(f'Dummy camera node is running... count: {self.count}')


def main(args=None):
    rclpy.init(args=args)

    node = CameraNode()

    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        node.get_logger().info('camera_node stopped by user.')
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()
 
 

setup.py를 열고 entry_points 부분에 아래 내용을 추가합니다.

 
entry_points={
    'console_scripts': [
        'camera_node = my_first_package.camera_node:main',
    ],
},
 
 
빌드하고 환경을 설정합니다.
 
cd ~/ros2_study

colcon build --packages-select my_first_package
source install/setup.bash
 
 
 

namespace_test.launch.py 파일을 생성합니다.

 

cd ros2_study/src/my_first_package/launch/

touch camera_test.launch.py

tree

 

새롭게 생성한 파일을 열어 아래의 내요을 작성합니다

 

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
    use_camera = LaunchConfiguration('use_camera')

    return LaunchDescription([
        DeclareLaunchArgument(
            'use_camera',
            default_value='true',
            description='Whether to launch camera node'
        ),

        Node(
            package='my_first_package',
            executable='camera_node',
            output='screen',
            condition=IfCondition(use_camera)
        )
    ])
 

 

카메라 노드를 실행하려면 다음처럼 합니다.

 
ros2 launch my_first_package camera_test.launch.py use_camera:=true
 

 

카메라 노드를 실행하지 않으려면 다음처럼 합니다.

 
ros2 launch my_first_package camera_test.launch.py use_camera:=false
 

 

위의 실행결과 use_camera:=false로 실행했기 때문에 camera_node가 실행되지 않습니다. 그래서 아래처럼 launch 자체 로그만 나오고,

[INFO] [launch]: All log files can be found below ...
[INFO] [launch]: Default logging verbosity is set to INFO
 

camera_node 관련 로그가 안 나오는 것이 정상입니다.

 

 

모바일 로봇 시스템에서는 다음과 같은 조건부 실행이 유용합니다.

use_lidar
use_camera
use_imu
use_nav2
use_slam
use_rviz
use_robot_state_publisher
use_logger
use_sim_time
 

 

예를 들어 라이다만 테스트할 때는 카메라와 내비게이션 노드를 꺼도 됩니다.

 

SLAM 테스트를 할 때는 use_slam:=true, 저장된 맵 기반 주행을 할 때는 use_slam:=false로 분리할 수 있습니다.

 

개발 단계에서는 모든 기능을 한 번에 켜기보다 필요한 기능만 켜는 것이 안정적입니다.

 

 

 

7. 다른 launch 파일 포함하기

프로젝트가 커지면 launch 파일 하나에 모든 노드를 넣는 방식은 관리가 어렵습니다.

 

이럴 때는 launch 파일을 기능별로 나누고, 상위 launch 파일에서 다른 launch 파일을 포함할 수 있습니다.

 

예를 들어 다음과 같이 구성할 수 있습니다.

my_first_package/
├── launch/
│   ├── bringup.launch.py
│   ├── sensor.launch.py
│   ├── navigation.launch.py
│   └── control.launch.py
 

 

sensor.launch.py에는 센서 관련 노드를 넣습니다.

navigation.launch.py에는 경로 계획과 주행 관련 노드를 넣습니다.

control.launch.py에는 모터 제어, 속도 제어, 주행 명령 처리 노드를 넣습니다.

 

그리고 bringup.launch.py에서 모두 포함합니다.

 
import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource


def generate_launch_description():
    package_dir = get_package_share_directory('my_first_package')

    dist_tutle_action_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(package_dir, 'launch', 'dist_turtle_action.launch.py')
        )
    )

    tutlesim_teleop_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(package_dir, 'launch', 'turtlesim_and_teleop.launch.py')
        )
    )

    return LaunchDescription([
        dist_tutle_action_launch,
        tutlesim_teleop_launch,
    ])
 

 

실행은 하나만 하면 됩니다.

 
ros2 launch my_first_package bringup.launch.py
 

 

이 방식은 실무에서 많이 사용됩니다.

 

특히 모바일 로봇 시스템이라면 다음처럼 나누는 것이 좋습니다.

robot_description.launch.py
sensors.launch.py
slam.launch.py
navigation.launch.py
control.launch.py
localization.launch.py
rviz.launch.py
bringup.launch.py
 

 

최종 실행은 다음처럼 단순해집니다.

 
ros2 launch mobile_robot_bringup bringup.launch.py
 

 

이렇게 구성하면 전체 시스템 실행은 bringup.launch.py 하나로 처리하고, 센서나 내비게이션만 따로 테스트할 때는 개별 launch 파일을 실행할 수 있습니다.

 

 

 

8. 모바일 로봇 시스템 예시 launch 구성

모바일 로봇 시스템을 예로 들면 launch 파일은 다음처럼 구성할 수 있습니다.

mobile_robot_bringup/
├── launch/
│   ├── bringup.launch.py
│   ├── robot_description.launch.py
│   ├── sensors.launch.py
│   ├── control.launch.py
│   ├── slam.launch.py
│   ├── localization.launch.py
│   ├── navigation.launch.py
│   └── rviz.launch.py
├── config/
│   ├── robot_params.yaml
│   ├── lidar_params.yaml
│   ├── controller_params.yaml
│   ├── slam_params.yaml
│   └── nav2_params.yaml
├── maps/
│   └── warehouse_map.yaml
└── urdf/
    └── mobile_robot.urdf.xacro
 

 

각 launch 파일의 역할은 다음과 같습니다.

robot_description.launch.py
- URDF 또는 Xacro 기반 로봇 모델 실행
- robot_state_publisher 실행
- joint_state_publisher 실행

sensors.launch.py
- 라이다 노드 실행
- 카메라 노드 실행
- IMU 노드 실행

control.launch.py
- 모터 제어 노드 실행
- cmd_vel 기반 속도 제어 노드 실행
- odom 발행 노드 실행

slam.launch.py
- SLAM 노드 실행
- 라이다 데이터를 이용한 지도 작성

localization.launch.py
- 저장된 map 파일 로드
- AMCL 또는 위치 추정 노드 실행

navigation.launch.py
- Nav2 관련 노드 실행
- global planner 실행
- local planner 실행
- behavior tree navigator 실행

rviz.launch.py
- RViz 실행
- 로봇 상태, 라이다, 지도, 경로 시각화

bringup.launch.py
- 전체 launch 파일을 하나로 묶어서 실행
 

 

최종 실행 명령은 다음처럼 단순하게 만들 수 있습니다.

 
ros2 launch mobile_robot_bringup bringup.launch.py robot_id:=robot1 use_lidar:=true use_camera:=false use_rviz:=true
 

 

이런 구조가 되면 시스템 실행이 편해집니다.

 

또한 문제가 생겼을 때 특정 기능만 따로 실행해서 디버깅할 수 있습니다.

 

예를 들어 센서만 테스트하고 싶다면 다음처럼 실행합니다.

 
ros2 launch mobile_robot_bringup sensors.launch.py
 

 

제어 노드만 테스트하고 싶다면 다음처럼 실행합니다.

 
ros2 launch mobile_robot_bringup control.launch.py
 

 

SLAM만 테스트하고 싶다면 다음처럼 실행합니다.

 
ros2 launch mobile_robot_bringup slam.launch.py
 

 

저장된 지도 기반 내비게이션만 테스트하고 싶다면 다음처럼 실행합니다.

 
ros2 launch mobile_robot_bringup navigation.launch.py map:=warehouse_map.yaml
 

 

이렇게 기능별로 launch 파일을 나누면 개발 속도가 빨라지고 문제 원인을 찾기 쉬워집니다.

 

 

728x90
728x90

'강좌 > ROS2' 카테고리의 다른 글

10일차 강의  (0) 2026.05.31
9일차 강의  (0) 2026.05.31
8일차 강의  (0) 2026.05.29
Cancel 테스트용 Action Client 만들기  (0) 2026.05.26
ROS 2 Python Topic 실습 : RobotStatus 메시지로 로봇 상태 주고받기  (0) 2026.05.26