AEGLOSLAB ROS 2 Jazzy 대전

제0부 개관과 출발

0.7 ROS 2 CLI 체계와 데몬의 내부

0.7.1 이 장에서 배우는 것 / 선수 지식

ros2 명령은 ROS 2를 관찰하는 가장 기본적인 창이다. 그런데 이 창은 때때로 거짓말을 한다. 종료된 노드를 실행 중이라고 하고, 흐르는 데이터를 안 흐른다고 하고, 실제 주파수와 다른 Hz를 보여준다. 이것은 버그가 아니라 CLI가 동작하는 방식의 필연적 결과이며, 그 방식을 알면 언제 믿고 언제 의심할지 판단할 수 있다.

이 장은 CLI를 도구 목록이 아니라 메커니즘으로 다룬다. 명령 목록 자체는 부록 A.1에 정리하고, 여기서는 "왜 그렇게 동작하는가"에 집중한다.

배우는 것:

  • ros2 명령의 구조 — ros2 <명령> <동사>가 어떻게 확장 가능한 플러그인으로 구성되는가.
  • 데몬(ros2 daemon)의 정체 — 왜 존재하고, 무엇을 캐시하며, 언제 거짓말의 원인이 되는가.
  • CLI가 그래프에 참여한다는 사실 — ros2 topic echo가 만드는 임시 노드와 그 부작용.
  • CLI가 틀리는 일곱 가지 경우와 각각의 대처.
  • 진단 명령의 보고값을 대조군으로 시험한 결과(0.7.11) — param get·node list·doctor가 각각 무엇을 답하고 무엇을 답하지 않는가.
  • ros2 topic hz의 정확도와 한계(0.7.12) — 어디까지 믿을 수 있고, 창(window)이 왜 실제 오차원인가.
  • QoS와 CLI — echo가 아무것도 못 보는 상황을 푸는 법.
  • 관측 명령의 실전 레시피 — 그래프·데이터·타이밍·파라미터를 빠르게 확인하는 조합.
  • 운영 로봇에서 CLI를 쓸 때의 주의 — 관측 자체가 시스템을 흔드는 경우.

선수 지식: 0.4(도메인·RMW 환경변수), 0.6(노드를 실행해 본 경험). QoS는 아직 몰라도 되지만, 이 장에서 처음 마주치게 된다(정식 설명은 1.5).

정직: CLI 옵션 이름·기본값은 배포판·패키지 버전에 따라 달라질 수 있다. 아래 옵션은 Jazzy 기준으로 적었으나 실행 검증하지 않았다(목차 3). ros2 <명령> <동사> --help가 언제나 정본이다.

검증 상태(추가): 이 장의 서술 가운데 3곳은 이후 회차에서 Jazzy 실물과 대조해 확인했다 — 본문의 (실행 확인) 표기가 그것이다. 표기가 없는 서술은 위 고지가 그대로 적용된다(목차 3의 검증 범위 표).

0.7.2 ros2 명령의 구조 — 하나의 진입점, 플러그인 확장

ROS 1의 rostopic·rosnode·rosservice는 각각 별도 실행 파일이었다. ROS 2는 ros2 하나의 진입점 아래 명령(command)과 동사(verb)를 둔다.

ros2 <명령> <동사> [인자]
위치값의 예
명령(command)topic · node · service · action · param · bag · launch · run · pkg · …
동사(verb)list · info · echo · pub · call · … (명령마다 다름)

이 구조는 setuptools 엔트리포인트 기반 플러그인으로 구현된다. ros2cli 패키지가 뼈대를 제공하고, ros2topic·ros2node 같은 패키지가 각각의 명령을 등록한다.

ros2 (ros2cli) 엔트리포인트 검색 ros2cli.command ros2topic · ros2node ros2service · ros2param … 각 명령의 동사 검색 ros2cli.verb.* 선택된 동사 실행

실무적 의미 셋:

  1. CLI는 Python이다. 그래서 시작이 느리고(수백 밀리초), 대용량 데이터 처리에서 비효율적이다. ros2 topic echo로 포인트클라우드를 받으면 CPU 사용률이 급증한다.
  2. 자체 명령을 추가할 수 있다. 조직 전용 진단 명령을 ros2 mycompany diag 형태로 붙일 수 있다 — 엔트리포인트를 등록하기만 하면 된다. 팀 도구를 표준 경로에 배치하는 좋은 방법이다.
  3. 설치된 패키지에 따라 사용 가능한 명령이 다르다. ros-base만 설치한 로봇에는 일부 명령이 없을 수 있다(0.4.3).

0.7.3 데몬 — 왜 있고 무엇을 캐시하나

ros2 node list를 실행할 때마다 새 프로세스가 DDS 디스커버리를 처음부터 수행한다면, 매번 수 초를 기다려야 한다(디스커버리는 즉시 완료되지 않는다 — 3.3). 그래서 ROS 2는 백그라운드 데몬을 둔다.

즉시 응답 ros2 node list (짧게 실행되는 CLI) ros2 daemon (백그라운드 상주) 그래프 정보 캐시 노드·토픽·서비스 목록 DDS 디스커버리에 계속 참여 중
  • 데몬은 도메인마다 하나씩 기동된다(ROS_DOMAIN_ID별). 첫 CLI 실행 시 자동으로 시작된다.
  • 데몬은 그래프에 참가자로 계속 남아 디스커버리 정보를 축적한다. 그래서 두 번째 이후의 ros2 node list가 빠르다.
ros2 daemon status   # 실행 여부
ros2 daemon stop     # 종료 (캐시 초기화)
ros2 daemon start    # 수동 시작

데몬이 만드는 문제 — "유령 노드":

데몬의 캐시는 즉시 정확하지 않다. 노드가 비정상 종료(SIGKILL·전원 차단)하면 DDS의 참가자 타임아웃이 지나기 전까지 데몬은 그 노드를 실행 중이라고 믿는다. 그래서 ros2 node list에 이미 종료된 노드가 남는다.

증상대처
종료된 노드가 목록에 남음ros2 daemon stop 후 재조회
새로 기동된 노드가 안 보임잠시 기다리거나 데몬 재시작
결과가 비정상적으로 일관되지 않음--no-daemon 옵션으로 직접 조회
ros2 node list --no-daemon    # 데몬을 우회해 직접 디스커버리 (느리지만 정확)

견해: 디버깅 중 무언가 비정상이면 가장 먼저 ros2 daemon stop을 해 보라. 5초 걸리는 조치로 30분을 아끼는 경우가 많다. 반대로 데몬이 없으면 모든 CLI가 느려지므로, 평소에는 켜 두는 것이 맞다.

0.7.4 CLI는 그래프의 참여자다

가장 중요한 사실이자 가장 자주 잊히는 사실이다. ros2 topic echo는 관찰자가 아니라 참여자다. 실행하는 순간 임시 노드(_ros2cli_<pid> 형태)가 만들어지고, 구독자가 생기고, 디스커버리가 일어나고, 데이터가 네트워크를 거쳐 그 프로세스로 온다.

여기서 세 가지 부작용이 나온다.

  1. 관측이 시스템을 바꾼다. 대용량 토픽에 echo를 적용하면 발행자는 구독자가 하나 더 생긴 것으로 처리한다 — 직렬화·전송 비용이 실제로 늘어난다. 무선 링크에서는 이것만으로 대역이 포화될 수 있다. image_transport의 lazy subscriber처럼 "구독자가 있을 때만 일하는" 노드는 동작 자체가 달라진다(7.2).
  2. CLI 자신의 QoS가 적용된다. 발행자와 호환되지 않으면 아무것도 못 받는다(0.7.5).
  3. 디스커버리 부하가 는다. 노드가 많은 시스템에서 CLI를 자주 실행하면 디스커버리 트래픽이 그만큼 증가한다(3.3).

숨은 엔티티는 기본적으로 목록에서 감춰진다. 필요하면 드러낼 수 있다.

ros2 topic list --include-hidden-topics
ros2 node list --all              # 실재(실행 확인). --count-nodes 도 있다

0.7.5 QoS와 CLI — 무엇이 달라졌나

오랫동안 이것이 가장 흔한 CLI 함정이었다. 예전 배포판의 상황은 이랬다.

  • 발행자가 BEST_EFFORT인데 ros2 topic echo는 기본 RELIABLE로 구독한다 → 연결되지 않음 → 아무것도 안 나옴.
  • 발행자가 TRANSIENT_LOCAL(래치)로 한 번만 발행했는데 echo는 VOLATILE로 구독한다 → 이미 지난 메시지를 못 받음 → 아무것도 안 나옴.

Jazzy에서는 이 증상이 CLI에서 대체로 사라졌다(실행 확인). ros2 topic echo의 Reliability·Durability·Liveliness 기본값이 best_available 이라, 이미 연결된 발행자에 맞춰 자동으로 낮춰 매칭되기 때문이다(1.5.3.8).

그러나 문제가 없어진 것이 아니라 자리를 옮겼다. 자동 협상은 CLI에만 적용되고 직접 작성한 구독 노드에는 적용되지 않는다 — 명시하지 않으면 기본이 RELIABLE이므로 위 첫 줄의 상황에 그대로 걸린다. 그래서 Jazzy 특유의 혼동은 이렇게 나타난다.

"ros2 topic echo로는 잘 보이는데, 내 노드에서는 아무것도 안 들어온다."

이 증상을 보면 CLI가 아니라 내 코드의 QoS를 의심해야 한다. 아래 진단은 그대로 유효하다.

진단:

ros2 topic info -v /some_topic

이 명령은 발행자·구독자 각각의 QoS 프로파일을 전부 보여준다. 여기서 신뢰성(reliability)·내구성(durability)이 서로 다른지 확인한다.

대처 — Jazzy에서는 먼저 "아무것도 붙이지 마라"다(실행 확인).

# Jazzy의 topic echo 기본 QoS는 best_available —
# 이미 연결된 발행자에 맞춰 자동으로 낮춰 매칭된다. 대개 이걸로 보인다.
ros2 topic echo /scan

실측으로 확인했다 — BEST_EFFORT 발행자에 대해 옵션 없는 ros2 topic echo는 정상 수신하고, --qos-reliability reliable을 강제하면 오히려 비호환으로 끊긴다(Last incompatible policy: RELIABILITY). 예전 배포판에서 통하던 "안 보이면 best_effort를 붙여라"는 Jazzy에서는 불필요하며, reliable을 붙이는 것은 적극적으로 해롭다(1.5.3.8).

# 옵션이 필요한 경우 — 자동 협상 결과를 일부러 고정하고 싶을 때
ros2 topic echo /scan --qos-reliability best_effort     # 명시적 고정
ros2 topic echo /map  --qos-durability transient_local  # 래치 토픽 과거분까지

# 발행 시에는 자동 협상이 없다 — 명시해야 한다
ros2 topic pub --once /map nav_msgs/msg/OccupancyGrid "{...}" \
  --qos-durability transient_local

--qos-profile best_available도 직접 지정할 수 있다. ros2 topic echo --help가 받아들이는 프로파일 목록에 best_available이 실재한다(실행 확인).

함정: "토픽이 안 나온다"를 코드 문제로 오해해 몇 시간을 쓰는 일이 흔하다. ros2 topic info -v를 먼저 보는 습관이 이 부류의 문제를 즉시 끝낸다. QoS의 정식 설명은 1.5, 실전 설계는 1.6.

0.7.6 CLI가 틀리는 일곱 가지 경우 — 함정 카탈로그

#상황왜대처
1종료된 노드가 목록에 남음데몬 캐시 + DDS 참가자 임대 만료 — 강제 종료일 때만(정상 종료는 0초, SIGKILL은 약 22초)ros2 daemon stop 뒤 다시 조회하거나, 조회할 때 --no-daemon을 붙인다 — 단 --no-daemon은 실행 중인 노드를 10회 중 2회 놓친다(0.7.11.2·0.7.11.3)
2머신마다 topic list 결과가 다름디스커버리는 비대칭·점진적양쪽에서 확인, 네트워크 점검(3.9)
3echo는 보이는데 내 노드만 침묵CLI는 best_available로 자동 협상, 내 노드는 기본 RELIABLEtopic info -v → 내 코드의 QoS 수정(0.7.5)
4hz가 실제와 다름대개는 창(window) 탓 — 기본 10,000개라 100 Hz면 100초를 기억한다. Python 처리 지연은 작은 메시지에서는 거의 무시할 수준(1 kHz까지 오차 ≈0)--window를 명시, 대용량이면 1~3% 낮게 나오는 것을 감안(0.7.12)
5echo를 적용하면 시스템이 느려짐관측이 참여다(0.7.4)--once·--field·필드 절단 옵션 사용
6param·service call이 응답 없음대상 노드가 spin하지 않거나 콜백이 막힘(1.7·2.4)대상 노드의 executor 상태 점검
7토픽·노드가 전혀 안 보임도메인·RMW 불일치, 멀티캐스트 차단ROS_DOMAIN_ID·RMW_IMPLEMENTATION 확인(0.4)

대용량 토픽을 안전하게 관찰하기:

ros2 topic echo /camera/image_raw --no-arr          # 배열 내용 생략
ros2 topic echo /points --field header              # 특정 필드만
ros2 topic echo /points --once                      # 한 번만
ros2 topic hz /camera/image_raw --window 50         # 주파수만
ros2 topic bw /camera/image_raw                     # 대역폭만

--no-arr·--field는 역직렬화·출력 비용을 줄이는 것이지 수신 비용을 없애지는 않는다 — 데이터는 여전히 네트워크를 거쳐 온다. 무선에서 이미지 토픽을 확인할 때는 bw·hz만 쓰고 echo는 피하는 편이 안전하다.

0.7.11 진단 명령이 보고하는 값과 실제를 대조했다 — 실측

82차에 ros2 topic info -v가 한 자리에서 사실과 다른 값을 낸다는 것이 드러났다(1.6.12). 그렇다면 다른 진단 명령은 어떤가 — 이 장이 "CLI가 틀리는 일곱 가지 경우"(0.7.6)를 이미 적어 두었으므로, 그 항목들을 대조군을 두고 실제로 시험했다(실행 확인, Jazzy · rmw_fastrtps_cpp 컨테이너).

대조군이 핵심이다 — "안 보인다"가 정말 없어서인지, 명령이 못 찾은 것인지를 가르지 못하면 아무것도 판정할 수 없다(11.7.13.6).

0.7.11.1 ros2 param get은 적용값이 아니라 저장값을 보여 준다

같은 파라미터를 캐시하는 노드와 콜백으로 반영하는 노드에 각각 걸고 값을 바꿨다.

# 흔한 형태 — 시작할 때 한 번 읽고 그 뒤로 안 본다
self.declare_parameter("rate_hz", 10.0)
self.rate = self.get_parameter("rate_hz").value   # ← 여기서 끝
노드ros2 param set 결과ros2 param get이 말하는 값노드가 실제로 쓰는 값
캐시형(콜백 없음)Set parameter successful99.010.0
대조군(콜백으로 반영)Set parameter successful99.099.0

두 명령이 모두 성공이라고 말하는데 노드는 옛 값으로 실행된다. 파라미터 서버에는 정말로 99.0이 들어갔으므로 param get은 거짓말을 한 것이 아니라 다른 것을 답한 것이다 — 저장값과 적용값은 별개다.

거부는 정직하게 보고된다. 콜백이 successful=False를 돌려주게 만들면 이렇게 나온다.

$ ros2 param set /rejector rate_hz 99.0
Setting parameter failed: 테스트용 거부
$ ros2 param get /rejector rate_hz
Double value is: 10.0

선이 여기 있다 — *거부는 정확히 보이고, 무시는 성공으로 보인다. 콜백이 없는 노드에게 파라미터를 바꾸는 것은 실패가 아니라 무의미이며, CLI에는 그 구분이 없다. "param set이 성공했으니 반영됐겠지"*가 성립하지 않는 이유다(1.9.8).

0.7.11.2 종료된 노드는 어떻게 종료됐는지에 따라 목록에 남는다

0.7.6의 1번 항목("종료된 노드가 목록에 남음")을 신호별로 갈랐다.

종료 방식ros2 node list에서 사라지기까지
SIGTERM(정상 종료)0초 — 즉시
SIGKILL(강제 종료)약 21~22초

정상 종료에서는 유령이 생기지 않는다. 노드가 내려가며 DDS가 참가자 소멸을 알리기 때문이다. SIGKILL은 그럴 틈이 없어 다른 참가자가 임대(lease)가 끊길 때까지 기다린다 — 관측된 20초 남짓이 그 시간이다.

이것이 가장 불리한 때에 일어난다. 세그폴트·OOM 킬러·kill -9로 종료된 노드는 정확히 당신이 원인을 찾고 있는 그 20초 동안 실행 중인 것처럼 보인다(14.8.12·14.8.13). "목록에 있으니 실행 중이다"로 판단하면 잘못된 곳을 조사한다.

0.7.11.3 --no-daemon은 대안이 아니라 다른 종류의 오류다

0.7.6은 대처로 --no-daemon을 제시한다. 두 경로를 나란히 측정해 봤다.

실행 중인 노드 탐지종료된 노드(SIGKILL) 보고
데몬 경유(기본)10/1022초간 계속 있다고 보고
--no-daemon8/10 ← 두 번 놓쳤다즉시 0건

바꿔치기가 아니라 오류의 종류를 바꾸는 것이다. 데몬은 거짓 양성(없는 것을 있다고), --no-daemon은 거짓 음성(있는 것을 없다고)을 낸다. --no-daemon은 매번 새로 디스커버리를 수행하는데 그 시간이 짧아 놓치는 경우가 있다.

그래서 한 번의 조회로 판정하지 마라. ① 기본(데몬)으로 두세 번 조회해 일관되게 나오는지 보고, ② 강제 종료했는데도 남아 있으면 --no-daemon으로 교차 확인하고, ③ 확실히 하려면 ros2 daemon stop 뒤 다시 조회한다. 두 경로가 다른 답을 주면 그 자체가 정보다 — 데몬만 보인다면 이미 종료된 노드다.

0.7.11.4 ros2 doctor는 통신이 끊긴 상태에서도 통과한다

0.3이 "통과했다고 시스템이 정상이라는 뜻은 아니다"라고 적어 둔 것을 실제로 확인했다. ROS_DOMAIN_ID=7에 노드를 실행해 기본 도메인에서는 보이지 않는 상태를 만든 뒤 진단했다.

$ ros2 topic list          # 그 노드의 토픽은 없다
/parameter_events
/rosout

$ ros2 doctor
… UserWarning: geometry2 has been updated to a new version. local: 0.36.21 < latest: 0.36.22
All 5 checks passed

노드가 서로 못 보는데 5개 검사가 전부 통과했다. 게다가 유일하게 나온 경고는 패키지 버전이 상류보다 낮다는, 지금 문제와 아무 상관 없는 내용이다.

*ros2 doctor는 환경을 보지 그래프를 보지 않는다. 배포판·플랫폼·네트워크 인터페이스·패키지 버전이 검사 대상이고, "내 노드가 상대를 보는가"는 검사 항목이 아니다. 노드가 서로 안 보일 때 칠 명령은 ros2 doctor가 아니라 ros2 doctor hello(11.1.15)다 — 이름이 비슷하지만 측정하는 것이 다르다.*

0.7.11.5 정리 — 무엇을 믿고 무엇을 확인할 것인가

명령믿어도 되는 것믿으면 안 되는 것
ros2 param get파라미터 서버의 저장값노드가 쓰는 적용값(0.7.11.1)
ros2 param set거부됐다는 사실성공 = 반영(콜백 없으면 무시)
ros2 node list(데몬)실행 중인 노드가 나온다목록에 있다 = 실행 중이다(강제 종료 후 22초)
ros2 node list --no-daemon나온 것은 실재한다안 나온다 = 없다(10회 중 2회 누락)
ros2 doctor환경·플랫폼·패키지통과 = 통신 정상(전혀 별개)
ros2 topic info -v신뢰성·타입system_default의 내구성(1.6.12)

공통점이 하나 있다 — 어느 명령도 틀린 값을 낸 것이 아니라, 독자가 묻는 것과 다른 것을 답했다. param get은 저장값을, node list는 디스커버리 상태를, doctor는 환경을 답한다. 명령이 무엇을 측정하는지 알면 어긋남이 사라진다.

견해: 진단 명령은 "확인"이 아니라 "증거 하나"로 다뤄라. 이 장이 이미 관측이 참여다(0.7.4)라고 말했듯, 관측 결과도 시스템의 일부이지 시스템 밖의 진실이 아니다. 판정은 두 개 이상의 서로 다른 증거가 같은 곳을 가리킬 때 내리는 것이 안전하다.

0.7.12 ros2 topic hz를 정답을 아는 발행자로 측정해 봤다 — 실측

0.7.10의 체크포인트가 "hz의 수치를 어디까지 믿을 수 있는가"를 묻는데, 이 책은 그 답을 갖고 있지 않았다. 0.7.6의 4번 함정도 "CLI의 Python 처리 지연·유실"이라고 원인을 짐작으로 적어 두었다. 그래서 발행률을 스스로 세어 알려 주는 C++ 발행자를 만들어 대조했다(실행 확인, Jazzy · 20코어 컨테이너).

0.7.12.1 정상 상태에서는 놀랄 만큼 정확하다

조건실제 발행률hz 보고오차
10 Hz · 8 B10.009.999−0.01%
100 Hz · 8 B100.0099.998−0.002%
1 kHz · 8 B999.99999.995≈0
5 kHz · 8 B4999.994997.450−0.05%
10 kHz · 8 B9999.969943.288−0.57%
100 Hz · 1 MB99.9999.016−0.97%
100 Hz · 4 MB99.9997.119−2.9%
100 Hz · 1 MB + CPU 부하100.0099.001−1.0% (부하 없을 때와 같다)

작은 메시지라면 1 kHz까지 사실상 정확하다. "Python이라 못 믿는다"는 통념이 이 조건에서는 성립하지 않는다.

두 가지가 더 보인다.

  • 오차는 항상 음의 방향이다 — hz가 실제보다 크게 나온 경우가 없었다. 그래서 hz가 목표보다 높게 나오면 그것은 CLI 탓이 아니라 실제 발행자 문제(중복 발행·다중 노드)라고 볼 수 있다.
  • 오차는 데이터량을 따라간다 — 초당 400 MB(4 MB × 100 Hz)에서 2.9%다. 주파수가 아니라 대역이 지배 요인이다.

0.7.6의 4번 함정을 정정했다. "부하 적은 시점에 재라"는 조언은 이 실험에서 근거가 없었다 — 20코어 중 4개를 바쁜 루프로 점유해도 결과가 그대로였다. 실제 오차원은 다음 절이다.

0.7.12.2 실제 문제는 창(window)이 메시지 단위라는 것

--help가 밝히듯 기본 창은 메시지 10,000개다(초가 아니다). 관측으로도 창이 10000에서 멈추는 것을 확인했다.

발행률이 100 Hz에서 10 Hz로 급락하는 발행자를 만들어 측정해 봤다.

급락 10초 뒤 hz 보고실제
기본 창(10,000)66.785 Hz10 Hz
--window 109.999 Hz10 Hz

기본 창은 6.7배 부풀린 값을 말한다. 옛 데이터가 창에 그대로 남아 평균을 끌어올리기 때문이다.

여기서 규칙이 하나 나온다.

$$\text{창이 기억하는 시간} = \frac{10{,}000}{\text{발행률(Hz)}}$$

토픽 주기기본 창이 기억하는 시간
1 kHz10초
100 Hz100초
10 Hz약 17분
1 Hz약 3시간

필요와 정반대로 작동한다. 느린 토픽일수록 빨리 이상을 알고 싶은데, 창이 메시지 단위라 느릴수록 기억이 길다. /scan(10 Hz)이 절반으로 떨어져도 기본 hz는 몇 분 동안 옛 값을 섞어 보여 준다.

처방은 하나다 — 진단할 때는 창을 명시하라. ros2 topic hz /scan --window 20이면 최근 20개만 본다. 0.7.6이 적어 둔 "짧게 측정"은 결과적으로 맞는 조언이었지만(짧게 보면 창도 안 찬다) 이유가 달랐다.

0.7.12.3 발행이 완전히 멈추면 아무 말도 하지 않는다

100 Hz로 10초 발행한 뒤 완전히 정지시키고 25초를 더 지켜봤다.

average rate: 99.999
	min: 0.009s max: 0.011s std dev: 0.00015s window: 906
        ← 여기서 출력이 아무 표시 없이 끊긴다. 25초를 기다려도 더 나오지 않는다.

0 Hz도, 경고도, 아무것도 없다. 화면에 마지막으로 남은 값은 99.999 다.

이것이 가장 위험한 형태다. 터미널을 흘깃 보면 "99.999 — 잘 동작하고 있네"로 읽힌다. 신호는 값이 아니라 "새 줄이 안 나온다"는 사실이며, 여러 창을 열어 놓고 보는 상황에서는 놓치기 쉽다.

그래서 hz는 "흐르는가"를 묻는 도구이지 "멈췄는가"를 묻는 도구가 아니다. 멈춤을 판정하려면 ros2 topic echo --once에 타임아웃을 걸거나(값이 오면 실행 중이다) 진단 갱신기의 Stale 판정(6.9.14)을 쓰는 편이 옳다 — 후자는 "안 온다"를 적극적으로 알리도록 설계된 장치다.

0.7.12.4 정리

묻는 것hz로 답할 수 있나
데이터가 흐르는가된다 — 줄이 나오면 흐른다
발행률이 얼마인가된다 — 정상 상태에서 1 kHz까지 정확
발행률이 방금 변했는가기본 창으로는 안 된다 — --window를 줘라
토픽 발행이 중단됐는가안 된다 — 출력이 끊길 뿐 아무 말도 없다
발행자가 정말 그 속도로 보내는가간접적으로만 — hz는 CLI에 도착한 것을 센다

마지막 줄이 83차의 결론과 같다(0.7.11.5) — hz도 "발행자가 얼마나 보내는가"가 아니라 "내게 얼마나 도착하는가"를 답한다. 대용량에서 나온 1~3%의 음의 오차가 바로 그 차이다.

0.7.7 관측 명령의 실전 레시피

그래프 파악(처음 보는 시스템에 들어갔을 때):

ros2 node list                    # 누가 있나
ros2 topic list -t                # 무엇이 흐르나 (-t: 타입 함께)
ros2 node info /some_node         # 이 노드의 발행·구독·서비스·액션
ros2 topic info -v /some_topic    # 양쪽 끝과 QoS

데이터 확인:

ros2 topic echo /topic --once
ros2 topic hz /topic
ros2 topic bw /topic
ros2 interface show sensor_msgs/msg/Imu    # 이 타입의 필드 정의
ros2 interface proto sensor_msgs/msg/Imu   # 채워 넣을 YAML 뼈대

ros2 interface proto는 topic pub·service call의 인자를 직접 작성할 때 특히 유용하다.

제어·설정:

ros2 param list /some_node
ros2 param describe /some_node some_param   # 타입·범위·읽기전용 여부
ros2 param get /some_node some_param
ros2 param set /some_node some_param 3.14
ros2 param dump /some_node > node_params.yaml   # 현재값 백업 (1.9)

ros2 service list -t
ros2 service call /some_service std_srvs/srv/Trigger "{}"

ros2 action list -t
ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose "{...}" --feedback

라이프사이클·컴포넌트(1.12·1.13):

ros2 lifecycle nodes
ros2 lifecycle get /managed_node
ros2 lifecycle set /managed_node configure

ros2 component list
ros2 component load /container my_pkg my_pkg::MyComponent

환경·네트워크 점검:

ros2 doctor --report      # 배포판·플랫폼·네트워크 요약
ros2 multicast receive    # 다른 터미널·다른 머신에서
ros2 multicast send       # 멀티캐스트 도달성 확인 (3.9)

ros2 multicast send·receive 쌍은 "디스커버리가 안 되는 이유가 멀티캐스트 차단인가" 를 가르는 가장 빠른 시험이다. 두 머신에서 이 쌍이 통하지 않으면 ROS 2 이전에 네트워크 문제다.

0.7.8 자동완성과 도움말 탐색

# 자동완성 — /opt/ros/jazzy/share/ros2cli/environment/ros2-argcomplete.{bash,zsh} (실행 확인)
source /opt/ros/jazzy/setup.bash   # 완성 스크립트 포함

ros2 --help
ros2 topic --help
ros2 topic echo --help             # 동사 단위 도움말이 가장 정확하다

CLI 옵션은 배포판마다 바뀌므로, 책이나 블로그의 옵션보다 --help가 정본이다. 특히 QoS 관련 옵션 이름은 배포판에 따라 표기가 달라진 이력이 있다.

0.7.9 운영 로봇에서의 CLI — 관측의 비용

개발 환경에서는 CLI를 마음껏 써도 되지만, 필드에서 운용되는 로봇에 SSH로 들어가 CLI를 쓸 때는 다르다.

위험설명완화
대역 포화echo가 대용량 토픽을 끌어와 무선을 막는다bw·hz만 쓰기, 로컬에서만 echo
디스커버리 부하CLI 프로세스마다 참가자가 늘어난다조회를 최소화, 데몬 재사용
lazy 노드 활성화구독자가 생겨 발행이 시작된다영향 있는 토픽 파악 후 접근
실시간 경로 방해Python CLI가 CPU를 점유CPU 격리·우선순위 분리(12.2)
의도치 않은 상태 변경param set·service call·lifecycle set은 시스템을 바꾼다읽기 명령과 쓰기 명령을 의식적으로 구분

대안(권장): 필드 진단은 CLI 즉석 조회보다 상시 관측 체계로 푸는 편이 안전하다 — 진단 토픽(6.9), 상시 기록(11.3), 메트릭·트레이싱(11.6). CLI는 그 위에서 "확인 사살"용으로 쓴다. 이 원칙은 11.1(현장 디버깅 플레이북)에서 절차로 정리한다.

0.7.10 체크포인트

  • ros2 <명령> <동사> 구조가 어떻게 확장되는지(엔트리포인트) 설명하고, 팀 전용 명령을 추가하는 방법을 말할 수 있는가.
  • 데몬이 존재하는 이유와, 데몬 때문에 생기는 대표적 오류(유령 노드)를 설명할 수 있는가. --no-daemon은 언제 쓰나.
  • "ros2 topic echo는 관찰자가 아니라 참여자"라는 문장의 실무적 함의 세 가지를 말할 수 있는가.
  • ros2 topic echo가 아무것도 출력하지 않을 때, 가장 먼저 실행할 한 줄은 무엇인가.
  • ros2 topic hz의 수치를 어디까지 믿을 수 있는가. 더 신뢰할 만한 측정 수단은 무엇인가(11.6·11.7·0.7.12).
  • 100 Hz 토픽이 방금 10 Hz로 떨어졌다. 기본 창의 hz는 왜 66 Hz라고 말하는가(0.7.12.2).
  • 토픽 발행이 중단됐는지 hz로 판정할 수 있는가. 못 한다면 무엇을 쓰는가(0.7.12.3).
  • 필드 로봇에서 echo 대신 bw·hz를 쓰라는 권고의 근거를 설명할 수 있는가.
  • 읽기 명령과 시스템을 바꾸는 명령을 구분해 나열할 수 있는가.
  • ros2 param set이 successful이라고 답했는데 노드 동작이 안 바뀐다. 버그인가 아닌가, 무엇으로 확인하는가(0.7.11.1).
  • ros2 node list에 노드가 보인다. 실행 중이라고 단정할 수 있는가. 어떤 경우에 못 하는가(0.7.11.2).
  • ros2 doctor가 전부 통과했는데 노드가 서로 안 보인다. 모순인가(0.7.11.4).

여기까지가 제0부다. 설치·워크스페이스·패키지·CLI라는 작업대가 갖춰졌다. 다음 장 1.1부터 제1부에서는 이 위에서 실행되는 계산 그래프의 코어 — 노드·이름·리매핑부터 시작해 토픽·QoS·서비스·액션·파라미터·라이프사이클까지 — 를 하나씩 원리 수준에서 연다.