Bonjour a tous ,j’espére avoir mis sur le bon post c’est mon 2eme projet à 70ans mon premier est la régulation des ventilo additionnels RGB et PWM de mon système de refroidissement pour mon ONDULEUR VICTRON QUADRO plus . Mon 2ème projet c’est celui que j’ai un problème , c’est un contrôle de niveau cuve FUEL aidé je l’avoue avec des IA , je comprends à peu prés la logique , mais j’ai un soucis… Voilà je suis sur IOT ARDUINO CLOUD , j’ai tout qui fonctionne, peut-être des améliorations , ou condensation . Mon IOT est à 600km et je voudrais même sur place mettre en veilles (extinction) mes Leds de niveaux et mon écran LCD I2C . J’ai bien créer 2 Buttons différents mais lorsque je simule en déconnectant le+5V des Leds ou de l’écran ….Plus de DASHBOARD et je pense une déconnection du WIFI et CLOUD .. .J’avoue je patauge dans la semoule . Question y a t-il une solution simple car mon code est déjà compliqué ……Merci d’avance , comme dit au début c’est la première fois que je demande l’aide ‘‘‘HUMAINE””. et que j’utilise le site ,alors si je réponds pas de suite ne m’en voulez pas… Pas d’urgence .. Passez tous de bonnes fêtes… Vincenzo M
/* ESP IOT URSULA – VERSION 1.9.8-upgraded – 21/12/2025
=============================================================
- Conso détectée uniquement après 3 mesures stables
- Plus d'incrémentation fantôme dans les buckets
- KY040 corrigé, écran stable, code complet
============================================================= */
#include <Arduino.h>
#include <Wire.h>
#include <DHT.h>
#include <EEPROM.h>
#include <WiFi.h>
#include <time.h>
#include <esp_task_wdt.h>
#include <algorithm>
#include "thingProperties.h"
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 7200;
const int daylightOffset_sec = 0;
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
#define LED_VERTE1 12
#define LED_VERTE2 14
#define LED_JAUNE1 27
#define LED_JAUNE2 26
#define LED_ORANGE1 25
#define LED_ROUGE1 33
#define LED_ROUGE2 32
#define BUZZER 13
#define LED_WIFI 18
#define BTN_ERASE_EEPROM 0
#define SDA 21
#define SCL 22
#define KY040_CLK 16
#define KY040_DT 17
#define KY040_SW 19
#define WDT_TIMEOUT 30
const int HAUTEUR = 160;
const int CAPACITE = 3200;
const int LITRES_CM = CAPACITE / HAUTEUR;
#define SEUIL_CONSOMMATION 2.0f
#define SEUIL_MIN_REMPLISSAGE_MANUEL 5.0f
#define DELAI_REMPLISSAGE_MANUEL_MIN 10
#define DELAI_REMPLISSAGE_MAX 120
#define DELAI_REMPLISSAGE_MIN 5
#define CHENILLARD_INTERVAL_MS 200
#define CHENILLARD_ALTERNANCE_MIN 5
#define NB_MESURES 100
#define MIN_MESURES_INITIALISATION 5
#define TRIGGER 15
#define ECHO 2
#define MAX_DISTANCE 160
#define TIMEOUT_US (MAX_DISTANCE * 100)
#define LCD_ADDR 0x27
#define LCD_COLS 20
#define LCD_ROWS 4
#define EEPROM_SIZE 2048
#define ADDR_BUCKET_MAGIC 1050
#define NB_BUCKETS 400
#define ADDR_BUCKETS 1060
#define ADDR_REMPLISSAGE_TIME 2000
#define TEMPS_SORTIE_AUTO_MIN 5
#define BTN_EEPROM_LONG_PRESS 5000
// Commande des relais ALLUMAGE LCD/LEDS
const int RelaisLCD = 35;
const int RelaisLEDS = 34;
const int BpLCD = 3;
const int BpLEDS = 1;
const int allumageTemps = 30000;
unsigned long allumageTimer = 0;
struct BucketHoraire {
uint16_t annee;
uint8_t mois, jour, heure;
float conso_L;
};
BucketHoraire buckets[NB_BUCKETS];
int idxBucket = 0;
const uint32_t bucketMagic = 0xCAFE2025;
float ancien_niveau = -1;
float tableau_mesures[NB_MESURES] = {0};
float tableau_temp[NB_MESURES] = {0};
float tableau_hum[NB_MESURES] = {0};
int index_mesure = 0;
int nb_mesures_valides = 0;
bool tableau_plein = false;
bool systeme_initialise = false;
float dcc = 2.05f;
unsigned long lastPing = 0, lastDisplay = 0, lastWifiBlink = 0, lastRouge2Blink = 0, cycleStartTime = 0;
bool showingWifiStatus = true;
const unsigned long CYCLE_DURATION = 60000;
const unsigned long WIFI_BLINK_INTERVAL = 1000;
const unsigned long CLOUD_BLINK_INTERVAL = 100;
int displayMode = 0, currentDisplayMode = -1;
const unsigned long DISPLAY_DURATION = 10000;
float conso_detectee = 0;
bool flag_conso_detectee = false;
unsigned long debut_affichage_conso = 0;
char date_conso[12], heure_conso[10];
bool alerte_sonde = false;
uint8_t mauvaises_lectures_consecutives = 0;
const uint8_t SEUIL_MAUVAISES_LECTURES = 25;
#define NB_REMPLISSAGES 10
struct Remplissage {
char date[12];
char heure[10];
float volumeIntroduit;
};
Remplissage remplissages[NB_REMPLISSAGES];
int idxRemplissage = 0;
int nb_remplissages_reels = 0;
unsigned long lastScrollRemplissage = 0;
int affichageRemplissageIdx = 0;
String lastLine[4] = {"", "", "", ""};
bool inMenu = false;
int menuItem = 0;
const char* menuItems[12] = {
"Effacer alarmes", "Effacer conso", "Effacer remplis.",
"Effacer stats", "Remplissage", "Effacer EEPROM",
"Voir consos horaires", "Voir consos journalieres",
"Voir consos mensuelles", "Voir consos annuelles",
"Voir alarmes", "Voir remplissages"
};
int lastCLKState = HIGH;
unsigned long lastRotationTime = 0;
const unsigned long DEBOUNCE_ROTATION = 200;
unsigned long swPressStart = 0;
int swLastState = HIGH;
const unsigned long LONG_PRESS_DURATION = 1500;
bool inRemplissageMode = false;
float volumeReferenceRemplissage = 0.0f;
unsigned long debutRemplissageMode = 0;
bool remplissageDetecte = false;
float volumeMaxDetecte = 0.0f;
int tempsRemplissageMinutes = DELAI_REMPLISSAGE_MANUEL_MIN;
bool reglageTempsActif = false;
int tempsReglageProvisoire = 0;
unsigned long lastChenillardUpdate = 0;
int chenillardPosition = 0;
bool chenillardActif = false;
bool afficherNiveauActuel = true;
unsigned long derniereAlternance = 0;
unsigned long btnEepromPressStart = 0;
int btnEepromLastState = HIGH;
#define ALERTE_ORANGE_PCT 30.0f
#define ALERTE_ROUGE_PCT 20.0f
#define ALERTE_CRITIQUE_PCT 10.0f
#define ALERTE_TRES_CRITIQUE_PCT 5.0f
bool alerte_fuel_orange = false;
bool alerte_fuel_rouge = false;
bool alerte_fuel_critique = false;
bool alerte_fuel_tres_critique = false;
unsigned long lastAlerteBeep = 0, lastAlerteBlink = 0;
bool alerteBlinkState = false;
bool conso_en_cours = false;
// ====================== LCD ======================
#define LCD_BACKLIGHT 0x08
#define En B00000100
#define Rs B00000001
void lcd_send(uint8_t data, uint8_t mode) {
uint8_t high = data & 0xF0;
uint8_t low = (data << 4) & 0xF0;
auto write4 = [](uint8_t d) -> void {
Wire.beginTransmission(LCD_ADDR);
Wire.write(d | LCD_BACKLIGHT);
Wire.endTransmission();
Wire.beginTransmission(LCD_ADDR);
Wire.write(d | En | LCD_BACKLIGHT);
Wire.endTransmission();
delayMicroseconds(1);
Wire.beginTransmission(LCD_ADDR);
Wire.write((d & ~En) | LCD_BACKLIGHT);
Wire.endTransmission();
delayMicroseconds(50);
};
write4(high | mode);
write4(low | mode);
}
void lcd_init() {
Wire.begin(SDA, SCL);
delay(50);
auto cmd = [](uint8_t c) { delayMicroseconds(c >= 0x10 ? 4500 : 150); lcd_send(c, 0); };
cmd(0x03); cmd(0x03); cmd(0x03); cmd(0x02);
lcd_send(0x28, 0);
lcd_send(0x08 | 0x04, 0);
lcd_send(0x06, 0);
lcd_clear();
}
void lcd_clear() { lcd_send(0x01, 0); delay(2); }
void lcd_setCursor(uint8_t c, uint8_t r) {
const uint8_t offsets[4] = {0x00, 0x40, 0x14, 0x54};
lcd_send(0x80 | (c + offsets[r]), 0);
}
void lcd_print(const char* s) { while (*s) lcd_send(*s++, Rs); }
void lcd_print(String s) { for (size_t i = 0; i < s.length(); ++i) lcd_send(s[i], Rs); }
void lcd_createChar(uint8_t loc, uint8_t map[]) {
lcd_send(0x40 | (loc << 3), 0);
for (int i = 0; i < 8; ++i) lcd_send(map[i], Rs);
}
void updateLine(int line, String text) {
if (text == lastLine[line]) return;
lcd_setCursor(0, line);
lcd_print(" ");
lcd_setCursor(0, line);
lcd_print(text);
lastLine[line] = text;
}
void displayPreciseBargraph(float pc) {
int tot = (int)(pc * 100);
if (tot < 0) tot = 0; else if (tot > 10000) tot = 10000;
int full = tot / 500;
int rest = (tot % 500) / 100;
String bar = "";
for (int i = 0; i < 20; ++i) {
if (i < full) bar += char(4);
else if (i == full && rest > 0) bar += char(rest - 1);
else bar += ' ';
}
updateLine(3, bar);
}
void resetLineCache() { for (auto& l : lastLine) l = ""; }
// ====================== EEPROM & BUCKETS ======================
void effacer_totale_eeprom() {
EEPROM.begin(EEPROM_SIZE);
for (int i = 0; i < EEPROM_SIZE; ++i) EEPROM.write(i, 0xFF);
EEPROM.commit(); EEPROM.end();
}
void chargerTempsRemplissage() {
EEPROM.begin(EEPROM_SIZE);
int tempsLu;
EEPROM.get(ADDR_REMPLISSAGE_TIME, tempsLu);
if (tempsLu >= DELAI_REMPLISSAGE_MIN && tempsLu <= DELAI_REMPLISSAGE_MAX) {
tempsRemplissageMinutes = tempsLu;
} else {
tempsRemplissageMinutes = DELAI_REMPLISSAGE_MANUEL_MIN;
}
EEPROM.end();
}
void sauvegarderTempsRemplissage(int temps) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.put(ADDR_REMPLISSAGE_TIME, temps);
EEPROM.commit();
EEPROM.end();
}
void initBuckets() { memset(buckets, 0, sizeof(buckets)); idxBucket = 0; }
void chargerBuckets() {
EEPROM.begin(EEPROM_SIZE);
uint32_t magic; EEPROM.get(ADDR_BUCKET_MAGIC, magic);
if (magic != bucketMagic) { EEPROM.end(); initBuckets(); sauvegarderBuckets(); return; }
int addr = ADDR_BUCKETS;
for (int i = 0; i < NB_BUCKETS; ++i) { EEPROM.get(addr, buckets[i]); addr += sizeof(BucketHoraire); }
idxBucket = 0;
for (int i = 0; i < NB_BUCKETS; ++i) if (buckets[i].annee >= 2020) idxBucket = (i + 1) % NB_BUCKETS;
EEPROM.end();
}
void sauvegarderBuckets() {
EEPROM.begin(EEPROM_SIZE);
EEPROM.put(ADDR_BUCKET_MAGIC, bucketMagic);
int addr = ADDR_BUCKETS;
for (int i = 0; i < NB_BUCKETS; ++i) { EEPROM.put(addr, buckets[i]); addr += sizeof(BucketHoraire); }
EEPROM.commit(); EEPROM.end();
}
void forcerBucketCourant() {
struct tm ti; if (!getLocalTime(&ti)) return;
uint16_t a = ti.tm_year + 1900;
uint8_t m = ti.tm_mon + 1, j = ti.tm_mday, h = ti.tm_hour;
int cur = (idxBucket == 0 ? NB_BUCKETS - 1 : idxBucket - 1);
if (buckets[cur].annee != a || buckets[cur].mois != m || buckets[cur].jour != j || buckets[cur].heure != h) {
buckets[idxBucket] = {a, m, j, h, 0.0f};
idxBucket = (idxBucket + 1) % NB_BUCKETS;
sauvegarderBuckets();
}
}
void ajouterConsoDansBucket(float q) {
forcerBucketCourant();
int cur = (idxBucket == 0 ? NB_BUCKETS - 1 : idxBucket - 1);
buckets[cur].conso_L += q;
sauvegarderBuckets();
}
float getConso(bool horaire = true, bool jour = false, bool mois = false, bool annee = false) {
forcerBucketCourant();
struct tm ti; if (!getLocalTime(&ti)) return 0.0f;
uint16_t a = ti.tm_year + 1900;
uint8_t m = ti.tm_mon + 1, j = ti.tm_mday, h = ti.tm_hour;
float total = 0.0f;
for (int i = 0; i < NB_BUCKETS; ++i) {
if ((horaire && buckets[i].annee == a && buckets[i].mois == m && buckets[i].jour == j && buckets[i].heure == h) ||
(jour && buckets[i].annee == a && buckets[i].mois == m && buckets[i].jour == j) ||
(mois && buckets[i].annee == a && buckets[i].mois == m) ||
(annee && buckets[i].annee == a))
total += buckets[i].conso_L;
}
return total;
}
// ====================== CHENILLARD LED ======================
void updateLEDs(float v) {
if (chenillardActif && !afficherNiveauActuel) return;
digitalWrite(LED_ROUGE2, v >= 400 ? HIGH : ((millis() - lastRouge2Blink >= 500) ? (lastRouge2Blink = millis(), !digitalRead(LED_ROUGE2)) : digitalRead(LED_ROUGE2)));
digitalWrite(LED_ROUGE1, v >= 500 ? HIGH : LOW);
digitalWrite(LED_ORANGE1, v >= 700 ? HIGH : LOW);
digitalWrite(LED_JAUNE2, v >= 800 ? HIGH : LOW);
digitalWrite(LED_JAUNE1, v >= 900 ? HIGH : LOW);
digitalWrite(LED_VERTE2, v >= 1000 ? HIGH : LOW);
digitalWrite(LED_VERTE1, v >= 1200 ? HIGH : LOW);
}
void updateChenillardLED() {
if (!chenillardActif) return;
unsigned long now = millis();
if (now - derniereAlternance >= CHENILLARD_ALTERNANCE_MIN * 60000UL) {
afficherNiveauActuel = !afficherNiveauActuel;
derniereAlternance = now;
if (afficherNiveauActuel) { updateLEDs(moyenne(tableau_mesures)); return; }
}
if (afficherNiveauActuel) return;
if (now - lastChenillardUpdate >= CHENILLARD_INTERVAL_MS) {
lastChenillardUpdate = now;
digitalWrite(LED_VERTE1, LOW); digitalWrite(LED_VERTE2, LOW);
digitalWrite(LED_JAUNE1, LOW); digitalWrite(LED_JAUNE2, LOW);
digitalWrite(LED_ORANGE1, LOW); digitalWrite(LED_ROUGE1, LOW); digitalWrite(LED_ROUGE2, LOW);
switch (chenillardPosition) {
case 0: digitalWrite(LED_VERTE1, HIGH); break;
case 1: digitalWrite(LED_VERTE2, HIGH); break;
case 2: digitalWrite(LED_JAUNE1, HIGH); break;
case 3: digitalWrite(LED_JAUNE2, HIGH); break;
case 4: digitalWrite(LED_ORANGE1, HIGH); break;
case 5: digitalWrite(LED_ROUGE1, HIGH); break;
case 6: digitalWrite(LED_ROUGE2, HIGH); break;
}
chenillardPosition = (chenillardPosition + 1) % 7;
}
}
void demarrerChenillard() {
chenillardActif = true; chenillardPosition = 0; lastChenillardUpdate = 0;
derniereAlternance = millis(); afficherNiveauActuel = false;
}
void arreterChenillard() {
chenillardActif = false; updateLEDs(moyenne(tableau_mesures));
}
// ====================== ÉCRAN REGLAGE TEMPS 4 LIGNES ======================
void ecran_reglage_temps() {
unsigned long lastRefresh = 0;
unsigned long startTime = millis();
int localCLKState = HIGH;
int localSWState = HIGH;
unsigned long localSWPress = 0;
while (true) {
esp_task_wdt_reset();
ArduinoCloud.update();
if (millis() - startTime > 30000) return;
if (millis() - lastRefresh >= 150) {
lastRefresh = millis();
lcd_clear(); resetLineCache();
lcd_setCursor(6, 0); lcd_print("CONFIG TEMPS");
char bufVal[10];
snprintf(bufVal, 10, "%3d min", tempsReglageProvisoire);
lcd_setCursor(20 - strlen(bufVal), 1);
lcd_print(bufVal);
lcd_setCursor(2, 2); lcd_print("Plage: 5-120 min");
lcd_setCursor(1, 3); lcd_print("KY040: +/- OK: val");
}
int clk = digitalRead(KY040_CLK);
int dt = digitalRead(KY040_DT);
int sw = digitalRead(KY040_SW);
if (clk != localCLKState && clk == LOW) {
if (millis() - lastRotationTime > DEBOUNCE_ROTATION) {
if (dt == LOW) tempsReglageProvisoire = min(tempsReglageProvisoire + 5, DELAI_REMPLISSAGE_MAX);
else tempsReglageProvisoire = max(tempsReglageProvisoire - 5, DELAI_REMPLISSAGE_MIN);
lastRotationTime = millis();
beep();
}
}
localCLKState = clk;
if (sw == LOW && localSWState == HIGH) localSWPress = millis();
else if (sw == HIGH && localSWState == LOW) {
unsigned long dur = millis() - localSWPress;
if (dur > 50 && dur < LONG_PRESS_DURATION) {
tempsRemplissageMinutes = tempsReglageProvisoire;
sauvegarderTempsRemplissage(tempsRemplissageMinutes);
lcd_clear(); resetLineCache();
lcd_setCursor(4, 1); lcd_print("TEMPS ENREGISTRE");
char buf[21];
snprintf(buf, 21, "%d minutes", tempsRemplissageMinutes);
lcd_setCursor(4, 2); lcd_print(buf);
beep_long();
delay(2000);
return;
}
}
localSWState = sw;
delay(10);
}
}
// ====================== FONCTIONS D'ÉCRAN ======================
bool confirmer_action(const char* titre) {
int choix = 0;
const char* options[3] = {"Oui", "Non", "Sortie"};
unsigned long lastRefresh = 0;
unsigned long startTime = millis();
int localCLKState = HIGH;
int localSWState = HIGH;
unsigned long localSWPress = 0;
while (true) {
esp_task_wdt_reset();
ArduinoCloud.update();
if (millis() - startTime > TEMPS_SORTIE_AUTO_MIN * 60000UL) return false;
if (millis() - lastRefresh >= 200) {
lastRefresh = millis();
lcd_clear(); resetLineCache();
lcd_setCursor(1, 0); lcd_print(titre);
for (int i = 0; i < 3; ++i) {
lcd_setCursor(0, 1 + i);
lcd_print(i == choix ? ">" : " ");
lcd_print(options[i]);
}
lcd_setCursor(0, 3); lcd_print("Appui long = Sortie");
}
int clk = digitalRead(KY040_CLK);
int dt = digitalRead(KY040_DT);
int sw = digitalRead(KY040_SW);
if (clk != localCLKState && clk == LOW) {
if (millis() - lastRotationTime > DEBOUNCE_ROTATION) {
choix = (choix + (dt == LOW ? 1 : -1) + 3) % 3;
lastRotationTime = millis();
}
}
localCLKState = clk;
if (sw == LOW && localSWState == HIGH) localSWPress = millis();
else if (sw == HIGH && localSWState == LOW) {
unsigned long dur = millis() - localSWPress;
if (dur > 50 && dur < LONG_PRESS_DURATION) return (choix == 0);
else if (dur >= LONG_PRESS_DURATION) return false;
}
localSWState = sw;
delay(100);
}
}
void afficher_fuel_opti(float vol, float pc, float dcorr, float hfuel, const char* titre = "FUEL RESTANT") {
if (currentDisplayMode != 0) {
lcd_clear(); resetLineCache();
lcd_setCursor((LCD_COLS - strlen(titre)) / 2, 0);
lcd_print(titre);
currentDisplayMode = 0;
}
char buf[21];
snprintf(buf, 21, "%.2f L %.2f %%", vol, pc); updateLine(1, buf);
snprintf(buf, 21, "%.3f cm %.2f cm", dcorr, hfuel); updateLine(2, buf);
displayPreciseBargraph(pc);
}
void afficher_remplissage_en_cours() {
float vol = moyenne(tableau_mesures);
float pc = vol * 100.0f / CAPACITE;
float dc = distance;
float hf = HAUTEUR - (dc - dcc);
if (currentDisplayMode != 0) {
lcd_clear(); resetLineCache();
lcd_setCursor(2, 0); lcd_print("REMPLISSAGE EN COURS");
currentDisplayMode = 0;
}
char buf[21];
snprintf(buf, 21, "%.2f L %.2f %%", vol, pc); updateLine(1, buf);
snprintf(buf, 21, "%.3f cm %.2f cm", dc, hf); updateLine(2, buf);
displayPreciseBargraph(pc);
}
void afficher_infos_capteurs_opti() {
if (currentDisplayMode != 1) { lcd_clear(); resetLineCache(); currentDisplayMode = 1; }
char d[12], h[10]; lire_date_heure(d, h);
float t = moyenne(tableau_temp), hum = moyenne(tableau_hum);
char buf[21];
snprintf(buf, 21, "%s %s", d, h); updateLine(0, buf);
snprintf(buf, 21, "Temp: %.1f C", t); updateLine(1, buf);
snprintf(buf, 21, "Hum : %.1f %%", hum); updateLine(2, buf);
snprintf(buf, 21, " WiFi:%s Cloud:%s", WiFi.status() == WL_CONNECTED ? "OK" : "NO", ArduinoCloud.connected() ? "OK" : "NO");
updateLine(3, buf);
}
void afficher_conso_opti() {
if (currentDisplayMode != 2) { lcd_clear(); resetLineCache(); lcd_setCursor(2, 0); lcd_print("CONSO CALENDAIRE"); currentDisplayMode = 2; }
char buf[21];
snprintf(buf, 21, "Heure : %.2f L", getConso(true)); updateLine(1, buf);
snprintf(buf, 21, "Jour : %.2f L", getConso(false, true)); updateLine(2, buf);
snprintf(buf, 21, "Mois : %.2f L", getConso(false, false, true)); updateLine(3, buf);
}
void afficher_conso_annuelle_opti() {
if (currentDisplayMode != 3) { lcd_clear(); resetLineCache(); lcd_setCursor(2, 0); lcd_print(" CONSO ANNUELLE"); currentDisplayMode = 3; }
float ca = getConso(false, false, false, true);
char buf[21];
snprintf(buf, 21, "%.2f L/an", ca); updateLine(2, buf);
snprintf(buf, 21, "%.3f m3/an", ca / 1000.0f); updateLine(3, buf);
}
void ecran_consommation_opti() {
if (currentDisplayMode != 9) { lcd_clear(); resetLineCache(); lcd_setCursor(4, 0); lcd_print("CONSOMMATION"); currentDisplayMode = 9; }
char buf[21];
snprintf(buf, 21, "Date : %s", date_conso); updateLine(1, buf);
snprintf(buf, 21, "Heure: %s", heure_conso); updateLine(2, buf);
snprintf(buf, 21, "Conso: -%.2f L", conso_detectee); updateLine(3, buf);
}
void displayMenu() {
lcd_clear(); resetLineCache();
lcd_setCursor(5, 0); lcd_print("*** MENU ***");
lcd_setCursor(0, 1); lcd_print(">"); lcd_print(menuItems[menuItem]);
lcd_setCursor(0, 3); lcd_print("Tourner: select Bref: OK");
}
// ====================== LED / WIFI / EEPROM BUTTONS ======================
void gerer_led_wifi_cloud() {
if (millis() - cycleStartTime >= CYCLE_DURATION) {
cycleStartTime = millis(); showingWifiStatus = !showingWifiStatus; digitalWrite(LED_WIFI, LOW);
}
bool connected = showingWifiStatus ? (WiFi.status() == WL_CONNECTED) : ArduinoCloud.connected();
unsigned long interval = showingWifiStatus ? WIFI_BLINK_INTERVAL : CLOUD_BLINK_INTERVAL;
if (connected && millis() - lastWifiBlink >= interval) { lastWifiBlink = millis(); digitalWrite(LED_WIFI, !digitalRead(LED_WIFI)); }
else if (!connected) digitalWrite(LED_WIFI, LOW);
}
void gerer_bouton_eeprom() {
int state = digitalRead(BTN_ERASE_EEPROM);
if (state != btnEepromLastState) {
if (state == LOW) btnEepromPressStart = millis();
btnEepromLastState = state;
}
if (state == LOW) {
unsigned long dur = millis() - btnEepromPressStart;
if (dur >= BTN_EEPROM_LONG_PRESS) {
lcd_clear(); lcd_setCursor(1,0); lcd_print("EFFACEMENT TOTAL");
lcd_setCursor(3,1); lcd_print("EEPROM EN COURS");
lcd_setCursor(5,2); lcd_print("PATIENTEZ...");
for (int i = 0; i < 3; ++i) { beep_long(); delay(200); }
effacer_totale_eeprom();
lcd_clear(); lcd_setCursor(2,1); lcd_print("EEPROM EFFACEE");
lcd_setCursor(3,2); lcd_print("REDEMARRAGE...");
delay(2000); ESP.restart();
} else if (dur >= 1000 && dur % 1000 < 100) {
lcd_clear(); lcd_setCursor(1,0); lcd_print("MAINTENIR POUR");
lcd_setCursor(1,1); lcd_print("EFFACER EEPROM");
char buf[21]; snprintf(buf, 21, "%d secondes...", (BTN_EEPROM_LONG_PRESS - dur) / 1000 + 1);
lcd_setCursor(5,2); lcd_print(buf); beep();
}
}
}
// ====================== MODE REMPLISSAGE AVEC REGLAGE TEMPS ======================
void mode_remplissage_avec_reglage_temps() {
tempsReglageProvisoire = tempsRemplissageMinutes;
reglageTempsActif = true;
ecran_reglage_temps();
reglageTempsActif = false;
volumeReferenceRemplissage = moyenne(tableau_mesures);
volumeMaxDetecte = 0.0f;
debutRemplissageMode = millis();
remplissageDetecte = false;
currentDisplayMode = -1;
resetLineCache();
demarrerChenillard();
int localCLKState = HIGH;
int localSWState = HIGH;
unsigned long localSWPress = 0;
while (true) {
esp_task_wdt_reset(); ArduinoCloud.update();
gerer_led_wifi_cloud(); updateChenillardLED();
if (millis() - lastPing >= 300) { lastPing = millis(); mesure_et_cloud(); }
afficher_remplissage_en_cours();
unsigned long tempsEcoule = millis() - debutRemplissageMode;
unsigned long tempsRestant = (tempsRemplissageMinutes * 60000UL) - tempsEcoule;
int minutesRestantes = max(0, (int)(tempsRestant / 60000UL));
char buf[21];
snprintf(buf, 21, "%.2f L %.2f %%", moyenne(tableau_mesures), moyenne(tableau_mesures) * 100.0f / CAPACITE);
updateLine(1, buf);
char buf2[8];
snprintf(buf2, 8, "%3d", minutesRestantes);
lcd_setCursor(17, 1); lcd_print(buf2);
float volActuel = moyenne(tableau_mesures);
float gain = volActuel - volumeReferenceRemplissage;
if (gain > volumeMaxDetecte) volumeMaxDetecte = gain;
if (gain >= SEUIL_MIN_REMPLISSAGE_MANUEL) remplissageDetecte = true;
int clk = digitalRead(KY040_CLK);
int sw = digitalRead(KY040_SW);
if (clk != localCLKState && clk == LOW) localCLKState = clk;
if (sw == LOW && localSWState == HIGH) localSWPress = millis();
else if (sw == HIGH && localSWState == LOW) {
unsigned long dur = millis() - localSWPress;
if (dur > 50 && dur < LONG_PRESS_DURATION) {
if (gain >= SEUIL_MIN_REMPLISSAGE_MANUEL) {
if (confirmer_action("Valider remplissage ?")) {
char d[12], h[10]; lire_date_heure(d, h);
strcpy(remplissages[idxRemplissage].date, d);
strcpy(remplissages[idxRemplissage].heure, h);
remplissages[idxRemplissage].volumeIntroduit = gain;
idxRemplissage = (idxRemplissage + 1) % NB_REMPLISSAGES;
if (nb_remplissages_reels < NB_REMPLISSAGES) ++nb_remplissages_reels;
beep_long();
lcd_clear(); resetLineCache();
lcd_setCursor(2,1); lcd_print("REMPLISSAGE");
lcd_setCursor(2,2); lcd_print("ENREGISTRE !");
delay(2000);
}
}
break;
}
}
localSWState = sw;
if (millis() - debutRemplissageMode >= tempsRemplissageMinutes * 60000UL) {
if (!remplissageDetecte || volumeMaxDetecte < SEUIL_MIN_REMPLISSAGE_MANUEL) {
lcd_clear(); resetLineCache();
lcd_setCursor(1,1); lcd_print("VOLUME TROP FAIBLE");
lcd_setCursor(3,2); lcd_print("MIN: 5L");
beep_long(); delay(3000);
}
break;
}
delay(10);
}
inRemplissageMode = false;
arreterChenillard();
ancien_niveau = moyenne(tableau_mesures);
currentDisplayMode = -1;
resetLineCache();
}
// ====================== SETUP ======================
void setup() {
Serial.begin(115200);
// Allumage
pinMode(3,OUTPUT);
pinMode(BpLCD, INPUT_PULLUP);
digitalWrite(RelaisLCD, LOW);
pinMode(1,OUTPUT);
pinMode(BpLEDS, INPUT_PULLUP);
digitalWrite(RelaisLEDS, LOW);
// ALLUMAGE
pinMode(BUZZER, OUTPUT); digitalWrite(BUZZER, LOW);
pinMode(LED_WIFI, OUTPUT); pinMode(TRIGGER, OUTPUT); pinMode(ECHO, INPUT);
digitalWrite(TRIGGER, LOW);
pinMode(KY040_CLK, INPUT_PULLUP); pinMode(KY040_DT, INPUT_PULLUP); pinMode(KY040_SW, INPUT_PULLUP);
pinMode(BTN_ERASE_EEPROM, INPUT_PULLUP);
for (int p : {LED_VERTE1,LED_VERTE2,LED_JAUNE1,LED_JAUNE2,LED_ORANGE1,LED_ROUGE1,LED_ROUGE2}) pinMode(p, OUTPUT);
esp_task_wdt_init(WDT_TIMEOUT, true); esp_task_wdt_add(NULL);
Wire.begin(SDA, SCL); lcd_init();
byte bars[5][8] = {
{0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10},
{0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18},
{0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C},
{0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E},
{0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F}
};
for (int i = 0; i < 5; ++i) lcd_createChar(i, bars[i]);
lcd_clear(); lcd_setCursor(3,0); lcd_print("CUVE FUEL 1.9.8");
lcd_setCursor(3,1); lcd_print("INITIALISATION...");
lcd_setCursor(3,2); lcd_print("ATTENTE MESURES");
lcd_setCursor(2,3); lcd_print("VINCENT MAGLIOLO");
chargerTempsRemplissage();
dht.begin();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
WiFi.begin();
unsigned long t = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t < 15000) { esp_task_wdt_reset(); delay(100); }
chargerBuckets();
initProperties(); ArduinoCloud.begin(ArduinoIoTPreferredConnection);
setDebugMessageLevel(4); ArduinoCloud.printDebugInfo();
swLastState = digitalRead(KY040_SW);
delay(2000); lcd_clear(); currentDisplayMode = -1;
cycleStartTime = millis(); showingWifiStatus = true;
}
// ====================== MESURE ET CLOUD ======================
void mesure_et_cloud() {
float temp = dht.readTemperature(), hum = dht.readHumidity();
if (isnan(temp)) temp = 20.0f; if (isnan(hum)) hum = 50.0f;
temperature = temp; humidity = hum;
tableau_temp[index_mesure] = temp; tableau_hum[index_mesure] = hum;
unsigned int dcm = 0;
for (int i = 0; i < 3 && !dcm; ++i) dcm = lire_distance_median();
bool def = (dcm == 0 || dcm > HAUTEUR || dcm < 5);
if (def) {
if (++mauvaises_lectures_consecutives >= SEUIL_MAUVAISES_LECTURES && !alerte_sonde) { alerte_sonde = true; beep_long(); }
} else {
mauvaises_lectures_consecutives = 0;
if (alerte_sonde) alerte_sonde = false;
}
if (alerte_sonde) return;
float corr = getCorrectionTempHum(moyenne(tableau_temp), moyenne(tableau_hum));
float dcorr = dcm + corr;
distance = dcorr + dcc;
float hfuel = HAUTEUR - dcorr;
float vol = hfuel * LITRES_CM;
if (vol > 0 && vol <= CAPACITE) {
fuel = vol;
tableau_mesures[index_mesure] = vol;
beep();
if (++index_mesure >= NB_MESURES) { index_mesure = 0; tableau_plein = true; }
if (++nb_mesures_valides >= MIN_MESURES_INITIALISATION && !systeme_initialise) {
float init = moyenne(tableau_mesures);
for (int i = 0; i < NB_MESURES; ++i) tableau_mesures[i] = init;
ancien_niveau = init; systeme_initialise = true;
}
}
if (systeme_initialise && tableau_plein && index_mesure == 0) {
float vol_moy = moyenne(tableau_mesures);
pourcentage = vol_moy * 100.0f / CAPACITE;
// 🔒 Attendre au moins 3 mesures stables avant de détecter une conso
static int mesures_stables = 0;
float diff = vol_moy - ancien_niveau;
if (abs(diff) <= SEUIL_CONSOMMATION) {
mesures_stables++;
} else {
mesures_stables = 0;
}
if (diff < -SEUIL_CONSOMMATION && mesures_stables >= 3) {
float delta_conso = -diff;
if (!conso_en_cours) {
conso_detectee = delta_conso;
lire_date_heure(date_conso, heure_conso);
conso_en_cours = true;
} else {
conso_detectee += delta_conso;
}
ajouterConsoDansBucket(delta_conso);
flag_conso_detectee = true;
debut_affichage_conso = millis();
beep();
ancien_niveau = vol_moy;
mesures_stables = 0;
} else if (conso_en_cours && abs(diff) <= SEUIL_CONSOMMATION) {
conso_en_cours = false;
}
tableau_plein = false;
}
ch = getConso(true);
cj = getConso(false, true);
float pc = pourcentage;
alerte_fuel_orange = (pc <= ALERTE_ORANGE_PCT && pc > ALERTE_ROUGE_PCT);
alerte_fuel_rouge = (pc <= ALERTE_ROUGE_PCT && pc > ALERTE_CRITIQUE_PCT);
alerte_fuel_critique = (pc <= ALERTE_CRITIQUE_PCT && pc > ALERTE_TRES_CRITIQUE_PCT);
alerte_fuel_tres_critique = (pc <= ALERTE_TRES_CRITIQUE_PCT);
}
// ====================== LOOP ======================
void loop() {
esp_task_wdt_reset();
ArduinoCloud.update();
forcerBucketCourant();
gerer_bouton_eeprom();
// ALLUMAGE EXTINCTION DU LCD
// Si le bouton 35 avec RELAIS 03 est pressé et l'allumage n'est pas en fonction
if (digitalRead(BpLCD) == LOW && allumageTimer == 0)
{
delay(250); // "anti-rebonds" du bouton
digitalWrite(RelaisLCD, HIGH); // Allumer le LCD
allumageTimer = millis(); // Démarrage du chrono
}
// Si allumage en fonction et allumageTemps échu
if (allumageTimer != 0 && (millis()-allumageTimer >= allumageTemps))
{
digitalWrite(RelaisLCD, LOW); // Eteindre le LCD
allumageTimer = 0; // Arrêt du chrono
}
// Si le bouton est pressé et l'allumage est en fonction
if (digitalRead(BpLCD) == LOW && allumageTimer != 0)
{
delay(250); // "anti-rebonds" du bouton
digitalWrite(RelaisLCD, LOW); // Eteindre le LCD
allumageTimer = 0; // Arrêt du chrono
}
// ALLUMAGE EXTINCTION DES LEDS
// Si le bouton 34 et RELAIS 1 est pressé et l'allumage n'est pas en fonction
if (digitalRead(BpLEDS) == LOW && allumageTimer == 0)
{
delay(250); // "anti-rebonds" du bouton
digitalWrite(RelaisLEDS, HIGH); // Allumer les LEDS
allumageTimer = millis(); // Démarrage du chrono
}
// Si allumage en fonction et allumageTemps échu
if (allumageTimer != 0 && (millis()-allumageTimer >= allumageTemps))
{
digitalWrite(RelaisLEDS, LOW); // Eteindre les LEDS
allumageTimer = 0; // Arrêt du chrono
}
// Si le bouton est pressé et l'allumage est en fonction
if (digitalRead(BpLEDS) == LOW && allumageTimer != 0)
{
delay(250); // "anti-rebonds" du bouton
digitalWrite(RelaisLEDS, LOW); // Eteindre les LEDS
allumageTimer = 0; // Arrêt du chrono
}
int clk = digitalRead(KY040_CLK);
int dt = digitalRead(KY040_DT);
int sw = digitalRead(KY040_SW);
if (!inRemplissageMode && !reglageTempsActif) {
if (clk != lastCLKState) {
if (clk == LOW && millis() - lastRotationTime > DEBOUNCE_ROTATION) {
if (dt != clk) menuItem = (menuItem + 1) % 12;
else menuItem = (menuItem - 1 + 12) % 12;
if (inMenu) displayMenu();
lastRotationTime = millis();
}
lastCLKState = clk;
}
}
if (sw == LOW && swLastState == HIGH) swPressStart = millis();
else if (sw == HIGH && swLastState == LOW) {
unsigned long dur = millis() - swPressStart;
if (dur > 50 && dur < LONG_PRESS_DURATION) {
if (inMenu) {
switch (menuItem) {
case 0: if (confirmer_action("Effacer alarmes ?")) { alerte_sonde = false; mauvaises_lectures_consecutives = 0; flag_conso_detectee = false; lcd_clear(); resetLineCache(); lcd_setCursor(2,1); lcd_print("ALARMES EFFACEES"); beep_long(); delay(1500); } break;
case 1: if (confirmer_action("Effacer conso ?")) { flag_conso_detectee = false; lcd_clear(); resetLineCache(); lcd_setCursor(2,1); lcd_print("CONSO EFFACEE"); beep_long(); delay(1500); } break;
case 2: if (confirmer_action("Effacer remplis. ?")) { memset(remplissages, 0, sizeof(remplissages)); idxRemplissage = nb_remplissages_reels = 0; lcd_clear(); resetLineCache(); lcd_setCursor(1,1); lcd_print("REMPLISSAGES EFFACES"); beep_long(); delay(1500); } break;
case 3: if (confirmer_action("Effacer stats ?")) { initBuckets(); sauvegarderBuckets(); lcd_clear(); resetLineCache(); lcd_setCursor(2,1); lcd_print("STATS EFFACEES"); beep_long(); delay(1500); } break;
case 4:
if (inRemplissageMode) {
lcd_clear(); resetLineCache(); lcd_setCursor(1,1); lcd_print("REMPLISSAGE ACTIF"); lcd_setCursor(2,2); lcd_print("ATTENDRE FIN..."); beep_long(); delay(2000);
} else {
inRemplissageMode = true;
inMenu = false;
mode_remplissage_avec_reglage_temps();
}
break;
case 5: if (confirmer_action("Effacer EEPROM ?")) { effacer_totale_eeprom(); ESP.restart(); } break;
case 6: inMenu = false; /* afficher_conso_horaire_7j(); */ break;
case 7: inMenu = false; /* afficher_conso_journaliere_60j(); */ break;
case 8: inMenu = false; /* afficher_conso_mensuelle_24m(); */ break;
case 9: inMenu = false; /* afficher_conso_annuelle_10a(); */ break;
case 10: inMenu = false; /* afficher_alarmes(); */ break;
case 11: inMenu = false; /* ecran_remplissages_opti(); */ break;
}
inMenu = false; currentDisplayMode = -1;
} else {
inMenu = true;
menuItem = 0;
displayMenu();
}
} else if (dur >= LONG_PRESS_DURATION) {
inMenu = false;
if (inRemplissageMode) {
inRemplissageMode = false;
arreterChenillard();
ancien_niveau = moyenne(tableau_mesures);
currentDisplayMode = -1;
resetLineCache();
}
}
}
swLastState = sw;
if (inRemplissageMode || reglageTempsActif) return;
if (inMenu) { gerer_led_wifi_cloud(); return; }
if (alerte_sonde) { gerer_led_wifi_cloud(); return; }
if (flag_conso_detectee && millis() - debut_affichage_conso < 6000) { ecran_consommation_opti(); gerer_led_wifi_cloud(); return; }
if (millis() - lastPing >= 300) { lastPing = millis(); mesure_et_cloud(); }
if (millis() - lastDisplay >= DISPLAY_DURATION) {
displayMode = (displayMode + 1) % 5;
lastDisplay = millis();
currentDisplayMode = -1;
}
switch (displayMode) {
case 0: {
float pc = pourcentage;
float dc = distance;
float hf = HAUTEUR - (dc - dcc);
afficher_fuel_opti(fuel, pc, dc, hf);
updateLEDs(fuel);
} break;
case 1: afficher_infos_capteurs_opti(); break;
case 2: afficher_conso_opti(); break;
case 3: afficher_conso_annuelle_opti(); break;
case 4: ecran_consommation_opti(); break;
}
gerer_led_wifi_cloud();
}
// ====================== UTILITAIRES ======================
float moyenne(float* tab) {
float s = 0; int c = 0;
for (int i = 0; i < NB_MESURES; ++i) if (tab[i] > 0) { s += tab[i]; ++c; }
return c ? s / c : (tab == tableau_temp ? 20.0f : (tab == tableau_hum ? 50.0f : 0.0f));
}
void beep() { digitalWrite(BUZZER, HIGH); delayMicroseconds(80000); digitalWrite(BUZZER, LOW); }
void beep_long() { digitalWrite(BUZZER, HIGH); delayMicroseconds(500000); digitalWrite(BUZZER, LOW); }
void lire_date_heure(char* d, char* h) {
struct tm ti;
if (getLocalTime(&ti)) {
strftime(d, 12, "%d/%m/%Y", &ti);
strftime(h, 10, "%H:%M:%S", &ti);
} else {
strcpy(d, "--/--/----"); strcpy(h, "--:--:--");
}
}
unsigned int lire_distance_median() {
unsigned int m[5];
for (int i = 0; i < 5; ++i) {
digitalWrite(TRIGGER, LOW); delayMicroseconds(25);
digitalWrite(TRIGGER, HIGH); delayMicroseconds(120);
digitalWrite(TRIGGER, LOW); delayMicroseconds(25);
unsigned long dur = pulseIn(ECHO, HIGH, TIMEOUT_US);
m[i] = (dur == 0) ? 0 : dur / 58;
if (m[i] < 2 || m[i] > MAX_DISTANCE) m[i] = 0;
delay(10);
}
std::sort(m, m + 5);
return m[2];
}
float getCorrectionTempHum(float t, float h) {
return (t - 20.0f) * 0.017f + (h - 50.0f) * 0.0018f;
}
// ====================== CLOUD CALLBACKS ======================
void onFuelChange() {}
void onPourcentageChange() {}
void onDistanceChange() {}
void onCjChange() {}
void onChChange() {}
/* =============================================================
FIN – VERSION 1.9.8-upgraded – Conso fiable + 3 mesures stables
============================================================= */