ROS2의 런치 시스템은 노드 및 시스템의 실행을 제어하는 매우 강력한 도구이다. 특히 런치 파일을 통해 조건부 실행을 설정하면, 특정 상황에 따라 노드를 실행하거나 중지할 수 있는 유연한 구성이 가능한다. 이 섹션에서는 런치 파일을 통해 조건부 실행을 구현하는 방법에 대해 상세히 설명한다.

조건부 실행의 개념

조건부 실행은 런치 파일 내에서 특정 조건이 충족될 때에만 노드나 그룹이 실행되도록 하는 기법이다. 이러한 조건은 사용자 입력, 특정 파라미터 값, 또는 환경 변수에 따라 설정될 수 있다. 기본적으로 launch.actions 라이브러리의 조건 객체를 사용하여 조건부 실행을 구현한다.

ROS2 런치 파일에서 조건부 실행을 설정하는 주요 구문은 IfConditionUnlessCondition이다. IfCondition은 조건이 참일 때 실행되며, UnlessCondition은 조건이 거짓일 때 실행된다.

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.conditions import IfCondition, UnlessCondition

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='demo_nodes_cpp',
            executable='talker',
            name='talker',
            condition=IfCondition('true')  # 조건이 참일 때만 실행
        ),
        Node(
            package='demo_nodes_cpp',
            executable='listener',
            name='listener',
            condition=UnlessCondition('false')  # 조건이 거짓일 때만 실행
        )
    ])

위 예시에서는 talker 노드가 조건이 참(true)일 때 실행되고, listener 노드는 조건이 거짓(false)일 때 실행된다.

런치 파일의 조건 설정

런치 파일에서 조건을 설정하는 방법에는 여러 가지가 있다. 주로 환경 변수, 파라미터 값, 또는 런치 파일 인수를 사용하여 조건을 결정할 수 있다.

환경 변수에 따른 조건부 실행

환경 변수를 기반으로 노드의 실행 여부를 결정할 수 있다. 예를 들어, 특정 환경 변수가 설정되어 있을 때에만 노드를 실행하고자 할 때, 아래와 같이 EnvironmentVariable을 사용할 수 있다.

from launch.actions import DeclareLaunchArgument
from launch.conditions import IfCondition
from launch_ros.actions import Node
from launch import LaunchDescription
from os import environ

def generate_launch_description():
    environ['MY_ENV_VAR'] = '1'
    return LaunchDescription([
        DeclareLaunchArgument('use_env_var', default_value='false'),
        Node(
            package='demo_nodes_cpp',
            executable='talker',
            name='talker',
            condition=IfCondition(environ['MY_ENV_VAR'])  # 환경 변수에 따라 실행
        )
    ])

이 코드에서 MY_ENV_VAR 환경 변수가 '1'로 설정되어 있을 때에만 talker 노드가 실행된다. 환경 변수는 시스템 설정이나 배포 환경에 따라 달라질 수 있기 때문에, 매우 유연한 조건부 실행을 구현할 수 있다.

런치 파일 인수를 통한 조건 설정

런치 파일 인수를 통해 조건부 실행을 설정하는 방법도 매우 유용하다. 사용자가 런치 파일을 실행할 때 인수를 제공하면, 그 인수에 따라 특정 노드를 실행할지 여부를 결정할 수 있다.

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

def generate_launch_description():
    return LaunchDescription([
        DeclareLaunchArgument('launch_talker', default_value='false'),
        Node(
            package='demo_nodes_cpp',
            executable='talker',
            name='talker',
            condition=IfCondition(LaunchConfiguration('launch_talker'))  # 인수에 따라 실행
        )
    ])

이 예시에서는 launch_talker라는 런치 파일 인수를 통해 talker 노드의 실행 여부를 결정한다. 런치 파일 실행 시 --launch_talker true와 같은 명령어 인수를 통해 해당 노드를 실행할 수 있다.

조건부 실행에 대한 시각화

다음은 조건부 실행 흐름을 시각화한 다이어그램이다.

graph TD; Start["런치 파일 실행"] --> Condition1{"조건 1"} Condition1 -->|참| NodeA["노드 A 실행"] Condition1 -->|거짓| Condition2{"조건 2"} Condition2 -->|참| NodeB["노드 B 실행"] Condition2 -->|거짓| End["실행 종료"]

이 다이어그램에서는 두 가지 조건을 통해 노드 A 또는 노드 B를 실행하거나, 아무 것도 실행하지 않고 종료하는 흐름을 시각적으로 나타냈다.

UnlessCondition을 사용한 조건부 실행

앞서 설명한 IfCondition과 함께, UnlessCondition을 통해 조건이 거짓일 때 노드 실행을 제어할 수 있다. 예를 들어, 특정 조건이 충족되지 않았을 때 실행을 방지하는 경우에 유용하다.

from launch.actions import DeclareLaunchArgument
from launch.conditions import UnlessCondition
from launch_ros.actions import Node
from launch import LaunchDescription

def generate_launch_description():
    return LaunchDescription([
        DeclareLaunchArgument('run_listener', default_value='true'),
        Node(
            package='demo_nodes_cpp',
            executable='listener',
            name='listener',
            condition=UnlessCondition(LaunchConfiguration('run_listener'))  # 조건이 거짓일 때 실행
        )
    ])

이 코드는 run_listener 인수가 false일 때만 listener 노드가 실행되도록 한다. 이를 통해 IfCondition과는 반대로, 특정 상황에서 노드 실행을 방지할 수 있다.

조건부 실행을 위한 여러 조건 결합

여러 조건을 결합하여 보다 복잡한 조건부 실행을 구현할 수도 있다. launch.conditions 모듈의 LaunchConfigurationEquals, LaunchConfigurationNotEquals, IfCondition, UnlessCondition 등의 조건을 조합하여 다양한 조건부 실행을 설정할 수 있다.

다중 조건 예시

아래 코드는 LaunchConfigurationEqualsIfCondition을 결합하여 노드 실행을 제어하는 예시이다.

from launch.conditions import IfCondition, LaunchConfigurationEquals
from launch_ros.actions import Node
from launch import LaunchDescription

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='demo_nodes_cpp',
            executable='talker',
            name='talker',
            condition=IfCondition(
                LaunchConfigurationEquals('use_talker', 'true')  # 조건이 'true'일 때 실행
            )
        )
    ])

위 코드는 use_talker 값이 true일 때만 talker 노드를 실행한다. 이와 같은 방법으로 여러 조건을 결합하여 실행 로직을 더욱 세밀하게 제어할 수 있다.

조건부 실행의 실제 사례

조건부 실행은 대규모 시스템에서 특히 유용하다. 예를 들어, 특정 하드웨어가 연결되어 있을 때에만 관련 노드를 실행하는 조건을 설정하거나, 테스트 환경에서는 일부 노드 실행을 건너뛰는 조건을 설정할 수 있다.

하드웨어 연결 여부에 따른 조건부 실행

다음은 특정 하드웨어 장치가 연결되어 있을 때에만 관련 노드를 실행하는 예시이다.

from launch_ros.actions import Node
from launch.conditions import IfCondition
from os.path import exists

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='hardware_interface',
            executable='sensor_node',
            name='sensor_node',
            condition=IfCondition(str(exists('/dev/sensor_device')))  # 장치가 연결되어 있을 때만 실행
        )
    ])

이 코드는 /dev/sensor_device 경로에 하드웨어가 존재하는 경우에만 sensor_node를 실행한다. 이를 통해 실제 하드웨어 상태에 따라 런치 파일에서 노드의 실행 여부를 제어할 수 있다.

조건부 실행의 한계 및 고려 사항

런치 파일을 통해 조건부 실행을 설정할 때 고려해야 할 몇 가지 중요한 사항이 있다.

  1. 조건 평가 시점: 조건은 런치 파일이 시작될 때 한 번 평가되며, 그 이후에는 조건이 변경되어도 해당 조건에 따른 실행 흐름이 바뀌지 않는다. 즉, 런치 파일 실행 중에는 조건이 동적으로 변경되지 않는다는 점을 염두에 두어야 한다.

  2. 복잡한 논리 조합: 조건이 복잡해질수록 런치 파일의 유지 보수가 어려워질 수 있다. 따라서 조건이 많아질 경우, 이를 깔끔하게 관리할 수 있는 별도의 로직이나 스크립트를 활용하는 것이 좋다.

  3. 실행 성능: 다수의 조건이 포함된 런치 파일은 시스템 리소스를 많이 소모할 수 있다. 특히, 조건에 따라 많은 노드가 동시에 실행되는 경우 시스템 성능에 미치는 영향을 고려해야 한다.

조건부 실행을 위한 런치 파일 템플릿

ROS2 런치 파일에서 조건부 실행을 구성할 때, 일관된 패턴을 따르는 것이 유지보수에 유리한다. 다음은 조건부 실행을 자주 활용하는 경우 사용할 수 있는 런치 파일 템플릿이다.

기본 템플릿 예시

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

def generate_launch_description():
    # 런치 인수 정의
    launch_arguments = [
        DeclareLaunchArgument(
            'run_node_1',
            default_value='true',
            description='노드 1 실행 여부 결정'
        ),
        DeclareLaunchArgument(
            'run_node_2',
            default_value='false',
            description='노드 2 실행 여부 결정'
        )
    ]

    # 노드 정의 및 조건부 실행 설정
    nodes = [
        Node(
            package='example_package',
            executable='node_1',
            name='node_1',
            condition=IfCondition(LaunchConfiguration('run_node_1'))  # run_node_1이 true일 때 실행
        ),
        Node(
            package='example_package',
            executable='node_2',
            name='node_2',
            condition=UnlessCondition(LaunchConfiguration('run_node_2'))  # run_node_2가 false일 때 실행
        )
    ]

    return LaunchDescription(launch_arguments + nodes)

이 템플릿에서는 두 개의 런치 인수를 정의하여, 사용자가 인수 값을 설정함에 따라 노드의 실행 여부를 조건부로 제어할 수 있다. 각 노드는 IfConditionUnlessCondition을 사용하여 실행된다.

조건부 실행 시 고려해야 할 다른 요소들

런치 파일에서 외부 조건 활용

ROS2 런치 파일은 내부 조건 외에도 외부의 여러 상태나 이벤트에 따라 노드 실행 여부를 결정할 수 있다. 이 때에는 Python 스크립트 내에서 특정 상태를 확인한 후 런치 파일에 반영하는 방법을 사용할 수 있다.

예를 들어, 네트워크 연결 상태를 확인하고, 연결이 있을 경우에만 특정 노드를 실행하는 상황을 고려할 수 있다.

from launch_ros.actions import Node
from launch.conditions import IfCondition
import socket

def check_network():
    try:
        # Google DNS 서버에 핑을 시도하여 네트워크 연결 상태를 확인
        socket.create_connection(("8.8.8.8", 53))
        return True
    except OSError:
        return False

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='network_monitor',
            executable='network_node',
            name='network_node',
            condition=IfCondition(str(check_network()))  # 네트워크가 연결된 경우에만 실행
        )
    ])

이 코드에서 check_network() 함수는 Google의 DNS 서버로의 연결을 시도하여 네트워크 상태를 확인하고, 그 결과에 따라 network_node의 실행 여부를 결정한다.

실행 중 조건 변경

런치 파일의 한계 중 하나는 실행 중 조건이 변경되었을 때 해당 조건이 반영되지 않는다는 점이다. 실행 중에도 조건에 따라 노드를 활성화하거나 비활성화하는 기능을 원할 경우, 노드 내에서 직접 조건부 로직을 처리하거나, ROS2 서비스나 액션을 통해 동적 제어를 구현하는 것이 필요하다.

노드 그룹의 조건부 실행

때로는 개별 노드뿐만 아니라, 여러 노드를 그룹으로 묶어 특정 조건에 따라 실행할 필요가 있다. 이 경우에는 GroupAction을 사용하여 그룹화된 노드에 조건을 적용할 수 있다.

from launch.actions import GroupAction
from launch_ros.actions import Node
from launch.conditions import IfCondition
from launch import LaunchDescription

def generate_launch_description():
    return LaunchDescription([
        GroupAction(
            actions=[
                Node(
                    package='example_package',
                    executable='node_1',
                    name='node_1',
                ),
                Node(
                    package='example_package',
                    executable='node_2',
                    name='node_2',
                )
            ],
            condition=IfCondition('true')  # 그룹 전체를 조건부로 실행
        )
    ])

이 코드는 node_1node_2를 그룹으로 묶어 IfCondition에 따라 실행 여부를 결정한다. 그룹화된 노드들은 동일한 조건을 공유하며 실행되거나 중지된다.

복잡한 시스템에서 조건부 실행 사용 사례

대규모 로봇 시스템에서의 조건부 실행

대규모 로봇 시스템에서는 여러 센서 및 장치가 동시에 작동하며, 특정 조건에 따라 노드의 실행을 제어해야 하는 경우가 많다. 예를 들어, 로봇의 센서가 고장 나거나 연결이 끊어진 경우, 해당 센서의 노드 실행을 중지하고 다른 대체 센서 노드를 실행하는 방식으로 시스템을 동적으로 관리할 수 있다.

from launch_ros.actions import Node
from launch.conditions import IfCondition
from os.path import exists

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='robot_sensors',
            executable='primary_sensor',
            name='primary_sensor',
            condition=IfCondition(str(exists('/dev/primary_sensor')))  # 메인 센서가 있을 때 실행
        ),
        Node(
            package='robot_sensors',
            executable='backup_sensor',
            name='backup_sensor',
            condition=UnlessCondition(str(exists('/dev/primary_sensor')))  # 메인 센서가 없을 때 백업 센서 실행
        )
    ])

이 예시에서는 /dev/primary_sensor 장치가 있을 때 메인 센서 노드가 실행되며, 장치가 없을 경우 백업 센서 노드가 대신 실행된다. 이를 통해 시스템의 안정성을 높이고, 다양한 환경에 대응할 수 있는 유연한 구성을 할 수 있다.

조건부 실행에서의 오류 처리 및 예외 사항

조건부 실행이 항상 예상대로 동작하는 것은 아니므로, 오류 처리가 필요할 수 있다. 조건이 만족되지 않아 노드가 실행되지 않을 경우의 로그 처리나, 특정 조건이 충족되지 않았을 때 사용자에게 경고를 표시하는 방식 등을 추가로 고려할 수 있다.