// Blocage Servo Moteur
// https://forum.arduino.cc/t/blocage-servo-moteur/1305482

#define SIMULATION
typedef uint16_t short_time_t;
typedef uint32_t time_t;

#include "Servo.h" //inclure la bibliothèque Servomoteur

#include <OneWire.h>
#include <DallasTemperature.h>

#include <LiquidCrystal_I2C.h>   // inclure la bibliothèque I2C
LiquidCrystal_I2C lcd(0x27, 16, 2); // adresse i2c , nombre de colonnes, nombre de lignes de l'écran

const byte PinModeManuel = 4;   //Bleu
const byte PinVanne[] = {12, 7, 8};
const byte pinServo[] = {11, 6, 9};
const uint8_t pinCourantVanne[] = {A0, A1, A2};

enum : byte {AUTO, MANUEL, START} mode = START;
enum : byte {NORD, EST, OUEST, PISCINE};
enum : byte {DEMARRE, REGULE} modeAuto = DEMARRE;
#define VANNE_OUVERTE 0
#define VANNE_FERMEE 90
#define COURANT_VANNE_MIN 100   // Seuil de courant consommé par une vanne en mouvement
#define COURANT_VANNE_MAX 750   // Courant indiquant une vanne grippée

char *NomVanne[] = {"NORD", "EST", "OUEST"};
const int8_t pos[] = {15, 6, 15};

#define DIX_MINUTES 10000UL
#define TEMPS_MOUVEMENT_SERVO 2000UL
#define MARGE_DE_SECURITE 2000UL
#define DECALAGE_VANNES 1000UL
#define QUARANTES_MINUTES 40000UL
#define DELAI_DEMARRAGE_SERVO 200UL

/* Broche du bus 1-Wire */
const byte BROCHE_ONEWIRE = 3;

/* Adresses des capteurs de température */
const byte SENSOR_PISCINE_ADDRESS[] = { 0x28, 0xBA, 0xC5, 0x99, 0x5F, 0x20, 0x01, 0xEF };
const byte SENSOR_EST_ADDRESS[] = { 0x28, 0xF6, 0xD2, 0x13, 0x37, 0x20, 0x01, 0x88 };
const byte SENSOR_OUEST_ADDRESS[] = { 0x28, 0x64, 0x09, 0x80, 0x5C, 0x20, 0x01, 0x74 };
const byte SENSOR_NORD_ADDRESS[] = { 0x28, 0x58, 0xE0, 0xDB, 0x5F, 0x20, 0x01, 0x65 };

/* Création de l'objet OneWire pour manipuler le bus 1-Wire */
OneWire ds(BROCHE_ONEWIRE);
DallasTemperature sensors(&ds);

#define NOMBRE_VANNES 3
Servo servo[NOMBRE_VANNES]; // création des objets "servo"

float temperature[4];
time_t tempsActuel;
time_t tempsReference[NOMBRE_VANNES];
int positionCible[NOMBRE_VANNES];


enum : uint8_t {OUVERTURE_VANNE, PAUSE_10_MN, REGULATION_TEMPERATURE_40_MN_MAX, FERMETURE_VANNE, ATTENTE_NOUVEAU_CYCLE} statutVanne[NOMBRE_VANNES];

// Définiton de symboles spécifiques pour l'afficheur réduit 16x2
enum : uint8_t {charAuto = 5, charManuel, charDegC};
const uint8_t DegC[8] = { // °C
  0b11000,
  0b11000,
  0b00000,
  0b00110,
  0b01000,
  0b01000,
  0b00110,
  0b00000
};

const uint8_t Auto[8] = {
  0b00110,
  0b01001,
  0b01111,
  0b01001,
  0b00000,
  0b01001,
  0b01001,
  0b00110
};

const uint8_t Manuel[8] = {
  0b11011,
  0b10101,
  0b10001,
  0b10001,
  0b00110,
  0b01001,
  0b01111,
  0b01001
};

const uint8_t decimal[][8] = { // chiffres 0 à 9 de petite taille précédés d'un point décimal
  { // 0
    0b00110,
    0b01001,
    0b01011,
    0b01101,
    0b01001,
    0b00110,
    0b10000,
    0b00000
  },
  { // 1
    0b00010,
    0b00110,
    0b00010,
    0b00010,
    0b00010,
    0b00111,
    0b10000,
    0b00000
  },
  { // 2
    0b00110,
    0b01001,
    0b00001,
    0b00010,
    0b00100,
    0b01111,
    0b10000,
    0b00000
  },
  { // 3
    0b01111,
    0b00001,
    0b00010,
    0b00001,
    0b01001,
    0b00110,
    0b10000,
    0b00000
  },
  { // 4
    0b00010,
    0b00110,
    0b01010,
    0b01111,
    0b00010,
    0b00010,
    0b10000,
    0b00000
  },
  { // 5
    0b01111,
    0b01000,
    0b01110,
    0b00001,
    0b01001,
    0b00110,
    0b10000,
    0b00000
  },
  { // 6
    0b00110,
    0b01000,
    0b01110,
    0b01001,
    0b01001,
    0b00110,
    0b10000,
    0b00000
  },
  { // 7
    0b01111,
    0b01001,
    0b00001,
    0b00010,
    0b00100,
    0b00100,
    0b10000,
    0b00000
  },
  { // 8
    0b00110,
    0b01001,
    0b00110,
    0b01001,
    0b01001,
    0b00110,
    0b10000,
    0b00000
  },
  { // 9
    0b00110,
    0b01001,
    0b01001,
    0b00111,
    0b00001,
    0b00110,
    0b10000,
    0b00000
  }
};

void lcdPrint(float temperature, byte colonne, byte ligne) {
  static int8_t charNum = 0;

  // Pour des questions de place sur l'afficheur, seules les températures de -9.9 à 99.9°C sont affichées
  if (-10.0 < temperature && temperature < 100.0) {
    int16_t temp = int(abs(temperature * 10.0) + .5); // temp contient l'arrondi de temperature * 10
    lcd.createChar(charNum, decimal[temp % 10]);      // choisir le chiffre de la partie décimale
    String str = String((temperature > -.05) ? temp / 10 : -temp / 10); // afficher la partie entière avec son signe
    lcd.setCursor(colonne, ligne);
    if (str.length() < 2) {     // et ajouter un espace si nécessaire
      lcd.print(' ');
    }
    lcd.print(str);
    lcd.print((char)charNum);
  }
  else {   // Affichage lorsque la température est hors limites
    lcd.setCursor(colonne, ligne);
    lcd.print(F("***"));
  }
  charNum = (charNum + 1) % 4;
}

void afficheTemperatures(const float *temperature) {
  Serial.print(F("Temperatures: Piscine:"));
  Serial.print(temperature[PISCINE], 4);
  Serial.write('°'); // Caractère degré
  Serial.print(F("C, Est: "));
  Serial.print(temperature[EST], 4);
  Serial.write('°'); // Caractère degré
  Serial.print(F("C, Ouest: "));
  Serial.print(temperature[OUEST], 4);
  Serial.write('°'); // Caractère degré
  Serial.print(F("C, Nord: "));
  Serial.print(temperature[NORD], 4);
  Serial.write('°'); // Caractère degré
  Serial.println("C, -10, 50");

  /* Affiche les températures sur l'écran lcd */
  lcdPrint(temperature[PISCINE], 2, 0);
  lcdPrint(temperature[EST], 2, 1);
  lcdPrint(temperature[OUEST], 11, 1);
  lcdPrint(temperature[NORD], 11, 0);
}

#if defined SIMULATION
uint32_t servoTime[NOMBRE_VANNES];

void servoWrite(uint8_t vanneNb, int positionVanne) {
  servoTime[vanneNb] = micros();
}

uint16_t analogReadSimu(uint8_t vanneNb) {
  uint32_t duration = micros() - servoTime[vanneNb];
  // Serial.print("Micro - servoTime[");
  // Serial.print(NomVanne[vanneNb]);
  // Serial.print("] = ");
  // Serial.println(duration);

  if (duration < DELAI_DEMARRAGE_SERVO * 250) return 0;
  else if (duration < DELAI_DEMARRAGE_SERVO * 800) return COURANT_VANNE_MAX * 4 / 3;
  else if (duration < TEMPS_MOUVEMENT_SERVO * 700) return COURANT_VANNE_MAX * 2 / 3;
  else return COURANT_VANNE_MIN / 2;
}
#endif

int8_t manageVanne(uint8_t vanneNb) {
  static time_t tempsDebutMouvementVanne[NOMBRE_VANNES] = {-1 - QUARANTES_MINUTES, -1 - QUARANTES_MINUTES, -1 - QUARANTES_MINUTES};
  static int positionVanne[NOMBRE_VANNES];
  static int8_t taskCount = 0;
  // static uint16_t courantVanne[NB_CAPTEURs_COURANT];

#if defined SIMULATION
  uint16_t courantVanne = analogReadSimu(vanneNb);
  // Serial.print("analogRead vanne ");
  // Serial.print(NomVanne[vanneNb]);
  // Serial.print(" = ");
  // Serial.println(courantVanne);

#else
  uint16_t courantVanne = analogRead(pinCourantVanne[vanneNb]);
#endif
  if (servo[vanneNb].attached()) {
    if (tempsDebutMouvementVanne[vanneNb] + DELAI_DEMARRAGE_SERVO < millis() &&
        courantVanne > COURANT_VANNE_MAX) {
      servo[vanneNb].detach();
      tempsDebutMouvementVanne[vanneNb] = -1 - QUARANTES_MINUTES;
      taskCount--;
      if (taskCount == 0) digitalWrite(LED_BUILTIN, LOW);
      Serial.print("Servo activé, consommation de courant anormale vanne ");
      Serial.println(NomVanne[vanneNb]);
    }
  }
  else if (COURANT_VANNE_MIN < courantVanne) {
    Serial.print("Servo non activé, consommation de courant anormale vanne ");
    Serial.println(NomVanne[vanneNb]);
  }

  // Serial.print(F("manageVanne["));
  // Serial.print(NomVanne[vanneNb]);
  // Serial.print(F("] Cible = "));
  // Serial.print(positionCible[vanneNb]);
  // Serial.print(F(", Pos = "));
  // Serial.println(positionVanne[vanneNb]);
  if (taskCount == 0 && positionVanne[vanneNb] != positionCible[vanneNb]) {
    servo[vanneNb].attach(pinServo[vanneNb]);
    // StatutVanne[vanneNb] == MOVE;
    tempsDebutMouvementVanne[vanneNb] = millis();
    digitalWrite(LED_BUILTIN, HIGH);
    taskCount++;
    positionVanne[vanneNb] = positionCible[vanneNb];
    servo[vanneNb].write(positionVanne[vanneNb]); // Ouverture Vanne Est
#if defined SIMULATION
    servoWrite(vanneNb, positionVanne[vanneNb]);
#endif
    // Serial.print(F("manageVanne "));
    // Serial.print(NomVanne[vanneNb]);
    // Serial.println(F(" démarre"));
  }
  else if (tempsDebutMouvementVanne[vanneNb] + TEMPS_MOUVEMENT_SERVO < millis() ||
           tempsDebutMouvementVanne[vanneNb] + DELAI_DEMARRAGE_SERVO < millis() &&
           courantVanne < COURANT_VANNE_MIN) {
    servo[vanneNb].detach();
    tempsDebutMouvementVanne[vanneNb] = -1 - QUARANTES_MINUTES;
    taskCount--;
    if (taskCount == 0) digitalWrite(LED_BUILTIN, LOW);
    // Serial.print(F("manageVanne "));
    // Serial.print(NomVanne[vanneNb]);
    // Serial.println(F(" actionnée"));
  }
  // Serial.print(F("TaskCount = "));
  // Serial.println(taskCount);
  lcd.setCursor(6, 0);
  lcd.print(taskCount);
  return taskCount;
}

void commandeManuelle(uint8_t vanneNb) {
  lcd.setCursor(pos[vanneNb], (vanneNb + 1) / 2);
  if (digitalRead(PinVanne[vanneNb]) == HIGH) {
    positionCible[vanneNb] = VANNE_OUVERTE;
    lcd.print('O');  // Vanne Ouverte
  }
  else {
    positionCible[vanneNb] = VANNE_FERMEE;
    lcd.print('F');  // Vanne Fermée
  }
  manageVanne(vanneNb);
}

void regule(uint8_t vanneNb) {
  switch (statutVanne[vanneNb]) {
    case OUVERTURE_VANNE:
      tempsReference[vanneNb] = tempsActuel;
      // if (positionCible[vanneNb] != VANNE_OUVERTE) {
      positionCible[vanneNb] = VANNE_OUVERTE;  // Ouverture vanne
      lcd.setCursor(pos[vanneNb], (vanneNb + 1) / 2);
      lcd.print('O');  // Vanne Ouverte
      Serial.print(F("Vanne "));
      Serial.print(NomVanne[vanneNb]);
      Serial.println(F(" Ouverture"));
      statutVanne[vanneNb] = PAUSE_10_MN;
      // }
      break;

    case PAUSE_10_MN:
      if (tempsActuel - tempsReference[vanneNb] > DIX_MINUTES) { // 10 seconde pour le proto
        statutVanne[vanneNb] = REGULATION_TEMPERATURE_40_MN_MAX;
        Serial.print(F("Vanne "));
        Serial.print(NomVanne[vanneNb]);
        Serial.println(F(" fin pause 10mn , régulation température"));
      }
      break;

    case REGULATION_TEMPERATURE_40_MN_MAX:
      if (temperature[PISCINE] > temperature[vanneNb] || tempsActuel - tempsReference[vanneNb] > QUARANTES_MINUTES - TEMPS_MOUVEMENT_SERVO - MARGE_DE_SECURITE) {
        statutVanne[vanneNb] = FERMETURE_VANNE;
        Serial.print(F("Vanne "));
        Serial.print(NomVanne[vanneNb]);
        Serial.println(F(" fin régulation température, fermeture vanne"));
      }
      break;

    case FERMETURE_VANNE:
      // if (positionCible[vanneNb] != VANNE_FERMEE) {
      positionCible[vanneNb] = VANNE_FERMEE;
      lcd.setCursor(pos[vanneNb], (vanneNb + 1) / 2);
      lcd.print('F');  // Vanne Fermée
      // echeanceFermetureVanne[vanneNb] = tempsActuel + TEMPS_MOUVEMENT_SERVO; // 1 seconde pour fermer vanne
      Serial.print(F("Vanne "));
      Serial.print(NomVanne[vanneNb]);
      Serial.println(F(" fermeture"));
      statutVanne[vanneNb] = ATTENTE_NOUVEAU_CYCLE;
      // }
      // else {
      //   // Placer ici le code de contrôle de la fermeture de vanne : courant max, temps max...
      //   if (tempsActuel > echeanceFermetureVanne[vanneNb]) {
      //     servo[vanneNb].detach();
      //     statutVanne[vanneNb] = ATTENTE_NOUVEAU_CYCLE;
      //     Serial.print(F("Vanne "));
      //     Serial.print(NomVanne[vanneNb]);
      //     Serial.println(F(" fermée, attente fin du cycle"));
      //   }
      // }
      break;

    case ATTENTE_NOUVEAU_CYCLE:
      if (tempsActuel - tempsReference[vanneNb]  > QUARANTES_MINUTES) {
        statutVanne[vanneNb] = OUVERTURE_VANNE;
        Serial.print(F("Vanne "));
        Serial.print(NomVanne[vanneNb]);
        Serial.println(F(" redémarrage cycle"));
      }
      break;
  }
  manageVanne(vanneNb);
}

short_time_t tempsMesureCapteur;
short_time_t mesureCapteurFaite;

void setup() {
  uint8_t sensorAddress[8];

  // Start serial communication for debugging purposes
  Serial.begin(115200);

  sensors.begin();
  sensors.setResolution(12);
  sensors.setWaitForConversion(false);
  sensors.setCheckForConversion(true);

  sensors.requestTemperatures();
  tempsMesureCapteur = 750 / (1 << (12 - sensors.getResolution()));
  mesureCapteurFaite = millis() + tempsMesureCapteur;

  /* Ecran LCD  */
  lcd.init();   // initialisation du LCD
  lcd.backlight();   // active le rétroéclairage
  lcd.createChar(charAuto, Auto);
  lcd.createChar(charManuel, Manuel);
  lcd.createChar(charDegC, DegC);

  // Affichage de base sur LCD
  lcd.setCursor(0, 0);    // mettre le curseur à la première colonne, première ligne
  lcd.print(F("P=   "));     // Piscine
  lcd.print((char)charDegC);     // °C
  lcd.print(F("   N=   "));  // plage Nord
  lcd.print((char)charDegC);     // °C

  lcd.setCursor(0, 1);    // mettre le curseur à la première colonne, deuxième ligne
  lcd.print(F("E=   "));     // plage EST
  lcd.print((char)charDegC);     // °C
  lcd.print(F("   O=   "));  // plage Ouest
  lcd.print((char)charDegC);     // °C

  pinMode(PinModeManuel, INPUT);
  pinMode(PinVanne[EST], INPUT);
  pinMode(PinVanne[OUEST], INPUT);
  pinMode(PinVanne[NORD], INPUT);

  for (uint8_t vanne = 0; vanne < NOMBRE_VANNES; vanne++) {
    positionCible[vanne] = VANNE_FERMEE;
    manageVanne(vanne);
  }

  while (millis() < mesureCapteurFaite) {
    for (uint8_t vanne = 0; vanne < NOMBRE_VANNES; vanne++) {
      manageVanne(vanne);
    }
  }
  sensors.getTempCByIndex(0);

  // Set the sensors resolution
  sensors.setResolution(12);
  sensors.requestTemperatures();
  tempsMesureCapteur = 750 / (1 << (12 - sensors.getResolution()));
  mesureCapteurFaite = millis() + tempsMesureCapteur;
  while (millis() < mesureCapteurFaite) {
    for (uint8_t vanne = 0; vanne < NOMBRE_VANNES; vanne++) {
      manageVanne(vanne);
    }
  }
  temperature[PISCINE] = sensors.getTempC(SENSOR_PISCINE_ADDRESS); // Température de la piscine
  temperature[EST] = sensors.getTempC(SENSOR_EST_ADDRESS); // Température du circuit Est
  temperature[OUEST] = sensors.getTempC(SENSOR_OUEST_ADDRESS); // Température du circuit Ouest
  temperature[NORD] = sensors.getTempC(SENSOR_OUEST_ADDRESS); // Température du circuit Nord
  afficheTemperatures(temperature);
  sensors.requestTemperatures();
  mesureCapteurFaite = millis() + tempsMesureCapteur;

  int taskCount;
  do {
    delay(10);
    for (uint8_t vanne = 0; vanne < NOMBRE_VANNES; vanne++) {
      tempsActuel = millis();
      taskCount = manageVanne(vanne);
    }
  } while (taskCount);
  Serial.println("Ready");
}

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

  // Afficher les températures lorsqu'elles sont mesurées et démarrer une nouvelle mesure
  if (tempsActuel > mesureCapteurFaite) {
    temperature[PISCINE] = sensors.getTempC(SENSOR_PISCINE_ADDRESS); // Température de la piscine
    temperature[EST] = sensors.getTempC(SENSOR_EST_ADDRESS); // Température du circuit Est
    temperature[OUEST] = sensors.getTempC(SENSOR_OUEST_ADDRESS); // Température du circuit Ouest
    temperature[NORD] = sensors.getTempC(SENSOR_NORD_ADDRESS); // Température du circuit Nord
    sensors.requestTemperatures();
    mesureCapteurFaite = millis() + tempsMesureCapteur; // Utiliser millis() car beaucoup de temps s'est
    afficheTemperatures(temperature);                   // écoulé depuis la MàJ de tempsActuel
  }

  if (digitalRead(PinModeManuel) == HIGH) {
    if (mode != MANUEL) {
      lcd.setCursor(7, 0);  // mettre le curseur à la sixième colonne, première ligne
      lcd.print((char)charManuel);  // mode Manuel
      mode = MANUEL;
    }

    for (uint8_t vanne = 0; vanne < NOMBRE_VANNES; vanne++) {
      commandeManuelle(vanne);
    }
  }

  else {
    // static short_time_t sequenceDemarreVanne;

    if (mode != AUTO) {
      lcd.setCursor(7, 0);
      lcd.print((char)charAuto);  // Mode automatique
      for (uint8_t vanne = 0; vanne < NOMBRE_VANNES; vanne++) {
        statutVanne[vanne] = OUVERTURE_VANNE; // Remise à zéro de la régulation
      }
      mode = AUTO;
      // modeAuto = DEMARRE;
      // sequenceDemarreVanne = tempsActuel;
    }

    // if (modeAuto == DEMARRE) {
    //   regule(NORD);
    //   if (tempsActuel > sequenceDemarreVanne + DECALAGE_VANNES) {
    //     regule(EST);
    //     if (tempsActuel > sequenceDemarreVanne + 2 * DECALAGE_VANNES) {
    //       regule(OUEST);
    //       modeAuto = REGULE;
    //     }
    //   }
    // }
    // else {
    for (uint8_t vanne = 0; vanne < NOMBRE_VANNES; vanne++) {
      regule(vanne);
    }
    // }
  }
  delay(10);
}
T° piscine
T° est
T° ouest
T°nord
est
ouest
nord
Manu
Auto