ARDUINO : un moteur de machine à états finis

Salut à tous

J'ai un projet en cours, assez complexe car le nombre d'états possibles est conséquent, ainsi que le nombre d'événements à gérer.

Cela m'a donné l'occasion de développer un petit moteur de machine à états finis basé sur une table d'états / transitions.

Il s'agit d'une librairie bien sûr.

@+
Henri

vous avez oublié YASM de @bricofoy dans les références :slight_smile: et la librairie de @bricoleau pour les boutons est sympa (en en français dans l'API) :slight_smile:

Je ne pense pas que l'approches de YASM soit comparable à la celle que j'ai exposé.

Dans YASM chaque fonction de l'automate prend elle-même la décision d'exécuter une action en fonction d'un événement. Brièvement il faut lire toutes les fonctions de l'automate pour s'y retrouver.

Ce n'est par vraiment ce que je recherchais.

Je préfère de loin concentrer les prises de décision et les changements d'état dans une table d'état / transition, ce qui permet permet de les centraliser, et facilite la lecture.
Dans mon approche, les fonctions de l'automate exécutent des actions et se contentent de ce rôle. Il suffit de lire la table d'état / transition pour suivre le déroulement. Aucune prise de décision n'est prise ailleurs, sauf si l'on en décide autrement.

J'aurais pu utiliser simpleBouton pour gérer l'aspect appui court / appui long.
Il serait bien de pouvoir disposer d'une repository (github, bitbucket), plus facile à dénicher qu'un zip perdu au fond d'un forum.

Mais l'oubli YASM est corrigé dans mon article :slight_smile:
merci

Mon point était effectivement juste d’ajouter cela à la liste - il y a effectivement différentes approches qui ont chacune leur avantages et inconvénients - perso je préfère m’en passer tout simplement et écrire à la main - je trouve que ça oblige à bien réfléchir au besoin

Oui faudrait faire une pétition pour que bricoleau ouvre un github ou équivalent :slight_smile:

perso je préfère m’en passer tout simplement et écrire à la main - je trouve que ça oblige à bien réfléchir au besoin

Mon parcours professionnel me dit le contraire. Tout dépend du nombre d’état et de transitions que l’on a à gérer. Quand cela dépasse le chiffre fatidique de 7 états et 7 transitions, il est difficile de s’y retrouver en écrivant du code classique (switch / case ou autre if / else).

Mais il est vrai que dans le monde ARDUINO il est rare que l’on ait affaire à des systèmes aussi complexes.
C’est justement pour résoudre un problème de ce genre que j’ai été obligé d’y passer (presque à contre-cœur car la retraite n’est pas faite pour nous rappeler notre profession). Je ne pensais pas y avoir affaire un jour dans le monde simple (en apparence) d’ARDUINO.
Sans un moteur je n’aurais jamais abouti aussi facilement.

Entre nous, ce topic posté ce matin affiche un nombre de vues assez peu commun : 422.
Je ne sais pas si vous voyez le même nombre. J'ai bazardé les cookies, aucun changement.
Le compteur déconne ou quoi ?
Ou alors les Arduinistes sont obsédés par les machines à états finis ?

:smiling_imp:

429 il semble effectivement !

OuI les machines à état c’est souvent la réponse à plein de petits codes

J’ai jamais rencontré de problèmes meme avec de nombreux états et transitions - si vous êtes capable de documenter votre structure et d’écrire un code qui fait l’abstraction pour la
Librairie je trouve que c’est pareil à la main... suffit d’être rigoureux dans l’approche formelle et pas juste cracher du code sans réfléchir - mais oui ça peut servir...

mon expérience c’est aussi que quand le cas est simple on n’en a pas besoin et quand c’est compliqué souvent une librairie va toujours se heurter à un cas particulier où vous aurez besoin d’un truc special - sauter à un état aléatoire parmi 10, passer plusieurs paramètres, effectuer de la recursion entre états (donc faut coder ré-entrant et dynamique ,...) et qu’on essayera ensuite de fairer rentrer au chausse pied un truc dans la libraries alors que ça ne va pas... bien sûr si vous avez écrit la librairie vous savez comment ça fonctionne et donc vous pouvez dérouler et voir ou ça va bloquer mais si vous utilisez ça comme une boîte noire...

Mais bon loin de moi l’idée de dire que c’est pas utile à d’autres - je parlais juste pour moi

mon expérience c'est aussi que quand le cas est simple on n'en a pas besoin

Bien évidemment.

quand c'est compliqué souvent une librairie va toujours se heurter à un cas particulier

En trente ans de pratique je n'ai jamais rencontré de cas de ce genre.

J'ai travaillé sur des machines de distribution de titres de transport où chaque périphérique (sur ligne série la plupart du temps) est géré à l'aide d'un moteur de machine à états finis et chaque partie de l'application a sa propre table d'états / transition.
Il s'agit d'une application multi-processus (je dirais une bonne vingtaine de processus) et tous communiquent par des messages postés dans des queues (avec des arguments multiples, forcément).

C'était si énorme que le moteur posséde, en plus de la notion d'état, une notion de contexte (une sorte de super-état ou macro-état), et chaque contexte possède sa propre table d'états / transitions.

Il ne serait venu à l'idée de personne de coder tout ce bazar sans moteur de machine à états finis.
C'est même carrément impossible.

Un automate fini est une représentation mathématique abstraite d'un système observé qui est susceptible d'être dans un nombre fini d'états, mais étant un moment donné dans un seul état à la fois.

  • Un état est la description de la configuration d'un système en attente d'exécuter une transition.

  • L'état dans lequel il se trouve alors est appelé l'« état courant ».

  • Le passage (transition ) d'un état à un autre est activé par un événement ou une condition

  • Une transition est un ensemble d'actions à exécuter lorsqu'une condition est remplie ou lorsqu'un événement est reçu

  • Dans certaines représentations, il est possible d'associer des actions à un état :

  • action d'entrée = réalisée lorsque l'on « entre » dans l'état,

  • action de sortie = réalisée lorsque l’on « quitte » l'état.

  • action de répétition = réalisée tous les ∆t au sein d'un même état (eg - led qui clignote)

  • action de transition = réalisée lors d'une transition (peut être une répétition sur l'état lui même)
    ainsi qu'à une mémoire (par exemple état précédent ou historique des états et transitions).

Mathématiquement, un automate particulier, pour un système donné, est défini par l'ensemble de ses états et l'ensemble de ses transitions.

-> Ce que je dis c'est qu'il faut suivre un design pattern (méthodologie), si vous êtes intellectuellement capable de visualiser l'automate, vous saurez le coder et si la complexité augmente bien sûr être très strict sur l'application du formalisme - ou faire appel à de l'abstraction (librairies, langages) et/ou de la génération de code.

Il ne manque pas de littérature sur le sujet et d'approches --> modèle du genre langage de spécification et de description (SDL), automates UML, Diagramme états-transitions, machine à états abstraits, Machine de Moore, Machine de Mealy…

Ce que vous décrivez est basé principalement sur du Pub/Sub, il y a l' "Actor model" ou le "State Pattern" (en POO) par exemple qui sont envisageables.

La plupart des frameworks FSM souffrent d'un ou plusieurs des problèmes:

• Les bibliothèques en font trop: de nombreuses bibliothèques gèrent les transitions d'état, mettent à jour des modèles, publient des événements, exécutent des gestionnaires antérieurs / postérieurs, etc. ça peut être trop riche pour le besoin de base.

• Forcent des modèles d'implémentation non naturels, par exemple la logique métier est intégrée dans l'implémentation de la machine. (cf sur un de vos exemples)

• Couplés à un modèle spécifique de communication (cf le modèle Actor genre Erlang et Akka qui sont bcp trop complexes pour la majorité des cas mais bien utiles quand on est sur un gros cas industriel type celui que vous citez)

--> bref ce que je dis c'est qu'il faut appliquer le bon niveau de formalisme, et que souvent quand on a un marteau on a trop souvent la tendance à voir chaque problème comme un clou...

Pour moi - un outil "machines à état" aurait les composants de code suivants:

  • Un mécanisme pour empêcher les transitions d'état incorrectes. Cela peut être une "carte de transition" si vous choisissez de modéliser les transitions de manière générique (pas top) ou pour le cas général un modèle où les transitions peuvent être isolées selon des comportements valides de l'état actuel.
  • Un mécanisme pour empêcher l'accès à des comportements en dehors de l'état actuel.
  • Une description de comportements associés à des états spécifiques. (blink)
  • Un mécanisme pour notifier des "observateurs" externes des transitions d'état (susceptible de générer des évènements et donc des transitions)

Dans un de vos exemples ("4.2. Un bouton + mesure de tension batterie") vous avez intégré le clignotement dans la fonction controlLed() et dans cette fonction vous avez un appel à l'état de la machine

int state = getMachineState();
  if (state == STATE_BATT_LOW) {...

Vous avez donc de la logique métier embarquée, vous vous retrouvez au milieu du gué.

Tout dépend bien sûr de ce que vous définissez comme étant le système et les états possibles mais on pourrait dire que le fait que la LED soit allumée ou éteinte sont en fait deux états bien différents et ça peut conduire à des soucis.

Par exemple, si la batterie était faible puis retrouve son autonomie (très possible quand on approche la zone BATT_LIMIT) dans votre exemple, vous pouvez vous retrouver avec la LED allumé (en fonction du clignotement) qui était lié à l'état précédent "batterie faible" (j'ai pas lu en détail, je me trompe peut-être) sans pour autant que l'utilisateur ait appuyé sur le bouton pour demander l'allumage... (et donc vous allez vider la batterie :slight_smile: )

Cet exemple est intéressant parce que vous avez fait le choix de définir STATE_BATT_LOW comme un état alors que c'est éventuellement juste un évènement comme le temps qui passe ou l'appui du bouton. On peut aussi dire qu'il faudrait mémoriser l'état précédent s'il y a possibilité de sortir de l'état batterie faible.

approche avec mémorisation de l'état précédent en créant 2 états:

Approche avec mémorisation de l'état précédent nécessitant une variable supplémentaire état_précédent que la librairie peut (ou pas) fournir.

Long post pour dire que chacun peut voir du plus et du moins dans une approche donnée, qu'il n'y a pas de réponse universelle et qu'il convient d'utiliser le bon sens et le bon outil pour un cas donné.... :slight_smile:

Vous m'avez largué mais je ne peux qu'être d'accord avec :

J-M-L:
Long post pour dire que chacun peut voir du plus et du moins dans une approche donnée, qu'il n'y a pas de réponse universelle et qu'il convient d'utiliser le bon sens et le bon outil pour un cas donné.... :slight_smile:

et ce n'est pas limité à la programmation ni à l'électronique.

Edit : et que la recherche systématique d'une bibliothèque n'est pas forcément le plus économique en temps si on compare le temps passé à analyser le sujet avec celui passé à comprendre une bibliothèque dont la documentation est souvent déficiente.

--> bref ce que je dis c'est qu'il faut appliquer le bon niveau de formalisme, et que souvent quand on a un marteau on a trop souvent la tendance à voir chaque problème comme un clou...

J'en ai vu de nombreux exemples ... c'est même une pathologie très courante chez les softeux.

Par exemple, si la batterie était faible puis retrouve son autonomie (très possible quand on approche la zone BATT_LIMIT) dans votre exemple, vous pouvez vous retrouver avec la LED allumé ou éteinte (en fonction du clignotement) qui était lié à l'état précédent "batterie faible" (j'ai pas lu en détail, je me trompe peut-être)

La fonction de lecture de l'état de la batterie retourne HIGH, LOW ou -1. -1 veut dire pas de changement depuis la dernière consultation.

  int batt = readBatteryState();
  if (batt != -1) {
    postEvent(&battEvent, EVENT_BATT, batt);
  }

On pourrait facilement introduire une notion d'hystérésis pour palier aux fluctuation de la mesure, mais ce n'est qu'un petit exemple didactique.

Donc si la tension batterie chute et remonte il y aura génération de 2 événements EVENT_BATT(LOW) et EVENT_BATT(HIGH).
Si on avait affaire à une chute de tension très brève (ce qui pourrait arriver avec une charge activée très brièvement), l'automate aurait une chance sur X de ne pas la voir, et cela ne serait pas gênant.

Cet exemple est intéressant parce que vous avez fait le choix de définir STATE_BATT_LOW comme un état alors que c'est éventuellement juste un évènement comme le temps qui passe ou l'appui du bouton. On peut aussi dire qu'il faudrait mémoriser l'état précédent s'il y a possibilité de sortir de l'état batterie faible.

On pourrait appeler cet état STATE_FAILED, STATE_DISABLED, STATE_DOWN, etc. A mon sens il s'agit bien d'un état pendant lequel l'automate ne répond plus aux sollicitations du bouton.

Le clignotement de la LED est réalisé en dehors de l'automate pour simplifier les choses.

Chacun a sa manière de voir les choses, et on pourrait facilement l'implémenter de plusieurs manières différentes.

oui oui - je ne critique pas, hein - c'est constructif comme débat.

sur ce point

Le clignotement de la LED est réalisé en dehors de l'automate pour simplifier les choses.

--> oui, mais donc en cas d'un EVENT_BATT(LOW) suivi d'un EVENT_BATT(HIGH) comment faites vous pour retourner à l'état précédent (ie la lumière était allumée ou éteinte) ?

si je lis bien je vois un   {IGNORE, EVENT_BATT,  HIGH,  ledOff,    STATE_OFF},est-ce à dire que on éteint toujours la LED si la batterie fonctionne à nouveau ?

Il faudrait ajouter un état supplémentaire :

STATE_BATT_LOW_OFF // batterie faible alors que la lumière est éteinte
STATE_BATT_LOW_ON // batterie faible alors que la lumière est allumée

Ce n'est pas une énorme difficulté.
Il faut voir cet exercice comme un exemple, pas comme une application réelle.

Exemple mis à jour avec ré-allumage ou non en fonction de l'état de la LED au moment de la baisse de tension.
C'est vraiment pour les puristes :slight_smile:
Dans la vraie vie, je ferais le choix de laisser la batterie se charger sans ré-allumer, mais bon, cela ajoute un peu de fun.

Deuxième exemple avec gestion du clignotement de la LED par l'automate. Cela mélange un peu trop les actions bas-niveau et haut-niveau à mon goût mais cela illustre bien les possibilités.

hbachetti:
Il faudrait ajouter un état supplémentaire :

STATE_BATT_LOW_OFF // batterie faible alors que la lumière est éteinte
STATE_BATT_LOW_ON // batterie faible alors que la lumière est allumée

Ce n'est pas une énorme difficulté.
Il faut voir cet exercice comme un exemple, pas comme une application réelle.

oui c'est ce que je représentais dans

effectivement, pour les "puristes" (qui diraient aussi qu'il faut respecter la signature des fonctions :slight_smile: )

oui c'est ce que je représentais dans

Je ne l'avais pas compris comme cela.

Comme je disais :

Dans la vraie vie, je ferais le choix de laisser la batterie se charger sans ré-allumer, mais bon, cela ajoute un peu de fun.

Du même coup ceci à disparu :

int state = getMachineState();
  if (state == STATE_BATT_LOW) {...

Remplacé par des actions startBlink() et stopBlink().
Cela n'ajoute pas vraiment de complexité, et surtout pas de ligne supplémentaire dans la table.

• Les bibliothèques en font trop: de nombreuses bibliothèques gèrent les transitions d’état, mettent à jour des modèles, publient des événements, exécutent des gestionnaires antérieurs / postérieurs, etc. ça peut être trop riche pour le besoin de base.

On voit cela dans unittest avec les méthodes setUp() et tearDown() :

Les méthodes setUp() et tearDown() vous autorisent à définir des instructions qui seront exécutées avant et après chaque méthode test.

J-M-L:
oui c'est ce que je représentais dans

effectivement, pour les "puristes" (qui diraient aussi qu'il faut respecter la signature des fonctions :slight_smile: )

bonjour
Je lis d'une oreille attentive ce topic d’échange courtois :grin:

@ J-M-L

tu utilise quoi pour générer "tes... jolis graphes" ? :wink:

Artouste:
@ J-M-L tu utilise quoi pour générer "tes... jolis graphes" ? :wink:

— attention Pub —

C'est le logiciel "Keynote" - l'équivalent de powerpoint qui est fourni avec les mac, iPad ou iPhone. C'est un logiciel "gratuit" (enfin compris dans le prix du produit :cold_sweat:) - on peut même travailler à plusieurs dans le cloud sur un document partagé (si on le sauve dans iCloud - on a 5Go aussi "gratuits" avec les produits).

On peut faire aussi pareil dans Numbers (qui se veut un petit excel pour faire des tableaux), ou dans Pages (qui est un word en plus simple) et qui sont aussi fournis en standard - parce que les fonctions de dessins sont présentes, mais je préfère le faire dans Keynote.

— Fin de la séquence de pub —

J-M-L:
— attention Pub —

C'est le logiciel "Keynote" - l'équivalent de powerpoint

OK , donc l'equivalent d'impress pour la suite libre office :smiley:

Fin de l'aparté pour moi