ITSkillsCenter
Intelligence Artificielle

Simuler un bras robotisé avec ROS 2 : URDF, RViz et commande

14 دقائق للقراءة
📍 Guide principal de la série : Robotique industrielle open source : ROS 2, Modbus, OPC-UA et automates. Ce tutoriel suppose que ROS 2 est déjà installé — voir le premier tutoriel de la série si ce n’est pas le cas.

Un robot reste abstrait tant qu’on ne le voit pas bouger. Dans ce tutoriel, vous allez donner un corps à un bras robotisé : le décrire mathématiquement, l’afficher en trois dimensions, articuler ses membres avec un curseur, puis le commander par programme. Tout cela en simulation, sans le moindre moteur réel — la façon la plus sûre et la moins coûteuse de concevoir et de tester un robot avant de le fabriquer.

🎯 Ce que vous allez apprendre

  • Décrire un robot articulé dans le format URDF, le langage standard de ROS 2 pour la géométrie des robots.
  • Publier cette description et l’arborescence des repères avec robot_state_publisher.
  • Visualiser le robot et le faire bouger interactivement dans RViz.
  • Installer correctement les fichiers d’un paquet (description, lancement) via colcon.
  • Commander les articulations par un programme plutôt qu’à la main.

🛠️ Ce que vous allez construire

Un bras à deux segments : une base fixe, un bras qui pivote dessus, et un avant-bras articulé au bout du bras. Vous le verrez d’abord apparaître dans RViz, puis vous bougerez ses deux articulations avec une petite fenêtre à curseurs. Enfin, vous écrirez un programme qui anime l’articulation toute seule, en boucle, comme le ferait un séquenceur de mouvement. C’est le squelette de n’importe quel robot manipulateur.

Prérequis

  • ROS 2 Jazzy installé et fonctionnel sur Ubuntu 24.04, avec un espace de travail colcon. Test express : si la démonstration talker/listener du premier tutoriel fonctionne chez vous, vous êtes prêt.
  • Deux paquets supplémentaires que nous installons à l’étape 1.
  • ⏱️ Temps estimé : 40 à 50 minutes.

Étape 1 — Installer les outils de visualisation

Deux paquets ne font pas partie de l’installation de base mais nous seront indispensables : l’interface à curseurs qui pilote les articulations, et l’utilitaire qui transforme les descriptions de robot. On les installe depuis les dépôts ROS 2.

sudo apt install ros-jazzy-joint-state-publisher-gui ros-jazzy-xacro

Le premier, joint_state_publisher_gui, ouvre une fenêtre avec un curseur par articulation : c’est notre télécommande manuelle. Le second, xacro, n’est pas strictement requis ici mais accompagne presque toujours l’URDF dans les projets réels. Une fois l’installation terminée sans erreur, on peut décrire le robot.

Étape 2 — Créer le paquet de description

On range la description du robot dans son propre paquet, comme le veut la bonne pratique ROS 2. Depuis le dossier src de votre espace de travail :

cd ~/ros2_cellule_ws/src
ros2 pkg create --build-type ament_python --license Apache-2.0 bras_description
cd bras_description
mkdir urdf launch

On vient de créer le paquet bras_description et deux dossiers : urdf pour la géométrie du robot, launch pour le fichier qui démarrera tout en une commande. Cette organisation — un paquet par responsabilité — est ce qui rend un projet ROS 2 maintenable quand il grossit.

Étape 3 — Décrire le bras en URDF

L’URDF (Unified Robot Description Format) est un fichier XML qui décrit un robot comme un assemblage de links (les segments rigides) reliés par des joints (les articulations). Chaque link a une forme visuelle ; chaque joint dit comment deux links sont reliés et comment ils bougent l’un par rapport à l’autre. Créez le fichier urdf/bras.urdf :

<?xml version="1.0"?>
<robot name="bras">

  <!-- Base fixe, posée au sol -->
  <link name="base">
    <visual>
      <geometry><cylinder length="0.1" radius="0.15"/></geometry>
      <material name="gris"><color rgba="0.5 0.5 0.5 1"/></material>
    </visual>
  </link>

  <!-- Premier segment : le bras -->
  <link name="bras">
    <visual>
      <origin xyz="0 0 0.3"/>
      <geometry><box size="0.08 0.08 0.6"/></geometry>
      <material name="bleu"><color rgba="0.2 0.4 0.9 1"/></material>
    </visual>
  </link>

  <!-- Second segment : l'avant-bras -->
  <link name="avant_bras">
    <visual>
      <origin xyz="0 0 0.25"/>
      <geometry><box size="0.06 0.06 0.5"/></geometry>
      <material name="orange"><color rgba="0.95 0.5 0.1 1"/></material>
    </visual>
  </link>

  <!-- Articulation 1 : le bras pivote sur la base -->
  <joint name="epaule" type="revolute">
    <parent link="base"/>
    <child link="bras"/>
    <origin xyz="0 0 0.05"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="10" velocity="1.0"/>
  </joint>

  <!-- Articulation 2 : l'avant-bras au bout du bras -->
  <joint name="coude" type="revolute">
    <parent link="bras"/>
    <child link="avant_bras"/>
    <origin xyz="0 0 0.6"/>
    <axis xyz="0 1 0"/>
    <limit lower="-2.0" upper="2.0" effort="10" velocity="1.0"/>
  </joint>

</robot>

Lisons cette description. Trois links : la base (un cylindre), le bras et l’avant-bras (deux pavés). Deux joints de type revolute (rotation autour d’un axe, avec butées) : epaule relie la base au bras, coude relie le bras à l’avant-bras. L’attribut origin d’un joint positionne l’enfant par rapport au parent ; axis donne l’axe de rotation ; limit fixe les butées angulaires en radians. Notez que l’origin du coude est à z=0.6, soit la longueur du bras : l’avant-bras s’attache donc bien au bout du bras, pas à sa base.

Étape 4 — Écrire le fichier de lancement

Plutôt que de démarrer trois programmes à la main dans trois terminaux, on écrit un fichier de lancement qui les orchestre. Créez launch/affichage.launch.py :

import os
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory

def generate_launch_description():
    chemin = get_package_share_directory('bras_description')
    fichier_urdf = os.path.join(chemin, 'urdf', 'bras.urdf')
    with open(fichier_urdf, 'r') as f:
        description = f.read()

    return LaunchDescription([
        Node(package='robot_state_publisher', executable='robot_state_publisher',
             parameters=[{'robot_description': description}]),
        Node(package='joint_state_publisher_gui', executable='joint_state_publisher_gui'),
        Node(package='rviz2', executable='rviz2'),
    ])

Ce fichier lit l’URDF installé, puis démarre trois nœuds. robot_state_publisher reçoit la description du robot et publie en continu l’arborescence des repères (la position de chaque link). joint_state_publisher_gui ouvre la fenêtre à curseurs qui fixe l’angle de chaque articulation. rviz2 est l’outil de visualisation. Le point clé est le paramètre robot_description : c’est par lui que robot_state_publisher connaît la forme du robot.

Étape 5 — Déclarer les fichiers à installer, puis compiler

colcon n’installe pas automatiquement les fichiers URDF et de lancement : il faut les déclarer dans setup.py. Ouvrez ce fichier et complétez la liste data_files pour qu’elle ressemble à ceci :

import os
from glob import glob
from setuptools import setup

package_name = 'bras_description'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages', ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'urdf'), glob('urdf/*.urdf')),
        (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    entry_points={'console_scripts': []},
)

Les deux lignes ajoutées copient, à la compilation, le contenu des dossiers urdf et launch vers l’emplacement partagé où ROS 2 va les chercher. C’est exactement ce mécanisme qui permettait au fichier de lancement de retrouver l’URDF avec get_package_share_directory. Compilez et sourcez :

cd ~/ros2_cellule_ws
colcon build
source install/setup.bash

La compilation doit se terminer sans erreur. Si elle échoue en se plaignant d’un fichier introuvable, vérifiez que les dossiers urdf et launch existent bien et contiennent vos fichiers.

Étape 6 — Lancer et visualiser le robot

Tout est prêt pour l’apparition du robot. Une seule commande démarre les trois nœuds :

ros2 launch bras_description affichage.launch.py

RViz s’ouvre, mais probablement vide au départ : il faut lui dire quoi afficher. Dans le panneau de gauche, réglez le champ Fixed Frame sur base. Puis cliquez sur Add, choisissez RobotModel, et dans ses propriétés réglez Description Topic sur /robot_description. Ajoutez aussi un affichage TF pour voir les repères. Le bras apparaît alors en trois dimensions. La petite fenêtre à curseurs, ouverte par joint_state_publisher_gui, contient un curseur epaule et un curseur coude : bougez-les, et le robot s’articule en direct dans RViz.

Point d’étape — Si le bras s’affiche et bouge quand vous déplacez les curseurs, la description, la publication des repères et la visualisation fonctionnent ensemble. Si rien n’apparaît, vérifiez le Fixed Frame et le Description Topic avant tout.

Étape 7 — Commander les articulations par programme

Bouger des curseurs à la main, c’est bien pour comprendre ; en production, c’est un programme qui commande le robot. Le principe : la fenêtre à curseurs publiait les angles sur le canal /joint_states ; nous allons publier nous-mêmes sur ce canal pour animer le bras. Fermez la fenêtre à curseurs (gardez RViz et robot_state_publisher), et créez un nœud d’animation. Dans le dossier de code du paquet, ajoutez animation.py :

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState
import math

class Animation(Node):
    def __init__(self):
        super().__init__('animation_bras')
        self.pub = self.create_publisher(JointState, 'joint_states', 10)
        self.timer = self.create_timer(0.05, self.tick)
        self.t = 0.0

    def tick(self):
        self.t += 0.05
        msg = JointState()
        msg.header.stamp = self.get_clock().now().to_msg()
        msg.name = ['epaule', 'coude']
        # Deux oscillations sinusoïdales déphasées
        msg.position = [0.8 * math.sin(self.t), 1.2 * math.sin(self.t * 0.7)]
        self.pub.publish(msg)

def main(args=None):
    rclpy.init(args=args)
    noeud = Animation()
    rclpy.spin(noeud)
    noeud.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Ce nœud publie, vingt fois par seconde, un message JointState qui contient le nom des articulations et leur angle courant, calculé par deux sinusoïdes déphasées. Le champ header.stamp est horodaté : robot_state_publisher en a besoin pour synchroniser. Déclarez ce programme dans setup.py (section console_scripts : 'animation = bras_description.animation:main'), recompilez, puis lancez-le pendant que robot_state_publisher et RViz tournent :

ros2 run bras_description animation

Le bras se met à osciller tout seul dans RViz, les deux articulations suivant chacune leur rythme. Vous venez de remplacer la commande manuelle par une commande logicielle — le pas exact qui sépare une maquette d’un robot animé par un programme de mouvement.

Comprendre l’arborescence des repères

Un concept revient sans cesse en robotique et mérite qu’on s’y arrête : le repère (frame en anglais). Chaque link du robot possède son propre système de coordonnées, et ces repères s’emboîtent en arbre, du sol jusqu’au bout de l’outil. La base définit un repère ; le bras, attaché à la base par l’articulation « epaule », définit un repère qui tourne par rapport à celui de la base ; l’avant-bras un autre, relatif au bras. C’est cette chaîne qui permet de savoir où se trouve le bout du robot dans l’espace, quel que soit l’état des articulations.

ROS 2 gère cet arbre via un système nommé TF, et c’est précisément ce que publie robot_state_publisher : à partir de l’URDF et des angles d’articulation reçus sur /joint_states, il calcule en continu la position et l’orientation de chaque repère par rapport aux autres. Quand vous ajoutez l’affichage TF dans RViz, les petits trièdres de couleur que vous voyez sont ces repères. Si un repère « flotte » loin du robot ou pointe dans une direction inattendue, c’est presque toujours qu’un origin ou un axis de joint est mal défini dans l’URDF.

Pourquoi cela compte ? Parce que toute la robotique utile repose là-dessus. Calculer la position de l’objet à saisir par rapport à la pince, transformer les coordonnées d’une caméra vers le référentiel du robot, planifier une trajectoire : tout passe par des transformations entre repères. Maîtriser l’arbre TF dès la simulation, c’est se donner les moyens de comprendre les robots réels, où cette même mécanique relie capteurs, articulations et outils. Pour inspecter l’arbre en cours d’exécution, la commande ros2 run tf2_tools view_frames génère un schéma de tous les repères et de leurs liens — un outil de diagnostic précieux quand une transformation manque.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
RViz reste vide Fixed Frame incorrect ou RobotModel mal configuré Régler Fixed Frame sur base et Description Topic sur /robot_description
Le robot apparaît mais ne bouge pas joint_state_publisher_gui non lancé, ou nœud d’animation non démarré Vérifier qu’une source publie bien sur /joint_states
file not found au lancement URDF non installé (data_files oublié) Compléter data_files dans setup.py et recompiler
Segments mal positionnés origin d’un joint incohérent avec la longueur du link parent Aligner l’origin du joint enfant sur le bout du link parent
Les articulations dépassent Angles publiés hors des butées limit Rester dans les bornes lower/upper, ou élargir les limites

✅ Récapitulatif

Vous avez décrit un bras à deux articulations en URDF, écrit un fichier de lancement qui orchestre la publication des repères et la visualisation, déclaré proprement l’installation des fichiers du paquet, affiché le robot dans RViz, puis remplacé la commande manuelle par un nœud qui anime les articulations. Vous tenez désormais le pont entre la théorie (la description géométrique) et le mouvement, en simulation et en toute sécurité.

🧾 Aide-mémoire

Élément Rôle
link Un segment rigide du robot
joint (revolute) Une articulation en rotation entre deux links
robot_state_publisher Publie l’arborescence des repères à partir de l’URDF
joint_state_publisher_gui Curseurs pour bouger les articulations à la main
/joint_states Canal des angles d’articulation
ros2 launch PAQUET FICHIER Démarrer plusieurs nœuds d’un coup

💪 À vous de jouer

Ajoutez une troisième articulation — un « poignet » au bout de l’avant-bras — en complétant l’URDF d’un link et d’un joint supplémentaires, puis en ajoutant son nom dans le message JointState du nœud d’animation. Pour aller plus loin, donnez à chaque link une masse et une inertie (balise inertial) : c’est le préalable à une simulation physique réaliste.

Voir la piste pour le poignet

Dupliquez le bloc avant_bras en un link poignet plus court, ajoutez un joint poignet de type revolute avec parent link="avant_bras" et un origin à z=0.5 (longueur de l’avant-bras). Dans animation.py, ajoutez 'poignet' à msg.name et une troisième valeur à msg.position. Recompilez : le poignet s’anime à son tour.

Tutoriels frères

Pour aller plus loin

FAQ

Quelle différence entre URDF et xacro ?
L’URDF est le format brut, parfois verbeux et répétitif. Xacro est une surcouche qui ajoute des macros et des variables pour générer l’URDF sans se répéter. Pour un robot simple, l’URDF suffit ; dès que la description grossit, xacro devient précieux.

Pourquoi mon robot « tombe » ou flotte bizarrement ?
RViz ne simule pas la physique : il affiche seulement la géométrie selon les angles publiés. Les positions étranges viennent presque toujours d’un origin de joint mal placé ou d’un axe de rotation mal choisi.

Peut-on simuler la gravité et les collisions ?
Oui, mais avec un simulateur physique comme Gazebo, distinct de RViz. RViz visualise ; Gazebo simule la dynamique. Les deux se complètent dans un projet robotique sérieux.

Comment commander le bras vers une position précise plutôt qu’en oscillation ?
Il suffit de publier une valeur fixe dans msg.position au lieu d’une sinusoïde. Pour des trajectoires complexes (atteindre un point dans l’espace), on utilise la cinématique inverse, un sujet à part entière.

مشاركة
Service ITSkillsCenter

Application mobile Android et iOS

Création d'application mobile Android et iOS. À partir de 350 000 FCFA.

Démarrer mon projet
Publicité