디버깅

목표: 일반적인 tf2 관련 문제를 디버깅하는 체계적인 방법을 사용하는 방법을 배우세요.

배경 정보

이 튜토리얼은 전형적인 tf2 문제를 디버깅하는 단계를 안내합니다. 또한 tf2_echo, tf2_monitor, view_frames 와 같은 다양한 tf2 디버깅 도구를 사용합니다. 이 튜토리얼은 tf2 학습 튜토리얼을 완료한 것으로 가정합니다.

디버깅 예제

1 예제 설정 및 시작

이 튜토리얼에서는 여러 문제가 있는 데모 애플리케이션을 설정합니다. 이 튜토리얼의 목표는 이러한 문제를 찾고 해결하기 위해 체계적인 접근 방식을 적용하는 것입니다. 먼저 소스 파일을 만들어 보겠습니다.

tf2 튜토리얼 에서 생성한 learning_tf2_cpp 패키지로 이동합니다. src 디렉토리에서 소스 파일 turtle_tf2_listener.cpp 의 사본을 만들고 turtle_tf2_listener_debug.cpp 로 이름을 바꿉니다.

선호하는 텍스트 편집기를 사용하여 파일을 열고, 67번째 줄을 다음과 같이 변경합니다.

std::string toFrameRel = "turtle2";

에서

std::string toFrameRel = "turtle3";

그리고 75-79번째 줄의 lookupTransform() 호출을 다음과 같이 변경합니다.

try {
   transformStamped = tf_buffer_->lookupTransform(
     toFrameRel,
     fromFrameRel,
     tf2::TimePointZero);
} catch (tf2::TransformException & ex) {

에서

try {
   transformStamped = tf_buffer_->lookupTransform(
     toFrameRel,
     fromFrameRel,
     this->now());
} catch (tf2::TransformException & ex) {

그리고 파일을 저장합니다. 이 데모를 실행하려면 learning_tf2_cpp 패키지의 launch 하위 디렉토리에 start_tf2_debug_demo.launch.py 라는 런치 파일을 만들어야 합니다.

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration

from launch_ros.actions import Node

def generate_launch_description():
   return LaunchDescription([
      DeclareLaunchArgument(
         'target_frame', default_value='turtle1',
         description='Target frame name.'
      ),
      Node(
         package='turtlesim',
         executable='turtlesim_node',
         name='sim',
         output='screen'
      ),
      Node(
         package='learning_tf2_cpp',
         executable='turtle_tf2_broadcaster',
         name='broadcaster1',
         parameters=[
               {'turtlename': 'turtle1'}
         ]
      ),
      Node(
         package='learning_tf2_cpp',
         executable='turtle_tf2_broadcaster',
         name='broadcaster2',
         parameters=[
               {'turtlename': 'turtle2'}
         ]
      ),
      Node(
         package='learning_tf2_cpp',
         executable='turtle_tf2_listener_debug',
         name='listener_debug',
         parameters=[
               {'target_frame': LaunchConfiguration('target_frame')}
         ]
      ),
   ])

또한 turtle_tf2_listener_debug 실행 파일을 CMakeLists.txt 에 추가하고 패키지를 빌드해야 합니다.

이제 실행하여 결과를 확인해 보겠습니다.

ros2 launch learning_tf2_cpp start_tf2_debug_demo.launch.py

이제 turtlesim이 실행됩니다. 동시에 다른 터미널 창에서 turtle_teleop_key 를 실행하면 화살표 키를 사용하여 turtle1 을 제어할 수 있습니다.

ros2 run turtlesim turtle_teleop_key

또한 왼쪽 하단에 두 번째 거북이가 있는 것을 알 수 있습니다. 데모가 올바르게 작동하면 두 번째 거북이는 화살표 키로 제어할 수 있는 거북이를 따라야 합니다. 그러나 현재 그렇지 않습니다. 몇 가지 문제를 해결해야 합니다. 다음과 같은 메시지를 확인해야 합니다.

[turtle_tf2_listener_debug-4] [INFO] [1630223454.942322623] [listener_debug]: Could not
transform turtle3 to turtle1: "turtle3" passed to lookupTransform argument target_frame
does not exist

2 tf2 요청 찾기

우선 정확히 어떤 것을 tf2에게 요청하는지 알아야 합니다. 따라서 tf2를 사용하는 코드 부분으로 이동합니다. src/turtle_tf2_listener_debug.cpp 파일을 열고 67번째 줄을 확인합니다.

std::string to_frame_rel = "turtle3";

그리고 75-79번째 줄을 확인합니다.

try {
   transformStamped = tf_buffer_->lookupTransform(
     toFrameRel,
     fromFrameRel,
     this->now());
} catch (tf2::TransformException & ex) {

여기서 tf2에게 실제 요청을 보내는 것입니다. 세 가지 인수는 우리가 tf2에게 무엇을 요청하고 있는지 직접 알려줍니다. 프레임 turtle3 에서 프레임 turtle1 로 시간 now 의 변환을 요청하고 있습니다.

이제 이 요청이 실패하는 이유를 살펴보겠습니다.

3 프레임 확인

먼저 tf2가 turtle3turtle1 간의 변환을 인식하는지 확인하려면 tf2_echo 도구를 사용합니다.

ros2 run tf2_ros tf2_echo turtle3 turtle1

결과에서 frame turtle3 이 존재하지 않음을 알 수 있습니다.

[INFO] [1630223557.477636052] [tf2_echo]: Waiting for transform turtle3 ->  turtle1:
Invalid frame ID "turtle3" passed to canTransform argument target_frame - frame does
not exist

그럼 어떤 프레임이 존재할까요? 이를 시각적으로 확인하려면 view_frames 도구를 사용합니다.

ros2 run tf2_tools view_frames

생성된 frames.pdf 파일을 열어 다음 출력을 확인합니다.

../../../_images/turtlesim_frames.png

따라서 문제가 바로 turtle3 프레임을 요청하고 있기 때문임을 알 수 있습니다. 이 버그를 수정하려면 67번째 줄에서 turtle3turtle2 로 바꾸면 됩니다.

이제 실행을 중지하고 데모를 빌드한 다음 다시 실행합니다.

ros2 launch turtle_tf2 start_debug_demo.launch.py

그런 다음 다음 문제에 직면합니다.

[turtle_tf2_listener_debug-4] [INFO] [1630223704.617382464] [listener_debug]: Could not
transform turtle2 to turtle1: Lookup would require extrapolation into the future. Requested
time 1630223704.617054 but the latest data is at time 1630223704.616726, when looking up
transform from frame [turtle1] to frame [turtle2]

4 타임스탬프 확인

이제 프레임 이름 문제를 해결했으므로 타임스탬프를 살펴봅니다. 기억하세요. 우리는 현재 시간(즉, now)에 대한 turtle2turtle1 간의 변환을 얻으려고 시도하고 있습니다. 타이밍에 대한 통계를 얻으려면 해당 프레임으로 tf2_monitor 를 호출하십시오.

ros2 run tf2_ros tf2_monitor turtle2 turtle1

결과는 다음과 같아야 합니다.

RESULTS: for turtle2 to turtle1
Chain is: turtle1
Net delay     avg = 0.00287347: max = 0.0167241

Frames:
Frame: turtle1, published by <no authority available>, Average Delay: 0.000295833, Max Delay: 0.000755072

All Broadcasters:
Node: <no authority available> 125.246 Hz, Average Delay: 0.000290237 Max Delay: 0.000786781

여기서 중요한 부분은 turtle2 에서 turtle1 로의 체인의 지연입니다. 결과에서 평균적으로 약 3 밀리초의 지연이 있는 것으로 나타납니다. 이것은 tf2가 3 밀리초 후에만 변환을 수행할 수 있음을 의미합니다. 따라서 now 대신에 변환을 하는 3 밀리초 전의 타임스탬프를 사용하면 가끔 답을 얻을 수 있습니다. 이를 빠르게 테스트하려면 75-79번째 줄을 다음과 같이 변경하십시오.

try {
   transformStamped = tf_buffer_->lookupTransform(
     toFrameRel,
     fromFrameRel,
     this->now() - rclcpp::Duration::from_seconds(0.1));
} catch (tf2::TransformException & ex) {

새 코드에서는 거북이 사이의 변환을 100 밀리초 전으로 요청하고 있습니다. 일반적으로 변환이 도착할 것이 확실하도록 더 긴 기간을 사용하는 것이 좋습니다. 데모를 중지하고 빌드한 다음 실행합니다.

ros2 launch turtle_tf2 start_debug_demo.launch.py

이제 거북이가 움직이는 것을 볼 수 있습니다!

../../../_images/turtlesim_follow1.png

마지막으로 우리가 한 마지막 수정은 실제로 원하는 것이 아니므로 참고용으로만 사용해야 합니다. 문제가 무엇인지 확인하기 위한 것이었습니다. 실제 수정은 다음과 같아야 합니다.

try {
   transformStamped = tf_buffer_->lookupTransform(
     toFrameRel,
     fromFrameRel,
     tf2::TimePointZero);
} catch (tf2::TransformException & ex) {

또는 다음과 같이 사용할 수 있습니다.

try {
   transformStamped = tf_buffer_->lookupTransform(
     toFrameRel,
     fromFrameRel,
     tf2::TimePoint());
} catch (tf2::TransformException & ex) {

타임아웃에 대한 자세한 내용은 시간 사용 튜토리얼에서 확인하고 다음과 같이 사용할 수 있습니다.

try {
   transformStamped = tf_buffer_->lookupTransform(
     toFrameRel,
     fromFrameRel,
     this->now(),
     rclcpp::Duration::from_seconds(0.05));
} catch (tf2::TransformException & ex) {

요약

이 튜토리얼에서는 tf2 관련 문제를 디버깅하기 위한 체계적인 접근 방법을 사용하는 방법을 배웠습니다. 또한 tf2 디버깅 도구인 tf2_echo, tf2_monitor, view_frames 등을 사용하여 tf2 문제를 디버깅하는 데 도움이 되는 방법을 배웠습니다.