Simulation
Le paquet ratbot_sim permet de développer et tester sans robot réel.
Simulateur cinématique léger
Le nœud base_sim imite le firmware, vu du graphe ROS : il s’abonne à
base/cmd_vel_safe (la sortie du garde-barrière, comme la vraie carte),
intègre une odométrie parfaite et publie base/odom,
base/joint_states et la TF ratbot_N/odom → ratbot_N/base_footprint.
Le watchdog du firmware est simulé : sans commande pendant 0,5 s, le
robot s’arrête. Toute la chaîne réelle est donc en jeu, arrêt d’urgence
compris.
source ros2_ws/install/local_setup.bash
ros2 launch ratbot_sim sim.launch.py robot_id:=1
Arguments : robot_id (défaut 1), rviz (défaut true).
Dans RViz : Fixed Frame = ratbot_1/odom, ajouter un affichage
RobotModel (topic /ratbot_1/robot_description) et TF.
Piloter au clavier :
ros2 run teleop_twist_keyboard teleop_twist_keyboard \
--ros-args -r cmd_vel:=/ratbot_1/base/cmd_vel
Tester l’arrêt d’urgence global pendant que le robot roule :
ros2 topic pub --qos-reliability reliable --qos-durability transient_local \
/ratbot_fleet/emergency_stop std_msgs/msg/Bool "data: true"
Paramètres du nœud base_sim : wheel_radius (0,02597 m) et
wheel_separation (0,140 m), rate (50 Hz), cmd_timeout (0,5 s).
Warning
Ces deux valeurs doivent suivre l’URDF, qui suit elle-même la flash de la carte — une seule géométrie, trois endroits qui la recopient. Elles avaient déjà divergé : 0,025 dans le simulateur contre 0,02597 dans l’URDF, soit 4 % d’écart entre le robot simulé et le vrai, relevé le 2026-09-26.
ros2 param get /ratbot_1/ratbot_controller wheel_radius_left
Gazebo
Le paquet ratbot_gazebo fournit la simulation physique sous
Gz Sim (Gazebo Fortress, apparié à ROS 2 Humble) : masses et inerties
de l’URDF, entraînement différentiel, IMU et lidar simulés. Le simulateur
cinématique base_sim reste utile pour les tests rapides sans physique.
Architecture : les annotations du modèle (plugins DiffDrive et
JointStatePublisher, capteurs, frictions) vivent dans
ratbot_description/urdf/ratbot.gazebo.xacro — le modèle reste avec le
modèle ; ratbot_gazebo ne contient que le monde, les launch files et le
pont ros_gz (topics Gz ↔ topics ROS 2).
Mondes disponibles (argument world:= de gazebo.launch.py) :
ratbot_lab.sdf (sol plat par défaut), tables.sdf (deux tables à pied
central, pour le capteur ToF, voir plus bas), enclos.sdf (l’enclos de
planches de 2 × 1 m du premier essai réel ; dimensions prévues, à mesurer) et deux labyrinthes fermés
(clin d’œil à Hampton Court, pour tester lidar/SLAM/nav) —
hampton_signature.sdf (compact, out of the box) et
hampton_full.sdf (plus complet). Ils sont générés par
ratbot_gazebo/scripts/build_maze.py (segments de mur → SDF). Le script
vérifie que le labyrinthe est fermé et solvable (flood-fill) et sait
afficher un aperçu ASCII :
python3 src/ratbot_gazebo/scripts/build_maze.py --layout signature --ascii
python3 src/ratbot_gazebo/scripts/build_maze.py --layout full
Le tracé (fonctions build_signature / build_full) et les paramètres
(épaisseur, hauteur des murs) s’éditent en tête du script. Lancement :
ros2 launch ratbot_gazebo gazebo.launch.py rviz:=true \
world:=$(ros2 pkg prefix ratbot_gazebo)/share/ratbot_gazebo/worlds/hampton_signature.sdf
ros2 launch ratbot_gazebo gazebo.launch.py robot_id:=1
# sans le lidar : use_lidar:=false
# avec RViz en plus : rviz:=true (Fixed Frame : ratbot_1/odom)
La simulation respecte le contrat multi-robots (Les topics du robot) : la
pile per-robot est celle du vrai robot — robot.launch.py du bringup
(agent:=false), donc cmd_vel_gate et robot_state_publisher
inclus — et le pont ros_gz expose base/cmd_vel_safe (⬇),
base/odom, base/joint_states, imu/data_raw, scan (⬆) sous
/ratbot_N/…, avec les frames TF ratbot_N/ sur le /tf partagé.
Comme base_sim, Gazebo est un remplaçant transparent du firmware.
Le capteur ToF (VL53L5CX, contrat dans Les topics du robot) n’existe pas dans
Gazebo. Le modèle porte un gpu_lidar de 40 × 40 rayons sur 45° × 45°
(ratbot.gazebo.xacro), ponté sur sim/tof_rays/points — un topic
interne à la simulation. ratbot_tof_sim (ratbot_sim) regroupe ces
rayons en 8 × 8 zones et publie sensors/tof/depth, le message de la
carte ; la suite (ratbot_tof_cloud, Nav2) est celle du vrai robot.
Plusieurs rayons par zone, et la zone garde le plus proche : un obstacle fin qui n’occupe qu’une partie du cône d’une zone la fait répondre, comme sur le vrai capteur. Avec un rayon par zone, la simulation passerait à côté d’un pied que le capteur voit.
Bruit et portée de la fiche technique (DS13754, mode 8 × 8 à 15 Hz, tolérances données à 2,7 σ) : σ = 5,5 mm jusqu’à 20 cm, puis 4 % de la distance (cible grise, pièce éclairée) ; 10 % de zones invalides ; portée coupée à 1,5 m (la fiche donne de 0,6 à 3,5 m selon la cible), pour une simulation plus pessimiste que le robot plutôt que l’inverse.
Le découpage en zones suit un modèle de caméra (parts égales dans le plan image), celui du capteur n’étant pas documenté : à moins d’un demi-degré près d’un découpage en angles égaux.
Essais du 2026-10-14, décor tables (chiffres et conditions dans la page
utilisateur « Le capteur de distance avant ») : sans le ToF, le robot finit
sur le pied ou dans le socle ; avec, il passe à côté et atteint le but.
La rampe de la carte, en simulation aussi (2026-10-25). Le plugin de
traction de Gazebo suivait un échelon de consigne d’un coup : le turbo de la
manette (0,06 → 0,18 m/s) était brutal, et chaque freinage faisait piquer le
robot du nez de 1°, assez pour que le capteur ToF prenne le sol pour un
obstacle. Il a désormais la rampe du firmware (ROBOT_ACCEL_MS2, 0,5 m/s²,
et 7 rad/s² en rotation) dans ratbot.gazebo.xacro ; mesuré sur un échelon
de 0 à 0,18 m/s : 0,5 m/s² à la montée comme à la descente, tangage nul.
Ce que le capteur simulé a révélé de la simulation elle-même (2026-10-14) :
Le parquet des décors n’était qu’un visuel, dessus à 1,2 cm du sol physique : le robot roulait « dedans », et le capteur ToF, qui voit les visuels, prenait ce faux sol pour un obstacle. Il affleure désormais le sol (
build_maze.py, et les quatre décors qui en ont un).Les roues de collision étaient des cylindres : couchés, ils touchent le sol par leurs deux bords, et Gazebo voyait un entraxe de 16 cm quand le plugin de traction en compte 14. Sur un tour sur place, les roues comptaient +14,2 % de rotation et l’EKF +3,6 % ; les murs vus par le lidar étaient posés de travers dans la carte de coûts globale, jusqu’à fermer des couloirs de 0,6 m. Elles sont désormais des sphères du rayon de la roue : 0,0 % d’écart. Sans effet sur le vrai robot, seul Gazebo lit les collisions.
Note
Les cartes des décors sont dessinées d’après leur plan
(python3 -m ratbot_gazebo.world_map, voir le README de
ratbot_maps), et non plus faites au SLAM : celles de rooms et
hampton_signature dataient de l’époque des roues à +14 %, avec des murs
décalés et, dans le labyrinthe, un passage de 38 cm réduit à 20.
Limite connue : le labyrinthe hampton_signature. Couloirs de 60 cm
libres, mais ouvertures de 38 cm entre les anneaux, à prendre en
virage. Avec la carte corrigée : 1 réussite sur 2 (213 s). L’échec : le
suivi de trajectoire (Regulated Pure Pursuit) coupe le virage dans
l’ouverture et s’arrête devant le bord du mur. Les décors rooms
(portes de 0,7 m) et tables passent.
Note
Un décor d’après le plan d’un bâtiment : un plan DXF dessiné dans
LibreCAD donne un décor Gazebo, puis sa carte par world_map. Calques,
commandes et vérifications : Un décor d’après le plan d’un bâtiment. Décor lisv : le
laboratoire, 2026-10-25.
Piloter au clavier (à travers le garde-barrière) :
ros2 run teleop_twist_keyboard teleop_twist_keyboard \
--ros-args -r cmd_vel:=/ratbot_1/base/cmd_vel
L’arrêt d’urgence global fonctionne aussi en simulation :
ros2 topic pub --qos-reliability reliable --qos-durability transient_local \
/ratbot_fleet/emergency_stop std_msgs/msg/Bool "data: true"
Ajouter d’autres robots dans la simulation qui tourne :
ros2 launch ratbot_gazebo spawn_robot.launch.py robot_id:=2 y:=0.5
Frame ``world``. Les arbres TF des robots sont naturellement disjoints
(ratbot_1/odom → …, ratbot_2/odom → …) : rien ne relie les robots
entre eux — sur le vrai système, ce lien relève du système hôte (par exemple
des caméras fixes qui recalent les poses). En simulation, la vérité terrain est
connue : spawn_robot.launch.py publie une TF statique
world → ratbot_N/odom à la pose de spawn du robot. RViz (config
sim.rviz, Fixed Frame world) peut ainsi afficher tous les robots
dans un repère commun.
Note
Différences avec le vrai firmware, assumées à ce stade :
le DiffDrive de Gz n’imite pas le watchdog (pas d’arrêt automatique si
cmd_vel_safese tait —base_simle simule, lui) ;scann’appartient pas au contrat firmware : sur le vrai robot le lidar est branché sur la Pi (driverrplidar_ros, topicscandans le namespace) — la simulation reproduit ce nommage.