Programmation Automate fini / Machine à état

Bonjour à toutes et tous

En ce jour gris, et vu le nombre de questions qui se résument à bien programmer un automate fini (ou une machine à état) je me suis dit que j'allais faire un petit tutoriel pour expliquer comment on peut aborder ce genre de code de façon structurée. Bien sûr il existe des librairies faisant cela plus ou moins pour vous, mais autant comprendre comment ça fonctionne, on peut souvent se passer de librairies qui vont alourdir votre code.

L'idée générale c'est d'écrire un programme pilotant un "système" qui doit réagir en déclenchant des "actions" modifiant le système par exemple en fonction "d'événements" qui se produisent et la réaction peut dépendre de l'état courant de votre système.


Vite un exemple de la vraie vie pour mieux comprendre :
Prenez une ampoule connectée à un interrupteur à impulsion. Vous appuyez sur le bouton, que doit il se passer? c'est simple, si l'ampoule était éteinte, alors elle doit s'allumer (action = alimenter l'ampoule) et si elle était allumée, alors elle doit s'éteindre (action = couper l'alimentation de l'ampoule).

On voit donc ici qu'on introduit la notion d'état de notre système. On va avoir deux états ALLUME ou ETEINT et il n'y a qu'un seul évènement possible, c'est le click sur le bouton. On a définit des actions associées à ces transitions d'état.

On peut représenter donc notre système sous la forme suivante:

Bien souvent on trouvera ce genre de système dans les minuteries. On peut donc rajouter un nouveau type d'évènement lié au temps passé dans un état. Dans le cas de la minuterie, si on est dans l'état allumé et que le délai est passé, alors il faut éteindre la lumière. On retrouve bien un évenement (délai expiré), une action (éteindre la lumière) et une transition d'état (on passe de ALLUME a ETEINT).

On peut représenter donc notre système sous la forme suivante:

Bon l'objet de ce tuto n'étant pas de rentrer dans la théorie des automates finis, donc je vous laisse lire wikipedia pour en savoir plus et on va se concentrer sur des exemples que l'on rencontre assez fréquemment quand on joue avec des Arduinos.


Un outil pratique pour le programmeur de machine à états : les énumérations

En C++ une énumération est un type, nommé ou non, qui regroupe dans une liste des constantes entières connues dès la compilation.

La valeur de chacun des éléments de la liste sera distincte de celles des autres — la première vaudra 0, la deuxième vaudra 1, la troisième vaudra 2, etc.
Je peux par exemple écrire le code suivant

enum {lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche} jour;

qui va déclarer une variable jour qui peut prendre comme valeur un des codes de la liste (lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche).

Donc si je fais le code suivant sur un Arduino

enum {lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche} jour;

void setup() {
  Serial.begin(115200);
  jour = jeudi;
  Serial.println(jour);
}

void loop() {}

Je vais voir 3 dans la console Série (réglée à 115200 bauds)

Si vous voulez imposer une valeur à certains éléments de la liste c'est possible. La numérotation par défaut est de commencer à 0 et d'incrémenter de 1 à moins que le programme n'impose une valeur explicitement à des constantes énumérée (dans ce cas elle prend cette valeur et les suivantes vaudront un de plus que celle qui la précède dans l'ordre de déclaration)

Je peux par exemple écrire le code suivant

enum {lundi=1, mardi, mercredi, jeudi, vendredi, samedi, dimanche} jour;

qui va déclarer que lundi vaut 1, donc mardi vaudra 2 etc et si vous utilisez cette définition dans le code ci dessus, au lieu d'imprimer 3 dans la console, vous verrez donc 4. (on aurait pu dire

enum {lundi, mardi, mercredi, jeudi=12, vendredi, samedi, dimanche} jour;

et dans ce cas lundi serait 0, mardi 1, mercredi 2, jeudi 12, vendredi 13 (ça porte bonheur), samedi 14 etc...)


Pourquoi je parle de cela? parce qu'un enum c'est bien pratique pour lister les états de notre système de façon à ce que le programmeur s'y retrouve facilement.

dans mon exemple ci dessus de minuterie, on a vu qu'on avait deux états et donc on pourrait déclarer

enum {lampeEteinte, lampeAllumee} etatCourant;

Je définis ainsi une variable etatCourant qui peut prendre la valeur lampeEteinte ou lampeAllumee


à noter que depuis C++ 11 (dispo dans les dernières version de l'IDE) il est possible de définir le type sous jacent aux variables de l'énumération. Par défaut c'est un entier donc type int qui va prendre deux octets de mémoire sur un UNO. si vous n'avez que quelques états (moins de 255) et que leur valeur ne vous importe pas (pas au dessus de 255) vous pouvez alors rajouter le type byte dans la déclaration est ainsi économiser un octet. (vous pourriez aussi vouloir des unsigned long par exemple si vous avez besoin de grandes valeurs pré-définies genre lecture d'un code de télécommande IR)

Dans ce code la variable jour sera un int sur deux octets

enum {lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche} jour;

et dans ce code la variable jour sera un byte, donc sur un seul octet !

enum :byte {lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche} jour;

Un autre outil pratique pour le programmeur de machine à états : le switch/case

Je vous laisse lire la doc de programmation sur le switch/case. Son intérêt réside dans le fait que bien souvent dans nos machines à état on aura besoin de dire "si l'état courant est celui ci, alors faire cela, sinon si l'état courant est celui là alors faire autre chose etc... Si vous avez de nombreux états possibles, tous ces tests imbriqués rendent le code difficile à lire et le switch/case simplifie tout cela. En combinant cela habilement avec notre enum, on pourra par exemple écrire

enum {lampeEteinte, lampeAllumee} etatCourant;
...

  switch (etatCourant) {
    case lampeEteinte:
      // faire quelque chose

      break;

    case lampeAllumee:
      // faire quelque chose d'autre

      break;
  }

Mise en pratique

Construisons un cas un peu similaire à celui de la minuterie, mais un peu plus complexe pour avoir de nombreux états à gérer.

étape 1: monter sa platine d'essai et connecter l'Arduino

il vous faudra

  • 4 LEDs de couleur (rouge, orange, jaune, verte)
  • 4 résistances de 200Ω à 300Ω (en fonction de vos LEDs)
  • un bouton momentané
  • un Arduino UNO ou similaire
  • des fils pour connecter tout cela

Voici le montage:

385695breadboard.png

928338carte.png

on relie les GND de l'Arduino avec le rail GND de la platine d'essai ( j'ai relié les 2 rails opposés GND de la platine ensemble pour avoir GND des 2 côtés)

On connecte

Pin 4 --> bouton --> GND (en câblant 'croisé' on est sûr d'avoir les bonnes pins)
Pin 8 --> Led rouge --> 200 Ω --> GND
Pin 9 --> Led orange --> 200 Ω --> GND
Pin 10 --> Led jaune --> 200 Ω --> GND
Pin 11 --> Led verte --> 200 Ω --> GND


Voilà à partir de là on va effectuer 3 exercices - cf les post suivants

2 Likes

Exercice #1

Dans cet exercice nous souhaitons démarrer avec tout éteint et que le bouton serve à allumer les LEDs les unes à la suite des autres pour éclairer de plus en plus fort par exemple (ou ici faire des couleurs):

  • Premier appui la LED verte s'allume
  • deuxième appui la LED verte reste allumée et on allume la jaune
  • troisième appui la LED orange s'allume en plus
  • quatrième appui la led rouge s'allume en plus
  • cinquième appui tout s'éteint.

Cela resemble fortement à une machine à état :slight_smile: que l'on pourrait décrire ainsi

plusieurs états:

  • tout éteint (REPOS)
  • Led Verte allumée (V)
  • Led Verte et Jaune allumées (VJ)
  • Led Verte, Jaune et Orange allumées (VJO)
  • Led Verte, Jaune et Orange et Rouge allumées (VJOR)

état initial = repos

action possible = click sur le bouton

et voici le diagramme des transitions possibles

Comment coder tout cela ?


Bon pour se concentrer sur l'essentiel, je vas utiliser la librairie OneButton.

--> télécharger et installer cette librairie OneButton.

EDIT: j'utilise aussi la librairie de @bricoleau pour gérer des boutons, elles n'ont pas la même API et pour les trucs simples l'encapsulation objet de celle de @bricoleau rend le code plus sympa.

EDIT (2022): @bricoleau a étendu sa la librairie et la classe Button est intégrée dans easyRun. EasyRun offre aussi des fonctions pour développer une machine à état (plus de détails: easyRun : un couteau suisse pour faire plusieurs choses à la fois)

Vous déclarez un objet bouton en précisant sur quelle pin il est connecté et s'il est actif à l'état High ou Low (c'est à dire si son pinMode() est en INPUT_PULLUP ou pas) et vous attachez une fonction à appeler (on dit que c'est un callBack en anglais) quand une action est détectée sur le bouton.

Dans le code ça ressemble à cela

#include <OneButton.h> // on inclut la librairie
const byte buttonPin = 4; // on définit un nom pour la pin associée au bouton
OneButton button(buttonPin, true); // true pour dire qu'on est en INPUT_PULLUP, donc actif LOW, connecté à GND

On déclare ensuite une fonction de call back

void simpleclick()
{
... // le code à exécuter quand on fait un click sur le bouton
}

et dans le setUp() on fera

void setup() {
  button.attachClick(simpleclick); // on associe le fonction callBack à l'appui sur le bouton
}

enfin dans la loop() la librairie doit être appelée de manière répétitive pour voir si un bouton est appuyé

void loop() {
  button.tick();  // On vérifie l'état des boutons, ce qui déclenche les appels aux fonctions
}

Voilà c'est donc simple d'emploi et ça permet de nous concentrer sur notre machine à état (si vous êtes curieux allez voir le source de la librairie et vous verrez que c'est aussi une machine à état :slight_smile: )


Revenons à notre code

il va falloir déclarer bien sûr toutes les pins utilisées pour les LEDs, instancier le bouton, et coder la machine à état en utilisant une union pour les différents états et on va déclarer une fonction callback qui est appelée quand on appuie sur le bouton dans la quelle on aura un beau switch/case comme mentionné plus haut

Voici la code commenté

// La librairie de gestion des boutons
#include <OneButton.h>
const byte buttonPin = 4; // notre bouton est sur la pin 4
OneButton button(buttonPin, true); // true pour le mettre en INPUT_PULLUP

// les pins utilisées pour les LEDs
const byte pinLedRouge = 8;
const byte pinLedOrange = 9;
const byte pinLedJaune = 10;
const byte pinLedVerte = 11;

// la liste des états possible de notre système
// ainsi qu'une variable etatCourant prenant une de ces valeurs
enum {REPOS, ETAT_V, ETAT_VJ, ETAT_VJO, ETAT_VJOR} etatCourant;

// ------------------------------------------------------
// Cette fonction installe l'état initial
// ------------------------------------------------------
void mettreAuRepos()
{
  digitalWrite(pinLedVerte,  LOW);
  digitalWrite(pinLedJaune,  LOW);
  digitalWrite(pinLedOrange, LOW);
  digitalWrite(pinLedRouge,  LOW);
  etatCourant = REPOS;
}

// ------------------------------------------------------
// La fonction de call back, appellée automatiquement quand on clique
// ------------------------------------------------------
void simpleclick()
{
  switch (etatCourant) {
    case REPOS: // on était au repos et on a un appui, on allume la verte
      digitalWrite(pinLedVerte, HIGH); // LED verte alimentée
      etatCourant = ETAT_V; // on note le nouvel état de notre système
      break;

    case ETAT_V: // on était led verte allumée et on a un appui, on allume la jaune
      digitalWrite(pinLedJaune, HIGH); // LED jaune alimentée
      etatCourant = ETAT_VJ;// on note le nouvel état de notre système
      break;

    case ETAT_VJ: // vert et jaune allumées, on a un appui, on allume la orange
      digitalWrite(pinLedOrange, HIGH); // LED orange alimentée
      etatCourant = ETAT_VJO;// on note le nouvel état de notre système
      break;

    case ETAT_VJO:// vert, orange et jaune allumées, on a un appui, on allume la rouge
      digitalWrite(pinLedRouge, HIGH); // LED rouge alimentée
      etatCourant = ETAT_VJOR;// on note le nouvel état de notre système
      break;

    case ETAT_VJOR: // tout était allumé, on a un appui, on retourne au repos
      mettreAuRepos(); // on retourne à l'état initial
      break;
  }
}

// ------------------------------------------------------
// On initialise notre système dans le setup
// ------------------------------------------------------
void setup() {
  pinMode (pinLedRouge, OUTPUT);
  pinMode (pinLedOrange, OUTPUT);
  pinMode (pinLedJaune, OUTPUT);
  pinMode (pinLedVerte, OUTPUT);

  //conditions Initiales
  mettreAuRepos();

  // On attache la fonction simpleClick() comme callBack
  button.attachClick(simpleclick);
}

void loop() {
  // On vérifie l'état des boutons, ce qui déclenche l'appel de la fonction callBack si nécessaire
  button.tick();

  // ici on peut faire autre chose du moment que ça ne prend pas trop longtemps

}

Toute l'intelligence de la machine est donc dans la fonction callBack simpleclick() qui est toute simple à lire grace au switch /case et à l'usage de code d'état simples à lire tels que déclarés dans l'enum.

Pour faire simple, grâce au switch /case on regarde quel est notre état courant et comme on sait que cette fonction n'est appelée que lorsqu'on a reçu un click, on sait qu'il faut passer à l'état suivant. en regardant sur le diagramme on sait quelle action il faut faire et quel est l'état suivant, il suffit donc de coder cela. c'est tout simple :slight_smile:

2 Likes

Exercice #2

Dans cet exercice nous souhaitons compliquer un peu le fonctionnement de notre machine précédente.

C'est bien gentil de pouvoir augmenter la luminosité petit à petit mais il y a des gens pressés sur terre et donc on nous demande maintenant de modifier notre machine pour qu'un double click sur le bouton allume toutes les LEDS si elles n'étaient pas déjà toutes allumées et les éteignent toutes si elles étaient toute allumées.

Notre machine se complique donc un petit peu. On a un nouvel évènement à prendre en compte, le double click qui va générer des transitions nouvelles: une transition qui va de tous les états sauf "tout allumé" vers l'état "tout allumé", et une transition de tout allumé vers l'état de repos en cas de double click et que tout était allumé.

sur un diagramme, les nouvelles transitions ressemblent donc à cela:


(bien sûr elles s'ajoutent aux anciennes transitions)


Comment va-t-on gérer cela?

Le concepteur de la librairie OneButton dans sa grands sagesse a prévu cela et cela fonctionne de la même manière que précédemment: vous déclarez une fonction "callback" qui sera appelée quand un double click est détecté. On va donc créer une fonction

void doubleclick()
{
   // notre code ici en cas de double click
}

et dans le setup() attacher ce callBack sur notre bouton en appelant la bonne méthode

  button.attachDoubleClick(doubleclick);

Le code qui se trouve dans le callBack est simple: on peut soit faire un if sur l'état pour voir si toutes les LEDs sont allumées et faire ce qu'il faut, ou conserver notre structure avec le switch / case (un peu moins efficace) ce que je vais faire ici puisque plus lisible

cela nous donne donc ceci, toute la magie est dans le callback doubleclick()

// La librairie de gestion des boutons
#include <OneButton.h>
const byte buttonPin = 4; // notre bouton est sur la pin 4
OneButton button(buttonPin, true); // true pour le mettre en INPUT_PULLUP

// les pins utilisées pour les LEDs
const byte pinLedRouge = 8;
const byte pinLedOrange = 9;
const byte pinLedJaune = 10;
const byte pinLedVerte = 11;

// la liste des états possible de notre système
// ainsi qu'une variable etatCourant prenant une de ces valeurs
enum {REPOS, ETAT_V, ETAT_VJ, ETAT_VJO, ETAT_VJOR} etatCourant;

// ------------------------------------------------------
// Cette fonction installe l'état initial
// ------------------------------------------------------
void mettreAuRepos()
{
  digitalWrite(pinLedVerte,  LOW);
  digitalWrite(pinLedJaune,  LOW);
  digitalWrite(pinLedOrange, LOW);
  digitalWrite(pinLedRouge,  LOW);
  etatCourant = REPOS;
}

// ------------------------------------------------------
// La fonction de call back, appellée automatiquement quand on clique
// ------------------------------------------------------
void simpleclick()
{
  switch (etatCourant) {
    case REPOS: // on était au repos et on a un appui, on allume la verte
      digitalWrite(pinLedVerte, HIGH); // LED verte alimentée
      etatCourant = ETAT_V; // on note le nouvel état de notre système
      break;

    case ETAT_V: // on était led verte allumée et on a un appui, on allume la jaune
      digitalWrite(pinLedJaune, HIGH); // LED jaune alimentée
      etatCourant = ETAT_VJ;// on note le nouvel état de notre système
      break;

    case ETAT_VJ: // vert et jaune allumées, on a un appui, on allume la orange
      digitalWrite(pinLedOrange, HIGH); // LED orange alimentée
      etatCourant = ETAT_VJO;// on note le nouvel état de notre système
      break;

    case ETAT_VJO:// vert, orange et jaune allumées, on a un appui, on allume la rouge
      digitalWrite(pinLedRouge, HIGH); // LED rouge alimentée
      etatCourant = ETAT_VJOR;// on note le nouvel état de notre système
      break;

    case ETAT_VJOR: // tout était allumé, on a un appui, on retourne au repos
      mettreAuRepos(); // on retourne à l'état initial
      break;
  }
}

// ------------------------------------------------------
// La fonction de call back, appellée automatiquement quand on double clique
// ------------------------------------------------------
void doubleclick()
{
  switch (etatCourant) {
    case REPOS:    // dans tous les états
    case ETAT_V:   // sauf celui où tout était allumé
    case ETAT_VJ:  // en cas de double click on veut
    case ETAT_VJO: // allumer toutes les LEDs
      digitalWrite(pinLedVerte, HIGH);
      digitalWrite(pinLedJaune, HIGH);
      digitalWrite(pinLedOrange, HIGH);
      digitalWrite(pinLedRouge, HIGH);
      etatCourant = ETAT_VJOR;  // et on déclare notre nouvel état courant 
      break;

    case ETAT_VJOR: // on a tout qui est allumé et on reçoit le double click
      mettreAuRepos(); // donc on retourne à l'état de repos
      break;
  }
}


// ------------------------------------------------------
// On initialise notre système dans le setup
// ------------------------------------------------------
void setup() {
  pinMode (pinLedRouge, OUTPUT);
  pinMode (pinLedOrange, OUTPUT);
  pinMode (pinLedJaune, OUTPUT);
  pinMode (pinLedVerte, OUTPUT);

  //conditions Initiales
  mettreAuRepos();

  // On attache la fonction simpleClick() comme callBack en cas de simple click
  button.attachClick(simpleclick);
  
    // On attache la fonction doubleClick() comme callBack en cas de double click
  button.attachDoubleClick(doubleclick);
}

void loop() {
  // On vérifie l'état des boutons, ce qui déclenche l'appel d'une des fonctions callBack si nécessaire
  button.tick();

  // ici on peut faire autre chose du moment que ça ne prend pas trop longtemps

}
1 Like

Exercice #3

Dans cet exercice, on nous demande de nous montrer économe.. .il ne faut pas laisser la lumière allumée trop longtemps et donc on nous demande de mettre une minuterie. Le cahier des charges stipule : "Si la lumière est allumée plus de 15 secondes sans action de la part de l'utilisateur, alors tout éteindre."

Maintenant nous somme rodés. on voit tout de suite qu'il s'agit d'un nouveau type d'événement qu'il va falloir prendre en compte dans notre machine à état : le temps qui passe.

Notre machine se complique donc un petit peu. On a un nouvel évènement à prendre en compte, le "délai expiré" qui va générer des transitions nouvelles: une transition qui va de tous les états sauf "tout éteint" vers l'état "tout éteint"

sur un diagramme, les nouvelles transitions ressemblent donc à cela:


(bien sûr elles s'ajoutent aux anciennes transitions)


Comment va-t-on gérer cela?

On ne peut bien sûr pas mettre de delay(15000) dans notre code sinon les boutons ne seraient plus opérationnels. On ne doit pas bloquer le code.... On ne va pas réinventer la roue pour cela, on va utiliser une technique classique.

Vous avez tous lu le tutoriel (sinon il faut le lire) blink without delay qui est un des exemple standard de la gestion du temps. Pour les non anglophone il y a bien sûr l'excellent tutoriel d'eskimon sur la gestion du temps et la fonction millis() (voir à la fin de l'article).

Une fois que vous maitrisez ce concept, on va l'appliquer


Il va donc nous falloir une variable chrono qui va mémoriser l'"heure" de la dernière action de l'utilisateur.

// On introduit le temps comme évènement supplémentaire
unsigned long chrono; // attention, type unsigned long comme millis()
const unsigned long TimeOut = 15000ul; // 15 secondes (le ul à la fin pour unsigned long, une bonne habitude à prendre)

L'évènement "délai expiré" est un événement comme un autre, il se gère au même niveau que là où on regarde si les boutons sont appuyés, donc dans la loop().

Après avoir vérifié les boutons, on va regarder si le délai depuis la dernière action est expiré et si oui on va déclencher un appel à une fonction qui timeOut().

Cette fonction doit regarder dans quel état on est et si au moins une des LEDs est allumée alors tout éteindre et revenir au repos. On pourrait faire un switch / case pour traiter chaque cas indépendamment et bien mettre du code pour chaque transition du diagramme comme dans l'exercice #2 où on avait conservé le switch case pour la lisibilité, mais maintenant vous êtes rodés et des pros, donc on va juste tester le cas qui nous intéresse plutôt que de regarder tous les cas. En effet un simple test sur l'état courant pour voir si on n'est pas au repos suffit et dans ce cas revenir à l'état repos.

la fonction fera donc tout simplement

void timeOut()
{
  if (etatCourant != REPOS) mettreAuRepos(); // si on n'est pas au repos, passer au repos
}

Bien sûr il faut ré-armer notre compteur à chaque fois que l'utilsateur appuie sur un bouton puisque le cahier des charges dit 15 secondes après la dernière action. on va donc rajouter dans nos fonctions de callback simpleclick() et doubleclick() une ligne de code qui maintient notre "top chrono" en faisant simplement

chrono = millis(); // on vient d'avoir une action donc on ré-arme notre chronomètre
1 Like

Voici donc le code final :

// La librairie de gestion des boutons
#include <OneButton.h>
const byte buttonPin = 4; // notre bouton est sur la pin 4
OneButton button(buttonPin, true); // true pour le mettre en INPUT_PULLUP

// les pins utilisées pour les LEDs
const byte pinLedRouge = 8;
const byte pinLedOrange = 9;
const byte pinLedJaune = 10;
const byte pinLedVerte = 11;

// On introduit le temps comme évènement supplémentaire
unsigned long chrono; // attention, type unsigned long comme millis()
const unsigned long TimeOut = 15000ul; // 15 secondes (le ul à la fin pour unsigned long)

// la liste des états possible de notre système
// ainsi qu'une variable etatCourant prenant une de ces valeurs
enum {REPOS, ETAT_V, ETAT_VJ, ETAT_VJO, ETAT_VJOR} etatCourant;

// ------------------------------------------------------
// Cette fonction installe l'état initial
// ------------------------------------------------------
void mettreAuRepos()
{
  digitalWrite(pinLedVerte,  LOW);
  digitalWrite(pinLedJaune,  LOW);
  digitalWrite(pinLedOrange, LOW);
  digitalWrite(pinLedRouge,  LOW);
  etatCourant = REPOS;
}

// ------------------------------------------------------
// La fonction de call back, appellée automatiquement quand on clique
// ------------------------------------------------------
void simpleclick()
{
  switch (etatCourant) {
    case REPOS: // on était au repos et on a un appui, on allume la verte
      digitalWrite(pinLedVerte, HIGH); // LED verte alimentée
      etatCourant = ETAT_V; // on note le nouvel état de notre système
      break;

    case ETAT_V: // on était led verte allumée et on a un appui, on allume la jaune
      digitalWrite(pinLedJaune, HIGH); // LED jaune alimentée
      etatCourant = ETAT_VJ;// on note le nouvel état de notre système
      break;

    case ETAT_VJ: // vert et jaune allumées, on a un appui, on allume la orange
      digitalWrite(pinLedOrange, HIGH); // LED orange alimentée
      etatCourant = ETAT_VJO;// on note le nouvel état de notre système
      break;

    case ETAT_VJO:// vert, orange et jaune allumées, on a un appui, on allume la rouge
      digitalWrite(pinLedRouge, HIGH); // LED rouge alimentée
      etatCourant = ETAT_VJOR;// on note le nouvel état de notre système
      break;

    case ETAT_VJOR: // tout était allumé, on a un appui, on retourne au repos
      mettreAuRepos(); // on retourne à l'état initial
      break;
  }
  chrono = millis(); // on vient d'avoir une action donc on ré-arme notre chronomètre
}

// ------------------------------------------------------
// La fonction de call back, appellée automatiquement quand on double clique
// ------------------------------------------------------
void doubleclick()
{
  switch (etatCourant) {
    case REPOS:    // dans tous les états
    case ETAT_V:   // sauf celui où tout était allumé
    case ETAT_VJ:  // en cas de double click on veut
    case ETAT_VJO: // allumer toutes les LEDs
      digitalWrite(pinLedVerte, HIGH);
      digitalWrite(pinLedJaune, HIGH);
      digitalWrite(pinLedOrange, HIGH);
      digitalWrite(pinLedRouge, HIGH);
      etatCourant = ETAT_VJOR;  // et on déclare notre nouvel état courant
      break;

    case ETAT_VJOR: // on a tout qui est allumé et on reçoit le double click
      mettreAuRepos(); // donc on retourne à l'état de repos
      break;
  }
  chrono = millis(); // on vient d'avoir une action donc on ré-arme notre chronomètre
}

// ------------------------------------------------------
// La fonction de appellée en cas de dépassement du délai
// (on pourrait aussi faire un if (etatCourant != REPOS) mettreAuRepos(); )
// ------------------------------------------------------
void timeOut()
{
  /* // version longue si on veut le switch case
    switch (etatCourant) {
    case ETAT_V:    // pour tous les états
    case ETAT_VO:   // sauf celui au repos
    case ETAT_VOJ:  // on doit tout éteindre
    case ETAT_VOJR: // et revenir à l'état initial
      mettreAuRepos(); // ce que fait cette fonction
      break;
    }
  */
  // version courte, si on n'est pas au repos, alors passer au repos
  if (etatCourant != REPOS) mettreAuRepos();

  // à noter que le timeOut continuera de se déclencher toutes les 15 secondes
  // mais ne fera rien puisqu'on sera au repos
  // ça peut être utile pour continuer à faire autre chose
  // sinon il faut tester avant de l’appeler qu’on n’est pas au repos
}

// ------------------------------------------------------
// On initialise notre système dans le setup
// ------------------------------------------------------
void setup() {
  pinMode (pinLedRouge,  OUTPUT);
  pinMode (pinLedOrange, OUTPUT);
  pinMode (pinLedJaune,  OUTPUT);
  pinMode (pinLedVerte,  OUTPUT);

  //conditions Initiales
  mettreAuRepos();

  // On attache la fonction simpleClick() comme callBack en cas de simple click
  button.attachClick(simpleclick);

  // On attache la fonction doubleClick() comme callBack en cas de double click
  button.attachDoubleClick(doubleclick);
}

void loop() {
  // On vérifie l'état des boutons, ce qui déclenche l'appel d'une des fonctions callBack si nécessaire
  button.tick();

  // On vérifie le timer et on déclenche l'évènement si nécéssaire
  // rajouter dans la condition “&& (etatCourant != REPOS)” si vous ne souhaitez pas
  // appeler la fonction au repos
  if (millis() - chrono >= TimeOut) {
    timeOut();
    chrono = millis(); // on ré-arme notre chronomètre
  }

  // ici on peut faire autre chose du moment que ça ne prend pas trop longtemps

}

Ce code correspond à l'ensemble du diagramme d'états

Qui bien que "compliqué" car il y a beaucoup de choses à gérer, n'est pas compliqué à programmer, même si on vous rajoute des transitions et des contraintes, car vous avez créé une structure de code saine et évolutive.

Cette technique s'applique à de nombreux cas, donc il est bon de la maîtriser

Bon codage à toutes et tous !

1 Like

Bonsoir , effectivement la plupart des codes sont en fait des machines etat :wink:

Joli post simple et qui explique quelques possibilités courantes simpleement et expressement !

Merci .

A votres avis , est il possible sur un mega 2560 niveau place en memoire et ressources de faire evoluer ce programme pour une dizaine de capteurs analogiques differents ( temperatures , humlidité etc ) et de gerer une 50 aine de relais en meme temps ?

Il faudrait voir dans le détail les besoins en fonction de vos composants et de la logique applicative nécéssaire mais oui sans doute (70 pins numériques dont 16 capables de faire de l'analogiques et 8k de SRAM sur une mega)

Bonjour , merci pour votre reponse .

Ce sera un mini serveur domotique , avec gestion chauffage , volets roulants lumieres interireures et exterierieures , arrosage automatique , niveau materiel rien de tres original et niveau code tres repetitif je suppose .

OK - sympa comme projet. Attention quand vous commencez à jouer avec des vrais courants forts à prendre des relais normés et d'installer cela dans les règles de l'art

Bonjour merci pour votre tuto qui ma bien éclairé, mon projet est de pilotée de volets roulant entre autre. Mon serveur domotique est sous domoticz , on peut faire varier la hauteur du volets en fonction d un pourcentage (comme un variateur de lumières). Donc voici ma demande peut on faire varier la minuterie comme ca et récupérer la valeur pour règle la minuterie

Tout est possible en général - le mieux c'est de commencer à coder un peu et éventuellement si vous avez besoin d'aide d'ouvrir un seule post dans la section générale du forum plutôt que dans la partie des tutos ou plusieurs endroits, en postant les détails spécifiques de votre projet et en montrant ce que vous avez déjà réalisé comme code

Oui désolé mes j ai déjà commence une discutions la mais personne ne ma répondu
https://forum.arduino.cc/index.php?topic=493281.msg3365527#msg3365527

Le bar n'est pas le meilleur endroit pour votre post - j'ai mis une petite notification au modérateur pour qu'il le déplace directement dans la section principale

Merci

Bonjour vous êtes un génie, merci pour votre partage et aide, tous mes respects.

Je n'en demandais pas tant :slight_smile:

Salut J-M-L,
Merci beaucoup pour le partage, ses cours sont à lire et relire pour tout ceux qui souhaitent travailler la domotique ou similaires, très claire et précis. Je lis et relie afin de mieu comprendre. Merci d'avoir pensé aux amateurs comme moi.
Très bonne continuation.

Merci !

En effet, c'est vraiment intéressant et utile. Merci.

Super intéressant, ce tuto sur les machines à état m'a permis de solutionner un problème de verrouillage à l’état inactif d'un relais pendant un temps prédéfini, après que le timeout l'ai ramené à l’état inactif.
Merci.