// --- Bibliothèques nécessaires ---
// #include <Wire.h> // Commenté car Wokwi gère implicitement la communication I2C
#include <LiquidCrystal_I2C.h> // Pour les afficheurs LCD I2C
#include <Ultrasonic.h> // Pour le capteur à ultrasons HC-SR04
#include <RotaryEncoder.h> // Pour l'encodeur rotatif KY-040
// --- Définitions des broches (selon votre diagram.json) ---
#define TRIG_PIN 9 // Broche Trigger du capteur à ultrasons
#define ECHO_PIN 10 // Broche Echo du capteur à ultrasons
#define ENCODER_CLK_PIN 2 // Broche CLK de l'encodeur rotatif
#define ENCODER_DT_PIN 3 // Broche DT de l'encodeur rotatif
#define ENCODER_SW_PIN 4 // Broche SW (bouton) de l'encodeur rotatif
#define RELAY_PIN 7 // Broche de contrôle du relais de la pompe
#define BUZZER_PIN 8 // Broche de contrôle du buzzer d'alarme
// --- Adresses I2C des LCDs ---
// Assurez-vous que ces adresses correspondent à celles de votre diagram.json
// et à vos modules physiques si vous en avez.
#define LCD_MENU_ADDRESS 0x27 // Adresse du LCD pour le menu (celui qui affichera 0x27)
#define LCD_INFO_ADDRESS 0x26 // Adresse du LCD pour les informations (celui qui affichera 0x26)
// --- Initialisation des objets ---
LiquidCrystal_I2C lcd_menu(LCD_MENU_ADDRESS, 20, 4); // 20 colonnes, 4 lignes, adresse 0x27
LiquidCrystal_I2C lcd_info(LCD_INFO_ADDRESS, 20, 4); // 20 colonnes, 4 lignes, adresse 0x26
Ultrasonic ultrasonic(TRIG_PIN, ECHO_PIN);
RotaryEncoder encoder(ENCODER_CLK_PIN, ENCODER_DT_PIN);
// --- Constantes et seuils du réservoir ---
// Ces valeurs DOIVENT être ajustées en fonction de votre réservoir réel.
// Distance entre le capteur et le fond du réservoir lorsque le réservoir est VIDE (en cm).
const float DISTANCE_CAPTEUR_FOND_VIDE = 150.0; // Exemple: 150 cm quand le réservoir est vide
// Hauteur maximale d'eau dans le réservoir (en cm).
const float HAUTEUR_MAX_EAU = 140.0; // Exemple: 140 cm de hauteur d'eau max
// Seuils de contrôle et d'alarme (en pourcentage)
const int SEUIL_POMPE_DEMARRAGE = 90; // La pompe démarre si le niveau atteint ou dépasse 90%
const int SEUIL_POMPE_ARRET = 10; // La pompe s'arrête si le niveau descend à 10% ou moins
const int SEUIL_ALARME_HAUT = 95; // Alarme si le niveau dépasse 95% (pour éviter les débordements)
// --- Variables d'état globales ---
long lastEncoderPos = 0; // Dernière position de l'encodeur
int currentMenuSelection = 0; // 0: Mode Auto, 1: Mode Manuel
const char* menuItems[] = {"Mode: AUTO", "Mode: MANUEL"};
const int numMenuItems = sizeof(menuItems) / sizeof(menuItems[0]);
bool pumpManualOverride = false; // Vrai si la pompe est contrôlée manuellement
bool pumpStatus = false; // false = OFF, true = ON
bool alarmActive = false; // true si l'alarme est active
unsigned long lastPumpToggleTime = 0; // Pour éviter les basculements trop rapides de la pompe
const unsigned long PUMP_TOGGLE_DEBOUNCE_TIME = 500; // 500 ms
unsigned long lastReadTime = 0;
const unsigned long READ_INTERVAL = 200; // Lire le capteur toutes les 200 ms
// --- Variables de mesure ---
float currentDistanceCm = 0.0;
float currentWaterLevelCm = 0.0;
float currentPercentage = 0.0;
const char* currentStatus = "INCONNU";
// --- Fonctions de prototypage ---
void readUltrasonicSensor();
void updateLCDs(float distanceCm, float percentage, const char* status);
void handleEncoder();
void controlPump(float percentage);
void activateAlarm(bool state);
const char* getWaterLevelStatus(float percentage);
// --- Setup : exécuté une seule fois au démarrage ---
void setup() {
// Wire.begin(); // Non nécessaire si Wokwi gère implicitement la communication I2C
// Initialisation des LCDs (lcd_menu, lcd_info)
lcd_menu.init();
lcd_menu.backlight(); // Allumer le rétroéclairage
lcd_info.init();
lcd_info.backlight(); // Allumer le rétroéclairage
// --- PARTIE CRUCIALE : Affichage des adresses I2C DISTINCTES au démarrage ---
// Pour le LCD Menu (adresse 0x27)
lcd_menu.clear();
lcd_menu.setCursor(0, 0);
lcd_menu.print("LCD Menu"); // Texte pour le LCD du menu
lcd_menu.setCursor(0, 1);
lcd_menu.print("Address: 0x27"); // Son adresse unique
// Pour le LCD Info (adresse 0x26)
lcd_info.clear();
lcd_info.setCursor(0, 0);
lcd_info.print("LCD Info"); // Texte pour le LCD d'info
lcd_info.setCursor(0, 1);
lcd_info.print("Address: 0x26"); // Son adresse unique
delay(3000); // Laisse un temps pour lire les adresses (3 secondes)
// --- FIN PARTIE CRUCIALE ---
// Configuration des broches
pinMode(RELAY_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(ENCODER_SW_PIN, INPUT_PULLUP); // Bouton de l'encodeur avec résistance de tirage interne
// Éteindre la pompe et l'alarme au démarrage
digitalWrite(RELAY_PIN, LOW); // Relais LOW = pompe OFF (vérifier la logique de votre relais)
digitalWrite(BUZZER_PIN, LOW);
// Affichage initial du système de réservoir (votre code original)
lcd_menu.clear();
lcd_menu.setCursor(0, 0);
lcd_menu.print("Systeme Reservoir");
lcd_menu.setCursor(0, 1);
lcd_menu.print("Demarrage...");
lcd_menu.setCursor(0, 2);
lcd_menu.print("Veuillez patienter");
lcd_info.clear();
lcd_info.setCursor(0, 0);
lcd_info.print("Initialisation...");
lcd_info.setCursor(0, 1);
lcd_info.print("Capteur...");
delay(2000); // Petite pause pour l'initialisation
}
// --- Loop : exécuté en continu ---
void loop() {
// Lire le capteur à intervalles réguliers
if (millis() - lastReadTime > READ_INTERVAL) {
readUltrasonicSensor();
lastReadTime = millis();
}
// Gérer l'encodeur rotatif
handleEncoder();
// Mettre à jour les afficheurs LCD
updateLCDs(currentDistanceCm, currentPercentage, currentStatus);
// Contrôler la pompe (logique automatique ou manuelle)
controlPump(currentPercentage);
// Gérer l'alarme
activateAlarm(currentPercentage > SEUIL_ALARME_HAUT);
}
// --- Implémentation des fonctions ---
/**
* @brief Lit la distance du capteur à ultrasons et calcule le niveau d'eau et le pourcentage.
*/
void readUltrasonicSensor() {
float distance = ultrasonic.distanceRead();
if (distance == 0) { // Si la lecture est invalide (hors de portée, erreur)
currentDistanceCm = -1.0; // Indique une erreur
currentWaterLevelCm = 0.0;
currentPercentage = 0.0;
currentStatus = "ERREUR";
} else {
currentDistanceCm = distance;
currentWaterLevelCm = DISTANCE_CAPTEUR_FOND_VIDE - currentDistanceCm;
if (currentWaterLevelCm < 0) {
currentWaterLevelCm = 0;
}
if (currentWaterLevelCm > HAUTEUR_MAX_EAU) {
currentWaterLevelCm = HAUTEUR_MAX_EAU;
}
currentPercentage = (currentWaterLevelCm / HAUTEUR_MAX_EAU) * 100.0;
if (currentPercentage < 0) currentPercentage = 0;
if (currentPercentage > 100) currentPercentage = 100;
currentStatus = getWaterLevelStatus(currentPercentage);
}
}
/**
* @brief Met à jour les informations sur les deux afficheurs LCD.
* @param distanceCm Distance mesurée par le capteur en cm.
* @param percentage Pourcentage du niveau d'eau.
* @param status État du niveau d'eau (chaîne de caractères).
*/
void updateLCDs(float distanceCm, float percentage, const char* status) {
// --- LCD Menu (lcd_menu) ---
lcd_menu.setCursor(0, 0);
lcd_menu.print(menuItems[currentMenuSelection]); // Affiche le mode sélectionné
lcd_menu.print(" "); // Efface le reste de la ligne
// Afficher l'état de la pompe
lcd_menu.setCursor(0, 1);
lcd_menu.print("Pompe: ");
lcd_menu.print(pumpStatus ? "ON " : "OFF");
if (currentMenuSelection == 0) { // Mode Auto
lcd_menu.print(" (AUTO) ");
} else { // Mode Manuel
lcd_menu.print(" (MANUEL)");
}
// Afficher l'état de l'alarme
lcd_menu.setCursor(0, 2);
lcd_menu.print("Alarme: ");
if (alarmActive) {
lcd_menu.print("ACTIVE! ");
} else {
lcd_menu.print("OFF ");
}
// Effacer la ligne 3 si non utilisée
lcd_menu.setCursor(0,3);
lcd_menu.print(" "); // Efface la ligne
// --- LCD Informations (lcd_info) ---
lcd_info.setCursor(0, 0);
lcd_info.print("Dist. Capteur: ");
if (distanceCm == -1.0) {
lcd_info.print("ERREUR");
} else {
lcd_info.print(distanceCm, 1); // 1 décimale
lcd_info.print(" cm ");
}
lcd_info.setCursor(0, 1);
lcd_info.print("Niveau Eau: ");
lcd_info.print(currentWaterLevelCm, 1); // 1 décimale
lcd_info.print(" cm ");
lcd_info.setCursor(0, 2);
lcd_info.print("Pourcentage: ");
lcd_info.print(percentage, 0); // Pas de décimale
lcd_info.print(" % ");
lcd_info.setCursor(0, 3);
lcd_info.print("Etat: ");
lcd_info.print(status);
lcd_info.print(" "); // Efface le reste de la ligne
}
/**
* @brief Gère les entrées de l'encodeur rotatif (rotation et bouton).
*/
void handleEncoder() {
encoder.tick(); // Met à jour l'état de l'encodeur
long newPos = encoder.getPosition();
if (newPos != lastEncoderPos) {
if (newPos > lastEncoderPos) {
// Rotation dans le sens horaire (CW)
currentMenuSelection = (currentMenuSelection + 1) % numMenuItems;
} else {
// Rotation dans le sens anti-horaire (CCW)
currentMenuSelection = (currentMenuSelection - 1 + numMenuItems) % numMenuItems;
}
lastEncoderPos = newPos;
// Mettre à jour le LCD du menu immédiatement après une rotation
lcd_menu.clear();
lcd_menu.setCursor(0,0);
lcd_menu.print(menuItems[currentMenuSelection]);
}
// Gérer le bouton de l'encodeur (appui)
static unsigned long lastButtonPress = 0;
if (digitalRead(ENCODER_SW_PIN) == LOW && millis() - lastButtonPress > 100) {
lastButtonPress = millis();
// Si on est en mode manuel, basculer l'état de la pompe
if (currentMenuSelection == 1) { // Mode Manuel
if (millis() - lastPumpToggleTime > PUMP_TOGGLE_DEBOUNCE_TIME) {
pumpManualOverride = !pumpManualOverride; // Bascule l'état manuel
pumpStatus = pumpManualOverride; // La pompe prend l'état de l'override manuel
digitalWrite(RELAY_PIN, pumpStatus ? HIGH : LOW); // Contrôle le relais
lastPumpToggleTime = millis();
}
}
}
}
/**
* @brief Contrôle l'état de la pompe en fonction du mode (Auto/Manuel) et du pourcentage d'eau.
* @param percentage Pourcentage actuel du niveau d'eau.
*/
void controlPump(float percentage) {
if (currentMenuSelection == 0) { // Mode Auto
// Désactiver l'override manuel si on est en mode auto
pumpManualOverride = false;
if (percentage >= SEUIL_POMPE_DEMARRAGE && !pumpStatus) {
// Démarrer la pompe si le niveau est haut et qu'elle est OFF
pumpStatus = true;
digitalWrite(RELAY_PIN, HIGH); // Activer le relais
} else if (percentage <= SEUIL_POMPE_ARRET && pumpStatus) {
// Arrêter la pompe si le niveau est bas et qu'elle est ON
pumpStatus = false;
digitalWrite(RELAY_PIN, LOW); // Désactiver le relais
}
}
}
/**
* @brief Active ou désactive l'alarme.
* @param state Vrai pour activer, Faux pour désactiver.
*/
void activateAlarm(bool state) {
if (state && !alarmActive) {
// Activer l'alarme
alarmActive = true;
digitalWrite(BUZZER_PIN, HIGH); // Allumer le buzzer
} else if (!state && alarmActive) {
// Désactiver l'alarme
alarmActive = false;
digitalWrite(BUZZER_PIN, LOW); // Éteindre le buzzer
}
}
/**
* @brief Détermine l'état du niveau d'eau en fonction du pourcentage.
* @param percentage Pourcentage actuel du niveau d'eau.
* @return Une chaîne de caractères décrivant l'état.
*/
const char* getWaterLevelStatus(float percentage) {
if (percentage >= 91) return "PLEIN";
if (percentage >= 71) return "ELEVEE";
if (percentage >= 51) return "MOYEN";
if (percentage >= 31) return "BAS";
if (percentage >= 11) return "TRES BAS";
return "VIDE";
}