/*--------------------------------------------------------------------------------
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
}