/*--------------------------------------------------------------------------------
Import des librairies nécessaires
--------------------------------------------------------------------------------*/
#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "DHT.h"
#include <ESP8266WebServer.h>
#include <NTPClient.h>
#include <FS.h>
#include <ESP8266mDNS.h>
#include <OneWire.h>            // https://github.com/PaulStoffregen/OneWire
#include <DallasTemperature.h>  // https://github.com/milesburton/Arduino-Temperature-Control-Library


/*--------------------------------------------------------------------------------
Définition des identifiants réseau
--------------------------------------------------------------------------------*/
const char* ssid = "XXXX";
const char* password = "YYYY";
const char* myhostname = "piscine";

/*--------------------------------------------------------------------------------
Définition des variables
--------------------------------------------------------------------------------*/
unsigned long previousLoopMillis = 0;       // Variable pour millis
unsigned long otamillis;                    // Variable qui va permettre de calculer le temps d'exécution d'OTA
unsigned long utcOffsetInSeconds = 0;       // Variable pour le NTP

// pour le tableau à 2 dimensions
const int nombredemesure = 60;
const int typedemesure = 3;
// le numero de mesure
int numerodemesure = 0; 

// on initialise le tableau
float sonde[nombredemesure][typedemesure];           
// les variables pour le calcul de la moyenne
float totaltemp = 0;                  
float moytemp = 0; 
float totalhumi = 0;                  
float moyhumi = 0; 
float totaltempPiscine = 0;                  
float moytempPiscine = 0; 
//les variables pour les sondes
float Temperature = 0;
float Humidite = 0;
float tempPiscine = 0;
// les variables pour la manipulation des fichiers
int flag = 0;
String fileTxt;
String fileTxt2;

// les variables pour changer le delai entre 2 mesures
/* 
 *  si vous passez en mode minute, il faut également modifier les temps dans les 2 setInterval(function() 
 *  du fichier index.html
 */
const long DELAY = 1000;         // mode seconde
//const long DELAY = 60000;      // mode minute  

/*--------------------------------------------------------------------------------
Définitions du client NTP pour récupérer les informations date et heures
--------------------------------------------------------------------------------*/
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org",utcOffsetInSeconds);

/*--------------------------------------------------------------------------------
Définitions de la sonde DHT, DS18B20 et du serveur web
--------------------------------------------------------------------------------*/
#define DHTTYPE DHT22  
const uint8_t DHTPin = 4;          // D2 --> GPIO4
DHT dht(DHTPin, DHTTYPE); 

#define ONE_WIRE_BUS 2             // D4 --> GPIO2 
OneWire oneWire(ONE_WIRE_BUS); 
DallasTemperature sensors(&oneWire);
DeviceAddress piscine;

ESP8266WebServer server;           //Définition de l'objet webserver

/*--------------------------------------------------------------------------------
Fonction de programmation OTA (ouvre un port réseau pour flasher le nodemcu à distance)
--------------------------------------------------------------------------------*/
void confOTA() {
  ArduinoOTA.setPort(8266);
  ArduinoOTA.setHostname(myhostname);  
  ArduinoOTA.begin();
}

/*--------------------------------------------------------------------------------
Fonction qui lit la sonde DS18B20
--------------------------------------------------------------------------------*/
void printTemperature(DeviceAddress deviceAddress)
{
  tempPiscine = sensors.getTempC(deviceAddress);
}

/*--------------------------------------------------------------------------------
Fonction qui calcule l'espace disponible et prépare le flag pour manipuler le fichier de sortie temperature.csv
--------------------------------------------------------------------------------*/
void espace_dispo() {
  String str = "";                                  
  long int total = 0;
  Dir dir = SPIFFS.openDir("/");                    // on liste les fichiers présent en mémoire 
  while (dir.next()) {
      str += dir.fileName();                          
      str += " / ";
      str += dir.fileSize();
      total = total + dir.fileSize();
      str += "\n";
  }
  Serial.println(str);                              // on les affiches avec leurs taille      
  Serial.print("total : ");                         // on affiche également le total / taille disponible
  Serial.print(total);
  FSInfo fs_info;
  SPIFFS.info(fs_info);
  Serial.print(" / ");
  Serial.println(fs_info.totalBytes);                         
   
  long int limite = fs_info.totalBytes - 1000;                      // on creer une limite (taille max alouée - 1000)
  if (total < limite/2)                        flag = 0;            // quand la taille occupée est inférieur a la moitier de la mémoire disponible le flag vaut 0
  else if (total > limite/2 && total < limite) flag = 1;            // quand la taille occupée est comprise entre la moitier de la mémoire disponible et la limite le flag vaut 1
  else if (total > limite)                     flag = 2;            // quand on dépasse la limite de la mémoire disponible le flag vaut 2
}

/*--------------------------------------------------------------------------------
Fonction qui lit les sondes et ajoute les datas au fichier csv présent dans la mémoire SPIFFS
--------------------------------------------------------------------------------*/
void addData() {  
  sensors.requestTemperatures();                  // on demande la lecture de la sonde DS18B20
  printTemperature(piscine);                      // on stock la temp dans la variable tempPiscine
  
  Temperature = dht.readTemperature(); // on lit la sonde dht et on stock la temp dans la variable Temperature
  Humidite = dht.readHumidity();       // on lit la sonde dht et on stock l' humi dans la variable Humidite
  
  // on enlève le dernier relevé du tableau
  totaltemp = totaltemp - sonde[numerodemesure][0];
  totalhumi = totalhumi - sonde[numerodemesure][1];
  totaltempPiscine = totaltempPiscine - sonde[numerodemesure][2];
  
   // on affecte les datas au tableau
  sonde[numerodemesure][0] = Temperature;  
  sonde[numerodemesure][1] = Humidite;
  sonde[numerodemesure][2] = tempPiscine; 

  // uniquement pour debug: affichage des données   
  Serial.print("relevé ");
  Serial.print(numerodemesure);
  Serial.print(" --> temperature : "); 
  Serial.print(sonde[numerodemesure][0]);
  Serial.print(" °C, humidite : "); 
  Serial.print(sonde[numerodemesure][1]);
  Serial.print(" %, "); 
  Serial.print("temperature de l'eau : "); 
  Serial.print(sonde[numerodemesure][2]);
  Serial.println(" °C");
  
  
  // on additionne le relevé au total  
  totaltemp = totaltemp + sonde[numerodemesure][0];
  totalhumi = totalhumi + sonde[numerodemesure][1];  
  totaltempPiscine = totaltempPiscine + sonde[numerodemesure][2];
  
  // on avance d'une ligne dans le tableau
  numerodemesure = numerodemesure + 1;

  // si on arrive à la fin du tableau
  if (numerodemesure >= nombredemesure) {    
    // calcul des moyennes     
    moytemp = totaltemp / nombredemesure;
    moyhumi = totalhumi / nombredemesure;    
    moytempPiscine = totaltempPiscine / nombredemesure;
    
    // uniquement pour debug: on écrit le résultat de la moyenne dans le port série    
    Serial.print("Moyenne Sondes --> temperature : "); 
    Serial.print(moytemp);
    Serial.print(" °C, humidite : "); 
    Serial.print(moyhumi);
    Serial.print(" %, "); 
    Serial.print("temperature de l'eau : "); 
    Serial.print(moytempPiscine);
    Serial.println(" °C"); 
    
    
    // on se replace au début du tableau
    numerodemesure = 0;
    // on récupère le temps en timestamp
    long timestamp = timeClient.getEpochTime(); 
    // on crée un buffer vide
    char strbuffer[128];
    // on formatte en string nos datas que l'on met dans notre buffer
    snprintf(strbuffer, 128, "%d,%.2f,%.2f,%.2f;\n", timestamp, moytemp, moyhumi, moytempPiscine);
    // uniquement pour debug: on écrit le buffer dans le port série
    Serial.println("le buffer envoyé est :");
    Serial.println(strbuffer);
     /* 
     *  le graphique se crée avec le fichier temperature.csv.
     *  nous on écrit pas directement nos datas dedans, on va couper la mémoire en deux (le flag a la fin de la fonction: espace_dispo().
     *  et on écrit nos datas dans 2 fichiers data.csv et data2.csv selon ou on se trouve dans la taille de la memoire.
     *  et ensuite on crée le fichier temperature.csv en y collant le contenu des fichiers précédents.
     *  ainsi quand on arrive au bout de la mémoire, on peut supprimer la moitier la plus ancienne.
     *  ce qui nous permet au bout d'un mois de collecte d'avoir toujour le mois précédent en archive.
     *  et la mémoire ne sera jamais saturé.
     */     
    if (flag == 0) {                                    // si le flag est a 0
    
      File f = SPIFFS.open("/data.csv", "a+");            // on ouvre le fichier data.csv en mode ecriture (à la suite)
      if (!f) {
        Serial.println("erreur ouverture fichier!");
      } else {
          f.print(strbuffer);                             // on y ajoute le buffer
          f.close();                                      // on ferme le fichier   
      }     
      
      File g = SPIFFS.open("/data.csv", "r");             // on ouvre le fichier data.csv en mode lecture
      if (!g) {
        Serial.println("erreur ouverture fichier!");
      } else {
          fileTxt = g.readString();                       // on stock les datas dans la variable fileTxt
          g.close();                                      // on ferme le fichier   
      }    
  
      File h = SPIFFS.open("/temperature.csv", "w+");     // on ouvre le fichier temperature.csv en mode ecriture (écrasement)
      if (!h) {
        Serial.println("erreur ouverture fichier!");
      } else {
          h.print(fileTxt);                               // on y ajoute le contenu de la variable fileTxt
          h.close();                                      // on ferme le fichier   
      }      
    }
    else if (flag == 1) {                                 // si le flag est a 1
      
      File f = SPIFFS.open("/data2.csv", "a+");           // on ouvre le fichier data2.csv en mode ecriture (à la suite)
      if (!f) {
        Serial.println("erreur ouverture fichier!");
      } else {
          f.print(strbuffer);                             // on y ajoute le buffer
          f.close();                                      // on ferme le fichier   
      }    
  
      File g = SPIFFS.open("/data2.csv", "r");            // on ouvre le fichier data2.csv en mode lecture
      if (!g) {
        Serial.println("erreur ouverture fichier!");
      } else {
          fileTxt2 = g.readString();                      // on stock les datas dans la variable fileTxt2
          g.close();                                      // on ferme le fichier   
      }    
      
      File h = SPIFFS.open("/temperature.csv", "w+");     // on ouvre le fichier temperature.csv en mode ecriture (écrasement)
      if (!h) {
        Serial.println("erreur ouverture fichier!");      
      } else {
          h.print(fileTxt);                               // on y ajoute le contenu de la variable fileTxt
          h.print(fileTxt2);                              // on y ajoute le contenu de la variable fileTxt2
          h.close();                                      // on ferme le fichier   
      }    
      
    }
    
    else if (flag == 2) {                                 // si le flag est a 2
      SPIFFS.remove("/data.csv");                         // on supprime le fichier data.csv
      SPIFFS.rename("/data2.csv", "/data.csv");           // on renomme le fichier data2.csv en data.csv
      
      File g = SPIFFS.open("/data.csv", "r");             // on ouvre le fichier data.csv en mode lecture
      if (!g) {
        Serial.println("erreur ouverture fichier!");
      } else {
          fileTxt = g.readString();                       // on stock les datas dans la variable fileTxt
          g.close();                                      // on ferme le fichier   
      } 
  
      File h = SPIFFS.open("/temperature.csv", "w+");     // on ouvre le fichier temperature.csv en mode ecriture (écrasement)
      if (!h) {
        Serial.println("erreur ouverture fichier!");
      } else {
          h.print(fileTxt);                               // on y ajoute le contenu de la variable fileTxt
          h.close();                                      // on ferme le fichier 
      }    
    }
    espace_dispo();                                       // on calcule l'espace mémoire restant
  }
}

/*--------------------------------------------------------------------------------
Fonction qui envoie les datas au serveur web (pour les jauges)
--------------------------------------------------------------------------------*/
void handleData(){
  char datas[64];
  snprintf(datas, 64, "%.2f,%.2f,%.2f", Temperature, Humidite, tempPiscine);
  server.send(200, "text/html",datas);
}

/*--------------------------------------------------------------------------------
Fonction qui écrit l'adresse sur le bus de la sonde DS18B20
--------------------------------------------------------------------------------*/
void printAddress(DeviceAddress deviceAddress)
{
  for (uint8_t i = 0; i < 8; i++)
  {
    if (deviceAddress[i] < 16) Serial.print("0");
    Serial.print(deviceAddress[i], HEX);
  }
}

/*--------------------------------------------------------------------------------
SETUP
--------------------------------------------------------------------------------*/
void setup()
{
  delay(4000);  
  Serial.begin(115200);
  Serial.println();
  pinMode(DHTPin, INPUT); 
  dht.begin();
  sensors.begin();  
  Serial.print(sensors.getDeviceCount(), DEC);
  Serial.println(" sonde présente");  
  Serial.print("le mode Parasite est : ");   
  Serial.println(sensors.isParasitePowerMode() ? "ON" : "OFF");
  if (!sensors.getAddress(piscine, 0)) Serial.println("Device 0 introuvable");
  Serial.print("Addresse du device 0 : ");
  printAddress(piscine);
  Serial.println();
  sensors.setResolution(piscine, 9); // 9, 10, 11, or 12 bits (12 -> haute resolution)
  Serial.print("Resolution du device 0: ");
  Serial.println(sensors.getResolution(piscine), DEC);   
  Serial.println();
  // Connexion au Wifi
  Serial.println("Connexion au Wifi");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\r\nWiFi connecté");
  Serial.print("Mon adresse IP: ");
  Serial.println(WiFi.localIP());
  if (!MDNS.begin(myhostname)) {
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }  
  if(!SPIFFS.begin()) {
    Serial.println("Erreur initialisation SPIFFS");
  }
  //Initialisation de la connexion avec le serveur NTP
  timeClient.begin();
  // configuration OTA
  confOTA();
  // la page appelée par les jauges
  server.on("/getData",handleData);
  // on crée le lien avec les pages en mémoire SPIFFS
  server.serveStatic("/", SPIFFS, "/index.html");
  server.serveStatic("/index.html", SPIFFS, "/index.html");
  server.serveStatic("/temperature.csv", SPIFFS, "/temperature.csv");
  server.serveStatic("/RGraph.common.core.js", SPIFFS, "/RGraph.common.core.js");
  server.serveStatic("/RGraph.common.dynamic.js", SPIFFS, "/RGraph.common.dynamic.js");  
  server.serveStatic("/RGraph.gauge.js", SPIFFS, "/RGraph.gauge.js");
  server.serveStatic("/index.css", SPIFFS, "/index.css");
  // on démarre le serveur
  server.begin();
  // on calcule l'espace mémoire SPIFFS disponoble
  espace_dispo();   
  // on initialise le tableau, on le rempli de 0.
  for (int mesure = 0; mesure < nombredemesure; mesure++) {
    sonde[mesure][typedemesure] = 0;
  }    
  // on incrémente le tableau 
  addData();        
}

/*--------------------------------------------------------------------------------
LOOP
--------------------------------------------------------------------------------*/
void loop(){
  // toutes les minutes
  unsigned long currentLoopMillis = millis();                    // currentLoopMillis vaut le temps écoulé depuis le début du programme en ms  
  if(currentLoopMillis - previousLoopMillis >= DELAY){           // toutes les secondes ou minutes selon config
    previousLoopMillis = millis();                               // on remet le compteur "a zero" 
    addData();                                                   // on incrémente le tableau puis le fichier csv au bout d'une heure       
  }
  // en boucle
  ArduinoOTA.handle();                                           // Appel de la fonction qui gère OTA
  server.handleClient();                                         // Appel de la fonction qui gère la communication avec le serveur web
  timeClient.update();                                           // Récupération du timestamp en temps réel
}