Feuille de route ================ État au 2026-09-12, et ce qui reste à faire. Cette page existe pour que rien ne se perde entre deux séances : elle est versionnée, contrairement à un historique de terminal. .. note:: **Nomenclature.** La carte électronique s'appelle le **ratbot_controller**, plus « la STM32 ». Le nom du microcontrôleur est un détail d'implémentation ; le rôle, lui, est stable. Le nœud ROS suit : ``/ratbot_N/ratbot_controller`` (anciennement ``ratbot_stm32``, renommé le 2026-09-11). Ce qui fonctionne ----------------- .. list-table:: :header-rows: 1 :widths: 30 70 * - Sous-système - État * - Lien micro-ROS - UART5 à 3 Mbaud, DMA, exécution depuis la flash QSPI externe avec les deux caches actifs * - Traction dynamixel - **deux** AX-18A (gauche ID 14, droite ID 10) sur le connecteur du groupe 2, mode roue, asservissement PI avec terme direct et gain de freinage, rampe à 1 m/s², watchdog 500 ms. L'estimateur reconstruit la position malgré les 60° aveugles du capteur — voir :doc:`../firmware/traction` * - Arrêt d'urgence - contact matériel, verrou logiciel, réarmement par service **ou par 5 appuis sur SW1**, sans ROS. Champignon 1NC à accrochage sur les trois robots depuis le 2026-10-27 : sécurité positive, une coupure du câble arrête le robot (``failsafe=1`` dans ``/diagnostics``) * - Capteurs - température TMP102, batterie * - Voyant et LED - RGB avec PWM logiciel sur le bleu, 4 LED de façade * - Écran OLED - SSD1309, 6 expressions, animation de repos autonome * - Buzzer - 15 mélodies non bloquantes sur le passif, plus les codes de bips comptés ; le buzzer actif est réservé à la sécurité * - Paramètres - 11 paramètres ROS persistés en flash U7, dont ``robot_id``, un rayon de roue par côté et la vitesse des deux ventilateurs. Sauvegarde et restauration par ``./scripts/ratbot config`` * - Centrale inertielle - LSM6DSOX sur I2C3, gyro et accéléromètre à 104 Hz, LPF1 à 11,5 Hz, biais mesuré au démarrage * - Signalisation - voyant et sons pilotés par la tâche IHM : bilan de démarrage en codes façon BIOS, priorité danger > panne > pas de Pi > couleur de la Pi. Voir :doc:`../ros2/signaux` * - Heure - RTC sur pile, recalée sur le serveur NTP du labo par l'agent, et capable de dépanner une Pi qui démarre sans réseau * - Watchdog - IWDG1 à 8 s, critère de vie fondé sur ``rmw_uros_sync_session`` et sur le battement de la tâche capteurs ; relance annoncée au démarrage suivant * - Journal et boîte noire - 256 événements datés en flash de données, cause du dernier plantage gardée sur pile. Voir :doc:`../ros2/journal` * - Diagnostic - ``/diagnostics``, 18 blocs publiés par la carte, étalés dans la seconde, dont les compteurs de l'estimateur de roue et les erreurs de la liaison série * - Banc de test - 36 vérifications sur PC, qui compilent les vrais fichiers de la carte avec du faux matériel — voir :doc:`../firmware/banc_de_test` * - Calibration - paquet ``ratbot_calibration`` : parcours UMBmark dans les deux sens, saisie de la mesure au sol, et calcul des deux erreurs systématiques de l'odométrie. Les équations sont vérifiées sur l'exemple publié de Borenstein * - Ventilateurs - deux sorties PWM indépendantes à 200 Hz, réglables à chaud. Un ventilateur **2 fils** ne se ralentit pas : il cale dès 95 % 1. Exploitation quotidienne --------------------------- Ces points ne sont pas du développement, mais ils coûtent du temps à chaque séance. **Fait : la pile embarquée est un service.** ``ratbot.service``, avec ``Restart=always``, attend l'heure (``chronyc waitsync``) avant de démarrer, puis séquence l'agent, la signalisation et la chaîne de commande dans ``boot.launch.py``. Un agent mort se manifestait par un robot totalement muet — symptôme trompeur qui envoie chercher la panne ailleurs. **Fait :** ``ROS_DOMAIN_ID=77`` est dans ``/etc/environment``, donc présent dans les shells non interactifs et les unités systemd — précisément là où on en a besoin. **Adresses fixes : fait pour ratbot-1** (192.168.1.171, 2026-09-11). Les suivants : ``sudo ./scripts/ratbot network`` sur chacun, voir :doc:`../ros2/bringup`. **Ce qui reste côté Pi.** L'espace de travail n'y est pas construit et les paquets de navigation ne sont pas installés : ``ratbot_status`` ne tourne donc pas, et c'est lui qui lit ``led_states.yaml`` pour envoyer couleurs et sons à la carte. Toute la signalisation **côté carte** est validée sur le robot ; la moitié Pi n'a jamais tourné. ``./scripts/ratbot deps`` puis ``build``. Accessoirement : ``systemd-networkd-wait-online`` échoue et coûte ~2 min au démarrage ; la clé GitLab du Pi est une clé de **compte utilisateur** et non une clé de déploiement en lecture seule ; ``nav2_msgs`` manque, donc ``explore_lite`` ne compile pas sur le Pi. 2. L'heure : le ratbot_controller est la mémoire du temps — **fait le 2026-09-11** ------------------------------------------------------------------------------------ .. note:: **Tout ce qui suit fonctionne et a été mesuré sur le robot** : RTC conservée sur pile à travers une coupure, Pi 44 jours en retard rattrapée depuis la carte, premier échantillon chrony à +0,27 ms du serveur du labo. Le détail d'exploitation est dans :doc:`../ros2/bringup`. La section est gardée parce qu'elle explique **pourquoi** c'est fait comme ça. Deux pièges découverts en route : le CSS du LSE ne doit **jamais** être armé sur cette carte (ES0396 §2.2.19 — il efface l'heure à chaque coupure alors que le quartz va très bien), et un saut d'horloge sous un agent vivant rend les entités DDS invisibles, d'où le redémarrage de l'agent après une remise à l'heure. **Le problème.** La Raspberry Pi n'a pas d'horloge sauvegardée : elle démarre à une date fausse tant qu'elle n'a pas vu le réseau. Le ratbot_controller, lui, a un RTC sur quartz LSE avec domaine sauvegardé — il garde l'heure même Pi éteinte. **Pourquoi ça compte.** Les *frames* doivent être horodatées avec précision pour être exploitables dans un système multi-robots, où elles sont recoupées avec celles d'autres capteurs et des autres robots. Une horloge fausse ne se voit pas à l'usage : elle se découvre à l'analyse, des heures plus tard, quand les données ne se recalent plus. Le cycle à mettre en place : .. code-block:: text 1. la Pi demarre -> demande l'heure au ratbot_controller 2. la Pi se synchronise -> serveur de temps du testbed (.190) 3. periodiquement -> la Pi remet le RTC du controller a l'heure L'étape 3 est celle qu'on oublie : sans elle, le RTC dérive (~20 ppm sur un quartz LSE, soit quelques secondes par jour) et l'étape 1 repart d'une valeur fausse au prochain démarrage. Une remise à l'heure toutes les quelques minutes suffit largement — la période exacte reste à fixer. .. important:: **Ne pas confondre avec l'horodatage des messages.** Celui-ci passe déjà par ``rmw_uros_sync_session()`` : la carte prend l'heure de *l'agent*, donc de la Pi. Tant que la Pi est juste, les messages sont justes. Le RTC sert à deux choses différentes : **amorcer** l'horloge de la Pi au démarrage, et **garder** l'heure quand ROS ne tourne pas. Les deux mécanismes sont complémentaires, pas redondants. **Comment c'est fait.** La carte publie ``status/rtc`` (``sensor_msgs/TimeReference``) ; ``ratbot-time.service`` lit ce topic au démarrage de la Pi, sous l'utilisateur de l'agent, et n'avance l'horloge système que si la carte a plus de 60 s d'avance. Dans l'autre sens, ``rmw_uros_sync_session()`` toutes les 2 s donne à la carte l'heure de l'agent, donc du serveur NTP, et la RTC n'est réécrite qu'au-delà d'une seconde d'écart — la réécrire toutes les deux secondes userait le domaine de sauvegarde pour rien. 3. Le biais du gyroscope, et ce qu'il faut encore mesurer --------------------------------------------------------- Mesure du 2026-09-09, carte et moteurs posés sur la table : .. code-block:: text moteurs arretes moyenne -0,00168 ecart-type 0,00074 rad/s moteurs en rotation moyenne -0,00162 ecart-type 0,00062 rad/s .. warning:: **Cette mesure ne prouve pas encore que le filtre suffit.** Le robot n'étant pas assemblé, les moteurs ne sont pas solidaires du châssis qui porte la carte : il n'existe aucun chemin mécanique pour que leur vibration atteigne le gyroscope. Elle montre seulement qu'il n'y a pas de couplage **électrique**. **À refaire châssis monté.** C'est le seul essai qui dira ce que le LPF à 11,5 Hz apporte réellement. Elle révèle en revanche autre chose. La moyenne résiduelle, −0,0017 rad/s, vaut **2,3 fois l'écart-type du bruit** : c'est le biais qui a dérivé depuis la calibration, faite quelques minutes plus tôt. En cap, cela fait **5,8° par minute**. Autrement dit, **la stabilité du biais domine largement le bruit** — ce qui confirme après coup le critère de choix du composant. La suite utile n'est donc pas plus de filtrage, mais une **recalibration automatique** dès que les roues sont à l'arrêt depuis quelques secondes. On dispose déjà des vitesses de roue pour le savoir. 4. Sécurité inertielle : chocs, renversement, patinage ------------------------------------------------------- .. note:: **Renversement et blocage : faits le 2026-09-27**, validés sur ratbot-1 (``App/motion_guard.c``, 19 cas au banc). Renversé = plus de 60° pendant 0,5 s ; bloqué = puissance commandée ≥ 40 % et roue sous 0,5 rad/s pendant 1 s. Mode banc par SW2 tenu 3 s ou le service ``bench_mode`` (``std_srvs/SetBool``), jamais sauvegardé. Documentation utilisateur : *Arrêter et relancer le robot*. Mesures qui ont fixé les réglages : - **la centrale est montée à l'envers** : à plat, Z = −9,67 m/s². L'axe haut est −Z ; l'avant est Y, le côté X ; - **la puissance ne distingue pas la boîte du mur** : à vide, 57 à 69 % à 6-7,6 rad/s. C'est la **vitesse** qui les sépare ; - **une roue freinée ne gagne que 5 % de puissance par seconde** : à 70 %, un blocage lent aurait mis dix secondes à être vu. À 40 %, environ deux ; - le registre ``PRESENT_SPEED`` des AX-18A, lu jusque-là comme « rapport de puissance », **rend la vitesse** en mode roue — et zéro sur une roue bloquée. La puissance vient désormais de la commande (``last_sent``). **Patinage, fait le même jour** : écart d'au moins 0,35 rad/s (et de la moitié) entre le lacet des roues et celui du gyroscope, pendant 1 s. Roues et gyroscope concordent à 0,03 rad/s près jusqu'à 1,5 rad/s, et en pivot sur une roue. Mesures contre un mur, par terre : les roues patinent **à leur vitesse de consigne, avec la même puissance commandée et la même charge** (``PRESENT_LOAD``) qu'en roulant librement. Le robot est léger, ses roues décrochent avant de forcer. **Une poussée droite contre un mur reste donc invisible** : seul un patinage qui fait tourner les roues différemment se voit. Pas de pare-chocs prévu (décidé le 2026-09-27) ; piste restante sans matériel : Nav2 Collision Monitor sur le lidar, côté Pi. L'orientation de la centrale a été vérifiée de trois façons : poses statiques, rotation, et force centrifuge en tournant sur place (Y de la centrale −0,16 m/s² à 1,5 rad/s, dans les deux sens ; X inchangé). **L'URDF la déclarait à l'endroit : l'EKF fusionnait un lacet de signe inverse.** Corrigé (``rpy="π 0 −π/2"``, 45 mm derrière l'axe des roues). **Choc, fait le même jour** : ``sensors.c`` garde le pic de l'écart horizontal (X, Y) entre chaque échantillon et la moyenne du bloc précédent — la moyenne seule effaçait les chocs. Seuil 5 m/s² : la conduite la plus brusque culmine à 2,2 m/s² (mesuré) ; aucun choc n'a été provoqué, 5 est estimé (mur heurté à 0,05 m/s). Le pic de chaque seconde est publié (``shock_peak_cms2``) pour le revoir sur le terrain. **Choc à l'arrêt ignoré (2026-10-04)** : reposer le robot donnait 5,1 à 9,4 m/s² et l'a coupé sept fois pendant les campagnes UMBmark. Un choc ne compte plus que pendant la conduite (une roue à ≥ 5 % de puissance ou ≥ 0,5 rad/s) ou dans les 500 ms qui suivent. **Zéro du gyroscope remesuré à chaque immobilité (2026-10-04)** : ``App/gyro_zero.c``, 10 cas au banc. Le pilote mesure le biais une fois au démarrage, à l'aveugle ; un robot touché pendant ces 2 s a gardé +13,7 °/min de dérive toute la soirée du 03/10. Désormais, roues arrêtées sans puissance, puis 0,5 s d'attente et 2 s de gyroscope calme (moins de 0,5 °/s d'amplitude sur chaque axe) : la moyenne remplace le résidu. Les arrêts de 1,5 s d'un carré UMBmark ne suffisent pas : le zéro ne change jamais au milieu d'un essai. **Échelle du gyroscope (2026-10-04)** : réglage ``gyro_scale`` par robot (configuration v4), appliqué au lacet après le zéro. Mesuré par ``gyro-scale-run`` (dix tours sur place contre un repère) : 1,007 sur ratbot-1. Rejoué sur les campagnes UMBmark, il fait passer la fusion roues + gyroscope de 4,3 à 2,3 cm d'erreur d'arrivée (roues seules : 4,2). Le zéro et l'échelle sont publiés dans le bloc ``imu``. **Campagne fusionnée, outil prêt** : ``umbmark-run --odom fused`` pilote sur ``odometry/filtered`` ; l'EKF est remis à zéro au départ par son service ``set_pose`` (il ne fusionne que des vitesses : remettre à zéro l'odométrie de la carte ne le ramène pas à l'origine). Vérifié contre un vrai ``ekf_node``, pas encore sur le robot. **Reste à faire** : la chute libre, et la poussée droite contre un mur. Une fois l'IMU installée, le robot doit **s'arrêter tout seul** sur : - **un choc** — pic d'accélération ; - **un renversement** — le vecteur gravité quitte l'axe vertical durablement ; - **un patinage ou un blocage contre un mur**. Cette couche viendra s'ajouter aux trois de :doc:`../ros2/topics` — même principe : elle vit dans le contrôleur, donc elle survit à une panne ROS. .. tip:: **Le blocage contre un mur est détectable dès aujourd'hui, sans IMU.** On lit déjà les deux grandeurs nécessaires : le rapport de puissance réellement appliqué par le servo, et la vitesse mesurée de la roue. Une puissance au plafond avec une vitesse quasi nulle **est** un blocage — c'est d'ailleurs ce qui fait chauffer et déclencher la protection thermique des AX-18A. Le patinage, lui, demande vraiment l'IMU : les roues tournent normalement, c'est le robot qui n'avance pas, et seule une mesure extérieure au moteur peut le dire. 5. Finir l'écran ---------------- ``ratbot_msgs/ScreenCommand`` existe et ses tailles sont bornées — il ne manque que de l'ajouter aux ``extra_packages`` de la bibliothèque micro-ROS et de régénérer celle-ci. Ensuite ``screen/command`` dans le firmware, avec ses quatre modes : repos, expression, message, menu. C'est ce qui permet d'afficher du **texte**, donc les alertes lisibles et le menu embarqué. .. note:: ``ScreenCommand`` désigne les expressions par ``face_id`` (0..5), la galerie du firmware les désigne par leur nom. Il faudra figer l'ordre des images pour que les deux coïncident, ou publier la correspondance. 6. L'odométrie -------------- Les deux moteurs sont en place — **gauche ID 14, droite ID 10** — et la cinématique différentielle est validée : une consigne ``angular.z`` de 1 rad/s donne bien −2,74 et +2,89 rad/s aux deux roues, pour −2,80 / +2,80 attendus. .. note:: Déclarer un moteur ne demande **aucune recompilation** : un ``ros2 param set dxl_id_right 10``, écrit en flash U7, relu au démarrage. .. warning:: Les deux identifiants ont été **échangés** le 2026-09-24. Une consigne à gauche faisait tourner à droite, et l'odométrie voyait chaque dérive du côté opposé — ce qui donnait une trajectoire cohérente en apparence, mais symétrique de la vraie. Corrigé en remettant les ID dans le bon sens, pas en inversant les signes : l'inversion des signes, tentée d'abord, était la mauvaise réponse à la bonne observation. ``base/odom`` et ``reset_odometry`` sont **faits**. L'intégration se fait sur les positions de roue par la méthode du point milieu, et la vitesse rapportée colle à la consigne : .. code-block:: text consigne lineaire 0.060 m/s -> mesure 0.0598 / 0.0606 < 1 % consigne angulaire 1.000 rad/s -> mesure 0.9992 / 0.9998 < 0,1 % La traversée du secteur aveugle reste faite **à l'estime**, dans le driver : la position déroulée qu'il reconstruit est déjà la meilleure estimation disponible, et intégrer la vitesse par-dessus ajouterait une seconde source de dérive sans rien apporter. **La pose est validée au sol** depuis le 2026-09-24 : distance parcourue à 0,1–0,35 %, rotation continue à 0,5 %, et un carré de 4 m qui se referme à 1 cm de ce que croyait le robot. Il a fallu pour cela corriger quatre défauts, dont deux dans le secteur aveugle et un dans la QoS de ``cmd_vel`` — voir :doc:`../firmware/traction` et :doc:`../ros2/stm32_communication`. Reste : 1. **La campagne UMBmark**, cinq carrés dans chaque sens, pour séparer l'erreur de diamètre des roues de celle d'entraxe et les corriger. L'outillage est prêt (``ratbot_calibration``), la mesure au sol reste à faire. Puis une seconde campagne, sans recalculer, pour prouver que l'erreur a baissé. 2. **L'écart entre roues**, mesuré à ~4 % : la gauche tourne 1,8 % sous la consigne, la droite 2,5 % au-dessus. Chaque roue ayant sa propre boucle, ce n'est pas la géométrie mais de la friction ou de la dispersion entre servos. C'est distinct de l'écart de *diamètre* que corrige UMBmark, et les deux se cumulent. 3. **La cadence.** 27 Hz mesurés à la source pour 30 visés. Ce n'est pas la Pi qui perd des messages : c'est le coût d'un message de 750 octets à travers ``cubemx_transport_write`` et son ``osDelay(1)``, le même plafond que celui relevé le 2026-09-06. 7. Décisions en suspens ----------------------- .. _renommage-controller: **Fait le 2026-09-11 :** le nœud ``ratbot_stm32`` s'appelle désormais ``ratbot_controller``. Les paramètres persistants, stockés en flash, ne dépendent pas du nom du nœud : rien n'a été perdu. **Le nommage ``cmd_vel`` / ``cmd_vel_safe``.** Inverser ferait du chemin par défaut le chemin **dangereux** : un nœud publiant naïvement sur ``cmd_vel`` court-circuiterait l'arbitrage, le limiteur et le garde-barrière. D'où le refus d'inverser — mais c'est un choix de contrat, pas une fatalité. **La géométrie vit en double**, dans la flash du contrôleur et dans l'URDF. Les faire diverger fausserait silencieusement soit les commandes, soit la simulation. Le plus propre serait que le Pi lise la géométrie depuis les paramètres du contrôleur. 8. Optimiser la boucle : le cache de données -------------------------------------------- La tâche micro-ROS tourne à **32,5 Hz**, pas 100. Le profilage au compteur de cycles du cœur donne la répartition : .. code-block:: text base 16 071 us odom (750 o) + transactions dynamixel sensors 8 912 us IMU (330 o) + lecture I2C executor 3 152 us diag 1 607 us autres < 1 000 us Deux corrections ont déjà été appliquées : **3 Mbaud** au lieu de 1 (le diviseur tombe juste des deux côtés — 60/20 sur le STM32, 48/16 sur le PL011 du Pi, qui est son maximum) et une **attente serrée** dans le transport à la place d'un ``osDelay(1)`` qui arrondissait chaque tour à la milliseconde. Ensemble : 21,0 → 32,5 Hz. **Fait.** Le **cache de données** est maintenant actif, avec un ``MPU_Config`` écrit à la main : QSPI en écriture immédiate, AXI SRAM en écriture différée, et 8 Kio non cacheables à 0x30000000 pour les tampons DMA. La boucle micro-ROS est passée de 32,5 Hz à environ 130, et l'odométrie tient ses 50 Hz, calées sur les pas d'asservissement. Ce qui suit reste pour mémoire — c'est le raisonnement qui a conduit là, et les pièges valent encore pour toute nouvelle zone DMA : .. code-block:: text avant : SCB->CCR = 0x00060200 bit 17 I-cache = 1 bit 16 D-cache = 0 Le cache d'**instructions**, lui, est actif — inutile donc de déplacer du code en ITCM, il est déjà servi depuis le cache. C'est le côté **données** qui coûte : sérialiser 750 octets sans cache, ce sont des centaines d'accès à pleine latence en AXI SRAM. .. warning:: **Ce n'est pas un interrupteur.** Avec le D-cache actif, tout tampon DMA doit être traité : sinon le DMA lit des données restées en cache, ou écrit des données que le processeur ne voit pas. C'est la famille de pannes silencieuses qui a déjà coûté cher trois fois sur ce projet. Deux voies : marquer ``RAM_D2`` non-cacheable par le MPU — mais le ``MPU_Config`` généré est justement désactivé, sa région de 4 Go en ``NO_ACCESS`` empêchant l'exécution depuis la QSPI — ou ajouter les opérations de maintenance à chaque transfert. La raison d'origine de le désactiver a disparu : les structures étaient relues à chaud au ST-LINK, ce que ``/diagnostics`` a remplacé. 9. En attente de matériel ------------------------- **Un ventilateur 4 fils.** Le 2 fils actuel ne se ralentit pas : son électronique de commutation est alimentée par les deux mêmes fils que le moteur, et décroche dès 15 % de temps mort. Mesuré le 2026-09-26 : il tourne à 100 %, cale à 95 %, et même une montée en régime complète de six secondes avant de redescendre à 70 % ne le maintient pas. Un 4 fils a une entrée PWM dédiée et se réglerait de 20 à 100 %. **Les trois autres robots**, attendus vers le 2026-10-10. Tout ce qui a été outillé ce mois-ci — configuration sauvegardable, calibration, point d'entrée unique piloté par le nom d'hôte — vise à ce que les mettre en service ne soit qu'une répétition. **Les moteurs à courant continu et leurs encodeurs** ne concernent pas RatBot. La carte est générique et les porte pour un autre robot ; ``motor_dc.c`` et ``encoder.c`` restent des squelettes tant qu'aucun projet ne les demande. 10. Le capteur de distance avant (ToF) -------------------------------------- Le lidar balaie à ~20 cm du sol et ne voit pas le pied des tables à pied central. Un **VL53L5CX** (8 × 8 zones) à l'avant, à 10 cm du sol, sur **J4** (I2C2), décidé le 2026-10-14. Contrat et choix : :doc:`../ros2/topics`. 1. **Le contrat et la simulation — fait le 2026-10-14.** Image 8 × 8 en millimètres, capteur imité dans Gazebo, conversion en nuage avec reconnaissance du sol, couche voxels dans Nav2, décor ``tables``. En chemin : les cartes de coûts de Nav2 écoutaient ``…/local_costmap/scan`` au lieu de ``/ratbot_N/scan`` et n'avaient **jamais reçu le lidar**. 2. **La carte publie l'image — fait et mesuré le 2026-10-14.** Tâche ``tof`` à 15 Hz, image d'essai (``tof_frame_fake`` : 3 m et plus, hors de ce que Nav2 marque), ``ros_tof`` qui la publie, bloc ``tof`` des diagnostics en WARN. Conversion des statuts du pilote et retournements prêts et testés au banc (``tof_frame.c``). Mesuré sur ratbot-1, même code avec et sans ToF : la boucle passe de 27,4 à 23,2 Hz, l'odométrie de 27,3 à 23,2 Hz, les IMU non publiées de 100 à 215 par minute ; ~7 ms de processeur par image publiée, un coût fixe par publication plus que lié à la taille. **La boucle tournait déjà à 27 Hz sans le ToF**, contre 132 Hz après l'activation du cache. Cause trouvée le jour même : le code de publication relu depuis la QSPI. Placé en ITCM, la boucle tourne à **175 Hz avec l'image ToF**, l'odométrie à 50 Hz sans pas sauté (:doc:`../firmware/application`, « Le chemin de publication en ITCM »). 3. **Le capteur — pilote écrit le 2026-10-20, capteur pas encore vu.** Pilote ST ULD 2.0.1 (firmware MM1.8, BSD-3) recopié tel quel dans ``App/drivers/vl53l5cx/``, plateforme à nous sur I2C2 ; tâche ``tof`` seule propriétaire d'I2C2, TMP102 compris (vérifié sur la carte : 24,25 °C, 0 erreur) ; 8 × 8, 15 Hz, continu, cible la plus proche ; retentative toutes les 5 s ; balayage du bus dans le diagnostic ; banc ``test_tof.c``. Le module de ratbot-1 ne répond à **aucune** adresse (seul 0x48 répond) : alimentation à mesurer, ou module à essayer sur la Pi (``i2cdetect -y 1``). **Vu le même jour** : la sérigraphie du connecteur était inversée. 15,2 images/s. Restent : l'orientation vérifiée (main devant un coin). **I2C2 en DMA le même jour** : la tâche ToF passe de 22 à 6,4 % du processeur, la boucle micro-ROS de 11,3 à ~5 ms ; 0 repli, 0 erreur sur 1 400 images ; puce stable à 65-66 °C en mode continu, et 57-58 °C une fois la pompe de charge de l'émetteur coupée, sans rien perdre de près (mur à 30 cm : 302 mm, bruit 1,2 mm au lieu de 1,5). Piste si besoin : demander « image prête ? » moins souvent (100 fois/s pour 15 images, en attente active). Ce qui était prévu : I2C2 tourne déjà à **400 kHz** (``firmware-fixes``), le minimum de la fiche technique (400 kHz à 1 MHz) ; tâche dédiée à basse priorité, seule propriétaire d'I2C2, pour que la lecture de l'IMU n'attende jamais ; pilote ST (envoi de son micrologiciel de 86 Ko au démarrage, ~2 s) ; image réduite (distance et statut seulement). LPn n'est pas câblé : pas de remise à zéro matérielle, la carte doit continuer sans le capteur et le signaler. Puis, le capteur monté : sa **pose mesurée** dans l'URDF, et le mode de distance (radial ou perpendiculaire) tranché devant un mur. **Hauteur du capteur, essais en simulation du 2026-10-14.** 4, 5, 7 et 10 cm évitent les pieds de table aussi bien ; plus bas, en revanche, le sol reconnu tolère moins les irrégularités (tolérance relative : 5 mm de seuil font 12 % à 4 cm). Prévu à 10 cm. Ce qui faisait la différence était côté Nav2 : marge de 25 cm au lieu de 15 (socle en disque évité à 38 cm au lieu de 20-28). 11. Navigation : ce que le lidar enfin reçu a révélé ---------------------------------------------------- Depuis que les cartes de coûts reçoivent le lidar (2026-10-14), trois défauts cachés sont apparus et ont été corrigés le jour même : le chemin recalculé chaque seconde basculait d'une route à l'autre (désormais recalculé seulement s'il devient invalide) ; les roues simulées comptaient 14 % de rotation de trop (collisions en sphères) ; le parquet des décors n'était qu'un visuel surélevé. **Reste ouvert** : le labyrinthe ``hampton_signature`` (couloirs de 0,6 m en arcs) échoue dans les virages serrés — le suivi de trajectoire coupe la courbe et détecte le mur. Pistes : un contrôleur qui contourne au lieu de s'arrêter (MPPI, ou DWB), ou une empreinte et un pivot pris en compte par le planificateur (Smac). Et **aucune navigation n'a encore été faite sur le vrai robot**. Comparaison du 2026-10-14 (cahier de labo) : Rotation Shim échoue à la porte de 0,7 m ; MPPI, avec les réglages standard puis une première correction, s'immobilise près des obstacles et échoue aussi à la porte. **Pure Pursuit reste en place** : il réussit la porte et les deux tables. Smac 2D avec un disque de 30 cm pour le planificateur : aussi bon à la porte et aux tables, mais refuse les ouvertures de 38 cm du labyrinthe. **Puis la vraie cause du labyrinthe** : sa carte, faite au SLAM avec les roues simulées fausses, réduisait un passage de 38 cm à 20 et collait le but à un mur. Cartes des décors redessinées d'après le plan (outil ``ratbot_gazebo.world_map``) : le labyrinthe passe 1 fois sur 2 avec Pure Pursuit. Les comparaisons de contrôleurs ci-dessus ont été faites avec les anciennes cartes ; leurs résultats au labyrinthe et à la porte sont à refaire avant de conclure. Reste, pour les ouvertures de 38 cm prises en virage : Smac State Lattice. 12. Conduire au clavier, apprendre un trajet — fait le 2026-10-15 ----------------------------------------------------------------- ``ratbot key N`` (clavier, même voie que la manette) et ``ratbot route record|follow|list`` : on conduit le robot pendant que sa pose est notée, puis Nav2 refait le trajet, par points ou sur la ligne exacte. Essayé en simulation (écart moyen 6 et 3 cm). **À essayer sur le vrai robot.** Détails : :doc:`../ros2/map`, « Trajets appris ». 13. Fiabiliser la navigation, couche par couche ----------------------------------------------- Décidé le 2026-10-15 : les réglages successifs de la section 11 ont été jugés sur deux passages chacun, avec des cartes fausses et sans mesure de la localisation. On reprend **de bas en haut**, chaque couche validée par un critère chiffré, fixé avant l'essai, avant de passer à la suivante. Tout en simulation jusqu'à l'étape 5. 0. **Un banc d'essai** qui rejoue les mêmes épreuves, chaque passage dans une simulation neuve, mesuré contre la vérité de Gazebo : ``ratbot navtest`` (:doc:`../ros2/simulation`, « Banc de navigation »). **Fait le 2026-10-15.** En le préparant : le ``behavior_server`` portait cinq paramètres écrits pour Iron (``local_costmap_topic``, ``local_frame``…), ignorés sans un mot par Humble ; ses dégagements cherchaient le robot dans ``map`` sur une carte de coûts tenue dans ``odom``. Corrigé, et vérifié : chaque clé de ``nav2.yaml`` et ``amcl.yaml`` est désormais déclarée par son nœud. 1. **Localisation** : odométrie, EKF, AMCL contre la vérité. Critère : erreur de position sous **5 cm** pendant les épreuves. **Atteint en simulation le 2026-10-15** : AMCL prêtait à l'odométrie un bruit (``alpha`` 0,2) qui la brouillait ; à 0,02, erreur maximale de 3,8 à 5,0 cm au lieu de 7 à 9, et le robot ne frôle plus le pied de table (il se croyait ailleurs). Valeur du vrai robot à mesurer. 2. **Cartes** : refaire une cartographie (SLAM) en simulation et la comparer au plan. Critère : carte de ``rooms`` à 5 cm près du plan. 3. **Marques des capteurs** dans les cartes de coûts : à moins de 5 cm des vrais obstacles. 4. **Planificateurs et contrôleurs** comparés sur ces bases saines (NavFn, Smac 2D ou State Lattice ; Pure Pursuit ou MPPI), 10 passages par combinaison. Critères : porte et tables 10/10 sans contact ; retour depuis la porte 10/10. Le **labyrinthe** n'est plus un critère : c'est le test final, passé à la toute fin (décision du 2026-10-15). 5. **Le vrai robot, progressivement** : localisation seule en poussant le robot ; carte du salon ; buts courts ; porte ; table ; trajet appris. Une relecture extérieure conseillait d'avancer le premier point ; on reste en simulation pour l'instant (robot absent). **Commencé le 2026-10-16** dans un enclos de planches de 180 × 95 cm : carte faite par le robot, localisation jamais perdue (3–10 cm), premiers buts autonomes, tour des coins et contournement d'une boîte, tous réussis sans manœuvre de dégagement. Restent : la valeur d'``alpha`` du vrai robot, une pièce réelle, la porte, la table, le trajet appris. **Le 2026-10-19**, dans un couloir : première exploration autonome du vrai robot (carte ``couloir``), puis quatre demi-tours à 20 cm d'un mur avec l'empreinte en disque, sans contact. 14. Plusieurs robots : cartes privées par défaut, partage en option ------------------------------------------------------------------- **Le constat (2026-10-19, en préparant une démonstration).** Les cartes ne suivent pas une règle, mais trois décisions prises à des moments différents : - ``ratbot slam N`` donne à chaque robot **sa** carte, ``/ratbot_N/map`` ; - ``ratbot nav N CARTE`` charge une carte **commune**, ``/map``, sur laquelle toute la flotte se localise. Lancer ``nav`` pour un second robot avec une autre carte démarrerait un second serveur commun, en conflit avec le premier ; - ``ratbot explore N``, posé sur la navigation, écrit donc sur la carte commune : on n'explore qu'**avec un robot à la fois**. D'où deux formes de ``map save`` (avec et sans numéro), qu'il faut connaître pour s'en servir. Le paquet de fusion ``multirobot_map_merge`` est dans le dépôt (``m-explore-ros2``), mais écarté de la compilation (``COLCON_IGNORE``, voir :doc:`../ros2/map`). **Le principe retenu : privé par défaut, partage explicite.** Deux usages de recherche, servis par le même code : - **des agents indépendants** : chaque robot n'a que sa carte et ce qu'il a vu lui-même. Ce qu'un robot sait d'un autre passe par une **communication explicite**, qui est un acte : que dire, à qui, quand, et que faire d'un message perdu ou d'une information périmée (un robot croit une porte ouverte parce qu'il l'a vue ouverte il y a une heure). C'est le terrain de la planification épistémique ; - **une équipe qui coopère** : les cartes sont fusionnées en une carte commune, et les robots se répartissent le travail. Une carte partagée par défaut ruinerait le premier usage, en silence : une expérience sur des agents indépendants dont les cartes fuient l'une dans l'autre ne mesure plus ce qu'elle croit mesurer. **Ce que ça donne :** 1. **chaque robot a toujours sa propre carte**, dans son espace de noms, avec ``slam``, ``explore`` et ``nav`` : .. code-block:: bash ./scripts/ratbot nav 1 salon # le robot 1 sur SA copie de salon ./scripts/ratbot nav 2 salon # le robot 2 sur la sienne ./scripts/ratbot nav 3 cuisine # le robot 3 sur une autre carte Le serveur de carte et AMCL passent dans ``/ratbot_N`` ; la carte commune ``/map`` ne sert plus que lorsqu'on demande le partage ; 2. **le partage est une option**, ``--shared`` : .. code-block:: bash ./scripts/ratbot explore 1 2 # deux agents indépendants ./scripts/ratbot explore 1 2 --shared # une équipe, carte fusionnée ./scripts/ratbot nav 1 2 3 salon --shared Avec ``--shared``, ``map_merge`` fusionne en continu les cartes des robots dans ``/map``, et chaque ``explore_lite`` choisit ses frontières sur la carte **fusionnée**, pour que deux robots n'explorent pas la même zone. Les positions de départ sont connues en simulation ; sur le vrai parc, elles devront l'être aussi, ou être estimées par la fusion. Sans ``--shared``, deux robots peuvent explorer la même pièce en double : c'est voulu ; 3. **la provenance est écrite dans la carte**, et non dans son nom. Une carte décrit un lieu, pas un robot : ``salon_1`` deviendrait faux dès qu'on la partage. ``map save`` ajoute au ``.yaml`` : .. code-block:: yaml robot: 1 method: explore # slam, explore ou merge merged_from: [] # [1, 2, 3] pour une carte fusionnée date: 2026-10-19T20:33 et ``map list`` l'affiche (``salon robot 1, explore, 2026-10-19``), comme ``route list`` le fait déjà pour les trajets ; 4. **une seule règle pour enregistrer** : ``map save NOM`` enregistre « la » carte, sans numéro, dès qu'elle est unique : .. list-table:: :header-rows: 1 :widths: 40 60 * - ce qui tourne - ``map save salon`` * - un seul robot (``slam 1``, ``explore 1``) - la carte de ce robot * - plusieurs robots, ``--shared`` - la carte **fusionnée** * - plusieurs robots, indépendants - **demande laquelle** : ``map save 1 salon``, ou ``--all``, qui les enregistre toutes (le nom porte alors le robot, pour éviter le conflit) * - rien - le dit Le numéro ne sert que pour vouloir volontairement un morceau précis, par exemple la carte d'un robot quand la fusion se passe mal. Une carte existante n'est jamais écrasée sans confirmation, comme aujourd'hui ; 5. **un canal de communication entre robots, fourni ; son contenu, au chercheur.** RatBot donne une interface standard pour qu'un robot envoie un message à un autre (ou à tous), datée et attribuée, que l'on peut limiter en portée ou en débit, ou dont on peut simuler des pertes pour étudier la robustesse. Ce qui est dit, et quand, reste à la charge de chaque projet : ce n'est pas à la plateforme de faire ce travail ; 6. **une épreuve au banc** à 2 et 3 robots, dans les deux modes : temps pour couvrir le décor, qualité de la carte fusionnée contre le plan, aucun contact entre robots, et, en mode indépendant, aucune information qui passe d'une carte à l'autre. En simulation d'abord, avant l'arrivée des robots 2 et 3. C'est aussi la matière de la section multi-robots de l'article.