Temporisation & Machine à états

Bonjour,
Après avoir essayé plusieurs codes, j'ai besoin de votre éclairage sur la temporisation dans le "case" de la machine à états
En effet, dans le code ci dessous, je veux mettre sous tension une électrovanne pendant 10 secondes pour ne pas laisser une tension de 220 V en permanence dans le faisceau électrique.
Cette temporisation fonctionne correctement pour effectuer la fermeture de l'électrovanne, mais c'est un peu plus différent pour l'ouverture car après les 10 secondes, il me reste une tension résiduelle ( plutôt une tension quasi alternative) qui fait vibrer le relais
Votre expertise devrait pouvoir détecter l'anomalie de ma programmation
Cela fait plus d'une semaine que j'ai fait différents essais sans y parvenir avec succès
Merci par avance de votre aide

#define MY_GATEWAY_SERIAL
#include <MySensors.h>

//Definition des NODES: ou NOEUD
#define NODE_ID_FUITE_ZONE1         1
#define NODE_ID_FUITE_TEXTE         4
#define NODE_ID_FUITE_SELECTOR         5

//https://www.mysensors.org/download/serial_api_20
// pour une node fuite d'eau il faut utiliser V_TRIPPED ou V_ARMED
MyMessage msgFuiteZone1(NODE_ID_FUITE_ZONE1, V_TRIPPED);
MyMessage msgFuiteTexte(NODE_ID_FUITE_TEXTE, V_TEXT);
MyMessage msgFuiteSelector(NODE_ID_FUITE_SELECTOR, V_PERCENTAGE);

#include <Wire.h>               // Bibliothèque pour la communication I2C
#include <LiquidCrystal_I2C.h>  // Bibliothèque pour le contrôle de l'écran LCD en mode I2C

LiquidCrystal_I2C lcd(0x27, 20, 4);  // Adresse I2C de l'écran LCD 20x4

const int brocheDebit = 2;         // Broche de la sonde de débit d'eau
const int brocheElectrovanneOuv = 4;  // Broche pour contrôler l'électrovanne ouverte
const int brocheElectrovanneFerm = 15;  // Broche pour contrôler l'électrovanne fermée
const int brocheBouton = 16;        // Broche du bouton-poussoir pour remise à zéro

unsigned int compteImpulsions = 0;
float debitInstantane = 0.0;
float totalLitres = 0.0;  // Changement d'unité de litres à mètres cubes
unsigned long tempsPrecedent = 0;
const long intervalle = 500;                            // Intervalle d'échantillonnage en millisecondes
const unsigned long dureeControleFuite = 1000;  // Durée de controle d'une fuite éventuelle en millisecondes
const unsigned long delaiTempsOuvElectrovanne = 10000;  // Délai du tempds d'ouverture de l'électrovanne en millisecondes
const unsigned long delaiTempsFermElectrovanne = 10000;  // Délai du temps de fermeture de l'électrovanne en millisecondes
unsigned long tempsOuvert = 0;

enum Etat { REPOS,   
            ACTIF,
            STOP };

// tous les éléments contenus dans la structure seront identifiés par un . précédant le nom de l'élément à rechercher
//par exemple, pour identifier le "seuilDebitCoupe", on inscrit "digitalRead(zone.seuilDebitCoupe)" 
struct SZone {            
  const byte broche;    // N° de la broche pour information de la zone d'arrosage
  const float seuilDebitCoupe;  // Débit maxi autorisé
  Etat etat;                    //
  unsigned long tempsDebutDebit;
  unsigned long tempsDebutEtat;
  unsigned long tempsDebutOuvre;
  unsigned long tempsDebutFerme;
  bool emis;
};

SZone zones[] = {
  {5, 1.1, REPOS, 0, 0, false},  // N° broche , Débit maxi, Etat etat , temps début débit, temps début Etat, message émis
  {6, 2.1, REPOS, 0, 0, false},
  {7, 3.1, REPOS, 0, 0, false},
  {8, 4.1, REPOS, 0, 0, false},
  {9, 5.1, REPOS, 0, 0, false},
  {10, 6.1, REPOS, 0, 0, false}
};


void setup() {
  lcd.begin(20, 4);  // Initialisation de l'écran LCD 20x4
  lcd.print("Debit: 0.0 m3/h");
  lcd.setCursor(0, 1);
  lcd.print("Total: 0.0 m3");

  pinMode(brocheDebit, INPUT);
  pinMode(brocheElectrovanneOuv, OUTPUT);
  pinMode(brocheElectrovanneFerm, OUTPUT);
  pinMode(brocheBouton, INPUT_PULLUP);
  
  attachInterrupt(digitalPinToInterrupt(brocheDebit), compteurImpulsions, FALLING);
}

void loop() {
  unsigned long tempsActuel = millis();  // Obtient le temps actuel en millisecondes

if (tempsActuel - tempsPrecedent >= intervalle) {
    // calcul et affichage du débit
    noInterrupts();
    unsigned int compteurLocal = compteImpulsions;  // Conversion du nombre d'impulsions en débit instantané en Litres par minute
    compteImpulsions = 0;
    interrupts();
    tempsPrecedent = tempsActuel;

    float volume=compteurLocal/450.0;   // volume en litre
    debitInstantane=volume/intervalle*1000*60;  // débit en l/mn
    totalLitres += volume;

    lcd.clear();
    lcd.print("Debit: ");
    lcd.print(debitInstantane, 0);
    lcd.print(" L/mn");

    lcd.setCursor(0, 1);
    lcd.print("Total: ");
    lcd.print(totalLitres, 0);
    lcd.print(" L");
  }

  for (auto &zone : zones)  // effectue le traitement pour toutes les zones
  {

    if (digitalRead(zone.broche) == HIGH) {  //Lecture de l'information de l'arrosage de la Zone
          

      switch (zone.etat) {
        case REPOS:
             digitalWrite(brocheElectrovanneOuv, HIGH);
                       
         if (tempsActuel - zone.tempsDebutOuvre >= delaiTempsOuvElectrovanne){
          digitalWrite(brocheElectrovanneOuv, LOW);}
          
          if (debitInstantane > zone.seuilDebitCoupe) {
            zone.tempsDebutDebit = tempsActuel;
            zone.etat = ACTIF;
          }
          break;

        case ACTIF:

          if (debitInstantane > zone.seuilDebitCoupe) {
            if (tempsActuel - zone.tempsDebutDebit >= dureeControleFuite) {
              digitalWrite(brocheElectrovanneFerm, HIGH);
              zone.tempsDebutFerme = tempsActuel;

              zone.etat = STOP;
              zone.tempsDebutEtat = tempsActuel;
             
            }
          }
          else {
            zone.etat = REPOS;
            zone.tempsDebutEtat = tempsActuel;
            zone.tempsDebutOuvre = tempsActuel;

          }
          break;

        case STOP:
          const int zoneIndex = &zone - zones + 1;
          lcd.setCursor(0, 2);
          lcd.print("Fuite Zone ");       // Affiche un avertissement de fuite
          lcd.print(zoneIndex);  // affiche numéro de zone
          if (tempsActuel - zone.tempsDebutFerme >= delaiTempsFermElectrovanne){
              digitalWrite(brocheElectrovanneFerm, LOW);}

          // On ne signale l'erreur que si ça n'a pas déjà été fait
          if (zone.emis == false) {
            char messageFuite[] = "Fuite zone 0";
            messageFuite[11] += zoneIndex; 
            send(msgFuiteTexte.set(messageFuite));
            zone.emis = true;
          }
          if (digitalRead(brocheBouton) == LOW) {
            if (tempsActuel - zone.tempsDebutEtat >= 2000) {
              zone.etat = REPOS;
              zone.emis = false;
              lcd.setCursor(0, 0);
              lcd.print("                 ");  // Efface le texte "Fuite" lors du retour à l'état REPOS
              lcd.setCursor(0, 1);
              lcd.print("                 ");  // Efface le texte "Fuite" lors du retour à l'état REPOS
            }
          } else {
            zone.tempsDebutEtat = tempsActuel;
            zone.tempsDebutOuvre = tempsActuel; 
          }
          break;
      }
    }
  }
}

  void compteurImpulsions() {
    compteImpulsions++;
  }
//Fonction de presentation a rajouter
void presentation()
{
  // Present locally attached sensors
  present(NODE_ID_FUITE_ZONE1, S_WATER_LEAK);
  present(NODE_ID_FUITE_TEXTE, S_INFO);
  present(NODE_ID_FUITE_SELECTOR, S_DIMMER);
}

Sans être un expert, loin de là, je chercherai plutôt un problème matériel, de conception de la commande du relais ou de câblage… je ne vois pas trop comment ton code pourrait faire vibrer le relais.

Bonjour bobardui1

Ton programme est trop complexe pour que je l'essaye, mais je te dis comment je procéderai pour débugger, avec des Serial.println aux endroits où tirent les relais:

	for (auto &zone : zones)  // effectue le traitement pour toutes les zones
	{

		if (digitalRead(zone.broche) == HIGH) {  //Lecture de l'information de l'arrosage de la Zone
			

			switch (zone.etat) {
				case REPOS:
				digitalWrite(brocheElectrovanneOuv, HIGH);
Serial.println("ElectrovanneOuv, HIGH");				
				if (tempsActuel - zone.tempsDebutOuvre >= delaiTempsOuvElectrovanne){
				digitalWrite(brocheElectrovanneOuv, LOW);}
Serial.println("ElectrovanneOuv, LOW");
				
				if (debitInstantane > zone.seuilDebitCoupe) {
					zone.tempsDebutDebit = tempsActuel;
					zone.etat = ACTIF;
				}
				break;

				case ACTIF:

				if (debitInstantane > zone.seuilDebitCoupe) {
					if (tempsActuel - zone.tempsDebutDebit >= dureeControleFuite) {
						digitalWrite(brocheElectrovanneFerm, HIGH);
Serial.println("brocheElectrovanneFerm, HIGH");
						zone.tempsDebutFerme = tempsActuel;

						zone.etat = STOP;
						zone.tempsDebutEtat = tempsActuel;
						
					}
				}
				else {
					zone.etat = REPOS;
					zone.tempsDebutEtat = tempsActuel;
					zone.tempsDebutOuvre = tempsActuel;

				}
				break;

				case STOP:
				const int zoneIndex = &zone - zones + 1;
				lcd.setCursor(0, 2);
				lcd.print("Fuite Zone ");       // Affiche un avertissement de fuite
				lcd.print(zoneIndex);  // affiche numéro de zone
				if (tempsActuel - zone.tempsDebutFerme >= delaiTempsFermElectrovanne){
				digitalWrite(brocheElectrovanneFerm, LOW);}
Serial.println("brocheElectrovanneFerm, LOW");

en mettant dans setup():

	Serial.begin(115200);

dans la console à 115200, tu "verra" fonctionner tes relais.

A+
Cordialement
jpbbricole

Je ne suis pas un expert du matériel, donc je ne saisie pas bien ce qu'est une tension résiduelle alternative.
Par contre si il s'agit d'un relais activer et désactiver en permanence à cause potentiellement du code.
tu peux chercher dans le code suivant, qui me parait potentiellement inadéquate(ou pas :slight_smile: )

digitalWrite(brocheElectrovanneOuv, HIGH);

if (tempsActuel - zone.tempsDebutOuvre >= delaiTempsOuvElectrovanne) {
    digitalWrite(brocheElectrovanneOuv, LOW);
}

if (debitInstantane > zone.seuilDebitCoupe) {
    zone.tempsDebutDebit = tempsActuel;
    zone.etat = ACTIF;
}
break;

Dans ce code le changement d'état en ACTIF me semble indépendant du passage à LOW de ton électrovanne.
c'est à dire que lorsque le temps et dépassé, tu va couper le courant à ton électrovanne, mais tu ne change pas forcément d'état ?
Du coup au prochain passage tu actives à nouveau ton électrovanne ?

Pour ce genre de cas il serait intéressant d'avoir un état, pour chaque cas.
Je n'ai pas trop pourquoi dans ton état repos, tu active ta vanne ?

Rien à voir, mais je n'ai pas trouvé comment zone.broche passe à HIGH

Il y a un problème entre la définition de la structure SZones et l'initialisation de zones. Les champs ne correspondent pas.
Tu définis 8 champs et tu n'en initialises que 6. Typiquement dans zones, le booléen false correspond à l'unsigned long tempsDebutOuvre dans la définition.

Merci à fdufnews de m'avoir ouvert les yeux sur mon erreur, en effet de plus je vérifiais mon code et du moins je trouvais l'incohérence
J'ai ajouté un autre état pour isoler l'ouverture de l'électrovanne tout en sécurisant le risque de fermeture en même temps que l'ouverture ( et vice versa)
Voici mon nouveau code et je m'aperçois que si je change de broche (zone.broche) qui est la source d'information pour déclencher la machine à état, l'ouverture de l'électrovanne ne se fait pas systématiquement. Là encore, je dois passer à coté d'une erreur (simple peut-être)

{
  unsigned long tempsActuel = millis();  // Obtient le temps actuel en millisecondes

if (tempsActuel - tempsPrecedent >= intervalle) {
    // calcul et affichage du débit
    noInterrupts();
    unsigned int compteurLocal = compteImpulsions;  // Conversion du nombre d'impulsions en débit instantané en Litres par minute
    compteImpulsions = 0;
    interrupts();
    tempsPrecedent = tempsActuel;

    float volume=compteurLocal/450.0;   // volume en litre
    debitInstantane=volume/intervalle*1000*60;  // débit en l/mn
    totalLitres += volume;

    lcd.clear();
    lcd.print("Debit: ");
    lcd.print(debitInstantane, 0);
    lcd.print(" L/mn");

    lcd.setCursor(0, 1);
    lcd.print("Total: ");
    lcd.print(totalLitres, 0);
    lcd.print(" L");
  }

  for (auto &zone : zones)  // effectue le traitement pour toutes les zones
  {

    if (digitalRead(zone.broche) == HIGH) {  //Lecture de l'information de l'arrosage de la Zone
          

      switch (zone.etat) {
        case REPOS:
             digitalWrite(brocheElectrovanneOuv, LOW);
             digitalWrite(brocheElectrovanneFerm, HIGH);          
         if (tempsActuel - zone.tempsDebutOuvre >= delaiTempsOuvElectrovanne){
          digitalWrite(brocheElectrovanneOuv, HIGH);}
          
          if (digitalRead(brocheElectrovanneOuv) == HIGH) {
             zone.etat = OUVERT;
          }
          break;
                  case OUVERT:
          
          if (debitInstantane > zone.seuilDebitCoupe) {
            zone.tempsDebutDebit = tempsActuel;
            zone.etat = ACTIF;
          }
          break;

        case ACTIF:

          if (debitInstantane > zone.seuilDebitCoupe) {
            if (tempsActuel - zone.tempsDebutDebit >= dureeControleFuite) {
              digitalWrite(brocheElectrovanneOuv, HIGH);
              digitalWrite(brocheElectrovanneFerm, LOW);
              zone.tempsDebutFerme = tempsActuel;

              zone.etat = STOP;
              zone.tempsDebutEtat = tempsActuel;
             
            }
          }
          else {
            zone.etat = REPOS;
            zone.tempsDebutEtat = tempsActuel;
            zone.tempsDebutOuvre = tempsActuel;

          }
          break;

        case STOP:
          const int zoneIndex = &zone - zones + 1;
          lcd.setCursor(0, 2);
          lcd.print("Fuite Zone ");       // Affiche un avertissement de fuite
          lcd.print(zoneIndex);  // affiche numéro de zone
          if (tempsActuel - zone.tempsDebutFerme >= delaiTempsFermElectrovanne){
              digitalWrite(brocheElectrovanneFerm, HIGH);}

          // On ne signale l'erreur que si ça n'a pas déjà été fait
          if (zone.emis == false) {
            char messageFuite[] = "Fuite zone 0";
            messageFuite[11] += zoneIndex; 
            send(msgFuiteTexte.set(messageFuite));
            zone.emis = true;
          }
          if (digitalRead(brocheBouton) == LOW) {
            if (tempsActuel - zone.tempsDebutEtat >= 2000) {
              zone.etat = REPOS;
              zone.emis = false;
              lcd.setCursor(0, 0);
              lcd.print("                 ");  // Efface le texte "Fuite" lors du retour à l'état REPOS
              lcd.setCursor(0, 1);
              lcd.print("                 ");  // Efface le texte "Fuite" lors du retour à l'état REPOS
            }
          } else {
            zone.tempsDebutEtat = tempsActuel;
            zone.tempsDebutOuvre = tempsActuel; 
          }
          break;
      }
    }
  }
}

A l'entré dans ce case brocheElectrovanneOuv est mise à LOW. Le seul cas ou elle pourrait être à HIGH c'est si la condition du premier if a été remplie. Donc autant l'écrire comme ça:

        case REPOS:
             digitalWrite(brocheElectrovanneOuv, LOW);
             digitalWrite(brocheElectrovanneFerm, HIGH);          
             if (tempsActuel - zone.tempsDebutOuvre >= delaiTempsOuvElectrovanne){
                digitalWrite(brocheElectrovanneOuv, HIGH);
               zone.etat = OUVERT;
            }
        break;

On ne peut pas utiliser l'addition sur une cstring. Il faut utiliser strcat() ou sprintf()

Bonsoir fdufnews

A l'entré dans ce case brocheElectrovanneOuv est mise à LOW. Le seul cas ou elle pourrait être à HIGH c'est si la condition du premier if a été remplie. Donc autant l'écrire comme ça:
l'electrovanne est pilotée par un module relais qui est activé par un état " LOW "
Est-ce cela correspond à ta proposition de code, car j'ai essayé et cela ne change rien ?

le principe est que la détection de la zone est faite par if (digitalRead(zone.broche) == HIGH) { //Lecture de l'information de l'arrosage de la Zone
Et c'est à partir du changement de l'état de cette broche que l'électrovanne doit être activée (LOW) avec une temporisation

Je n'ai pas tout compris, car le code me permet d'être alerté sur mon Domoticz, et cela fonctionne
Qu'ai-je loupé ( je ne suis pas expert dans ce domaine) ?

Bonsoir Terwal
C'est une information qui arrive d'un programmateur d'arrosage multizones
ce qui me permet d'avoir une seule vanne motorisée pour plusieurs zones d'arrosage en sachant qu'aucunes zones ne se superposent

Au temps pour moi. Je n'avais pas vu que c'était une ruse infâme pour transformer le dernier caractère de la chaîne. En première lecture j'avais cru à une concaténation de chaîne. Le problème avec cette solution c'est que la valeur que tu insères dans la chaîne ne peut pas dépasser 9.

Et si je veux avoir 10, voire 12 zones à surveiller, quelle solution me préconise-tu ?

Un exemple avec sprintf

void setup() {
  char buffer[20] ;
  int zone = 12;

  Serial.begin(115200);
  sprintf(buffer, "Fuite zone %d", zone);
  Serial.println(buffer);
}

void loop() {
  // put your main code here, to run repeatedly:

}

Le buffer doit être assez grand pour contenir la chaîne constante + le nombre à insérer + le terminateur de chaîne.

Merci pour ton code, je vais essayé de l'appliquer pour bien comprendre chaque partie

Bonsoir,
Je continue à rechercher mes erreurs sur la temporisation et je vous sollicite une fois de plus après avoir fait plusieurs essais sans arriver à ce que je souhaite.
En effet je veux avoir une durée différente pour l'ouverture et la fermeture
Dans la section déclaration :

unsigned long tempsOuvEst = 0;

Dans le loop

 unsigned long tempsActuel = millis();  
if (tempsActuel - tempsOuvEst >= 10000){

   servoOuest.write(0);  // Vanne Ouest Ouverte
      tempsOuvEst=tempsActuel;
}
 
      if (tempsActuel - tempsOuvEst >= 15000)

 { 
 servoOuest.write(90); // Fermeture Vanne Est
     tempsOuvEst=tempsActuel;
     
  }  

Merci par avance de votre aide

Les if se suivent comme ça dans ton code?
Si c'est le cas il manque une variable dans les tests pour distinguer l'ouverture de la fermeture. Le premier test est vrai aussi lorsque le delta de temps est supérieur à 15000.

en cas de doute (ou pour éviter tout problème) on peut utiliser snprintf()

void setup() {
  char buffer[20] ;
  int zone = 12;

  Serial.begin(115200);
  snprintf(buffer, sizeof buffer, "Fuite zone %d", zone); // https://cplusplus.com/reference/cstdio/snprintf/
  Serial.println(buffer);
}

void loop() {}

oui, mais j'ai peut-être tord de faire ainsi ?
Mon objectif est de faire ouvrir une vanne pendant 5 mn, puis de mesurer la T°, et de fermer cette vanne pendant 40 mn, ...ainsi de suite ( j'ai mis des secondes pour faire mes essais, c'est plus facile à voir les ouvertures / fermetures)
Je suis perdu dans le positionnement des différentes étapes des temporisations millis et leur positionnement dans la chronologie du code
Mon but est de bien comprendre ce fonctionnement
Merci

Vous pouvez poster le dessin de votre machine à états avec les transitions et actions associées?

Bonjour ,

Etat de Repos = Vanne fermée
Ouverture de la vanne pendant un certain temps
Mesure de la T°
Si la T° > Seuil => La vanne reste ouverte
Si la T°< Seuil => la vanne est fermée pendant un certain temps