#include <Wire.h>
#include <hd44780.h>                        // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h>  // i2c expander i/o class header
#include <Toggle.h>                         // https://github.com/Dlloydev/Toggle
#include "Sonde.h"

// les leds
const byte ledCa = 50;  // pin Led fonction CANICULE
const byte ledFr = 51;  // pin led fonction FROID
const byte ledCh = 52;  // pin Led fonction CHAUFFAGE

// les relais
const byte relaisComp = 30;          // pin relais Compresseur
const byte relaisV4V = 31;           // pin relais vanne 4 voies
const byte relaisVentUniteExt = 32;  // pin relais Ventilateur Unité Exterieur
const byte relaisVentExt = 33;       // pin relais Ventilateur Exterieur (petite et grande Vitesses)
const byte relaisVitesse1 = 34;  // pin relais Ventilateur Interieur vitesse 1
const byte relaisVitesse2 = 35;       // pin relais Ventilateur Interieur vitesse 2
const byte relaisVitesse3 = 36;     // pin relais Ventilatuer Interieur vitesse 3
const byte relaisVitesse4 = 37;     // pin relais Ventilateur Interieur vitesse 4
const byte relaisVolets = 38;        // pin relais Saison été / hiver

// les capteurs
const byte thermostatsPin = 8;        // pin thermostat Chambre 1 , bouton poussoir pour test
const byte capteurFiltrePin = 40;  // pin capteur de presence porte filtre
Toggle thermostats;
Toggle capteurFiltre;
const unsigned int ms = 200;

// les boutons
const byte menuPin = 22;         // pin bouton Menu
const byte validPin = 23;        // pin bouton Valid
const byte boutonPlusPin = 24;   // pin bouton Plus
const byte boutonMoinsPin = 25;  // pin bouton Moins
Toggle boutonMenu;
Toggle boutonValid;
Toggle boutonPlus;
Toggle boutonMoins;
const unsigned int ms1 = 2000;

// les led boutons
const byte ledBoutonMenu = 42;  // pin Led bouton Menu
const byte ledBoutonValid = 43; // pin led bouton Valid
const byte ledBoutonPlus = 44;  // pin led bouton Plus
const byte ledBoutonMoins = 45; // pin led bouton Moins

// Les sondes
Sonde sondes[] = {
  { 2, "T""\xDF""Ext:" },  // pin sonde Exterieur
  { 3, "T""\xDF""UnitExt:" },  // pin sonde Unite Exterieur
  { 4, "T""\xDF""EchaExt:" },  // pin sonde Echangeur Exterieur
  { 5, "T""\xDF""UnitInt:" },  // pin sonde Unite Interieur
  { 6, "T""\xDF""EchaInt:" }   // pin sonde Echangeur Interieur
};
constexpr size_t nombreDeSondes = sizeof sondes / sizeof * sondes;
size_t indexSondeEnCours = 0;
float temperatureAffichee = -127;

enum : byte { SondeExterieur,
              UniteExterieur,
              EchangeurExterieur,
              UniteInterieur,
              EchangeurInterieur
            };
#define tempExtLue (sondes[SondeExterieur].temperature())
#define tempUnitExtLue (sondes[UniteExterieur].temperature())
#define tempEchangeurExtLue (sondes[EchangeurExterieur].temperature())
#define tempUnitIntLue (sondes[UniteInterieur].temperature())
#define tempEchangeurIntLue (sondes[EchangeurInterieur].temperature())

// les tempos
unsigned long long tempoTempLcd;
unsigned long long tempoLcdConsigne;
unsigned long long tempoBacklightLcd;

unsigned long long tempoFr;
unsigned long long tempoV4VFr;
unsigned long long tempoCompFr;
unsigned long long tempoDegFr;
unsigned long long tempoTempDegFr;
unsigned long long tempoCh;
unsigned long long tempoCompCh;
unsigned long long tempoV4VCh;
unsigned long long tempoTempDegEle;
unsigned long long tempoDegCh;
unsigned long long tempoDegNat;
unsigned long long tempoFinDegNat;
unsigned long long tempoEgouttageFr;
unsigned long long tempoEgouttageNat;
unsigned long long tempoEgouttageEle;
unsigned long long tempoFinEgouttageEle;
unsigned long long tempoCa;

unsigned long long tempoVentilation;

unsigned long long departChronoFiltre;
unsigned long long finChronoFiltre;
unsigned long nettoyageFiltre;

unsigned long tempoLedFrClignoteDeg = 0;
bool ledFrCl = 0;
unsigned long tempoLedChClignoteDeg = 0;
bool ledChCl = 0;
unsigned long tempoLedCaClignoteDeg = 0;
bool ledCaCl = 0;
unsigned long tempoLedArretCompletProgramClignote = 0;
bool ledChFrCaCl = 0;
unsigned long tempoLedBoutonClignote = 0;
bool ledBoutonMenuCl = 0;
bool ledBoutonMoinsCl = 0;
bool ledBoutonPlusCl = 0;
bool ledBoutonValidCl = 0;

unsigned long tempoAutoMode = 0;

// les consignes
struct Consigne {
  const char* nom;
  float consigne;
};

struct Consigne consignes[] = {
  { "T""\xDF""Ch/Fr", 13.5 }, //0 consigne via sonde Ext
  { "T""\xDF""Ca", 30.0 },  //1 sonde Ext
  { "T""\xDF""GVExtFr", 20.0 },   //2 sonde UnitExt
  { "T""\xDF""GVExtCh", 5.0 },    //3 sonde UnitExt
  { "T""\xDF""DegNaCh", 5.0 },    //4 sonde UnitExt
  { "T""\xDF""blocCh", 11.0 },  //5 sonde UnitExt
  { "T""\xDF""DegEleCh", -3.0 },  //6 sonde EchangeurExt
  { "T""\xDF""FinDegCh", 12.5 },  //7 sonde EchangeurExt
  { "T""\xDF""PVIntFr", 23.0 },   //8 sonde UnitInt
  { "T""\xDF""PVIntCh", 22.0 },   //9 sonde UnitInt
  { "T""\xDF""VentFr", 24.0 },  //10 sonde UnitInt
  { "T""\xDF""VentCh", 22.0 },  //11 sonde UnitInt
  { "T""\xDF""DVIntCh", 30.0 },  //12 sonde EchangeurInt
  { "T""\xDF""DegFr", -3.0 },      //13 sonde EchangeurInt
  { "T""\xDF""FinDegFr", 15.0 },  //14 sonde EchangeurInt
  { "T""\xDF""Delta", 6 }      //15
};
constexpr size_t nombreDeConsignes = sizeof consignes / sizeof * consignes;
size_t indexConsigneEnCours = 0;

float consigneIntCa;

// les vitesses interieur
struct Vitesse {
  const char* nom;
  byte vitesse;
};

struct Vitesse vitesses[] = {
  { "Auto", 0 },
  { "Vitesse", 1 },
  { "Vitesse", 2 },
  { "Vitesse", 3 },
  { "Vitesse", 4 }
};
constexpr size_t nombreDeVitesses = sizeof vitesses / sizeof * vitesses;
size_t indexVitesseEnCours = 0;
bool modifVitesseInt = false;
byte vitesseTravail = vitesses[indexVitesseEnCours].vitesse;

bool departGainable = false; // active ou desactive la machine a etasGainable (arret)

// le lcd
const uint8_t nbColonnes = 16;
const uint8_t nbLignes = 2;
hd44780_I2Cexp lcd;

// les caracteres speciaux
byte flecheFr[8] = {
  B00100,
  B00100,
  B00100,
  B00100,
  B00100,
  B10101,
  B01110,
  B00100
};
byte flecheCh[8] = {
  B00100,
  B01110,
  B10101,
  B00100,
  B00100,
  B00100,
  B00100,
  B00100
};
byte goutte[8] = {
  B00100,
  B00100,
  B00100,
  B01010,
  B10001,
  B10001,
  B10001,
  B01110
};
byte degivrage[8] = {
  B11111,
  B10001,
  B00100,
  B00100,
  B01010,
  B10001,
  B10001,
  B01110
};
byte eclaire[8] = {
  B00010,
  B00100,
  B01000,
  B11111,
  B00010,
  B00100,
  B01000,
  B10000
};
byte soleil[8] = {
  B00100,
  B10101,
  B01110,
  B11010,
  B01011,
  B01110,
  B10101,
  B00100
};
byte ventilateur[8] = {
  B01100,
  B01100,
  B00101,
  B11011,
  B11011,
  B10100,
  B00110,
  B00110
};

// les fonctions de l'affichages

void affiche(const char* etiquette, float valeur, byte ligne, byte precision = 2, byte tailleValeur = 6) {
  // on a nbColonnes-tailleValeur cases pour le nom - cadré à gauche
  // tailleValeur cases pour la consigne - cadrée à droite
  char bufferEtiquette[nbColonnes - tailleValeur + 1];
  strlcpy(bufferEtiquette, etiquette, sizeof bufferEtiquette);  // ça va tronquer l'étiquette si nécesaire
  lcd.setCursor(0, ligne);
  lcd.print(bufferEtiquette);

  char bufferValeur[nbColonnes];                           // suffisamment grand  pour nos besoins
  dtostrf(valeur, tailleValeur, precision, bufferValeur);  // conversion en chaîne de caractères

  byte nbEspaces = nbColonnes - (strlen(bufferEtiquette) + strlen(bufferValeur));
  for (byte i = 0; i < nbEspaces; i++) lcd.write(' ');  // pour cadrer la valeur à droite

  lcd.print(bufferValeur);
  static uint32_t x = 0;
}

void affiche(const char* etiquette, int valeur, byte ligne, byte tailleValeur = 6) {
  // on a nbColonnes-tailleValeur cases pour le nom - cadré à gauche
  // tailleValeur cases pour la consigne - cadrée à droite
  char bufferEtiquette[nbColonnes - tailleValeur + 1];
  strlcpy(bufferEtiquette, etiquette, sizeof bufferEtiquette);  // ça va tronquer l'étiquette si nécesaire
  lcd.setCursor(0, ligne);
  lcd.print(bufferEtiquette);

  char bufferValeur[nbColonnes];   // suffisamment grand  pour nos besoins
  itoa(valeur, bufferValeur, 10);  // conversion en chaîne de caractères

  byte nbEspaces = nbColonnes - (strlen(bufferEtiquette) + strlen(bufferValeur));
  for (byte i = 0; i < nbEspaces; i++) lcd.write(' ');  // pour cadrer la valeur à droite

  lcd.print(bufferValeur);
}

void affiche(const char* etiquette, byte ligne) {

  char bufferEtiquette[nbColonnes];
  strlcpy(bufferEtiquette, etiquette, sizeof bufferEtiquette);
  lcd.setCursor(0, ligne);
  lcd.print(bufferEtiquette);
}

// les consignes en fonction des sondes
void consigneSonde() { // affichage consignes par rapport au sondes sur la ligne 0
  if (indexConsigneEnCours == 0 || indexConsigneEnCours == 1) { // si : consigne 0 (13.5°C) ou consigne 1 (30.0°C)
    affiche(consignes[indexConsigneEnCours].nom, sondes[0].temperature(), 0); // affiche la consigne en cours et affiche la temperature lue par la sonde 0 (Temp-Ext) a la ligne 0
  } else if (indexConsigneEnCours == 2 || indexConsigneEnCours == 3 || indexConsigneEnCours == 4 || indexConsigneEnCours == 5) {
    affiche(consignes[indexConsigneEnCours].nom, sondes[1].temperature(), 0);
  } else if (indexConsigneEnCours == 6 || indexConsigneEnCours == 7) {
    affiche(consignes[indexConsigneEnCours].nom, sondes[2].temperature(), 0);
  } else if (indexConsigneEnCours == 8 || indexConsigneEnCours == 9 || indexConsigneEnCours == 10 || indexConsigneEnCours == 11) {
    affiche(consignes[indexConsigneEnCours].nom, sondes[3].temperature(), 0);
  } else if (indexConsigneEnCours == 12 || indexConsigneEnCours == 13 || indexConsigneEnCours == 14) {
    affiche(consignes[indexConsigneEnCours].nom, sondes[4].temperature(), 0);
  } else { // si non :
    affiche(consignes[indexConsigneEnCours].nom, consigneIntCa, 0); // affiche la consigne en cours et affiche la consigne canicule
  }
}

// les modification de vitesses interieur
void vitesseInterieur() { // modification des relais ventilateur interieur en fonction de l'affichage pour changer les vitesses
  if (indexVitesseEnCours == 0) { // si : 0 (automatique)
    modifVitesseInt = false; // pas de modification de vitesse
    vitesseTravail = vitesses[indexVitesseEnCours].vitesse;
  } else if (indexVitesseEnCours == 1) { // si non si : 1 (vitesse 1)
    modifVitesseInt = true; // modification de vitesse possible
    digitalWrite(relaisVitesse1, HIGH); // desactive relais ventilateur interieur petite vitesse 1 et grande vitesse 4
    digitalWrite(relaisVitesse2, LOW); // desactive relais ventilateur interieur petite vitesse 2
    digitalWrite(relaisVitesse3, LOW); // desactive relais ventilateur interieur grande vitesse 3
    digitalWrite(relaisVitesse4, LOW);
    vitesseTravail = vitesses[indexVitesseEnCours].vitesse;
  } else if (indexVitesseEnCours == 2) { // si non si ; 2 (vitesse 2)
    modifVitesseInt = true; // modification de vitesse possible
    digitalWrite(relaisVitesse1, LOW); // desactive relais ventilateur interieur petite vitesse 1 et grande vitesse 4
    digitalWrite(relaisVitesse2, HIGH); // active relais ventilateur interieur petite vitesse 2
    digitalWrite(relaisVitesse3, LOW); // desactive relais ventilateur interieur grande vitesse 3
    digitalWrite(relaisVitesse4, LOW);
    vitesseTravail = vitesses[indexVitesseEnCours].vitesse;
  } else if (indexVitesseEnCours == 3) {
    modifVitesseInt = true;
    digitalWrite(relaisVitesse1, LOW);
    digitalWrite(relaisVitesse2, LOW);
    digitalWrite(relaisVitesse3, HIGH);
    digitalWrite(relaisVitesse4, LOW);
    vitesseTravail = vitesses[indexVitesseEnCours].vitesse;
  } else if (indexVitesseEnCours == 4) {
    modifVitesseInt = true;
    digitalWrite(relaisVitesse1, LOW);
    digitalWrite(relaisVitesse2, LOW);
    digitalWrite(relaisVitesse3, LOW);
    digitalWrite(relaisVitesse4, HIGH);
    vitesseTravail = vitesses[indexVitesseEnCours].vitesse;
  }
}

// les erreurs
bool erreur = false;

// les erreurs sondes
void erreurs() {
  if (tempExtLue == -127) {
    lcd.backlight();
    lcd.clear();
    affiche("!Er-Sonde-Ext!", 1);
    etablirModeErreur();
  } else if (tempUnitExtLue == -127) {
    lcd.backlight();
    lcd.clear();
    affiche("!Er-Sonde-UExt!", 1);
    etablirModeErreur();
  } else if (tempEchangeurExtLue == -127) {
    lcd.backlight();
    lcd.clear();
    affiche("!Er-Sonde-EExt!", 1);
    etablirModeErreur();
  } else if (tempUnitIntLue == -127) {
    lcd.backlight();
    lcd.clear();
    affiche("!Er-Sonde-UInt!", 1);
    etablirModeErreur();
  } else if (tempEchangeurIntLue == -127) {
    lcd.backlight();
    lcd.clear();
    affiche("!Er-Sonde-EInt!", 1);
    etablirModeErreur();
  }
}

// La machine à état de commande
enum { ETEINT,
       CONSIGNE,
       DETAIL_CONSIGNE,
       VITESSE,
       ACTIF,
       AUTOMATIQUE,
       ERREUR,
       NETTOYAGE_FILTRE
     } mode = ETEINT;

// les Variables de commande
bool entretienFiltre = false;
byte ledMaitre = 0;

// les fonctions de commande
void etablirModeEteint() {
  lcd.backlight();
  if (departGainable == true) {
    lcd.clear();
    lcd.print("** ETEINDRE ? **");
  } else {
    lcd.clear();
    lcd.print("***  ETEINT  ***");
  }
  mode = ETEINT;
}

void etablirModeActif() {
  lcd.backlight();
  lcd.clear();
  lcd.print("** DEMARAGE ? **");
  mode = ACTIF;
}

void etablirModeAutomatique() {
  lcd.backlight();
  lcd.clear();
  departGainable = true;
  tempoBacklightLcd = millis();
  mode = AUTOMATIQUE;
}

void etablirModeConsigne() {
  lcd.backlight();
  lcd.clear();
  lcd.print("*** CONSIGNE ***");
  affiche(consignes[indexConsigneEnCours].nom, consignes[indexConsigneEnCours].consigne, 1);
  tempoLcdConsigne = millis();
  mode = CONSIGNE;
}

void etablirModeDetailConsigne() {
  lcd.backlight();
  lcd.clear();
  consigneSonde();
  affiche("consigne:", consignes[indexConsigneEnCours].consigne, 1);
  tempoLcdConsigne = millis();
  mode = DETAIL_CONSIGNE;
}

void etablirModeVitesse() {
  lcd.backlight();
  lcd.clear();
  lcd.print("*** VITESSES ***");
  affiche(vitesses[indexVitesseEnCours].nom, vitesses[indexVitesseEnCours].vitesse, 1);
  mode = VITESSE;
}

void etablirModeNettoyageFiltre() {
  lcd.backlight();
  lcd.clear();
  mode = NETTOYAGE_FILTRE;
}

void etablirModeErreur() {
  affiche(" **  ERREUR  **", 0);
  departGainable = false;
  mode = ERREUR;
}

void majBoutons() {
  boutonMenu.poll();
  boutonValid.poll();
  boutonPlus.poll();
  boutonMoins.poll();
}

void majCapteurs() {
  thermostats.poll();
  capteurFiltre.poll();
}

// les leds clignotantes des boutons
void activeLedBoutonClignote() { // clignotement des leds des boutons a la meme cadence
  if (millis() - tempoLedBoutonClignote > 300) {
    ledMaitre = !ledMaitre;
    if (mode == ETEINT && departGainable == true)
    {
      digitalWrite(ledBoutonMenu, ledMaitre);
      digitalWrite(ledBoutonValid, ledMaitre);
      digitalWrite(ledBoutonPlus, LOW);
      digitalWrite(ledBoutonMoins, LOW);
    } else if (mode == ETEINT) {
      digitalWrite(ledBoutonMenu, ledMaitre);
      digitalWrite(ledBoutonValid, LOW);
      digitalWrite(ledBoutonPlus, LOW);
      digitalWrite(ledBoutonMoins, LOW);
    } else if (mode == CONSIGNE || mode == VITESSE || mode == ERREUR) {
      digitalWrite(ledBoutonMenu, ledMaitre);
      digitalWrite(ledBoutonValid, ledMaitre);
      digitalWrite(ledBoutonMoins, ledMaitre);
      digitalWrite(ledBoutonPlus, ledMaitre);
    } else if (mode == DETAIL_CONSIGNE) {
      digitalWrite(ledBoutonValid, ledMaitre);
      digitalWrite(ledBoutonMoins, ledMaitre);
      digitalWrite(ledBoutonPlus, ledMaitre);
      digitalWrite(ledBoutonMenu, LOW);
    } else if (mode == ACTIF) {
      digitalWrite(ledBoutonMenu, ledMaitre);
      digitalWrite(ledBoutonValid, ledMaitre);
      digitalWrite(ledBoutonPlus, LOW);
      digitalWrite(ledBoutonMoins, LOW);
    } else if (mode == AUTOMATIQUE) {
      digitalWrite(ledBoutonMenu, ledMaitre);
      digitalWrite(ledBoutonValid, LOW);
      digitalWrite(ledBoutonPlus, LOW);
      digitalWrite(ledBoutonMoins, LOW);
    }
    tempoLedBoutonClignote = millis();
  }
}

// le mode
void gestionEtat() {

  majBoutons();
  gainable();

  switch (mode) {

    case ETEINT:

      if (departGainable == true) {
        if (millis() - tempoLcdConsigne >= 15000) {
          etablirModeAutomatique();
          tempoBacklightLcd = millis();
        }
        if (boutonMenu.onPress()) {
          etablirModeAutomatique();
        } else if (boutonValid.onPress()) {
          departGainable = false;
          etablirModeEteint();
        } else {
          activeLedBoutonClignote();
        }
      } else if (boutonMenu.onPress() && !capteurFiltre.pressedFor(ms)) {
        etablirModeActif();
      } else {
        activeLedBoutonClignote();
      }
      break;

    case CONSIGNE:

      if (millis() - tempoLcdConsigne >= 15000) {
        etablirModeAutomatique();
        tempoBacklightLcd = millis();
      }
      if (boutonPlus.onPress()) {
        tempoLcdConsigne = millis();
        indexConsigneEnCours = (indexConsigneEnCours + 1) % nombreDeConsignes;
        affiche(consignes[indexConsigneEnCours].nom, consignes[indexConsigneEnCours].consigne, 1);
      } else if (boutonMoins.onPress()) {
        tempoLcdConsigne = millis();
        if (indexConsigneEnCours == 0) indexConsigneEnCours = nombreDeConsignes - 1;
        else indexConsigneEnCours--;
        affiche(consignes[indexConsigneEnCours].nom, consignes[indexConsigneEnCours].consigne, 1);
      } else if (boutonValid.onPress()) {
        etablirModeDetailConsigne();
      } else if (boutonMenu.onPress()) {
        etablirModeVitesse();
      } else {
        activeLedBoutonClignote();
      }
      break;

    case DETAIL_CONSIGNE:

      {
        if (millis() - tempoLcdConsigne >= 15000) {
          etablirModeAutomatique();
          tempoBacklightLcd = millis();
        }
        consigneSonde();
        if (boutonPlus.onPress()) {
          tempoLcdConsigne = millis();
          consignes[indexConsigneEnCours].consigne += 0.5;
          affiche("consigne:", consignes[indexConsigneEnCours].consigne, 1);
        } else if (boutonMoins.onPress()) {
          tempoLcdConsigne = millis();
          consignes[indexConsigneEnCours].consigne -= 0.5;
          affiche("consigne:", consignes[indexConsigneEnCours].consigne, 1);
        } else if (boutonValid.onPress()) {
          etablirModeConsigne();
        } else {
          activeLedBoutonClignote();
        }
      }
      break;

    case VITESSE:

      {
        if (millis() - tempoLcdConsigne >= 15000) {
          etablirModeAutomatique();
          tempoBacklightLcd = millis();
        }
        vitesseInterieur();
        if (boutonPlus.onPress()) {
          tempoLcdConsigne = millis();
          indexVitesseEnCours = (indexVitesseEnCours + 1) % nombreDeVitesses;
          affiche(vitesses[indexVitesseEnCours].nom, vitesses[indexVitesseEnCours].vitesse, 1);
        } else if (boutonMoins.onPress()) {
          tempoLcdConsigne = millis();
          if (indexVitesseEnCours == 0) indexVitesseEnCours = nombreDeVitesses - 1;
          else indexVitesseEnCours--;
          affiche(vitesses[indexVitesseEnCours].nom, vitesses[indexVitesseEnCours].vitesse, 1);
        } else if (boutonMenu.onPress()) {
          etablirModeEteint();
        } else if (boutonValid.onPress()) {
          etablirModeAutomatique();
        } else {
          tempoBacklightLcd = millis();
          activeLedBoutonClignote();
        }
      }
      break;

    case ACTIF:

      if (boutonValid.onPress()) {
        etablirModeAutomatique();
        tempoBacklightLcd = millis();
      } else if (boutonMenu.onPress()) {
        digitalWrite(ledBoutonValid, LOW);
        etablirModeEteint();
      } else {
        activeLedBoutonClignote();
      }
      break;

    case AUTOMATIQUE:

      {
        const unsigned long Auto = 10000ul;
        static unsigned long chronoAuto = -Auto;
        bool noBacklightLcd = false;

        if (entretienFiltre == true) {
          etablirModeNettoyageFiltre();
        } else if (millis() - chronoAuto >= Auto) {
          indexSondeEnCours = (indexSondeEnCours + 1) % nombreDeSondes;
          chronoAuto = millis();
        } else {
          affichageGainable();
          affiche(sondes[indexSondeEnCours].nom, sondes[indexSondeEnCours].temperature(), 1);
        }
        if (millis() - tempoBacklightLcd >= 60000) {
          digitalWrite(ledBoutonValid, LOW);
          digitalWrite(ledBoutonPlus, LOW);
          digitalWrite(ledBoutonMoins, LOW);
          digitalWrite(ledBoutonMenu, LOW);
          lcd.noBacklight();
          noBacklightLcd = true;
          if (boutonMenu.onPress()) {
            lcd.backlight();
            tempoBacklightLcd = millis();
            noBacklightLcd = false;
          }
        } else if (boutonMenu.onPress() && noBacklightLcd == false) {
          etablirModeConsigne();
        } else {
          activeLedBoutonClignote();
          erreurs();
        }
      }
      break;

    case ERREUR:

      if (boutonMenu.onPress()) {
        erreurs();
      } else if (boutonValid.onPress()) {
        etablirModeEteint();
      } else {
        activeLedBoutonClignote();
      }
      break;

    case NETTOYAGE_FILTRE:

      lcd.backlight();
      affiche("Nettoyage       ", 0);
      affiche("         Filtre", 1);

      break;
  }
}

// la machine à état gainable
enum { ARRET,
       DEPART,
       COMMANDE_FROID,
       TEMPO_V4V,
       TEMPO_COMPRESSEUR_FROID,
       TEMPO_DEGIVRAGE_FROID,
       DEGIVRAGE_FROID,
       EGOUTTAGE_FROID,
       COMMANDE_CHAUFFAGE,
       TEMPO_COMPRESSEUR_CHAUFFAGE,
       TEMPO_DEGIVRAGE,
       MODE_DEGIVRAGE,
       DEGIVRAGE_NATUREL,
       EGOUTTAGE_NATUREL,
       TEMPO_DEG_V4V,
       TEMPO_DEG_COMPRESSEUR,
       DEGIVRAGE_ELECTRIC,
       EGOUTTAGE_CHAUFFAGE,
       FIN_EGOUTTAGE_CHAUFFAGE,
       COMMANDE_CANICULE,
       TEMPO_V4V_CANICULE,
       TEMPO_COMPRESSEUR_CANICULE,
       TEMPO_DEGIVRAGE_CANICULE,
       FILTRE
     } etatsGainable;

// les variables de gainable
float hysteresis0 = 0.5;
float hysteresis3 = 3.0;

bool tempVentIntCh = false;
bool tempVentExtCh = false;
bool tempVentIntFr = false;
bool tempVentExtFr = false;

bool ventilation = false;
bool ventilationInt = false;
bool circulationAir = false;

bool caniculeLed = false;
bool forceVentDegFr = false;

// les hysteresis de consignes vitesses ventilation

void hysteresisTempVentIntCh() {
  if (tempVentIntCh) {
    tempVentIntCh = (tempUnitIntLue >= (consignes[9].consigne - hysteresis0));
  } else {
    tempVentIntCh = (tempUnitIntLue >= (consignes[9].consigne + hysteresis0));
  }
}

void hysteresisTempVentExtCh() {
  if (tempVentExtCh) {
    tempVentExtCh = (tempUnitExtLue <= (consignes[3].consigne + hysteresis0));
  } else {
    tempVentExtCh = (tempUnitExtLue <= (consignes[3].consigne - hysteresis0));
  }
}

void hysteresisTempVentIntFr() {
  if (tempVentIntFr) {
    tempVentIntFr = (tempUnitIntLue <= (consignes[8].consigne + hysteresis0));
  } else {
    tempVentIntFr = (tempUnitIntLue <= (consignes[8].consigne - hysteresis0));
  }
}

void hysteresisTempVentExtFr() {
  if (tempVentExtFr) {
    tempVentExtFr = (tempUnitExtLue >= (consignes[2].consigne - hysteresis3));
  } else {
    tempVentExtFr = (tempUnitExtLue >= consignes[2].consigne);
  }
}

// les clignotements des leds des fonctions en degivrage
void activeLedClignoteFr() {
  if (millis() - tempoLedFrClignoteDeg > 300) {
    tempoLedFrClignoteDeg = millis();
    ledFrCl = (ledFrCl == HIGH) ? LOW : HIGH;
    digitalWrite(ledFr, ledFrCl);
  }
}

void activeLedClignoteVentFr() {
  if (millis() - tempoLedFrClignoteDeg > 1000) {
    tempoLedFrClignoteDeg = millis();
    ledFrCl = (ledFrCl == HIGH) ? LOW : HIGH;
    digitalWrite(ledFr, ledFrCl);
  }
}

void activeLedClignoteCa() {
  if (millis() - tempoLedCaClignoteDeg > 300) {
    tempoLedCaClignoteDeg = millis();
    ledCaCl = (ledCaCl == HIGH) ? LOW : HIGH;
    digitalWrite(ledCa, ledCaCl);
  }
}

void activeLedClignoteVentCa() {
  if (millis() - tempoLedCaClignoteDeg > 1000) {
    tempoLedCaClignoteDeg = millis();
    ledCaCl = (ledCaCl == HIGH) ? LOW : HIGH;
    digitalWrite(ledCa, ledCaCl);
  }
}

void activeLedClignoteCh() {
  if (millis() - tempoLedChClignoteDeg > 300) {
    tempoLedChClignoteDeg = millis();
    ledChCl = (ledChCl == HIGH) ? LOW : HIGH;
    digitalWrite(ledCh, ledChCl);
  }
}

void activeLedClignoteVentCh() {
  if (millis() - tempoLedChClignoteDeg > 1000) {
    tempoLedChClignoteDeg = millis();
    ledChCl = (ledChCl == HIGH) ? LOW : HIGH;
    digitalWrite(ledCh, ledChCl);
  }
}

// les relais des ventilateurs interieur et exterieur
void activeRelaisVentIntFroid() {      // controle ventilateurs en FROID
  ventilationInt = true;
  if (tempVentIntFr) {                 // si : la temperature unite interieur est inferieur a consigne Petite vitesse interieur 23°C a l'aspiration
    digitalWrite(relaisVitesse1, HIGH);  // ventilateur interieur petite vitesse
    digitalWrite(relaisVitesse4, LOW);
  } else {                             // si non :
    digitalWrite(relaisVitesse4, HIGH);  // ventilateur interieur grande vitesse
    digitalWrite(relaisVitesse1, LOW);
  }
}

void activeRelaisVentExtFroid() {           // controle ventilateur exterieur en froid
  digitalWrite(relaisVentUniteExt, HIGH);  // relais ventilateur unité exterieur oN
  if (tempVentExtFr) {                     // si : la temperature unite exterieur est superieur a consigne grande vitesse exterieur 20°C ambiante
    digitalWrite(relaisVentExt, HIGH);     // ventilateur exterieur grande vitesse
  } else {                                 // si non :
    digitalWrite(relaisVentExt, LOW);      // ventilateur exterieur petite vitesse
  }
}

void activeRelaisVentIntCh() { // controle ventilateur interieur en chauffage
  ventilationInt = true;
  if (tempVentIntCh) {                 // si : la temperature unite interieur est superieur a consigne petite vitesse 20°C a l'aspiration
    digitalWrite(relaisVitesse1, HIGH);  // petite vitesse
    digitalWrite(relaisVitesse4, LOW);
  } else {                             // si non :
    digitalWrite(relaisVitesse4, HIGH);  // grande vitesse
    digitalWrite(relaisVitesse1, LOW);
  }
}

void activeRelaisVentExtCh() {             // controle ventilateur exterieur en chauffage
  digitalWrite(relaisVentUniteExt, HIGH);  // relais ventilateur de l'unité exterieur oN
  if (tempVentExtCh) {                     // si : la temperature unite exterieur est inferieur a 5°C ambiante
    digitalWrite(relaisVentExt, HIGH);     // ventilateur exterieur grande vitesse
  } else {                                 // si non :
    digitalWrite(relaisVentExt, LOW);      // ventilateur exterieur petite vitesse
  }
}

void activeRelaisVents() {  // controle ventilateurs Interieur Canicule
  ventilationInt = true;
  digitalWrite(relaisVitesse4, HIGH);  // relais ventilateur interieur grande vitesse
  digitalWrite(relaisVentUniteExt, HIGH); // relais ventilateur unite exterieur oN (coupure au neutre en serie avec pressostat Hp2)
  digitalWrite(relaisVentExt, HIGH); // relais ventilateur exterieur Grande vitesse
}

void activeRelaisVentExt() { // controle ventilateur exterieur en degivrage chauffage
  digitalWrite(relaisVentUniteExt, HIGH); // relais ventilateur unite exterieur oN (coupure au neutre en serie avec pressostat Hp2)
  digitalWrite(relaisVentExt, HIGH); // relais ventilateur exterieur Grande vitesse
}

// l'arret des relais
void desactiveTousRelais() {
  ventilationInt = false;
  digitalWrite(relaisVentUniteExt, LOW);
  digitalWrite(relaisVitesse1, LOW);
  digitalWrite(relaisVitesse2, LOW);
  digitalWrite(relaisVitesse3, LOW);
  digitalWrite(relaisVitesse4, LOW);
  digitalWrite(relaisVentExt, LOW);
  digitalWrite(relaisComp, LOW);
  digitalWrite(relaisV4V, LOW);
}

void desactiveRelaisVentInt() {
  ventilationInt = false;
  digitalWrite(relaisVitesse1, LOW);
  digitalWrite(relaisVitesse2, LOW);
  digitalWrite(relaisVitesse3, LOW);
  digitalWrite(relaisVitesse4, LOW);
}

// les affichages des etats du gainable
void affichageGainable() {
  if (etatsGainable == COMMANDE_FROID) {
    if (circulationAir == true) {
      affiche("Ventilation ", 0);
      lcd.setCursor(13, 0);
      lcd.print(char(6));
    } else {
      affiche("Froid oFF       ", 0);
      lcd.setCursor(15, 0);
      lcd.print(" ");
    }
  } else if (etatsGainable == TEMPO_V4V || etatsGainable == TEMPO_COMPRESSEUR_FROID || etatsGainable == TEMPO_DEGIVRAGE_FROID) {
    if (forceVentDegFr == true) {
      lcd.setCursor(14, 0);
      lcd.print("4");
    } else if (ventilationInt == true) {
      lcd.setCursor(13, 0);
      lcd.print(char(6));
      if (modifVitesseInt == true) {
        lcd.setCursor(14, 0);
        lcd.print(vitesseTravail);
      } else {
        if (digitalRead(relaisVitesse4)) {
          lcd.setCursor(14, 0);
          lcd.print("4");
        } else {
          lcd.setCursor(14, 0);
          lcd.print("1");
        }
      }
    } else {
      affiche("Froid           ", 0);
    }
    if (digitalRead(relaisComp)) {
      lcd.setCursor(15, 0);
      lcd.print(char(0));
    }
  } else if (etatsGainable == DEGIVRAGE_FROID) {
    affiche("Degivrage       ", 0);
    lcd.setCursor(15, 0);
    lcd.print(char(2));
  } else if (etatsGainable == EGOUTTAGE_FROID) {
    affiche("Egouttage       ", 0);
    lcd.setCursor(15, 0);
    lcd.print(char(3));
  } else if (etatsGainable == COMMANDE_CHAUFFAGE) {
    if (circulationAir == true) {
      affiche("Ventilation  ", 0);
      lcd.setCursor(13, 0);
      lcd.print(char(6));
    } else {
      affiche("Chauffage oFF   ", 0);
      lcd.setCursor(13, 0);
      lcd.print("    ");
    }
  } else if (etatsGainable == TEMPO_COMPRESSEUR_CHAUFFAGE || etatsGainable == TEMPO_DEGIVRAGE || etatsGainable == MODE_DEGIVRAGE) {
    if (digitalRead(relaisComp)) {
      lcd.setCursor(15, 0);
      lcd.print(char(1));
      if (ventilationInt == true) {
        lcd.setCursor(13, 0);
        lcd.print(char(6));
        if (modifVitesseInt == true) {
          lcd.setCursor(14, 0);
          lcd.print(vitesseTravail);
        } else {
          if (digitalRead(relaisVitesse4)) {
            lcd.setCursor(14, 0);
            lcd.print("4");
          } else {
            lcd.setCursor(14, 0);
            lcd.print("1");
          }
        }
      }
    } else {
      affiche("Chauffage       ", 0);
    }
  } else if (etatsGainable == DEGIVRAGE_NATUREL) {
    affiche("Degivrage      ", 0);
    lcd.setCursor(15, 0);
    lcd.print(char(3));
  } else if (etatsGainable == EGOUTTAGE_NATUREL) {
    affiche("Egouttage      ", 0);
    lcd.setCursor(15, 0);
    lcd.print(char(4));
  } else if (etatsGainable == TEMPO_DEG_V4V || etatsGainable == TEMPO_DEG_COMPRESSEUR || etatsGainable == DEGIVRAGE_ELECTRIC) {
    affiche("Degivrage     ", 0);
    lcd.setCursor(14, 0);
    lcd.print(char(4));
    lcd.setCursor(15, 0);
    lcd.print(char(3));
  } else if (etatsGainable == EGOUTTAGE_CHAUFFAGE) {
    affiche("Egouttage    ", 0);
    lcd.setCursor(14, 0);
    lcd.print(char(4));
    lcd.setCursor(15, 0);
    lcd.print(char(3));
  } else if (etatsGainable == FIN_EGOUTTAGE_CHAUFFAGE) {
    affiche("Fin Egouttage ", 0);
    lcd.setCursor(14, 0);
    lcd.print(char(4));
    lcd.setCursor(15, 0);
    lcd.print(char(3));
  } else if (etatsGainable == COMMANDE_CANICULE) {
    if (circulationAir == true) {
      affiche("Ventilation ", 0);
      lcd.setCursor(13, 0);
      lcd.print(char(6));
    } else {
      affiche("Canicule oFF", 0);
      lcd.setCursor(12, 0);
      lcd.print(char(5));
      lcd.setCursor(13, 0);
      lcd.print("   ");
    }
  } else if (etatsGainable == TEMPO_V4V_CANICULE || etatsGainable == TEMPO_COMPRESSEUR_CANICULE || etatsGainable == TEMPO_DEGIVRAGE_CANICULE) {
    affiche("Canicule    ", 0);
    lcd.setCursor(12, 0);
    lcd.print(char(5));
    lcd.setCursor(13, 0);
    lcd.print(char(6));
    if (ventilationInt == true) {
      lcd.setCursor(14, 0);
      lcd.print("4");
    }
    if (digitalRead(relaisComp)) {
      lcd.setCursor(15, 0);
      lcd.print(char(0));
    }
  }
}

// les etats du gainable possible pour modifier les vitesses interieur
void vitesseGainable() { // les etats Gainable pour modifier vitesse ventilateur interieur
  if (etatsGainable == TEMPO_V4V || etatsGainable == TEMPO_COMPRESSEUR_FROID || etatsGainable == TEMPO_DEGIVRAGE_FROID || etatsGainable == TEMPO_DEGIVRAGE || etatsGainable == MODE_DEGIVRAGE) {
    etablirModeVitesse();
  } else {
    etablirModeEteint();
  }
}

void autoMode() {
  if (millis() - tempoAutoMode >= 10000) {
    etatsGainable = DEPART;
  }
}

// le gainable
void gainable() {

  majCapteurs();

  if (departGainable == false) {
    etatsGainable = ARRET;
  }

  switch (etatsGainable) {

    case ARRET:

      if (departGainable == true) {
        tempoAutoMode = millis();
        etatsGainable = DEPART;
      } else {
        desactiveTousRelais();
        digitalWrite(relaisVolets, LOW);
        digitalWrite(ledCa, LOW);
        digitalWrite(ledFr, LOW);
        digitalWrite(ledCh, LOW);
      }
      break;

    case DEPART:  // controle automatique de la temperature pour selectionner le mode

      if (nettoyageFiltre >= 4250000) {  // 49 jours de fonctionnement du compresseur 4250000000ms
        entretienFiltre = true;
        etatsGainable = FILTRE;
      } else if (tempExtLue < consignes[0].consigne) {  // si la temperature exterieur est inferieur as la consigne (12.5°C)
        tempoAutoMode = millis();
        tempoCh = millis();
        tempoVentilation = millis();
        digitalWrite(relaisVolets, HIGH); // relai volets en mode chauffage
        etatsGainable = COMMANDE_CHAUFFAGE;
      } else if (tempExtLue < consignes[1].consigne) {  // si non si la temperature exterieur est inferieur a la consigne canicule (30°C)
        tempoAutoMode = millis();
        tempoFr = millis();
        tempoVentilation = millis();
        digitalWrite(relaisVolets, LOW); // relai volets en mode froid
        etatsGainable = COMMANDE_FROID;
      } else {  // si non
        tempoAutoMode = millis();
        tempoCa = millis();
        tempoVentilation = millis();
        etatsGainable = COMMANDE_CANICULE;  // j'active FROID CANICULE
      }
      break;

    case COMMANDE_FROID:

      if (thermostats.pressedFor(ms)) { // si un des thermosats est enclencher
        if (millis() - tempoVentilation >= 5000) { // tempo de 5 secondes
          if (millis() - tempoFr >= 18000) { // tempo de 3 minutes
            if (tempUnitIntLue > consignes[10].consigne) { // si la temperature unité interieur est superieur as 24°C
              tempoV4VFr = millis(); // enregistre le temps pour V4V
              circulationAir = false; // circulation de l'air a faux
              etatsGainable = TEMPO_V4V; // passe dans l'etats tempoV4V pour la production de froid
            } else if (tempUnitIntLue < consignes[11].consigne) { // si non si la temperature interieur est inferieur as 22°C
              tempoVentilation = millis(); // enregistre le temp pour ventilation
              digitalWrite(ledFr, LOW); // eteint led froid
              digitalWrite(relaisVolets, HIGH); // relai volets en mode chauffage
              etatsGainable = COMMANDE_CHAUFFAGE; // passe dans l'etats commande chauffage
            } else { // si non
              tempoFr = millis(); // enregistre le temps de tempoFr
            }
          } else { // si non
            activeLedClignoteVentFr(); // active le clignotement de 1 seconde de la led froid
            digitalWrite(relaisVitesse4, HIGH); // ventilateur grande vitesse
          }
        } else { // si non
          activeLedClignoteVentFr(); // active le clignotement de 1 seconde de la led froid
          circulationAir = true; // circulation de l'air vrai
          tempoFr = millis(); // enrgistre le temps pour tempofr
        }
      } else { // si non
        circulationAir = false; // circulation de l'air faux
        digitalWrite(relaisVolets, LOW); // inversion des contacts NC/NO
        digitalWrite(ledFr, LOW); // led froid off
        digitalWrite(ledCh, LOW); // led chauffage off
        digitalWrite(ledCa, LOW); // led canicule off
        desactiveTousRelais(); // desactive tous les relais
        tempoVentilation = millis(); // enregistre temps pour tempoventilation
        autoMode(); // controle temperature exterieur (nord)
      }
      break;

    case TEMPO_V4V:

      if (!thermostats.pressedFor(ms)) {
        etatsGainable = DEPART;
      } else if (millis() - tempoV4VFr >= 6000) {  // temporisation de 1 minute
        tempoCompFr = millis();
        digitalWrite(relaisV4V, HIGH);
        etatsGainable = TEMPO_COMPRESSEUR_FROID;
      } else {
        activeRelaisVentExtFroid();
        digitalWrite(ledFr, HIGH);
        if (modifVitesseInt == false) {
          activeRelaisVentIntFroid();
        }  else {
          vitesseInterieur();
        }
      }
      break;

    case TEMPO_COMPRESSEUR_FROID:

      if (!thermostats.pressedFor(ms)) {
        etatsGainable = DEPART;
      } else if (millis() - tempoCompFr >= 18000) {  // temporisation de 3 minutes avant demarrage compresseur 180000ul
        departChronoFiltre = millis();
        tempoDegFr = millis();
        digitalWrite(relaisComp, HIGH);
        etatsGainable = TEMPO_DEGIVRAGE_FROID;
      } else {
        activeRelaisVentExtFroid();
        if (modifVitesseInt == false) {
          activeRelaisVentIntFroid();
        }  else {
          vitesseInterieur();
        }
      }
      break;

    case TEMPO_DEGIVRAGE_FROID:

      if (!thermostats.pressedFor(ms)) {
        finChronoFiltre = millis();
        nettoyageFiltre = (finChronoFiltre - departChronoFiltre) + nettoyageFiltre;
        etatsGainable = DEPART;
      } else if (millis() - tempoDegFr >= 60000) { // 20 minutes
        if (tempEchangeurIntLue < consignes[13].consigne) {
          forceVentDegFr = true;
          digitalWrite(relaisVitesse4, HIGH);
          digitalWrite(relaisVitesse1, LOW);
          digitalWrite(relaisVitesse2, LOW);
          digitalWrite(relaisVitesse3, LOW);
          if (millis() - tempoTempDegFr >= 30000) { // 1 minute
            if (tempEchangeurIntLue < consignes[13].consigne) {
              finChronoFiltre = millis();
              nettoyageFiltre = (finChronoFiltre - departChronoFiltre) + nettoyageFiltre;
              vitesseTravail = vitesses[0].vitesse;
              forceVentDegFr = false;
              etatsGainable = DEGIVRAGE_FROID;
            } else {
              tempoTempDegFr = millis();
            }
          }
        } else {
          forceVentDegFr = false;
          activeRelaisVentExtFroid();
          if (modifVitesseInt == false) {
            activeRelaisVentIntFroid();
          } else {
            vitesseInterieur();
          }
        }
      } else {
        tempoTempDegFr = millis();
        activeRelaisVentExtFroid();
        if (modifVitesseInt == false) {
          activeRelaisVentIntFroid();
        } else {
          vitesseInterieur();
        }
      }
      break;

    case DEGIVRAGE_FROID:

      if (tempEchangeurIntLue >= consignes[14].consigne) {
        tempoEgouttageFr = millis();
        etatsGainable = EGOUTTAGE_FROID;
      } else {
        desactiveTousRelais();
        if (caniculeLed == true) {
          activeLedClignoteCa();
        } else {
          activeLedClignoteFr();
        }
      }
      break;

    case EGOUTTAGE_FROID:  // etat EGOUTTAGE_FR

      if (millis() - tempoEgouttageFr >= 120000) {  // 120000ms = 2 minutes
        desactiveTousRelais();
        digitalWrite(ledFr, LOW);
        caniculeLed = false;
        etatsGainable = DEPART;
      } else {
        digitalWrite(relaisVitesse4, HIGH);
        if (caniculeLed == true) {
          activeLedClignoteCa();
        } else {
          activeLedClignoteFr();
        }
      }
      break;

    case COMMANDE_CHAUFFAGE:

      if (thermostats.pressedFor(ms)) {
        if (millis() - tempoVentilation >= 5000) {
          if (millis() - tempoCh >= 18000) {
            if (tempUnitIntLue < consignes[11].consigne) {
              if (tempUnitExtLue < consignes[5].consigne) {
                circulationAir = false;
                ventilation = false;
                tempoCompCh = millis();
                desactiveRelaisVentInt();
                etatsGainable = TEMPO_COMPRESSEUR_CHAUFFAGE;
              } else {
                tempoCh = millis();
              }
            } else if (tempUnitIntLue > consignes[10].consigne) {
              digitalWrite(ledCh, LOW);
              tempoVentilation = millis();
              digitalWrite(relaisVolets, LOW); // relai volets en mode Froid
              etatsGainable = COMMANDE_FROID;
            } else {
              tempoCh = millis();
            }
          } else {
            digitalWrite(relaisVitesse4, HIGH);
          }
        } else {
          activeLedClignoteVentCh();
          circulationAir = true;
          tempoCh = millis();
        }
      } else {
        circulationAir = false;
        digitalWrite(relaisVolets, HIGH);
        digitalWrite(ledCh, LOW);
        digitalWrite(ledFr, LOW);
        desactiveTousRelais();
        tempoVentilation = millis();
        autoMode();
      }
      break;

    case TEMPO_COMPRESSEUR_CHAUFFAGE:

      if (!thermostats.pressedFor(ms)) {
        etatsGainable = DEPART;
      } else if (millis() - tempoCompCh >= 18000) {  // temporisation de 3 minutes avant le demarage du compresseur 180000ul
        departChronoFiltre = millis();
        tempoDegCh = millis();
        etatsGainable = TEMPO_DEGIVRAGE;
      } else {
        activeRelaisVentExtCh();
        digitalWrite(ledCh, HIGH);
      }
      break;

    case TEMPO_DEGIVRAGE:

      if (!thermostats.pressedFor(ms)) {
        finChronoFiltre = millis();
        nettoyageFiltre = (finChronoFiltre - departChronoFiltre) + nettoyageFiltre;
        desactiveTousRelais();
        etatsGainable = DEPART;
      } else if (millis() - tempoDegCh >= 240000) {  // si : la temporisation de 40 minutes (si compresseur a fonctionner pendant 40 minutes) 2400000ul
        finChronoFiltre = millis();
        nettoyageFiltre = (finChronoFiltre - departChronoFiltre) + nettoyageFiltre;
        etatsGainable = MODE_DEGIVRAGE;
      } else {
        digitalWrite(relaisComp, HIGH);
        activeRelaisVentExtCh();
        if (tempEchangeurIntLue >= consignes[12].consigne) {  //
          if (modifVitesseInt == false) {
            activeRelaisVentIntCh();
          } else {
            vitesseInterieur();
          }
        }
      }
      break;

    case MODE_DEGIVRAGE:

      if (tempUnitExtLue >= consignes[4].consigne) {  // si : la temperature de l'unité exterieur est supperieur ou egal a 5°C ( lancement degivrage naturel )
        tempoDegNat = millis();
        etatsGainable = DEGIVRAGE_NATUREL;                        // passe a l'etat DEGIVRAGE_NATUREL
      } else if (tempEchangeurExtLue <= consignes[6].consigne) {  // si : la temperature de l'Echangeur exterieur est inferieur ou egal a -3°C ( degivrage gaz chaud inversion de cycle )
        tempoV4VCh = millis();
        etatsGainable = TEMPO_DEG_V4V;
      } else {
        activeRelaisVentExtCh();
        if (modifVitesseInt == false) {
          activeRelaisVentIntCh();
        } else {
          vitesseInterieur();
        }
      }
      break;

    case DEGIVRAGE_NATUREL:  // etat DEGIVRAGE_NATUREL

      if (millis() - tempoDegNat >= 600000) {  // 600000ms = 10 minutes
        tempoEgouttageNat = millis();
        etatsGainable = EGOUTTAGE_NATUREL;  // passe a l'etat EGOUTTAGE_NATUREL
      } else {
        desactiveTousRelais();
        activeLedClignoteCh();
      }
      break;

    case EGOUTTAGE_NATUREL:  // etat EGOUTTAGE_NATUREL

      if (millis() - tempoEgouttageNat >= 300000) {  // 300000ms
        digitalWrite(ledCh, LOW);
        desactiveTousRelais();
        etatsGainable = DEPART;  // passe a l'etat
      } else {
        vitesseTravail = vitesses[0].vitesse;
        activeRelaisVentExt();
        activeLedClignoteCh();
      }
      break;

    case TEMPO_DEG_V4V:  // etat DEGIVRAGE_ELECTRIC

      if (millis() - tempoV4VCh >= 78000) {  // 78000ms 1 minute 30 secondes
        tempoCompCh = millis();
        etatsGainable = TEMPO_DEG_COMPRESSEUR;
      } else {
        desactiveTousRelais();
        activeLedClignoteCh();
      }
      break;

    case TEMPO_DEG_COMPRESSEUR:

      if (millis() - tempoCompCh >= 120000) {  // 120000ms 2 minutes
        tempoTempDegEle = millis();
        etatsGainable = DEGIVRAGE_ELECTRIC;
      } else {
        digitalWrite(relaisV4V, HIGH);  // relais vanne 4 voies oN
        activeLedClignoteCh();
      }
      break;

    case DEGIVRAGE_ELECTRIC:

      if (tempEchangeurExtLue >= consignes[7].consigne || (millis() - tempoTempDegEle >= 600000ul)) {  // si la temperature est superieur ou egal a 12.5°C ( fin de degivrage )
        tempoEgouttageEle = millis();
        etatsGainable = EGOUTTAGE_CHAUFFAGE;  // passe a l'etat EGOUTTAGE_ELECTRIC
      } else {
        digitalWrite(relaisComp, HIGH);  // relais compresseur oN
        activeLedClignoteCh();
      }
      break;

    case EGOUTTAGE_CHAUFFAGE:  // etat EGOUTTAGE_ELECTRIC

      if (millis() - tempoEgouttageEle >= 300000) {  // 300000ms = 5 minutes
        tempoFinEgouttageEle = millis();
        etatsGainable = FIN_EGOUTTAGE_CHAUFFAGE;  // passe a l'etat FIN_EGOUTTAGE_ELE
      } else {
        desactiveTousRelais();
        activeLedClignoteCh();
      }
      break;

    case FIN_EGOUTTAGE_CHAUFFAGE:  // etat FIN_EGOUTTAGE_ELE

      if (millis() - tempoFinEgouttageEle >= 150000) {  // 150000ms = 2.5 minutes
        desactiveTousRelais();
        digitalWrite(ledCh, LOW);
        etatsGainable = DEPART;  // passe a l'etat
      } else {
        vitesseTravail = vitesses[0].vitesse;
        activeRelaisVentExt();
        activeLedClignoteCh();
      }
      break;

    case COMMANDE_CANICULE:  // etat FROID_CANICULE

      if (thermostats.pressedFor(ms)) {
        if (millis() - tempoVentilation >= 5000) {
          if (millis() - tempoCa >= 18000) {
            if (tempUnitIntLue > consigneIntCa) {
              circulationAir = false;
              tempoV4VFr = millis();
              etatsGainable = TEMPO_V4V_CANICULE;
            } else {
              tempoCa = millis();
            }
          } else {
            digitalWrite(relaisVitesse4, HIGH);
          }
        } else {
          activeLedClignoteVentCa();
          circulationAir = true;
        }
      } else {
        circulationAir = false;
        digitalWrite(ledCa, LOW);
        digitalWrite(ledFr, LOW);
        desactiveTousRelais();
        tempoVentilation = millis();
        autoMode();
      }
      break;

    case TEMPO_V4V_CANICULE:

      if (!thermostats.pressedFor(ms)) {
        etatsGainable = DEPART;
      } else if (millis() - tempoV4VFr >= 6000) {  // temporisation de 1 minute
        tempoCompFr = millis();
        digitalWrite(relaisV4V, HIGH);
        etatsGainable = TEMPO_COMPRESSEUR_CANICULE;
      } else {
        activeRelaisVents();
        digitalWrite(ledCa, HIGH);
      }
      break;

    case TEMPO_COMPRESSEUR_CANICULE:

      if (!thermostats.pressedFor(ms)) {
        etatsGainable = DEPART;
      } else if (millis() - tempoCompFr >= 18000) {  // temporisation de 3 minutes avant demarrage compresseur
        digitalWrite(relaisComp, HIGH);
        departChronoFiltre = millis();
        etatsGainable = TEMPO_DEGIVRAGE_CANICULE;
      } else {
        activeRelaisVents();
      }
      break;

    case TEMPO_DEGIVRAGE_CANICULE:

      if (!thermostats.pressedFor(ms)) {
        finChronoFiltre = millis();
        nettoyageFiltre = (finChronoFiltre - departChronoFiltre) + nettoyageFiltre;
        etatsGainable = DEPART;
      } else if (millis() - tempoDegFr >= 60000) { // 20 minutes
        if (tempEchangeurIntLue < consignes[13].consigne) {
          if (millis() - tempoTempDegFr >= 30000) { // 1 minute
            if (tempEchangeurIntLue < consignes[13].consigne) {
              finChronoFiltre = millis();
              nettoyageFiltre = (finChronoFiltre - departChronoFiltre) + nettoyageFiltre;
              caniculeLed = true;
              etatsGainable = DEGIVRAGE_FROID;
            } else {
              tempoTempDegFr = millis();
            }
          }
        } else {
          tempoTempDegFr = millis();
        }
      } else {
        tempoTempDegFr = millis();
        activeRelaisVents();
      }
      break;

    case FILTRE:

      if (millis() - tempoLedArretCompletProgramClignote >= 500) {
        tempoLedArretCompletProgramClignote = millis();
        ledChFrCaCl = (ledChFrCaCl == HIGH) ? LOW : HIGH;
        digitalWrite(ledFr, ledChFrCaCl);
        digitalWrite(ledCa, ledChFrCaCl);
        digitalWrite(ledCh, ledChFrCaCl);
        if (capteurFiltre.pressedFor(ms)) {
          entretienFiltre = false;
          nettoyageFiltre = 0;
          etablirModeEteint();
        }
      }
      break;
  }
}

// la fonction calculs
void autresCalculs() {
  hysteresisTempVentIntCh();
  hysteresisTempVentExtCh();
  hysteresisTempVentIntFr();
  hysteresisTempVentExtFr();
  consigneIntCa = tempExtLue - consignes[15].consigne;  // consigne interieur canicule est egale a la temperature exterieur (NORD) - temperature delta (-6)
}

// l'affichage Serial
void imprimeTout() {
  const unsigned long deltaT = 2500;    // en ms, toutes les 2,5 secondes
  static unsigned long chrono = -deltaT;  // valeur initiale pour affichage au premier appel

  if (millis() - chrono >= deltaT) {

    const char* nomDesEtats[] = { "ARRET", "DEPART", "COMMANDE_FROID", "TEMPO_V4V", "TEMPO_COMPRESSEUR_FROID", "TEMPO_DEGIVRAGE_FROID", "DEGIVRAGE_FROID", "EGOUTTAGE_FROID", "COMMANDE_CHAUFFAGE", "TEMPO_COMPRESSEUR_CHAUFFAGE", "TEMPO_DEGIVRAGE", "MODE_DEGIVRAGE", "DEGIVRAGE_NATUREL", "EGOUTTAGE_NATUREL", "TEMPO_DEG_V4V", "TEMPO_DEG_COMPRESSEUR", "DEGIVRAGE_ELECTRIC", "EGOUTTAGE_CHAUFFAGE", "FIN_EGOUTTAGE_CHAUFFAGE", "COMMANDE_CANICULE", "TEMPO_V4V_CANICULE", "TEMPO_COMPRESSEUR_CANICULE", "TEMPO_DEGIVRAGE_CANICULE", "FILTRE" };
    Serial.print(F("ETAT: "));
    Serial.println(nomDesEtats[etatsGainable]);

    const char* nomDesEtatsMode[] = { "ETEINT", "CONSIGNE", "DETAIL_CONSIGNE", "VITESSE", "ACTIF", "AUTOMATIQUE", "ERREUR", "NETTOYAGE_FILTRE" };
    Serial.print(F("MODE: "));
    Serial.println(nomDesEtatsMode[mode]);

    Serial.print(F("Nettoyage filtre = "));
    Serial.println(nettoyageFiltre);

    chrono = millis();
  }
}

// le setup
void setup() {
  pinMode(relaisComp, OUTPUT);            // Relais Compresseur
  pinMode(relaisV4V, OUTPUT);             // Relais Vanne 4 Voies
  pinMode(relaisVentUniteExt, OUTPUT);    // Relais Ventilateur Unité Exterieur oN/oFF ( coupur au neutre )
  pinMode(relaisVentExt, OUTPUT);         // Relais Ventilateur Exterieur grande et petite vitesse
  pinMode(relaisVitesse1, OUTPUT);    // Relais Ventilateur unité interieur oN/oFF  ( coupure au neutre )
  pinMode(relaisVitesse2, OUTPUT);         // Relais Ventilateur Interieur grande et petite vitesse
  pinMode(relaisVitesse3, OUTPUT);       // Relais ventilateur Interieur petite vitesse
  pinMode(relaisVitesse4, OUTPUT);       // Relais ventilateur Interieur Grande Vitesse
  pinMode(relaisVolets, OUTPUT);     // Relais Automatique
  pinMode(ledCa, OUTPUT);                 // LED fonction canicule
  pinMode(ledFr, OUTPUT);                 // LED fonction Froid
  pinMode(ledCh, OUTPUT);                 // LED fonction Chauffage
  pinMode(ledBoutonMenu, OUTPUT);         // LED Bouton Menu
  pinMode(ledBoutonValid, OUTPUT);        // LED Bouton Valid
  pinMode(ledBoutonPlus, OUTPUT);         // LED Bouton Plus
  pinMode(ledBoutonMoins, OUTPUT);        // LED Bouton Moins
  thermostats.begin(thermostatsPin);      // Contact NC/NO Thermostat Radio Chambre 1
  capteurFiltre.begin(capteurFiltrePin);  // Capteur Presence Filtre
  boutonMenu.begin(menuPin);              // Bouton Menu
  boutonValid.begin(validPin);            // Bouton Valid
  boutonPlus.begin(boutonPlusPin);        // Bouton Plus
  boutonMoins.begin(boutonMoinsPin);      // Bouton Moins

  for (Sonde& s : sondes) s.begin();

  for (size_t indice = 0; indice < nombreDeConsignes; indice++);

  Serial.begin(115200);

  int result = lcd.begin(nbColonnes, nbLignes);
  if (result) {
    Serial.print("LCD initialization failed: ");
    Serial.println(result);
    hd44780::fatalError(result);
  }
  lcd.createChar(0, flecheFr);
  lcd.createChar(1, flecheCh);
  lcd.createChar(2, degivrage);
  lcd.createChar(3, goutte);
  lcd.createChar(4, eclaire);
  lcd.createChar(5, soleil);
  lcd.createChar(6, ventilateur);
  lcd.clear();

  etablirModeEteint();
}

// la loop
void loop() {
  gestionEtat();
  autresCalculs();
  imprimeTout();
}
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module