AEGLOSLAB ROS 2 Jazzy 대전

제5부 좌표·모델

5.2 tf2 실전 — 시간·외삽·정적 변환의 함정

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

5.1이 tf2의 구조였다면 이 장은 그 구조가 실제로 어긋나는 지점들이다. ROS 2 사용자가 tf에서 잃는 시간의 대부분이 여기에 있고, 대부분은 같은 몇 가지 원인으로 수렴한다.

가장 유명한 오류 메시지부터 시작한다.

Lookup would require extrapolation into the future.
Requested time 1723531234.567 but the latest data is at time 1723531234.512

이 메시지 하나에 시간 동기화·발행 주기·처리 지연·시뮬레이션 시간의 문제가 전부 얽혀 있다.

배우는 것:

  • 외삽 오류의 정확한 의미 — 과거·미래 각각의 원인과 대처.
  • Time(0)의 의미와 위험 — 편리하지만 정합성을 포기하는 것.
  • 타임아웃 설계 — 얼마나 기다릴 것인가.
  • 시계 불일치 — 다중 머신·시뮬레이션에서.
  • 정적 변환의 실전 함정 — 늦은 구독자·중복·시간.
  • 다중 로봇에서의 tf — 네임스페이스와 프레임 접두어.
  • 진단 플레이북 — 증상에서 원인으로.
  • 오차를 만들어 측정해 본 결과(5.2.11) — 메시지에서 시계 차이를 읽어 내는 법.

선수 지식: 5.1(tf2 구조), 1.10(시계·use_sim_time·시간 점프), 1.6.3.3(TRANSIENT_LOCAL), 1.1.7(다중 로봇 이름).

정직: 원인 분석과 대처는 tf2 구현 구조에 근거하며 대부분 이 환경에서 실행 검증하지 않았다(목차 3). 다만 오류 메시지 문구는 Jazzy에서 확인했다 — 위 예시의 형식이 실제 출력과 일치한다(5.2.11). 다른 배포판에서는 다를 수 있다.

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

5.2.2 외삽 오류 — 두 방향

tf2는 외삽하지 않는다. 이력 범위 밖의 시각을 요청하면 오류다. 방향에 따라 원인이 다르다.

외삽 오류 미래 방향 'into the future' 과거 방향 'into the past' 요청 시각 > 최신 데이터 요청 시각 < 가장 오래된 데이터 원인: 발행 지연 · 주기 부족 · 시계 불일치 · 처리 순서 원인: 캐시 시간 부족 · 처리 지연 과다 · 시간 점프

5.2.2.1 미래 방향 — into the future

"내가 원하는 시각의 변환이 아직 안 왔다."

원인설명대처
타임아웃 미사용발행을 기다리지 않고 즉시 조회lookupTransform에 타임아웃 인자
발행 주기 부족tf가 10 Hz인데 센서가 30 Hz발행 주기 증가
시계 불일치다른 머신의 스탬프가 미래로 보임시계 동기(1.10.8)
발행 지연발행 노드가 밀림executor 확인(2.3)
use_sim_time 불일치일부 노드만 시뮬 시간(1.10.3)전체 통일

타임아웃이 첫 번째 대처다:

// ❌ 타임아웃 없음 — 아직 안 왔으면 즉시 실패
auto t = tf_buffer_->lookupTransform("base_link", "laser", stamp);

// ✅ 잠시 기다린다
auto t = tf_buffer_->lookupTransform("base_link", "laser", stamp,
                                     rclcpp::Duration::from_seconds(0.1));

타임아웃은 그 시간만큼 콜백을 블로킹한다. 즉 executor를 점유한다(2.2.5). 이것이 트레이드오프다.

타임아웃효과대가
0(없음)블로킹 없음실패 잦음
짧게(50~100 ms)대부분 성공약간의 블로킹
길게(1 s 이상)거의 성공executor 정지·지연 누적

견해: 센서 주기의 1~3배 정도가 합리적 출발점이다. 10 Hz 센서면 100~300 ms. 그보다 길게 설정해야 성공한다면 그것은 타임아웃 문제가 아니라 발행 측이나 시계에 문제가 있다는 신호다 — 타임아웃을 늘려 덮지 말고 원인을 보라.

함정: 콜백 안에서 긴 타임아웃으로 tf를 기다리는 것은 1.7의 데드락과 유사한 위험이 있다. 그동안 다른 콜백이 실행되지 않고, tf 발행이 같은 executor에서 온다면 영원히 기다릴 수도 있다. tf 리스너는 별도 콜백 그룹에 두거나(2.4), 멀티스레드 executor를 쓰는 것이 안전하다.

5.2.2.2 과거 방향 — into the past

"내가 원하는 시각의 변환은 이미 버려졌다."

원인대처
버퍼 캐시 시간 부족캐시 시간 확대(5.1.5)
처리 지연 과다파이프라인 최적화(7.8)
bag 재생 시간 점프버퍼 초기화(1.10.4)
시뮬레이션 리셋동일
노드가 늦게 시작TRANSIENT_LOCAL 아닌 /tf는 과거를 못 받음
// 캐시 시간 확대
tf_buffer_ = std::make_unique<tf2_ros::Buffer>(
    this->get_clock(), tf2::durationFromSec(30.0));

시간 점프 시 버퍼를 비워야 한다(1.10.4):

rcl_jump_threshold_t threshold;                      // 인자 셋 — 1.10.11
threshold.on_clock_change = true;
threshold.min_forward.nanoseconds = RCL_S_TO_NS(1);
threshold.min_backward.nanoseconds = -RCL_S_TO_NS(1);

jump_handler_ = get_clock()->create_jump_callback(
  []() {},
  [this](const rcl_time_jump_t & jump) {
    RCLCPP_WARN(get_logger(), "시간 점프 %.3f초 — tf 버퍼 초기화",
                jump.delta.nanoseconds / 1e9);
    tf_buffer_->clear();
  },
  threshold);

견해: bag을 --loop로 재생하며 개발한다면 이 콜백은 사실상 필수다. 없으면 루프가 반복될 때마다 tf가 비정상이 되고, 그 현상을 재현·설명하지 못한 채 시간을 버린다.

5.2.3 Time(0)의 의미와 위험

// "가장 최근에 사용 가능한 변환"
auto t = tf_buffer_->lookupTransform("base_link", "laser", tf2::TimePointZero);
t = self.tf_buffer.lookup_transform('base_link', 'laser', rclpy.time.Time())

동작: 스탬프를 무시하고 버퍼에 있는 가장 최신 변환을 준다. 외삽 오류가 나지 않는다.

장점단점
오류가 거의 안 남시간 정합성을 포기
단순데이터 시각과 변환 시각이 다름
초기화·프로토타입에 편리조용한 오차

얼마나 틀리는가: 로봇이 1 m/s로 이동하고 변환이 50 ms 어긋나면 5 cm 오차다. 회전이 1 rad/s면 0.05 rad(약 3도). 정밀 작업에서는 치명적이고, 대략적 용도에서는 무시할 만하다.

$$\varepsilon_{\text{위치}} \approx v \cdot \Delta t, \qquad \varepsilon_{\text{각도}} \approx \omega \cdot \Delta t$$

견해: Time(0)이 정당한 경우는 셋이다. ① 변환이 실제로 거의 정적일 때(고정 센서 간), ② 초기화·설정 단계에서 한 번 읽을 때, ③ 정밀도 요구가 명백히 낮을 때. 그 외에는 실제 스탬프를 쓰라. 특히 인지 결과를 지도에 투영하거나 매니퓰레이션 목표를 계산할 때 Time(0)은 원인 불명의 오차를 만든다.

Time(0)으로 문제를 "해결"하는 것이 흔한 유혹이다 — 외삽 오류가 사라지므로. 그러나 오류가 사라진 것이지 문제가 해결된 것이 아니다. 오류는 정보였고, 그것을 버린 것이다.

5.2.4 시계 불일치

다중 머신 로봇에서 tf 문제의 큰 축이다(1.10.8).

머신 A (주 컴퓨터) 시계: t odom → base_link 스탬프 t 머신 B (인지 컴퓨터) 시계: t + 0.3s 센서 데이터 스탬프 t+0.3 조회 요청: t+0.3 버퍼 최신: t ☠️ 'into the future' 0.3초

증상의 특징: 외삽 오류의 크기가 일정하다. 0.3초씩 계속 부족하면 시계 차이를 의심한다.

# 지연 통계 — 평균 지연이 일정하게 크면 시계 문제
ros2 run tf2_ros tf2_monitor odom base_link

# 머신 간 시계 차이 직접 확인
# 각 머신에서
date +%s.%N
chronyc tracking          # chrony 사용 시

대처:

수단정밀도(대략)관련 장
chrony(LAN)1 ms 이하 가능1.10.8
PTP마이크로초7.7
하드웨어 트리거나노초7.7

견해: 컴퓨터가 둘 이상이면 시계 동기를 초기 인프라로 세우라(1.10.8의 반복). tf 외삽 오류를 며칠 쫓다가 이 문제에 도달하는 경우가 매우 흔하다.

시뮬레이션 시간 불일치:

상황증상
일부 노드만 use_sim_time=true시간대가 완전히 다름 → 대량 외삽 오류
/clock 발행자 없음시간이 0에 멈춤(1.10.3)
tf 발행 노드가 wall timer 사용주기와 스탬프 불일치(2.5.7)
# 전체 적용 (1.9.7)
/**:
  ros__parameters:
    use_sim_time: true

5.2.5 정적 변환의 실전 함정

① 늦게 시작한 노드가 못 받는 경우

/tf_static은 TRANSIENT_LOCAL이므로 원칙적으로 늦은 구독자도 받는다(5.1.6). 그런데 발행자가 이미 종료됐다면 못 받는다 — TRANSIENT_LOCAL은 발행자 수명 안에서만 유효하기 때문이다(1.6.3.3).

상황결과
정적 발행 노드가 실행 중✅ 늦은 구독자도 받음
발행 후 노드가 종료❌ 받을 수 없음
발행자가 재시작그동안 연결된 구독자는 다시 받음

함정: "정적 변환을 한 번 발행하고 종료하는 노드"는 안티패턴이다. static_transform_publisher가 계속 실행 중인 이유가 이것이다 — 데이터를 계속 보내는 것이 아니라 TRANSIENT_LOCAL 보관자로 남아 있는 것이다.

② 여러 발행자의 누적

/tf_static은 여러 발행자의 변환이 함께 보관된다. 이는 의도된 설계지만 함정도 있다.

상황결과
서로 다른 프레임 쌍✅ 정상 누적
같은 프레임 쌍을 둘이 발행⚠️ 충돌 — 어느 것이 우선할지 불명
# 정적 변환 전체 확인
ros2 topic echo /tf_static --qos-durability transient_local --once
# 중복 확인
ros2 run tf2_tools view_frames    # 트리에서 비정상적인 부모 관계 확인

③ 시간 처리의 미묘함

정적 변환은 모든 시각에 유효하지만, 버퍼에 들어온 시점 이전의 조회가 어떻게 처리되는지는 구현 세부다(확인 필요). 노드 시작 직후 과거 시각으로 조회하면 실패할 수 있으므로, 초기화 단계에서는 타임아웃을 넉넉히 준다.

④ URDF와의 중복

robot_state_publisher가 URDF의 고정 관절을 /tf_static으로 발행한다(5.5). 같은 변환을 별도로 정적 발행하면 충돌한다.

견해: URDF에 있는 것은 URDF에만 두라. 센서 위치를 URDF에도 적고 launch에서 static_transform_publisher로도 발행하는 구성이 흔한 실수이며, 두 값이 달라지는 순간 원인 불명의 오차가 된다. 단일 출처 원칙을 지킨다(5.7).

5.2.6 다중 로봇에서의 tf

1.1.7에서 "/tf는 전역 이름이라 리매핑이 필요하다"고 했다. 구체적으로 무엇을 해야 하는가.

두 가지를 함께 해야 한다:

다중 로봇 tf 분리 ① 토픽 네임스페이스 /tf → /robot1/tf ② 프레임 이름 접두어 base_link → robot1/base_link 그래프 분리 프레임 충돌 방지

① 토픽 리매핑:

Node(
    package='robot_state_publisher', executable='robot_state_publisher',
    namespace='robot1',
    remappings=[('/tf', 'tf'), ('/tf_static', 'tf_static')],
    parameters=[{'robot_description': robot_desc}],
)

('/tf', 'tf') 리매핑이 관용구다 — 절대 이름을 상대 이름으로 바꿔 네임스페이스가 적용되게 한다(1.1.4).

② 프레임 접두어:

<!-- xacro에서 접두어 파라미터 (5.4) -->
<xacro:arg name="prefix" default=""/>
<link name="${prefix}base_link"/>
<joint name="${prefix}laser_joint" type="fixed">
  <parent link="${prefix}base_link"/>
  <child link="${prefix}laser_link"/>
</joint>
xacro robot.urdf.xacro prefix:=robot1/ > robot1.urdf

왜 둘 다 필요한가:

조치해결하는 것
토픽 리매핑만그래프는 분리되지만, 중앙 노드가 두 로봇의 tf를 합칠 때 프레임 이름 충돌
접두어만프레임은 구분되지만 /tf 토픽 부하가 그대로
둘 다완전한 분리

함정: 접두어에 슬래시를 쓸지(robot1/base_link) 밑줄을 쓸지(robot1_base_link) 는 관행이 갈린다. 슬래시가 계층적으로 읽히지만, 일부 도구가 슬래시를 다르게 처리할 수 있다(확인 필요). 시스템 안에서 하나로 통일하는 것이 중요하며, Nav2·MoveIt 2 등 생태계 패키지가 프레임 이름을 어떻게 다루는지 확인해야 한다.

견해: 로봇이 서로 협조할 필요가 없다면 도메인 분리(1.1.7의 ②)가 훨씬 단순하다. tf 리매핑·접두어·설정 파일 수정이 전부 불필요해진다. 협조가 필요할 때만 위 복잡도를 감수한다.

5.2.7 진단 플레이북

끊김 중복 부모 정상 예 아니오 future past 없음 tf 문제 view_frames 트리가 온전한가? 누락된 발행자 노드 실행 확인 중복 발행자 tf2_monitor tf2_monitor 지연이 일정하게 큰가? 시계 불일치 5.2.4 외삽 오류 방향은? 타임아웃 · 발행 주기 5.2.2.1 캐시 시간 · 처리 지연 5.2.2.2 프레임 이름 · 축 방향 5.1.7

단계별 명령:

# 1. 트리 전체 확인 (항상 여기서 시작)
ros2 run tf2_tools view_frames

# 2. 발행자·지연 확인
ros2 run tf2_ros tf2_monitor
ros2 run tf2_ros tf2_monitor odom base_link

# 3. 특정 변환 실시간 확인
ros2 run tf2_ros tf2_echo base_link camera_link

# 4. 정적 변환 확인 (QoS 옵션 필수)
ros2 topic echo /tf_static --qos-durability transient_local --once

# 5. 발행 주기
ros2 topic hz /tf

# 6. 시뮬 시간 확인
ros2 param get /some_node use_sim_time
ros2 topic hz /clock

# 7. 시계 동기 (다중 머신)
chronyc tracking

증상별 빠른 판별:

증상우선 의심
into the future, 오차가 일정시계 불일치
into the future, 오차가 변동발행 주기·지연
into the past캐시 시간·처리 지연
Could not find a connection트리 끊김·프레임 이름 오타
변환값이 흔들림중복 발행자
결과가 90도 회전광학 프레임 혼동
재측위 때 로봇이 튐map 기준 제어(5.1.11의 13번)
bag 루프마다 이상시간 점프 미처리

5.2.8 견고한 tf 사용 패턴

실전에서 검증된 형태를 정리한다.

class RobustTfConsumer : public rclcpp::Node {
public:
  RobustTfConsumer() : Node("consumer") {
    tf_buffer_ = std::make_unique<tf2_ros::Buffer>(
        get_clock(), tf2::durationFromSec(10.0));    // 넉넉한 캐시

    // tf 리스너를 별도 콜백 그룹에 (2.4)
    tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_, this);

    // 시간 점프 대응 (1.10.4) — 임계값은 필수 인자다 (1.10.11)
    rcl_jump_threshold_t threshold;
    threshold.on_clock_change = true;
    threshold.min_forward.nanoseconds = RCL_S_TO_NS(1);
    threshold.min_backward.nanoseconds = -RCL_S_TO_NS(1);
    jump_handler_ = get_clock()->create_jump_callback(
      []() {},
      [this](const rcl_time_jump_t &) { tf_buffer_->clear(); reset_state(); },
      threshold);
  }

private:
  void on_sensor(const sensor_msgs::msg::LaserScan::SharedPtr msg) {
    geometry_msgs::msg::TransformStamped tf;
    try {
      tf = tf_buffer_->lookupTransform(
          "base_link", msg->header.frame_id, msg->header.stamp,
          rclcpp::Duration::from_seconds(0.1));
    } catch (const tf2::ExtrapolationException & ex) {
      ++extrapolation_failures_;
      RCLCPP_WARN_THROTTLE(get_logger(), *get_clock(), 2000,
          "tf 외삽 실패(누적 %zu): %s", extrapolation_failures_, ex.what());
      return;
    } catch (const tf2::TransformException & ex) {
      ++other_failures_;
      RCLCPP_ERROR_THROTTLE(get_logger(), *get_clock(), 2000,
          "tf 조회 실패(누적 %zu): %s", other_failures_, ex.what());
      return;
    }

    // 데이터 나이 검사 (1.10.7)
    auto age = now() - rclcpp::Time(msg->header.stamp, get_clock()->get_clock_type());
    if (age > rclcpp::Duration::from_seconds(0.5)) {
      RCLCPP_WARN_THROTTLE(get_logger(), *get_clock(), 1000,
          "데이터가 %.2f초 묵음 — 무시", age.seconds());
      return;
    }

    process(msg, tf);
  }

  // 진단 토픽으로 실패율 보고 (6.9)
  void publish_diagnostics() {
    // extrapolation_failures_ · other_failures_ 를 보고
  }

  size_t extrapolation_failures_{0}, other_failures_{0};
};

이 패턴의 요소들:

요소이유
넉넉한 캐시 시간과거 방향 오류 방지
예외 종류별 구분원인이 다르므로 대처도 다르다
THROTTLE 로그로그 폭주 방지(1.11.5)
실패 카운터 + 진단조용한 실패를 드러낸다
데이터 나이 검사묵은 데이터로 제어 방지
시간 점프 콜백bag·시뮬 대응

견해: 실패 카운터를 진단으로 내는 것이 이 패턴의 핵심이다. tf 조회 실패는 조용하다 — 노드는 계속 실행 중이고, 로그는 THROTTLE되어 눈에 안 띈다. 카운터를 진단 토픽으로 내면 "이 노드가 데이터의 30%를 버리고 있다" 를 운영에서 알 수 있다(6.9·11.1).

5.2.11 시각 오차를 만들어 측정해 봤다 — 실측

이 장의 서술이 맞는지, 그리고 현장에서 오차의 크기와 방향을 어떻게 읽어 내는지를 확인하려고 스탬프를 일부러 어긋낸 발행자를 만들어 측정해 봤다. odom → base_link를 20 Hz로 발행하되 스탬프에 오프셋을 주고, 구독 측에서 두 가지 방식으로 조회했다(실행 확인, Jazzy).

5.2.11.1 세 가지 오차에서의 결과

발행자 스탬프lookup(Time())lookup(now())메시지의 시각 차
오차 없음✅ 성공❌ into the future약 9 ms
5초 과거✅ 성공❌ into the future5.001초
5초 미래✅ 성공❌ into the past약 4.02초

세 가지가 한꺼번에 확인된다.

① Time()은 무슨 짓을 해도 성공한다. 스탬프가 5초 어긋난 상태에서도 오류가 없다 — 5초 묵은 변환을 조용히 돌려준다. 5.2.3이 "조용한 오차"라고 부른 것이 정확히 이것이며, 실측으로 보면 훨씬 심각하다. 오류가 없다는 것이 정상이라는 뜻이 아니다.

② now()로 조회하면 오차가 0이어도 실패한다. 9 ms — 발행 주기와 전송 지연의 합이다. 즉 "현재 시각의 변환을 달라"는 요청은 원리적으로 거의 항상 실패한다. 타임아웃이 선택이 아니라 필수인 이유다(5.2.2.1).

③ 메시지가 방향과 크기를 둘 다 알려 준다.

Lookup would require extrapolation into the future.
Requested time 1786921021.674859 but the latest data is at time 1786921016.673610

두 숫자의 차가 곧 오차의 크기다 — 여기서는 5.001초. 그리고 방향이 원인을 가른다.

메시지뜻전형적 원인
into the future + 차이가 일정발행 측 시계가 그만큼 과거시계 미동기(5.2.4)
into the future + 차이가 수~수십 ms단순 지연타임아웃 미사용·발행 주기 부족
into the past발행 측 시계가 미래, 또는 캐시 부족시계 미동기·처리 지연(5.2.2.2)

실무 팁: 오류 메시지를 읽지 말고 빼라. "외삽 오류가 난다"까지만 보고 넘기는 경우가 많은데, 두 숫자를 빼면 시계 차이가 초 단위로 바로 나온다. chronyc tracking을 치러 가기 전에 이미 답이 손에 있다.

5.2.11.2 타임아웃은 지연을 고치고 시계는 못 고친다 — 구분하는 법

같은 실험에 timeout=0.5s를 주고 다시 측정하면 두 경우가 명확하게 갈린다.

발행자 스탬프결과걸린 시간
오차 없음✅ 성공41~61 ms
5초 과거❌ 실패506 ms — 타임아웃을 다 쓰고

진단 규칙이 여기서 나온다.

타임아웃을 줬을 때 수십 ms 만에 성공하면 지연이었고, 타임아웃을 전부 소진하고 실패하면 시계 오차다. 후자는 타임아웃을 늘려도 절대 해결되지 않는다 — 5초 어긋난 시계에 0.5초를 1초로 늘려 봐야 실패는 그대로이고, 콜백만 두 배로 오래 막힌다(5.2.2.1의 executor 점유).

5.2.2.1은 "그보다 길게 설정해야 성공한다면 발행 측이나 시계에 문제가 있다는 신호"라고 적었는데, 실측이 이를 더 날카로운 형태로 바꿔 준다 — "길게 설정해야 성공"이 아니라 "얼마를 주든 다 쓰고 실패"가 시계 오차의 지문이다.

5.2.11.3 이 실험에서 확인하지 못한 것

  • 머신 두 대의 실제 시계 차이는 측정하지 못했다. 컨테이너는 호스트 커널의 시계를 공유하므로(별도 time namespace를 주지 않는 한) 컨테이너 시계만 어긋내는 것이 불가능했다. 그래서 스탬프에 오프셋을 주는 방식으로 대신했다 — tf2가 보는 것은 스탬프뿐이므로 tf2 관점에서는 동등하지만, NTP·chrony의 실제 동작은 확인하지 않았다.
  • tf2_monitor의 출력으로 같은 진단이 되는지는 측정해 보지 않았다(확인 필요).

5.2.9 함정 카탈로그

#함정증상대처
1타임아웃 없이 조회잦은 외삽 오류센서 주기의 1~3배
2타임아웃을 계속 늘림executor 정지·지연 누적원인 진단
3Time(0)으로 오류 회피조용한 오차실제 스탬프 사용
4시계 미동기(다중 머신)일정한 크기의 외삽 오류chrony·PTP
5일부 노드만 use_sim_time대량 외삽 오류/**:로 전체 적용
6정적 변환 발행 후 노드 종료늦은 노드가 못 받음발행자 상주
7URDF와 별도 정적 발행 중복값 불일치·조용한 오차단일 출처
8bag 루프에서 버퍼 미초기화루프마다 이상 동작점프 콜백
9다중 로봇에서 /tf 미리매핑프레임 충돌리매핑 + 접두어
10접두어 형식 불일치일부 도구에서 실패시스템 내 통일
11예외를 포착하고 로그만실패율을 모름카운터 + 진단
12콜백에서 긴 tf 대기executor 정지·교착콜백 그룹 분리
13캐시 시간이 파이프라인보다 짧음past 방향 오류버퍼 확대
14오류 방향을 구분하지 않음잘못된 대처future/past 판별
15외삽 오류 메시지를 읽고 넘김시계 차이가 손에 있는데 놓침두 시각을 빼라(5.2.11.1)
16실패하니 타임아웃을 늘림시계 문제는 그대로, 콜백만 더 막힘다 쓰고 실패하면 시계(5.2.11.2)

14번을 강조한다. into the future와 into the past는 완전히 다른 문제다. 전자는 "아직 안 왔다"(발행·시계), 후자는 "이미 버렸다"(캐시·지연). 메시지를 끝까지 읽고 방향을 확인하는 것만으로 진단 시간이 크게 줄어든다.

5.2.10 체크포인트

  • 외삽 오류의 두 방향과 각각의 원인 세 가지 이상을 말할 수 있는가.
  • 타임아웃을 어떻게 정하는가. 타임아웃을 계속 늘려야 한다면 그것이 무엇의 신호인가.
  • 콜백 안에서 tf를 기다리는 것이 왜 위험한가. 어떻게 완화하는가.
  • Time(0)이 하는 일과, 그것이 만드는 오차를 식으로 표현할 수 있는가. 정당한 사용 세 가지는?
  • "Time(0)으로 오류가 사라졌다"가 왜 해결이 아닌가.
  • 시계 불일치로 인한 외삽 오류의 특징적 증상은 무엇인가. 어떻게 확인하는가.
  • /tf_static 발행 후 노드를 종료하면 무슨 일이 생기는가. static_transform_publisher가 계속 실행 중인 이유는?
  • URDF와 별도 정적 발행을 중복하면 안 되는 이유는?
  • 다중 로봇에서 tf를 분리하려면 무엇을 둘 다 해야 하는가. 각각이 해결하는 문제는?
  • 진단 플레이북의 첫 단계는 무엇인가. 증상별 우선 의심 대상을 다섯 가지 말할 수 있는가.
  • 견고한 tf 소비 패턴의 여섯 요소를 들 수 있는가. 그중 조용한 실패를 드러내는 것은?
  • into the future와 into the past를 구분하는 것이 왜 중요한가.
  • 시각 오차가 0일 때 now()로 조회하면 성공하는가, 실패하는가. 왜 그런가(5.2.11.1).
  • 외삽 오류 메시지만 보고 시계 차이의 크기와 방향을 알아내는 방법은(5.2.11.1).
  • 타임아웃을 줬을 때 "지연"과 "시계 오차"를 무엇으로 구분하는가(5.2.11.2).

다음 장 5.3부터는 tf가 다루는 관계를 어디서 얻는가 — 로봇 모델로 넘어간다. URDF의 구조, 관절 종류, 관성·충돌 기하, 그리고 URDF가 표현하지 못하는 것들.