Carte et localisation

Dans sa configuration de base, le RatBot n’embarque aucun capteur extéroceptif : sa seule source de localisation propre est l’odométrie, qui dérive au fil du déplacement. Un lidar RPLidar optionnel peut être monté au sommet du robot (activable via le paramètre use_lidar de la description) ; il ouvre la voie au SLAM embarqué décrit plus bas. Cette page décrit comment le robot peut malgré tout disposer d’une carte et d’une pose corrigée, selon le contexte d’utilisation.

Carte fournie par un système extérieur

Le robot peut recevoir une carte d’un système hôte. Par exemple, avec un système de caméras fixes :

  • les caméras détectent le marqueur ArUco du robot et recalent sa pose (l’odométrie ne sert qu’entre deux observations) ;

  • le robot peut demander la carte globale au système central et l’utiliser pour naviguer.

TODO : interface exacte côté RatBot (souscription à une carte de type nav_msgs/OccupancyGrid, service de requête…), à définir de façon générique pour ne dépendre d’aucun hôte en particulier.

Carte propre du robot : SLAM

Avec le lidar, le RatBot construit sa propre carte par SLAM (slam_toolbox). L’architecture est en couches optionnelles, jamais lancées par le bringup du robot :

  • ratbot_slam — produit des cartes (dépend de slam_toolbox) ;

  • ratbot_nav — consomme des cartes (dépend de nav2) ;

  • ratbot_maps — paquet de données (pgm/yaml + posegraph), zéro code : ratbot_slam y écrit, ratbot_nav y lit. Les deux restent découplés.

Le SLAM tourne dans le namespace ratbot_N : il s’abonne à ratbot_N/scan, publie ratbot_N/map et la TF ratbot_N/map → ratbot_N/odom.

Important

Un seul parent par frame TF. En simulation, spawn_robot.launch.py publie une TF de vérité terrain world → ratbot_N/odom. Le SLAM (et plus tard AMCL) publie ratbot_N/map → ratbot_N/odom : odom aurait alors deux parents. Il faut donc lancer la simulation avec ground_truth_tf:=false quand on fait du SLAM ou de la nav.

Workflow (en simulation, robot 1) :

# 1. simulation SANS vérité terrain
ros2 launch ratbot_gazebo gazebo.launch.py ground_truth_tf:=false \
   world:=$(ros2 pkg prefix ratbot_gazebo)/share/ratbot_gazebo/worlds/hampton_signature.sdf

# 2. SLAM (+ RViz)
ros2 launch ratbot_slam slam.launch.py robot_id:=1

# 3. téléopérer pour couvrir tout le labyrinthe
ros2 run teleop_twist_keyboard teleop_twist_keyboard \
   --ros-args -r cmd_vel:=/ratbot_1/base/cmd_vel

# 4. sauvegarder la carte dans le dossier source de ratbot_maps
#    (depuis ros2_ws/ ; le fichier devient committable)
ros2 run nav2_map_server map_saver_cli \
   -f src/ratbot_maps/maps/hampton_signature \
   --ros-args -r map:=/map

La carte est un artefact partageable : un autre robot ne refait pas de SLAM, il charge le même fichier et se localise dedans (AMCL, voir la navigation). Sur un système hôte doté d’un serveur central, cette carte peut aussi lui être partagée.

Exploration autonome (ratbot_slam)

ratbot_slam explore.launch.py fait cartographier le robot seul, en réutilisant le cœur nav2 au-dessus de la carte vivante du SLAM :

  • slam_toolbox construit /map en direct et publie map → ratbot_N/odom (ici map_frame='map', la frame globale de nav2 — pas de map_server ni d’AMCL) ;

  • bringup_nav2.launch.py : le cœur nav2, dont la costmap globale s’abonne au /map vivant ;

  • explore_lite (m-explore-ros2) détecte les frontières (limite connu/inconnu) sur la carte du SLAM, /map, et envoie des buts sur navigate_to_pose. Il s’arrête quand il n’y a plus de frontière (enceinte fermée).

Pourquoi la carte du SLAM et pas la costmap globale : la recherche de frontières d’explore_lite ne progresse que vers des cases de coût égal ou inférieur. Avec l’inflation de navigation de 0,50 m, le milieu d’une porte de 0,8 m porte déjà un coût, et la recherche s’arrêtait au seuil des portes : 44 % du décor lisv exploré, couloirs seulement, contre 95 % sur la carte du SLAM, Nav2 gardant ses 50 cm (campagnes du 2026-10-26 et du 2026-10-27).

explore_lite vient de notre fork (ratbot.repos, branche fix/blacklist-reached-frontier), qui ajoute le paramètre blacklist_reached. Sans lui, une frontière que le lidar ne peut pas lever (au pied du robot, sous sa portée minimale de 0,2 m) restait choisie : Nav2 annonçait le but atteint sans bouger, et explore_lite, qui ne mettait en liste noire que les buts abandonnés, le renvoyait sans fin. Le robot restait figé, parfois bien avant la fin : quatre explorations de lisv sur quatre s’y sont arrêtées, entre 75 et 97 % du plan. Avec blacklist_reached, la frontière atteinte passe en liste noire (campagne du 2026-10-27 au cahier). Correction à proposer au projet d’origine.

Réglage du choix des frontières (campagne du 2026-10-27/28, décor lisv, 3 passages par réglage, arrêt à 3000 s simulées) :

planner_frequency / min_frontier_size

couverture, médiane (min–max)

frontières restantes

0,1 Hz / 0,15 m

92,6 % (87,6–94,0)

55 à 62

0,33 Hz / 0,15 m

93,3 % (88,1–95,2)

65 à 81

0,1 Hz / 0,75 m (retenu)

95,2 % (91,2–96,5)

6 à 8

À 0,15 m, les trous de quelques cases au milieu des pièces devenaient des frontières que le robot allait voir une à une, et l’exploration ne finissait jamais. À 0,75 m (valeur conseillée par le projet d’origine sur une carte de SLAM), un passage sur trois s’est arrêté de lui-même (« All frontiers traversed »), et les demi-tours tombent de 36 à 27 en médiane. 0,33 Hz explore plus vite à mi-parcours mais envoie 70 % de buts en plus. Limite : une frontière de moins de 0,75 m est ignorée ; à vérifier dans les labyrinthes de Hampton (couloirs de 0,6 m), pour lesquels 0,15 m avait été choisi.

C’est ce que permet le split : la même brique bringup_nav2 sert la navigation sur carte statique (AMCL) et l’exploration sur carte vivante (SLAM).

explore_lite n’est pas dans l’apt : il est vendoré via ros2_ws/ratbot.repos (vcs import), depuis notre fork hdd-robot/m-explore-ros2, branche fix/blacklist-reached-frontier (un commit au-dessus de main d’origine, pour une future pull request). vcs import --skip-existing ne remplace pas un clone déjà présent : ratbot build compare l’adresse du clone à celle de ratbot.repos et avertit ; supprimer src/m-explore-ros2 et relancer le build. Pour travailler sur la correction : git -C ros2_ws/src/m-explore-ros2 log, commits en anglais (destinés au dépôt d’origine). Le paquet multirobot_map_merge du même dépôt (fusion multi-robots) est ignoré pour l’instant (COLCON_IGNORE) — il resservira pour l’exploration multi-robots.

cd ros2_ws
vcs import src < ratbot.repos
touch src/m-explore-ros2/map_merge/COLCON_IGNORE
colcon build && source install/setup.bash

ros2 launch ratbot_gazebo gazebo.launch.py ground_truth_tf:=false \
   world:=$(ros2 pkg prefix ratbot_gazebo)/share/ratbot_gazebo/worlds/rooms.sdf
ros2 launch ratbot_slam explore.launch.py robot_id:=1

Trajets appris

« Apprendre puis refaire » (page utilisateur « Apprendre et refaire un trajet ») : ratbot route record lance ratbot_route_record, qui lit la TF map → ratbot_N/base_footprint à 10 Hz et garde une pose tous les 10 cm ou 15° (ratbot_nav/route.py, testé dans test/test_route.py) ; Ctrl-C écrit ratbot_maps/routes/NOM.yaml. ratbot route follow lance ratbot_route_follow :

  • il ramène d’abord le robot au départ s’il en est à plus de 30 cm (NavigateToPose). Sans cela, un robot déjà à l’arrivée voyait Nav2 déclarer le trajet fait aussitôt ;

  • mode points : NavigateThroughPoses, un point tous les 50 cm. L’arbre de Nav2 pour ce cas a été copié (ratbot_nav/behavior_trees/) pour une seule raison : son nœud RemovePassedGoals cherche le robot dans base_link par défaut, et aucun trajet par points ne démarrait ; le marqueur __ROBOT_NAMESPACE__ y est remplacé au lancement (pas de < dans un attribut XML) ;

  • mode exact : FollowPath sur la ligne enregistrée, sans replanifier ;

  • les poses sont datées à zéro (« la plus récente » pour tf2) : datées à l’heure du PC, la simulation les rejetait — « Transform data too old » — et le contrôleur se disait arrivé sans avoir bougé ;

  • l’arrivée est vérifiée par le nœud (moins de 30 cm du but), pas seulement crue sur parole ;

  • Ctrl-C annule le but : rclpy est initialisé sans son gestionnaire de signaux, sinon il coupait la liaison avant l’annulation et Nav2 aurait continué de conduire.

Essais du 2026-10-15, décor rooms, trajet de 2 m passant une porte : points 6 cm d’écart moyen à la ligne apprise (10 cm au plus), exact 3 cm (6 cm). Échecs restants, tous en repartant de l’encadrement de la porte en virage : la limite de Pure Pursuit notée dans Simulation.