제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는 외삽하지 않는다. 이력 범위 밖의 시각을 요청하면 오류다. 방향에 따라 원인이 다르다.
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).
증상의 특징: 외삽 오류의 크기가 일정하다. 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는 전역 이름이라 리매핑이 필요하다"고 했다. 구체적으로 무엇을 해야 하는가.
두 가지를 함께 해야 한다:
① 토픽 리매핑:
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 진단 플레이북
단계별 명령:
# 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 future | 5.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 정지·지연 누적 | 원인 진단 |
| 3 | Time(0)으로 오류 회피 | 조용한 오차 | 실제 스탬프 사용 |
| 4 | 시계 미동기(다중 머신) | 일정한 크기의 외삽 오류 | chrony·PTP |
| 5 | 일부 노드만 use_sim_time | 대량 외삽 오류 | /**:로 전체 적용 |
| 6 | 정적 변환 발행 후 노드 종료 | 늦은 노드가 못 받음 | 발행자 상주 |
| 7 | URDF와 별도 정적 발행 중복 | 값 불일치·조용한 오차 | 단일 출처 |
| 8 | bag 루프에서 버퍼 미초기화 | 루프마다 이상 동작 | 점프 콜백 |
| 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가 표현하지 못하는 것들.