Gestion de grands projets

Objectif : Apprendre les meilleures pratiques de gestion de grands projets à l’aide des fichiers de lancement ROS 2.

Niveau du didacticiel : Intermédiaire

Durée : 20 minutes

Arrière-plan

Ce didacticiel décrit quelques conseils pour écrire des fichiers de lancement pour des projets volumineux. L’accent est mis sur la façon de structurer les fichiers de lancement afin qu’ils puissent être réutilisés autant que possible dans différentes situations. En outre, il couvre des exemples d’utilisation de différents outils de lancement ROS 2, tels que les paramètres, les fichiers YAML, les remappages, les espaces de noms, les arguments par défaut et les configurations RViz.

Conditions préalables

Ce tutoriel utilise turtlesim et turtle_tf2_py paquets. Ce didacticiel suppose également que vous avez créé un nouveau package de type de construction ament_python appelé ``launch_tutorial ``.

Introduction

Les grandes applications sur un robot impliquent généralement plusieurs nœuds interconnectés, chacun pouvant avoir de nombreux paramètres. La simulation de plusieurs tortues dans le simulateur de tortues peut servir de bon exemple. La simulation de tortue se compose de plusieurs nœuds de tortue, de la configuration mondiale et des nœuds de diffusion et d’écoute TF. Entre tous les nœuds, il existe un grand nombre de paramètres ROS qui affectent le comportement et l’apparence de ces nœuds. Les fichiers de lancement ROS 2 nous permettent de démarrer tous les nœuds et de définir les paramètres correspondants en un seul endroit. À la fin d’un didacticiel, vous construirez le fichier de lancement launch_turtlesim.launch.py dans le package launch_tutorial. Ce fichier de lancement fera apparaître différents nœuds responsables de la simulation de deux simulations turtlesim, du démarrage des diffuseurs et de l’écouteur TF, du chargement des paramètres et du lancement d’une configuration RViz. Dans ce didacticiel, nous allons passer en revue ce fichier de lancement et toutes les fonctionnalités associées utilisées.

Ecriture des fichiers de lancement

1 Organisation au plus haut niveau

L’un des objectifs du processus d’écriture des fichiers de lancement devrait être de les rendre aussi réutilisables que possible. Cela peut être fait en regroupant les nœuds et les configurations associés dans des fichiers de lancement distincts. Ensuite, un fichier de lancement de niveau supérieur dédié à une configuration spécifique pourrait être écrit. Cela permettrait de se déplacer entre des robots identiques sans modifier du tout les fichiers de lancement. Même un changement tel que le passage d’un robot réel à un robot simulé peut être effectué avec seulement quelques changements.

Nous allons maintenant passer en revue la structure de fichier de lancement de niveau supérieur qui rend cela possible. Tout d’abord, nous allons créer un fichier de lancement qui appellera des fichiers de lancement séparés. Pour ce faire, créons un fichier launch_turtlesim.launch.py dans le dossier /launch de notre package launch_tutorial.

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource


def generate_launch_description():
   turtlesim_world_1 = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_world_1.launch.py'])
      )
   turtlesim_world_2 = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_world_2.launch.py'])
      )
   broadcaster_listener_nodes = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/broadcaster_listener.launch.py']),
      launch_arguments={'target_frame': 'carrot1'}.items(),
      )
   mimic_node = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/mimic.launch.py'])
      )
   fixed_frame_node = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/fixed_broadcaster.launch.py'])
      )
   rviz_node = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_rviz.launch.py'])
      )

   return LaunchDescription([
      turtlesim_world_1,
      turtlesim_world_2,
      broadcaster_listener_nodes,
      mimic_node,
      fixed_frame_node,
      rviz_node
   ])

Ce fichier de lancement inclut un ensemble d’autres fichiers de lancement. Chacun de ces fichiers de lancement inclus contient des nœuds, des paramètres et éventuellement des inclusions imbriquées, qui se rapportent à une partie du système. Pour être exact, nous lançons deux mondes de simulation turtlesim, un diffuseur TF, un écouteur TF, un synoptique, un diffuseur à cadre fixe et des nœuds RViz.

Note

Conseil de conception : les fichiers de lancement de niveau supérieur doivent être courts, se composer d’inclusions dans d’autres fichiers correspondant aux sous-composants de l’application et des paramètres couramment modifiés.

L’écriture des fichiers de lancement de la manière suivante facilite l’échange d’un élément du système, comme nous le verrons plus tard. Cependant, il existe des cas où certains nœuds ou fichiers de lancement doivent être lancés séparément pour des raisons de performances et d’utilisation.

Note

Conseil de conception : Soyez conscient des compromis lorsque vous décidez du nombre de fichiers de lancement de niveau supérieur dont votre application a besoin.

2 paramètres

2.1 Réglage des paramètres dans le fichier de lancement

Nous allons commencer par écrire un fichier de lancement qui lancera notre première simulation turtlesim. Tout d’abord, créez un nouveau fichier appelé turtlesim_world_1.launch.py.

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

from launch_ros.actions import Node


def generate_launch_description():
   background_r_launch_arg = DeclareLaunchArgument(
      'background_r', default_value=TextSubstitution(text='0')
   )
   background_g_launch_arg = DeclareLaunchArgument(
      'background_g', default_value=TextSubstitution(text='84')
   )
   background_b_launch_arg = DeclareLaunchArgument(
      'background_b', default_value=TextSubstitution(text='122')
   )

   return LaunchDescription([
      background_r_launch_arg,
      background_g_launch_arg,
      background_b_launch_arg,
      Node(
         package='turtlesim',
         executable='turtlesim_node',
         name='sim',
         parameters=[{
            'background_r': LaunchConfiguration('background_r'),
            'background_g': LaunchConfiguration('background_g'),
            'background_b': LaunchConfiguration('background_b'),
         }]
      ),
   ])

Ce fichier de lancement démarre le nœud turtlesim_node, qui démarre la simulation turtlesim, avec les paramètres de configuration de la simulation qui sont définis et transmis aux nœuds.

2.2 Chargement des paramètres depuis le fichier YAML

Lors du second lancement, nous lancerons une seconde simulation turtlesim avec une configuration différente. Créez maintenant un fichier turtlesim_world_2.launch.py.

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
   config = os.path.join(
      get_package_share_directory('launch_tutorial'),
      'config',
      'turtlesim.yaml'
      )

   return LaunchDescription([
      Node(
         package='turtlesim',
         executable='turtlesim_node',
         namespace='turtlesim2',
         name='sim',
         parameters=[config]
      )
   ])

Ce fichier de lancement lancera le même turtlesim_node avec des valeurs de paramètres chargées directement depuis le fichier de configuration YAML. La définition d’arguments et de paramètres dans les fichiers YAML facilite le stockage et le chargement d’un grand nombre de variables. De plus, les fichiers YAML peuvent être facilement exportés à partir de la liste actuelle ros2 param. Pour savoir comment procéder, reportez-vous au didacticiel Understand parameters.

Créons maintenant un fichier de configuration, turtlesim.yaml, dans le dossier /config de notre package, qui sera chargé par notre fichier de lancement.

/turtlesim2/sim:
   ros__parameters:
      background_b: 255
      background_g: 86
      background_r: 150

Si nous démarrons maintenant le fichier de lancement turtlesim_world_2.launch.py, nous lancerons le turtlesim_node avec des couleurs d’arrière-plan préconfigurées.

Pour en savoir plus sur l’utilisation des paramètres et l’utilisation des fichiers YAML, consultez le didacticiel Understand parameters.

2.3 Utilisation de caractères génériques dans les fichiers YAML

Il y a des cas où nous voulons définir les mêmes paramètres dans plusieurs nœuds. Ces nœuds peuvent avoir des espaces de noms ou des noms différents mais avoir toujours les mêmes paramètres. La définition de fichiers YAML distincts qui définissent explicitement les espaces de noms et les noms de nœuds n’est pas efficace. Une solution consiste à utiliser des caractères génériques, qui remplacent les caractères inconnus dans une valeur de texte, pour appliquer des paramètres à plusieurs nœuds différents.

Créons maintenant un nouveau fichier turtlesim_world_3.launch.py similaire à turtlesim_world_2.launch.py pour inclure un autre nœud turtlesim_node.

...
Node(
   package='turtlesim',
   executable='turtlesim_node',
   namespace='turtlesim3',
   name='sim',
   parameters=[config]
)

Cependant, le chargement du même fichier YAML n’affectera pas l’apparence du troisième monde turtlesim. La raison en est que ses paramètres sont stockés sous un autre espace de noms, comme indiqué ci-dessous :

/turtlesim3/sim:
   background_b
   background_g
   background_r

Par conséquent, au lieu de créer une nouvelle configuration pour le même nœud qui utilise les mêmes paramètres, nous pouvons utiliser la syntaxe des caractères génériques. /** attribuera tous les paramètres à chaque nœud, malgré les différences de noms de nœuds et d’espaces de noms.

Nous allons maintenant mettre à jour le turtlesim.yaml, dans le dossier /config de la manière suivante :

/**:
   ros__parameters:
      background_b: 255
      background_g: 86
      background_r: 150

Incluez maintenant la description de lancement turtlesim_world_3.launch.py dans notre fichier de lancement principal. L’utilisation de ce fichier de configuration dans nos descriptions de lancement affectera les paramètres background_b, background_g et background_r aux valeurs spécifiées dans les nœuds turtlesim3/sim et turtlesim2/sim.

3 espaces de noms

Comme vous l’avez peut-être remarqué, nous avons défini l’espace de noms pour le monde turlesim dans le fichier turtlesim_world_2.launch.py. Les espaces de noms uniques permettent au système de démarrer deux nœuds similaires sans conflit de nom de nœud ou de nom de rubrique.

namespace='turtlesim2',

Cependant, si le fichier de lancement contient un grand nombre de nœuds, définir des espaces de noms pour chacun d’eux peut devenir fastidieux. Pour résoudre ce problème, l’action PushROSNamespace peut être utilisée pour définir l’espace de noms global pour chaque description de fichier de lancement. Chaque nœud imbriqué héritera automatiquement de cet espace de noms.

Pour ce faire, nous devons d’abord supprimer la ligne namespace='turtlesim2' du fichier turtlesim_world_2.launch.py. Ensuite, nous devons mettre à jour le launch_turtlesim.launch.py pour inclure les lignes suivantes :

from launch.actions import GroupAction
from launch_ros.actions import PushROSNamespace

   ...
   turtlesim_world_2 = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_world_2.launch.py'])
      )
   turtlesim_world_2_with_namespace = GroupAction(
     actions=[
         PushROSNamespace('turtlesim2'),
         turtlesim_world_2,
      ]
   )

Enfin, nous remplaçons turtlesim_world_2 par turtlesim_world_2_with_namespace dans l’instruction return LaunchDescription. Par conséquent, chaque nœud dans la description de lancement turtlesim_world_2.launch.py aura un espace de noms turtlesim2.

4 Réutiliser les nœuds

Créez maintenant un fichier broadcaster_listener.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='turtle_tf2_py',
         executable='turtle_tf2_broadcaster',
         name='broadcaster1',
         parameters=[
            {'turtlename': 'turtle1'}
         ]
      ),
      Node(
         package='turtle_tf2_py',
         executable='turtle_tf2_broadcaster',
         name='broadcaster2',
         parameters=[
            {'turtlename': 'turtle2'}
         ]
      ),
      Node(
         package='turtle_tf2_py',
         executable='turtle_tf2_listener',
         name='listener',
         parameters=[
            {'target_frame': LaunchConfiguration('target_frame')}
         ]
      ),
   ])

Dans ce fichier, nous avons déclaré l’argument de lancement target_frame avec une valeur par défaut de turtle1. La valeur par défaut signifie que le fichier de lancement peut recevoir un argument à transmettre à ses nœuds, ou si l’argument n’est pas fourni, il transmettra la valeur par défaut à ses nœuds.

Ensuite, nous utilisons le nœud turtle_tf2_broadcaster deux fois en utilisant des noms et des paramètres différents lors du lancement. Cela nous permet de dupliquer le même nœud sans conflits.

Nous démarrons également un nœud turtle_tf2_listener et définissons son paramètre target_frame que nous avons déclaré et acquis ci-dessus.

5 Remplacements de paramètres

Rappelez-vous que nous avons appelé le fichier broadcaster_listener.launch.py dans notre fichier de lancement de niveau supérieur. En plus de cela, nous lui avons passé l’argument de lancement target_frame comme indiqué ci-dessous :

broadcaster_listener_nodes = IncludeLaunchDescription(
   PythonLaunchDescriptionSource([os.path.join(
      get_package_share_directory('launch_tutorial'), 'launch'),
      '/broadcaster_listener.launch.py']),
   launch_arguments={'target_frame': 'carrot1'}.items(),
   )

Cette syntaxe nous permet de changer le cadre cible de l’objectif par défaut en carrot1. Si vous souhaitez que turtle2 suive turtle1 au lieu de carrot1, supprimez simplement la ligne qui définit launch_arguments. Cela affectera à target_frame sa valeur par défaut, qui est turtle1.

6 Remappage

Créez maintenant un fichier mimic.launch.py.

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
   return LaunchDescription([
      Node(
         package='turtlesim',
         executable='mimic',
         name='mimic',
         remappings=[
            ('/input/pose', '/turtle2/pose'),
            ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
         ]
      )
   ])

Ce fichier de lancement démarrera le nœud mimic, qui donnera des commandes à une tortue pour qu’elle suive l’autre. Le nœud est conçu pour recevoir la pose cible sur le sujet /input/pose. Dans notre cas, nous voulons remapper la pose cible du sujet /turtle2/pose. Enfin, nous remappons le sujet /output/cmd_vel à /turtlesim2/turtle1/cmd_vel. De cette façon, turtle1 dans notre monde de simulation turtlesim2 suivra turtle2 dans notre monde initial de simulation de tortues.

7 fichiers de configuration

Créons maintenant un fichier appelé turtlesim_rviz.launch.py.

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
   rviz_config = os.path.join(
      get_package_share_directory('turtle_tf2_py'),
      'rviz',
      'turtle_rviz.rviz'
      )

   return LaunchDescription([
      Node(
         package='rviz2',
         executable='rviz2',
         name='rviz2',
         arguments=['-d', rviz_config]
      )
   ])

Ce fichier de lancement démarrera le RViz avec le fichier de configuration défini dans le package turtle_tf2_py. Cette configuration RViz définira le cadre mondial, activera la visualisation TF et démarrera RViz avec une vue descendante.

8 variables d’environnement

Créons maintenant le dernier fichier de lancement appelé fixed_broadcaster.launch.py dans notre package.

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import EnvironmentVariable, LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
   return LaunchDescription([
      DeclareLaunchArgument(
            'node_prefix',
            default_value=[EnvironmentVariable('USER'), '_'],
            description='prefix for node name'
      ),
      Node(
            package='turtle_tf2_py',
            executable='fixed_frame_tf2_broadcaster',
            name=[LaunchConfiguration('node_prefix'), 'fixed_broadcaster'],
      ),
   ])

Ce fichier de lancement montre comment les variables d’environnement peuvent être appelées dans les fichiers de lancement. Les variables d’environnement peuvent être utilisées pour définir ou pousser des espaces de noms pour distinguer les nœuds sur différents ordinateurs ou robots.

Exécution des fichiers de lancement

1 Mettre à jour setup.py

Ouvrez setup.py et ajoutez les lignes suivantes afin que les fichiers de lancement du dossier launch/ et le fichier de configuration de config/ soient installés. Le champ data_files devrait maintenant ressembler à ceci :

data_files=[
      ...
      (os.path.join('share', package_name, 'launch'),
         glob(os.path.join('launch', '*.launch.py'))),
      (os.path.join('share', package_name, 'config'),
         glob(os.path.join('config', '*.yaml'))),
   ],

2 Construire et exécuter

Pour enfin voir le résultat de notre code, construisez le package et lancez le fichier de lancement de niveau supérieur à l’aide de la commande suivante :

ros2 launch launch_tutorial launch_turtlesim.launch.py

Vous verrez maintenant les deux simulations de turtlesim démarrer. Il y a deux tortues dans le premier et une dans le second. Dans la première simulation, turtle2 apparaît dans la partie inférieure gauche du monde. Son but est d’atteindre le repère carrot1 qui est à cinq mètres de distance sur l’axe des abscisses par rapport au repère turtle1.

Le turtlesim2/turtle1 dans le second est conçu pour imiter le comportement du turtle2.

Si vous voulez contrôler la turtle1, lancez le nœud teleop.

ros2 run turtlesim turtle_teleop_key

En conséquence, vous verrez une image similaire :

../../../_images/turtlesim_worlds.png

En plus de cela, le RViz aurait dû démarrer. Il affichera tous les cadres de tortues par rapport au cadre world, dont l’origine se trouve dans le coin inférieur gauche.

../../../_images/turtlesim_rviz.png

Résumé

Dans ce didacticiel, vous avez découvert divers conseils et pratiques de gestion de projets volumineux à l’aide de fichiers de lancement ROS 2.