#include <LiquidCrystal_I2C.h>      // Bibliothèque pour l'écran LCD fonctionnant avec I2C.

LiquidCrystal_I2C lcd(0x27, 20, 4); // Définitions des paramètres de lcd 2004 à l'adresse 0x27.
LiquidCrystal_I2C lcd1(0x26, 20, 4); // Définitions des paramètres de lcd1 2004 à l'adresse 0x26.

unsigned long currentTime = 0;    // Variable utilisée pour récupérer la valeur de millis()
unsigned long previousTime = 0;   // Variable utilisée pour stocker la valeur de millis() pour le prochain "loop"

// valeurs du temps de départ
long TempsEcoule, Milliseconde, heure, minute, seconde ;

const int buzzer = 13;    // Buzzer relié à la broche 13
// DÉCLARATION DES BOUTONS POUSSOIRS
const int btn_StartStop = 2; bool etat_btn_StartStop, etat_BoutonAppuyer, etat_ChronoOnOFF;
const int btn_Reset = 3;     bool etat_btn_Reset;
const int btn_Mute = 4;      bool etat_btn_Mute, etat_Mute;
const int btn_TimeOn = 5;    bool etat_btn_TimeOn;
const int btn_TimeOff = 6;   bool etat_btn_TimeOff;
const int btn_Set = 7;       bool etat_btn_Set;
const int SSR_relais = 12;   bool etat_SSRrelais = LOW;
bool  FlagON = false;
char Chrono[20], TimeOn[20], TimeOff[20],MinuteurON[20], MinuteurOFF[20];

long onTime, offTime;
long MyTimeON, H_ON = 0, M_ON = 0, S_ON = 0, MS_ON = 0;
long MyTimeOFF, H_OFF = 0, M_OFF = 0, S_OFF = 0, MS_OFF = 0;
long minutes_OFF = 0, minutes_ON = 0;

// Tableaux de type String
String Start_Stop[] = {"START | RESET |", "STOP  | RESET |" };
String Mute[] = {"OFF", "ON "};
String Mark[11] = {"                    ",
                   "**",
                   "****",
                   "******",
                   "********",
                   "**********",
                   "************",
                   "**************",
                   "****************",
                   "******************",
                   "********************"
                  };
int index = 0;
int Record, Start;
bool etatRecord = false;

void setup(void) { // Initialisation
  lcd.init(); lcd.backlight();  // Initialisation de l'écran LCD, et active le rétroéclairage de l'écran LCD
  lcd1.init(); lcd1.backlight();  // Initialisation de l'écran LCD1, et active le rétroéclairage de l'écran LCD1

  lcdPrintCenter("Chronometre", 0);        // écran de démarrage
  lcd1PrintCenter("Minuteur Cyclique", 0);        // écran de démarrage
  lcd1.setCursor(0, 3); lcd1.print("Time ON | OFF | Set");      // écran de démarrage

  Fn_Display();
  pinMode(btn_StartStop, INPUT_PULLUP); // Assigne le mode ENTREE et résistance PULL UP
  pinMode(btn_Reset, INPUT_PULLUP);     // Assigne le mode ENTREE et résistance PULL UP
  pinMode(btn_Mute, INPUT_PULLUP);
  pinMode(btn_TimeOn, INPUT_PULLUP);
  pinMode(btn_TimeOff, INPUT_PULLUP);
  pinMode(btn_Set, INPUT_PULLUP);
  pinMode(buzzer, OUTPUT);     // Assigne le mode SORTIE à la broche 13.
  pinMode(SSR_relais, OUTPUT); // Assigne le mode SORTIE à la broche 12
  lcd.setCursor(0, 3); lcd.print(Start_Stop[0]);
  lcd.setCursor(17, 3); lcd.print(Mute[0]);
}

void loop(void) {
  Fn_Scan_Buttons(); // Fonction de balayage de l'état de tous les boutons poussoirs
  ///Fn_boutons();
  Fn_Chronometre();  // Fonction de gestion du temps qui passe lorsque le chrono est à ON
} // fin de loop()

// fonction perso beep pour Émettre une tonalité de x kHz pendant x ms
void beep() {
  if (!etat_Mute) {
    tone(buzzer, 2500, 100);  // Émet une tonalité de 2,5kHz pendant 100 ms
  }
}
void tick() {  // fonction perso pour Émettre une très brèvetonalité de 5kHz pendant 1 ms
  if (!etat_Mute) {
    tone(buzzer, 5000, 4);   // Émet un son de 5 kHz pendant 1 ms
  }
}
void Fn_Scan_Buttons() { //balayage de l'état de tous les boutons poussoirs
  etat_btn_StartStop = digitalRead(btn_StartStop); // État Bouton Start/Stop
  etat_btn_Reset = digitalRead(btn_Reset);     // État Bouton Start/Stop
  etat_btn_Mute = digitalRead(btn_Mute);
  etat_btn_TimeOn = digitalRead(btn_TimeOn);
  etat_btn_TimeOff = digitalRead(btn_TimeOff);
  etat_btn_Set = digitalRead(btn_Set);

  if (etat_btn_StartStop == LOW && etat_BoutonAppuyer == 0) { // Test pression bouton START/STOP
    etat_BoutonAppuyer = 1;  beep(); delay(500);   // Evite l'effet rebond
    etat_ChronoOnOFF = !etat_ChronoOnOFF;                     // Inverse l'état du chronomètre
    lcd.setCursor(0, 3); lcd.print(Start_Stop[0]);
    while (etat_btn_StartStop)etat_btn_StartStop = digitalRead(btn_StartStop); // fonction anti rebond
  }
  if (etat_btn_Reset == LOW && etat_ChronoOnOFF == 0 && etat_BoutonAppuyer == 0) { // Test pression bouton RESET
    etat_BoutonAppuyer = 1;  beep(); delay(500);   // Evite l'effet rebond
    seconde = 0; minute = 0; heure = 0; Milliseconde = 0;  // Remet toutes les variables de temps à 0.
    index = 0;
    while (etat_btn_Reset)etat_btn_Reset = digitalRead(btn_Reset); // fonction anti rebond
    Fn_Display();   // Mise à zéro du contenu de l'écran LCD
  }
  if (etat_btn_Mute == LOW ) {
    tone(buzzer, 2500, 100); delay(500);
    etat_Mute = !etat_Mute;  // Inverse l'état Mute (ON/OFF)
    lcd.setCursor(17, 3); lcd.print(Mute[etat_Mute]);
    while (etat_btn_Mute)etat_btn_Mute = digitalRead(btn_Mute); // fonction anti rebond
  }
  if (etat_btn_TimeOn == LOW) { // Réglage du temps ON
    minutes_ON = minutes_ON + 1; // Incrémente de 1 à chaque pression du bouton bleu.
    if (minutes_ON > 59) { // si dépassement de 59, remet à 0.
      minutes_ON = 0;
    }
    S_ON = minutes_ON; Fn_Display();
    delay(10); // Délais de 10 ms entre chaque pression du bouton
    // Empêche les rebonds des boutons poussoirs
    while (etat_btn_TimeOn) etat_btn_TimeOn = digitalRead(btn_TimeOn);
  }
  if (etat_btn_TimeOff == LOW) { // Réglage du temps OFF
    minutes_OFF = minutes_OFF + 1; //Incrémente de 1 à chaque pression du bouton jaune.
    if (minutes_OFF > 59) { // si dépassement de 59, remet à 0.
      minutes_OFF = 0;
    }
    S_OFF = minutes_OFF;
    Fn_Display(); // Rafraîchi l'affichage.
    delay(10); // Délais de 10 ms entre chaque pression du bouton
    // Empêche les rebonds des boutons poussoirs
    while (etat_btn_TimeOff) etat_btn_TimeOff = digitalRead(btn_TimeOff);
  }

  // Enregitrement des temps ON/OFF du minuteur
  if (etat_btn_Set == LOW) {
    Record = Record + 1; // Nombre de fois que le bouton gris est pressé.
    if (Record == 1) { // S'il est égale à 1, exécute les instructions entre ses accolades.
      lcd1.setCursor(17, 1); lcd1.print("SET");// Set indique que la valeur du temps ON est stocké.
      onTime = minutes_ON;  MyTimeON = onTime;  S_ON = MyTimeON; // Enregistre les variables.
      Fn_Display(); // Rafraîchi l'affichage.
    }
    if (Record == 2) {  // S'il est égale à 2, exécute les instructions entre ses accolades.
      lcd1.setCursor(17, 2); lcd1.print("SET"); // Set indique que la valeur du temps OFF est stocké.
      offTime = minutes_OFF; MyTimeOFF = offTime; S_OFF = MyTimeOFF;  // Enregistre les variables.
      Fn_Display(); // Rafraîchi l'affichage.
      etatRecord = true; // Confirme que les valeurs sélectionnées on été engeristrées.
      delay(1000); // Délais de 1 secondes avant d'afficher les messages qui suivent...
      lcd1.setCursor(14, 1); lcd1.print("Ready!"); // Ready! indique que le minuteur ON est programmé et prêt à être lancé.
      lcd1.setCursor(14, 2); lcd1.print("Ready!"); // Ready! indique que le minuteur OFF est programmé et prêt à être lancé.
      delay(1000);// Délais de 1 secondes avant d'afficher les messages qui suivent..
      lcd1.setCursor(14, 1); lcd1.print("Relais");
      lcd1.setCursor(14, 2); lcd1.print("  OFF "); // État actuel du relais. Prêt à être lancé avec le chronomètre (bouton rouge Start/Stop).
    }
    delay(10); // Délais de 10 ms entre chaque pression du bouton
    // Empêche les rebonds des boutons poussoirs
    while (etat_btn_Set) etat_btn_Set = digitalRead(btn_Set);
  }
}

void Fn_Chronometre() {// Fonction pricipale de la gestion de l'horloge du chrono
  // Incrémentation du compteur de chrono en utilisant la fonction millis()
  currentTime = millis();                    // Le temps courant = l'horloge interne en ms.
  TempsEcoule = currentTime - previousTime;  // Le temps écoulée = au temps courant - le temps précédant
  previousTime = millis();                   // nouveau temps précédant = l'horloge interne en ms.

  // Fonction compteur d'incrémentation des variables Seconde, Minute, Heure
  if (etat_ChronoOnOFF == 1) {                 // Si Etat_ChronoOnOff = true exécute le code entre {}.
    lcd.setCursor(0, 3); lcd.print(Start_Stop[1]);
    Milliseconde = Milliseconde + TempsEcoule; // Incrémentation des millisecondes
    if (Milliseconde > 999) {                  // Si Milliseconde est supérieure à 999... (1000ms)
      Milliseconde = Milliseconde - 1000;      // ...Remise à 0 de la variable Milliseconde
      seconde++;   // Incrémentation de 1 seconde toutes les 1000 Millisecondes
      index++;     // Index de 0 à 10 pour le tableau nommé Mark[11].
      tick();      // Émet un tic toutes les secondes
    

    if (MyTimeON > 0) {
      FlagON = true;//*****************
      etat_SSRrelais = FlagON; // État du relais selon la valeur de FlagOn (On/OFF ou 1/0 ou true/false)
      digitalWrite(SSR_relais, FlagON);  // Mettre à jour le relais selon la valeur de FlagOn (On/OFF ou 1/0 ou true/false)
      lcd1.setCursor(14, 2); lcd1.print("  ON  ");
      MyTimeON--;
      S_ON = MyTimeON;
    }

    if ((MyTimeOFF > 0) && (MyTimeON == 0)) {
      FlagON = false ;//**********************
      lcd1.setCursor(14, 2); lcd1.print("  OFF ");
      etat_SSRrelais = FlagON; // État du relais selon la valeur de FlagOn (On/OFF ou 1/0 ou true/false)
      digitalWrite(SSR_relais, FlagON); // Mettre à jour le relais selon la valeur de FlagOn (On/OFF ou 1/0 ou true/false)
      MyTimeOFF--; S_OFF = MyTimeOFF;
      if ((MyTimeOFF == 0) && (MyTimeON == 0)) {
        MyTimeON = onTime;
        MyTimeOFF = offTime;
      }
    }
    }
    if (seconde > 59) { // Si Seconde est supérieure à 59... (60sec)
      seconde = 0;      // ...Remise à 0 de la variable Seconde
      beep();           // Émet un beep par minute
      minute++;         // Incrémentation de 1 Minute toutes les 60 Secondes
    }
    if (minute > 59) {  // Si Minute est supérieure à 59... (60 minutes)
      minute = 0;       // ...Remise à 0 de la variable Minute
      heure++;          // Incrémentation de 1 Heure toutes les 60 Minutes
    }
  }
 
  if (etat_btn_StartStop == HIGH && etat_btn_Reset == HIGH) {
    etat_BoutonAppuyer = 0;   // réinitialise la variable "etat_BoutonAppuyer" à "0" cequi évite l'effet rebond
  }
Fn_Display();     // Fonction principale d'Affichage en temps réel.
}
// fonction perso pour centrer le texte à afficher sur lcd.
void lcdPrintCenter(String text, int ligne) {
  int len = text.length();                     // défini la longeur de la variable text
  lcd.setCursor((20 - len) / 2, ligne);  // positionne le curseur au centre de la ligne choisie et soustrait la longeur du text à afficher
  lcd.print(text);                         // affiche le text centré
}
// fonction perso pour centrer le texte à afficher sur lcd1.
void lcd1PrintCenter(String text, int ligne) {
  int len = text.length();                     // défini la longeur de la variable text
  lcd1.setCursor((20 - len) / 2, ligne);  // positionne le curseur au centre de la ligne choisie et soustrait la longeur du text à afficher
  lcd1.print(text);                         // affiche le text centré
}

void Fn_Display() { // rafraîchi l'Affichage en temps réel toutes les secondes.
  lcdPrintCenter(Mark[index], 2);
  sprintf(MinuteurON, "T-ON:  %02ld:%02ld", M_ON, S_ON); //Formate l'affichage.
  sprintf(MinuteurOFF, "T-OFF: %02ld:%02ld", M_OFF, S_OFF); //Formate l'affichage.
  sprintf(Chrono, "%02ld:%02ld:%02ld:%03ld ", heure, minute, seconde, Milliseconde ); //Formate l'affichage.
  lcdPrintCenter(Chrono, 1);  // Affiche le temps écoulé.
  lcd1.setCursor(0, 1); lcd1.print(MinuteurON); // Afffiche le minuteur ON sur la 3e ligne du lcd.
  lcd1.setCursor(0, 2); lcd1.print(MinuteurOFF);// Afffiche le minuteur OFF sur la 4e ligne du lcd.
  if (index >= 10)index = 0;
}
NOCOMNCVCCGNDINLED1PWRRelay Module