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

Bonjour , je n' arrive pas a faire enclenché mon relais pour le portail .
je ne rentre pas dans le switch case et je n' arrive pas a trouver la trace de là ou l' info se perd en route .

Voici le code si certains ont les yeux assez affutés :stuck_out_tongue:

#define RELAIS_ACTIF LOW
#define RELAIS_INACTIF HIGH

enum t_commandeRelais : uint8_t {ACTIVE, INACTIVE, TIMERR_R, NULL_R};
enum t_etatRelais : uint8_t {R_ACTIF, R_INACTIF, R_TIMER};
struct t_relais {
  uint8_t     	pinRelais;
  t_etatRelais  etat;
  const char* 	nomRelais;
  uint32_t    	timerRelais;
};

t_relais lesRelais[] = {
{44, R_INACTIF, "Portail", TIMER_P},
{45, R_INACTIF, "Heures creuses", 0}
};
const uint8_t NOMBREDERELAIS = sizeof(lesRelais) / sizeof(t_relais);

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

////*   Commandes web    *////
enum : uint8_t {VOLETS_GLOBAL, VOLET_OUVRIR, VOLET_FERMER, VOLET_ARRETER, PORTAIL, ARROSAGE_MARCHE, ARROSAGE_ARRET, ARROSAGE_AUTO, RESTE}; // switch case du parseCommand
struct t_commandeWeb {
  bool 					      active;
  t_volet* 			    	volet; // le pointeur du volet à commander
  t_commandeVolet 		actionCMD_V;
  t_arrosage*			  	arrosage;
  t_commandeArrosage	actionCMD_A;
  t_relais*           relais;
  t_commandeRelais    actionCMD_R;
};
t_commandeWeb commandeWeb;


void parseCommand() {
  char * item;
  boolean labelFound;
  long int labelValue = -10;
  int labelIndex;

  item = strtok(urlCommand, "=");

  while (item) {
    labelFound = false;
    for (unsigned int i = 0; i < MAX_LABELS_OF_INTEREST; ++i) {
      if (!strcmp(LABELS_OF_INTEREST[i], item)) {
        item = strtok (NULL, ",");
        if (item != NULL) {
          labelFound = true;
          labelIndex = i;
          // then parse the value , we expect integers
          labelValue = atol(item);
          if (DEBUG) {
            Serial.print ("labelIndex  = ");
            Serial.println (labelIndex );
            Serial.print ("labelValue = ");
            Serial.println (labelValue);
          }
        } // end if we had a value for the label
      } // end string compare
    } // end for

    // ****************************************************************
    // THIS IS WHERE YOU PERFORM YOUR ACTIONS FOR EACH VARIABLE FOUND
    // ****************************************************************
    if (labelFound && verifPinOK(labelValue)) {
      switch (labelIndex) { // VOLETS_GLOBAL, VOLET_OUVRIR, VOLET_FERMER, VOLET_ARRETER, PORTAIL, ARROSAGE, RESTE};
        case VOLETS_GLOBAL :// "VG", "VO", "VF","VA", "P", "AG", "AM", "AA", "R"
          if(DEBUG) Serial.print(F("* parseCommand => VOLETS_GLOBAL "));
          if (labelValue == -2) { // on arrete tous les volets
            if(DEBUG) Serial.println(F(" => ARRETER_TOUT"));
            //arreterLesVolets();
            affecteCommandeWeb(true, NULL, ARRETER_TOUT);
          }
          else if (labelValue == -3) { // on ouvre tous les volets
            if(DEBUG) Serial.println(F(" => OUVRIR_TOUT"));
            //ouvrirLesVolets();
            affecteCommandeWeb(true, NULL, OUVRIR_TOUT);
          }
          else if (labelValue == -4) { // on ferme tous les volets
            if(DEBUG) Serial.println(F(" => FERMER_TOUT"));
            //fermerLesVolets();
            affecteCommandeWeb(true, NULL, FERMER_TOUT);
          }
          break;

        case VOLET_OUVRIR :
          if(DEBUG) Serial.println(F("* parseCommand => VOLET_OUVRIR"));

          affecteCommandeWeb(true, &(lesVolets[labelValue]), OUVRIR);
          break;

        case VOLET_FERMER :
          if(DEBUG) Serial.println(F("* parseCommand => VOLET_FERMER"));

          affecteCommandeWeb(true, &(lesVolets[labelValue]), FERMER);
          break;

        case VOLET_ARRETER :
          if(DEBUG) Serial.println(F("* parseCommand => VOLET_ARRETER"));

          affecteCommandeWeb(true, &(lesVolets[labelValue]), ARRETER);
          break;

        case PORTAIL :
          if(DEBUG) {
            Serial.print(F("* parseCommand => PORTAIL => labelValue = "));
            Serial.println(labelValue);
          }
          //if (! TEST) inverserUnRelaisEtUnTimer (&(lesRelais[0])); // fonctionne aussi
          for (uint8_t r = 0; r < NOMBREDERELAIS; r++) {
            if (lesRelais[r].pinRelais == labelValue) {
              //if (! TEST) inverserUnRelaisEtUnTimer (&(lesRelais[r])); // fonctionne aussi
              
              // Sinon , pour etre uniforme : 
              affecteCommandeWeb(true, NULL, NULL_V, NULL, NULL_A, &(lesRelais[r]), NULL_R);
              // n ' a pas l ' air de vouloir fonctionner sans un enum etat relais specifique              
              
              if(DEBUG) {
                Serial.print(F("* parseCommand => PORTAIL => envoi = "));
                Serial.println(lesRelais[r].pinRelais);
              }
            }
          }
          break;

        case ARROSAGE_MARCHE : // ARROSAGE_MARCHE, ARROSAGE_ARRET, ARROSAGE
          if(DEBUG) Serial.println(F("* parseCommand => ARROSAGE => single => MARCHE"));
          affecteCommandeWeb(true, NULL, NULL_V, &(lesArrosages[labelValue]), MARCHE); // passage d ' un int a un pointeur : &(lesArrosages[labelValue])
          break;

        case ARROSAGE_ARRET :
          if(DEBUG) Serial.println(F("* parseCommand => ARROSAGE => single => ARRET"));
          affecteCommandeWeb(true, NULL, NULL_V, &(lesArrosages[labelValue]), ARRET);
          break;

        case ARROSAGE_AUTO :
          if (labelValue == -1) {
            arrosageAutoON = !arrosageAutoON;
            affecteCommandeWeb(true, NULL, NULL_V, NULL, AUTOMA);
            if(DEBUG) {
              Serial.print(F("* parseCommand => ARROSAGE => AUTO : "));
              Serial.println ( arrosageAutoON == false ? F("OFF") : F("ON"));
            }
            //if (arrosageAutoON == false) // il faut desactiver les arrosages et apeller les bonnes fonctions
          }
          //else if (labelValue == -2) initArrosageOK = NOMBREDARROSAGE; // reinitialise , on ne l ' appelle pas manuellement ?
          else if (labelValue == -3) {
            if(DEBUG) Serial.println(F("* parseCommand => ARROSAGE => MARCHE_TOUT"));
            affecteCommandeWeb(true, NULL, NULL_V, NULL, MARCHE_TOUT);
          }
          else if (labelValue == -4) {
            if(DEBUG) Serial.println(F("* parseCommand => ARROSAGE => ARRET_TOUT"));
            affecteCommandeWeb(true, NULL, NULL_V, NULL, ARRET_TOUT);
          }
          break;

        case RESTE :
          break;
        default :
          if(DEBUG) Serial.println(F("* parseCommand => case INCONNU !!"));
          break;
      } // fin du switch
    } // fin if
    else {
      item = strtok (NULL, ","); // skip this label value, not recognized
    }
    item = strtok (NULL, "="); // got to next one - will modify urlCommand
  } // fin boucle
}

//// Fonctions de gestion des relais : ////
bool verifPinOK (int8_t pin) { // on s' assure grace a cette fonction que le pin qu ' on recoit est bien un numero valide compris dans la plage de pin que doit gerer notre serveur.
	return (pin >= -4 && pin < 53);
}

void initialiserUnRelais (t_relais* unRelais) {
  pinMode(unRelais->pinRelais, OUTPUT);
  digitalWrite(unRelais->pinRelais, RELAIS_INACTIF); // à l'arret
  if (DEBUG) {
    Serial.print (F("* initialiserUnRelais => PIN : "));
    Serial.println (unRelais->pinRelais);
  }
}

void activerUnRelais(t_relais* unRelais) {
	if (unRelais->etat != R_ACTIF) {
		if (!TEST) digitalWrite(unRelais->pinRelais, RELAIS_ACTIF);
		unRelais->etat = R_ACTIF;
	}
}

void desactiverUnRelais(t_relais* unRelais) {
	if (unRelais->etat != R_INACTIF) {
		if (!TEST) digitalWrite(unRelais->pinRelais, RELAIS_INACTIF);
		unRelais->etat = R_INACTIF;
	}
}

void inverserUnRelais(t_relais* unRelais) {
  if (unRelais->etat != R_ACTIF) activerUnRelais(unRelais);
  else desactiverUnRelais(unRelais);
}

void inverserUnRelaisEtUnTimer(t_relais* unRelais) {
  if (unRelais->etat != R_ACTIF) activerUnRelaisEtUnTimer (unRelais);
  else if (unRelais->etat != R_INACTIF) desactiverUnRelaisEtUnTimer (unRelais);
}

void activerUnRelaisEtUnTimer (t_relais* unRelais) {
  activerUnRelais(unRelais);
  activerUnTimerRelais(unRelais);
}

void desactiverUnRelaisEtUnTimer (t_relais* unRelais) {
  desactiverUnRelais(unRelais);
  desactiverTimerRelais(unRelais->pinRelais);
}

void gererLesRelais () {
  
  if (commandeWeb.active) {
    /*
    if (commandeWeb.volet == NULL && commandeWeb.actionCMD_V == NULL_V  && commandeWeb.arrosage == NULL && commandeWeb.actionCMD_A == NULL_A && commandeWeb.actionCMD_R == NULL_R) {
      commandeWeb.active=false;
      if (DEBUG) {
        Serial.print (F("* gererLesRelais , 1er if "));
      }
    }
  */
    if (commandeWeb.relais == !NULL) for (uint8_t r = 0; r < NOMBREDERELAIS; r++) gererUnRelais(&(lesRelais[r]));
  }
  //else for (uint8_t r = 0; r < NOMBREDERELAIS; r++) gererUnRelais(&(lesRelais[r]));
}

void gererUnRelais (t_relais* unRelais) {
	switch (unRelais->etat) { // R_ACTIF, R_INACTIF, R_TIMER
		case R_INACTIF : // actionCMD_R = ACTIVE, INACTIVE, TIMERR_R, NULL_R
			if ((commandeWeb.active) && (commandeWeb.relais == unRelais)) {
				if (commandeWeb.actionCMD_R == ACTIVE) {
          if (DEBUG) {
            Serial.print (F("* gererUnRelais => case INACTIF => unRelais->pinRelais : "));
            Serial.println (unRelais->pinRelais);
          }
					activerUnRelais(unRelais);
					commandeWeb.active = false;
				}
			}
			break;

		case R_ACTIF :
			if ((commandeWeb.active) && (commandeWeb.relais == unRelais)) {
				if (commandeWeb.actionCMD_R == INACTIVE) {
          if (DEBUG) {
            Serial.print (F("* gererUnRelais => case ACTIF => unRelais->pinRelais : "));
            Serial.println (unRelais->pinRelais);
          }
					desactiverUnRelais(unRelais);
					commandeWeb.active = false;
				}
			}
			break;
    case R_TIMER :
      if ((commandeWeb.active) && (commandeWeb.relais == unRelais)) {
				if (commandeWeb.actionCMD_R == TIMERR_R) {
          if (DEBUG) {
            Serial.print (F("* gererUnRelais => case TIMERR_R =>unRelais->pinRelais : "));
            Serial.println (unRelais->pinRelais);
          }
					inverserUnRelais(unRelais);
					commandeWeb.active = false;
				}
      }
      break;
     default:
      if (DEBUG) Serial.println (F("* gererUnRelais => probleme = case non reconnu !"));
      break;
	} // fin switch
  // comme cette fonction est appellé en permanence , il vaut mieux eviter de mettre quoi que ce soit ici
}

/************************************************************************************/
// version standard

// on recoit le pin du timer asynctask ...
// cette fonction sert uniquement a desactiver le bon relais et mettre a jour l' etat .
// elle est appelée uniquement par le callback du gestionnaire de tache , voir fichier timers_rtc.ino
void desactiverRelais(t_commandID pin) {
  for (uint8_t i = 0; i < NOMBREDERELAIS; i++) {
    if (lesRelais[i].pinRelais == pin) {
      lesRelais[i].etat = R_INACTIF;
      if (!TEST) digitalWrite(lesRelais[i].pinRelais, RELAIS_INACTIF);
      if (DEBUG) {
        Serial.print (F("* desactiverRelais => indice = "));
        Serial.print (i);
        Serial.print (F(", relais = "));
        Serial.println (lesRelais[i].pinRelais);
      }
    }
  }
}

void activerUnTimerRelais (t_relais* unRelais) {
  // si une tache existe deja , on la remplace par la nouvelle , donc , 1ere chose , on supprime la tache si elle existe deja .
  desactiverTimerRelais(unRelais->pinRelais);

  if (!gestionnaireDeTache.findAsyncCommand(unRelais->pinRelais)) {
    gestionnaireDeTache.registerAsyncCommand(unRelais->pinRelais, unRelais->timerRelais, desactiverRelais); // normal le reinit ici : oui fonction de callback ( c ' est elle qui reinitialisera le relais a la fin du timer )    
    if (DEBUG) {
      Serial.print (F("* activerTimerRelais => tache "));
      Serial.print (unRelais->pinRelais);
      Serial.print (F(" enregistrée pour dans "));
      Serial.println (unRelais->timerRelais);
    }
  }
}

void desactiverTimerRelais (uint8_t pin) { // en cas de suppression manuelle du timer 
  if (gestionnaireDeTache.findAsyncCommand(pin)) {
    gestionnaireDeTache.unregisterAsyncCommand(pin);
    if (DEBUG) {
      Serial.print (F("* desactiverTimerRelais => tache : "));
      Serial.print (pin);
      Serial.println (F(" supprimee"));
    }
  }
}

//// FIN Des Fonctions de gestion des relais ////

void setup() {
  if (DEBUG) Serial.begin(115200);

  Wire.begin();
  initWebServer ();
  initGestionnaireDeTache ();
  for (uint8_t r = 0; r < NOMBREDERELAIS; r++) initialiserUnRelais(&(lesRelais[r]));
}

void loop() {
  actualiserRTC();
  gestionnaireDeTache.updateQueue();
  //if (renewDHCPLease())
  handleClient();	// we still have a DHCP IP
  gererLesRelais();
}

Quelle commande envoyez vous ?

Bonjour @J-M-L , la commande envoyée est la suivante :

http://192.168.1.111/P=44

lorsque j' essaie d' ouvrir le portail , apres le clic , je recois du moniteur serie :

09:02:49.816 -> labelIndex  = 4
09:02:49.817 -> labelValue = 44
09:02:49.817 -> * parseCommand => PORTAIL => labelValue = 44
09:02:49.817 -> * parseCommand => PORTAIL => envoi = 44

S'il affiche ça c'est qu'il rentre dans le switch et qu'il case bien la valeur PORTAIL.

oui clairement ça rentre dans ce case

donc en fait ce que vous voulez dire c'est que cette ligne

affecteCommandeWeb(true, NULL, NULL_V, NULL, NULL_A, &(lesRelais[r]), NULL_R);

ne fait pas ce que vous voulez ?

qu'en attendez vous et où est elle définie ?

jusque là , on est d' accord , c ' est celui d' apres que ca ne va plus .

la ligne :

affecteCommandeWeb(true, NULL, NULL_V, NULL, NULL_A, &(lesRelais[r]), NULL_R);

est ensuite " recupere " par la fonction :

void gererLesRelais () 

qui elle meme renvoie ( ou est censée du moins ) renvoyée sur :
gererUnRelais ()

c ' est une de ses 2 etapes qui pose a priori probleme . vu que je n' ai pas de sortie moniteur debug , je dirais plutot que c ' est affectecommandeweb qui ne va pas , mais sans certitude . j ' ai essayé plein de choses differentes mais , peu importe , y a rien qui sort de la fonction " gererUnRelais " et c ' est là que se situe mon soucis .

tu ne donne pas le code de la fonction affectecommandeweb, c'est donc difficile de savoir comment ou si elle met à jour la structure commandeWeb

quand définit tu la valeur de commandeWeb.active en dehors de gererUnRelais?

Bonjour terwal , je pensais l a voir mis mais non , il n y est pas ici .
je le mettrais ce soir en rentrant chez moi .

les valeurs affectées a commandweb sont dans le switch - case de la fonction "parseCommand " .
comme l ' a decrit @J-M-L dans son post precedent .

Je pense que l'on s'est mal compris.
@J-M-L te demande aussi ce que affecteCommandeWeb est censé faire et donc comment elle modifie commandeWeb.active, qui doit être à vrai, dans un prmier temps pour que cela fasse ce que tu demande.
Et dans un second temps, que vaut commandeWeb.relais

tien d'ailleurs pourquoi tu écris commandeWeb.relais == !NULL et non commandeWeb.relais != NULL
qui serait plus lisible et surement plus juste.
Je n'ai pas tester mais !NULL doit renvoyer 1, qui ne sera jamais égale à ton pointeur, je pense?

affecteCommandeWeb , est justement la façon d' affecter une action a effectuer en fonction des parametres que l' on defini dans parseCommand et envoyé et recu par le serveur web . c ' est dans parseCommand que j ' " attribue " les bons ( en theorie ) parametres , ce qui permet de renvoyer sur les fonctions adequates

commandeWeb.relais , est defini ( dans la structure que je donnerais ce soir ) pour etre un pointeur vers la structure t_relais .

pour ce qui est de " commandeWeb.relais == !NULL " , je n' ai pas corrigé ici , oubli de ma part ... mais je me suis fait exactement la meme remarque que toi , et j' ai corrigé hier soir , juste apres avoir posté mon message sur le forum .
j ' y a ai cru , mais , apres tests , non , resultat identique :face_with_thermometer:

redonnez tout le code ce sera plus simple

Moi, je te crois, mais comme tu semble indiquer que cela ne se passe pas comme tu veux, c'est qu'il y a un soucis quelque pars, donc la première chose à regarder c'est ton premier if.
Pourquoi tu ne peux pas avoir accès au port série, cela serait bien plus pratique.

j ' ai acces au moniteur serie , aucun soucis de ce coté là .
Mais ca fait 3 soirs que je cherche d ' ou peut provenir le probleme , je n' y arrive pas :hot_face:

je vous donnerai ce soir le code , dans environ une heure . ca risque de faire pas mal de ligne .

ha oui, tu as donné la sortie du moniteur :woozy_face:

Donc c'est bien ce qu'il y a dans ta fonction affecteCommandeWeb qui est intéressant.

Alors voilà le fichier principal :

// test du 28/02/2023 a 18 H 20


#include <SPI.h>
#include <Ethernet.h>
#include <Wire.h>
#include "AsyncTask.h"
#include "simpleRTC.h"
#define ENABLE_SDCARD false  // enable  disable SDcard and uncomment library if needed below
#if ENABLE_SDCARD
  #include <SdFat.h> // bibliotheque pour carte SD
#endif
/*************************************/
// Pour avoir un mode debug de notre programme
#define DEBUG true              // à mettre à false si on ne veut pas de debug
#define DEBUGSTREAM Serial

#if DEBUG
  #define DEBUG_PRINT(...)    DEBUGSTREAM.print(__VA_ARGS__)
  #define DEBUG_PRINTLN(...)  DEBUGSTREAM.println(__VA_ARGS__)
  #else
  #define DEBUG_PRINT(...)
  #define DEBUG_PRINTLN(...)
#endif

const bool TEST = 0; // active ou desactive les digitalWrite sur pin
const char* NUMERO_VERSION = "machine etats j-M-L 7.329";

const unsigned long TIMER_A = 7200000UL, TIMER_P = 120000UL, TIMER_V = 30000UL; // 2 heures , 2 minutes et 30 sec.

const unsigned int // pour les heures creuses
HC_START_TIME_N = 1*60 + 30,// 1 H 30 min = 90
HC_END_TIME_N = 7*60 +30,   // 7 H 30 min = 450
HC_START_TIME_D = 12*60,    // 12 h 00 min = 720
HC_END_TIME_D = 14*60;      // 14 h 00 min = 8940
/*************************************/

uint8_t MacAddress[] = {0x90, 0xA2, 0xDA, 0x10, 0x2F, 0x93};
IPAddress ip(192,168,1,111); // L'adresse IP que prendra le shield ethernet
// mettre (0,0,0,0) pour laisser en serveur DHCP sinon ce sera une adresse IP statique .
const unsigned int SERVER_PORT = 80;
EthernetServer webServer(SERVER_PORT);
EthernetUDP Udp;

////*   Volets    *////
enum t_commandeVolet : uint8_t {FERMER, OUVRIR, ARRETER, FERMER_TOUT, OUVRIR_TOUT, ARRETER_TOUT, NULL_V};
enum t_etatVolet : uint8_t {OUVERT, FERME, EN_FERMETURE, EN_OUVERTURE, ENTROUVERT};
struct t_volet {
  uint8_t     	pinRelaisOuverture;		//1
  uint8_t     	pinRelaisFermeture;		//2
  t_etatVolet 	etat;				        	//3
  const char* 	nom;					        //4
  uint32_t	  	dureeCourseTotale;		//5
  uint32_t    	debutCourse;		    	//6
  uint32_t 	   	dureeJusquaFinCourse;	//7
};
#define tpsDeCourse(hauteur) ((uint32_t) (TIMER_V / 210.0 * (hauteur))) // il faut 30s pour faire 2m10
t_volet lesVolets[] = {
  {22, 30, OUVERT, "Cuisine",   	tpsDeCourse (95), 0, 0},
  {23, 31, OUVERT, "Sa manger", 	tpsDeCourse(210), 0, 0},
  {24, 32, OUVERT, "Salon", 		  tpsDeCourse(210), 0, 0},
  {25, 33, OUVERT, "Salle TV",  	tpsDeCourse (95), 0, 0},
  {26, 34, OUVERT, "Ch Lucie",  	tpsDeCourse(103), 0, 0},
  {27, 35, OUVERT, "Ch parents",	tpsDeCourse(102), 0, 0},
  {28, 36, OUVERT, "S D Bains", 	tpsDeCourse(101), 0, 0},
  {29, 37, OUVERT, "Bureau", 		  tpsDeCourse(104), 0, 0}
};
const uint8_t NOMBREDEVOLETS = sizeof(lesVolets) / sizeof(t_volet);
/************************************************************************************/

////*   Arrosage    *////
bool arrosageAutontialise = false;
bool arrosageAutoON = false;
bool resetZoneArrosee;
uint8_t saison = 5;
enum t_saison : uint8_t {PRINTEMPS, ETE, AUTOMNE, HIVER};
enum t_commandeArrosage : uint8_t {MARCHE, ARRET, MARCHE_TOUT, ARRET_TOUT, TIMERR_A, AUTOMA, NULL_A};
enum t_etat : uint8_t {ACTIF, INACTIF, TIMER, AUTO};
struct t_arrosage { // "Arbres", "Bordures", "Fruitiers", "Haies", "Jardin", "Potager
  uint8_t     	  zone;				  	        //1 index
  const char* 	  nom;				  	        //2
  uint8_t     	  pinRelais;  			      //3
  t_etat	        etat;					          //4
  uint32_t		    timerArrosage;        	//5
  uint32_t    	  debutArrosage;      		//6   utile ?  
  uint32_t    	  finArrosage;      		  //7   utile ?

// mode auto :
  uint8_t     	  joursIntervalle;	    	//8
  bool     	  	  zoneArrosee;	    	    //9
  uint32_t      	heureDebutArrosage;			//10
  uint32_t		    dureeJusquaFinArrosage;	//11
  uint32_t		    timerProchainArrosage;  //12

};
float coeffSaison = 1UL;
//#define tempsDArrosage(coeffSaison) ((uint32_t) (TIMER_A * (coeffSaison)))

t_arrosage lesArrosages[] = {
//zone nom        pin	 etat     DuréeArrosage coeff //     ZA
// 1     2          3     4     5  6  7    8    9  10   11      12
  {0, "Arbres",    38, INACTIF, 0, 0, 0, false, 0, 0, TIMER_A, 0},
  {1, "Bordures",  39, INACTIF, 0, 0, 0, false, 0, 0, TIMER_A, 0},
  {2, "Fruitiers", 40, INACTIF, 0, 0, 0, false, 0, 0, TIMER_A, 0},
  {3, "Haies",     41, INACTIF, 0, 0, 0, false, 0, 0, TIMER_A, 0},
  {4, "Jardin",    42, INACTIF, 0, 0, 0, false, 0, 0, TIMER_A, 0},
  {5, "Potager",   43, INACTIF, 0, 0, 0, false, 0, 0, TIMER_A, 0}
};
const uint8_t NOMBREDARROSAGE = sizeof(lesArrosages) / sizeof(t_arrosage) , HEURE_RESET_A = 24, HEURE_DEBUT_A = 0;

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

////*   Relais    *////
#define RELAIS_ACTIF LOW
#define RELAIS_INACTIF HIGH

enum t_commandeRelais : uint8_t {ACTIVE, INACTIVE, TIMERR_R, NULL_R};
enum t_etatRelais : uint8_t {R_ACTIF, R_INACTIF, R_TIMER};
struct t_relais {
  uint8_t     	pinRelais;
  t_etatRelais  etat;
  const char* 	nomRelais;
  uint32_t    	timerRelais;
};

t_relais lesRelais[] = {
{44, R_INACTIF, "Portail", TIMER_P},
{45, R_INACTIF, "Heures creuses", 0}
};
const uint8_t NOMBREDERELAIS = sizeof(lesRelais) / sizeof(t_relais);

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

////*   Commandes web    *////
enum : uint8_t {VOLETS_GLOBAL, VOLET_OUVRIR, VOLET_FERMER, VOLET_ARRETER, PORTAIL, ARROSAGE_MARCHE, ARROSAGE_ARRET, ARROSAGE_AUTO, RESTE}; // switch case du parseCommand
struct t_commandeWeb {
  bool                active;
  t_volet*            volet; // le pointeur du volet à commander
  t_commandeVolet     actionCMD_V;
  t_arrosage*         arrosage;
  t_commandeArrosage  actionCMD_A;
  t_relais*           relais;
  t_commandeRelais    actionCMD_R;
};
t_commandeWeb commandeWeb;

/********************************************************************************************************
  Arduino communicates with both the Ethernet Shield and SD card using the SPI bus
  This is on digital pins 10, 11, 12, and 13 on the Uno and pins 50, 51, and 52 on the Mega.
  Pin 10 is used to select the Ethernet chip and pin 4 for the SD card.
  These pins cannot be used for general I/O.
  On the Mega, the hardware SS pin, 53, is not used to select either the Ethernet chip or the SD card,
  but it must be kept as an output or the SPI interface won't work.
  Note that because the Ethernet chip and SD card share the SPI bus, only one at a time can be active.
  If you are using both peripherals in your program, this should be taken care of by the corresponding libraries.
  
  If you're not using one of the peripherals in your program, however, you'll need to explicitly deselect it.
  To do this with the SD card, set pin 4 as an output and write a high to it.
  For the Ethernet chip, set digital pin 10 as a high output.

// ?????????????????????????? i use a mega 2560 , so should i put pin 4, 10, 53 as an output and write a high to it ??????????????
********************************************************************************************************/

/********************************************************************************************************
  *** USED PINS communicate with shield and others devices ***
  *** 0 , 1 , 2 , 3 , 4 , 10 , 20 , 21 , 50 , 51 , 52 , 53
  // 22 => 29 : ouvertures volets
  // 30 => 37 fermetures volets
  // 38 => 43 arrosages
  // 44 = Portail
  // 45 = RHC
  // 46 ?
  // 47 ?
  // 48 = alimentation capteur pluie
  // 49 = alimentation capteur humidité
  // A0 = 54 = capteur pluie
  // A1 = 55 = capteur humidité
********************************************************************************************************/
/*** pins definitions ***/
const uint8_t
PLUIE_SENSOR_PIN = A0,
HUMIDITY_SENSOR_PIN = A1,
BROCHE_ONEWIRE = 2,
SDCARD_PIN = 4,
PLUIE_SENSOR_VCC = 48,
HUMIDITY_SENSOR_VCC = 49,
MAX_COMMAND = 30;
AsyncTask<NOMBREDARROSAGE*2 + 5> gestionnaireDeTache;
// autorise un nombre de taches predefinies . 12 pour arrosage et 1 portail + 1 heure + 1 saison .

/*****************************************************************
  ** This is for our URL Parser. We are expecting an URL in the form
  ** of http://domain.com/x=v1,y=v2,z=v3
  ** x, y and z would be the labels of Interests and v1, v2, v3
  ** will be valued extracted by the parser
******************************************************************/
const char * LABELS_OF_INTEREST[] = {"VG", "VO", "VF","VA", "P", "AM", "AA", "AG","R"}; // envoie requete HTTP
char urlCommand[MAX_COMMAND + 1]; // +1 for the trailing '\0'
const unsigned int MAX_LABELS_OF_INTEREST = sizeof(LABELS_OF_INTEREST) / sizeof(char*);
long VG=0, VO=0, VF=0, VA=0, P=0, AM=0, AA=0, AG=0, R=0;

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

////*   Capteurs    *////
const uint8_t SEUIL_PLUIE = 50, SEUIL_HUMIDITE = 50;

////*   NTP UDP   */////
char timeServer[] = "ntp.unice.fr";
const unsigned int localPort = 8888, timeOut = 1000;       // local port to listen for UDP packets
const uint8_t NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
uint8_t packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

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

void setup() {
  if (DEBUG) Serial.begin(115200);

  Wire.begin();
  initWebServer ();
  initGestionnaireDeTache ();
  for (uint8_t v = 0; v < NOMBREDEVOLETS; v++) initialiserUnVolet(&(lesVolets[v]));
  for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) initialiserUnArrosage(&(lesArrosages[a]));
  for (uint8_t r = 0; r < NOMBREDERELAIS; r++) initialiserUnRelais(&(lesRelais[r]));
  initSDCard ();
  initSetupPluie ();
  initSetupHumidity ();

if (DEBUG) {
    //printServerAddress();
    // verif internet acces
    if (internetAccessible()) Serial.println (F("setup => acces internet OK"));
    else Serial.println(F("setup => Pas d ' acces internet !!!"));
    // verif tache en cours au demarrage
    /*
    uint8_t count = 0;
    for (uint8_t i=0; i<255; i++) {
      if (gestionnaireDeTache.findAsyncCommand(i)) {
        count++;
        Serial.print (F("setup => trouver taches au nombre de : "));
        Serial.println (i);
      }
    }
    if (! count) Serial.print (F("setup => pas de tache trouvée ."));
    */
    /*
    for (uint8_t i = 0; i < RELAY_NUMBER; i++) {
      Serial.print ("pinRelaisFermeture");
      Serial.print (lesVolets[i].pinRelaisFermeture);
      Serial.print (" = ") ;
      Serial.println (digitalRead(lesVolets[i].pinRelaisFermeture));

      Serial.print ("pinRelaisOuverture");
      Serial.print (lesVolets[i].pinRelaisOuverture);
      Serial.print (" = ") ;
      Serial.println (digitalRead(lesVolets[i].pinRelaisOuverture));

      Serial.print ("tableau_Etat_Relais");
      Serial.print (i);
      Serial.println (lesRelais[i].etatRelaisi]);

    }*/
  }
}

void loop() {
  actualiserRTC();
  gestionnaireDeTache.updateQueue();
  //if (renewDHCPLease())
  handleClient();	// we still have a DHCP IP

  gererLesVolets();
  gererLesArrosages(); // logiquement inutile , en mode manuel en tout cas .
  gererLesRelais();
  relaisHeureCreuse();
  //resetZoneArrosee();
}

le fichier des fonctions de relais :

//// Fonctions de gestion des relais : ////
bool verifPinOK (int8_t pin) { // on s' assure grace a cette fonction que le pin qu ' on recoit est bien un numero valide compris dans la plage de pin que doit gerer notre serveur.
	return (pin >= -4 && pin < 53);
}

void initialiserUnRelais (t_relais* unRelais) {
  pinMode(unRelais->pinRelais, OUTPUT);
  digitalWrite(unRelais->pinRelais, RELAIS_INACTIF); // à l'arret
  if (DEBUG) {
    Serial.print (F("* initialiserUnRelais => PIN : "));
    Serial.println (unRelais->pinRelais);
  }
}

void activerUnRelais(t_relais* unRelais) {
	if (unRelais->etat != R_ACTIF) {
		if (!TEST) digitalWrite(unRelais->pinRelais, RELAIS_ACTIF);
		unRelais->etat = R_ACTIF;
	}
}

void desactiverUnRelais(t_relais* unRelais) {
	if (unRelais->etat != R_INACTIF) {
		if (!TEST) digitalWrite(unRelais->pinRelais, RELAIS_INACTIF);
		unRelais->etat = R_INACTIF;
	}
}

void inverserUnRelais(t_relais* unRelais) {
  if (unRelais->etat != R_ACTIF) activerUnRelais(unRelais);
  else desactiverUnRelais(unRelais);
}

void inverserUnRelaisEtUnTimer(t_relais* unRelais) {
  if (unRelais->etat != R_ACTIF) activerUnRelaisEtUnTimer (unRelais);
  else if (unRelais->etat != R_INACTIF) desactiverUnRelaisEtUnTimer (unRelais);
}

void activerUnRelaisEtUnTimer (t_relais* unRelais) {
  activerUnRelais(unRelais);
  activerUnTimerRelais(unRelais);
}

void desactiverUnRelaisEtUnTimer (t_relais* unRelais) {
  desactiverUnRelais(unRelais);
  desactiverTimerRelais(unRelais->pinRelais);
}

void gererLesRelais () {
  
  if (commandeWeb.active) {
    /*
    if (commandeWeb.volet == NULL && commandeWeb.actionCMD_V == NULL_V  && commandeWeb.arrosage == NULL && commandeWeb.actionCMD_A == NULL_A && commandeWeb.actionCMD_R == NULL_R) {
      commandeWeb.active=false;
      if (DEBUG) {
        Serial.print (F("* gererLesRelais , 1er if "));
      }
    }
    */
    if (commandeWeb.relais != NULL) for (uint8_t r = 0; r < NOMBREDERELAIS; r++) gererUnRelais(&(lesRelais[r]));
  }
  //else for (uint8_t r = 0; r < NOMBREDERELAIS; r++) gererUnRelais(&(lesRelais[r]));
}

void gererUnRelais (t_relais* unRelais) {
	switch (unRelais->etat) { // R_ACTIF, R_INACTIF, R_TIMER
		case R_INACTIF : // actionCMD_R = ACTIVE, INACTIVE, TIMERR_R, NULL_R
			if ((commandeWeb.active) && (commandeWeb.relais == unRelais)) {
				if (commandeWeb.actionCMD_R == ACTIVE) {
          if (DEBUG) {
            Serial.print (F("* gererUnRelais => case INACTIF => unRelais->pinRelais : "));
            Serial.println (unRelais->pinRelais);
          }
					activerUnRelais(unRelais);
					commandeWeb.active = false;
				}
			}
			break;

		case R_ACTIF :
			if ((commandeWeb.active) && (commandeWeb.relais == unRelais)) {
				if (commandeWeb.actionCMD_R == INACTIVE) {
          if (DEBUG) {
            Serial.print (F("* gererUnRelais => case ACTIF => unRelais->pinRelais : "));
            Serial.println (unRelais->pinRelais);
          }
					desactiverUnRelais(unRelais);
					commandeWeb.active = false;
				}
			}
			break;
    case R_TIMER :
      if ((commandeWeb.active) && (commandeWeb.relais == unRelais)) {
				if (commandeWeb.actionCMD_R == TIMERR_R) {
          if (DEBUG) {
            Serial.print (F("* gererUnRelais => case TIMERR_R =>unRelais->pinRelais : "));
            Serial.println (unRelais->pinRelais);
          }
					inverserUnRelais(unRelais);
					commandeWeb.active = false;
				}
      }
      break;
    /*case AUTO :
      if ((commandeWeb.active) && (commandeWeb.relais == unRelais)) {
				if (commandeWeb.actionCMD_R == AUTO) {
					inverserUnRelais(unRelais);
					commandeWeb.active = false;
				}
      }
      break;*/
        
    default:
      if (DEBUG) Serial.println (F("* gererUnRelais => probleme = case non reconnu !"));
      break;
	} // fin switch
  // comme cette fonction est appellé en permanence , il vaut mieux eviter de mettre quoi que ce soit ici
}

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

void relaisHeureCreuse () { // ok
	static unsigned long chrono = 0;
  unsigned int timestamp = RTC.heure()*60 + RTC.minute();
	static int RHC = -1; // jamais lu
	static bool oldRHC = false;

	if ((RHC == -1) || (millis() - chrono >= TIMER_V)) { // si jamais lue ou lue depuis plus de 30 secondes
    if (DEBUG && RHC == -1 ) {
			Serial.print (F("relaisHeureCreuse => initialisation sur PIN : "));
			Serial.print (lesRelais[1].pinRelais);
			Serial.print (F(" , etat : "));
			Serial.println (lesRelais[1].etat);
		}
    // si on est dans le bon crenau horraire
		//if ( (timestamp > HC_START_TIME_N && timestamp < HC_END_TIME_N) || (timestamp > HC_START_TIME_D && timestamp < HC_END_TIME_D) ) { // entre 12 et 14 heures + la nuit
    if (timestamp > HC_START_TIME_D && timestamp < HC_END_TIME_D ) { // enclenchement uniquement entre 12 et 14 Heures
			RHC = 1;
      //activerRelais(1);
			//if (DEBUG) Serial.println (F("relaisHeureCreuse => etat actif"));
		}
		else {
			RHC = 0;
      //desactiverRelais(1);
			//if (DEBUG) Serial.println (F("relaisHeureCreuse => etat passif"));
		}
    if ((oldRHC != RHC) && (RHC != -1)) {
      if (DEBUG) {
        Serial.print (F("relaisHeureCreuse => oldRHC avant = ")); 
        Serial.print (oldRHC);
        Serial.print (F(" , relaisHeureCreuse => RHC = ")); 
        Serial.println (RHC);
      }
      //inverserRelais(1);
      inverserUnRelais(&(lesRelais[1]));
      oldRHC = RHC;
    }
		chrono = millis();

  // histoire de verifier le nombre de tache au cours de l' evolution du programme.
    /*
    if (DEBUG) {  
      uint8_t count = 0;
      for (uint8_t i=0; i<255; i++) {
        if (gestionnaireDeTache.findAsyncCommand(i)) {
          count++;
          Serial.print (F("setup => trouver taches : "));
          Serial.println (i);
        }
      }
      if (! count) Serial.print (F("setup => pas de tache trouvée ."));
      else {
        Serial.print (F("setup => "));
        Serial.print (count);
        Serial.println (F(" tache(s) trouvée(s) ."));
      }
    }
    */
	}
}

// on recoit le pin du timer asynctask ...
// cette fonction sert uniquement a desactiver le bon relais et mettre a jour l' etat .
// elle est appelée uniquement par le callback du gestionnaire de tache , voir fichier timers_rtc.ino
void desactiverRelais(t_commandID pin) {
  for (uint8_t i = 0; i < NOMBREDERELAIS; i++) {
    if (lesRelais[i].pinRelais == pin) {
      lesRelais[i].etat = R_INACTIF;
      if (!TEST) digitalWrite(lesRelais[i].pinRelais, RELAIS_INACTIF);
      if (DEBUG) {
        Serial.print (F("* desactiverRelais => indice = "));
        Serial.print (i);
        Serial.print (F(", relais = "));
        Serial.println (lesRelais[i].pinRelais);
      }
    }
  }
}

void activerUnTimerRelais (t_relais* unRelais) {
  // si une tache existe deja , on la remplace par la nouvelle , donc , 1ere chose , on supprime la tache si elle existe deja .
  desactiverTimerRelais(unRelais->pinRelais);

  if (!gestionnaireDeTache.findAsyncCommand(unRelais->pinRelais)) {
    gestionnaireDeTache.registerAsyncCommand(unRelais->pinRelais, unRelais->timerRelais, desactiverRelais); // normal le reinit ici : oui fonction de callback ( c ' est elle qui reinitialisera le relais a la fin du timer )    
    if (DEBUG) {
      Serial.print (F("* activerTimerRelais => tache "));
      Serial.print (unRelais->pinRelais);
      Serial.print (F(" enregistrée pour dans "));
      Serial.println (unRelais->timerRelais);
    }
  }
}

void desactiverTimerRelais (uint8_t pin) { // en cas de suppression manuelle du timer 
  if (gestionnaireDeTache.findAsyncCommand(pin)) {
    gestionnaireDeTache.unregisterAsyncCommand(pin);
    if (DEBUG) {
      Serial.print (F("* desactiverTimerRelais => tache : "));
      Serial.print (pin);
      Serial.println (F(" supprimee"));
    }
  }
}

//// FIN Des Fonctions de gestion des relais ////

celui des fonctions serveurs :

//// Fonctions serveur : ////

////*    verif acces internet  *////
bool attenteChaine(EthernetClient& timeoutClient, const char * endMarker, unsigned long duration) {
  int localBufferSize = strlen(endMarker); // we won't need an \0 at the end
  char localBuffer[localBufferSize];
  int index = 0;
  boolean endMarkerFound = false;
  unsigned long currentTime;

  memset(localBuffer, '\0', localBufferSize); // clear buffer

  currentTime = millis();
  while (millis() - currentTime <= duration) {
    if (timeoutClient.available() > 0) {
      if (index == localBufferSize) index = 0;
      localBuffer[index] = (uint8_t) timeoutClient.read();
      endMarkerFound = true;
      for (int i = 0; i < localBufferSize; i++) {
        if (localBuffer[(index + 1 + i) % localBufferSize] != endMarker[i]) {
          endMarkerFound = false;
          break;
        }
      }
      index++;
    }
    if (endMarkerFound) break;
  }

  return endMarkerFound;
}

bool internetAccessible() {
  const unsigned long timeOut = 1000;
  EthernetClient timeoutClient;
  boolean r;

  // client.connect Returns an int (1,-1,-2,-3,-4) indicating connection status :
  // SUCCESS 1
  // TIMED_OUT -1
  // INVALID_SERVER -2
  // TRUNCATED -3
  // INVALID_RESPONSE -4

  timeoutClient.setTimeout(timeOut);
  if (timeoutClient.connect("clients3.google.com", 80) != 1) {
    return false;
  }

  // si on est connnecté on devrait recevoir une réponse HTTP/1.0 204 No Content
  timeoutClient.println("GET /generate_204 HTTP/1.0\r\nHost: arduino.cc\r\nUser-Agent: Arduino\r\n");
  r = attenteChaine(timeoutClient, "204", timeOut); // on se donne au max  timeOut millisecondes pour recevoir la réponse
  while(timeoutClient.available()) timeoutClient.read(); // on vide le buffer
  timeoutClient.stop();
  return r;
}

////*    serveur UDP  *////
// send an NTP request to the time server at the given address
void sendNTPpacket(char* address) {
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  // all NTP fields have been given values, now you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

////*    serveur web  *////
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) gererLesVolets();
    else if (ca != NULL_A) gererLesArrosages();
    else if (cr != NULL_R) gererLesRelais();
  }
}

void parseCommand() {
  char * item;
  boolean labelFound;
  long int labelValue = -10;
  int labelIndex;

  item = strtok(urlCommand, "=");

  while (item) {
    labelFound = false;
    for (unsigned int i = 0; i < MAX_LABELS_OF_INTEREST; ++i) {
      if (!strcmp(LABELS_OF_INTEREST[i], item)) {
        item = strtok (NULL, ",");
        if (item != NULL) {
          labelFound = true;
          labelIndex = i;
          // then parse the value , we expect integers
          labelValue = atol(item);
          if (DEBUG) {
            Serial.print ("labelIndex  = ");
            Serial.println (labelIndex );
            Serial.print ("labelValue = ");
            Serial.println (labelValue);
          }
        } // end if we had a value for the label
      } // end string compare
    } // end for

    // ****************************************************************
    // THIS IS WHERE YOU PERFORM YOUR ACTIONS FOR EACH VARIABLE FOUND
    // ****************************************************************
    if (labelFound && verifPinOK(labelValue)) {
      switch (labelIndex) { // VOLETS_GLOBAL, VOLET_OUVRIR, VOLET_FERMER, VOLET_ARRETER, PORTAIL, ARROSAGE, RESTE};
        case VOLETS_GLOBAL :// "VG", "VO", "VF","VA", "P", "AG", "AM", "AA", "R"
          if(DEBUG) Serial.print(F("* parseCommand => VOLETS_GLOBAL "));
          if (labelValue == -2) { // on arrete tous les volets
            if(DEBUG) Serial.println(F(" => ARRETER_TOUT"));
            //arreterLesVolets();
            affecteCommandeWeb(true, NULL, ARRETER_TOUT);
          }
          else if (labelValue == -3) { // on ouvre tous les volets
            if(DEBUG) Serial.println(F(" => OUVRIR_TOUT"));
            //ouvrirLesVolets();
            affecteCommandeWeb(true, NULL, OUVRIR_TOUT);
          }
          else if (labelValue == -4) { // on ferme tous les volets
            if(DEBUG) Serial.println(F(" => FERMER_TOUT"));
            //fermerLesVolets();
            affecteCommandeWeb(true, NULL, FERMER_TOUT);
          }
          break;

        case VOLET_OUVRIR :
          if(DEBUG) Serial.println(F("* parseCommand => VOLET_OUVRIR"));

          affecteCommandeWeb(true, &(lesVolets[labelValue]), OUVRIR);
          break;

        case VOLET_FERMER :
          if(DEBUG) Serial.println(F("* parseCommand => VOLET_FERMER"));

          affecteCommandeWeb(true, &(lesVolets[labelValue]), FERMER);
          break;

        case VOLET_ARRETER :
          if(DEBUG) Serial.println(F("* parseCommand => VOLET_ARRETER"));

          affecteCommandeWeb(true, &(lesVolets[labelValue]), ARRETER);
          break;

        case PORTAIL :
          if(DEBUG) {
            Serial.print(F("* parseCommand => PORTAIL => labelValue = "));
            Serial.println(labelValue);
          }
          //if (! TEST) inverserUnRelaisEtUnTimer (&(lesRelais[0])); // fonctionne aussi
          for (uint8_t r = 0; r < NOMBREDERELAIS; r++) {
            if (lesRelais[r].pinRelais == labelValue) {
              //if (! TEST) inverserUnRelaisEtUnTimer (&(lesRelais[r])); // fonctionne aussi
              
              // Sinon , pour etre uniforme : 
              affecteCommandeWeb(true, NULL, NULL_V, NULL, NULL_A, &(lesRelais[r]), NULL_R);
              // n ' a pas l ' air de vouloir fonctionner sans un enum etat relais specifique              
              
              if(DEBUG) {
                Serial.print(F("* parseCommand => PORTAIL => envoi = "));
                Serial.println(lesRelais[r].pinRelais);
              }
            }
          }
          break;

        case ARROSAGE_MARCHE : // ARROSAGE_MARCHE, ARROSAGE_ARRET, ARROSAGE
          if(DEBUG) Serial.println(F("* parseCommand => ARROSAGE => single => MARCHE"));
          affecteCommandeWeb(true, NULL, NULL_V, &(lesArrosages[labelValue]), MARCHE); // passage d ' un int a un pointeur : &(lesArrosages[labelValue])
          break;

        case ARROSAGE_ARRET :
          if(DEBUG) Serial.println(F("* parseCommand => ARROSAGE => single => ARRET"));
          affecteCommandeWeb(true, NULL, NULL_V, &(lesArrosages[labelValue]), ARRET);
          break;

        case ARROSAGE_AUTO :
          if (labelValue == -1) {
            arrosageAutoON = !arrosageAutoON;
            affecteCommandeWeb(true, NULL, NULL_V, NULL, AUTOMA);
            if(DEBUG) {
              Serial.print(F("* parseCommand => ARROSAGE => AUTO : "));
              Serial.println ( arrosageAutoON == false ? F("OFF") : F("ON"));
            }
            //if (arrosageAutoON == false) // il faut desactiver les arrosages et apeller les bonnes fonctions
          }
          //else if (labelValue == -2) initArrosageOK = NOMBREDARROSAGE; // reinitialise , on ne l ' appelle pas manuellement ?
          else if (labelValue == -3) {
            if(DEBUG) Serial.println(F("* parseCommand => ARROSAGE => MARCHE_TOUT"));
            affecteCommandeWeb(true, NULL, NULL_V, NULL, MARCHE_TOUT);
          }
          else if (labelValue == -4) {
            if(DEBUG) Serial.println(F("* parseCommand => ARROSAGE => ARRET_TOUT"));
            affecteCommandeWeb(true, NULL, NULL_V, NULL, ARRET_TOUT);
          }
          break;

        case RESTE :
          break;
        default :
          if(DEBUG) Serial.println(F("* parseCommand => case INCONNU !!"));
          break;
      } // fin du switch
    } // fin if
    else {
      item = strtok (NULL, ","); // skip this label value, not recognized
    }
    item = strtok (NULL, "="); // got to next one - will modify urlCommand
  } // fin boucle
}

void handleCommand(EthernetClient& client) {
  // the command is in urlCommand
  if (strlen(urlCommand) != 0) parseCommand();
  sendHTTPResponse(client);
}

void handleClient() {
  boolean urlCommandFound = false;
  char httpHeader[MAX_COMMAND + 1];
  uint8_t httpHeaderIndex = 0;
  httpHeader[0] = '\0';
  urlCommand[0] = '\0';

  EthernetClient client = webServer.available();     // listen for incoming clients

  if (client) {
    boolean currentLineIsBlank = true;
    //if (DEBUG) Serial.println (F("handleClient =>requete HTTP : "));
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        //if (DEBUG) Serial.print(c); // if you want to see the HTTP request
        if (!urlCommandFound) {
          httpHeader[httpHeaderIndex++] = c;
          httpHeader[httpHeaderIndex] = '\0';
          if (httpHeaderIndex > MAX_COMMAND - 1) httpHeaderIndex = MAX_COMMAND - 1;
        }

        if (c == '\n' && currentLineIsBlank) {  // an http request ends with a blank line
          handleCommand(client);
          delay(10); // give the web browser time to receive the data
          client.stop(); // close the connection:
          break;
        }

        if (c == '\n') {
          currentLineIsBlank = true; // starting a new line
          if (!strncmp("GET / ", httpHeader, 5)) {
            strcpy(urlCommand, (httpHeader + 5)); // get rid of the "GET / "
            char * firstSpacePtr = strchr(urlCommand, ' ');
            if (firstSpacePtr) *firstSpacePtr = '\0'; // get rid of the " HTTP / 1.1 etc
            urlCommandFound = true;
          }
          else {
            httpHeaderIndex = 0;
            httpHeader[0] = '\0';
          }
        }
        else if (c != '\r') {
          currentLineIsBlank = false; // new character on the current line, ignore '\r'
        }
      } // fin du client.available
    } //  fin de la boucle while
  } // fin du if (client)
}

void printServerAddress() {
  if (Ethernet.localIP() != IPAddress(0,0,0,0)) {
    Serial.print(F("printServerAddress => Hello , connect to your arduino with http://"));
    if (SERVER_PORT != 80) { // 80 is the default port so not needed in the URL
      Serial.print(Ethernet.localIP());
      Serial.print(F(" : "));
      Serial.println(SERVER_PORT);
    }
    else Serial.println(Ethernet.localIP());
    Serial.print (F("printServerAddress => vesrion en cours d ' utilisation : "));
    Serial.println (NUMERO_VERSION);
  }
  else Serial.print(F("printServerAddress => probleme : pas d' addresse IP attribue !"));
}

/* 	Allows for the renewal of DHCP leases.
  //	When assigned an IP address via DHCP, ethernet devices are given a lease on the address
  //	for an amount of time. With Ethernet.maintain(), it is possible to request a renewal from
  //	the DHCP server. Depending on the server's configuration, you may receive the same address,
  //	a new one, or none at all.
  //	best for a server would be to use a static ip, but for demo purpose you are likely to keep your ip long enough */
bool renewDHCPLease() { // uniquement en cas de DHCP activé ,donc pas d' adesse ip statique entree
  boolean ok = true;
  switch (Ethernet.maintain()) {
    case 1:
    //renewed fail
    if (DEBUG) Serial.println(F("renewDHCPLease => Error: renewed fail"));
    ok = false;
    break;

    case 2:
    //renewed success
    if (DEBUG) {
      Serial.println(F("renewDHCPLease => Renewed success"));
      printServerAddress();
    }
    break;

    case 3:
    //rebind fail
    ok = false;
    if (DEBUG) Serial.println(F("renewDHCPLease => Error: rebind fail"));
    break;

    case 4:
    //rebind success
    if (DEBUG) {
      Serial.println(F("renewDHCPLease => Rebind success"));
      //printServerAddress();
    }
    break;

    default:
    //nothing happened
    break;
  }
  return ok;
}

//// FIN Des Fonctions serveur ////

celui qui affiche la page web :

//// Fonctions de la page WEB : ////
void sendHTTPResponse(EthernetClient& client) {
	//infos pour le navigateur
	client.println (F("HTTP/1.1 200 OK")); // type du HTML
	client.println (F("Content-Type: text/html; charset=UTF8")); //type de fichier et encodage des caractères
	client.println (F("Connection: close"));  // fermeture de la connexion quand toute la réponse sera envoyée
	client.println();
	//balises d'entête
	client.println (F("<!DOCTYPE HTML>"));

	//client.print(F("<html><head><meta http-equiv='Refresh' content='31'; URL='http://"));
	//client.print (ip);
	//client.print(F("/'>"));
	client.print (F("<html><head>"));
	client.print (F("<style>body {text-align:center;}</style></head>"));
	client.println (F("<body bgcolor='#00979C'>")); // page backgroud color
	//client.println (F("<body bgcolor='#41B3A4'>")); // page backgroud color
	//**********************************************************//
	corpsPage(client); // fonction pour le corps
	//**********************************************************//
	client.println(F("</body></html>"));// fin de page

	/*
	if (DEBUG) {
		// affichage des etats des relais lors d ' une action sur le serveur web
		for (uint8_t i = 0; i < RELAY_NUMBER; i++) {
		Serial.print (F("sendHTTPResponse => Relais numero : "));
		Serial.print (i);
		Serial.print (F(" etat = "));
		Serial.println (lesRelais[i].etat);
	}

	// affichage des taches asynchrones en cours lors d ' une action sur le serveur web
	uint8_t count = 0;
	for (uint8_t i=0; i<255; i++) {
		if (gestionnaireDeTache.findAsyncCommand(i)) {
			count++;
			Serial.print (F("* sendHTTPResponse => find tache : "));
			Serial.println (i);
		}
	}
	if (! count) Serial.print (F("pas de tache trouvée ."));
	}
	*/
}

void corpsPage (EthernetClient& client) {
	welcomeCorps (client);
	ephemerideCorps (client);
	voletsCorps (client);
	arrosageCorps (client);
	//BALCorps (client);
	relaisAutreCorps (client);
}
//// FIN Des Fonctions de la page WEB ////

//// Fonctions du corps de la page WEB ////
void welcomeCorps (EthernetClient& client) {  // Print Welcome IP ADRESS :
	if (Ethernet.localIP() != IPAddress(0,0,0,0)) {
		client.print(F("Bienvenue , l ' adresse IP de votre arduino est : "));

		if (SERVER_PORT != 80) { // 80 is the default port so not needed in the URL
			client.print(Ethernet.localIP());
			client.print(F(" : "));
			client.println(SERVER_PORT);
		}
		else client.print(Ethernet.localIP());
	}
	client.print (F(" , vesrion en cours d ' utilisation : "));
	client.println (NUMERO_VERSION);
	client.println (F("</br></br>"));

	if (DEBUG) {
		if (ENABLE_SDCARD) {
			client.print (F("welcomeCorps => La carte SD est activee sur le SDCARD_PIN : "));
			client.println (SDCARD_PIN);
			client.println (F("</br>"));
		}
	}
}

void ephemerideCorps (EthernetClient& client) { // Print Welcome time ephemeride

	affichageDateHeure (client);
	client.print (readPluieSensor() == true ? F(" il pleut ! ") : F(" il ne pleut pas . "));
	client.print (F("Le sol est "));
	client.print (readHumiditySensor() == true ? F("sec .") : F("humide ."));
	client.println (F("</br></br>"));
	// Actualise button
	client.print (F("<a><button onclick=\"location.href='Z'\" type='button'>Actualiser</button></a>"));

	client.println (F("</br></br>"));
	if (DEBUG && TEST) client.println (F("MODE TEST ET DEBUG ACTIFS !!!"));
	else if (DEBUG) client.println (F("MODE DEBUG ACTIF !!!"));
	else if (TEST) client.println (F("MODE TEST ACTIF !!!"));
}

void voletsCorps (EthernetClient& client) { // Relais volets
		client.print (F("<TABLE width=90% border=1 align='center'><TR><TD colspan="));
		client.print (NOMBREDEVOLETS);
		client.println (F("><b>VOLETS ROULANTS</b></TD></TR>")); // en tete du tableau

	{ // debut de la 1ere ligne du tableau : // affichage des noms seulements
		client.println (F("<TR>"));

		for (uint8_t i=0; i<NOMBREDEVOLETS; i++) {
			client.print (F("<TD width=10%>"));
			client.print (lesVolets[i].nom);
			client.println (F("</TD>"));

		} // fin de boucle
		client.println (F("</TR>"));
	} // fin de la 1ere ligne du tableau

	{ // debut de la 2eme ligne du tableau : boutons ouvertures
		client.println (F("<TR>"));
		//t_etatVolet : uint8_t { OUVERT, FERME, EN_FERMETURE, EN_OUVERTURE, ENTROUVERT};

		for (uint8_t i=0; i<NOMBREDEVOLETS; i++) { // ligne ouverture des relais
			if (lesVolets[i].etat == EN_OUVERTURE) {
				client.print (F("<TD width=10%><a><button onclick=\"location.href='VA="));
				client.print (i);
				client.println (F("'\" type='button'> Stop</button></a></TD>"));
			}
			else if (lesVolets[i].etat == OUVERT) client.println (F("<TD width=10%></TD>"));
			else {
				client.print (F("<TD width=10%><a><button onclick=\"location.href='VO="));
				client.print (i);
				client.println (F("'\" type='button'>Ouvrir</button></a></TD>"));
			}
		} // fin de boucle
		client.println (F("</TR>"));
	} // fin de la 2eme ligne du tableau

	{ // debut de la 3eme ligne du tableau : boutons fermers
		client.println (F("<TR>")); // boutons fermeture volets

		for (uint8_t i=0; i<NOMBREDEVOLETS; i++) {
			if (lesVolets[i].etat == EN_FERMETURE) {
				client.print (F("<TD width=10%><a><button onclick=\"location.href='VA="));
				client.print (i);
				client.println (F("'\" type='button'> Stop</button></a></TD>"));
			}
			else if (lesVolets[i].etat == FERME) client.println (F("<TD width=10%></TD>"));
			else {
				client.print (F("<TD width=10%><a><button onclick=\"location.href='VF="));
				client.print (i);
				client.println (F("'\" type='button'>Fermer</button></a></TD>"));
			}
		} // fin de boucle

		client.println (F("</TR>"));
	} // fin de la 3eme ligne du tableau

	{ // debut de la 4eme ligne du tableau : boutons groupes
		client.println (F("<TR>"));

		// bouton tout ouvrir
		client.println (F("<TD width=10%><a><button onclick=\"location.href='VG=-3'\" type='button'>Ouvrir</br>les volets</button></a></TD>"));

		// bouton tout stopper
		client.println (F("<TD width=10%><a><button onclick=\"location.href='VG=-2'\" type='button'>Stopper</br>les volets</button></a></TD>"));

		// bouton tout fermer
		client.println (F("<TD width=10%><a><button onclick=\"location.href='VG=-4'\" type='button'>Fermer</br>les volets</button></a></TD>"));

		// case vide
		client.println (F("<TD width=10%></TD>"));

		// bouton ouvrir groupe jour
		client.println (F("<TD width=10%><a><button onclick=\"location.href='VO=0,VO=1,VO=2,VO=3'\" type='button'>Ouvrir groupe jour</button></a></TD>"));

		// bouton ouvrir groupe nuit
		client.println (F("<TD width=10%><a><button onclick=\"location.href='VO=4,VO=5,VO=6,VO=7'\" type='button'>Ouvrir groupe nuit</button></a></TD>"));

		// bouton fermer groupe jour
		client.println (F("<TD width=10%><a><button onclick=\"location.href='VF=0,VF=1,VF=2,VF=3'\" type='button'>Fermer groupe jour</button></a></TD>"));

		// bouton fermer groupe nuit
		client.println (F("<TD width=10%><a><button onclick=\"location.href='VF=4,VF=5,VF=6,VF=7'\" type='button'>Fermer groupe nuit</button></a></TD>"));
		client.println (F("</TR>"));
	} // fin de la 4eme ligne du tableau

	client.println(F("</TABLE>"));
} // fin du tableau volets

void arrosageCorps (EthernetClient& client) {
	{ // en tete du tableau
		client.println (F("</br>"));
		client.print (F("<TABLE width=90% border=1 align='center'><TR><TD colspan="));
		client.print (NOMBREDARROSAGE + 4); // +4 pour arrosage mode global et mode auto
		client.print (F("><b>ARROSAGE</b>"));
		client.println (F("</TD></TR>")); // en tete du tableau
	}

	{ // debut de la 1ere ligne du tableau : affichage des noms seulement
		client.print (F("<TR>"));
		for (uint8_t i=0; i < NOMBREDARROSAGE; i++) {
			client.print (F("<TD width=10%>"));
			client.print (lesArrosages[i].nom);
			client.println (F("</TD>"));
		} // fin de boucle

		client.println (F("<TD colspan=4 width=30%>Global</TD>"));
		client.println (F("</TR>"));
	} // fin de la 1ere ligne du tableau

	{ // debut de la 2eme ligne du tableau : boutons
		client.println (F("<TR>"));

		for (uint8_t i = 0; i < NOMBREDARROSAGE; i++) { // ligne ouverture des relais
			client.print (lesArrosages[i].etat == INACTIF ? F("<TD width=10%><a><button onclick=\"location.href='AM=") : F("<TD width=10%><a><button onclick=\"location.href='AA="));
			client.print (i);
			client.print (F("'\" type='button'>"));
			client.print (lesArrosages[i].etat == INACTIF ? F("OFF") : F("ON"));
			client.println (F("</button></a></TD>"));// fin du lien
		} // fin de boucle

		client.println (F("<TD width=10%><a><button onclick=\"location.href='AG=-3'\" type='button'>Marche</button></a></TD>")); // MARCHE_TOUT
		client.println (F("<TD width=10%><a><button onclick=\"location.href='AG=-4'\" type='button'>Arret</button></a></TD>")); // ARRET_TOUT
		client.println (F("<TD width=10%><a><button onclick=\"location.href='AG=-2'\" type='button'>Mode Auto</button></a></TD>")); // initArrosageOK = NOMBREDARROSAGE;
		client.print   (F("<TD width=10%><a><button onclick=\"location.href='AG=-1'\" type='button'>")); // arrosageAutoON = !arrosageAutoON;
		client.println (arrosageAutoON == true ? F("Auto ON </button></a></TD>") : F("Auto OFF </button></a></TD>"));// fin du lien

		client.println (F("</TR>"));
	} // fin de la 2eme ligne du tableau

	client.println (F("</TABLE>"));
}

/*void BALCorps (EthernetClient& client) {

}*/

void relaisAutreCorps (EthernetClient& client) {
	{ // en tete du tableau
		client.println (F("</br>"));
		client.print (F("<TABLE width=90% border=1 align='center'><TR><TD colspan="));
		client.print (NOMBREDERELAIS);
		client.print (F("><b>RELAIS AUTRE</b>"));
		client.println (F("</TD></TR>"));
	} // fin en tete du tableau

	{ // debut de la 1ere ligne du tableau : // affichage des noms seulements
		client.println (F("<TR>"));
		for (uint8_t i =  0; i <  NOMBREDERELAIS; i++) {
			client.print (F("<TD width=10%>"));
			client.print (lesRelais[i].nomRelais);
			client.println (F("</TD>"));
		} // fin de boucle
		client.println (F("</TR>"));
	} // fin de la 1ere ligne du tableau.

	{ // debut de la 2eme ligne du tableau :
		client.println (F("<TR>"));
		for (uint8_t i = 0; i < NOMBREDERELAIS; ++i) {
      if (strcmp(lesRelais[i].nomRelais, "Portail") == 0) {
        client.print (F("<TD width=10%><a><button onclick=\"location.href='P="));
			 	client.print (lesRelais[i].pinRelais);
			 	client.print (F("'\" type='button'>"));
			 	client.print (lesRelais[i].etat == RELAIS_INACTIF ? F("Ouvrir") : F("Fermer"));
				client.println (F("</button></a></TD>"));// fin du lien
      }
			if (strcmp(lesRelais[i].nomRelais, "Heures creuses") == 0) {
				  client.print (F("<TD width=10%>"));
				  client.print (lesRelais[i].etat == RELAIS_INACTIF ? F("Desactivé") : F("Activé"));
				  client.println (F("</TD>"));// Pas de lien
      }
		} // fin de boucle
		client.println (F("</TR>"));
	} // fin de la 2eme ligne du tableau

	client.println (F("</TABLE>"));
}
//// FIN Des Fonctions du corps de la page WEB ////

//// Fonctions speciales affichage de la page WEB ////
const char* afficherJourSemaine(uint8_t joursem) {
	const char* jours[]  {"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"};
	return jours[joursem]; // de 0 à 6
}

void print2Digits(EthernetClient& client, uint8_t nombre) {
	if (aficher0ouPas(nombre)) client.write('0');
	client.print (nombre);
}

inline bool aficher0ouPas(uint8_t nombre) {return (nombre < 10);}  // vrai si plus petit que 10
// inline Comme la fonction est courte  c'est pour dire au compilateur qu'il peut "copier coller ce code" à la place d'appeler la fonction s'il pense que ça sera plus rapide / simple / efficace. bien sûr ça rallonge le code mais on gagne un appel de fonction.
void affichageDateHeure (EthernetClient& client) {
	client.print (F("Aujourd ' hui nous sommes le "));
	client.print (afficherJourSemaine(RTC.jourSemaine()));
	client.print (F(" "));
	print2Digits(client, RTC.jour());
	client.print (F(" / "));
	print2Digits(client, RTC.mois());
	client.print (F(" / "));
	client.print (F("20"));
	client.print (RTC.annee());
	client.print (F(" , il est : "));
	client.print (RTC.heure());
	client.print (F(" H "));
	client.print (RTC.minute());
	client.print (F(" min "));
	client.print (RTC.seconde());
	client.print (F(" sec ."));
}

Voilà , normalement pour ce qui est des relais il ne devrait rien manquer .
la fonction commandeWeb est situé dans le fichier des fonctions serveur .
je rajoute les autres fichiers pour completer le code , mais ca ne devrait pas etre necessaire de les voir pour trouver le bug , cela dit ils peuvent aider a comprendre le fonctionnement au cas ou :

void initialiserUnVolet(t_volet* unVolet) {
  pinMode(unVolet->pinRelaisOuverture, OUTPUT);
  digitalWrite(unVolet->pinRelaisOuverture, RELAIS_INACTIF); // à l'arret
  if (DEBUG) {
    Serial.print (F("* initialiserUnVolet => PIN ouverture : "));
    Serial.println (unVolet->pinRelaisOuverture);
  }
  pinMode(unVolet->pinRelaisFermeture, OUTPUT);
  digitalWrite(unVolet->pinRelaisFermeture, RELAIS_INACTIF); // à l'arret
  if (DEBUG) {
    Serial.print (F("* initialiserUnVolet => PIN fermeture : "));
    Serial.println (unVolet->pinRelaisFermeture);
  }
}

void ouvrirUnVolet(t_volet* unVolet) {
  if (unVolet->etat != OUVERT) {
    if (!TEST) digitalWrite(unVolet->pinRelaisOuverture, RELAIS_ACTIF);
    unVolet->debutCourse = millis();
    unVolet->etat = EN_OUVERTURE;
  }
}

void fermerUnVolet(t_volet* unVolet) {
  if (unVolet->etat != FERME) {
    if (!TEST) digitalWrite(unVolet->pinRelaisFermeture, RELAIS_ACTIF);
    unVolet->debutCourse = millis();
    unVolet->etat = EN_FERMETURE;
  }
}

void arreterUnVolet(t_volet* unVolet) {
  if (!TEST) digitalWrite(unVolet->pinRelaisOuverture, RELAIS_INACTIF);
  if (!TEST) digitalWrite(unVolet->pinRelaisFermeture, RELAIS_INACTIF);
  unVolet->etat = ENTROUVERT; // par défaut on ne sait pas s'il est en haut ou en bas
  if(DEBUG) {
    if (unVolet->etat == EN_FERMETURE) {
      Serial.print(F("EN_FERMETURE : "));
      Serial.println(millis());
    }
    else if (unVolet->etat == EN_OUVERTURE) {
      Serial.print(F("EN_OUVERTURE : "));
      Serial.println(millis());
    }
  }
}

void gererUnVolet(t_volet* unVolet) {
  switch (unVolet->etat) { // OUVERT, FERME, EN_FERMETURE, EN_OUVERTURE, ENTROUVERT

    case OUVERT: // on regarde si on a un ordre en attente et s'il est pour nous
      if ((commandeWeb.active) && (commandeWeb.volet == unVolet)) { // la seule commande à accepter c'est de fermer
        if (commandeWeb.actionCMD_V == FERMER) fermerUnVolet(unVolet);
        commandeWeb.active = false;
      }
      break;

    case FERME: // on regarde si on a un ordre en attente et s'il est pour nous
      if ((commandeWeb.active) && (commandeWeb.volet == unVolet)) { // la seule commande à accepter c'est d'ouvrir
        if (commandeWeb.actionCMD_V == OUVRIR) ouvrirUnVolet(unVolet);
        commandeWeb.active = false;
      }
      break;

    case EN_FERMETURE:
      if (millis() - unVolet->debutCourse >= (unVolet->dureeCourseTotale - unVolet->dureeJusquaFinCourse)) { // on regarde si le temps est écoulé , arrive en fin de course
        arreterUnVolet(unVolet);
        unVolet->dureeJusquaFinCourse = unVolet->dureeCourseTotale;
        unVolet->etat = FERME;
      }
      else { // cas du stop ou d ' inversion de sens
        if ((commandeWeb.active) && (commandeWeb.volet == unVolet)) { // on regarde si on a un ordre en attente et s'il est pour nous
          if (commandeWeb.actionCMD_V == OUVRIR) { // on repart en sens inverse , donc  on met à jour le temps restant pour ouverture complète , cas de l ' inversion de sens
            arreterUnVolet(unVolet);
            unVolet->dureeJusquaFinCourse += (millis() - unVolet->debutCourse);
            delay(100);
            ouvrirUnVolet(unVolet);
          }
          else if (commandeWeb.actionCMD_V == ARRETER) { // on met à jour le temps restant pour ouverture complète , cas du STOP
            unVolet->dureeJusquaFinCourse += (millis() - unVolet->debutCourse);
            arreterUnVolet(unVolet);
          }
          commandeWeb.active = false;
        }
      }
      /*if(DEBUG) {
        Serial.print(F("unVolet->dureeCourseTotale = "));
        Serial.println(unVolet->dureeCourseTotale);
        Serial.print(F("unVolet->dureeJusquaFinCourse = "));
        Serial.println(unVolet->dureeJusquaFinCourse);
        Serial.print(F("unVolet->debutCourse = "));
        Serial.println(unVolet->debutCourse);
      }*/
      break;

    case EN_OUVERTURE:
      if (millis() - unVolet->debutCourse >= unVolet->dureeJusquaFinCourse) { // on regarde si le temps écoulé est supérieur au temps restant pour ouverture , arrive en fin de course
        arreterUnVolet(unVolet);
        unVolet->dureeJusquaFinCourse = 0;
        unVolet->etat = OUVERT;
      }
      else { // cas du stop ou d ' inversion de sens
        if ((commandeWeb.active) && (commandeWeb.volet == unVolet)) { // on regarde si on a un ordre en attente et s'il est pour nous
          if (commandeWeb.actionCMD_V == FERMER) { // on repart en sens inverse , donc  on met à jour le temps restant pour ouverture complète , cas de l ' inversion de sens
            arreterUnVolet(unVolet);
            unVolet->dureeJusquaFinCourse -= (millis() - unVolet->debutCourse);
            delay(100);
            fermerUnVolet(unVolet);
          }
          else if (commandeWeb.actionCMD_V == ARRETER) { // on met à jour le temps restant pour ouverture complète
            unVolet->dureeJusquaFinCourse -= (millis() - unVolet->debutCourse);
            arreterUnVolet(unVolet);
          }
          commandeWeb.active = false;
        }
      }
      /*if(DEBUG) {
        Serial.print(F("unVolet->dureeCourseTotale = "));
        Serial.println(unVolet->dureeCourseTotale);
        Serial.print(F("unVolet->dureeJusquaFinCourse = "));
        Serial.println(unVolet->dureeJusquaFinCourse);
        Serial.print(F("unVolet->debutCourse = "));
        Serial.println(unVolet->debutCourse);
      }*/
      break;

    case ENTROUVERT:
      if ((commandeWeb.active) && (commandeWeb.volet == unVolet)) { // on regarde si on a un ordre en attente et s'il est pour nous
        if (commandeWeb.actionCMD_V == OUVRIR) ouvrirUnVolet(unVolet);
        else if (commandeWeb.actionCMD_V == FERMER) fermerUnVolet(unVolet);
        commandeWeb.active = false;
      }
      break;

    default :
      if(DEBUG) {
        Serial.print(F("commandeWeb.active apres = "));
        Serial.println(commandeWeb.active);
        Serial.print(F("commandeWeb.actionCMD_V apres = "));
        Serial.println(commandeWeb.actionCMD_V);
      }
      commandeWeb.active = false;
      break;
  } // fin du switch
  // comme cette fonction est appellé en permanence , il vaut mieux eviter de mettre quoi que ce soit ici
} //fin fonction

void gererLesVolets() {
  if (commandeWeb.active && commandeWeb.volet == NULL && commandeWeb.arrosage == NULL && commandeWeb.actionCMD_A == NULL_A) {
    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]));
}

//// Fonctions d ' arrosage : ////

void initialiserUnArrosage(t_arrosage* unArrosage) {
  pinMode(unArrosage->pinRelais, OUTPUT);
  digitalWrite(unArrosage->pinRelais, RELAIS_INACTIF); // à l'arret
  if (DEBUG) {
    Serial.print (F("* initialiserUnArrosage => PIN : "));
    Serial.println (unArrosage->pinRelais);
  }
}

//// mode manuel ////

void activerTimerArrosage (t_arrosage* unArrosage) {
  // si une tache existe deja , on la remplace par la nouvelle , donc , 1ere chose , on supprime la tache si elle existe deja .
  desactiverTimerArrosage(unArrosage->pinRelais);

  if (!gestionnaireDeTache.findAsyncCommand(unArrosage->pinRelais)) {
    gestionnaireDeTache.registerAsyncCommand(unArrosage->pinRelais, unArrosage->timerArrosage, desactiverArrosage);// normal le reinit ici : oui fonction de callback ( c ' est elle qui reinitialisera le relais a la fin du timer )
    if (DEBUG) {
      Serial.print (F("* activerTimerArrosage => tache "));
      Serial.print (unArrosage->pinRelais);
      Serial.print (F(" enregistrée pour dans "));
      Serial.println (unArrosage->timerArrosage);
    }
  }
}

// pour supprimer le timer lors de l' arret manuel d ' un arrosage si celui-ci a été activé
void desactiverTimerArrosage (uint8_t pin) {
  if (gestionnaireDeTache.findAsyncCommand(pin)) {
    gestionnaireDeTache.unregisterAsyncCommand(pin);
    if (DEBUG) {
      Serial.print (F("* desactiverTimerArrosage => tache : "));
      Serial.print (pin);
      Serial.println (F(" supprimee"));
    }
  }
  else { // gestion des doubles ID manuel / auto
    pin=pin+100;
    if (gestionnaireDeTache.findAsyncCommand(pin)) {
      gestionnaireDeTache.unregisterAsyncCommand(pin);
      if (DEBUG) {
        Serial.print (F("* desactiverTimerArrosage => tache : "));
        Serial.print (pin);
        Serial.println (F(" supprimee"));
      }
    }
  }
}

// on recoit le pin du timer asynctask ...
// cette fonction sert uniquement a desactiver le bon relais et mettre a jour l' etat .
// elle est appelée uniquement par le callback du gestionnaire de tache , voir fichier timers_rtc.ino
void desactiverArrosage(t_commandID pin) {
  for (uint8_t i = 0; i < NOMBREDARROSAGE; i++) {
    if (lesArrosages[i].pinRelais == pin) {
      lesArrosages[i].etat = INACTIF;
      if (!TEST) digitalWrite(lesArrosages[i].pinRelais, RELAIS_INACTIF);
      lesArrosages[i].finArrosage = millis();
      if (DEBUG) {
        Serial.print (F("* desactiverArrosage => indice = "));
        Serial.print (i);
        Serial.print (F(", relais = "));
        Serial.println (lesArrosages[i].pinRelais);
      }
    }
  }
}

void activerUnArrosageEtUnTimer(t_arrosage* unArrosage) {
  if (!TEST) digitalWrite(unArrosage->pinRelais, RELAIS_ACTIF);
  unArrosage->etat = ACTIF;
  //activerTimerArrosage (unArrosage->zone);
  activerTimerArrosage (unArrosage);
  unArrosage->debutArrosage = millis(); // voir comment faire pour afficher le temps d' arrosage effectue pour chaque zone active.
  unArrosage->finArrosage = 0;

  if (DEBUG) {
    Serial.print (F("* activerUnArrosageEtUnTimer => "));
    Serial.print (F("unArrosage->zone : "));
    Serial.print (unArrosage->zone);
    Serial.print (F(", unArrosage->pinRelais : "));
    Serial.println (unArrosage->pinRelais);
  }
}

void desactiverUnArrosageEtUnTimer(t_arrosage* unArrosage) {
  if (DEBUG) {
    Serial.print (F("* desactiverUnArrosageEtUnTimer => "));
    Serial.print (F("unArrosage->zone : "));
    Serial.print (unArrosage->zone);
    Serial.print (F(", unArrosage->pinRelais : "));
    Serial.println (unArrosage->pinRelais);
  }
  if (!TEST) digitalWrite(unArrosage->pinRelais, RELAIS_INACTIF);
  unArrosage->etat = INACTIF;
  desactiverTimerArrosage (unArrosage->pinRelais);
  //unArrosage->debutArrosage = millis(); // voir comment faire pour afficher le temps d' arrosage effectue pour chaque zone active.
  unArrosage->finArrosage = millis();
}

void inverserUnArrosageEtUnTimer(t_arrosage* unArrosage) {
  if (unArrosage->etat != ACTIF) activerUnArrosageEtUnTimer (unArrosage);
  else if (unArrosage->etat != INACTIF) desactiverUnArrosageEtUnTimer (unArrosage);
}

void activerUnArrosage(t_arrosage* unArrosage) {
  if (!TEST) digitalWrite(unArrosage->pinRelais, RELAIS_ACTIF);
  unArrosage->debutArrosage = millis(); // voir comment faire pour afficher le temps d' arrosage effectue pour chaque zone active.
  unArrosage->finArrosage = 0;
  unArrosage->etat = ACTIF;
  if (DEBUG) {
    Serial.print (F("* activerUnArrosage : "));
    Serial.println (unArrosage->pinRelais);
  }
}

void desactiverUnArrosage(t_arrosage* unArrosage) {
  unArrosage->finArrosage = millis();
  if (!TEST) digitalWrite(unArrosage->pinRelais, RELAIS_INACTIF);
  unArrosage->etat = INACTIF;
  if (DEBUG) {
    Serial.print (F("* desactiveArrosage : "));
    Serial.println (unArrosage->pinRelais);
  }
}

void gererUnArrosage(t_arrosage* unArrosage) {
  switch (unArrosage->etat) { // ACTIF, INACTIF, TIMER, AUTO
    case INACTIF : // MARCHE, ARRET, NULL_A, MARCHE_TOUT, ARRET_TOUT
      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 ACTIF :
      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 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 ((commandeWeb.active) && (commandeWeb.arrosage == unArrosage)) {
        if (commandeWeb.actionCMD_A == AUTOMA) {
          if (DEBUG) {
            Serial.print (F("* gererUnArrosage => case AUTOMA => unArrosage-> zone : "));
            Serial.print (unArrosage->zone);
            Serial.print (F(", unArrosage->pinRelais : "));
            Serial.println (unArrosage->pinRelais);
          }
          inverserUnArrosageEtUnTimer(unArrosage);
          commandeWeb.active = false;
        }
      }        break;
    default:
      if (DEBUG) Serial.println (F("* gererUnArrosage => probleme = case non reconnu !"));
      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 == AUTOMA) {
        if (arrosageAutoON) for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) initialArrosageAuto(&(lesArrosages[a]));
        else for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) desactiverUnArrosageEtUnTimer(&(lesArrosages[a]));
      }
    commandeWeb.active=false;
  }
  else for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) gererUnArrosage(&(lesArrosages[a]));
}
/*		mode manuel		*/

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

/*		mode auto		*/

void initialArrosageAuto (t_arrosage* unArrosage) {
  static uint8_t initialAutoA = NOMBREDARROSAGE;
  // objectif lancer les taches auto a intervalles reguliers
  // et remplir la structure et tableau arrosage avec les valeurs pour travailler
  if (arrosageAutoON && initialAutoA) {
    // si on desire se fier au relais utilisé , il faut d' abord recuperer celui-ci : lesArrosages[i].pinRelais
    uint8_t ID100 = unArrosage->pinRelais + 100;

    calculDataArrosageAuto(unArrosage);

    gestionnaireDeTache.registerAsyncCommand(ID100, TIMER_V, marcheArrosageAuto); // initialisation autonome : ID 100 a 105
    // 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 .
    if (DEBUG) {
      Serial.print (F("* initialArrosageAuto => effectue sur zone : "));
      Serial.print (unArrosage->zone);
      Serial.print (F(". tache d ' arrosage principale enclenchée dans "));
      Serial.print (TIMER_V);
      Serial.print (F(". ID100 : "));
      Serial.print (ID100);
      Serial.print (F(" sur pin correspondant : "));
      Serial.print (unArrosage->pinRelais);
      Serial.print (F(", unArrosage->joursIntervalle : "));
      Serial.print (unArrosage->joursIntervalle);
      Serial.print (F(", unArrosage->timerProchainArrosage : "));
      Serial.println (unArrosage->timerProchainArrosage);

    }
  initialAutoA--;
  }
}

void marcheArrosageAuto (t_commandID ID100) { // ID100 est le numero de pin +100 , pas un index du tableau

  uint8_t pin = ID100-100;

  for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) {
    if (lesArrosages[a].pinRelais == pin ) {
      lesArrosages[a].zoneArrosee = false;
      //if (saison != HIVER && !lesArrosages[pin].zoneArrosee) {
      //if (!lesArrosages[pin].zoneArrosee) { // la condition peut si zone arrosée vaut vrai , bloquer la creation de la tache d' arrosage
      lesArrosages[a].etat = ACTIF; // gestion etat du relais
      if (!TEST) digitalWrite (lesArrosages[a].pinRelais , RELAIS_ACTIF); // gestion du relais
      gestionnaireDeTache.registerAsyncCommand(ID100, lesArrosages[a].dureeJusquaFinArrosage, resetTimerArrosageAuto);
      // on utilise ID100 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 .
      if (DEBUG) {
        Serial.print (F("* marcheArrosageAuto => tache arrosageAuto id100 : "));
        Serial.print (ID100);
        Serial.print (F(" lance pendant "));
        Serial.print (lesArrosages[a].dureeJusquaFinArrosage/1000);
        Serial.print (F(" secondes , sur le pin : "));
        Serial.println (lesArrosages[a].pinRelais);
      }
    }
  }
}

void resetTimerArrosageAuto (t_commandID ID100) { // permet de relancer le prochain arrosage automatique
  // ID100 est le numero de pin +100 , pas un index du tableau
  uint8_t pin = ID100-100;

  for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) {
    if (lesArrosages[a].pinRelais == pin ) {
      if (lesArrosages[a].etat == ACTIF) {
        resetZoneArrosee = true; // marque les zones a reinitialiser
        lesArrosages[a].zoneArrosee = true; // marque la zone comme arrosee
        if (!TEST) digitalWrite(lesArrosages[a].pinRelais, RELAIS_INACTIF); // relais off
        lesArrosages[a].etat = INACTIF; // gestion etat

        if (DEBUG) {
          Serial.print (F("* resetTimerArrosageAuto => zoneArrosee "));
          Serial.print (a);
          Serial.print (F(" = "));
          Serial.println (lesArrosages[a].zoneArrosee);
        }
      }
      if (arrosageAutoON) {
        calculDataArrosageAuto(&(lesArrosages[a]));
        gestionnaireDeTache.registerAsyncCommand(ID100, lesArrosages[a].timerProchainArrosage, marcheArrosageAuto); // tourne en boucle , enregistre le prochain arrosage differe
                // on active une nouvelle tache ayant pour id le numero de pin + 100 / le timer pour relancer le prochain arrosage / et la fonction automatique d ' arrosage pour callback .
        if (DEBUG) {
          Serial.print (F("* resetTimerArrosageAuto => Nouvelle tache enregistrée ayant pour ID : "));
          Serial.print (ID100);
          Serial.print (F(" pour durée :"));
          Serial.println (lesArrosages[a].timerProchainArrosage);
        }
      }
      else {
        gestionnaireDeTache.unregisterAsyncCommand(ID100);
        if (DEBUG) Serial.print (F("* resetTimerArrosageAuto => tache supprimée ! "));
      }
    }
  }
}

void resetZoneArrosage () { // on reinitialise les zones arrosées pour le lendemain
  if (resetZoneArrosee && (RTC.heure() >= HEURE_RESET_A)) {
    resetZoneArrosee = false;
    for (uint8_t a = 0; a < NOMBREDARROSAGE; a++) lesArrosages[a].zoneArrosee = false;
    if (DEBUG) Serial.println (F("resetZoneArrosee => zones reinitialisees !"));
  }
}

//// Fonctions de calculs : ////
void setSaison(t_commandID identifier) {	// Gere le mode HORSGEL_ON  *///////
  if ((RTC.mois() == 3 && RTC.jour() >= 20) || RTC.mois() ==  4 || RTC.mois() == 5 || (RTC.mois() == 6 && RTC.jour() < 20)) saison = PRINTEMPS;
  else if ((RTC.mois() == 6 && RTC.jour() >= 20) || RTC.mois() == 7  || RTC.mois() == 8  || (RTC.mois() == 9 && RTC.jour() < 22)) saison = ETE;
  else if ((RTC.mois() == 9 && RTC.jour() >= 22) || RTC.mois() == 10 || RTC.mois() == 11 || (RTC.mois() == 12 && RTC.jour() < 21)) saison = AUTOMNE;
  else if ((RTC.mois() == 12 && RTC.jour() >= 21) || RTC.mois() ==  1 || RTC.mois() == 2 || (RTC.mois() == 3 && RTC.jour() < 20)) saison = HIVER;

  if (saison != 5) { // initialisation a 5 expres .
    gestionnaireDeTache.registerAsyncCommand(identifier, 12 * TIMER_A, setSaison); // verifie 24 H apres si la saison n' a pas changé , ID 98
    if (DEBUG) {
        Serial.print (F("* setSaison => saison = "));
        Serial.println (saison);
      }
  }
  else {
    gestionnaireDeTache.registerAsyncCommand(identifier, TIMER_V, setSaison); // on recommence dans 30 secondes .
    if (DEBUG) {
      Serial.print (F("* setSaison => saison = ERREUR !"));
    }
  }
}

void calculDataArrosageAuto (t_arrosage* unArrosage) {	// l ' objectif est d ' espacer a intervalle de jours reguliers les arrosages des differentes zones en fonctions des saisons et de la zone a arroser
  uint8_t nbJours = 0;
  uint8_t HDA=0;
  uint32_t convertJoursToMs = 24UL*60UL*60UL*1000UL; // Conversion en millisecondes

  switch (saison) {
    case PRINTEMPS :
      coeffSaison = 1;
      if (strcmp(unArrosage->nom, "Arbres") == 0 || strcmp(unArrosage->nom, "Fruitiers") == 0) {
        nbJours = 7; // utile pour lancer le prochain arrosage via asynctask.
      }
      else if (strcmp(unArrosage->nom, "Haies") == 0 || strcmp(unArrosage->nom, "Jardin") == 0)	{
        nbJours = 5 ;
      }
      else if (strcmp(unArrosage->nom, "Bordures") == 0 || strcmp(unArrosage->nom, "Potager") == 0)	{
        nbJours = 2;
      }
      HDA=5;
        break;

    case ETE :
      coeffSaison=1.5;
      if (strcmp(unArrosage->nom, "Arbres") == 0 || strcmp(unArrosage->nom, "Fruitiers") == 0)	{
        nbJours = 4;
      }
      else if (strcmp(unArrosage->nom, "Haies") == 0 || strcmp(unArrosage->nom, "Jardin") == 0)	{
        nbJours = 3;
      }
      else if (strcmp(unArrosage->nom, "Bordures") == 0 || strcmp(unArrosage->nom, "Potager") == 0)	{
        nbJours = 1;
      }
      HDA=0;
      break;

    case AUTOMNE :
      coeffSaison=1;
      if (strcmp(unArrosage->nom, "Arbres") == 0 || strcmp(unArrosage->nom, "Fruitiers") == 0) {
        nbJours = 7; // utile pour lancer le prochain arrosage via asynctask.
      }
      else if (strcmp(unArrosage->nom, "Haies") == 0 || strcmp(unArrosage->nom, "Jardin") == 0)	{
        nbJours = 5 ;
      }
      else if (strcmp(unArrosage->nom, "Bordures") == 0 || strcmp(unArrosage->nom, "Potager") == 0)	{
        nbJours = 2;
      }
      HDA=6;
      break;

    case HIVER : // voir avec temperature exterieure , sinon desactive
      coeffSaison=0.5;
      nbJours = 21;
      HDA=12;
      break;

    default:
      if (DEBUG) Serial.println(F("* => calculIntervalleJoursArrosage : ERREUR !!!"));
      break;
  }
  unArrosage->dureeJusquaFinArrosage = (TIMER_A * coeffSaison);
  unArrosage->joursIntervalle = nbJours;
  unArrosage->timerProchainArrosage = convertJoursToMs * nbJours;
  unArrosage->heureDebutArrosage = HDA;

  if (DEBUG) {
    Serial.print(F("* => calculIntervalleJoursArrosage : nbJours = "));
    Serial.print(nbJours);
    Serial.print(F(" , coeffSaison = "));
    Serial.print(coeffSaison);
    Serial.print(F(" , heureDebutArrosage = "));
    Serial.print(unArrosage->heureDebutArrosage);
    Serial.print(F(" , tempsDArrosage = "));
    Serial.println(unArrosage->dureeJusquaFinArrosage);
  }
}

/*		mode auto		*/
//// FIN des Fonctions d ' arrosage : ////
//// Detecteurs ////

// detecteur de pluie
bool readPluieSensor () {
	// mise en marche , attente , lecture puis arret du module :
	// pour eviter la corrosion et limiter la consomation d ' energie.
	// brancher sur un pin de sortie au lieu du 3.3 volts direct
	static unsigned long chrono = 0;
	static int value = -1; // jamais lu
	if ((value == -1) || (millis() - chrono > TIMER_V)) { // si jamais lue ou lue depuis plus de 30 secondes
		digitalWrite(PLUIE_SENSOR_VCC, RELAIS_ACTIF);
    //   Attente ??
 		value = analogRead(PLUIE_SENSOR_PIN);
		digitalWrite(PLUIE_SENSOR_VCC, INACTIF);
		chrono = millis();
    value = 0;
	}
	return (map(value, 0, 1023, 0, 100) > SEUIL_PLUIE); // définir 990 et 300 en constantes globales serait mieux
}

//	 detecteur d' humidité du sol : humidity
//	 The sensor has a built-in potentiometer for sensitivity adjustment of the digital output (D0)
//	  All the resources for this project:
//	 https://randomnerdtutorials.com/
bool readHumiditySensor() {
	// mise en marche , attente , lecture puis arret du module :
	// pour eviter la corrosion et limiter la consomation d ' energie.
	// brancher sur un pin de sortie au lieu du 3.3 volts direct
	static unsigned long chrono = 0;
	static int value = -1; // jamais lu
	if ((value == -1) || (millis() - chrono > TIMER_V)) { // si jamais lue ou lue depuis plus de 30 secondes
		digitalWrite(HUMIDITY_SENSOR_VCC, RELAIS_ACTIF);
    //   Attente ??
		value = analogRead(HUMIDITY_SENSOR_PIN);
		digitalWrite(HUMIDITY_SENSOR_VCC,  INACTIF);
		chrono = millis();
	}
	return (map(value, 0, 1023, 0, 100) > SEUIL_HUMIDITE); // définir 990 et 300 en constantes globales serait mieux
}

//// FIN Detecteurs ////
//// Fonctions d initialisation : ////
void initSDCard () { // disable enable SD Card
	if (ENABLE_SDCARD) {
		pinMode(SDCARD_PIN, OUTPUT);
		digitalWrite(SDCARD_PIN, RELAIS_INACTIF);
    if(DEBUG) {
      Serial.print (F("* initSDCard => ENABLE_SDCARD : "));
      Serial.println (ENABLE_SDCARD);
      Serial.print (F("* initSDCard => SDCARD_PIN : "));
      Serial.println (SDCARD_PIN);
    }
  }
  else if(DEBUG) Serial.println (F("* No SDCard ."));
}

void initWebServer () { // Eth and UDP
	if (ip != IPAddress(0,0,0,0) ) Ethernet.begin(MacAddress, ip); // demarrage du shield en IP static , On démarre le shield Ethernet
	else if (Ethernet.begin(MacAddress) == 0) {
		if (DEBUG) Serial.println (F("initWebServer => Failed to configure Ethernet using DHCP"));
		// no point in carrying on, so do nothing forevermore:
		for (;;)
		;
	}
	else if (! int (Ethernet.begin(MacAddress))) { // Probleme de DHCP
		if (DEBUG) Serial.println (F("initWebServer => NO DHCP"));
		while (true); // stop here
	}

	webServer.begin();
	Udp.begin(localPort);
	if (DEBUG) printServerAddress();
}

void initGestionnaireDeTache () {
	gestionnaireDeTache.registerAsyncCommand(99, TIMER_V/10, reglerHeureByNTP); // 3 secondes , le temps que le shield ethernet soit initialisé correctement . ensuite les reglages se passe directement dans la fonction .
  delay(750);
  gestionnaireDeTache.registerAsyncCommand(98, TIMER_V/10, setSaison); // 24 heures
}

void initSetupPluie () {
	pinMode(PLUIE_SENSOR_PIN, INPUT);
	pinMode(PLUIE_SENSOR_VCC, OUTPUT);
	digitalWrite(PLUIE_SENSOR_VCC, RELAIS_INACTIF); // ACTIF OU PAS au demarrage ???
  if(DEBUG) {
    Serial.print (F("* initSetupPluie => PLUIE_SENSOR_PIN ( input ) : "));
    Serial.println (PLUIE_SENSOR_PIN);
    Serial.print (F("* initSetupPluie => PLUIE_SENSOR_VCC : "));
    Serial.println (PLUIE_SENSOR_VCC);
  }
}

void initSetupHumidity () {
	pinMode(HUMIDITY_SENSOR_PIN, INPUT);
	pinMode(HUMIDITY_SENSOR_VCC, OUTPUT);
	digitalWrite(HUMIDITY_SENSOR_VCC, RELAIS_INACTIF); // ACTIF OU PAS au demarrage ???
  if(DEBUG) {
    Serial.print (F("* initSetupHumidity => HUMIDITY_SENSOR_PIN ( input ) : "));
    Serial.println (HUMIDITY_SENSOR_PIN);
    Serial.print (F("* initSetupHumidity => HUMIDITY_SENSOR_VCC : "));
    Serial.println (HUMIDITY_SENSOR_VCC);
  }
}

//// FIN Des Fonctions d ' initialisations ////

NOTE : les arrosages et les volets fonctionnent sur le meme principe que celui attendu des relais sans aucun soucis .
Pour les volets le code est de @J-M-L .

Pour info , avant que je modifie le code pour passer aussi par elle pour les relais , je l' avais ecrite comme cela :

void affecteCommandeWeb(bool b, t_volet* v=NULL, t_commandeVolet cv=NULL_V, t_arrosage* a=NULL, t_commandeArrosage ca=NULL_A)
{
  commandeWeb.active=b; // bool
  commandeWeb.volet=v;
  commandeWeb.actionCMD_V=cv;
  commandeWeb.actionCMD_A=ca;
  commandeWeb.arrosage=a;

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

Mais au final , si je me rapelle bien ( pas sur d ' avoir testé au final tellement j ' ai galeré avec ce truc là ) ,en ajoutant et en gardant le meme principe , ca ne fonctionnait pas non plus .
je vais refaire un test , sait on jamais .


ci joint le zip complet du code , si vous preferez :
serveur_machine_etats_7.330.zip (21.9 KB)

ok , alors il me semblait bien que c' etait par quoi j' avais commencé .
en modifiant de la sorte , resultat identique , je n' atteris toujours pas dans " gererUnRelais () " :

/*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) gererLesVolets();
    else if (ca != NULL_A) gererLesArrosages();
    else if (cr != NULL_R) gererLesRelais();
  }
}*/
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(); 
  }
}

EDIT : je viens de voir que la fonction pour determiner la saison ne s ' execute plus non plus .
je pense que j ' ai du faire une bourde quelquepart . ce qui est bizzare c ' est que le compilateur n' affiche meme pas un warning ...


2eme EDIT , j ' avais ecrit TIMERR_A au lieu de TIMER_A ... et les 2 existent , donc pas d ' erreur , mais pas d ' execution de la fonction .

enum t_commandeArrosage : uint8_t {MARCHE, ARRET, MARCHE_TOUT, ARRET_TOUT, TIMERR_A, AUTOMA, NULL_A};

car ce n ' est pas un timer , chose que ne verifie a priori pas la bibliotheque asyncTask ...
Mais pas de changement quand au relais portail avec affecteCommandeWeb .

Bonjour , alors ce matin je me suis repenché sur la question et finalement j' ai mis cela :

              // Sinon , pour etre uniforme : ACTIVE, INACTIVE, TIMERR_R, NULL_R
              affecteCommandeWeb(true, NULL, NULL_V, NULL, NULL_A, &(lesRelais[r]), ACTIVE);

et la magie a operée :stuck_out_tongue_winking_eye:

sauf que ce n' est pas celui que je voudrais employer pour le portail .
je voudrais utiliser le TIMER .
alors j' ai mis :

              // Sinon , pour etre uniforme : ACTIVE, INACTIVE, TIMERR_R, NULL_R
              affecteCommandeWeb(true, NULL, NULL_V, NULL, NULL_A, &(lesRelais[r]), TIMERR_R);

et là retour a la case depart .
Mais y a du progrés :wink:

A+

Titre édité pour être plus représentatif du sujet.

Les titre sont utilisés par le moteur de recherche du forum.

votre fonction

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();
  }
}

fait des choix composés et ne passe pas à la fonction gererLesRelais() la valeur de cr par exemple donc même si les conditions sont requises cr != NULL_R && cv == NULL_V && ca == NULL_A une fois dans la fonction gererLesRelais() vous ne saurez plus ce qui avait été demandé


le code a beaucoup évolué depuis le début et vous gagneriez à faire un gros nettoyage et repartir sur une version 2 à bases de classe et d'héritage et de fonctions virtuelles.... :innocent: :scream:

C'est quand même mieux avec tout le code, car moi j'avais compris que ta fonction affecteCommandeWeb modifiait le propriété de ta structure et la variable commandeWeb pour que la fonction gererLesRelais active tes relais.
Alors qu'elle positionne à vrai commandeWeb.active et appelle la fonction gererUnRelais sur le relais donné dans la commande WEB.

par contre je n'ai pas compris ce que tu cherchais à faire avec le paramètre cr, qui appelle la fonction gererLesRelais, mais cette fonction ne semble pas utiliser la prorpiété actionCMD_R de ta variable comandeWEB?
Bien au contraire si cr est différent de null, devrait appeler gererLesRelais , qui lui même va appeler gererUnRelais sur tout les relais, puisque tu a donné quelque chose à ta variable r

Tout ceci semble un peu compliqué et que comme ça ne marchait pas comme tu veux, tu as du perdre le fil de ton algorithme.