``ratbot`` : le point d'entrée unique ===================================== ``./scripts/ratbot`` est la **seule** commande du projet. Elle est identique sur la machine de développement et sur les robots : c'est la *machine* qui décide de ce qu'elle installe, construit et lance. .. code-block:: bash ./scripts/ratbot role # ce que la machine croit être Pourquoi un point d'entrée unique --------------------------------- Un seul dépôt, un seul jeu de sources, une seule commande. Ce choix évite trois dérives que l'on paie toujours plus tard. **Les procédures ne peuvent pas diverger.** La commande lancée à la main est exactement celle qu'exécute systemd — ``ratbot.service`` appelle ``ratbot boot``, pas une ligne recopiée. Une correction profite aux deux d'un coup. **Un robot neuf n'a rien à configurer.** Le nom de machine porte déjà le numéro du robot, et tout en découle. Il suffit qu'il soit bien nommé. **On ne se trompe pas de rôle.** La machine de développement ne construit pas les paquets du robot, et le robot ne construit pas ceux de la simulation. Comment le rôle est déterminé ----------------------------- Dans cet ordre, la première réponse gagne : .. list-table:: :header-rows: 1 :widths: 34 66 * - Source - Usage * - ``RATBOT_ROLE`` - variable d'environnement, priorité maximale — pour un essai ponctuel * - le **nom de machine** - ``ratbot-3`` → rôle ``robot``, ``robot_id`` 3. C'est le cas normal * - sinon - rôle ``dev`` Le **numéro du robot**, lui, est toujours lu dans le nom de machine, même quand le rôle est forcé : forcer le rôle ne fait jamais perdre le numéro. À défaut de nom conforme, il vient de ``RATBOT_ID``, puis vaut 1. .. note:: Avant le 2026-10-11, le numéro n'était lu dans le nom que lorsque ce nom décidait du rôle. Un rôle forcé sur ``ratbot-2`` donnait donc le robot 1 — et une commande visait le journal, les réglages ou le mode banc d'un autre robot, sans un message. Un rôle autre que ``robot`` ou ``dev`` fait sortir la commande avec le code 2 : mieux vaut s'arrêter que d'agir sur une supposition. La chaîne des conventions ~~~~~~~~~~~~~~~~~~~~~~~~~ .. graphviz:: :caption: Tout découle du nom de machine. digraph conventions { rankdir=LR; bgcolor="transparent"; node [shape=box, style="rounded,filled", fontname="sans-serif", fontsize=10, margin="0.18,0.10"]; edge [fontname="sans-serif", fontsize=9, color="#999999"]; host [label="hostname\\nratbot-3", fillcolor="#dae8fc", color="#6c8ebf"]; id [label="robot_id = 3", fillcolor="#f5f5f5", color="#999999"]; ns [label="namespace\\n/ratbot_3/", fillcolor="#d5e8d4", color="#82b366"]; tf [label="frames TF\\nratbot_3/…", fillcolor="#d5e8d4", color="#82b366"]; ip [label="192.168.1.173\\n= 170 + N", fillcolor="#ffe6cc", color="#d79b00"]; host -> id; id -> ns; id -> tf; id -> ip; } Le **domaine DDS vaut 77** pour tout le parc. Il est figé dans le firmware de la carte, donc elle ne peut pas se tromper de domaine même si l'environnement de la Raspberry est mal réglé. Le PC est réglé sur le même domaine : ``~/.bashrc`` en natif, ``compose.yaml`` pour le conteneur Docker. Une simulation y croiserait les robots réels sous les mêmes noms ; ``ratbot up`` refuse donc de démarrer si un ``/ratbot_N`` répond déjà dans le domaine ``ROBOTS_DOMAIN``. Les commandes ------------- .. list-table:: :header-rows: 1 :widths: 20 12 68 * - Commande - Rôle - Ce qu'elle fait * - ``role`` - les deux - affiche ce que la machine croit être : rôle, numéro, workspace, distribution, domaine DDS. **À lancer en premier** quand quelque chose surprend * - ``install `` - robot - met en service une Raspberry neuve ou clonée, depuis une fiche d'identité — voir :doc:`../ros2/bringup`. ``network.gateway`` est transmise à ``ratbot network`` (``RATBOT_PASSERELLE``), après comparaison à la route par défaut en service ; ``network.address`` n'existe plus (l'adresse vient de ``robot_id``), une valeur contraire est refusée * - ``deps`` - les deux - les paquets ROS du rôle, par apt * - ``build`` - les deux - ``colcon build``, en écartant les paquets de simulation sur un robot. Récupère d'abord, s'ils manquent, les paquets externes de ``ros2_ws/ratbot.repos`` (``vcs import``) ; ne touche ni à ``vcstool`` ni au réseau s'ils sont présents. Dans le conteneur Docker, ``build/``, ``install/`` et ``log/`` sont les dossiers ``*_docker/`` du PC, montés par ``compose.yaml`` : la compilation du PC n'y fonctionne pas * - ``up [DECOR] [N] [args]`` - les deux - lance la pile du rôle. Sur le PC, la simulation : décor par son nom court, N robots (1 à 4) posés sur les places du décor (``worlds/NOM.spawn.yaml``, voir ``ratbot_gazebo/worlds.py``), vérité terrain activée — ``--list`` lit les décors **installés**. Dans le domaine 77, refuse si un ``/ratbot_N`` y répond déjà (``sim_hors_des_robots``, 2 s d'écoute). Sur un robot, refuse si ``ratbot.service`` tourne déjà (il porte toute la pile) * - ``boot`` - robot - la pile embarquée séquencée (``boot.launch.py``) : agent, attente de la carte, puis le reste. **C'est ce que lance systemd** — voir :doc:`../ros2/bringup` * - ``agent`` - robot - l'agent micro-ROS seul, sur ``/dev/ttyAMA0`` à 3 Mbaud, dans ``/ratbot_N`` — pour la mise au point, service arrêté. Même définition que celle de ``boot`` (``micro_ros_agent.launch.py``) * - ``services`` - robot - installe les services systemd, la source de temps, le groupe ``dialout``, et arrête les mises à jour automatiques * - ``network`` - robot - l'adresse fixe netplan * - ``time`` - robot - heure de secours depuis la RTC de la carte, en root, au démarrage — voir :doc:`../firmware/horloge` * - ``config [N] show|save|load`` - les deux - paramètres de la carte : ``show``, ``save``, ``load`` — voir :doc:`../ros2/topics` * - ``log [N]`` - les deux - journal de bord de la carte, ``--clear`` pour l'effacer * - ``signals`` - robot - voyant et sons côté Raspberry, seuls — pour la mise au point. Le service les lance déjà, avec la pile * - ``bench [N] on|off`` - les deux - mode banc : suspend la sécurité inertielle, pour faire tourner les roues robot posé sur sa boîte. Jamais enregistré dans la carte * - ``slam N`` - PC - cartographie du robot ``N`` : vérifie qu'il est visible, remet son odométrie à zéro (l'origine de la carte), lance slam_toolbox et RViz. En simulation (``/clock`` publié, fonction ``en_simulation``) : ``use_sim_time:=true``, pas de remise à zéro (l'origine est la place de départ), et refus si la vérité terrain tourne (nœud ``world_to_odom_ratbot_N``) * - ``map save [N] F`` - PC - enregistre dans ``F.yaml`` et ``F.pgm`` la carte du robot ``N``, publiée par slam_toolbox sur ``/ratbot_N/map`` (``map_name`` de ``slam.launch.py`` : une carte par robot). Sans ``N``, ``/map``, celle de l'exploration. ``F`` sans ``/`` ni ``.yaml`` : un nom, rangé dans ``MAPS_DIR`` (``ros2_ws/src/ratbot_maps/maps``, les SOURCES : pas de recompilation). Refus d'écraser une carte suivie par git * - ``map list`` - PC - les cartes de ``MAPS_DIR``, et le décor du même nom s'il existe * - ``nav N [carte]`` - PC - navigation du robot ``N`` sur une carte : localisation, Nav2, RViz. Heure réelle, ou heure de la simulation si ``/clock`` est publié ; en simulation, même refus que ``slam`` si la vérité terrain tourne. La carte : un nom (``carte_de``) ou un chemin ; omise en simulation, celle du décor en cours (``decor_en_cours`` : le monde Gz, ``/world/NOM/``) * - ``explore N [args]`` - PC - ``ratbot_slam/launch/explore.launch.py`` (slam_toolbox sur ``/map``, cœur Nav2, ``explore_lite``). Mêmes contrôles que ``slam`` : robot visible, simulation reconnue, refus si la vérité terrain tourne ; sur un robot réel, remise à zéro de l'odométrie (``origine_ici``) et avertissement : l'exploration réelle n'a été essayée qu'une fois, dans un couloir (2026-10-19) * - ``fleet [show|status|battery|time|diag|who|identify N]`` - PC - ``python3 -m ratbot_bringup.fleet`` sur ``deploy/fleet.yaml`` : le fichier est validé (numéros uniques de 1 à 19, états connus, adresses hors de la plage des robots, interventions datées et dans l'ordre), puis chaque robot en service est interrogé en parallèle, une connexion SSH par robot en ``BatchMode`` (service, ``git rev-parse HEAD`` comparé en entier au PC, ``chronyc tracking`` ; un refus SSH est expliqué d'après ``stderr``) et ``sensors/battery`` par ``ros2 topic echo --once``, en best effort pour ne pas dépendre de la découverte. Les campagnes sont trouvées par ``ratbot_calibration.campaigns.find``, le code de ``ratbot-calibrate`` (``_exclus``, abandonnées et simulations écartées ; robot reconnu au nom à défaut de ``campagne.yaml``) ; une date ``applied`` illisible donne une alerte, pas une exception. Une intervention invalide les réglages de son champ ``affects`` (par défaut ``umbmark``, ``gyro``), pas le même jour. Code de retour 1 si un robot en service a une alerte. ``battery``, ``diag``, ``who`` et ``identify`` passent par une seule participation DDS (``ros_snapshot`` : rclpy, ``domain_id`` du fichier ou ``--domain``, abonnements best effort, 3 s), au lieu d'un ``ros2 topic echo`` par robot ; ``status`` lit aussi la batterie ainsi. ``time`` mesure l'écart de chaque Pi au PC par SSH (« ready », puis un seul aller-retour chronométré, ``date +%s.%N`` rapporté au milieu), lit chrony des deux côtés (timesyncd sur le PC à défaut), et l'écart RTC/Pi dans ``status/rtc`` (``time_ref - header.stamp``). ``diag`` garde le dernier état de chaque bloc et range par ``hardware_id`` (``ratbot_N``, que ``status_manager`` porte aussi désormais). ``who`` croise le graphe ROS (nœuds d'activité, éditeurs des entrées du multiplexeur) et les ``ps`` du PC et de la Pi (``__node:=`` et ``__ns:=``, ou le module d'un script). ``identify`` répète l'état ``identify`` de ``led_states.yaml`` sur ``status/set_state`` (maintenu 2 s) et ``SOUND_BEEP`` chaque seconde, après avoir attendu ses abonnés. Tests : ``ratbot_bringup/test/test_fleet.py``, dont un qui vérifie que le domaine DDS du fichier est celui du firmware et du script * - ``navtest [épreuves] [-n N] [-j J]`` - PC - ``python3 -m ratbot_gazebo.navtest`` : banc de navigation en simulation, chaque passage dans une simulation neuve, domaine ``100 + place`` (voir :doc:`../ros2/simulation`, « Banc de navigation »). Refusé sur un robot * - ``rviz N [args]`` - PC - ``ratbot_nav/launch/rviz.launch.py`` : RViz seul, ``robot.rviz`` recopié pour le robot ``N``, repère ``ratbot_N/odom`` (publié par l'EKF dès le démarrage). ``use_sim_time`` vaut ``true`` si ``/clock`` est publié, c'est-à-dire en simulation * - ``joy [args]`` - PC - lance ``joy_fleet.launch.py`` (une manette, robots 1 à 4) ; refuse si ``/dev/input/jsN`` est absent (``device_id:=N``, 0 par défaut) ou illisible (conteneur sans ``device_cgroup_rules``, groupe ``input``), car ``joy_node`` échouerait sans rien signaler * - ``key N`` - PC - ``ratbot_bringup/key_teleop.py`` (nœud ``ratbot_key_teleop``) remappé sur ``/ratbot_N/base/cmd_vel_teleop`` (la voie de la manette, priorité 100), 10 cm/s au départ ; vérifie d'abord que le robot est visible. Commande **retenue**, renvoyée à 10 Hz jusqu'à ``k`` ; une fois arrêté, l'arrêt est répété 0,5 s puis plus rien, pour rendre la main à twist_mux. Remplace ``teleop_twist_keyboard`` depuis le 2026-10-19 (à-coups : répétition du clavier à ~600 ms, chien de garde de la carte à 500 ms). rclpy sans gestion de SIGINT, pour que l'arrêt final parte. Tests : ``test/test_key_teleop.py`` * - ``route record N NOM`` - PC - ``ratbot_nav route_record`` : pose ``map → ratbot_N/base_footprint`` tous les 10 cm ou 15°, écrite dans ``ROUTES_DIR`` (``ros2_ws/src/ratbot_maps/routes``, les SOURCES) au Ctrl-C ; refuse un nom déjà pris ; la carte est notée (décor en cours en simulation) * - ``route follow N NOM [exact]`` - PC - ``ratbot_nav route_follow`` : retour au départ, puis ``NavigateThroughPoses`` (points tous les 50 cm) ou ``FollowPath`` (``exact``) ; voir :doc:`../ros2/map`, « Trajets appris ». Un Ctrl-C entre l'envoi et l'acceptation attend la réponse pour annuler le but accepté (SIGINT rétabli au démarrage, même hérité ignoré) ; un trajet en boucle (arrivée à 30 cm du départ) est envoyé en deux buts, jusqu'à sa pose la plus éloignée du départ puis retour, car le contrôleur juge l'arrivée sur la fin du chemin, déjà atteinte ; l'attente de la pose du robot se compte en temps réel (l'horloge simulée saute à l'arrivée de ``/clock``) ; ``route.load`` refuse un YAML qui n'est pas un dictionnaire et les valeurs non finies. Le ``RemovePassedGoals`` de l'arbre ``navigate_through_poses.xml`` a un rayon de 0,2 m (0,7 jusqu'au 2026-10-18 : avec des points tous les 50 cm, chacun était retiré avant d'être atteint) * - ``route list`` - PC - les trajets de ``ROUTES_DIR``, avec robot, carte, longueur et date **Le numéro du robot** se donne en premier : ``slam 1``, ``nav 1 …``, ``log 2``, ``bench 3 off``. Sur un robot, il est facultatif pour ``log`` et ``bench`` et vaut le sien ; **sur le PC, il est obligatoire**. Un numéro par défaut, avec plusieurs robots sur le réseau, viserait le mauvais sans rien dire. Ce que la commande délègue -------------------------- Trois sujets sont assez gros pour vivre dans leur propre fichier, et ``ratbot`` leur passe la main sans rien réimplémenter : .. list-table:: :header-rows: 1 :widths: 30 70 * - Fichier - Appelé par * - ``scripts/ratbot-install`` - ``ratbot install`` — mise en service d'une machine * - ``scripts/ratbot-config`` - ``ratbot config`` — sauvegarde et restauration des paramètres * - ``scripts/ratbot-profile`` - autonome — mesure le coût réel de la boucle micro-ROS * - ``scripts/ratbot-calibrate`` - autonome — calibration guidée (``N lidar``, ``N gyro``, ``N umbmark``, ``N fusion``) et consultation (``list``, ``stats``, ``show``, ``compare``, ``compute``, ``apply``, ``exclude``). ``compare`` lit le témoin enregistré par ``umbmark-run`` (``witness_end``, robot_localization quand le robot se pilote sur ses roues) : E_max et erreur moyenne des deux estimations, sur les mêmes carrés ; rien n'est modifié. Chaque campagne porte un ``campagne.yaml`` (robot, test, date d'application, notée dès l'écriture, avant les sauvegardes) ; les sauvegardes de réglages d'une campagne sont horodatées (``reglages_avant_HHMMSS.yaml``…), sauf le ``reglages_avant.yaml`` d'une mesure UMBmark, écrit une fois et lu par l'analyse. « Déjà dans la carte » n'est conclu que si chaque valeur a pu être LUE : une lecture ratée n'empêche pas d'écrire ; ``ratbot_calibration/campaigns.py`` (``ratbot-campaigns``) les retrouve par robot, les résume et recalcule E_max avec ``umbmark-analyze``. Une mesure UMBmark finie est fermée (``CLOSE``) : la suivante part dans un dossier neuf. Enchaîne les outils de ``ratbot_calibration`` sans les réécrire, range les résultats dans ``cahier_de_labo/campagnes/`` et attend un « o » avant chaque mouvement. ``gyro`` sauvegarde les réglages avant, propose la moyenne des deux sens et ne l'écrit (``ros2 param set``) qu'après accord. ``umbmark`` reprend une campagne interrompue, même un autre jour (le dossier ouvert le plus récent du robot), après confirmation et vérification du firmware et du côté ; sinon la mesure est abandonnée (``abandoned`` dans ``campagne.yaml``, ``CLOSE``) et ne compte plus. Le côté est celui des mesures réelles du robot (``ratbot-campaigns side``), imposé ; ``stats`` ne compare que des côtés égaux (sinon « autre côté »). Avant tout mouvement, refus si ``controller_server``, ``bt_navigator``, ``explore_node`` ou ``slam_toolbox`` tourne dans ``/ratbot_N/``. ``apply`` compare les réglages de la carte à ceux de la mesure (``ratbot-campaigns reference``) et refuse une campagne ``_simulation`` sur un robot réel. ``RATBOT_UMB_RUNS`` et ``RATBOT_UMB_SIDE`` raccourcissent une campagne, en simulation seulement (ignorées sur un robot réel). À part de ``ratbot`` : on ne calibre qu'à la mise en service Ils sont en Python, parce qu'ils valident des données et rendent des rapports, sauf ``ratbot-calibrate``, en bash comme ``ratbot`` : il enchaîne des commandes. ``ratbot`` lui-même reste en bash, pour la même raison. Ce qu'on peut forcer -------------------- Toutes ces variables ont un défaut raisonnable ; elles existent pour l'essai ponctuel, pas pour l'usage courant. .. list-table:: :header-rows: 1 :widths: 30 18 52 * - Variable - Défaut - Effet * - ``RATBOT_ROLE`` - d'après le nom - force ``robot`` ou ``dev`` * - ``RATBOT_ID`` - 1 - numéro du robot, si le nom de machine ne le donne pas. Le nom l'emporte toujours * - ``ROS_DISTRO`` - ``humble`` - la distribution visée * - ``ROS_DOMAIN_ID`` - 77 - le domaine DDS * - ``RATBOT_AGENT_DEV`` - ``/dev/ttyAMA0`` - le port série vers la carte * - ``RATBOT_AGENT_BAUD`` - 3000000 - la vitesse de la liaison .. note:: ``ROS_DOMAIN_ID`` doit vivre **à deux endroits** sur un robot : ``/etc/environment`` *et* le fichier du service. ``/etc/environment`` n'est lu que par PAM, donc par les sessions de connexion — une unité systemd ne le voit pas. Ce n'est pas une redondance, les deux sont nécessaires. Voir aussi ---------- - :doc:`../ros2/bringup` — mettre en service une machine neuve. - :doc:`../firmware/horloge` — la chaîne de l'heure, et le service de secours. - :doc:`../ros2/topics` — les paramètres de la carte.