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 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.

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.

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/<pid>/maps — et /dev/shm était vide :

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.

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 :

[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 :

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 :

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.

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.

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é :

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 :

# 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.

# 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="<adresse de l hote>: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 :

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.

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