/*
* ========================================
* SYSTÈME DE CONTRÔLE DE POMPE ESP32
* ========================================
*
* Capteurs:
* - DHT22: Indicateur température ambiante
* - DS18B20 #1: Température réservoir (Pompe_Action_JP)
* - DS18B20 #2: Pic température retour (Retour_Pic_Temp_JP)
*
* Actionneurs:
* - Relay: Contrôle pompe
* - LED Verte: Relay activé
* - LED Rouge: Relay désactivé
* - LED Bleue: WiFi connecté
*
* Interface:
* - LCD I2C: Affichage état système
* - Bouton: Démarrage manuel/arrêt pompe
*/
#include <WiFi.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <time.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
// ========================================
// VARIABLES DE CONFIGURATION
// ========================================
// WiFi Configuration
/*
const char* WIFI_SSID = "TP-Link_DD7F";
const char* WIFI_PASSWORD = "53562244";
#define USE_STATIC_IP true
*/
/* 002 - START - a supprimer */
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASSWORD = "";
#define USE_STATIC_IP false
/* 002 - END - a supprimer */
IPAddress staticIP(192, 168, 0, 189); // Adresse IP statique
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);
// NTP Configuration
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 3600; // GMT+1 pour la Belgique
const int daylightOffset_sec = 3600; // Heure d'été
// Google Script URL
const char* GOOGLE_SCRIPT_URL = "https://script.google.com/macros/s/AKfycbyjog5khCAJohoE8hhBqePivipewDA-jVuLMDrliW8Gip7j8QvvrD_rYKIPXJV9Syq-/exec";
// Paramètres de contrôle pompe
const float TEMP_SEUIL_DHT22 = 12.0; // Température seuil DHT22 (°C)
const unsigned long DUREE_RELAY_MS = 300000; // Durée activation relay (5 min = 300000 ms)
const unsigned long INTERVALLE_CYCLES_MS = 3600000; // Intervalle entre cycles (1h = 3600000 ms)
// Pins GPIO
#define PIN_DHT22 4
#define PIN_DS18B20 5
#define PIN_RELAY 26
#define PIN_BUTTON 27
#define PIN_LED_VERTE 32
#define PIN_LED_ROUGE 33
#define PIN_LED_BLEUE 25
#define PIN_SDA 21
#define PIN_SCL 22
// ========================================
// INITIALISATION COMPOSANTS
// ========================================
DHT dht(PIN_DHT22, DHT22);
OneWire oneWire(PIN_DS18B20);
DallasTemperature sensors(&oneWire);
LiquidCrystal_I2C lcd(0x27, 16, 2); // Adresse I2C à ajuster si nécessaire (0x27 ou 0x3F)
// Adresses DS18B20
DeviceAddress sondePompe = {0x28, 0xEB, 0xEB, 0x69, 0x81, 0x22, 0x0B, 0xDF}; // Pompe_Action_JP
DeviceAddress sondeRetour = {0x28, 0xF1, 0x2E, 0xB3, 0xB1, 0x22, 0x09, 0xC3}; // Retour_Pic_Temp_JP
// ========================================
// VARIABLES D'ÉTAT
// ========================================
bool relayActif = false;
bool boutonPrecedent = HIGH;
unsigned long dernierCycle = 0;
unsigned long debutRelayActif = 0;
float tempDHT22 = 0;
float tempPompe = 0;
float tempRetour = 0;
float tempPicRetour = 0;
bool cycleEnCours = false;
bool arretManuel = false;
unsigned long dernierTestWifi = 0;
const unsigned long INTERVALLE_TEST_WIFI = 30000; // Test WiFi toutes les 30s
bool gWifiOK = false; // État WiFi pour fonction postOne
bool premierCycle = true; // Pour permettre démarrage immédiat au boot
enum EtatCycle {
ATTENTE,
DEMARRAGE_POMPE,
POMPE_ACTIVE,
MESURE_PIC,
ARRET_POMPE
};
EtatCycle etatCycle = ATTENTE;
// ========================================
// SETUP
// ========================================
void setup() {
Serial.begin(115200);
Serial.println("\n========================================");
Serial.println("Démarrage système contrôle pompe ESP32");
Serial.println("========================================");
// Configuration pins
pinMode(PIN_RELAY, OUTPUT);
pinMode(PIN_LED_VERTE, OUTPUT);
pinMode(PIN_LED_ROUGE, OUTPUT);
pinMode(PIN_LED_BLEUE, OUTPUT);
pinMode(PIN_BUTTON, INPUT_PULLUP);
// État initial
digitalWrite(PIN_RELAY, LOW);
digitalWrite(PIN_LED_ROUGE, HIGH);
digitalWrite(PIN_LED_VERTE, LOW);
digitalWrite(PIN_LED_BLEUE, LOW);
// Initialisation LCD
Wire.begin(PIN_SDA, PIN_SCL);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Demarrage...");
// Initialisation capteurs
dht.begin();
sensors.begin();
// Recherche des sondes DS18B20
Serial.println("\nRecherche des sondes DS18B20...");
Serial.print("Nombre de sondes trouvées: ");
Serial.println(sensors.getDeviceCount());
/*
if (sensors.getDeviceCount() >= 2) {
sensors.getAddress(sondePompe, 0);
sensors.getAddress(sondeRetour, 1);
Serial.println("Sondes DS18B20 configurées");
afficherAdresseDS18B20("Pompe", sondePompe);
afficherAdresseDS18B20("Retour", sondeRetour);
} else {
Serial.println("ERREUR: Moins de 2 sondes DS18B20 détectées!");
lcd.clear();
lcd.print("ERR: Sondes DS");
}
*/
// Connexion WiFi
connecterWiFi();
// Configuration NTP
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.println("Configuration NTP effectuée");
// Affichage prêt
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Systeme pret");
delay(2000);
Serial.println("========================================");
Serial.println("Système prêt!");
Serial.println("========================================\n");
}
// ========================================
// BOUCLE PRINCIPALE
// ========================================
void loop() {
// Lecture capteurs
lireCapteurs();
// Gestion bouton
gererBouton();
// Test périodique WiFi
testerWiFi();
// Logique de contrôle pompe
gererCyclePompe();
// Mise à jour affichage
mettreAJourAffichage();
delay(100);
}
// ========================================
// FONCTIONS CAPTEURS
// ========================================
void lireCapteurs() {
// Lecture DHT22
tempDHT22 = dht.readTemperature();
if (isnan(tempDHT22)) {
Serial.println("Erreur lecture DHT22");
tempDHT22 = 0;
}
// Lecture DS18B20
sensors.requestTemperatures();
tempPompe = sensors.getTempC(sondePompe);
tempRetour = sensors.getTempC(sondeRetour);
if (tempPompe == DEVICE_DISCONNECTED_C) {
Serial.println("Erreur lecture sonde Pompe");
Serial.println(tempPompe);
tempPompe = 0;
}
if (tempRetour == DEVICE_DISCONNECTED_C) {
Serial.println("Erreur lecture sonde Retour");
tempRetour = 0;
}
}
// ========================================
// GESTION BOUTON
// ========================================
void gererBouton() {
bool boutonActuel = digitalRead(PIN_BUTTON);
// Détection front descendant (bouton pressé)
if (boutonActuel == LOW && boutonPrecedent == HIGH) {
delay(50); // Anti-rebond
if (digitalRead(PIN_BUTTON) == LOW) {
Serial.println("\n>>> BOUTON PRESSÉ <<<");
if (!relayActif) {
// Démarrage manuel
Serial.println("Démarrage manuel de la pompe");
demarrerPompe("bouton");
arretManuel = false;
} else {
// Arrêt manuel
Serial.println("Arrêt manuel de la pompe");
arreterPompe("bouton");
arretManuel = true;
}
}
}
boutonPrecedent = boutonActuel;
}
// ========================================
// GESTION CYCLE POMPE
// ========================================
void gererCyclePompe() {
unsigned long maintenant = millis();
switch (etatCycle) {
case ATTENTE:
// Vérifier si conditions démarrage automatique sont remplies
// Debug des conditions
static unsigned long dernierDebug = 0;
if (maintenant - dernierDebug >= 5000) { // Debug toutes les 5s
dernierDebug = maintenant;
Serial.println("\n=== DEBUG CONDITIONS ===");
Serial.print("Temp DHT22: "); Serial.print(tempDHT22); Serial.println("°C");
Serial.print("Seuil: "); Serial.print(TEMP_SEUIL_DHT22); Serial.println("°C");
Serial.print("Arrêt manuel: "); Serial.println(arretManuel ? "OUI" : "NON");
Serial.print("Temps depuis dernier cycle: "); Serial.print((maintenant - dernierCycle) / 1000); Serial.println("s");
Serial.print("Intervalle requis: "); Serial.print(INTERVALLE_CYCLES_MS / 1000); Serial.println("s");
Serial.print("Temp < Seuil: "); Serial.println(tempDHT22 < TEMP_SEUIL_DHT22 ? "OUI" : "NON");
Serial.print("Temp > 0: "); Serial.println(tempDHT22 > 0 ? "OUI" : "NON");
Serial.print("Délai OK: "); Serial.println((maintenant - dernierCycle >= INTERVALLE_CYCLES_MS) ? "OUI" : "NON");
Serial.println("========================\n");
}
if (!arretManuel &&
tempDHT22 < TEMP_SEUIL_DHT22 &&
tempDHT22 > 0 &&
(premierCycle || (maintenant - dernierCycle >= INTERVALLE_CYCLES_MS))) {
Serial.println("\n>>> CONDITIONS REMPLIES: DÉMARRAGE AUTO <<<");
premierCycle = false; // Désactiver après le premier démarrage
demarrerPompe("auto");
}
break;
case DEMARRAGE_POMPE:
// Envoyer données démarrage
postOne("Pompe_Action_JP", tempPompe, "auto(on)");
etatCycle = POMPE_ACTIVE;
tempPicRetour = tempRetour; // Initialiser pic
break;
case POMPE_ACTIVE:
// Surveiller pic température retour
if (tempRetour > tempPicRetour) {
tempPicRetour = tempRetour;
}
// Vérifier durée d'activation
if (maintenant - debutRelayActif >= DUREE_RELAY_MS) {
Serial.println("\n>>> FIN DURÉE ACTIVATION <<<");
etatCycle = MESURE_PIC;
}
break;
case MESURE_PIC:
// Envoyer pic température retour
postOne("Retour_Pic_Temp_JP", tempPicRetour, "auto(mesure)");
etatCycle = ARRET_POMPE;
break;
case ARRET_POMPE:
// Arrêter pompe et envoyer température finale
arreterPompe("auto");
postOne("Pompe_Action_JP", tempPompe, "auto(off)");
etatCycle = ATTENTE;
dernierCycle = maintenant;
arretManuel = false;
break;
}
}
// ========================================
// CONTRÔLE RELAY
// ========================================
void demarrerPompe(String mode) {
relayActif = true;
digitalWrite(PIN_RELAY, HIGH);
digitalWrite(PIN_LED_VERTE, HIGH);
digitalWrite(PIN_LED_ROUGE, LOW);
debutRelayActif = millis();
if (mode == "auto") {
etatCycle = DEMARRAGE_POMPE;
} else {
// Démarrage bouton: envoyer données immédiatement
postOne("Pompe_Action_JP", tempPompe, "bouton(on)");
etatCycle = POMPE_ACTIVE;
tempPicRetour = tempRetour;
}
Serial.println("Pompe démarrée - Mode: " + mode);
}
void arreterPompe(String mode) {
relayActif = false;
digitalWrite(PIN_RELAY, LOW);
digitalWrite(PIN_LED_VERTE, LOW);
digitalWrite(PIN_LED_ROUGE, HIGH);
if (mode == "bouton") {
// Arrêt manuel: envoyer pic et température finale
postOne("Retour_Pic_Temp_JP", tempPicRetour, "bouton(mesure)");
postOne("Pompe_Action_JP", tempPompe, "bouton(off)");
etatCycle = ATTENTE;
dernierCycle = millis();
}
Serial.println("Pompe arrêtée - Mode: " + mode);
}
// ========================================
// COMMUNICATION
// ========================================
// Fonction pour obtenir timestamp ISO
String nowISO() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "";
}
char buffer[30];
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &timeinfo);
return String(buffer);
}
// Vérification synchronisation NTP
bool verifierSyncNTP() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("NTP non synchronisé");
return false;
}
Serial.println("NTP synchronisé: " + nowISO());
return true;
}
// Encodage URL pour caractères spéciaux
static String urlEncode_(const String& s) {
String out;
out.reserve(s.length()*3);
for (size_t i=0; i<s.length(); ++i) {
unsigned char c = (unsigned char)s[i];
if (('a'<=c && c<='z')||('A'<=c && c<='Z')||('0'<=c && c<='9')||c=='-'||c=='_'||c=='.'||c=='~')
out += (char)c;
else if (c==' ')
out += '+';
else {
char b[4];
snprintf(b, sizeof(b), "%%%02X", c);
out += b;
}
}
return out;
}
// Fonction principale d'envoi de données en GET (Google Apps Script n'accepte pas POST)
bool postOne(const char* sondeName, float tempC, const char* mode) {
if (!gWifiOK || WiFi.status() != WL_CONNECTED) {
Serial.println("[GAS] WiFi non connecté");
return false;
}
// Délai entre les requêtes pour éviter le rate limiting de Google
static unsigned long derniereRequete = 0;
unsigned long maintenant = millis();
unsigned long delaiDepuisDerniereRequete = maintenant - derniereRequete;
if (derniereRequete > 0 && delaiDepuisDerniereRequete < 2000) {
unsigned long delaiRestant = 2000 - delaiDepuisDerniereRequete;
Serial.printf("[GAS] Attente de %lu ms avant envoi...\n", delaiRestant);
delay(delaiRestant);
}
// Construction de l'URL avec paramètres GET
String url = String(GOOGLE_SCRIPT_URL);
url += "?sondeName=" + urlEncode_(String(sondeName));
url += "&tempC=" + String(tempC, 2);
url += "&mode=" + urlEncode_(String(mode));
WiFiClientSecure client;
client.setInsecure(); // Pour validation; utiliser root CA en production
client.setTimeout(20000);
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.setConnectTimeout(15000);
http.setReuse(false); // HTTP/1.1, pas de keep-alive
Serial.printf("[GAS] GET %s\n", url.c_str());
if (!http.begin(client, url)) {
Serial.println("[GAS] http.begin() a échoué");
return false;
}
http.addHeader("User-Agent", "ESP32-Module/1.0");
int code = http.GET();
String err = http.errorToString(code);
String resp = (code > 0) ? http.getString() : "";
http.end();
derniereRequete = millis(); // Mettre à jour le timestamp après la requête
Serial.printf("[GAS] GET -> code=%d (%s)\n", code, err.c_str());
if (resp.length() && resp.length() < 200) { // Afficher seulement si réponse courte
Serial.println("[GAS] Réponse: " + resp);
}
if (code >= 200 && code < 300) {
Serial.println("[GAS] ✓ Données envoyées avec succès");
return true;
}
Serial.println("[GAS] ✗ Échec envoi données");
return false;
}
void connecterWiFi() {
Serial.print("\nConnexion WiFi à: ");
Serial.println(WIFI_SSID);
lcd.clear();
lcd.print("WiFi...");
// Configuration IP statique si activée
if (USE_STATIC_IP) {
Serial.println("Configuration IP statique...");
WiFi.config(staticIP, gateway, subnet, primaryDNS);
} else {
Serial.println("Configuration DHCP...");
}
// Mode WiFi Station
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int tentatives = 0;
while (WiFi.status() != WL_CONNECTED && tentatives < 40) { // 40 tentatives = 20 secondes
delay(500);
Serial.print(".");
lcd.setCursor(tentatives % 16, 1); // Animation sur LCD
lcd.print(".");
tentatives++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi connecté!");
Serial.print("Adresse IP: ");
Serial.println(WiFi.localIP());
Serial.print("Force signal (RSSI): ");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
digitalWrite(PIN_LED_BLEUE, HIGH);
gWifiOK = true;
lcd.clear();
lcd.print("WiFi OK");
lcd.setCursor(0, 1);
lcd.print(WiFi.localIP());
delay(2000);
} else {
Serial.println("\nÉchec connexion WiFi!");
Serial.println("Vérifiez le SSID et le mot de passe");
gWifiOK = false;
lcd.clear();
lcd.print("WiFi ERREUR");
lcd.setCursor(0, 1);
lcd.print("Verif config");
}
}
void testerWiFi() {
unsigned long maintenant = millis();
if (maintenant - dernierTestWifi >= INTERVALLE_TEST_WIFI) {
dernierTestWifi = maintenant;
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi déconnecté! Reconnexion...");
digitalWrite(PIN_LED_BLEUE, LOW);
connecterWiFi();
} else {
digitalWrite(PIN_LED_BLEUE, HIGH);
}
}
}
void envoyerDonnees(String nomSonde, float temperature, String mode) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Erreur: WiFi non connecté, impossible d'envoyer les données");
return;
}
HTTPClient http;
// Obtenir horodatage
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("Erreur obtention heure NTP");
}
char horodatage[30];
strftime(horodatage, sizeof(horodatage), "%Y-%m-%d %H:%M:%S", &timeinfo);
// Construction URL avec paramètres
String url = String(GOOGLE_SCRIPT_URL);
url += "?sonde=" + urlEncode(nomSonde);
url += "&temperature=" + String(temperature, 2);
url += "&mode=" + urlEncode(mode);
url += "×tamp=" + urlEncode(String(horodatage));
Serial.println("\n--- ENVOI DONNÉES ---");
Serial.println("URL: " + url);
http.begin(url);
http.setTimeout(10000);
int httpCode = http.GET();
if (httpCode > 0) {
Serial.print("Code réponse HTTP: ");
Serial.println(httpCode);
if (httpCode == HTTP_CODE_OK) {
String response = http.getString();
Serial.println("Réponse: " + response);
Serial.println("✓ Données envoyées avec succès");
}
} else {
Serial.print("Erreur HTTP: ");
Serial.println(http.errorToString(httpCode));
}
http.end();
Serial.println("---------------------\n");
}
// Encodage URL pour caractères spéciaux
String urlEncode(String str) {
String encodedString = "";
char c;
char code0;
char code1;
for (int i = 0; i < str.length(); i++) {
c = str.charAt(i);
if (c == ' ') {
encodedString += '+';
} else if (isalnum(c)) {
encodedString += c;
} else {
code1 = (c & 0xf) + '0';
if ((c & 0xf) > 9) {
code1 = (c & 0xf) - 10 + 'A';
}
c = (c >> 4) & 0xf;
code0 = c + '0';
if (c > 9) {
code0 = c - 10 + 'A';
}
encodedString += '%';
encodedString += code0;
encodedString += code1;
}
}
return encodedString;
}
// ========================================
// AFFICHAGE
// ========================================
void mettreAJourAffichage() {
static unsigned long dernierMaj = 0;
unsigned long maintenant = millis();
if (maintenant - dernierMaj >= 2000) { // Mise à jour toutes les 2s
dernierMaj = maintenant;
lcd.clear();
// Ligne 1: État relay + température DHT22
lcd.setCursor(0, 0);
if (relayActif) {
lcd.print("POMPE ON ");
} else {
lcd.print("Attente ");
}
lcd.print(tempDHT22, 1);
lcd.print("C");
// Ligne 2: Températures DS18B20
lcd.setCursor(0, 1);
lcd.print("B:");
lcd.print(tempPompe, 1);
lcd.print(" R:");
lcd.print(tempRetour, 1);
}
}
void afficherAdresseDS18B20(String nom, DeviceAddress addr) {
Serial.print("Adresse sonde ");
Serial.print(nom);
Serial.print(": ");
for (uint8_t i = 0; i < 8; i++) {
if (addr[i] < 16) Serial.print("0");
Serial.print(addr[i], HEX);
if (i < 7) Serial.print(":");
}
Serial.println();
}