Aide pour activation relais en mode automatique temporisé via machine a etats

J’ai pas dit qu’il fallait qu’une seule machine à états - vous pouvez en avoir plusieurs mais il ne faut pas transformer le code en spaghetti :spaghetti: . Chaque machine doit traiter son problème

Par exemple la classe asyncTask ou une classe bouton c’est une machine à état. Si vous voulez depuis une machine déclencher une action dans une autre il faut poster un événement qui est attendu par l’autre machine et ne pas venir mélanger les codes avec des if qui vont lire des variables internes de l’autre machine à états

Comme @J-M-L le soucis n'est pas si il faut une ou plusieurs machine.

Pour les pointeurs ou indice oui cela ne change pas vraiment grand chose. sauf si tu fais des for, pour retrouver les indices, cela veut simplement dire que ce n'est pas le plus logique, donc ... :slight_smile:

Pour ton schéma, j'aurais tendance à dire que activation manuelle n'est pas un état.
Sauf si tu es précisément dans une attente spécifique d'une activation manuelle.
Je me demande si "attente du prochain arrosage", n'est pas justement une attente d'un évènement programmé ou manuelle.
Dans ton schéma, j'aurais tendance à séparer les évènements des états, par exemple GEL n'est pas forcément un état mais un évènement, qui peut être recueillis lorsque tu es dans l'état qui peut le prendre en compte (arrosage, attente d'évènement).

De même "arrosée" pour moi ne correspond à aucun état, que fait tu ou attends tu dans cet états?

Du coup la détection de pluie, ne serait-t-elle pas un évènement qui empêche de passer de l'état en attente à en arrosage?

Après il n'y a pas qu'une seule machine à état, le principal, c'est que tu ais identifié tout les états et les interactions intervenants sur chacun d'eux.

bon , je suis pas certain d' avoir compris ce que vous dites precisément , mais bon .

là je me sui penché en particulier sur le mise en route et l' arret de l ' arrosage automatique seulement .

dans la page web j ' ai donc modifié le bouton de l' arrosage auto comme ceci :

        case ARROSAGE_AUTO:
          if (labelValue == -1) {
            arrosageAutoON = !arrosageAutoON;
            if (arrosageAutoON) affecteCommandeWeb(true, NULL, NULL_V, NULL, A_A_ON);
            else affecteCommandeWeb(true, NULL, NULL_V, NULL, A_A_OFF);
            if (DEBUG) {
              Serial.print(F("* parseCommand => ARROSAGE => AUTO : "));
              Serial.println(arrosageAutoON == false ? F("OFF") : F("ON"));
            }
          }

j ' ai modifié les enums suivants :

enum t_commandeArrosage : uint8_t { MARCHE, ARRET, MARCHE_TOUT, ARRET_TOUT, TIMERR_A, AUTOMA, NULL_A, A_A_ON, A_A_OFF };
enum t_etat : uint8_t { INCONNU, ACTIF, INACTIF, TIMER, AUTO };

modifié les fonctions de gestion pour les arrosages et un arrosage :

void gererLesArrosages() {
  if (commandeWeb.active && commandeWeb.arrosage == NULL && commandeWeb.volet == NULL && commandeWeb.actionCMD_V == NULL_V) {
    if (commandeWeb.actionCMD_A == MARCHE_TOUT) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) activerUnArrosage(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == ARRET_TOUT) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) desactiverUnArrosage(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == A_A_ON) for (uint8_t a=0; a < NOMBREDARROSAGE; a++) initialArrosageAuto(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == A_A_OFF) desactiverLesArrosages();
    commandeWeb.active = false;
  }
  else for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) gererUnArrosage(&(lesArrosages[a]));
}

creer une fonction ( encore ) :

void desactiverLesArrosages () {
  for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) {
    lesArrosages[a].etat_auto = OFF;
    lesArrosages[a].etat = INACTIF;
    if (!TEST) digitalWrite(lesArrosages[a].pinRelais, RELAIS_INACTIF);
    desactiverTimerArrosage(lesArrosages[a].pinRelais);
    
    if (DEBUG) {
      Serial.print(F("* desactiverLesArrosages => arrosage_auto desactive ! "));
      Serial.print(F("relais = "));
      Serial.println(lesArrosages[a].pinRelais);
    }
  }
}

ca a l' air de fonctionner pour activer /desactiver l ' arrosage auto .


dois je conserver les 2 etats differents pour arrosage : etat et etat_auto , ou en mettre qu ' un seul ?

Certes , mais ca n' aide pas tellement a mettre en place la structure pure de la machine .

Si , tu as tout a fait raison . je ne sais pas encore trop comment je mettrais ca en place , mais il faudra certainement detecter de la pluie depuis un certain temps pour que ca rentre en ligne de compte .

c ' est un reste de mes essais precedents , dans lequel je marquais les zonnes comme arrosées , pour ensuite reinitialisée , afin d' empecher un autre arrosage sur la meme zone .

je n' avais pas vu ca sous cet angle là . mais ca parait plutot evident que ca devrait etre le cas .

Encore une fois , je pense que tu as tout a fait raison .

Le truc , c ' est que je n' arrive pas a dissocier les evnements des etats correctement , la preuve ...
comment je pourrais ( en pseudo code ) integré un evenement dans un etat selon toi ?

Alors je sais bien que mon code est " spaghetti " , mais c ' est pas forcement facile d ' y voir clair et de ne pas faire ca quand on ne maitrise pas .

Est ce qu ' utiliser moins de fonction pourrait rendre le code moins spaghetti ?
En particulier lorsque celles-ci ne comporte par exemple que 2 lignes , comme activerUnRelais () ?


j ' ai laisser gel et pluie , mais j' ai bien compris que ce ne sont pas etats de la machine , juste des evenements a integrer dans le cas ou on arrose et dans le cas ou la machine va arroser automatiquement .

j ' ai donc modifie la machine en fonction des remarques de @terwal , en esperant que j' ai pas fait de betises . Ca commence a s' eclaircir dans mon esprit .

un petit recapitulatif de là ou j' en suis actuellement .
j ' essaie de faire fonctionner le coeur de la machine seulement .

j ' ai remodifie quelques bricoles :

void gererLesArrosages() {
  if (commandeWeb.active && commandeWeb.arrosage == NULL && commandeWeb.volet == NULL && commandeWeb.actionCMD_V == NULL_V) {
    if (commandeWeb.actionCMD_A == MARCHE_TOUT) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) activerUnArrosage(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == ARRET_TOUT) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) desactiverUnArrosage(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == A_A_ON) for (uint8_t a=0; a < NOMBREDARROSAGE; a++) initialArrosageAuto(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == A_A_OFF) for (uint8_t a=0; a < NOMBREDARROSAGE; a++) desactiverUnArrosageAuto(lesArrosages[a].pinRelais);
    commandeWeb.active = false;
  }
  else {
    for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) gererUnArrosage(&(lesArrosages[a]));
    if (arrosageAutoON) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) gererUn_A_A(&(lesArrosages[a]));
  }
}
/*		mode auto		*/
void gererUn_A_A(t_arrosage* unArrosage) {
  switch (unArrosage->etat_auto) {  // ON, OFF, INITIAL, EN_ATTENTE, A_ACTIVER, EN_ARROSAGE, ARROSE
    case EN_ATTENTE:
      attenteUnArrosageAuto(unArrosage);
      break;
    case A_ACTIVER:
      marcheArrosageAuto(unArrosage);
      break;
    case EN_ARROSAGE:
      break;
    case ARROSE:
      retourEnAttenteAuto(unArrosage);
      break;
    default:
      if (DEBUG) Serial.println(F("* gererUn_A_A => probleme = case non reconnu !"));
      break;
  }
}

void initialArrosageAuto(t_arrosage* unArrosage) {
  if (unArrosage->etat_auto != EN_ATTENTE && unArrosage->etat == INACTIF) {
    calculDataArrosageAuto(unArrosage);
    unArrosage->etat_auto = EN_ATTENTE;
    // on active une nouvelle tache ayant pour id le numero de pin + 100 / un temps de 30 secondes / et la fonction automatique d ' arrosage pour callback .
    // NOTE : on n ' active pas les relais ici , il s' agist juste de lancer la tache apres l' intervalle defini .
  }
}

void attenteUnArrosageAuto(t_arrosage* unArrosage) {
  if (unArrosage->etat_auto != A_ACTIVER && unArrosage->etat == INACTIF) {
    if (RTC.heure() == unArrosage->heureDebutArrosage) {
      unArrosage->etat_auto = A_ACTIVER;
    }
    else {
      // on poireaute
    }
  }
}

void marcheArrosageAuto(t_arrosage* unArrosage) {
  if (unArrosage->etat_auto != EN_ARROSAGE && unArrosage->etat == INACTIF) {
    unArrosage->zoneArrosee = false;
    unArrosage->etat = ACTIF;
    unArrosage->etat_auto = EN_ARROSAGE;
    if (!TEST) digitalWrite(unArrosage->pinRelais, RELAIS_ACTIF);
    gestionnaireDeTache.registerAsyncCommand(unArrosage->pinRelais, unArrosage->dureeJusquaFinArrosage, desactiverUnArrosageAuto);
    // on utilise pin pour les taches automatiques / la durée de l' arrosage desirée / la fonction qui arretera le relais ( et l' arrosage donc ) et qui lancera le prochain arrosage a l 'heure desirée .
  }
}

void retourEnAttenteAuto(t_arrosage* unArrosage) {
  if (unArrosage->etat_auto != EN_ATTENTE && unArrosage->etat == INACTIF) {
    calculDataArrosageAuto(unArrosage);
    unArrosage->etat_auto = EN_ATTENTE;
  }
}

void desactiverUnArrosageAuto(t_commandID pin) {
  for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) {
    if (lesArrosages[a].pinRelais == pin && lesArrosages[a].etat_auto != ARROSE) {
      lesArrosages[a].etat = INACTIF;
      lesArrosages[a].etat_auto = ARROSE;
      if (!TEST) digitalWrite(lesArrosages[a].pinRelais, RELAIS_INACTIF);
      //desactiverTimerArrosage(lesArrosages[a].pinRelais);
    }
  }
}

Dans le code , je ne parviens pas a enlever l ' etat " arrosé " , mais ca me semble pas plus mal .
Evidemment si vous trouvez un moyen pourquoi pas .
ca m ' a l' air de fonctionner pâs trop mal .
Evidemment , il reste du taff pour la gestion des evenements , mais ca vous semble comment pour le " coeur de la machine " ?

le zip
serveur_machine_etats.7.336.zip (25.9 KB)

Bonjour , en enlevant les evenements : gel , pluie ca donne ceci :

Maintenant , si j ' enleve aussi les evenements d' activation et d' arret , je me retrouve avec :

cela parait il mieux ?
A noter que finalement , initial auto , ne me parait pas etre un etat mais plutot un evenement de la consequence de l ' initialisation de l' arrosage automatique ...

sauf que je suis toujours autant perdu , quand a comment mettre en place tout cela .

Vis a vis du code ci dessus , ne peut on pas gerer les evenements avec une machine a etats ?
car cela semble finalement correspondre a ca non ?
Si oui , dans ce cas , ne faudrait il pas que j' integre la fonction gererUn_A_A(t_arrosage* unArrosage) dans la fonction gererUnArrosage(t_arrosage* unArrosage) dans le case AUTO ? ca me parait en fait assez logique .
ce qui donnerait alors :

void gererUnArrosage(t_arrosage* unArrosage) {
  switch (unArrosage->etat) {  // INCONNU, ACTIF, INACTIF, TIMER, AUTO
    case INCONNU:
      for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) initialiserUnArrosage(&(lesArrosages[a]));
      break;
    case ACTIF: // MARCHE, ARRET, NULL_A, MARCHE_TOUT, ARRET_TOUT
      if ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) {
        if (commandeWeb.actionCMD_A == ARRET) {
          if (DEBUG) {
            Serial.print(F("* gererUnArrosage => case actif => unArrosage-> zone : "));
            Serial.print(unArrosage->zone);
            Serial.print(F(", unArrosage->pinRelais : "));
            Serial.println(unArrosage->pinRelais);
          }
          desactiverUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
        }
      }
      break;
    case INACTIF:              
      if ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) {
        if (commandeWeb.actionCMD_A == MARCHE) {
          if (DEBUG) {
            Serial.print(F("* gererUnArrosage => case inactif => unArrosage-> zone : "));
            Serial.print(unArrosage->zone);
            Serial.print(F(", unArrosage->pinRelais : "));
            Serial.println(unArrosage->pinRelais);
          }
          activerUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
        }
      }
      break;


    case TIMER:
      if ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) {
        if (commandeWeb.actionCMD_A == TIMERR_A) {
          inverserUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
          if (DEBUG) {
            Serial.print(F("* gererUnArrosage => case TIMER => unArrosage-> zone : "));
            Serial.print(unArrosage->zone);
            Serial.print(F(", unArrosage->pinRelais : "));
            Serial.println(unArrosage->pinRelais);
          }
        }
      }
      break;
    case AUTO :
      if (arrosageAutoON) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) gererUn_A_A(&(lesArrosages[a]));
      break;
    default:
      if (DEBUG) Serial.println(F("* gererUnArrosage => probleme = case non reconnu !"));
      commandeWeb.active = false;
      break;
  }  // fin switch
  // comme cette fonction est appellé en permanence , il vaut mieux eviter de mettre quoi que ce soit ici
}

void gererLesArrosages() {
  if (commandeWeb.active && commandeWeb.arrosage == NULL && commandeWeb.volet == NULL && commandeWeb.actionCMD_V == NULL_V) {
    if (commandeWeb.actionCMD_A == MARCHE_TOUT) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) activerUnArrosage(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == ARRET_TOUT) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) desactiverUnArrosage(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == A_A_ON) for (uint8_t a=0; a < NOMBREDARROSAGE; a++) initialArrosageAuto(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == A_A_OFF) for (uint8_t a=0; a < NOMBREDARROSAGE; a++) desactiverUnArrosageAuto(lesArrosages[a].pinRelais);
    commandeWeb.active = false;
  }
  else {
    for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) gererUnArrosage(&(lesArrosages[a]));
  }
}

/***************************************************************************************************************/

/*		mode auto		*/
void gererUn_A_A(t_arrosage* unArrosage) {
  switch (unArrosage->etat_auto) {  // ON, OFF, INITIAL, EN_ATTENTE, A_ACTIVER, EN_ARROSAGE, ARROSE
    case EN_ATTENTE:
      attenteUnArrosageAuto(unArrosage);
      break;
    case A_ACTIVER:
      marcheArrosageAuto(unArrosage);
      break;
    case EN_ARROSAGE:
      break;
    case ARROSE:
      retourEnAttenteAuto(unArrosage);
      break;
    default:
      if (DEBUG) Serial.println(F("* gererUn_A_A => probleme = case non reconnu !"));
      break;
  }
}

l ' idée , c ' est de pouvoir conserver un arrosage manuel avec un arret apres un temps donné en permanence , et disposer d ' un arrosage entierement automatisé activable / desactivable .
En esperant que je sois relativement clair avec mes explications :upside_down_face:

est il possible de regrouper tout dans une fonction de gestion , ca serait certainement plus simple a gerer , par exemple comme cela :

void gererUnArrosage_TEST(t_arrosage* unArrosage) {
  switch (unArrosage->etat) {  // INCONNU, EN_ARROSAGE, EN_ATTENTE, TIMER, A_ACTIVER, ARROSE
    case INCONNU:
      for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) initialiserUnArrosage(&(lesArrosages[a]));
      break;
    case EN_ARROSAGE:
      if ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) { // mode manuel
        if (commandeWeb.actionCMD_A == ARRET) {
          if (DEBUG) {
            Serial.print(F("* gererUnArrosage => case actif => unArrosage-> zone : "));
            Serial.print(unArrosage->zone);
            Serial.print(F(", unArrosage->pinRelais : "));
            Serial.println(unArrosage->pinRelais);
          }
          desactiverUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
        }
      }
      else if (arrosageAutoON) { // mode auto
        
      }
      // inclure les evenements de capteur pour la pluie et le gel
      break;
    case EN_ATTENTE:
      if ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) {
        if (commandeWeb.actionCMD_A == MARCHE) {
          if (DEBUG) {
            Serial.print(F("* gererUnArrosage => case inactif => unArrosage-> zone : "));
            Serial.print(unArrosage->zone);
            Serial.print(F(", unArrosage->pinRelais : "));
            Serial.println(unArrosage->pinRelais);
          }
          activerUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
        }
      }
      else if (arrosageAutoON) {
        attenteUnArrosageAuto(unArrosage);
      }
       // inclure les evenements de capteur pour la pluie et le gel
      break;
    case TIMER:
      if ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) {
        if (commandeWeb.actionCMD_A == TIMERR_A) {
          inverserUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
          if (DEBUG) {
            Serial.print(F("* gererUnArrosage => case TIMER => unArrosage-> zone : "));
            Serial.print(unArrosage->zone);
            Serial.print(F(", unArrosage->pinRelais : "));
            Serial.println(unArrosage->pinRelais);
          }
        }
      }
      break;
    
    case A_ACTIVER:
      if (arrosageAutoON) {
        marcheArrosageAuto(unArrosage);
      }
      break;
    case ARROSE :
      if (arrosageAutoON) {
        retourEnAttenteAuto(unArrosage);
	    }
      break;
    default:
      if (DEBUG) Serial.println(F("* gererUnArrosage => probleme = case non reconnu !"));
      commandeWeb.active = false;
      break;
  }  // fin switch
}

EDIT , j ' ai tenté la modification pour n' avoir qu ' une seule fonction de gestion , ce qui evite de faire 2 boucles distinctes sur arrosage , mais je ne sais pas si c ' est une bonne idée ou pas ...
voilà ce que ca donne :
serveur_machine_etats_7.337_un_seul_gestion.zip (40.6 KB)

Bon j'avoue que je n'ai pas tout lu ce que tu as écris :woozy_face:, donc je peux dire des conneries, enfin plus que d'habitude :upside_down_face:

Si ca peut t'aider, prend le rond pour tes état et le carré pour tes évènements.
Du coup tes évènements peuvent être relié à un ou plusieurs états avec une flèche vers ces derniers.
Ce qui peut ce traduire par une variable qui représente le "status" de ton événement.

si tu as une boucle sans fin avec ta machine à état principal(switch entre tes états).
dans le "case" en attente d'un évènement par exemple, suivant les évènements qui arrive(status) tu passera dans un autre état.
Donc pour passer à l'état arrosage, il faudra que le status de l'évènement gel soit à faux que soit arrosage auto ou manuel soit à vrai.
on pourrait même prendre en compte que manuel ne doit pas être faux.

j ' en deduis donc que tu penses que la meme machine doit gerer le mode manuel et le mode auto :stuck_out_tongue:
toute la literrature precedente ( que tu n' as pas lue ) est justement de savoir ci c ela est mieux ou pas de gerer cela dans un seul switch case :wink:
je pense donc avoir la reponse a ma question .

je pense ( ... ) que mon dernier zip envoyé reflete donc ton idée et ( pour l' arrosage ) cette machine :

il me faut encore , deja voir si cela fonctionne correctement , puis y integrer les evenements des capteurs.

mon souci avec votre code est parfaitement illustré avec ceci

pour gérer les arrosages vous avez un if qui parle de volets.
➜ c'est ce que j'appelle spaghetti :wink:

pour moi il faudrait une sorte d'agrégateur, que la commande vienne du web, d'un bouton, d'un bout de code il faut que tout cela soit agrégé en une commande unique qui rentre dans la machine à état du volet ou de l'arrosage pour changer son état.

la structure générale du code pourrait refléter un truc comme cela

Normalement il n'y a aucune raison pour que les volets doivent connaitre l'état des commandes pour l'arrosage.

la machine a états peut gérer bien sûr des capteurs (trop de vent, fermeture du store banne, pluie = pas la peine d'arroser, ...) mais ça rentre dans la définition de la machine. Si une machine doit parler à une autre machine alors elle passe par le gestionnaire de commandes.

en gros la loop devrait être

void loop() {
  commandes();
  volets();
  vannes();
  relais();
  tachesAsynchrones();
}

c'est à dire que vous donnez la main à chacune des machines à états à tour de rôle.

je pense , que la condition que vous citez est en fait mal ecrite car , si on regarde commandeWeb () suite a sa derniere re-ecriture , elle est comme ceci :

void affecteCommandeWeb(bool b, t_volet* v = NULL, t_commandeVolet cv = NULL_V, t_arrosage* a = NULL, t_commandeArrosage ca = NULL_A, t_relais* r = NULL, t_commandeRelais cr = NULL_R) {
  commandeWeb.active = b;  // bool

  commandeWeb.volet = v;
  commandeWeb.arrosage = a;
  commandeWeb.relais = r;

  commandeWeb.actionCMD_V = cv;
  commandeWeb.actionCMD_A = ca;
  commandeWeb.actionCMD_R = cr;

  if (b) {
    if (v) gererUnVolet(v);
    else if (a) gererUnArrosage(a);
    else if (r) gererUnRelais(r);
    else if (cv != NULL_V && ca == NULL_A && cr == NULL_R) gererLesVolets();
    else if (ca != NULL_A && cv == NULL_V && cr == NULL_R) gererLesArrosages();
    else if (cr != NULL_R && cv == NULL_V && ca == NULL_A) gererLesRelais();
  }
}

ce qui signifie que j' aurais du ecrire la condition citée plutot comme ceci :

if (commandeWeb.active && commandeWeb.arrosage == NULL && commandeWeb.actionCMD_A != NULL_A) {

ce que je vais d' ailleurs modifié pour chaque appel de machine , merci pour cette remarque .

Pour la remarque sur la loop , il me semble que c ' est deja le cas de ce qu ' elle est , a peu de chose pres , sauf pour les commandes .
Vous suggérez donc creer une machine a etats pour la gestion des commandes si je comprends bien ?

superbe schema recapitulatif du code au passage .
j ' ai pourtant l' impression qu ' il resume assez bien le code actuel , hors mis bien sur , que je n ' utilise pas de boutons physiques , pas le serial , ni le BLE ( je sais pas ce que c ' est ) pour envoyer des commandes .

la loop actuelle est la suivante:

void loop() {
  actualiserRTC();
  gestionnaireDeTache.updateQueue();

  //les 2 lignes ci-dessous en etait qu ' une au depart , mais ca n' est utile que pour le cas ou on utilise une IP en DHCP .
  //if (renewDHCPLease())
  handleClient();  // we still have a DHCP IP
  
  gererLesVolets();
  gererLesArrosages();
  gererLesRelais();
  relaisHeureCreuse();
  //resetZoneArrosee();
}

EDIT : suite a votre remarque j ' ai re-ecrit correctement les conditions des fonctions suivantes :

void gererLesVolets() {
  if (commandeWeb.active && commandeWeb.volet == NULL && commandeWeb.actionCMD_V != NULL_V) {
    if (commandeWeb.actionCMD_V == FERMER_TOUT)
      for (uint8_t v = 0; v < NOMBREDEVOLETS; v++) fermerUnVolet(&(lesVolets[v]));
    else if (commandeWeb.actionCMD_V == OUVRIR_TOUT)
      for (uint8_t v = 0; v < NOMBREDEVOLETS; v++) ouvrirUnVolet(&(lesVolets[v]));
    else if (commandeWeb.actionCMD_V == ARRETER_TOUT)
      for (uint8_t v = 0; v < NOMBREDEVOLETS; v++) arreterUnVolet(&(lesVolets[v]));
    commandeWeb.active = false;
  } else
    for (uint8_t v = 0; v < NOMBREDEVOLETS; v++) gererUnVolet(&(lesVolets[v]));
}

void gererLesRelais() {
  if (commandeWeb.active) {
    if ( commandeWeb.relais == NULL && commandeWeb.actionCMD_R != NULL_R) {
      // on peut faire ici un " activer tout les relais par exemple "
      commandeWeb.active = false;
      if (DEBUG) {
        Serial.print(F("* gererLesRelais , 1er if "));
      }
    }
  } else
    for (uint8_t r = 0; r < NOMBREDERELAIS; r++) gererUnRelais(&(lesRelais[r]));
}

void gererLesArrosages() {
  if (commandeWeb.active && commandeWeb.arrosage == NULL && commandeWeb.actionCMD_A != NULL_A) {
    if (commandeWeb.actionCMD_A == MARCHE_TOUT) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) activerUnArrosage(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == ARRET_TOUT) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) desactiverUnArrosage(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == A_A_ON) for (uint8_t a=0; a < NOMBREDARROSAGE; a++) initialArrosageAuto(&(lesArrosages[a]));
    else if (commandeWeb.actionCMD_A == A_A_OFF) for (uint8_t a=0; a < NOMBREDARROSAGE; a++) desactiverUnArrosageAuto(lesArrosages[a].pinRelais);
    commandeWeb.active = false;
  }
  else {
    for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) gererUnArrosage(&(lesArrosages[a]));
    //if (arrosageAutoON) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) gererUn_A_A(&(lesArrosages[a]));
  }
}

le zip
serveur_machine_etats_7.337_un_seul_gestion.zip (40.7 KB)

oui votre loop est déjà propre.

il faudrait que la partie web (handleClient) soit plus simple pour éviter ce genre de tests à rallonge

if (commandeWeb.active && commandeWeb.arrosage == NULL && commandeWeb.actionCMD_A != NULL_A) {

idéalement on devrait avoir juste un

  if (commandeArrosage) { // commandeArrosage a été activé par module de gestion des commandes
    // préparer les détails d'un événement qui sera repris par la machine à état
    ...
  } 

  switch (etat) {
    case ...
    case ...
    case ...
    default: ...
  }

PS/ BLE c'est Bluetooth Low Energy (si d'aventure un jour vous vouliez piloter votre truc depuis votre smartphone mais sans ethernet). Les boutons physiques c'est parfois bien aussi si la partie ethernet a un souci (routeur en rade par exemple)

Merci pour l' explication sur BLE = le bluetooth .

vu le code ci dessus , je pense qu ' on peut encore meme simplifier l' ecriture de "gererLesTrucs" , vu que c ' est deja fait avant .
on pourrait se contenter simplement de :

if (commandeWeb.active && commandeWeb.actionCMD_V != NULL_V) {

puisque dans le cas ou
commandeWeb.volet != NULL , ca envoie prealablement a "gererUnVolet" de toute maniere , non ?

en gros vous avez des commandes pour un élément ou tous les éléments d'un groupe et vous êtes responsable des paramètres passés il ne devrait pas en avoir 2 actifs

if (action) {
    if (v)      gererUnVolet(v);
    else if (a) gererUnArrosage(a);
    else if (r) gererUnRelais(r);
    else if (cv != NULL_V) gererLesVolets();
    else if (ca != NULL_A) gererLesArrosages();
    else if (cr != NULL_R) gererLesRelais();
  }
}

oui c ' est ca .
il me reste un petit soucis a regler dans la machine arrosage .

y a un moyen plus propre que ( mettre autre chose ) que des " if (arrosageAutoON) ... " a tout bout de champ dans :

void gererUnArrosage(t_arrosage* unArrosage) {
  switch (unArrosage->etat) {  // INCONNU, EN_ARROSAGE, EN_ATTENTE, TIMER, A_ACTIVER, ARROSE
    case INCONNU:
      for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) initialiserUnArrosage(&(lesArrosages[a]));
      break;
    case EN_ARROSAGE:
      if ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) { // mode manuel
        if (commandeWeb.actionCMD_A == ARRET) {
          if (DEBUG) {
            Serial.print(F("* gererUnArrosage => EN_ARROSAGE => unArrosage-> zone : "));
            Serial.print(unArrosage->zone);
            Serial.print(F(", unArrosage->pinRelais : "));
            Serial.println(unArrosage->pinRelais);
          }
          desactiverUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
        }
      }
      else if (arrosageAutoON) { // mode auto
        
      }
      // inclure les evenements de capteur pour la pluie et le gel
      break;
    case EN_ATTENTE:
      if ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) {
        if (commandeWeb.actionCMD_A == MARCHE) {
          if (DEBUG) {
            Serial.print(F("* gererUnArrosage => case inactif => unArrosage-> zone : "));
            Serial.print(unArrosage->zone);
            Serial.print(F(", unArrosage->pinRelais : "));
            Serial.println(unArrosage->pinRelais);
          }
          activerUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
        }
      }
      else if (arrosageAutoON) {
        attenteUnArrosageAuto(unArrosage);
      }
       // inclure les evenements de capteur pour la pluie et le gel
      break;
    case TIMER:
      if ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) {
        if (commandeWeb.actionCMD_A == TIMERR_A) {
          inverserUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
          if (DEBUG) {
            Serial.print(F("* gererUnArrosage => case TIMER => unArrosage-> zone : "));
            Serial.print(unArrosage->zone);
            Serial.print(F(", unArrosage->pinRelais : "));
            Serial.println(unArrosage->pinRelais);
          }
        }
      }
      break;
    
    case A_ACTIVER:
      if (arrosageAutoON) {
        marcheArrosageAuto(unArrosage);
      }
      break;
    case ARROSE :
      if (arrosageAutoON) {
        retourEnAttenteAuto(unArrosage);
	    }
      break;
    default:
      if (DEBUG) Serial.println(F("* gererUnArrosage => probleme = case non reconnu !"));
      commandeWeb.active = false;
      break;
  }  // fin switch
}

1°) sinon , reste 3 petits soucis avant d' aller plus loin .
si je desactive manuellement un arrosage , alors que le mode auto l ' a mis en route , il s ' arrete , puis se remet en route .

2°) lors de l' activation automatique de l' arrosage , la machine etant deja EN_ATTENTE , le mode auto ne s ' initialize pas en laissant :

if (unArrosage->etat != EN_ATTENTE) {
...

dans la fonction :

void initialArrosageAuto(t_arrosage* unArrosage) {
  // objectif lancer les taches auto a intervalles reguliers
  // et remplir la structure et tableau arrosage avec les valeurs pour travailler
  //if (unArrosage->etat_auto != EN_ATTENTE && unArrosage->etat == INACTIF) {
  
  if (unArrosage->etat != EN_ATTENTE) {
    calculDataArrosageAuto(unArrosage);
    //unArrosage->etat_auto = EN_ATTENTE; // les relais etant dejà en attente , ca n' initialise pas l ' arrosage auto .
    unArrosage->etat = EN_ATTENTE;
    // on active une nouvelle tache ayant pour id le numero de pin + 100 / un temps de 30 secondes / et la fonction automatique d ' arrosage pour callback .
    // NOTE : on n ' active pas les relais ici , il s' agit juste de lancer la tache apres l' intervalle defini .
  }
}

et 3°) , en desactivant le mode auto , alors que les relais sont actifs , le changement d' etat du relais physique s ' opere bien , mais pas sur le serveur web , ca reste a " ON " .

Oui, c'est typiquement le cas que je te cité avec le gel.
Il faut que les conditions soit réunit pour passer dans l'état arrosage.
si on schématise on peut passer dans l'état arrosage si on a une programmation ou le mode manuel à vrai et (pas de gel, pas de manuel à off).
dans l'état arrosage, tu peux en sortir si il y a du gel, fin de programmation, manuel à off.

les valeurs de la variable arrosage_manuel pouvant être ACTIF, INACTIF, INDEFINI, cela represnete donc un "status" de l'arrosage manuel

C'est soit que tu gère mal les états de ta machine en fonction des évènements, soit ce n'est finalement pas un état de ta machine.
Peut tu avoir plusieurs unArrosage à l'état arrosage ?

Si tu dois faire quelque chose (modification de variable du WEB, coupure des relais) tu peux utiliser un état "demande d'arrêt d'arrosage", qui fait les actions nécessaires, puis à la fin passe ton état à en "repos"

pour l'unification de la partie arrosage automatique et manuel en un seul état arrosage ou actif, si tu veux gérer de la même façon des volets que de l'arrosage, je pense que oui.
Mais si cela te parait plus simple de les séparer c'est aussi possible.
Il faut surtout que cela soit simple dans ta tête :slight_smile:

Donc la machine telle que modifiée dans le message 87 n ' est pas bonne , il faut que je fasse une " addition " des etats manuels et auto .
en gros :

switch (unArrosage->etat) {  // INCONNU, ACTIF, INACTIF, EN_ARROSAGE, EN_ATTENTE, TIMER, A_ACTIVER, ARROSE

pour le soucis 3 , je pense que c ' est l' appel de la fonction qui n' est pas bon , le changement d ' etat dedans celle-ci n' est pas le bon .
j ' ai d ' ailleurs remarquer a ce sujet que j ' utilise 2 fonctions presque similaire .
il faut que je n ' en fasse qu ' une qui va gerer a la fois l ' arret auto et manuel , ca me parait simple .

Alors pour moi tu as beaucoup trop d'état.
quel est la différence entre:

  • actif et en arrosage et arrose.
  • inactif et en attente.
    à quoi correspond:
  • inconnu
  • timer

de plus quel évènement fait passer les états à un autre?

vous pourriez inverser et commencer par ce test là mais ça oblige à répéter le switch

void gererUnArrosage(t_arrosage* unArrosage) {
  if (arrosageAutoON) {
    // mode auto
    switch (unArrosage->etat) {
      case INCONNU:     break;
      case EN_ATTENTE:  break;
      case TIMER:       break;
      case A_ACTIVER:   break;
    }
  } else {
    // mode manuel
    switch (unArrosage->etat) {
      case INCONNU:     break;
      case EN_ATTENTE:  break;
      case TIMER:       break;
      case A_ACTIVER:   break;
    }
  }
}

ça a peut être l'avantage de clarifier les choses

tout dépend s'il y a des différences fortes de comportement suivant les case entre le mode auto et le mode manuel.

Vos if / else sont un peu louches , si vous recevez une commande web vous la traiter indépendamment du fait que vous soyez en mode auto ou pas et ça prend la priorité sur le mode ?


1 Like