// les librairies utilisées 

#include <TimeLib.h>                   // pour le serveur NTP
#include <ESP8266WiFi.h>               // pour le réseau
#include <WiFiUdp.h>                   // pour interroger le serveur NTP
#include "DHT.h"                       // pour lire les sondes  
#include <Wire.h>                      // pour l'I2C 
#include <LiquidCrystal_I2C.h>         // pour l'écran lcd
#include <WiFiClient.h>                // pour se connecter à un serveur
#include <ESP8266WebServer.h>          // crée un serveur
#include <ESP8266mDNS.h>               // pour les DNS
#include <ESP8266HTTPUpdateServer.h>   // pour flasher le nodemcu par le web 

LiquidCrystal_I2C lcd(0x3F, 16, 2);    // paramétrage de l'écran : adresse I2C de lécran (pour la trouver utiliser scanI2C.ino (voir tuto)) et 16 colonnes, 2 lignes

// pin des relais
#define lum 5               
#define chauff 16

// Pin des sondes

#define DHTPINPC 14
#define DHTPINPF 0

// défini le capteur DHT22 
#define DHTTYPE DHT22

// Initialise les capteurs
DHT dhtPC(DHTPINPC, DHTTYPE);
DHT dhtPF(DHTPINPF, DHTTYPE);

              ///////partie à modifier avec vos paramètres \\\\\\\
// le Host 
const char* host = "192.168.0.2";  // adresse du serveur web (NAS synology chez moi)

//pour la page web 
const char* hostU = "webupdate";
const char* update_path = "/firmware";
const char* update_username = "XXXX";  // on defini un login pour la fonction update
const char* update_password = "YYYY"; // votre mot de passe

//le wifi
const char ssid[] = "XXXX";  //  votre SSID
const char pass[] = "YYYY";       // votre password

             //////////////////////////////////////////////////////

// on initialise le seveur web
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

// le serveur NTP que l'on va interroger:
static const char ntpServerName[] = "fr.pool.ntp.org";

const int timeZone = 1;     // paris - bruxelle

//On défini le bouton
const int  buttonPin = 12;    

int buttonState = 0;         // état du bouton est a 0
int lastButtonState = 0;     // l'ancien état est a 0

//On déclare les variables pour gérer la boucle de chaque fonction du loop avec millis()
long tempsterra;
long tempsenvoi;
long tempscsv;

WiFiUDP Udp;
unsigned int localPort = 8888;  // Le port d'Écoute des paquets UDP


void setup()
{
  // On démarre la com série 
  Serial.begin(115200);  
  delay(100);
  // initialise le GPIO du bouton en input PULLUP: 
  pinMode(buttonPin, INPUT_PULLUP);
  // initialise les GPIO pour l'I2C
   Wire.begin(2,4);
  // initialise le LCD
  lcd.begin();  
  // éteint le retro-éclairage et efface l'écran
  lcd.noBacklight();
  lcd.clear();
  // on discute un peu sur le port série
  Serial.println("Terrarium");  
  Serial.print("Connexion à ");
  Serial.println(ssid);
  // on se connecte au réseau
  WiFi.begin(ssid, pass);  
  // on attend la connexion :
  while (WiFi.status() != WL_CONNECTED) { 
    delay(500);
    Serial.print(".");
  }
  // on affiche l'IP attribué
  Serial.print("L'adresse IP assignée par DHCP est : ");
  Serial.println(WiFi.localIP());
  // on défini la page web
  MDNS.begin(hostU);
  httpUpdater.setup(&httpServer, update_path, update_username, update_password);
  httpServer.begin();
  MDNS.addService("http", "tcp", 80);
  Serial.printf("HTTPUpdateServer prêt !!! Ouvre http://");
  Serial.print(WiFi.localIP());
  Serial.printf("/update dans ton navigateur\n", hostU);
    // on initialise les DHT 
  dhtPC.begin();
  dhtPF.begin();
   // on déclare les pins des relais en sortie
  pinMode(lum, OUTPUT);
  pinMode(chauff, OUTPUT);
  // on discute encore un peu 
  Serial.println("Lancement de l'UDP");
  // on démarre l'écoute UDP
  Udp.begin(localPort);
  Serial.print("Port local: ");
  Serial.println(Udp.localPort());
  Serial.println("attente de synchronisation");
  // on récupère l'heure
  setSyncProvider(getNtpTime);
  setSyncInterval(300);
  // on  donne le temps actuel aux variable pour le démarrage du timer des fonctions
  tempsterra = millis();
  tempsenvoi =  millis();
  tempscsv = millis();
  
}


 //******   on lance les fonctions en boucle ******

 void loop() {
  httpServer.handleClient();
  terrarium();       
  envoidata();
  bouton();
}


//******  la fonction qui s'occupe du chauffage et de la lumière ******
void terrarium() {

  if((millis() - tempsterra) > 5000) {  // si le temps actuel par rapport au temps de démarrage du timer est > 10 s ( toutes les 10 s )

     // On déclare les variables
    int Hnow;
    int target;
    int Hmatin = 730;           // à modifier selon vos souhaits.
    int Hsoir = 1730;           // à modifier selon vos souhaits.
    
    Hnow = hour() * 100 + minute(); // pour faciliter les calcul (21h03 devient 2103) 
      
    if ( Hnow > Hmatin && Hnow < Hsoir ) {  // si c'est le jour
    target = 28;                            // la consigne du chauffage est de 28 °C   
    digitalWrite(lum, LOW);                 // on active pas le relais et le courant passe donc la lumière est allumée
    } else {                                // sinon , donc c'est la nuit
      target = 23;                          // la consigne du chauffage est de 23 °C
      digitalWrite(lum, HIGH);              // on active le relais, il coupe le courant, la lumière est éteinte
       }                 
     
          // lis les sondes (température uniquement)
    float tC = dhtPC.readTemperature(); // (point chaud)
      
    float tF = dhtPF.readTemperature(); // (point froid) 
    
    if (target < tC) {        // si la température au point chaud dépasse la target (28 le jour ou 23 la nuit) et qu'au point froid il fait plus de 23 °C (pour l'été).
      digitalWrite(chauff, HIGH);         // on active le relais qui éteint le chauffage.
      } else {                            // sinon
        digitalWrite(chauff, LOW);        // on allume le chauffage.
        }
        
    tempsterra = millis();                // on réinitialise le timer 
  }  
   
}

//******  envoi des datas à la base de donnée ******

void envoidata() {

  if((millis() - tempsenvoi) > 60000) {   // si le temps actuel par rapport au temps de démarrage du timer est > 15 min ( toutes les 15 min )

    Serial.print("Connecting to ");         // on discute un peu sur le port série
    Serial.println(host);
    Serial.println("pour envoidata");
    
    // création de la connexion TCP
    WiFiClient client;
    const int httpPort = 80;
    if (!client.connect(host, httpPort)) {
      Serial.println("connection failed");
      return;
    }
    
    // lire la sonde point chaud 
    float hC = dhtPC.readHumidity();  
    float tC = dhtPC.readTemperature();
      // lire la sonde point froid
    float hF = dhtPF.readHumidity();  
    float tF = dhtPF.readTemperature();
    
    // envoi les datas par l'URL par GET au fichier dht22bdd.php sur le serveur (dans le dossier terranodemcu)
    client.print(String("GET /terranodemcu/dht22.php?tempC=") + String(float (tC)) + "&humiC=" + String(float(hC)) + "&tempF=" + String(float(tF)) + "&humiF=" + String(float(hF)) + " HTTP/1.1\r\n" +
                 "Host: " + host + "\r\n" + 
                 "Connection: close\r\n\r\n");
        
    // Lire toutes les lignes de la réponse du serveur, les écrire et fermer la connexion.
    while(client.available()){
      String line = client.readStringUntil('\r');
      Serial.print(line);
    }
      
    Serial.println();
    Serial.println("closing connection");    

    tempsenvoi = millis();  // on réinitialise le timer 
    }  
    
}


//****** le bouton ******

void bouton() {
  // lit l'état du bouton 
  buttonState = digitalRead(buttonPin);

  // compare avec l'état précédant
  if (buttonState != lastButtonState) {
    // si l'etat du bouton est a 1 (bouton relâché) on ecrit off dans la com serie et rien d 'autre
    if (buttonState == HIGH) {
      Serial.println("off");
    } else {
      // si l'état du bouton est a 0 (bouton enclenché) on écrit on dans la com série et lance la fonction d'affichage      
      Serial.println("on");
      affichage();
      delay(1000);
    }
    // Delay pour la sensibilité du bouton 
    delay(50);
  }
  // sauve l'état du bouton pour la réinitialiser la boucle  
  lastButtonState = buttonState;

}

//****** animation LCD avant l'affichage des datas ******

void intro() {

// on définie les variables où seront stockés les caractères perso TAIL HEAD CLEAR 

  const int TAIL = 4;            
  const int HEAD = 1;              
  const int CLEAR = 2;              
 

//le dessin des caractères HEAD TAIL CLEAR

  byte snakeHead[8] = {     // head
     B10001,
     B01010,
     B11111,
     B10101,
     B11111,
     B10101,
     B10001,
     B01110
  };

  byte snakeTail[8] = {    // tail
     B00100,
     B01110,
     B01110,
     B11011,
     B11011,
     B01110,
     B01110,
     B00100
  };

byte noSnake[8] = {        // Clear 
     B00000,
     B00000,
     B00000,
     B00000,
     B00000,
     B00000,
     B00000,
     B00000
  };

// création des caractères

  lcd.createChar(HEAD, snakeHead);
  lcd.createChar(TAIL, snakeTail);
  lcd.createChar(CLEAR, noSnake);

// l'animation en elle même.

       
   lcd.setCursor(0,1);
   lcd.write(HEAD);   
   delay(250);  
       
   lcd.setCursor(0,1);
   lcd.write(TAIL);
   lcd.setCursor(1,1);
   lcd.write(HEAD);
   delay(250);
       
   lcd.setCursor(1,1);
   lcd.write(TAIL);
   lcd.setCursor(2,1);
   lcd.write(HEAD);
   delay(250);
     
   lcd.setCursor(2,1);
   lcd.write(TAIL);
   lcd.setCursor(3,1);
   lcd.write(HEAD);
   delay(250);
     
   lcd.setCursor(3,1);
   lcd.write(TAIL);
   lcd.setCursor(4,1);
   lcd.write(HEAD);
   delay(250);
      
   lcd.setCursor(4,1);
   lcd.write(TAIL);
   lcd.setCursor(5,1);
   lcd.write(HEAD);
   delay(250);
       
   lcd.setCursor(5,1);
   lcd.write(TAIL);
   lcd.setCursor(6,1);
   lcd.write(HEAD);
   delay(250);
       
   lcd.setCursor(0,1);
   lcd.write(CLEAR);
   lcd.setCursor(6,1);
   lcd.write(TAIL);
   lcd.setCursor(7,0);
   lcd.write(HEAD);
   delay(250);
      
   lcd.setCursor(0,0);
   lcd.print("Terra");
   lcd.setCursor(1,1);
   lcd.write(CLEAR);
   lcd.setCursor(7,0);
   lcd.write(TAIL);
   lcd.setCursor(8,0);
   lcd.write(HEAD);
   delay(250);
   
   lcd.setCursor(2,1);
   lcd.write(CLEAR);
   lcd.setCursor(8,0);
   lcd.write(TAIL);
   lcd.setCursor(9,0);
   lcd.write(HEAD);
   delay(250);
   
   lcd.setCursor(3,1);
   lcd.write(CLEAR);
   lcd.setCursor(9,0);
   lcd.write(TAIL);
   lcd.setCursor(10,0);
   lcd.write(HEAD);
   delay(250);
   
   lcd.setCursor(4,1);
   lcd.write(CLEAR);
   lcd.setCursor(10,0);
   lcd.write(TAIL);
   lcd.setCursor(11,0);
   lcd.write(HEAD);
   delay(250);
   
   lcd.setCursor(5,1);
   lcd.write(CLEAR);
   lcd.setCursor(11,0);
   lcd.write(TAIL);
   lcd.setCursor(12,0);
   lcd.write(HEAD);
   delay(250);
   
   lcd.setCursor(6,1);
   lcd.write(CLEAR);
   lcd.setCursor(12,0);
   lcd.write(TAIL);
   lcd.setCursor(13,0);
   lcd.write(HEAD);
   delay(250);

   lcd.setCursor(7,1);
   lcd.print("NodeMcu");
   lcd.setCursor(7,0);
   lcd.write(CLEAR);
   lcd.setCursor(13,0);
   lcd.write(TAIL);
   lcd.setCursor(14,0);
   lcd.write(HEAD);
   delay(250);
   
   lcd.setCursor(8,0);
   lcd.write(CLEAR);
   lcd.setCursor(14,0);
   lcd.write(TAIL);
   lcd.setCursor(15,0);
   lcd.write(HEAD);
   delay(250); 
   
   lcd.setCursor(8,0);
   lcd.write(CLEAR);
   lcd.setCursor(15,0);
   lcd.write(TAIL);
   delay(250);
   
   lcd.setCursor(9,0);
   lcd.write(CLEAR);
   delay(250);
   
   lcd.setCursor(10,0);
   lcd.write(CLEAR);
   delay(250);
   
   lcd.setCursor(11,0);
   lcd.write(CLEAR);
   delay(250);
   
   lcd.setCursor(12,0);
   lcd.write(CLEAR);
   delay(250);
   
   lcd.setCursor(13,0);
   lcd.write(CLEAR);
   delay(250);
   
   lcd.setCursor(14,0);
   lcd.write(CLEAR);
   delay(250);
   
   lcd.setCursor(15,0);
   lcd.write(CLEAR); 
   lcd.clear(); 
   delay(1000);

}
//****** affichage des datas des sondes en temps réel sur l'écran LCD ********

void affichage() {
      // lire la sondes point chaud
    float hC = dhtPC.readHumidity();  
    float tC = dhtPC.readTemperature();
      // lire la sonde point froid
    float hF = dhtPF.readHumidity();  
    float tF = dhtPF.readTemperature();

    // on allume le retro-éclairage
    lcd.backlight();
    delay(500);
    // on lance l'animation d'introduction
    intro();
    // puis on affiche les datas
    lcd.setCursor(3,0);
    lcd.print("Point chaud :");
    lcd.setCursor(0,1);
    lcd.print("Temp = ");
    lcd.print(float (tC));
    lcd.print(" C");
    delay(4000);    
    lcd.setCursor(0,1);
    lcd.print("Humi = ");
    lcd.print(float (hC));
    lcd.print(" %");
    delay(4000);
    lcd.clear();
    
    lcd.setCursor(3,0);
    lcd.print("Point froid :");
    lcd.setCursor(0,1);
    lcd.print("Temp = ");
    lcd.print(float (tF));
    lcd.print(" C");
    delay(4000);        
    lcd.setCursor(0,1);
    lcd.print("Humi = ");
    lcd.print(float (hF));
    lcd.print(" %");
    delay(4000);
    lcd.clear(); 
    delay(1000);

    outro(); 
        
}

//****** sortie de l'affichage  ******

void outro () {
      
    delay(1000);  
    lcd.setCursor(2,0);
    lcd.print("Bye bye !!!");
    lcd.setCursor(1,1);
    lcd.print("Terra-NodeMcu");
    delay(3000); 
    lcd.clear(); 
    lcd.noBacklight();

 }



//****** la partie suivante n'est pas de moi , c'est l'exemple TimeNTP_ESP8266WIFI de la librairie Time. ( pour avoir l'heure par le réseau) ******

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

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