ROS 2 심화 계약을 하나씩 정리하고 나니 오히려 입구가 비어 있다는 문제가 보였습니다. QoS, executor, Lifecycle, tf2, rosbag2를 각각 설명한 글은 있는데, 카메라 영상은 왜 topic이고 “걷기 시작”은 왜 action이어야 하는지 한 그림에서 찾아볼 수 없었습니다. 기능별 tutorial을 차례로 따라가도 실제 G1의 sensor에서 policy와 motor까지 어떤 책임으로 나뉘는지는 따로 조립해야 했습니다.
이 문제는 ROS 2를 로봇용 운영체제라고만 기억할 때 더 커집니다. ROS 2가 policy를 학습하거나 motor torque를 계산해 주는 것은 아닙니다. 서로 다른 계산 단위가 어떤 이름과 자료형으로 데이터를 주고받고, 어떤 요청을 누가 소유하며, 실행 중인 graph를 어떻게 관찰할지를 제공하는 middleware와 도구 모음에 가깝습니다. 그래서 처음 잡아야 할 질문은 “ROS 2 기능을 얼마나 아는가”가 아니라 이 데이터나 명령의 수명과 소유자가 누구인가입니다.

node는 프로그램 이름이 아니라 ROS graph의 책임 단위입니다
ROS 2 공식 node 문서는 node를 ROS graph의 participant이자 보통 하나의 논리적 일을 맡는 계산 단위로 설명합니다. 한 node는 topic publisher와 subscriber, service server와 client, action server와 client, parameter를 동시에 가질 수 있습니다. node 하나가 실행 파일 하나와 항상 같은 것도 아닙니다. 같은 process 안에 여러 composable node를 넣을 수 있고, 다른 machine의 node와도 통신할 수 있습니다.
G1 runtime을 설계한다면 처음부터 거대한 robot_node 하나에 모든 일을 넣는 것보다 책임이 끊기는 지점을 찾는 편이 낫습니다. 예를 들면 hardware에서 joint state와 IMU를 읽는 feedback bridge, sensor를 시간·좌표계에 맞춰 state로 만드는 estimator, state를 29차원 목표로 바꾸는 policy, stale command와 한계를 검사하는 safety guard, 검증된 명령만 hardware 형식으로 바꾸는 actuator bridge가 각각 하나의 논리적 책임입니다.
그렇다고 node를 무조건 잘게 쪼개면 좋은 것도 아닙니다. process 경계를 넘을 때 serialization과 scheduling 비용이 생기고, node 수가 늘면 이름·namespace·QoS·lifecycle을 관리할 대상도 늘어납니다. 분리 기준은 파일 개수가 아니라 독립적으로 시작·중지·교체·관찰할 필요가 있는가입니다. policy만 새 checkpoint로 바꾸고 feedback bridge는 유지해야 한다면 두 책임을 분리할 이유가 생깁니다.
topic은 계속 흐르는 상태에 맞고, 완료 응답을 기다리는 명령에는 맞지 않습니다
ROS 2 공식 topic 문서에서 topic은 sensor data나 robot state 같은 연속 stream을 위한 publish/subscribe interface입니다. publisher는 receiver가 몇 개인지 기다리지 않고 message를 내보내며, 같은 topic에 여러 publisher와 subscriber가 붙을 수 있습니다. message type은 강하게 정해지지만, “이 명령을 실행했는가”라는 거래의 완료 상태는 topic 자체가 보장하지 않습니다.
G1에서는 joint position·velocity, IMU, contact, camera, 추정 state, policy target처럼 시간에 따라 계속 바뀌는 값이 topic 후보가 됩니다. 여기서 중요한 것은 /state나 /cmd라는 이름보다 message의 의미입니다. source timestamp, frame, unit, joint order, sequence, 유효 기한이 없으면 29개 숫자가 도착해도 어느 시각의 어느 관절 목표인지 판정할 수 없습니다.
topic으로 “걷기 시작” 문자열을 한 번 publish하는 구현도 만들 수 있습니다. 하지만 subscriber가 늦게 붙었을 때 과거 명령을 받아야 하는지, 같은 명령을 두 번 받으면 두 작업을 시작하는지, 진행률과 취소는 어디로 보내는지 별도 규칙이 필요합니다. 일회성 명령을 topic에 얹는 순간 application이 service나 action의 lifecycle을 다시 만들어야 합니다.
service는 짧게 끝나는 질문이고, action은 시간이 걸리는 작업입니다
ROS 2 공식 interface 선택 가이드는 세 가지를 명확히 나눕니다. topic은 연속 data flow, service는 빨리 끝나는 remote procedure call, action은 실행 중 feedback과 cancel·preemption이 필요한 긴 작업에 사용합니다. 이 구분은 message 크기가 아니라 interaction의 수명으로 결정됩니다.
| 질문 | 맞는 interface | G1 예시 | 잘못 고르면 생기는 일 |
|---|---|---|---|
| 계속 새 값이 생기는가 | topic | joint state, IMU, image, 추정 state, command target | service polling이 요청 폭주와 오래된 응답을 만든다 |
| 짧은 요청 뒤 한 번의 응답이면 끝나는가 | service | 현재 mode 조회, calibration ID 확인, recorder 상태 조회 | 긴 동작을 service로 막아 timeout과 취소 불능을 만든다 |
| 목표·진행률·취소·최종 결과가 필요한가 | action | 지정 위치까지 걷기, 자세 전환, 물체 검사 | topic 명령에 중복 실행·취소·terminal state를 다시 구현한다 |
| node 동작을 구성하는 값인가 | parameter | threshold, frame ID, model path, policy rate | 명령과 설정 변경의 이력·검증 경계가 섞인다 |
예를 들어 “안전 supervisor의 현재 authority epoch을 알려 달라”는 짧은 query는 service에 가깝습니다. “앞으로 2m 걸어가라”는 몇 초 동안 feedback을 내고 넘어짐이나 새 명령에 따라 취소할 수 있어야 하므로 action이 맞습니다. 반면 action server가 내부 50Hz 제어를 goal feedback으로 모두 운반하는 것은 다른 문제입니다. 고수준 action이 목표를 소유하더라도, 아래쪽 state와 command의 연속 흐름은 topic 또는 process 내부의 bounded handoff로 유지할 수 있습니다.
긴 명령의 중복 실행과 cancel 이후 물리 정지까지 필요한 세부 계약은 service·action command lifecycle 글에서 따로 다뤘습니다. 이 글에서는 “짧은 질문인가, 취소 가능한 긴 작업인가”라는 첫 선택 기준만 소유합니다.
parameter는 node의 설정이지, 아무 값이나 넣는 전역 변수는 아닙니다
ROS 2 공식 parameter 문서에 따르면 parameter는 개별 node에 연결되고 node의 시작 시점과 runtime 동작을 code 변경 없이 구성합니다. key와 typed value뿐 아니라 descriptor와 범위 제약도 가질 수 있습니다. ROS 1처럼 모든 node가 공유하는 하나의 global parameter server를 전제로 하면 ownership이 흐려집니다.
G1 policy node의 checkpoint_path, action_scale, policy_rate_hz는 parameter 후보가 될 수 있습니다. 하지만 runtime에서 값이 바뀌었다는 사실과 control loop가 그 값을 안전하게 적용했다는 사실은 다릅니다. 29개 관절의 scale을 callback 도중 절반만 교체하거나 checkpoint identity를 검증하지 않은 채 경로만 바꾸면 node는 살아 있어도 control bundle은 일관되지 않습니다.
그래서 parameter update는 type·range·cross-field validation을 통과하고, immutable generation으로 묶은 다음 control tick 경계에서 한 번에 교체해야 합니다. 그 상세는 parameter·YAML·원자적 갱신 계약으로 분리해 두었습니다. 입문 단계에서는 parameter를 “명령을 보내는 또 다른 방법”이 아니라 특정 node가 소유하는 설정으로 기억하면 충분합니다.
tf2는 좌표 message 하나가 아니라 시간축을 가진 공간 graph입니다
camera가 본 점을 pelvis 기준으로 옮기려면 두 frame 사이의 관계가 필요합니다. tf2는 sensor, link, world 같은 local frame을 tree로 연결하고, listener가 특정 시각의 transform을 조회하도록 합니다. ROS 2 공식 tf2 frame tutorial도 각 frame이 하나의 parent를 갖는 tree 구조와 중간 transform의 합성을 설명합니다.
여기서 /tf와 /tf_static이 topic으로 전달된다고 해서 tf2를 일반 topic과 같은 것으로 보면 안 됩니다. consumer가 원하는 것은 최신 message 한 개가 아니라 measurement timestamp에서 source frame을 target frame으로 바꿀 수 있는 path입니다. frame 이름이 맞아도 history가 그 시각을 덮지 못하면 lookup은 실패해야 합니다.
좌표 변환과 시간 오류를 더 깊게 보려면 tf2 frame·timestamp·extrapolation 계약과 G1 tick·ROS time·monotonic clock 동기화 글을 이어서 읽는 편이 낫습니다. camera와 robot state를 한 묶음으로 만드는 문제는 message_filters 동기화 계약이 소유합니다.
launch·Lifecycle·executor·QoS는 graph가 실제로 움직이는 조건을 정합니다
node와 interface를 정했다고 runtime이 완성되는 것은 아닙니다. launch는 여러 process와 node를 어떤 argument·namespace·parameter로 시작하고 종료할지 묶습니다. Lifecycle Node는 configure·activate·deactivate 같은 상태를 드러냅니다. executor는 준비된 callback을 어느 thread에서 언제 실행할지 정합니다. QoS는 publisher와 subscriber가 reliability·durability·queue·deadline 같은 전달 조건에서 호환되는지 결정합니다.
이 네 가지를 한 단어로 줄이면 “운영 조건”입니다. topic 이름과 type이 맞아도 QoS가 호환되지 않으면 callback은 오지 않을 수 있습니다. message가 middleware에 도착해도 executor가 다른 callback에 막혀 늦을 수 있습니다. process가 떴어도 checkpoint를 아직 읽는 중이면 policy는 준비되지 않았습니다. Lifecycle이 Active여도 actuator authority를 자동으로 가져서는 안 됩니다.
따라서 live G1 bringup은 launch·readiness·shutdown, Lifecycle 활성화, executor·backpressure, QoS 제어 계약을 함께 만족해야 합니다. 이 글은 그 세부 설정을 반복하지 않고, interface 설계 다음에 반드시 운영 조건이 온다는 위치만 표시합니다.
rosbag2는 흐른 message를 남기지만, application 전체 상태를 자동으로 저장하지 않습니다
topic은 publisher와 subscriber를 느슨하게 연결하므로 recorder를 새 subscriber로 붙이기 쉽습니다. rosbag2는 선택한 topic을 기록하고 다시 publish할 수 있어 sensor·state·command 흐름을 재현하는 기반이 됩니다. 그러나 bag에 topic 이름과 message count가 있다는 사실만으로 원래 사건이 재현되지는 않습니다.
QoS, /clock, /tf와 /tf_static, parameter generation, checkpoint hash, node lifecycle, command authority가 빠지면 byte는 있어도 application context는 사라집니다. 그래서 별도로 작성한 rosbag2·MCAP 재현 계약 초안에서는 storage readable, graph replayable, incident reproducible을 서로 다른 단계로 나눴습니다. 그 글은 아직 공개하지 않았기 때문에 여기에는 내부 링크를 걸지 않았습니다.
현재 workspace에는 계산 경로가 있고, live ROS 2 graph는 아직 없습니다
현재 robot-control-router의 Runner.run()은 Python dictionary로 BackendRequest를 만들고 selector가 scripted·WBC·VLA·RL backend 중 하나를 고른 뒤 동기적으로 run_step()을 호출합니다. 결과는 BackendResponse로 돌아오며 backend, action type, action, status, log를 JSON 파일에 남깁니다. 이 구조는 계산 backend의 공통 interface와 fallback 흐름을 시험하는 데 유용합니다.
Isaac Lab의 G1WalkEnv에는 body-frame linear·angular velocity, projected gravity, effective command, gait phase, target swing side, joint position offset과 joint velocity를 합친 73차원 observation이 있습니다. policy의 29차원 action은 nominal joint position에 action_scale=0.25를 곱한 offset으로 적용됩니다. eval_rl.py는 episode별 fall, velocity error, energy, action rate 같은 지표를 저장합니다. 즉 simulation 내부의 observation→action→evaluation 경로는 code로 존재합니다.
반면 지금 확인한 robot-control-router/, sim/, training/에는 현재 G1 경로를 구성하는 rclpy·rclcpp publisher/subscriber, service/action server, ROS 2 package manifest, launch description이 없습니다. sim/assets/robots/h1/package.xml은 H1 asset의 별도 패키지 흔적이지 G1 control graph 구현의 증거가 아닙니다. 따라서 앞의 runtime map은 구현 완료 보고가 아니라 다음 integration의 책임 분해도입니다.
| 계층 | 현재 확인된 것 | 아직 필요한 것 |
|---|---|---|
| 계산 interface | BackendRequest/Response, selector, backend, JSON log |
timestamp·sequence·frame·valid-until을 가진 ROS message schema |
| simulation | 73D observation, 29D action, Isaac Lab env와 평가 도구 | simulation clock·state·command를 ROS graph에 잇는 bridge |
| runtime transport | 설계 글과 target contract | publisher/subscriber, service/action, QoS, executor 구성 |
| 운영 | 개별 readiness·lifecycle·recording 설계 | launch package, fault test, authority-0 통합 실행 증거 |
첫 구현은 로봇을 움직이지 않는 다섯 node graph가 맞습니다
처음부터 실제 motor command를 연결하면 interface 오류와 물리 위험이 한 시험에 섞입니다. 첫 통합 graph는 mock feedback publisher, estimator, policy shadow, safety supervisor, command observer 다섯 책임으로 충분합니다. actuator bridge는 연결하지 않고 authority를 0으로 고정합니다. 목적은 보행 성능이 아니라 각 interface의 의미가 관찰 가능한지 확인하는 것입니다.
- mock feedback이 timestamp·sequence·joint name을 가진 state topic을 정해진 주기로 publish합니다.
- estimator는 stale·out-of-order sample을 분류하고 fresh state만 새 topic으로 내보냅니다.
- policy shadow는 73차원 입력과 config·checkpoint identity를 검증한 뒤 29차원 candidate target을 publish합니다.
- safety supervisor는 command age, joint order, lifecycle, authority epoch을 검사하되 actuator lease를 발급하지 않습니다.
- command observer와 rosbag2가 입력·reject reason·candidate output·parameter snapshot·TF·clock을 기록합니다.
- 짧은 query는 service, 2초 이상 지속되고 취소가 필요한 mock walk goal은 action으로 만들어 수명 차이를 시험합니다.
통과 기준도 “node가 다 떴다”가 아닙니다. ros2 node list와 graph introspection으로 예상 node·interface가 보이고, topic rate·age·drop이 허용 범위에 있으며, service가 짧게 종료되고, action이 feedback·cancel·terminal result를 남겨야 합니다. parameter를 잘못 바꾸면 원자적으로 거절되고, TF lookup과 rosbag replay는 같은 authority-0 환경에서 다시 확인돼야 합니다.
ROS 2를 이해했다는 기준은 명령어 암기가 아니라 실패 위치를 말할 수 있는가입니다
ROS 2는 로봇을 똑똑하게 만드는 policy가 아닙니다. policy·estimator·hardware bridge가 서로의 내부 구현을 몰라도 협업하게 만들고, 그 경계가 틀렸을 때 어디에서 끊겼는지 관찰하게 하는 실행 기반입니다. node는 책임, topic·service·action은 수명, parameter는 설정 소유권, tf2는 시간축을 가진 공간 관계, launch·Lifecycle·executor·QoS는 운영 조건, rosbag2는 전달된 증거를 맡습니다.
제가 다음 구현에서 먼저 만들 것은 화려한 dashboard가 아니라 authority-0 mock graph입니다. 여기서 “message가 안 왔다”를 topic 이름 하나로 설명하지 않고 discovery, type, QoS, callback scheduling, timestamp, frame, lifecycle 중 어디에서 실패했는지 나눌 수 있어야 합니다. 그때부터 지금 흩어져 있는 ROS 2 심화 계약들이 하나의 실제 runtime으로 연결됩니다.