![]() |
Suivi du niveau |
avril 2025 |
Connaitre le niveau de son réservoir d'eau de pluie n'est certes pas indispensable,
sachant que l'on ne peut pas agir sur son remplissage ! Par contre on peut réguler la vitesse à laquelle on va le vider.
On peut également envisager de prévoir une augmentation de la quantité d'eau stockée en fonction de la vitesse de remplissage (Achat de nouvelles citernes.)
de la réserve actuelle. Ces quelques remarques et le fait que j'aime bien concevoir des petits projets m'ont conduit à me lancer
dans ce développement. Je vous partage ici tous les éléments de ce petit projet.
Je vous présente ici un plan B qui fait suite au dév à base
d'un capteur ultrasonique présenté "ici". Ce projet est plus complexe, il comporte
plus de composant, son prix de revient est donc plus important mais les mesures sont plus fiables dans diverses types de cuve et pour une profondeur
pouvant atteindre jusqu'à 5 mètres.
Si vous avez réalisé "le projet de base" avec le capteur ultrasonique, il n'y a que la partie électronique et logiciel à revoir, l'impression 3D étant exactement identique.
Vous pouvez également trouver dans d'autres page du site "Bricolages" comment réparer une cuve en PE qui est percée mais aussi "l'impression en 3D" d'un système de capteur d'eau à installer sur une gouttière avec un gros débit.
Capteur de niveau de liquide ALS-MPM-2F DC24V 4-20mA, profondeur de gamme 0-5m
Entre 20 et 25 euros chez Ali et + ou - 50 euros chez AmaZ mais c'est plus rapide !
Mesure de la distance entre le fond de la cuve et la surface. Le système sera autonome, alimenté par deux batteries de 3v7 rechargées par un capteur solaire. Afin de limiter au maximum la consommation, le processeur sera passé en mode Deep Sleep et ne se réveillera qu'une fois toutes les heures. La sonde ainsi que tous les éléments liés aux mesures ne seront alimentés que lorsque le processeur sera éveillé et sera coupé dés que les mesures seront effectuées à l'aide d'un MOSFET commandé par le processeur. Le montage ne sera disponible par wifi que toutes les heures pendant cinq minutes toujours dans l'idée d'économiser la batterie.
Toutes les données des mesures effectuées toutes les heures seront sauvegardées dans des tableaux qui seront stockés dans la mémoire EEPROM du processeur de façon à assurer leur sauvegarde pendant les périodes de deep sleep.
L'horloge est mise à jour par un serveur internet régulièrement.
Le logiciel peut s'adapter à plusieurs type de configuration de cuve pourvu que le diamètre sur la hauteur soit stable
le système calcul un nombre de litre au cm de hauteur donc si le diamètre n'est pas égal en haut qu'en bas le calcul sera faussé !
Il faut aussi prendre en compte que toutes les valeurs de remplissage sont des taux donc exprimés en pourcentage.
L'adresse pour se connecter au système est fixe : 192.168.1.88 (vous pouvez la modifier dans le code) par contre il ne faut pas oublier que la connexion n'est possible qu'a heure pleine et fixe pendant seulement 5 minutes (ou il faut aussi modifier le code).
Plusieurs solutions matériels ont été testées au fil des années. Ce plan B semble enfin être totalement fiable et malgrés sont prix de revient plus important je vous le conseil !
Après avoir saisie l'adresse dans un navigateur le menu principal suivant apparait. Il est possible ici de configurer le type de cuve et un aperçu des dernières mesures est visible (seul graphique en litre). Ici un petit indicateur permet de voir l'etat de la batterie.
Graphique de suivi des 72 dernieres heures en %.
Graphique de suivi des 31 derniers jours en %.
Graphique des 12 derniers mois en %.
Affichage des mesures sauvegardées dans L'EEPROM du processeur.
d'abord les valeurs des 72 dernières heures puis celles des 31 derniers jour puis celles des 12 derniers mois.
Fin de l'affichage des mesures (ici on voit la fin des données de l'année).
En toute fin de la liste on retrouve les dix dernières mesures stockées, celle-ci non exprimées en pourcentage.
Ce sont ces 10 dernières mesures qui apparaissent sur la page principale du site.
Vue du boitier à imprimer.
Vue du couvercle à imprimer.
Les fichiers STL pour l'impression sont disponibles dans la partie "Impression3D" du site.
Exemple de résultat de lecture de la sonde avec la tension lue sur 10b directement par l'ESP, on peut voir les écarts de lecture alors que la profondeur est stable. Cela peut être due au fait que le processeur exécute d'autres taches en même temps que la lecture, échange wifi, interface, calculs... Je vais donc éliminer rapidement cette solution !
Exemple de résultat de lecture de la sonde avec la tension lue sur l'ADS 15bits, circuit spécialisé qui n'a que la tache de traduire la conversion et de la transférer sur le réseau I2C. On voit ici que non seulement le résultat est beaucoup plus précis mais également beaucoup plus stable. Je vais donc utilisé cette solution même si elle impose un circuit supplémentaire.
Exemple de résultat de lecture de la sonde avec la tension lue sur l'ADS 15bits, cuve presque pleine (on voit que l'on a de la marge en mesure (je suis loin des 5m)).
Video de démo du mode deepsleep et de la consommation du montage. pour ces tests les durées de deepsleep on été adaptées à la démo. Le mode deepsleep n'est déclenché qu'une fois que les 5 minutes d'autorisation WiFi sont écoulées, par contre tout le montage nécessaire aux mesures (convertisseur I>T, alim 24v, ADS 15b et sonde) est lui coupé par le MOSFET des que les mesures sont terminées.
Le code de base qui fonctionne bien pour moi mais que je vous laisse adapter pour votre besoin...
Attention le SSID "ZZZZZZZ"et le passe "XXXXXX" doivent être adaptés à votre configuration internet.
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <Adafruit_ADS1X15.h>
Adafruit_ADS1115 ads;
//#define debug_sur_usb
//#define debug_temp
int MosFet = 14; // D5 est uitilisé pour alimenter le montage suite deepSleep avec un MOSFET
#define ssid "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" // SSID de la box
#define password "XXXXXXXXXXXXXXXXXXXXXXXXX" // Pass de la box
ESP8266WebServer server(80); // Port IP par defaut du serveur HTML
const uint64_t duree_sommeil = (60e6)*54; // 54 minutes (duree du sommeil entre deux mesures, ensuite le systeme reste 5 minutes acif).
const unsigned long memo_temps = (1000*60)*5; // 5 minutes d'activité (mesure + acces wifi)
unsigned long temps_qui_passe; // Mesure le delai de fonctionnement pendant lequel une cnx est possible.
unsigned long temps_inter_h; // Mesure du temps entre deux mesure du serveur heure
boolean top_mesure; // Si true on lance la mesure
struct tm * timeinfo;
// declaration structure memo memoire RTC (l'ensemble fait 4 octets) pour ne pas les perdre avec le mode deepsleep
#define debut_RTC 65 // Début de la zone de mémoire RTC (paquet de 4 octets)
#define fin_RTC 193 // debut_RTC + 512/4 Fin de la zone de mémoire RTC (paquet de 4 octets)
struct { // Mémorisation d'une mesure (enregistrement de 65 à 192)
uint8_t v_m; // mois (1 - 12)
uint8_t v_j; // jour (1 - 31)
uint8_t v_h; // heure (0 - 23)
uint8_t v_pc; // % de remplissage de la reserve (0 - 100)
} memoval;
#define debut_TJ 65 // Début tableau Jour
#define fin_TJ 137 // Début tableau Jour
struct gr_jour { // Info du graph des 72 dernieres heures (enregistrement de 65 à 137)
uint8_t v_m; // mois (1 - 12)
uint8_t v_j; // jour (1 - 31)
uint8_t v_h; // heure (0 - 23)
uint8_t v_pc; // % de remplissage de la reserve (0 - 100)
};
gr_jour tgr_jour[74];
#define debut_TM 138 // Début tableau Mois
#define fin_TM 169 // Début tableau Mois
struct gr_mois { // Info du graph des 31 derniers jours (enregistrement de 138 à 169)
uint8_t v_a; // an (20 - 99)
uint8_t v_m; // mois (1 - 12)
uint8_t v_j; // jour (1 - 31)
uint8_t v_pc; // % de remplissage de la reserve (0 - 100) (moyenne)
};
gr_mois tgr_mois[32];
struct String nom_mois[13] = {"nul", "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Decembre"};
#define debut_TA 170 // Début tableau An
#define fin_TA 182 // Fin tableau An
struct gr_an { // Info du graph des 12 derniers mois (enregistrement de 170 à 182)
uint8_t v_a; // an (20 - 99)
uint8_t v_m; // mois (1 - 12)
uint8_t v_0; // = à 0
uint8_t v_pc; // % de remplissage de la reserve (0 - 100) (moyenne)
} ;
gr_an tgr_an[13];
#define debut_TD 183 // Début tableau Data
#define fin_TD 192 // Fin tableau Data
// Variables divers (enregistrement de 183 à 192) 10x4 octets ou 10 int
struct { // Mémorisation des dernieres mesures en int (enregistrement de 183 à 192)
int v_x;
} memodiv;
int tdiv[11];
String const v_css_body = "body { background-color: grey; font-family: Sans-Serif; Color: orange; text-align: center;}";
String const v_css_lien = "a.btn { text-decoration: none; padding: 10px; font-family: arial; font-size: 1em; color: #FFFFFF; background-color: #b2a2c7; border-radius: 24px;-webkit-border-radius: 24px; -moz-border-radius: 24px; border: 4px solid #ffffff; box-shadow: 3px 3px 8px #444444; -webkit-box-shadow: 3px 3px 8px #444444; -moz-box-shadow: 3px 3px 8px #444444; }";
String const v_css_lien_vol = "a.btn:hover { padding: 10px; color: #ffff00; background-color: #5f497a; border: 4px solid #fbd5b5; box-shadow: 1px 1px 4px #777777; -webkit-box-shadow: 1px 1px 4px #777777; -moz-box-shadow: 1px 1px 4px #777777; }";
// Structure des infos sauvegardées dans l'eeprom de l'esp
struct {
int h_cuve;
int c_cuve;
char v_ok;
} eeprom_esp;
char str_mesure[15];
int mesure;
int vh_cuve; // Hauteur de la cuve
int vc_cuve; // Capacité de la cuve
void init_RTC_complet();
void charge_tableaux();
String niveau_bat; // Représentation graphique de la charge de la batterie
// -----------------------------------------------------------------------------------------------------------------------------------
// Ecriture d'une valeur en mémoire 4 octets dif
void set_val_mem(uint8_t v_adr){
system_rtc_mem_write(v_adr, &memoval, sizeof(memoval));
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Ecriture d'une valeur en mémoire 1 int de 4 octets
void set_val_div(uint8_t v_adr){
system_rtc_mem_write(v_adr, &memodiv, sizeof(memodiv));
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Lecture d'une valeur en mémoire 4 octets dif
void get_val_mem(uint8_t v_adr){
system_rtc_mem_read(v_adr, &memoval, sizeof(memoval));
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Lecture d'une valeur en mémoire 1 int de 4 octets
void get_val_div(uint8_t v_adr){
system_rtc_mem_read(v_adr, &memodiv, sizeof(memodiv));
}
// -----------------------------------------------------------------------------------------------------------------------------------
// dernieres mesures
String der_mes(){
String page = "<!DOCTYPE html><html dir='ltr' lang='fr'><head><meta http-equiv='content-type' content='text/html; charset=UTF-8'>";
page += "<title>Gestion Reserve d'eau Graph dernieres valeurs</title>";
page += "<style> " + v_css_body + v_css_lien + v_css_lien_vol + " </style>";
page += "<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>";
page += "<script type='text/javascript'>";
page += "google.charts.load('current', {'packages':['corechart']});";
page += "google.charts.setOnLoadCallback(drawChart);";
page += "function drawChart() { var data = google.visualization.arrayToDataTable([['',''],";
int vadr=0; while (vadr < 9){ page += "['H" + String(vadr+-9) + "', "; page += String((tdiv[vadr] == 1024) ? 0 : tdiv[vadr]) + "],"; vadr++; }
page += "['H', " + String((tdiv[9] == 1024) ? 0 : tdiv[9]) + "]]); var options = {title: 'Reserve eau (dernieres valeurs en litres)',";
page += "curveType: 'function',legend: { position: 'bottom' }}; ";
page += "var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));";
page += "chart.draw(data, options);}</script></head><body>";
page += "<div align='center'>";
page += "<h1>Gestion réserve d'eau (version sonde inox)</h1>";
page += "Cuve(s) d'une hauteur de " + String(vh_cuve) + " cm et d'une capacité de " + String(vc_cuve) + " litres.";
page += " Nous sommes le " + String(timeinfo->tm_mday) + "/" + String(timeinfo->tm_mon + 1) + "/" + String(timeinfo->tm_year + 1900);
page += " il est " + String(timeinfo->tm_hour) + " H " + String(timeinfo->tm_min) + " Niveau batterie : " + niveau_bat + " <br/>";
page += "<h2>Profondeur enregistrée à la derniere mesure " + String(str_mesure) + " soit " + String(int(vc_cuve/vh_cuve) * mesure) + " litres" + "</h2>";
page += "</div>";
page += "<table align='center'>";
page += "<tr><td width='60%'><form action='/mod_param' method='POST'>";
page += "<H3>PARAMETRES</H3><br/>";
page += "Hauteur maximum de la ou des cuve(s) : <input type='number' id='h_cuve' name='h_cuve' min='10' max='200' value='" + String(vh_cuve) + "'/> cm<br/><br/>";
page += "Capacité maximum de la ou des cuve(s) : <input type='number' id='c_cuve' name='c_cuve' min='10' max='10000' value='" + String(vc_cuve) + "'/> litres<br/><br/><br/>";
page += "Soit " + String(int(vc_cuve/vh_cuve)) + " litres / cm <br/><br/> Soit " + String(int(vc_cuve/100)) + " litres / %";
page += "<br/><br/><br/><button>Valider les modifications</button> <input type='reset' value='Annuler les modifications' /></form>";
page += "<br/><br/>Attention en cas de modification de ces valeurs les % mémorisés seront faux, une réinitialisation de toutes les mesures est à prévoir...</td>";
page += "<td><div id='curve_chart' style='width: 400px; height: 400px'></div></td></tr>";
page += "<tr><td>";
page += "<br><p> <a class='btn' target='_self' href='/mem_RTC'>Affichage de toutes les données en mémoire</a> </p>";
page += "<br><p> <a class='btn' target='_self' href='/raz_data'>Réinitialiser toutes les mesures</a> </p>";
page += "</td>";
page += "<td align='center'>";
page += "<br><p><a class='btn' target='_self' href='/graph_jour'>Graphique des 72 dernieres heures en %</a></p>";
page += "<br><p><a class='btn' target='_self' href='/graph_mois'>Graphique des 31 derniers jours en %</a></p>";
page += "<br><p><a class='btn' target='_self' href='/graph_an'>Graphique des 12 derniers mois en %</a></p>";
page += "</td></tr></table>";
page += "<br><p><a class='btn' href='https://www.castoo.fr' target='_blank'>Visiter le site https://www.castoo.fr</a></p>";
page += "</body></html>";
return page;
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Construction de la page HTML Graphique Jour 72 heures
String page_grjour(){
String page = "<!DOCTYPE html><html dir='ltr' lang='fr'><head><meta http-equiv='content-type' content='text/html; charset=UTF-8'>";
page += "<title>Gestion Reserve d'eau Graph 72 heures</title>";
page += "<style> " + v_css_body + v_css_lien + v_css_lien_vol + " </style>";
page += "<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>";
page += "<script type='text/javascript'>";
page += "google.charts.load('current', {'packages':['corechart']});";
page += "google.charts.setOnLoadCallback(drawChart);";
page += "function drawChart() { var data = google.visualization.arrayToDataTable([['Heure',";
page += "'Derniere date " + String(tgr_jour[72].v_j) + " " + nom_mois[tgr_jour[72].v_m] + " à " + String(tgr_jour[72].v_h) + "H '],";
int vadr=0;
while (vadr <= 72){
page += "['" + String(tgr_jour[vadr].v_j) + "/" + String(tgr_jour[vadr].v_h) + "h', ";
page += String((tgr_jour[vadr].v_pc == 255) ? 0 : tgr_jour[vadr].v_pc) + "],";
vadr++;
}
page += "['" + String(tgr_jour[vadr].v_h) + "h', " + String((tgr_jour[vadr].v_pc == 255) ? 0 : tgr_jour[vadr].v_pc) + "]";
page += "]); var options = {";
page += "title: 'Evolution de la réserve d eau sur les 72 dernieres heures en % de remplissage',";
page += "curveType: 'function',";
page += "legend: { position: 'bottom' }";
page += "}; var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));";
page += "chart.draw(data, options);}</script></head><body><div align='center'><div id='curve_chart' style='width: 1000px; height: 500px'></div>";
page += "<br><p align='center'><a class='btn' target='_self' href='/'>Retour Accueil</a> ";
page += " <a class='btn' href='https://www.castoo.fr' target='_blank'>Visiter le site https://www.castoo.fr</a></p>";
page += "</div></body></html>";
return page;
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Construction de la page HTML Graphique Mois 31 jours
String page_grmois(){
String page = "<!DOCTYPE html><html dir='ltr' lang='fr'><head><meta http-equiv='content-type' content='text/html; charset=UTF-8'>";
page += "<title>Gestion Reserve d'eau Graph 31 jours</title>";
page += "<style> " + v_css_body + v_css_lien + v_css_lien_vol + " </style>";
page += "<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>";
page += "<script type='text/javascript'>";
page += "google.charts.load('current', {'packages':['corechart']});";
page += "google.charts.setOnLoadCallback(drawChart);";
page += "function drawChart() { var data = google.visualization.arrayToDataTable([['Jour',";
page += "'(début analyse) Mois de " + nom_mois[tgr_mois[0].v_m] + " " + String(tgr_mois[0].v_a + 1900) + "'],";
int vadr=0;
while (vadr <= 30){
page += "['" + String(tgr_mois[vadr].v_j) + "/" + String(tgr_mois[vadr].v_m) + "', ";
page += String((tgr_mois[vadr].v_pc == 255) ? 0 : tgr_mois[vadr].v_pc) + "],";
vadr++;
}
page += "['" + String(tgr_mois[vadr].v_j) + "/" + String(tgr_mois[vadr].v_m) + "', " + String((tgr_mois[vadr].v_pc == 255) ? 0 : tgr_mois[vadr].v_pc) + "]";
page += "]); var options = {title: 'Evolution de la réserve eau sur les 31 derniers jours en % de remplissage',";
page += "curveType: 'function',legend: { position: 'bottom' }}; ";
page += "var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));";
page += "chart.draw(data, options);}</script></head><body><div align='center'><div id='curve_chart' style='width: 900px; height: 500px'></div>";
page += "<br><p align='center'><a class='btn' href='/' target='_self'>Retour Accueil</a> ";
page += " <a class='btn' href='https://www.castoo.fr' target='_blank'>Visiter le site https://www.castoo.fr</a></p>";
page += "valeur 31 => " + String(tgr_mois[31].v_pc);
page += "</div></body></html>";
return page;
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Construction de la page HTML Graphique An 12 mois
String page_gran(){
String page = "<!DOCTYPE html><html dir='ltr' lang='fr'><head><meta http-equiv='content-type' content='text/html; charset=UTF-8'>";
page += "<title>Gestion Reserve d'eau Graph 12 mois</title>";
page += "<style> " + v_css_body + v_css_lien + v_css_lien_vol + " </style>";
page += "<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>";
page += "<script type='text/javascript'>";
page += "google.charts.load('current', {'packages':['corechart']});";
page += "google.charts.setOnLoadCallback(drawChart);";
page += "function drawChart() { var data = google.visualization.arrayToDataTable([['Jour',";
page += "'(début analyse) Année " + String(tgr_an[0].v_a + 1900) + "'],";
int vadr=0;
while (vadr <= 11){
page += "['" + nom_mois[tgr_an[vadr].v_m] + "', ";
page += String((tgr_an[vadr].v_pc == 255) ? 0 : tgr_an[vadr].v_pc) + "],";
vadr++;
}
page += "['" + nom_mois[tgr_an[vadr].v_m] + "', " + String((tgr_an[vadr].v_pc == 255) ? 0 : tgr_an[vadr].v_pc) + "]";
page += "]); var options = {title: 'Evolution de la réserve eau sur les 12 derniers mois en % de remplissage',";
page += "curveType: 'function',legend: { position: 'bottom' }}; ";
page += "var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));";
page += "chart.draw(data, options);}</script></head><body><div align='center'><div id='curve_chart' style='width: 900px; height: 500px'></div>";
page += "<br><p align='center'><a class='btn' href='/' target='_self'>Retour Accueil</a> ";
page += " <a class='btn' href='https://www.castoo.fr' target='_blank'>Visiter le site https://www.castoo.fr</a></p>";
page += "valeur 12 => " + String(tgr_an[12].v_pc);
page += "</div></body></html>";
return page;
}
// -----------------------------------------------------------------------------------------------------------------------------------
// -------------------------- Afficher toutes les valeurs de la mémoire RTC ----------------------
String aff_toutes_valeurs_RTC(){
String page = "<!DOCTYPE html><html dir='ltr' lang='fr'><head><meta http-equiv='content-type' content='text/html; charset=UTF-8'>";
page += "<style> " + v_css_body + v_css_lien + v_css_lien_vol + " </style>";
page += "<title>mémoire RTC</title>";
page += "<style> body { background-color: grey; font-family: Sans-Serif; Color: orange; }</style>";
page += "</head><body><div align='center'><h1>Gestion reserve eau</h1>";
page += "<p>Liste des variables mémorisées dans la mémoire RTC :</p>";
for(int vadr=debut_RTC; vadr <= fin_TA; vadr++){
get_val_mem(vadr);
page += "<p align='center'> val : " + String(vadr) + " => mois :" + String(memoval.v_m) + " => jour :" + String(memoval.v_j) + " => heure :" + String(memoval.v_h) + " => % :" + String(memoval.v_pc) + "</p>";
}
for(int vadr=debut_TD; vadr <= fin_TD; vadr++){
get_val_div(vadr);
page += "<p align='center'> val : " + String(vadr) + " RTC x1 :" + String(memodiv.v_x) + "</p>";
}
page += "<br><p align='center'><a class='btn' href='/' target='_self'>Retour Accueil</a> ";
page += " <a class='btn' href='https://www.castoo.fr' target='_blank'>Visiter le site https://www.castoo.fr</a></p>";
page += "</body></html>";
return page;
}
// ---------------------------------------------------------------
// --------- Demande de page inexistante ------
void page_inexistante() {
String page_inexist = "Page inexistante";
page_inexist += "<br/>URL: ";
page_inexist += server.uri();
page_inexist += "<br/>Method: ";
page_inexist += (server.method() == HTTP_GET) ? "GET" : "POST";
page_inexist += "<br/>Arguments: ";
page_inexist += server.args();
page_inexist += "<br/>";
for (uint8_t i = 0; i < server.args(); i++) { page_inexist += " " + server.argName(i) + ": " + server.arg(i) + "<br/>"; }
server.send(404, "text/plain", page_inexist);
#ifdef debug_sur_usb
Serial.println("Envoi : page_inexistante");
#endif
}
// -----------------------------------------------------------------------------------------------------------------------------------
// -------------------------- Mesure de la profondeur d'eau ----------------------
// ADS1115 la résolution est de 15 bits (allant de 0 à 32767)
float lireHauteurCm() { // Fonction de conversion analogique -> hauteur en cm
float tension;
int16_t brut;
brut = ads.readADC_SingleEnded(0); // Lecture de la valeur de l'ADS (convertisseur analogique numerique)
#ifdef debug_sur_usb
Serial.print("Mesure brut de l'ADS sonde (allant de 0 à 32767) : ");
Serial.println(brut);
#endif
tension = ads.computeVolts(brut); // Valeur brut entre 0 et 32767 (32767 = 5m) convertie en tension
float tensionMin = 0.66; // correspond à 4 mA
float tensionMax = 3.3; // correspond à 20 mA
if (tension < tensionMin) tension = tensionMin;
if (tension > tensionMax) tension = tensionMax;
float pourcentage = (tension - tensionMin) / (tensionMax - tensionMin);
float hauteurCm = pourcentage * 500.0; // Plage 0–5 m
return hauteurCm;
}
// -----------------------------------------------------------------------------------------------------------------------------------
// -------------------------- Mesure du niveau de la batterie ----------------------
int lireVBatterie() { // Fonction de conversion analogique -> Niveau de la batterie
float tension;
int tension_p; // tension en pourcentage
int16_t brut;
brut = ads.readADC_SingleEnded(1); // Lecture de la valeur de l'ADS (convertisseur analogique numerique)
tension = brut * 0.125 / 100.0; // volts
if (tension >= 4.20) tension_p = 100;
else if (tension >= 4.10) tension_p = 90;
else if (tension >= 3.95) tension_p = 75;
else if (tension >= 3.85) tension_p = 50;
else if (tension >= 3.73) tension_p = 25;
else if (tension >= 3.50) tension_p = 10;
else if (tension >= 3.20) tension_p = 0;
else tension_p = 0;
#ifdef debug_sur_usb
Serial.print("Mesure brut de l'ADS batterie (allant de 0 à 32767) : "); Serial.println(brut);
Serial.print("Tension en volt : ");Serial.println(tension);
Serial.print("Tension en % : ");Serial.println(tension_p);
#endif
return tension_p;
}
// -----------------------------------------------------------------------------------------------------------------------------------
// -------------------------- Création d'une barre pour représenter la charge de la batterie ----------------------
String batterie_Icon(int tension_pourcentage) {
int blocks = map(tension_pourcentage, 0, 100, 0, 10);
String icon = "[";
for (int i = 0; i < 10; i++) {
if (i < blocks) icon += "█";
else icon += " ";
}
icon += "]";
return icon;
}
// -----------------------------------------------------------------------------------------------------------------------------------
// ---------------- Calcul de la moyenne des mesures --------------------
float moyenneTableau(float tableau[], float tailleTableau){
float somme=0;
for(int i=0; i < tailleTableau; i++){
somme=somme+tableau[i];
}
somme = somme / (tailleTableau);
return somme;
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Remise à 0 de toutes les variables
void raz_toutes_les_donnees(){
init_RTC_complet();
charge_tableaux();
server.send(200, "text/html", der_mes());
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Enregistrement d'une nouvelle valeur dans la mémoire RTC
void enr_nouvelle_valeur(int v_val){
int vadr, van, vmois, vjour, vheure;
vjour = timeinfo->tm_mday;
vmois = timeinfo->tm_mon + 1;
vheure = timeinfo->tm_hour;
van = timeinfo->tm_year;
int vnbh = 1;
int v_val_pc = (v_val * 100)/ vh_cuve;
if(v_val_pc > 100){v_val_pc = 100;} // En cas d'erreur (lors du dev.)
#ifdef debug_sur_usb
Serial.println("v_val cm : " + String(v_val));
Serial.println("vh_cuve cm : " + String(vh_cuve));
Serial.println("Enregistrement mesure v_val_pc : " + String(v_val_pc));
#endif
int vcumulh = v_val_pc;
// ------------------------- Enr. dans RTC jour on place la val en position RTC-72(+65) et on recopie TJour de 2 à 72 vers RTC-1(+65) à RTC-71(+65)
// memo
for(vadr=debut_TJ; vadr <= fin_TJ-1; vadr++){
memoval.v_m = tgr_jour[vadr-debut_TJ+1].v_m; tgr_jour[vadr-debut_TJ].v_m = tgr_jour[vadr-debut_TJ+1].v_m;
memoval.v_j = tgr_jour[vadr-debut_TJ+1].v_j; tgr_jour[vadr-debut_TJ].v_j = tgr_jour[vadr-debut_TJ+1].v_j;
memoval.v_h = tgr_jour[vadr-debut_TJ+1].v_h; tgr_jour[vadr-debut_TJ].v_h = tgr_jour[vadr-debut_TJ+1].v_h;
memoval.v_pc = tgr_jour[vadr-debut_TJ+1].v_pc; tgr_jour[vadr-debut_TJ].v_pc = tgr_jour[vadr-debut_TJ+1].v_pc;
// selection des données pour calcul de la moyenne du jour
if(memoval.v_j==vjour && memoval.v_pc!=255){
vnbh++;
vcumulh+=memoval.v_pc;
}
set_val_mem(vadr);
}
// memo 72eme heure
memoval.v_m = vmois; tgr_jour[72].v_m = vmois;
memoval.v_j = vjour; tgr_jour[72].v_j = vjour;
memoval.v_h = vheure; tgr_jour[72].v_h = vheure;
memoval.v_pc = v_val_pc; tgr_jour[72].v_pc = v_val_pc;
set_val_mem(fin_TJ);
// -------------------------------------------- Enr. dans RTC mois
// calcul de la moyenne du jour
int moyjour=0;
moyjour = int(vcumulh/vnbh);
int vnbj = 1;
int vcumulj = moyjour;
// Si nouvelle journée alors on decale les 30 premiers jours
if(tgr_mois[fin_TM-debut_TM].v_j != vjour){
for(vadr=debut_TM; vadr <= fin_TM-1; vadr++){
memoval.v_m = tgr_mois[vadr-debut_TM+1].v_a; tgr_mois[vadr-debut_TM].v_a = tgr_mois[vadr-debut_TM+1].v_a;
memoval.v_j = tgr_mois[vadr-debut_TM+1].v_m; tgr_mois[vadr-debut_TM].v_m = tgr_mois[vadr-debut_TM+1].v_m;
memoval.v_h = tgr_mois[vadr-debut_TM+1].v_j; tgr_mois[vadr-debut_TM].v_j = tgr_mois[vadr-debut_TM+1].v_j;
memoval.v_pc = tgr_mois[vadr-debut_TM+1].v_pc; tgr_mois[vadr-debut_TM].v_pc = tgr_mois[vadr-debut_TM+1].v_pc;
// selection des données pour calcul de la moyenne du mois
if(memoval.v_j==vmois && memoval.v_pc!=255){
vnbj++;
vcumulj+=memoval.v_pc;
}
set_val_mem(vadr);
}
}
// memo 31em jour
memoval.v_m = van; tgr_mois[31].v_a = van;
memoval.v_j = vmois; tgr_mois[31].v_m = vmois;
memoval.v_h = vjour; tgr_mois[31].v_j = vjour;
memoval.v_pc = moyjour; tgr_mois[31].v_pc = moyjour;
set_val_mem(fin_TM);
// ---------------------------- Enr. dans RTC an
// Calcul moyenne mois
int moymois=0;
moymois = int(vcumulj/vnbj);
// Si nouveau mois alors on decale les 11 premiers mois
if(tgr_an[fin_TA-debut_TA].v_m != vmois){
for(vadr=debut_TA; vadr <= fin_TA-1; vadr++){
memoval.v_m = tgr_an[vadr-debut_TA+1].v_a; tgr_an[vadr-debut_TA].v_a = tgr_an[vadr-debut_TA+1].v_a;
memoval.v_j = tgr_an[vadr-debut_TA+1].v_m; tgr_an[vadr-debut_TA].v_m = tgr_an[vadr-debut_TA+1].v_m;
memoval.v_h = tgr_an[vadr-debut_TA+1].v_0; tgr_an[vadr-debut_TA].v_0 = tgr_an[vadr-debut_TA+1].v_0;
memoval.v_pc = tgr_an[vadr-debut_TA+1].v_pc; tgr_an[vadr-debut_TA].v_pc = tgr_an[vadr-debut_TA+1].v_pc;
set_val_mem(vadr);
}
}
// memo 12em mois
memoval.v_m = van; tgr_an[12].v_a = van;
memoval.v_j = vmois; tgr_an[12].v_m = vmois;
memoval.v_h = 0; tgr_an[12].v_0 = 0;
memoval.v_pc = moymois; tgr_an[12].v_pc = moymois;
set_val_mem(fin_TA);
// ------------------------- Enr. dans RTC divers
// On decale dans tous les cas les 9 dernieres valeurs
for(vadr=1; vadr <= 9; vadr++){
memodiv.v_x = tdiv[vadr];
set_val_div((vadr-1)+debut_TD);
}
// Mémo derniere valeur
tdiv[9] = int(vc_cuve / vh_cuve) * v_val;
memodiv.v_x = tdiv[9];
set_val_div(fin_TD-1); // fin_TD);
#ifdef debug_sur_usb
Serial.println("Enregistrement mesure : " + String(tdiv[9]));
#endif
//-----------------------------
// a enlever quand le deepsleep sera en route
//charge_tableaux();
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Chargement des tableaux (jour, mois, an, divers)
void charge_tableaux(){
int vadr;
// Chargement tableau Jour
#ifdef debug_sur_usb
Serial.println("Chargement tableau Jour");
#endif
for(vadr=debut_TJ-1; vadr <= fin_TJ; vadr++){
get_val_mem(vadr);
tgr_jour[vadr-debut_TJ].v_m = memoval.v_m;
tgr_jour[vadr-debut_TJ].v_j = memoval.v_j;
tgr_jour[vadr-debut_TJ].v_h = memoval.v_h;
tgr_jour[vadr-debut_TJ].v_pc = memoval.v_pc;
#ifdef debug_sur_usb
//Serial.println( "val : " + String(vadr-debut_TJ) + " => mois :" + String(tgr_jour[vadr-debut_TJ].v_m) + " => jour :" + String(memoval.v_j) + " => heure :" + String(memoval.v_h) + " => % :" + String(tgr_jour[vadr-debut_TJ].v_pc) );
#endif
}
// Chargement tableau Mois
#ifdef debug_sur_usb
Serial.println("Chargement tableau Mois");
#endif
for(vadr=debut_TM; vadr <= fin_TM; vadr++){
get_val_mem(vadr);
tgr_mois[vadr-debut_TM].v_a = memoval.v_m;
tgr_mois[vadr-debut_TM].v_m = memoval.v_j;
tgr_mois[vadr-debut_TM].v_j = memoval.v_h;
tgr_mois[vadr-debut_TM].v_pc = memoval.v_pc;
#ifdef debug_sur_usb
//Serial.println( "val : " + String(vadr) + " => mois :" + String(memoval.v_m) + " => jour :" + String(memoval.v_j) + " => heure :" + String(memoval.v_h) + " => % :" + String(memoval.v_pc) );
#endif
}
// Chargement tableau An
#ifdef debug_sur_usb
Serial.println("Chargement tableau An");
#endif
for(vadr=debut_TA; vadr <= fin_TA; vadr++){
get_val_mem(vadr);
tgr_an[vadr-debut_TA].v_a = memoval.v_m;
tgr_an[vadr-debut_TA].v_m = memoval.v_j;
tgr_an[vadr-debut_TA].v_0 = memoval.v_h;
tgr_an[vadr-debut_TA].v_pc = memoval.v_pc;
#ifdef debug_sur_usb
//Serial.println( "val : " + String(vadr) + " => mois :" + String(memoval.v_m) + " => jour :" + String(memoval.v_j) + " => heure :" + String(memoval.v_h) + " => % :" + String(memoval.v_pc) );
#endif
}
// Chargement tableau Divers
#ifdef debug_sur_usb
Serial.println("Chargement tableau Divers");
#endif
for(vadr=debut_TD; vadr <= fin_TD; vadr++){
get_val_div(vadr);
tdiv[vadr-debut_TD] = memodiv.v_x;
#ifdef debug_sur_usb
//Serial.println( "val : " + String(vadr) + " => x : " + String(memodiv.v_x) );
#endif
}
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Sauve param dans eeprom esp
void sauve_data_eeprom_esp(){
eeprom_esp.h_cuve = vh_cuve;
eeprom_esp.c_cuve = vc_cuve;
eeprom_esp.v_ok = 'O';
EEPROM.put(0,eeprom_esp);
EEPROM.commit();
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Les parametres ont été modifiés
void modif_param(){
if ( server.hasArg("h_cuve")) { vh_cuve = server.arg("h_cuve").toInt(); sauve_data_eeprom_esp();}
if ( server.hasArg("c_cuve")) { vc_cuve = server.arg("c_cuve").toInt(); sauve_data_eeprom_esp();}
server.send ( 200, "text/html", der_mes() );
}
// -----------------------------------------------------------------------------------------------------------------------------------
void setup() {
#ifdef debug_sur_usb
Serial.begin(9600);
#endif
#ifdef debug_temp
Serial.begin(9600);
#endif
pinMode(MosFet, OUTPUT); // Alimentation du montage pour mesures
digitalWrite(MosFet, LOW);
IPAddress ip(192, 168, 1, 88); // Declaration adr IP fixe
IPAddress dns(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255, 255, 255, 0);
WiFi.mode(WIFI_STA);
WiFi.config(ip, gateway, subnet, dns);
WiFi.begin(ssid, password);
//wifiMulti.addAP(ssid, password);
#ifdef debug_sur_usb
Serial.println("Init WiFi : ");
#endif
//while (wifiMulti.run() != WL_CONNECTED) {
while (WiFi.status() != WL_CONNECTED) {
#ifdef debug_sur_usb
Serial.print(".");
#endif
delay ( 100 );
}
#ifdef debug_sur_usb
Serial.print("IP : ");
Serial.println(WiFi.localIP());
Serial.println("Init TIME_ZONE pool.ntp.org : ");
#endif
//configTime(TIME_ZONE, "pool.ntp.org", "time.nis.gov");
configTime(0, 0 , "pool.ntp.org", "time.nis.gov"); // 7200 heure hiver / 3600 heure ete
setenv("TZ", "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", 1);
while (time(nullptr) < 1000000000ul) {
delay(100);
#ifdef debug_sur_usb
Serial.print(".");
#endif
}
time_t now = time(nullptr);
timeinfo = localtime (&now);
char buffer [80];
strftime (buffer,80,"Local time: %H:%M.",timeinfo);
#ifdef debug_sur_usb
Serial.println(buffer);
#endif
time(&now);
temps_qui_passe = millis();
temps_inter_h = temps_qui_passe;
ads.setGain(GAIN_ONE); // +/- 4.096V Résolution de 0.125mV par bit
if (!ads.begin()) { Serial.println("Probleme init ADS 1115"); }
top_mesure = true;
EEPROM.begin(sizeof(eeprom_esp)+1); //eeprom esp
EEPROM.get(0, eeprom_esp);
if(eeprom_esp.v_ok == 'O'){ // l'enregistrement est valide alors on charge les variables de l'eeprom
vh_cuve = eeprom_esp.h_cuve;
vc_cuve = eeprom_esp.c_cuve;
} else {
vh_cuve = 170; // valeur par defaut pour hauteur
vc_cuve = 2000; // valeur par defaut pour capacité
sauve_data_eeprom_esp();
}
charge_tableaux(); // Chargement des tableaux (jour, mois, an, divers) qui ont été sauvegardés pendant le deepsleep
server.on ( "/" , [](){ server.send(200, "text/html", der_mes()); });
server.on ( "/graph_jour", [](){ server.send(200, "text/html", page_grjour()); });
server.on ( "/graph_mois", [](){ server.send(200, "text/html", page_grmois()); });
server.on ( "/graph_an" , [](){ server.send(200, "text/html", page_gran()); });
server.on ( "/mem_RTC" , [](){ server.send(200, "text/html", aff_toutes_valeurs_RTC()); });
server.on ( "/raz_data" , raz_toutes_les_donnees);
server.on ( "/mod_param" , modif_param); // Retour des parametres (hauteur cuve et capacité cuve)
server.onNotFound(page_inexistante);
server.begin();
delay(1000);
digitalWrite(MosFet, HIGH); // On alimente le capteur
delay(50);
#ifdef debug_sur_usb
Serial.println("Montage sous tension ");
#endif
}
// -----------------------------------------------------------------------------------------------------------------------------------
// Initialise la totalité des tableaux de variables
void init_RTC_complet(){
int vadr, van, vmois, vjour, vheure;
// RAZ memo jour (72 heures)
vjour = timeinfo->tm_mday;
vmois = timeinfo->tm_mon + 1;
vheure = timeinfo->tm_hour;
for(vadr=debut_TJ; vadr <= fin_TJ; vadr++){
memoval.v_m = vmois;
memoval.v_j = vjour;
memoval.v_h = vheure;
vheure++;
if(vheure==24){
vheure=0;
vjour++;
if(vjour==31){
vjour=1;
vmois++;
if(vmois==13) vmois=1;
}
}
memoval.v_pc = 255;
set_val_mem(vadr);
}
// RAZ memo mois (31 jours)
van = timeinfo->tm_year;
vmois = timeinfo->tm_mon + 1;
vjour = timeinfo->tm_mday;
for(vadr=debut_TM; vadr <= fin_TM; vadr++){
memoval.v_m = van;
memoval.v_j = vmois;
memoval.v_h = vjour;
vjour++;
if(vjour==31){
vjour=1;
vmois++;
if(vmois==13){
vmois=1;
van++;
}
}
memoval.v_pc = 255;
set_val_mem(vadr);
}
// RAZ memo an (12 mois)
van = timeinfo->tm_year;
vmois = timeinfo->tm_mon + 1;
for(vadr=debut_TA; vadr <= fin_TA; vadr++){
memoval.v_m = van;
memoval.v_j = vmois;
vmois++;
if(vmois==13){
vmois=1;
van++;
}
memoval.v_h = 0;
memoval.v_pc = 255;
set_val_mem(vadr);
}
// RAZ memo variables divers
for(vadr=debut_TD; vadr <= fin_TD; vadr++){
memodiv.v_x = 1024;
set_val_div(vadr);
}
}
// -----------------------------------------------------------------------------------------------------------------------------------
void loop() {
server.handleClient(); // mise en reception du serveur wifi
if(top_mesure){ // On vient de demarrer alors on lance une mesure
#ifdef debug_sur_usb
Serial.println("Lancement de la mesure suite demarrage...");
#endif
float vmesure[3]; // on va faire une moyenne sur 3 mesures
float resultat = 0;
for(int cpt = 0; cpt < 3; cpt++){
vmesure[cpt] = lireHauteurCm();
#ifdef debug_sur_usb
Serial.print("Lancement mesure : ");
Serial.print(cpt);
Serial.print(" valeur : ");
Serial.println(vmesure[cpt]);
#endif
delay(1000);
}
resultat = moyenneTableau(vmesure, 3);
mesure = int(resultat);
snprintf (str_mesure, 50, " : %d cm", mesure);
enr_nouvelle_valeur(mesure);
int tension_bat = lireVBatterie(); // Lecture de la tension de la batterie
niveau_bat = batterie_Icon(tension_bat); // Création d'un icone pour présenter la charge de la batterie
top_mesure = false; // On ne relancera pas la mesure avant une heure
digitalWrite(MosFet, LOW); // On coupe l'alim du capteur (plus besoin pour la lecture des infos web wifi)
}
if((millis() - temps_inter_h) >= 60000){ // on ne lance une interrogation serveur horaire qu'une fois toutes les minutes
time_t now = time(nullptr);
timeinfo = localtime (&now);
temps_inter_h += 60000;
#ifdef debug_sur_usb
Serial.println(String(timeinfo->tm_hour) + "H" + String(timeinfo->tm_min));
Serial.println("Lancement interrogation serveur heure...");
#endif
}
if((millis() - temps_qui_passe) >= memo_temps){ // On ne laisse que 5 minutes en route pour faire la mesure et se connecter au wifi.
#ifdef debug_sur_usb
Serial.println(String(timeinfo->tm_hour) + "H" + String(timeinfo->tm_min));
Serial.println("Les 5 minutes sont en cours...");
#endif
if(timeinfo->tm_min > 5){ // Si on est bien sur un nombre de minute > 5 on coupe l'esp
#ifdef debug_sur_usb
Serial.println(String(timeinfo->tm_hour) + "H" + String(timeinfo->tm_min));
Serial.println("Les 5 minutes sont en passées, arret de l'esp !!!!!-------!!!!!!");
#endif
// on essai de partir sur un arret de l'esp jusqu'à la prochaine heure pleine.
if(timeinfo->tm_min == 6){
ESP.deepSleep(duree_sommeil); // On coupe l'esp pendant 54 minutes (impossible de le joindre pendant cette période)
}else{
int nb_min = 60 - timeinfo->tm_min;
ESP.deepSleep((60e6)*nb_min); // On coupe l'esp jusqu'à la prochaine heure pleine (impossible de le joindre pendant cette période)
}
}
}
delay(100);
}