Débogage
Objectif : Apprendre à utiliser une approche systématique pour déboguer les problèmes liés à tf2.
Niveau du didacticiel : Intermédiaire
Durée : 10 minutes
Contenu
Arrière-plan
Ce didacticiel vous guide à travers les étapes de débogage d’un problème typique de tf2. Il utilisera également de nombreux outils de débogage tf2, tels que tf2_echo
, tf2_monitor
et view_frames
. Ce didacticiel suppose que vous avez terminé les didacticiels learning tf2.
Exemple de débogage
1 Réglage et démarrage de l’exemple
Pour ce didacticiel, nous allons configurer une application de démonstration qui présente un certain nombre de problèmes. L’objectif de ce didacticiel est d’appliquer une approche systématique pour trouver et résoudre ces problèmes. Commençons par créer le fichier source.
Accédez au package learning_tf2_cpp
que nous avons créé dans tf2 tutorials. Dans le répertoire src
, faites une copie du fichier source turtle_tf2_listener.cpp
et renommez-le en turtle_tf2_listener_debug.cpp
.
Ouvrez le fichier à l’aide de votre éditeur de texte préféré et modifiez la ligne 67 de
std::string toFrameRel = "turtle2";
pour
std::string toFrameRel = "turtle3";
et changez l’appel lookupTransform()
dans les lignes 75-79 de
try {
transformStamped = tf_buffer_->lookupTransform(
toFrameRel,
fromFrameRel,
tf2::TimePointZero);
} catch (tf2::TransformException & ex) {
pour
try {
transformStamped = tf_buffer_->lookupTransform(
toFrameRel,
fromFrameRel,
this->now());
} catch (tf2::TransformException & ex) {
Et enregistrez les modifications apportées au fichier. Pour exécuter cette démo, nous devons créer un fichier de lancement start_tf2_debug_demo.launch.py
dans le sous-répertoire launch
du package learning_tf2_cpp
:
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')}
]
),
])
N’oubliez pas d’ajouter l’exécutable turtle_tf2_listener_debug
au CMakeLists.txt
et de compiler le paquet.
Maintenant, exécutons-le pour voir ce qui se passe :
ros2 launch learning_tf2_cpp start_tf2_debug_demo.launch.py
Vous verrez maintenant que la simulation de tortue est apparue. En même temps, si vous lancez la turtle_teleop_key
dans une autre fenêtre de terminal, vous pouvez utiliser les touches fléchées pour conduire la turtle1
.
ros2 run turtlesim turtle_teleop_key
Vous remarquerez également qu’il y a une deuxième tortue dans le coin inférieur gauche. Si la démo fonctionne correctement, cette deuxième tortue devrait suivre la tortue que vous pouvez commander avec les touches fléchées. Cependant, ce n’est pas le cas car nous devons d’abord résoudre certains problèmes. Vous devriez remarquer le message suivant :
[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 Trouver la requête TF2
Tout d’abord, nous devons savoir exactement ce que nous demandons à TF2 de faire. Par conséquent, nous entrons dans la partie du code qui utilise tf2. Ouvrez le fichier src/turtle_tf2_listener_debug.cpp
et regardez la ligne 67 :
std::string to_frame_rel = "turtle3";
et lignes 75-79 :
try {
transformStamped = tf_buffer_->lookupTransform(
toFrameRel,
fromFrameRel,
this->now());
} catch (tf2::TransformException & ex) {
Ici, nous faisons la demande réelle à TF2. Les trois arguments nous disent directement ce que nous demandons à tf2 : transformer du cadre turtle3
au cadre turtle1
au temps now
.
Voyons maintenant pourquoi cette requête à tf2 échoue.
3 Vérification des cadres
Tout d’abord, pour savoir si tf2 connaît notre transformation entre turtle3
et turtle1
, nous allons utiliser l’outil tf2_echo
.
ros2 run tf2_ros tf2_echo turtle3 turtle1
La sortie nous indique que le cadre turtle3
n’existe pas :
[INFO] [1630223557.477636052] [tf2_echo]: Waiting for transform turtle3 -> turtle1:
Invalid frame ID "turtle3" passed to canTransform argument target_frame - frame does
not exist
Alors quels cadres existent ? Si vous souhaitez obtenir une représentation graphique de cela, utilisez l’outil view_frames
.
ros2 run tf2_tools view_frames
Ouvrez le fichier frames.pdf
généré pour voir la sortie suivante :

Donc, évidemment, le problème est que nous demandons une transformation à partir du cadre turtle3
, qui n’existe pas. Pour corriger ce bogue, remplacez simplement turtle3
par turtle2
à la ligne 67.
Et maintenant, arrêtez la démo en cours d’exécution, créez-la et exécutez-la à nouveau :
ros2 launch turtle_tf2 start_debug_demo.launch.py
Et tout de suite nous rencontrons le problème suivant :
[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 Vérification de l’horodatage
Maintenant que nous avons résolu le problème du nom de trame, il est temps de regarder les horodatages. Rappelez-vous, nous essayons d’obtenir la transformation entre turtle2
et turtle1
à l’heure actuelle (c’est-à-dire, now
). Pour obtenir des statistiques sur le timing, appelez tf2_monitor
avec les images correspondantes.
ros2 run tf2_ros tf2_monitor turtle2 turtle1
Le résultat devrait ressembler à ceci :
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
La partie clé ici est le délai pour la chaîne de turtle2
à turtle1
. La sortie montre qu’il y a un retard moyen d’environ 3 millisecondes. Cela signifie que tf2 ne peut se transformer entre les tortues qu’après 3 millisecondes. Donc, si nous demandions à tf2 la transformation entre les tortues il y a 3 millisecondes au lieu de maintenant
, tf2 pourrait parfois nous donner une réponse. Testons cela rapidement en changeant les lignes 75-79 en :
try {
transformStamped = tf_buffer_->lookupTransform(
toFrameRel,
fromFrameRel,
this->now() - rclcpp::Duration::from_seconds(0.1));
} catch (tf2::TransformException & ex) {
Dans le nouveau code, nous demandons la transformation entre les tortues il y a 100 millisecondes. Il est habituel d’utiliser des périodes plus longues, juste pour s’assurer que la transformation arrivera. Arrêtez la démo, compilez et exécutez :
ros2 launch turtle_tf2 start_debug_demo.launch.py
Et vous devriez enfin voir la tortue bouger !

Ce dernier correctif que nous avons apporté n’est pas vraiment ce que vous voulez faire, c’était juste pour nous assurer que c’était notre problème. Le vrai correctif ressemblerait à ceci:
try {
transformStamped = tf_buffer_->lookupTransform(
toFrameRel,
fromFrameRel,
tf2::TimePointZero);
} catch (tf2::TransformException & ex) {
Ou comme ceci :
try {
transformStamped = tf_buffer_->lookupTransform(
toFrameRel,
fromFrameRel,
tf2::TimePoint());
} catch (tf2::TransformException & ex) {
Vous pouvez en savoir plus sur les délais d’attente dans le didacticiel Using time et les utiliser comme ci-dessous :
try {
transformStamped = tf_buffer_->lookupTransform(
toFrameRel,
fromFrameRel,
this->now(),
rclcpp::Duration::from_seconds(0.05));
} catch (tf2::TransformException & ex) {