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

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 :

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

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 !

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

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) {

Résumé

Dans ce didacticiel, vous avez appris à utiliser une approche systématique pour déboguer les problèmes liés à tf2. Vous avez également appris à utiliser les outils de débogage tf2, tels que tf2_echo, tf2_monitor et view_frames pour vous aider à déboguer ces problèmes tf2.