제14부 시스템 설계·품질·운영
14.3 launch 심층 I — 구조와 substitution
14.3.1 이 장에서 배우는 것 / 선수 지식
4.9에서 launch의 기본을 다뤘다. 이 장은 왜 launch가 그렇게 생겼는지 — 그 설계의 근본을 분석한다.
핵심 개념 하나를 먼저 세운다. launch 파일은 스크립트가 아니라 "실행 계획을 기술하는 것"이다. generate_launch_description()은 실행 시점에 한 번 호출되어 액션의 목록을 만들 뿐이고, 실제 실행은 그다음에 launch 시스템이 한다. 이 구분을 모르면 "왜 내 if 문이 동작하지 않는가"라는 벽에 부딪힌다 — ROS 2 launch 학습의 최대 난관이다.
배우는 것:
- 실행 모델 — 기술 시점과 실행 시점.
- substitution — 지연 평가의 핵심.
- 주요 substitution — 무엇이 있는가.
- 조건 —
IfCondition과 그 이유. - 액션 — 무엇을 할 수 있는가.
- 이벤트 — 반응형 구성.
- 파라미터 전달 — 흔한 함정들.
- 디버깅 — launch가 안 될 때.
선수 지식: 0.6(패키지·launch 첫 경험), 1.9(파라미터), 1.1.5(리매핑), 1.13(컴포지션).
정직: substitution·condition 클래스는 Jazzy에서 전수 조회하고 조건 평가를 실제로 실행해 확인했다(실행 확인 — 14.3.12). 그 결과 이 장이 다루지 않던 substitution 여러 개와, 폐기 예정 클래스 두 개를 찾았다. 93차에 함정 문단 하나를 추가로 재현하다가 서술 오류를 찾아 정정했다(14.3.12.4) — 기본값 없는
LaunchConfiguration은 "빈 문자열이 됐다가 종료되는" 것이 아니라 참조하는 순간 즉시 예외를 던진다. 표기가 없는 나머지 서술(실행 모델·이벤트·구조 설명)은 문서·소스에 근거하며 실행 검증하지 않았다(목차 3).
14.3.2 실행 모델 — 두 시점
이것이 이 장에서 가장 중요한 개념이다.
| 시점 | 무슨 일이 |
|---|---|
| ① 기술 | Python 코드가 실행되어 액션 목록을 만든다 |
| ② 실행 | launch가 액션을 하나씩 수행하며 substitution을 평가 |
결정적 함의:
| 사실 | 결과 |
|---|---|
| launch 인자 값은 ①에서 모른다 | Python if로 분기 불가 |
| substitution은 ②에서 평가 | 값이 필요하면 substitution으로 |
| ①의 Python 변수는 상수 | 실행 중 변하지 않음 |
LaunchConfiguration은 값이 아니라 "참조" | 문자열 연산 불가 |
# ✗ 동작하지 않는다 — 기술 시점에는 값이 없다
use_sim = LaunchConfiguration('use_sim_time')
if use_sim == 'true': # ✗ 항상 False (객체 비교)
nodes = [sim_node]
else:
nodes = [real_node]
# ✓ 조건은 substitution으로 표현
Node(package='p', executable='sim_node',
condition=IfCondition(LaunchConfiguration('use_sim_time')))
Node(package='p', executable='real_node',
condition=UnlessCondition(LaunchConfiguration('use_sim_time')))
견해: "launch 인자로 분기하려면
condition을 쓴다"가 규칙이다. Pythonif는 기술 시점에 이미 알 수 있는 것(환경 변수, 파일 존재 여부, 함수 인자)에만 쓸 수 있다. 이 구분을 명확히 하면 launch의 상당 부분이 이해된다.
견해: Python
if를 쓸 수 있는 경우도 있다 —OpaqueFunction(14.3.6)을 쓰면 실행 시점에 값을 얻어 Python으로 분기할 수 있다. 다만 그러면 launch의 선언적 성격을 잃으므로, 꼭 필요할 때만 쓰는 것이 좋다.
14.3.3 substitution
실행 시점에 평가되는 "값의 약속"이다.
| substitution | 의미 |
|---|---|
LaunchConfiguration('x') | launch 인자 값 |
PathJoinSubstitution([...]) | 경로 결합 |
FindPackageShare('pkg') | 패키지 share 경로 |
TextSubstitution(text=...) | 리터럴 |
EnvironmentVariable('X') | 환경 변수 |
Command([...]) | 명령 실행 결과(xacro 등) |
PythonExpression([...]) | 파이썬 식 평가 |
AndSubstitution·OrSubstitution·NotSubstitution | 논리 — 전부 실재(실행 확인) |
LocalSubstitution | 이벤트 문맥 |
# 경로 결합 — 문자열 연산 대신
params = PathJoinSubstitution([
FindPackageShare('my_robot_bringup'), 'config', 'nav.yaml'])
# xacro 처리 (5.4)
robot_description = ParameterValue(
Command(['xacro ', PathJoinSubstitution([
FindPackageShare('my_robot_description'), 'urdf', 'robot.urdf.xacro']),
' use_sim:=', LaunchConfiguration('use_sim_time')]),
value_type=str)
# 파이썬 식으로 조건 조합
condition=IfCondition(PythonExpression([
"'", LaunchConfiguration('mode'), "' == 'nav' and ",
LaunchConfiguration('use_sim_time')]))
함정:
Command(['xacro ', path])에서'xacro '뒤 공백을 빠뜨리면 명령이 붙어 버린다. substitution 리스트는 그대로 이어 붙여지므로, 공백을 명시적으로 넣어야 한다. 흔한 실수다.
함정:
Command의 결과를 파라미터로 줄 때ParameterValue(..., value_type=str)로 감싸야 하는 경우가 있다(확인 필요: 배포판별 필요성). 감싸지 않으면 타입 추론이 잘못돼 URDF가 비정상적으로 전달될 수 있다.
견해:
os.path.join과PathJoinSubstitution을 구분하라. 기술 시점에 확정된 경로는os.path.join으로 충분하지만, launch 인자가 섞이면 반드시PathJoinSubstitution이어야 한다. 섞어 쓰다 오류가 나는 것이 흔하다.
견해:
PythonExpression은 강력하지만 읽기 어렵다. 따옴표 처리가 까다롭고 디버깅이 어렵다. 복잡한 조건이 필요하면OpaqueFunction으로 옮기는 것이 대개 더 명확하다.
14.3.4 조건
DeclareLaunchArgument('use_rviz', default_value='true',
description='RViz2를 함께 실행')
Node(package='rviz2', executable='rviz2',
condition=IfCondition(LaunchConfiguration('use_rviz')))
| 조건 | 의미 |
|---|---|
IfCondition(x) | x가 참이면 실행 |
UnlessCondition(x) | x가 거짓이면 |
LaunchConfigurationEquals | 폐기 예정(실행 확인) — IfCondition(EqualsSubstitution(a, b))로 대체 |
함정: 참으로 인정되는 문자열이 정해져 있다 — 대소문자를 무시하고
true·1만 참,false·0만 거짓이다(실행 확인).'yes'·'ON'은 거짓으로 판정되는 것이 아니라InvalidConditionExpressionError로 중단된다. 빈 문자열도 마찬가지다. 조용한 오판이 아니라는 점은 다행이지만, launch 파일이 그 자리에서 종료된다.DeclareLaunchArgument의choices로 값을 제한하면 실수를 줄인다.
DeclareLaunchArgument('mode', default_value='nav',
choices=['nav', 'manip', 'idle'],
description='시스템 모드')
견해: 모든 launch 인자에
description과 가능하면choices를 넣으라.ros2 launch <pkg> <file> --show-args로 인자 목록을 볼 수 있어(14.3.8), 사용자가 소스를 안 읽어도 되도록 한다. 이것이 launch 파일의 사용성을 크게 바꾼다.
14.3.5 주요 액션
| 액션 | 역할 |
|---|---|
Node | ROS 노드 실행 |
ExecuteProcess | 임의 프로세스 |
ComposableNodeContainer·LoadComposableNodes | 컴포넌트(2.7) |
IncludeLaunchDescription | 다른 launch 포함 |
GroupAction | 묶기·범위 |
PushRosNamespace | 네임스페이스 적용 |
SetParameter | 전역 파라미터 |
SetEnvironmentVariable | 환경 변수 |
DeclareLaunchArgument | 인자 선언 |
TimerAction | 지연 실행 |
OpaqueFunction | Python 콜백(14.3.6) |
RegisterEventHandler | 이벤트(14.3.7) |
LogInfo | 메시지 |
# 그룹 + 네임스페이스 (1.1.6)
GroupAction([
PushRosNamespace('robot1'),
IncludeLaunchDescription(
PythonLaunchDescriptionSource(PathJoinSubstitution([
FindPackageShare('my_robot_bringup'), 'launch', 'base.launch.py'])),
launch_arguments={'use_sim_time': LaunchConfiguration('use_sim_time')}.items()),
])
견해:
GroupAction으로 범위를 만드는 것이 대규모 구성의 핵심이다(14.4).PushRosNamespace·SetParameter가 그룹 안에만 적용되므로, 여러 로봇·여러 서브시스템을 격리할 수 있다.
함정:
SetParameter는 그 범위의 모든 노드에 적용된다.use_sim_time을 전역으로 주는 데는 편리하지만(1.10.9), 의도치 않은 노드에도 들어간다. 범위를GroupAction으로 제한하는 것이 안전하다.
함정:
TimerAction으로 순서를 맞추는 것은 취약하다(14.2.3의 반복). 이벤트 핸들러나 준비 확인이 견고하다. 다만 간단한 개발용 launch에서는 실용적일 수 있으므로, 운영용과 구분한다.
14.3.6 OpaqueFunction
실행 시점에 Python 코드를 실행하는 탈출구다.
def launch_setup(context, *args, **kwargs):
# ★ 여기서는 값을 실제로 읽을 수 있다
mode = LaunchConfiguration('mode').perform(context)
robot = LaunchConfiguration('robot_name').perform(context)
nodes = []
if mode == 'nav': # ✓ 이제 Python if가 동작
nodes.append(Node(package='nav2_bringup', executable='...'))
elif mode == 'manip':
nodes.append(Node(package='moveit_ros_move_group', executable='...'))
# 파일 존재 확인 등 임의 로직 가능
cfg = os.path.join(get_package_share_directory('my_bringup'),
'config', f'{robot}.yaml')
if not os.path.exists(cfg):
raise RuntimeError(f'설정 파일 없음: {cfg}')
return nodes
def generate_launch_description():
return LaunchDescription([
DeclareLaunchArgument('mode', default_value='nav'),
DeclareLaunchArgument('robot_name', default_value='robot1'),
OpaqueFunction(function=launch_setup),
])
| 쓸 때 | 이유 |
|---|---|
| 복잡한 조건 분기 | PythonExpression보다 명확 |
| 설정 파일 검증 | 존재·내용 확인 |
| 동적 노드 생성 | 개수가 인자에 의존 |
| 오류를 조기에 | 명확한 메시지 |
견해:
OpaqueFunction은 남용하면 launch가 스크립트가 된다. 선언적 구조의 이점(구성 파악·재사용·도구 지원)을 잃는다. 분기가 서너 갈래를 넘거나 검증이 필요할 때만 쓰고, 단순 조건은condition으로 두는 것이 좋다.
견해: 설정 파일 존재를 launch에서 검증하는 것은 매우 유용하다. 파일이 없으면 노드가 비정상적인 기본값으로 조용히 기동되는 대신(1.7.9), 명확한 오류 메시지와 함께 즉시 실패한다. 현장 진단 시간을 크게 줄인다.
14.3.7 이벤트
반응형 구성을 만든다.
# 프로세스 종료에 반응
RegisterEventHandler(
OnProcessExit(
target_action=map_saver_node,
on_exit=[LogInfo(msg='지도 저장 완료'), EmitEvent(event=Shutdown())]))
# 프로세스 시작에 반응
RegisterEventHandler(
OnProcessStart(
target_action=driver_node,
on_start=[TimerAction(period=2.0, actions=[controller_node])]))
# 생애주기 전이에 반응 (1.12)
RegisterEventHandler(
OnStateTransition(
target_lifecycle_node=nav_node,
goal_state='active',
entities=[LogInfo(msg='항법 활성화됨')]))
| 이벤트 | 용도 |
|---|---|
OnProcessExit | 종료 후 정리·연쇄 |
OnProcessStart | 시작 후 후속 |
OnStateTransition | 생애주기 조율(1.12·14.2.3) |
OnShutdown | 종료 처리 |
OnExecutionComplete | 액션 완료 |
견해: 생애주기 이벤트로 부팅 순서를 조율하는 것이 launch의 표준적 해법이다(14.2.3).
TimerAction의 지연 대신 "드라이버가 active가 되면 컨트롤러를 활성화" 로 기술하면, 느린 날에도 올바르게 동작한다.
함정:
OnProcessExit로 재시작을 구현하면 무한 루프가 될 수 있다. 노드가 계속 종료되면 계속 재시작된다(14.2.6의 함정). 횟수 제한이 필요하면 launch보다 systemd(14.8)가 적합하다.
견해: launch는 "기동"에 강하고 "장기 운영 관리"에는 약하다. 재시작 정책·자원 제한·로그 관리는 systemd나 컨테이너 오케스트레이터의 영역이다. 개발에는 launch, 운영에는 systemd + launch 조합이 흔하다(14.8).
14.3.8 파라미터 전달의 함정
| 방법 | 특성 |
|---|---|
| 딕셔너리 | 코드에 직접 |
| YAML 파일 경로 | 외부 관리 |
| 섞어 쓰기 | 순서 주의 |
SetParameter | 범위 전체 |
Node(package='my_pkg', executable='my_node',
name='my_node', # ★ 이름이 YAML 키와 일치해야
namespace='robot1',
parameters=[
params_yaml, # 파일
{'use_sim_time': LaunchConfiguration('use_sim_time')}, # 덮어쓰기
],
remappings=[('/scan', '/robot1/scan')],
output='screen')
함정: YAML 파일의 노드 이름·네임스페이스가 실제와 다르면 파라미터가 적용되지 않는다(1.9.7). 조용히 기본값으로 동작하므로 발견이 늦다.
ros2 param get으로 실제 값을 확인하는 습관이 필요하다.
# 네임스페이스가 있으면 YAML도 맞춰야 한다
/robot1/my_node:
ros__parameters:
rate: 20.0
# 와일드카드 — /** 는 네임스페이스 깊이와 무관하게 모두 적용된다(실행 확인)
/**:
ros__parameters:
use_sim_time: true
견해:
/**와일드카드가use_sim_time같은 공통 파라미터에 유용하다(실행 확인 — 아래 실측). 다만 의도치 않은 노드에도 적용되므로 범위를 인지해야 한다.
두 와일드카드의 차이(실행 확인, Jazzy):
| 키 | 적용 대상 |
|---|---|
/* | 루트 직속 노드만 — /top은 받고 /a/deep은 못 받는다 |
/** | 모든 깊이 — /top·/a/deep·/a/b/deeper 전부 |
와일드카드와 특정 노드 항목은 함께 적용된다. /**에 use_sim_time, /my_ns/specific_node에 only_here를 두면 그 노드는 둘 다 받는다(실측). 즉 "공통은 /**, 개별은 노드 이름으로"가 성립한다.
함정:
/*를 "모든 노드"로 오해하기 쉽다. 네임스페이스를 쓰는 순간(다중 로봇·컴포넌트 컨테이너) 그 아래 노드는/*에 걸리지 않는다 —use_sim_time처럼 빠지면 조용히 틀리는 파라미터에/*를 쓰면 위험하다. 공통 파라미터에는/**를 쓰라.
함정: 파라미터 목록은 "뒤가 앞을 덮어쓴다"만으로는 부족하다 — 지정자의 구체성이 순서에 우선한다(실행 확인, 14.4.11.2). 부분 덮어쓰기는 딥 머지로 기대대로 동작하지만, 나중에 준
/**:파일도 앞선/노드이름:항목은 못 덮고 명령줄-p조차 진다. 파일과 딕셔너리를 섞을 때는 이 규칙까지 감안해 의도를 주석으로 남기라.
견해: 리매핑과 네임스페이스를 함께 쓸 때 혼란이 크다(1.1.5·1.1.6). 절대 이름(
/scan)과 상대 이름(scan)의 차이가 결과를 바꾸므로,ros2 node info로 실제 이름을 확인하는 것이 확실하다.
14.3.9 디버깅
# 인자 목록 확인
ros2 launch my_pkg my.launch.py --show-args
# 실행 없이 기술만 출력 (확인 필요: 옵션)
ros2 launch my_pkg my.launch.py --print-description
# 상세 로그
ros2 launch my_pkg my.launch.py --debug
# 실제 실행되는 명령 확인
# (Node 액션이 만드는 프로세스 인자)
| 증상 | 원인 후보 |
|---|---|
| 조건이 안 먹힘 | Python if 사용·문자열 값 |
| 경로 오류 | os.path vs substitution 혼용 |
| 파라미터 미적용 | 이름·네임스페이스 불일치 |
| xacro 실패 | Command 공백·경로 |
| 노드가 안 뜸 | 실행 파일명·패키지 미설치 |
| 순서 문제 | 이벤트·준비 확인 필요 |
| 오버레이 문제 | 옛 launch 파일 실행(4.5.8) |
견해: "고쳤는데 반영이 안 된다"면 오버레이를 의심하라(11.1.4).
colcon build로 설치된 launch 파일이 실행되므로, 소스만 고치고 빌드하지 않으면 옛것이 실행된다. 심볼릭 설치(--symlink-install)를 쓰면 이 문제가 줄어든다(4.5).
견해: launch 오류 메시지는 읽기 어렵지만 실마리가 있다. 스택 트레이스 안에 substitution 평가 실패 지점이 나오므로, 끝까지 읽는 것이 추측보다 빠르다.
14.3.12 substitution·condition 전수 — 실측
Jazzy에 설치된 launch·launch_ros를 직접 조회해 클래스 전수 목록을 얻고, 조건 평가는 값을 넣어 실행해 봤다(실행 확인).
python3 -c "import launch.substitutions as s; print([n for n in dir(s) if n[0].isupper()])"
14.3.12.1 이 장이 다루지 않던 substitution
launch.substitutions는 22종이다. 14.3.4의 표에 없던 것 중 실무에서 쓸 만한 것들:
| 클래스 | 하는 일 | 언제 |
|---|---|---|
EqualsSubstitution · NotEqualsSubstitution | 두 값 비교 → 'true'/'false' | 폐기된 LaunchConfigurationEquals의 정식 대체 |
IfElseSubstitution | 조건에 따라 둘 중 하나 | 값 자체를 갈라야 할 때(노드를 갈라야 하면 IfCondition) |
AllSubstitution · AnySubstitution | 리스트 전체 AND / OR | 조건이 여러 개일 때 And를 겹치지 않아도 된다 |
NotSubstitution | 부정 | |
FileContent | 파일 내용을 그대로 | URDF를 Command로 매번 생성하지 않고 미리 만들어 둔 파일을 읽을 때 |
AnonName | 익명 이름 생성 | |
LaunchLogDir | 이번 실행의 로그 디렉터리 | 산출물을 로그 옆에 남길 때 |
ThisLaunchFile · ThisLaunchFileDir | 자기 자신 경로 | |
PathSubstitution · PathJoinSubstitution | 경로 |
launch_ros.substitutions는 5종이다: FindPackageShare · FindPackage · FindPackagePrefix · ExecutableInPackage · Parameter.
FindPackageShare(share 디렉터리)와FindPackagePrefix(설치 prefix)를 혼동하기 쉽다. 설정 파일·URDF는 share, 실행 파일은ExecutableInPackage다.
14.3.12.2 조건 평가 — 값별 실측
IfCondition에 값을 넣어 실제로 평가시킨 결과다:
| 넣은 값 | 결과 |
|---|---|
'true' · 'True' · '1' | 참 |
'false' · 'False' · '0' | 거짓 |
'yes' | InvalidConditionExpressionError |
''(빈 문자열) | InvalidConditionExpressionError |
내부 상수는 VALID_TRUE_EXPRESSIONS = ['true', '1'] · VALID_FALSE_EXPRESSIONS = ['false', '0']이고, 비교 전에 소문자화하므로 'True'도 통한다.
이 규칙은 xacro와 같다. 5.4.12.2에서 xacro의 xacro:if를 같은 값들로 시험했는데 결과가 일치한다 — 둘 다 true/1 계열은 참, yes는 오류. 로봇 설정을 launch 인자 → xacro 인자로 넘길 때 값이 그대로 통한다는 뜻이라, 실무에서 신경 쓸 일이 하나 줄어든다.
함정: 빈 문자열이 오류라는 점을 놓치기 쉽다(위 표,
IfCondition('')).DeclareLaunchArgument에default_value를 반드시 주는 이유가 이것이다 — 정확한 실패 경로는 14.3.12.4에서 정정한다.
14.3.12.3 폐기 예정 두 개
UserWarning: The 'LaunchConfigurationEquals' and 'LaunchConfigurationNotEquals'
Conditions are deprecated. Use the 'EqualsSubstitution' ...
실제로 생성만 해도 위 경고가 출력된다(실행 확인). 대체는 이렇다:
# 옛 방식 (폐기 예정)
Node(..., condition=LaunchConfigurationEquals('mode', 'nav'))
# 지금 방식
Node(..., condition=IfCondition(
EqualsSubstitution(LaunchConfiguration('mode'), 'nav')))
조건 클래스는 IfCondition·UnlessCondition 둘로 수렴하고, 비교는 substitution이 맡는 구조로 정리된 것이다. 조합이 자유로워져 And/Or/Not과 섞어 쓸 수 있다.
14.3.12.4 "선언 안 됐거나 기본값이 없으면 빈 문자열"은 틀렸다 — 실측
실행 확인(2026-08-18, ros:jazzy-ros-base, ros2 launch) — 14.3.12.2의 함정 문단은 "LaunchConfiguration이 선언되지 않았거나 기본값이 없으면 빈 문자열이 되고, 그 자리에서 launch가 종료된다"고 적었다. 재현해 보니 빈 문자열 단계 자체가 없었다.
| 시나리오 | 실제 오류 |
|---|---|
DeclareLaunchArgument 없이 LaunchConfiguration('use_sim')을 바로 참조 | launch configuration 'use_sim' does not exist |
DeclareLaunchArgument('use_sim')로 선언은 했지만 default_value 없음, CLI 인자도 안 줌 | Included launch description missing required argument 'use_sim' … |
둘 다 종료 코드 1로 즉시 종료되는 것은 맞지만, 값이 빈 문자열로 치환된 뒤 종료되는 것이 아니라 LaunchConfiguration을 평가(perform)하는 순간 그 자체가 예외를 던진다. 선언 여부에 따라 오류 메시지도 다르다 — 선언조차 안 됐으면 "존재하지 않는다", 선언은 됐는데 값이 없으면 "필수 인자 누락"이다.
결론은 바뀌지 않지만 원인은 바뀐다. "
default_value를 반드시 주라"는 처방은 여전히 옳다 — 다만 그 이유는 "안 그러면 빈 문자열이 조용히 흘러들어서"가 아니라 "안 그러면 그 자리에서 명시적 예외로 즉시 멈추기 때문"이다. 실무 영향은 오히려 반대다 — 빈 문자열이라면 하위 노드까지 흘러가 조용히 이상 동작할 위험이 있지만, 실제로는 launch 시작 단계에서 크고 시끄럽게 실패하므로 디버깅은 이 문단이 암시하는 것보다 쉽다.
14.3.10 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | Python if로 launch 인자 분기 | 항상 한쪽 | condition |
| 2 | LaunchConfiguration을 문자열로 취급 | 오류·이상 동작 | substitution |
| 3 | os.path.join에 substitution 혼용 | 경로 오류 | PathJoinSubstitution |
| 4 | Command(['xacro', path]) 공백 누락 | 명령 실패 | 'xacro ' |
| 5 | ParameterValue 미사용 | URDF 전달 이상 | 감싸기 |
| 6 | 참 문자열 오해 | 조건 무시됨 | choices 제한 |
| 7 | 인자에 description 없음 | 사용성 저하 | 항상 작성 |
| 8 | SetParameter 범위 과다 | 의도치 않은 적용 | GroupAction |
| 9 | TimerAction으로 순서 | 간헐 실패 | 이벤트·준비 확인 |
| 10 | OpaqueFunction 남용 | 선언성 상실 | 필요 시만 |
| 11 | YAML 노드 이름 불일치 | 파라미터 미적용(경고 없음) | 이름·NS 대조 → /**:로 바꿔 적용되면 확정(11.1.14.3) |
| 12 | 파라미터 순서 혼동 | 예상 밖 값 | 주석·확인 |
| 13 | 리매핑 절대/상대 혼동 | 잘못된 연결 | node info |
| 14 | OnProcessExit 재시작 루프 | 무한 반복 | systemd |
| 15 | 빌드 없이 launch 수정 | 반영 안 됨 | 빌드·symlink |
| 16 | LaunchConfiguration 기본값 미지정 | 참조하는 순간 즉시 예외로 중단(빈 문자열이 흘러가는 것이 아니다) | default_value 필수(14.3.12.2·14.3.12.4) |
14.3.11 체크포인트
- launch의 두 시점을 설명하고, 그 결정적 함의 네 가지를 말할 수 있는가.
- Python
if를 쓸 수 있는 경우와 없는 경우를 구분할 수 있는가. - 주요 substitution 아홉 가지와 각각의 용도는? 실제로 제공되는 것은 몇 종인가(14.3.12.1).
Command로 xacro를 처리할 때의 두 함정은?os.path.join과PathJoinSubstitution의 사용 기준은?PythonExpression의 한계와 대안은?- 조건 액션 사용법과, 참 문자열 함정의 대처는?
- launch 인자에
description·choices를 넣으라는 조언의 근거는? - 주요 액션 열세 가지 중 대규모 구성의 핵심은 무엇인가.
SetParameter의 범위 문제와 대처는?OpaqueFunction을 쓸 네 상황과, 남용의 위험은?- 설정 파일 검증을 launch에서 하는 것의 이점은?
- 이벤트 다섯 종류와, 부팅 순서 조율의 표준적 해법은?
- launch와 systemd의 역할 구분은?
- 파라미터 전달의 함정 네 가지를 들 수 있는가.
- launch 디버깅 명령 셋과, "반영이 안 된다"의 원인은?
다음 장 14.4은 이 도구로 대규모 시스템을 구성하는 방법 — 계층화·재사용·다중 로봇을 다룬다.