Tutoriel · Débutant · 17 min de lecture

Machines à états finis pour le comportement robotique

Machine à états finis pour un robot : transitions explicites, temporisations, récupération, mises à jour non bloquantes, sécurité testable.

Les programmes de robots commencent souvent comme une boucle pleine de if : suivez la ligne sauf si un obstacle est proche, tournez jusqu’à dégagement, arrêtez quand la batterie est basse, et récupérez somehow quand un capteur dépasse son délai. Quand les comportements grandissent, la même condition peut déclencher des commandes contradictoires.

Une machine à états finis (FSM) rend le comportement actif explicite. À tout instant le robot est dans un état ; événements et gardes déterminent quand il transite vers un autre. Le motif est assez petit pour un Arduino et assez structuré pour être testé.

Séparez état, événement et action

Un état décrit ce que fait le robot dans la durée : FOLLOW_LINE, AVOID, SEARCH ou FAULT. Un événement est observé : un obstacle entre en zone d’arrêt, la ligne est perdue, une temporisation expire, ou l’opérateur appuie sur stop. Une action est la sortie : fixez les vitesses de roues, effacez un intégrateur, ou levez une alerte.

Évitez de créer des états pour des faits instantanés. OBSTACLE_DETECTED est généralement un événement ou une garde ; AVOIDING_OBSTACLE est un état car il persiste pendant que le robot exécute un comportement.

Commencez par une table de transitions avant le code :

État courant Garde ou événement État suivant Action d’entrée
Idle Démarrage demandé et capteurs valides Follow Réinitialiser le contrôleur
Follow Obstacle dans le seuil d’arrêt Avoid Arrêter la traction avant
Follow Ligne manquante depuis 200 ms Search Mémoriser le dernier côté
Avoid Chemin libre depuis 500 ms Follow Réinitialiser le contrôleur
Tout état actif Temporisation capteur Fault Arrêter les moteurs
Fault Reset opérateur et santé valide Idle Effacer la faute

Le tableau expose les cas manquants et rend visible la priorité de sécurité.

Un diagramme d'états à quatre boîtes en cycle : DRIVE, STOP, SCAN et TURN. Des flèches les relient en ordre, chacune légendée de la condition de transition : obstacle à moins de vingt centimètres de DRIVE vers STOP, arrêté de STOP vers SCAN, balayage fini de SCAN vers TURN, et cap atteint de TURN vers DRIVE.
Un seul état actif à la fois, et chaque transition est une condition nommée. L'alternative est un nid de conditionnels sur lequel personne ne peut répondre. Télécharger le SVG

Gardez la boucle de mise à jour non bloquante

Un état doit effectuer une petite mise à jour et revenir. Un delay() de deux secondes dans AVOID empêche le programme de voir une demande d’arrêt ou une panne capteur pendant ces deux secondes.

enum class State { Idle, Follow, Avoid, Search, Fault };

State state = State::Idle;
unsigned long enteredAt = 0;

void transitionTo(State next) {
  state = next;
  enteredAt = millis();
}

void loop() {
  Sensors s = readSensors();

  if (!s.healthy && state != State::Fault) transitionTo(State::Fault);

  switch (state) {
    case State::Idle:   updateIdle(s); break;
    case State::Follow: updateFollow(s); break;
    case State::Avoid:  updateAvoid(s); break;
    case State::Search: updateSearch(s); break;
    case State::Fault:  updateFault(s); break;
  }
}

Chaque mise à jour lit l’instantané capteur déjà capturé et écrit une seule commande d’actionneurs cohérente. Cela évite que différents états lisent des valeurs changeantes à différents moments dans un même cycle.

Placez les transitions à des frontières claires

Choisissez une règle de propriété. Une conception embarquée simple laisse chaque état demander ses transitions sortantes, tandis qu’un contrôle de sécurité de haut niveau peut outrepasser tout état actif.

void updateFollow(const Sensors& s) {
  if (s.obstacleMm < stopThresholdMm) {
    setWheels(0, 0);
    transitionTo(State::Avoid);
    return;
  }

  if (s.lineMissingForMs > 200) {
    transitionTo(State::Search);
    return;
  }

  followLine(s.lineError);
}

Revenez aussitôt après une transition pour que l’ancien état ne puisse pas aussi commander les actionneurs. À l’entrée d’un état de commande, réinitialisez l’historique qui ne doit pas fuir entre comportements, comme un intégral PID ou un échantillon dérivé.

Utilisez les actions de sortie avec parcimonie. La plupart du nettoyage peut se faire dans la fonction centralisée transitionTo() ou au chemin d’entrée de l’état suivant. Des fonctions séparées onEntry, onUpdate et onExit ne deviennent utiles que quand plusieurs états en ont vraiment besoin.

Utilisez temporisations et hystérésis comme gardes

Les événements physiques sont bruités. Si AVOID sort dès que la distance repasse au-dessus du même seuil d’entrée, le bruit de mesure peut causer un broutage rapide d’états. Utilisez deux seuils ou un temps de persistance : entrez sous 250 mm, mais ne sortez qu’après 350 mm tenus pendant 500 ms.

Chaque état qui attend un progrès exige une temporisation. Si le robot entre en SEARCH sans jamais trouver la ligne, il ne doit pas tourner indéfiniment. Transitez vers FAULT ou une récupération bornée après un intervalle testé.

Basez les minuteries sur le temps depuis l’entrée ou depuis le dernier progrès. Utilisez des soustractions comme now - enteredAt >= timeout, correctes au débordement du compteur non signé quand les types sont cohérents.

Donnez priorité aux transitions de sécurité

Définissez ce qui peut tout interrompre : arrêt d’urgence, données capteur critiques invalides, faute du pilote moteur, ou temporisation de chien de garde. Évaluez ces conditions avant les transitions propres au comportement et rendez explicite la sortie sûre.

Un état FAULT doit verrouiller quand une récupération automatique pourrait redémarrer un mouvement dangereux. Exiger un reset opérateur après disparition de la cause est souvent plus sûr que de resauter directement en FOLLOW.

Ne laissez pas deux machines à états indépendantes écrire les mêmes commandes moteurs sans règle d’arbitrage. Si navigation et gestion batterie touchent toutes deux au mouvement, un seul propriétaire doit choisir la commande finale et appliquer les limites de sécurité.

Testez les transitions sans faire tourner les moteurs

Déplacez les décisions dans des fonctions qui acceptent un instantané capteur et un temps, puis rendent un état suivant ou une commande. Nourrissez une séquence d’instantanés synthétiques :

  1. Ligne valide et chemin libre restent en FOLLOW.
  2. Un seul échantillon proche bruité ne déclenche pas si une persistance est exigée.
  3. Un obstacle proche soutenu entre en AVOID et commande d’abord l’arrêt.
  4. Un chemin libre tenu pendant l’intervalle de sortie revient en FOLLOW.
  5. Une temporisation capteur atteint FAULT depuis chaque état actif.

Journalisez les changements d’état comme ancien → nouveau, l’événement déclencheur, et l’horodatage. Les journaux continus par boucle noient les transitions dans le bruit ; les journaux de transitions expliquent pourquoi le comportement a changé.

Sachez quand une FSM cesse d’être le modèle le plus simple

Une FSM convient quand le robot a un nombre modeste de modes mutuellement exclusifs. Elle devient difficile quand les états se multiplient pour représenter des combinaisons comme « naviguer tout en surveillant la batterie tout en réessayant la localisation ». Le nombre de transitions peut croître plus vite que les comportements eux-mêmes.

Les arbres de comportement composent des actions réutilisables avec structure de séquence, repli et récupération, d’où leur usage par des systèmes de navigation comme Nav2 pour des tâches complexes. N’introduisez pas un cadriciel d’arbres de comportement pour quatre états. Migrez quand la table de transitions devient vraiment difficile à relire ou que les comportements exigent une réutilisation hiérarchique.

Diagnostiquer les pannes de conception courantes

  • Le robot ignore stop en tournant : un état contient une boucle bloquante ou un délai.
  • Les moteurs reçoivent deux commandes par cycle : le code de transition continue après changement d’état.
  • La machine broute : des gardes bruitées sans hystérésis ni persistance.
  • La récupération ne finit jamais : l’état n’a ni condition de progrès ni temporisation.
  • Une faute redémarre aussitôt le mouvement : les fautes de sécurité ne sont pas verrouillées ou les conditions de reset sont trop faibles.
  • Tester exige tout le robot : décisions et accès matériel sont mélangés au lieu d’instantanés capteurs et sorties de commandes.

Le simulateur d’évitement d’obstacles fournit une séquence de comportements utile à modéliser : croisière, braquage autour d’une menace, arrivée, ou arrêt quand aucun mouvement sûr ne reste. Le simulateur de résolution de labyrinthe montre une autre séparation entre détection, mise à jour de carte, planification, et mouvement d’une case. Le robot mini sumo est le test le plus net de la règle de sécurité verrouillée ci-dessus : son réflexe de bord doit préempter tout autre état, depuis tout état, sinon le robot se sort lui-même de l’arène.

Explorer le graphe

Fait partie de ces réalisations

Les projets et parcours d'apprentissage qui incluent ce tutoriel.

Pour aller plus loin

Références