La découverte DDS : qui voit qui ================================== .. important:: **C'est la couche à laquelle personne ne pense, jusqu'au jour où elle lâche.** Quand elle tombe, aucun message d'erreur n'apparaît : les nœuds démarrent, publient, et les compteurs restent à zéro à l'autre bout. On soupçonne alors la QoS, le firmware, le matériel — et on cherche des heures. La page :doc:`bringup`, section *Réseau*, traite de l'**adressage**. Celle-ci traite de la **découverte** et du **transport** : comment deux nœuds apprennent l'existence l'un de l'autre, puis par où passent leurs données. Le principe ------------- ROS 2 n'a pas d'annuaire central. Chaque participant s'annonce et écoute les annonces des autres. Deux nœuds qui se découvrent négocient ensuite leurs QoS, et seulement alors les messages circulent. Quatre faits suffisent à comprendre tout ce qui suit : 1. **Le transport dépend de la topologie.** Fast DDS, l'implémentation par défaut sous Humble, fait passer les données entre deux processus d'une **même machine** par la **mémoire partagée** (``/dev/shm``), et entre deux machines par l'**UDP**. Quand la mémoire partagée est utilisable, elle est prioritaire et l'UDP n'est pas utilisé pour ce couple. 2. **Les segments de mémoire partagée s'ouvrent par leur NOM**, des fichiers ``fastrtps_*`` dans ``/dev/shm``. Un processus déjà en marche garde ses projections même si le fichier disparaît ; un nouvel appariement, lui, ne trouve plus rien. 3. **Un participant lit l'état des interfaces réseau à sa création**, et ne le relit pas. Ce qui change après coup ne l'atteint pas. 4. **Rien ne signale un échec d'appariement ni de livraison.** ``ros2 topic info`` continue d'afficher les deux extrémités, en QoS compatibles. .. _decouverte-incident: La panne de septembre 2026 : logind effaçait la mémoire partagée ------------------------------------------------------------------ Le symptôme ~~~~~~~~~~~~~ Premier essai du pilotage à la manette, le 2026-09-26 : le robot ne bougeait pas, et le voyant restait éteint. .. list-table:: :header-rows: 1 :widths: 48 52 * - mesure - ce qu'on en concluait * - la chaîne de commande **sur la Pi** publie 2086 → 401 → 402 → 394 messages, jusqu'à ``base/cmd_vel_safe``, tous à 0,06 m/s - la chaîne ROS calcule juste * - ``commands_received`` de la carte reste à **0** - la carte ne reçoit rien * - la même consigne publiée **depuis le PC** : le compteur monte - la carte reçoit très bien d'ailleurs * - ``ros2 topic info --verbose`` : 1 éditeur / 1 abonné, **best-effort des deux côtés** - l'appariement est bon * - ``wheels_online`` = 3, bus Dynamixel 125017 échanges **0 erreur**, batterie 11,5 V - le matériel est sain **Tout avait l'air juste, et rien n'arrivait.** Une relance de la pile réparait tout — jusqu'au démarrage suivant. La cause ~~~~~~~~~~ Par défaut, ``systemd-logind`` applique ``RemoveIPC=yes`` : dès que la **dernière session** d'un utilisateur se ferme, il efface **tous** ses objets de communication inter-processus, dont la mémoire partagée de ``/dev/shm``. Seuls les comptes système (uid < 1000) sont épargnés. Or : - la pile tourne en service sous l'utilisateur ``ratbot``, **uid 1000** — concerné ; - ce compte n'a pas de session persistante (``Linger=no``) ; - chaque connexion SSH ouvre puis ferme une session ``ratbot``. **Chaque déconnexion SSH effaçait donc les segments de la pile en marche.** Par les faits n° 1 et 2 : les couples déjà établis continuaient de marcher sur leurs projections, mais tout appariement **local** fait ensuite cherchait des segments disparus et ne délivrait rien. Un PC distant passait par l'UDP et n'était pas touché. La preuve ~~~~~~~~~~~ Pendant la panne, les segments étaient encore projetés par **chacun** des processus de la pile, mais marqués effacés dans ``/proc//maps`` — et ``/dev/shm`` était **vide** : .. code-block:: text micro_ros_agent /dev/shm/fastrtps_port26661 (deleted) status_manager /dev/shm/fastrtps_port26663 (deleted) cmd_vel_gate /dev/shm/fastrtps_port26665 (deleted) Le journal de ``logind`` montrait des sessions ``ratbot`` fermées juste avant la dernière modification de ``/dev/shm``. Puis l'expérience à **une seule variable**, le 2026-09-27 : même démarrage, serveur de temps allumé, aucune relance de la pile. .. list-table:: :header-rows: 1 :widths: 22 26 26 26 * - ``RemoveIPC`` - ``/dev/shm`` - voyant - ``commands_received`` * - ``yes`` (défaut) - **vide**, segments effacés - **éteint** - **0** * - ``no`` - plein, aucun effacé - **bleu** - **223** Et avec ``RemoveIPC=no``, une vingtaine de sessions SSH ouvertes et fermées pendant le démarrage n'ont rien effacé. Le correctif ~~~~~~~~~~~~~~ ``deploy/logind-ratbot.conf``, installé dans ``/etc/systemd/logind.conf.d/ratbot.conf`` par ``./scripts/ratbot services`` : .. code-block:: ini [Login] RemoveIPC=no ``logind`` ne relit sa configuration qu'à son démarrage : **il faut redémarrer la Pi** après l'installation. Le script le rappelle, et ne relance pas ``logind`` lui-même — ça peut fermer les sessions ouvertes. Deux autres correctifs existaient, écartés : ``loginctl enable-linger ratbot`` marche mais par effet de bord, et un compte système pour la pile obligerait à déplacer le dépôt, les droits sur le port série et le workspace. Pourquoi tout avait l'air contradictoire ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Le déclencheur est une **déconnexion SSH**, pas un événement du robot. D'où des résultats qui semblaient se contredire d'un essai à l'autre : .. list-table:: :header-rows: 1 :widths: 46 54 * - observation - explication * - le PC délivrait toujours - il passe par l'UDP, pas par ``/dev/shm`` * - une relance de la pile réparait - elle recrée des segments neufs, et les couples s'établissent avant la prochaine déconnexion * - ça marchait quand le serveur de temps était **éteint** - ``ratbot-time`` relançait alors la pile toute seule, vers 57 s — et masquait la panne * - deux processus lancés **à la main** sur la Pi se voyaient et s'échangeaient des données - ils créaient leurs segments **après** l'effacement * - les **services** restaient invisibles et sourds depuis un shell - leurs segments à eux avaient été effacés * - l'hypothèse ``RemoveIPC`` a d'abord été **écartée** - le test était faussé : une connexion SSH en arrière-plan était restée ouverte, donc la « dernière session » ne se fermait jamais .. warning:: **La leçon de méthode.** Un test qui innocente doit reproduire *toutes* les conditions de la panne. Ici, un terminal oublié ouvert suffisait à empêcher l'effacement — et a coûté une nuit. Les fausses pistes ~~~~~~~~~~~~~~~~~~~~ Chacune a été écartée par une expérience. À ne pas reproposer sans élément nouveau : .. list-table:: :header-rows: 1 :widths: 30 70 * - piste - ce qui la réfute * - la **QoS** - depuis le PC, un éditeur fiable **et** un éditeur best-effort atteignent la carte ; sur ``status/led`` les deux côtés sont en best-effort * - l'**ordre de démarrage** - ``wait_for_board`` trouve l'abonné de la carte en 1,5 s avant de lancer ``status_manager`` : le voyant restait éteint quand même * - le **saut d'horloge** - serveur de temps allumé, horloge juste **avant** la pile, un seul démarrage : la panne restait * - le **pilotage de bande** Wi-Fi - défaut réel, corrigé côté box — voir plus bas — mais la panne persistait * - le démon ``ros2``, Docker, le domaine ROS, le matériel - ``--no-daemon`` idem ; aucun conteneur (l'image Docker ne sert qu'à essayer le projet sans installer ROS) ; domaine ``77`` partout ; roues, bus et batterie sains Le drapeau ``MULTICAST`` sur ``lo`` ------------------------------------- ``lo`` n'a pas le drapeau ``MULTICAST`` par défaut sous Linux. ``deploy/ratbot-loopback-multicast.service`` le pose au démarrage, **avant** la pile (fait n° 3). L'expérience qui l'a fait adopter semblait nette : drapeau posé sans toucher aux services, et ``status_manager`` puis ``ratbot_controller`` sont apparus aussitôt dans ``ros2 node list`` lancé sur la Pi. .. note:: **Son utilité réelle reste à revérifier.** Cette expérience a été faite pendant que ``logind`` effaçait la mémoire partagée : la découverte locale devait alors se rabattre sur l'UDP multicast de la boucle locale, qui a besoin de ce drapeau. Avec ``RemoveIPC=no``, il est possible qu'il ne serve plus à rien. Il ne coûte rien, donc on le garde — mais la question est ouverte. .. code-block:: bash systemctl is-active ratbot-loopback-multicast # doit repondre « active » ip link show lo | grep -o MULTICAST # doit repondre « MULTICAST » Le Wi-Fi qui change de radio sous les pieds --------------------------------------------- Défaut **distinct**, trouvé le même soir. La box diffusait le même nom de réseau sur ses radios 2,4 et 5 GHz, et faisait du **pilotage de bande** (802.11v / MBO) : une quarantaine de secondes après l'association, elle ordonnait à la Pi de changer de point d'accès. .. code-block:: text 20:47:06 la pile demarre 20:47:19 wlan0: MBO-TRANSITION-REASON reason=9 20:47:19 wlan0: Lost carrier / DHCPv6 lease lost 20:47:23 wlan0: Associated with 92:55:1f:b6:e5:6d <- BSSID different Par le fait n° 3, un participant né avant ce basculement peut garder des adresses qui n'existent plus — et ``Restart=always`` ne le verrait pas, puisqu'il ne meurt pas. Ce n'était **pas** la cause de la panne manette, mais un robot qui change de radio en pleine manip reste un risque. **Correctif, côté box :** séparer les bandes, pour que chaque radio ait son propre nom. Sur la Bbox du labo, l'option s'appelle *« Séparer les bandes 2.4, 5 et 6 GHz »*, et elle a donné : .. list-table:: :header-rows: 1 :widths: 40 20 40 * - nom du réseau - bande - remarque * - ``Bbox-219DC5A5`` - 2,4 GHz - **garde le nom de base** : les robots s'y reconnectent sans rien changer * - ``Bbox-219DC5A5-5GHz`` - 5 GHz - plus rapide (433 contre 72 Mbit/s mesurés) * - ``Bbox-219DC5A5-6GHz`` - 6 GHz - hors de portée d'une Pi 4, qui est en Wi-Fi 5 .. tip:: Avant de séparer les bandes, vérifier **quel nom garde la bande de base** : si le nom enregistré dans le robot disparaît de toutes les bandes, une Pi sans écran devient injoignable. Ici le nom de base est resté sur le 2,4 GHz — une chance, pas une garantie. La configuration réseau du robot ne déclare **aucune** interface Ethernet : un câble ne le récupérerait pas. Un repli ``eth0`` en DHCP serait un filet de sécurité utile. Diagnostiquer --------------- Devant « la Pi publie, la carte ne reçoit rien », dans cet ordre : .. code-block:: bash # 1. L'arbitre : la carte compte-t-elle les consignes recues ? ros2 topic echo /diagnostics | grep -A8 traction # 2. La memoire partagee est-elle encore la ? (sur la Pi) ls /dev/shm | grep -c fastrtps # 0 avec la pile en marche : suspect PID=$(pgrep -f 'micro_ros_agent serial' | head -1) sudo grep -c 'fastrtps.*(deleted)' /proc/$PID/maps # doit valoir 0 # 3. logind efface-t-il encore ? busctl get-property org.freedesktop.login1 /org/freedesktop/login1 \ org.freedesktop.login1.Manager RemoveIPC # doit valoir « b false » # 4. Le lien Wi-Fi a-t-il change depuis le demarrage de la pile ? journalctl -b | grep -iE "MBO-TRANSITION|Lost carrier|Associated with" Le compteur ``commands_received`` du bloc ``traction`` est l'arbitre : **il distingue « la consigne n'arrive pas » de « la consigne arrive et le robot ne bouge pas »**, deux pannes qui n'ont rien à voir. Pour le lire par programme, s'abonner en **best-effort** : un abonné fiable ne reçoit rien de ces éditeurs. Serveur de découverte : l'option de parc ------------------------------------------ Fast DDS sait remplacer le multicast par un **serveur de découverte** : chaque machine s'annonce en **unicast** à une adresse unique. .. code-block:: bash # sur la machine hôte (le binaire vient de ros-humble-fastrtps) fast-discovery-server -i 0 -p 11811 # sur toutes les autres export ROS_DISCOVERY_SERVER=":11811" **Aucune modification du firmware.** La carte parle XRCE-DDS à l'agent par la liaison série et ne fait pas de découverte DDS ; seul l'agent devient client du serveur. .. warning:: **Ce n'est pas une décision RatBot.** Un serveur de découverte est nécessairement *commun* : un participant qui en devient client ne découvre **plus** les participants restés en multicast. Le basculement est donc indivisible — il concerne toutes les machines du domaine à la fois. Or le domaine ``77`` est partagé avec d'autres machines du labo. Le serveur relève donc de l'infrastructure commune, et la décision se prend à ce niveau-là, pas dans RatBot. **RatBot ne doit pas en dépendre.** Le robot est intégré *par* un système hôte ; il ne s'accroche pas à lui. Un RatBot posé sur une table, hors de tout système hôte, doit continuer de fonctionner. .. note:: **Pourquoi le serveur NTP, lui, reste une dépendance acceptable.** RatBot est bien client du serveur de temps du testbed (192.168.1.190), et cela ne change pas. La différence n'est pas l'appartenance de la machine, c'est la **façon dont la dépendance tombe** : .. list-table:: :header-rows: 1 :widths: 24 38 38 * - - serveur NTP absent - serveur de découverte absent * - conséquence - la RTC sur pile de la carte garde l'heure, chrony se rattrape au retour - **aucun nœud n'en trouve un autre** : plus rien ne fonctionne * - nature - dégradation progressive - panne totale Une dépendance qui se dégrade proprement est acceptable ; une dépendance dont l'absence arrête tout ne l'est pas. C'est le même critère qui fait que la carte signale sans la Pi, et que le champignon est câblé et non logiciel. .. list-table:: :header-rows: 1 :widths: 34 66 * - ce qu'on gagne - ce qu'on paie * - le multicast disparaît du parc entier - point unique de défaillance **pour la découverte seulement** : deux nœuds déjà appariés continuent de se parler, mais un robot qui démarre pendant la panne ne trouve personne * - fiable là où le multicast Wi-Fi est bridé par le point d'accès — le problème qui attend un parc de **quatre robots** - ``ROS_DISCOVERY_SERVER`` à poser sur chaque machine * - le trafic de découverte cesse d'inonder le réseau - les outils ``ros2`` ne voient plus rien sans ``ROS_SUPER_CLIENT=true`` : *la* source de confusion de cette architecture