/// ouvertureSerre
// Version 1.1
// Auteur : Alban Réveillé, 0651029833 alban.reveille@gmail.com

#include <Bounce2.h>
#include <DualVNH5019MotorShield.h>
#include <OneWire.h> 
#include <DallasTemperature.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>


////Constantes à définir par l'usager
// temperature en degres celcius
const float TEMPERATURE_CONSIGNE = 23;
const int HYGROMETRIE_CONSIGNE = 80;
const float HYSTERESIS = 2;

// la valeur du courant en milliampère qui déclanchera l'arrêt de sécurité du moteur
const int MOTEUR_COURANT_FORCE = 17000;
// La valeur de durée en milliseconde durant laquelle le moteur est en lancement et comsomme beaucoup de courant
const int MOTEUR_TEMPS_LANCEMENT = 1500;

////
// Constantes liées à l'utilisation d'une alimentation de PC
// Power : l'arduino dit à l'alim de démarrer sur la PIN 12
const int PIN_POWER_ON = 12;
const int PIN_POWER_GOOD = 13;

//// Constantes à définir par l'installateur (lié au montage arduino)
// Mode débug 
// LOG=0 no log
// LOG=1 for serial monitor
const int LOG = 0;

// MODE= 0 : manuel
// MODE = 1 : automatique
int MODE = 0;

//// Capteur Fin de Course HAUT : doit être branché en normalement LOW, c'est à dire normalement fermé
// 1 pin sur le GND
// L'autre sur la borne 3 de l'arduino
const int PIN_CAPTEUR_HAUT = 3;

//// Capteur Fin de Course BAS : doit être branché en normalement LOW, c'est à dire normalement fermé
// 1 pin sur le GND
// L'autre sur la borne 6 de l'arduino
const int PIN_CAPTEUR_BAS = 6;

//// Sonde de température
// la pin données du capteur de temperature de la serre sur la borne D5
const int PIN_TEMPERATURE = 5;
// La pin - sur le GND
// La pin + sur le stand by de l'alimentation ou le 5V de l'arduino

//// Bouton bas : doit être branché en normalement HIGH, c'est à dire normalement Ouvert
// 1 pin sur le GND
// L'autre pin sur la borne 11 de l'arduino
const int PIN_BOUTON_BAS = 11;

//// Bouton haut : doit être branché en normalement HIGH, c'est à dire normalement Ouvert
// 1 pin sur le GND
// L'autre pin sur la borne 10 de l'arduino
const int PIN_BOUTON_HAUT = 10;

// Bouton manuel/automatique
// 1 fil sur le GND
// L'autre fils sur la borne 8 de l'arduino
const int PIN_BOUTON_MANUEL = 8;

//// Sonde d'hygrometrie
//la pin de donnée sur la borne 6
const int PIN_HYGROMETRIE = 7;
// La pin - sur le GND
// La pin + sur le stand by de l'alimentation ou le 5V de l'arduino

// Le type de la sonde d'hygrometrie
#define DHTTYPE           DHT21

//PINs à utiliser pour monitorer le moteur avec DualVNH5019MotorShield
// vert d9 ->PWM
//jaune d4 -> INB
// orange d2 -> INA
// noir gnd ->GND
// rouge 5v-> VDD
// Bleu A0 -> CS

// capacité condensateur pour la carte moteur ATTENTION AU SENS
//1 fil GND
//1 fil blanc sur CS de la carte moteur

// capteur de fin de course bas : doit être branché en normalement LOW, c'est à dire normalement fermé
// fil orange : D6
// fil noir : GND

//capteur de fin de courses haut : doit être branché en normalement LOW, c'est à dire normalement fermé
// fil orange D3
//fils rouge GND

// une variable pour compter le temps entre 2 appels à la fonction millis
float TIME = 0;

//// Instances de librairies
//Moniteur pour le Moteur
DualVNH5019MotorShield G_Moteur;

// Moniteur pour la sonde de température
OneWire oneWire(PIN_TEMPERATURE); 
DallasTemperature G_SondeTemperature(&oneWire);

//Moniteur pour gérer les anti-rebond des capteurs de fin de course
Bounce G_debouncerFCHaut = Bounce(); 
Bounce G_debouncerFCBas = Bounce(); 

//Moniteur pour gérer les boutons manuels haut et bas
Bounce G_debouncerBoutonHaut = Bounce(); 
Bounce G_debouncerBoutonBas = Bounce(); 

//Moniteur pour gérer le bouton manuel/auto
Bounce G_debouncerBoutonManuel = Bounce(); 

//Moniteur pour la sonde d'hygrometrie
DHT_Unified G_SondeHygrometrie(PIN_HYGROMETRIE, DHTTYPE);

// La fonction qui se lance 1 seule fois au démarrage
void setup() {
  // Définie le niveau de log
  if (LOG)
  {
    // Open serial communications and wait for port to open:
    Serial.begin(9600);
    while (!Serial) {
      ; // wait for serial port to connect. Needed for native USB port only
    }
  }
  // Démarre les sondes 
  G_SondeTemperature.begin();
  G_SondeHygrometrie.begin();

  // Attache le capteur FDC HAUT
  G_debouncerFCHaut.attach(PIN_CAPTEUR_HAUT);
  G_debouncerFCHaut.interval(5); //en ms
  
  // Attache le capteur FDC BAS
  G_debouncerFCBas.attach(PIN_CAPTEUR_BAS);
  G_debouncerFCBas.interval(5); //en ms

  // Attache le bouton haut
  G_debouncerBoutonHaut.attach(PIN_BOUTON_HAUT);
  G_debouncerBoutonHaut.interval(5); //en ms

  // Attache le bouton BAS
  G_debouncerBoutonBas.attach(PIN_BOUTON_BAS);
  G_debouncerBoutonBas.interval(5); //en ms

  // Attache le bouton manuel/auto
  G_debouncerBoutonManuel.attach(PIN_BOUTON_MANUEL);
  G_debouncerBoutonManuel.interval(5); //en ms

  G_Moteur.init();
    
  //Déclare des mode des pin
  DeclarePinMode();
  
  //purge les premieres valeurs des sondes et capteurs
  purge();
  
}

//purge les premieres valeurs des sondes et capteurs
void purge()
{
  getTemp();
  
  G_debouncerFCBas.update();
  G_debouncerFCBas.read();
  
  G_debouncerFCHaut.update();
  G_debouncerFCHaut.read();
  
  G_debouncerBoutonManuel.update();
  G_debouncerBoutonManuel.read();
  
  G_debouncerBoutonBas.update();
  G_debouncerBoutonBas.read();
  
  G_debouncerBoutonHaut.update();
  G_debouncerBoutonHaut.read();
}

// Allume l'alimentation de PC en envoyant LOW sur le fil vert
void AllumeAlimentation()
{
  digitalWrite(PIN_POWER_ON, LOW);

  // On attend que l'alimentation soit stable pour continuer. C'est le fil Gris que l'on regarde
  while(digitalRead(PIN_POWER_GOOD)!=HIGH)
  {
    logger("Alimentation en cours de démarrage");
  }
  
  logger("Alimentation démarrée");
}


//Fonction qui déclare les PINs des différentes entrées-sorties
void DeclarePinMode()
{
  logger("Initialise les pins...");
  
  // Capteur d'arrêt haut
  pinMode(PIN_CAPTEUR_HAUT, INPUT_PULLUP);
  // Capteur d'arrêt bas
  pinMode(PIN_CAPTEUR_BAS, INPUT_PULLUP);
    // Bouton haut
  pinMode(PIN_BOUTON_HAUT, INPUT_PULLUP);
  // Bouton bas
  pinMode(PIN_BOUTON_BAS, INPUT_PULLUP);
  // Bouton Manuel
  pinMode(PIN_BOUTON_MANUEL, INPUT_PULLUP);
  // Capteur de température de la serre
  pinMode(PIN_TEMPERATURE, INPUT);
  // Capteur de température de la serre
  pinMode(PIN_TEMPERATURE, INPUT);
  // pin power on
  pinMode(PIN_POWER_ON, OUTPUT);
  // pin power good
  pinMode(PIN_POWER_GOOD, INPUT);
  
}

// La fonction qui s'execute en boucle dès que l'arduino est sous tension
void loop() { 
  // ralenti l'execution, TODO : à faire plus propre en etteignant l'arduino
  delay(2000);
  
  //detecte le mode d'execution en fonction de la position du bouton dédié
  detecteMode();
  
  // Si la serre doit être ouverte
  if (DoitEtreOuverte())
  {
    logger("Ouverture de la serre...");
    // ALORS on l'ouvre
    int l_iMoteurForce = Ouvre();
    // SI le moteur a forcé durant l'ouverture
    if (l_iMoteurForce)
    {
      logger("Arrêt du moteur car il a forcé : ouverture partielle");
    }
    else
    {
      logger("Ouverture effectuée");
    }
  }

  // SI la serre doit être fermée
  if (DoitEtreFermee())
  {
    logger("Fermeture de la serre");
    // ALORS on la ferme
    int l_iMoteurForce = Ferme();

    // SI le moteur a forcé durant la fermeture
    if (l_iMoteurForce)
    {
      logger("Arrêt du moteur car il a forcé : fermeture partielle");
    }
    else
    {
      logger("Fermeture effectuée");
    }
  }
}

// Réccupère la température actuelle moyenne de la serre
float getTemp()
{
  G_SondeTemperature.requestTemperatures();
  return (G_SondeTemperature.getTempCByIndex(0));
}

// Dertermine si la porte doit être ouverte en fonction de
// - mode manuel ou automatique
// - position du capteur haut
// - température actuelle de la serre
boolean DoitEtreOuverte()
{
  // Réccupère l'information sur le capteur haut
  G_debouncerFCHaut.update();
  boolean l_bCapteurHautValue = G_debouncerFCHaut.read();
  
  boolean G_BoutonHautValue = HIGH;

  // en fonction du mode
  switch(MODE)
  {
    //Manuel
    case 0:
      // Réccupère la valeur du bouton haut
      G_debouncerBoutonHaut.update();
      G_BoutonHautValue = G_debouncerBoutonHaut.read();

      // vrai si bouton HAUT appuyé et capteur FDC Haut pas touché, FAUX sinon
      return (G_BoutonHautValue==LOW && l_bCapteurHautValue==LOW);
    break;

    //Automatique
    case 1:
      // Réccupère la température
      float l_fTemperature = getTemp();
      
      // Réccupère l'humidité
      sensors_event_t event;  
      G_SondeHygrometrie.humidity().getEvent(&event);
      int l_iHygrometrie = event.relative_humidity;

      // Si l'hygrometrie lue n'est pas un nombre
      if (isnan(l_iHygrometrie)) {
        logger("Impossible de lire l'humidité");
        return false;
      }
      // Si la température lue n'est pas un nombre
      else if (isnan(l_fTemperature)) 
      {
        logger("Impossible de lire la température");
        return false;
      }
      // Sinon, les lectures se sont bien passées
      else
      {
        // Renvoie la porte n'est pas en haut ET (la température de la serre est supérieur à la température de consigne + hysteresis OU la sonde d'hygro renvoie 99 (incohrent)
        return (l_bCapteurHautValue==LOW && ((l_fTemperature > (TEMPERATURE_CONSIGNE + HYSTERESIS)) || (l_iHygrometrie>HYGROMETRIE_CONSIGNE + HYSTERESIS && l_iHygrometrie<99)));
      }
    break;
    
  }
  
}

// Dertermine si la porte doit être fermée en fonction de
// - mode manuel/auto
// - la position du capteur bas
// - la température de la serre
boolean DoitEtreFermee()
{
  // Réccupère l'information sur le capteur bas
  G_debouncerFCBas.update();
  boolean l_bCapteurBasValue = G_debouncerFCBas.read();
  
  boolean G_BoutonBasValue = HIGH;
  
  // En fonction du mode
  switch(MODE)
  { 
    // Manuel
    case 0:
      //Bouton bas
      G_debouncerBoutonBas.update();
      G_BoutonBasValue = G_debouncerBoutonBas.read();

      // VRAI si bouton bas appuyé et capteur bas non touché, FAUX sinon
      return (G_BoutonBasValue==LOW && l_bCapteurBasValue==LOW);
    break;
    
    // Automatique
    case 1:
      // Réccupère la température actuelle
      float l_fTemperature = getTemp();
      // Renvoie la porte n'est pas en bas ET la température de la serre est inférieur à la température de consigne - Hysteresis
      return (l_bCapteurBasValue==LOW && (l_fTemperature < TEMPERATURE_CONSIGNE-HYSTERESIS));
    break;
  }
}

// Ferme la porte : envoi un signal créneau au moteur (PWM) tant que la porte n'est pas fermée et que le moteur ne force pas
int Ferme()
{
  // Réccupère les valeurs capteur haut
  G_debouncerFCBas.update();
  boolean l_bCapteurBasValue = G_debouncerFCBas.read();
 ou log dans un fichier ArduinoLog.txt sur la carte SD
  // Réccupère la valeur de si le moteur force
  boolean l_bMoteurForce = leMoteurForce();

  //Bouton bas
  G_debouncerBoutonBas.update();
  boolean l_bBoutonBasValue = G_debouncerBoutonBas.read();

  // Lance l'alim et attend qu'elle soit stabilisée
  AllumeAlimentation();
 
  // Lance le moteur dans le bon sens -400 correspond à la vitesse maximum
  G_Moteur.setM1Speed(-400);
  TIME = millis();
  
  // TANT QUE la porte n'est pas fermée et que le moteur ne force pas et que (soit le mode auto soit mode manuel et bouton bas appuyé)
  while (l_bCapteurBasValue==LOW  && !l_bMoteurForce  && ((MODE==0 && l_bBoutonBasValue==LOW) || MODE ==1))
  {

    // Ré-actualise les valeurs des capteurs
    G_debouncerFCBas.update();
    l_bCapteurBasValue = G_debouncerFCBas.read();
    
    l_bMoteurForce = leMoteurForce();

    G_debouncerBoutonBas.update();
    l_bBoutonBasValue = G_debouncerBoutonBas.read();
  }

  // La porte est fermée OU le moteur force => On arrête le moteur
  G_Moteur.setM1Speed(0);

  // Eteint l'alimentation
  digitalWrite(PIN_POWER_ON, HIGH);
  
  // Retourne la valeur du fait que le moteur force
  return l_bMoteurForce;
}

// Ouvre la porte : envoi un signal créneau au moteur (PWM) tant que la porte n'est pas ouverte et que le moteur ne force pas
int Ouvre()
{
  // Réccupère la valeur capteur haut
  G_debouncerFCHaut.update();
  boolean l_bCapteurHautValue = G_debouncerFCHaut.read();

  // Si le moteur force
  boolean l_bMoteurForce = leMoteurForce();

  //Bouton haut
  G_debouncerBoutonHaut.update();
  boolean l_bBoutonHautValue = G_debouncerBoutonHaut.read();
  
  // Lance l'alim et attend qu'elle soit stabilisée
  AllumeAlimentation();
  
  // Lance le moteur dans le bon sens 400 correspond à la vitesse maximum
  G_Moteur.setM1Speed(400);
  TIME = millis();
  
  // TANT QUE la porte n'est pas fermée et que le moteur ne force pas et que (soit le mode auto soit mode manuel et bouton haut appuyé)
  while (l_bCapteurHautValue==LOW  && !l_bMoteurForce && ((MODE==0 && l_bBoutonHautValue==LOW) || MODE ==1))
  {
    // Ré-actualise les valeurs des points de contrôle
    G_debouncerFCHaut.update();
    l_bCapteurHautValue = G_debouncerFCHaut.read();
    
    l_bMoteurForce = leMoteurForce();
    
    G_debouncerBoutonHaut.update();
    l_bBoutonHautValue = G_debouncerBoutonHaut.read();
  }

  // Eteint l'alimentation
  digitalWrite(PIN_POWER_ON, HIGH);
  
  // La porte est ouverte OU le moteur force => On arrête le moteur
  G_Moteur.setM1Speed(0);

  // Retourne la valeur du fait que le moteur force
  return l_bMoteurForce;
}

// Retourne true si le moteur force, false sinon.
// Objectif : en fonction du courant qui traverse le moteur, déterminer s'il force ou pas
boolean leMoteurForce()
{  
  // Nous commençons à regarder si le moteur force qu'une fois sa période de démarrage passée, donc nous comparons la valeur du temps actuel à celle à laquelle le moteur a été lancé
  float l_ufTime = millis();
  while(l_ufTime<TIME+MOTEUR_TEMPS_LANCEMENT)
  {
    l_ufTime = millis();
  }

  if(G_Moteur.getM1CurrentMilliamps()>MOTEUR_COURANT_FORCE)
  {
    return true;
  }
  else
  {
    return false;
  }
}

//Système de log, qui en fonction du niveau de LOG, ne log pas, log dans le moniteur série
void logger(String s)
{
  switch(LOG)
  {
  case 1:
    Serial.println(s);
    break;
  } 
}

int detecteMode()
{
  if(digitalRead(PIN_BOUTON_MANUEL)==LOW)
  {
    logger("Mode manuel");
    MODE = 0;
  }
  else if(digitalRead(PIN_BOUTON_MANUEL)==HIGH)
  {
    logger("Mode automatique");
    MODE = 1;
  }
}
