// -----------------------------------------------------------------------------
// I. INCLUSIONS DES LIBRAIRIES
// -----------------------------------------------------------------------------
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <string.h>
#include <EEPROM.h>
// -----------------------------------------------------------------------------
// II. DÉFINITION DES BROCHES, ADRESSES ET CONSTANTES
// -----------------------------------------------------------------------------
// 1. Relais et Buzzer
const int PIN_RELAIS_GRANULES = 11;
const int PIN_RELAIS_VIBRATEUR = 3;
const int PIN_BUZZER = 12;
// 2. Joystick (Broches)
const int PIN_JOY_VERT = A1;
const int PIN_JOY_HORZ = A0;
const int PIN_JOY_SEL = 2;
// 3. Joystick (Seuils et Anti-Rebond)
const int SEUIL_CENTRE_BAS = 300;
const int SEUIL_CENTRE_HAUT = 700;
const int SEUIL_PRESSION = LOW;
const long INTERVALLE_ANTI_REBOND = 200;
// 4. Capteurs de Température (4 bus OneWire)
const int PIN_ONEWIRE_TEMP1 = A2;
const int PIN_ONEWIRE_TEMP2 = A3;
const int PIN_ONEWIRE_TEMP3 = A4;
const int PIN_ONEWIRE_TEMP4 = A5;
// 5. LCD I2C (Adresses vérifiées : 0x27, 0x26, 0x20)
LiquidCrystal_I2C lcd_Menus(0x27, 20, 4);
LiquidCrystal_I2C lcd_Rapports(0x26, 20, 4);
LiquidCrystal_I2C lcd_Infos(0x20, 20, 4);
// 6. Objets RTC, OneWire et DallasTemperature
RTC_DS1307 rtc;
OneWire oneWire1(PIN_ONEWIRE_TEMP1);
OneWire oneWire2(PIN_ONEWIRE_TEMP2);
OneWire oneWire3(PIN_ONEWIRE_TEMP3);
OneWire oneWire4(PIN_ONEWIRE_TEMP4);
DallasTemperature sensors1(&oneWire1);
DallasTemperature sensors2(&oneWire2);
DallasTemperature sensors3(&oneWire3);
DallasTemperature sensors4(&oneWire4);
// 7. Adresses EEPROM
const int EEPROM_ADDR_GRANULES_TON = 0;
const int EEPROM_ADDR_GRANULES_TOFF = 4;
const int EEPROM_ADDR_VIBREUR_TON = 8;
const int EEPROM_ADDR_VIBREUR_TOFF = 12;
const int EEPROM_ADDR_MARKER = 16;
const byte EEPROM_MARKER_VALUE = 0xAA;
const int EEPROM_ADDR_TEMP_UNIT = 17;
const int EEPROM_ADDR_GRANULE_QUALITY = 18;
const int EEPROM_ADDR_CONSO_PERIOD = 19;
// -----------------------------------------------------------------------------
// III. VARIABLES GLOBALES ET ÉNUMÉRATIONS
// -----------------------------------------------------------------------------
// Buffers pour la simulation d'écran Processing (4 lignes x 20 caractères)
char lcd_Menus_Buffer[4][21];
char lcd_Rapports_Buffer[4][21];
char lcd_Infos_Buffer[4][21];
unsigned long dernierEnvoiSerie = 0;
const long INTERVALLE_ENVOI_SERIE = 100; // 10 fois par seconde (100 ms)
// Variables de lecture et calculs
float temp_1 = 0.0, temp_2 = 0.0, temp_3 = 0.0, temp_4 = 0.0;
DateTime maintenant;
float consoGranules = 0.0;
float consoEnergie = 0.0;
float puissanceMoyenneKW = 0.0;
const float BASE_CONSO_GRANULES_PER_DAY = 64800.00; // g/jour
const float BASE_CONSO_ENERGIE_PER_DAY = 311.04; // kWh/jour
// Gestion du temps non bloquant
unsigned long derniereLectureTemp = 0;
const long INTERVALLE_LECTURE_TEMP = 5000; // Intervalle entre les demandes de conversion (5s)
unsigned long derniereLectureRTC = 0;
const long INTERVALLE_LECTURE_RTC = 1000;
unsigned long dernierMouvement = 0;
const long TEMPS_CONVERSION_MAX = 800; // Temps max d'attente après requestTemperatures (800ms)
unsigned long tempsDebutConversion = 0;
// *** La variable 'tempsReadyToRead' a été supprimée ***
// Variables des minuteries (EEPROM)
long Granules_Time_ON = 60;
long Granules_Time_OFF = 1;
long Vibreur_Time_ON = 5;
long Vibreur_Time_OFF = 10;
// Variables de paramètres (EEPROM)
byte tempUnit = 0;
byte granuleQuality = 0;
byte consoPeriodMode = 0;
// Libellés
const char* TEMP_UNIT_LABELS_FULL[] = {"CELSIUS", "FAHRENHEIT"};
const char* TEMP_UNIT_LABELS_SHORT[] = {"CELSIUS", "FAHRENHEIT"};
const char* QUALITE_GRANULES_LABELS[] = {"Supreme", "Moyen", "Commun"};
const char* CONSO_PERIOD_LABELS[] = {"par minute", "Par heure", "Par jour", "Par semaine", "Par mois"};
const int NOMBRE_QUALITE_GRANULES = 3;
const int NOMBRE_OPTIONS_PERIOD = 5;
const int NOMBRE_OPTIONS_PARAMETRES = 5;
const char* PARAMETRES_LABELS[] = {
"1. Date/Heure",
"2. Unite Temp.",
"3. Qualite Granules",
"4. Conso/Unit",
"5. Reglage T.Cibles"
};
int parametresSelection = 0;
int consoUnitSelection = 0;
bool isSystemRunning = false;
byte rapportMode = 0;
byte ancienRapportMode = 255;
unsigned long granules_lastSwitchTime = 0;
bool granules_isON = false;
unsigned long vibreur_lastSwitchTime = 0;
bool vibreur_isON = false;
bool test_granules_state = false;
bool test_vibreur_state = false;
bool test_buzzer_state = false;
enum Direction { NONE, UP, DOWN, LEFT, RIGHT, SELECT };
char menuOptions[4][20] = {
"1. System OFF",
"2. Test Actionneurs",
"3. Minuteurs",
"4. Parametres"
};
const int NOMBRE_OPTIONS_MENU = 4;
int menuSelection = 0;
enum AppState {
MAIN_MENU,
SUB_MENU_RTC,
SUB_MENU_TCIBLES,
SUB_MENU_ACTIONNEURS,
SUB_MENU_MINUTEURS,
EDITING_MINUTERIE,
SUB_MENU_PARAMETRES,
EDITING_PARAMETRE,
SUB_MENU_CONSO_UNIT,
EDITING_CONSO_UNIT
};
AppState currentState = MAIN_MENU;
const char* minuteursOptions_V8[] = {
"1. Gr T-ON",
"2. Gr T-OFF",
"3. Vib T-ON",
"4. Vib T-OFF"
};
const int NOMBRE_OPTIONS_MINUT = sizeof(minuteursOptions_V8) / sizeof(minuteursOptions_V8[0]);
int minuteursSelection = 0;
char actionneursOptions[3][20] = {
"1. Granules : OFF",
"2. Vibreur : OFF",
"3. Buzzer : OFF"
};
const int NOMBRE_OPTIONS_ACTIONNEURS = 3;
int actionneursSelection = 0;
enum RTCField {
RTC_YEAR,
RTC_MONTH,
RTC_DAY,
RTC_HOUR,
RTC_MINUTE,
RTC_SECOND,
RTC_FIELD_COUNT
};
RTCField rtcSelection = RTC_YEAR;
// -----------------------------------------------------------------------------
// IV. FONCTIONS UTILITAIRES (Affichage)
// -----------------------------------------------------------------------------
/**
* Efface le buffer LCD donné (remplissage par espaces)
*/
void clearBuffer(char buffer[4][21]) {
for(int i = 0; i < 4; i++) {
memset(buffer[i], ' ', 20);
buffer[i][20] = '\0';
}
}
/**
* Synchronise le contenu d'un buffer (RAM) vers l'écran LCD physique (I2C)
*/
void syncBufferToPhysicalLCD(LiquidCrystal_I2C& lcd, char buffer[4][21]) {
lcd.clear();
for(int i = 0; i < 4; i++) {
lcd.setCursor(0, i);
// Utiliser strncpy pour assurer que seuls 20 caractères max sont affichés
char tempLine[21];
strncpy(tempLine, buffer[i], 20);
tempLine[20] = '\0';
lcd.print(tempLine);
}
}
/**
* Initialise un écran LCD I2C, affiche un message de test, puis l'efface.
*/
void init_lcd(LiquidCrystal_I2C& lcd, char buffer[4][21], const char* message, uint8_t addr) {
lcd.init();
lcd.backlight();
clearBuffer(buffer);
// Remplissage du buffer pour le test
snprintf(buffer[0], 21, "Init OK - Adresse: ");
snprintf(buffer[1], 21, "0x%02X ", addr);
snprintf(buffer[2], 21, "%-20s", message);
snprintf(buffer[3], 21, " ");
// Synchronisation avec l'écran physique
syncBufferToPhysicalLCD(lcd, buffer);
lcd.clear();
clearBuffer(buffer);
}
// Fonction utilitaire pour centrer le texte sur une ligne du buffer
void printCenteredBuffer(char buffer[4][21], int line, const char* message) {
int len = strlen(message);
int pos = (20 - len) / 2;
if (pos < 0) pos = 0;
// Remplir le buffer avec des espaces
memset(buffer[line], ' ', 20);
// Copier le message au bon endroit
strncpy(buffer[line] + pos, message, min(len, 20 - pos));
buffer[line][20] = '\0';
}
/**
* Fait défiler le contenu de lcd_Rapports vers la gauche.
*/
void scrollLeftRapports() {
for (int i = 0; i < 4; i++) {
lcd_Rapports.scrollDisplayLeft();
delay(75);
}
// IMPORTANT: Redessiner le contenu après le défilement
miseAJourAfficheRapports();
}
/**
* Fait défiler le contenu de lcd_Rapports vers la droite.
*/
void scrollRightRapports() {
for (int i = 0; i < 4; i++) {
lcd_Rapports.scrollDisplayRight();
delay(75);
}
// IMPORTANT: Redessiner le contenu après le défilement
miseAJourAfficheRapports();
}
/**
* Mise à jour de l'affichage de lcd_Rapports en fonction du mode.
* Écrit d'abord dans lcd_Rapports_Buffer, puis synchronise.
*/
void miseAJourAfficheRapports() {
if (currentState == SUB_MENU_RTC || currentState == SUB_MENU_TCIBLES ||
currentState == SUB_MENU_CONSO_UNIT || currentState == EDITING_CONSO_UNIT) return;
if (rapportMode != ancienRapportMode || millis() - derniereLectureRTC >= INTERVALLE_LECTURE_RTC) {
if (millis() - derniereLectureRTC >= INTERVALLE_LECTURE_RTC) {
derniereLectureRTC = millis();
maintenant = rtc.now();
}
if (rapportMode != ancienRapportMode) {
ancienRapportMode = rapportMode;
// Efface l'écran physique uniquement lors du changement de mode
lcd_Rapports.clear();
}
clearBuffer(lcd_Rapports_Buffer);
char floatBuffer[10];
if (rapportMode == 0) {
// Mode 0: Date, Heure, Consommation et Énergie
char dateTimeString[21];
// LIGNE 0: Date et Heure (Centré)
sprintf(dateTimeString, "%02d/%02d/%d %02d:%02d",
maintenant.day(), maintenant.month(), maintenant.year(),
maintenant.hour(), maintenant.minute());
printCenteredBuffer(lcd_Rapports_Buffer, 0, dateTimeString);
// LIGNE 1: Consommation Granulés
dtostrf(consoGranules, 7, 2, floatBuffer);
snprintf(lcd_Rapports_Buffer[1], 21, "Conso: %s g/%s", floatBuffer,
CONSO_PERIOD_LABELS[consoPeriodMode]);
// LIGNE 2: Consommation Énergie
dtostrf(consoEnergie, 7, 4, floatBuffer);
snprintf(lcd_Rapports_Buffer[2], 21, "Energie: %s kWh/%s", floatBuffer,
CONSO_PERIOD_LABELS[consoPeriodMode]);
// LIGNE 3: Puissance moyenne
dtostrf(puissanceMoyenneKW, 5, 2, floatBuffer);
snprintf(lcd_Rapports_Buffer[3], 21, "P. Moyenne: %s kW", floatBuffer);
} else if (rapportMode == 1) {
// Mode 1: État du Système et Minuteries
// Ligne 0: État Général & Qualité Granules
snprintf(lcd_Rapports_Buffer[0], 21, "System:%s | Qualite:%s",
isSystemRunning ? " ON" : "OFF",
QUALITE_GRANULES_LABELS[granuleQuality]);
// Ligne 1: Cycles Granules
snprintf(lcd_Rapports_Buffer[1], 21, "Gr: %s (%ld / %ld s)",
granules_isON ? "ON" : "OFF",
Granules_Time_ON, Granules_Time_OFF);
// Ligne 2: Cycles Vibreur
snprintf(lcd_Rapports_Buffer[2], 21, "Vib: %s (%ld / %ld s)",
vibreur_isON ? "ON" : "OFF",
Vibreur_Time_ON, Vibreur_Time_OFF);
// Ligne 3: Instruction de navigation
snprintf(lcd_Rapports_Buffer[3], 21, " <L/R: Nav> ");
} else if (rapportMode == 2) {
// Mode 2: Infos Paramètres
// Ligne 0: Titre centré
printCenteredBuffer(lcd_Rapports_Buffer, 0, "Infos System");
// Ligne 1: Qualité des Granules
snprintf(lcd_Rapports_Buffer[1], 21, "Qualite: %s", QUALITE_GRANULES_LABELS[granuleQuality]);
// Ligne 2: Période de Consommation
snprintf(lcd_Rapports_Buffer[2], 21, "Conso: %s", CONSO_PERIOD_LABELS[consoPeriodMode]);
// Ligne 3: Unité de Température
snprintf(lcd_Rapports_Buffer[3], 21, "Unite: %s", TEMP_UNIT_LABELS_SHORT[tempUnit]);
}
// Synchronisation finale vers l'écran physique
syncBufferToPhysicalLCD(lcd_Rapports, lcd_Rapports_Buffer);
}
}
/**
* Mise à jour de l'affichage des températures sur l'écran Infos (0x20).
* Correction V7: Logique non bloquante de lecture du DS18B20.
*/
void miseAJourAfficheInfos() {
unsigned long now = millis();
// Phase 1: Envoi de la commande de conversion (toutes les 5000ms)
if (now - derniereLectureTemp >= INTERVALLE_LECTURE_TEMP) {
// 1. Déclenchement de la conversion pour les 4 bus
sensors1.requestTemperatures();
sensors2.requestTemperatures();
sensors3.requestTemperatures();
sensors4.requestTemperatures();
// 2. Enregistrement du temps de début (pour l'attente de 800ms)
tempsDebutConversion = now;
// 3. Réinitialisation du temps de lecture (pour l'attente de 5000ms)
derniereLectureTemp = now;
// 4. On sort de la fonction, car les données ne sont pas encore prêtes.
return;
}
// Phase 2: Lecture et affichage des résultats (après 800ms)
if (now - tempsDebutConversion >= TEMPS_CONVERSION_MAX) {
// Avancer tempsDebutConversion pour bloquer cette section jusqu'à la prochaine demande (Phase 1)
// Cela garantit que la lecture n'est faite qu'une seule fois après l'attente de 800ms.
tempsDebutConversion = now + TEMPS_CONVERSION_MAX;
// Lecture des températures (elles sont maintenant prêtes)
float t1_c = sensors1.getTempCByIndex(0);
float t2_c = sensors2.getTempCByIndex(0);
float t3_c = sensors3.getTempCByIndex(0);
float t4_c = sensors4.getTempCByIndex(0);
// Conversion C -> F si tempUnit == 1
float t1 = (tempUnit == 1) ? (t1_c * 1.8) + 32 : t1_c;
float t2 = (tempUnit == 1) ? (t2_c * 1.8) + 32 : t2_c;
float t3 = (tempUnit == 1) ? (t3_c * 1.8) + 32 : t3_c;
float t4 = (tempUnit == 1) ? (t4_c * 1.8) + 32 : t4_c;
// Unité à afficher
const char* unit = (tempUnit == 1) ? "F" : "C";
clearBuffer(lcd_Infos_Buffer);
// T1, T2, T3, T4 sur Ligne 0 à 3
snprintf(lcd_Infos_Buffer[0], 21, "T1: %.1f%s", t1, unit);
snprintf(lcd_Infos_Buffer[1], 21, "T2: %.1f%s", t2, unit);
snprintf(lcd_Infos_Buffer[2], 21, "T3: %.1f%s", t3, unit);
snprintf(lcd_Infos_Buffer[3], 21, "T4: %.1f%s", t4, unit);
syncBufferToPhysicalLCD(lcd_Infos, lcd_Infos_Buffer);
}
}
/**
* Affiche le menu principal sur le LCD_Menus (0x27)
* Écrit d'abord dans lcd_Menus_Buffer, puis synchronise.
*/
void afficherMenuPrincipal() {
clearBuffer(lcd_Menus_Buffer);
int debutAffichage = menuSelection;
if (menuSelection > 2) {
debutAffichage = menuSelection - 2;
} else {
debutAffichage = 0;
}
for (int i = 0; i < 4; i++) {
int indexOption = debutAffichage + i;
if (indexOption < NOMBRE_OPTIONS_MENU) {
char pointeur = (indexOption == menuSelection) ? '>' : ' ';
snprintf(lcd_Menus_Buffer[i], 21, "%c %s", pointeur, menuOptions[indexOption]);
}
}
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
// Affichage Minuteurs
void afficherReglagesMinuteurs() {
clearBuffer(lcd_Menus_Buffer);
char valueBuffer[5];
// Afficher les 4 options (i=0 à 3) sur les lignes 0 à 3
for (int i = 0; i < NOMBRE_OPTIONS_MINUT; i++) {
char pointeur = ' ';
if (i == minuteursSelection) {
pointeur = (currentState == EDITING_MINUTERIE) ? 'E' : '>';
}
long value = 0;
if (i == 0) value = Granules_Time_ON;
else if (i == 1) value = Granules_Time_OFF;
else if (i == 2) value = Vibreur_Time_ON;
else if (i == 3) value = Vibreur_Time_OFF;
sprintf(valueBuffer, "%04ld", value);
// Construction de la ligne dans le buffer
snprintf(lcd_Menus_Buffer[i], 21, "%c %s : %s",
pointeur,
minuteursOptions_V8[i],
valueBuffer);
}
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
/**
* Affiche le sous-menu de Test des Actionneurs
*/
void afficherTestactionneurs() {
clearBuffer(lcd_Menus_Buffer);
// Mettre à jour les libellés avec l'état actuel (ON ou OFF)
sprintf(actionneursOptions[0], "1. Granules : %s", test_granules_state ? "ON " : "OFF");
sprintf(actionneursOptions[1], "2. Vibreur : %s", test_vibreur_state ? "ON " : "OFF");
sprintf(actionneursOptions[2], "3. Buzzer : %s", test_buzzer_state ? "ON " : "OFF");
for (int i = 0; i < NOMBRE_OPTIONS_ACTIONNEURS; i++) {
char pointeur = (i == actionneursSelection) ? '>' : ' ';
snprintf(lcd_Menus_Buffer[i], 21, "%c %s", pointeur, actionneursOptions[i]);
}
// Afficher l'état du système principal sur la 4ème ligne
snprintf(lcd_Menus_Buffer[3], 21, "Etat System: %s", isSystemRunning ? "ON" : "OFF");
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
/**
* Affiche le sous-menu de réglage de l'heure/date du RTC sur lcd_Menus.
*/
void afficherReglagesRTC() {
clearBuffer(lcd_Menus_Buffer);
DateTime temp_now = rtc.now();
int y = temp_now.year();
int m = temp_now.month();
int d = temp_now.day();
int h = temp_now.hour();
int min = temp_now.minute();
int s = temp_now.second();
// LIGNE 0: AAAA/MM/JJ
snprintf(lcd_Menus_Buffer[0], 21,
"%c%04d%c/%c%02d%c/%c%02d%c",
(rtcSelection == RTC_YEAR) ? '[' : ' ', y, (rtcSelection == RTC_YEAR) ? ']' : ' ',
(rtcSelection == RTC_MONTH) ? '[' : ' ', m, (rtcSelection == RTC_MONTH) ? ']' : ' ',
(rtcSelection == RTC_DAY) ? '[' : ' ', d, (rtcSelection == RTC_DAY) ? ']' : ' ');
// LIGNE 1: HH:MM:SS
snprintf(lcd_Menus_Buffer[1], 21,
" %c%02d%c:%c%02d%c:%c%02d%c",
(rtcSelection == RTC_HOUR) ? '[' : ' ', h, (rtcSelection == RTC_HOUR) ? ']' : ' ',
(rtcSelection == RTC_MINUTE) ? '[' : ' ', min, (rtcSelection == RTC_MINUTE) ? ']' : ' ',
(rtcSelection == RTC_SECOND) ? '[' : ' ', s, (rtcSelection == RTC_SECOND) ? ']' : ' ');
// LIGNE 3: Instruction de navigation
snprintf(lcd_Menus_Buffer[3], 21, " <LEFT:Retour> <RIGHT:Suivant>");
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
/**
* Affiche le sous-menu Parametres
*/
void afficherParametres() {
clearBuffer(lcd_Menus_Buffer);
bool isEditing = (currentState == EDITING_PARAMETRE);
int debutAffichage = parametresSelection;
if (parametresSelection > 2) {
debutAffichage = parametresSelection - 2;
} else {
debutAffichage = 0;
}
for (int i = 0; i < 4; i++) {
int indexOption = debutAffichage + i;
if (indexOption < NOMBRE_OPTIONS_PARAMETRES) {
char pointeur = (indexOption == parametresSelection) ? '>' : ' ';
if (indexOption == 0 || indexOption == 3 || indexOption == 4) {
snprintf(lcd_Menus_Buffer[i], 21, "%c%s", pointeur, PARAMETRES_LABELS[indexOption]);
}
else {
const char* valueStr;
const char* simpleLabel = (indexOption == 1) ? "Unite:" : "Qualite:";
if (indexOption == 1) {
valueStr = TEMP_UNIT_LABELS_FULL[tempUnit];
} else {
valueStr = QUALITE_GRANULES_LABELS[granuleQuality];
}
if (indexOption == parametresSelection && isEditing) {
snprintf(lcd_Menus_Buffer[i], 21, "%c%d. %s [%s]", pointeur, indexOption + 1, simpleLabel, valueStr);
} else {
snprintf(lcd_Menus_Buffer[i], 21, "%c%d. %s %s", pointeur, indexOption + 1, simpleLabel, valueStr);
}
}
}
}
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
/**
* Affiche le sous-menu Reglage Conso/Unit
*/
void afficherReglagesConsoUnit() {
clearBuffer(lcd_Menus_Buffer);
const char* periodLabel = CONSO_PERIOD_LABELS[consoPeriodMode];
// LIGNE 0 (Index 0): Titre centré
printCenteredBuffer(lcd_Menus_Buffer, 0, "CONSO/UNIT");
// LIGNE 1 (Index 1): Option à éditer
char pointeur = (currentState == EDITING_CONSO_UNIT) ? 'E' : '>';
snprintf(lcd_Menus_Buffer[1], 21, "%c Periode: %s",
pointeur,
periodLabel);
// LIGNES 2 & 3
snprintf(lcd_Menus_Buffer[3], 21, " <LEFT: Retour> ");
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
/**
* Affiche le sous-menu T.Cibles (À implémenter)
*/
void afficherReglagesTCibles() {
clearBuffer(lcd_Menus_Buffer);
printCenteredBuffer(lcd_Menus_Buffer, 0, "REGLAGE T.CIBLES");
printCenteredBuffer(lcd_Menus_Buffer, 1, "(A IMPLEMENTER)");
snprintf(lcd_Menus_Buffer[3], 21, " <LEFT: Retour> ");
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
// -----------------------------------------------------------------------------
// NOUVEAU: FONCTION D'ENVOI SÉRIE VERS SIMULATEUR PROCESSING
// -----------------------------------------------------------------------------
/**
* Envoie le contenu des 3 buffers LCD via la liaison série.
* Format: [M]L0|L1|L2|L3[R]L0|L1|L2|L3[I]L0|L1|L2|L3\n
*/
void envoyerContenuLCDs() {
if (!Serial) return;
// Envoi du contenu du Menu
Serial.print("[M]");
for (int i = 0; i < 4; i++) {
Serial.print(lcd_Menus_Buffer[i]);
if (i < 3) Serial.print("|");
}
// Envoi du contenu des Rapports
Serial.print("[R]");
for (int i = 0; i < 4; i++) {
Serial.print(lcd_Rapports_Buffer[i]);
if (i < 3) Serial.print("|");
}
// Envoi du contenu des Infos
Serial.print("[I]");
for (int i = 0; i < 4; i++) {
Serial.print(lcd_Infos_Buffer[i]);
if (i < 3) Serial.print("|");
}
Serial.println(); // Terminer avec un retour chariot pour signaler la fin du paquet
}
// -----------------------------------------------------------------------------
// V. FONCTIONS GESTIONNAIRES (Logique & Non-Bloquante)
// -----------------------------------------------------------------------------
/**
* Calcule la consommation et l'énergie en fonction du minuteur granules
* ET du mode de période sélectionné.
*/
void calculerConsommation() {
float cycleTotalTime = (float)Granules_Time_ON + (float)Granules_Time_OFF;
float dutyCycleFactor = 0.0;
if (cycleTotalTime > 0.0) {
dutyCycleFactor = (float)Granules_Time_ON / cycleTotalTime;
}
float qualiteFactor;
if (granuleQuality == 0) {
qualiteFactor = 1.0;
} else if (granuleQuality == 1) {
qualiteFactor = 0.95;
} else {
qualiteFactor = 0.90;
}
float baseGranules = BASE_CONSO_GRANULES_PER_DAY;
float baseEnergie = BASE_CONSO_ENERGIE_PER_DAY * qualiteFactor;
float adjustedDailyGranules = baseGranules * dutyCycleFactor;
float adjustedDailyEnergie = baseEnergie * dutyCycleFactor;
puissanceMoyenneKW = adjustedDailyEnergie / 24.0;
float periodFactor = 1.0;
switch (consoPeriodMode) {
case 0: // "par minute" (Base / 1440 minutes)
periodFactor = 1.0 / (24.0 * 60.0);
break;
case 1: // "Par heure" (Base / 24 heures)
periodFactor = 1.0 / 24.0;
break;
case 2: // "Par jour" (Base * 1)
periodFactor = 1.0;
break;
case 3: // "Par semaine" (Base * 7 jours)
periodFactor = 7.0;
break;
case 4: // "Par mois" (Base * 30.4167 jours, moyenne)
periodFactor = 30.4167;
break;
}
consoGranules = adjustedDailyGranules * periodFactor;
consoEnergie = adjustedDailyEnergie * periodFactor;
}
void gererAffichageRapports() {
miseAJourAfficheRapports();
}
void gererLecturesTemperature() {
miseAJourAfficheInfos();
}
Direction lireJoystick() {
if (digitalRead(PIN_JOY_SEL) == SEUIL_PRESSION) return SELECT;
int valVert = analogRead(PIN_JOY_VERT);
int valHorz = analogRead(PIN_JOY_HORZ);
if (valVert < SEUIL_CENTRE_BAS) return UP;
if (valVert > SEUIL_CENTRE_HAUT) return DOWN;
if (valHorz < SEUIL_CENTRE_BAS) return RIGHT;
if (valHorz > SEUIL_CENTRE_HAUT) return LEFT;
return NONE;
}
// Gère le cycle ON/OFF du Relais Granules (PIN 11)
void gererCycleGranules() {
if (!isSystemRunning) return;
unsigned long currentTime = millis();
unsigned long interval;
if (granules_isON) {
interval = (unsigned long)Granules_Time_ON * 1000UL;
if (currentTime - granules_lastSwitchTime >= interval) {
digitalWrite(PIN_RELAIS_GRANULES, LOW);
granules_isON = false;
granules_lastSwitchTime = currentTime;
}
} else {
interval = (unsigned long)Granules_Time_OFF * 1000UL;
if (currentTime - granules_lastSwitchTime >= interval) {
digitalWrite(PIN_RELAIS_GRANULES, HIGH);
granules_isON = true;
granules_lastSwitchTime = currentTime;
}
}
}
// Gère le cycle ON/OFF du Relais Vibreur (PIN 3)
void gererCycleVibreur() {
if (!isSystemRunning) return;
unsigned long currentTime = millis();
unsigned long interval;
if (vibreur_isON) {
interval = (unsigned long)Vibreur_Time_ON * 1000UL;
if (currentTime - vibreur_lastSwitchTime >= interval) {
digitalWrite(PIN_RELAIS_VIBRATEUR, LOW);
vibreur_isON = false;
vibreur_lastSwitchTime = currentTime;
}
} else {
interval = (unsigned long)Vibreur_Time_OFF * 1000UL;
if (currentTime - vibreur_lastSwitchTime >= interval) {
digitalWrite(PIN_RELAIS_VIBRATEUR, HIGH);
vibreur_isON = true;
vibreur_lastSwitchTime = currentTime;
}
}
}
void gererMenuPrincipalNavigation(Direction commande) {
if (commande == UP) {
if (menuSelection > 0) {
menuSelection--;
afficherMenuPrincipal();
}
} else if (commande == DOWN) {
if (menuSelection < NOMBRE_OPTIONS_MENU - 1) {
menuSelection++;
afficherMenuPrincipal();
}
} else if (commande == SELECT) {
if (menuSelection == 0) {
isSystemRunning = !isSystemRunning;
if (isSystemRunning) {
granules_lastSwitchTime = millis();
granules_isON = true;
digitalWrite(PIN_RELAIS_GRANULES, HIGH);
vibreur_lastSwitchTime = millis();
vibreur_isON = true;
digitalWrite(PIN_RELAIS_VIBRATEUR, HIGH);
strcpy(menuOptions[0], "1. System ON");
} else {
digitalWrite(PIN_RELAIS_GRANULES, LOW);
digitalWrite(PIN_RELAIS_VIBRATEUR, LOW);
strcpy(menuOptions[0], "1. System OFF");
}
afficherMenuPrincipal();
} else {
executerOptionMenu(menuSelection);
}
}
}
void saveSettings() {
EEPROM.put(EEPROM_ADDR_GRANULES_TON, Granules_Time_ON);
EEPROM.put(EEPROM_ADDR_GRANULES_TOFF, Granules_Time_OFF);
EEPROM.put(EEPROM_ADDR_VIBREUR_TON, Vibreur_Time_ON);
EEPROM.put(EEPROM_ADDR_VIBREUR_TOFF, Vibreur_Time_OFF);
EEPROM.put(EEPROM_ADDR_TEMP_UNIT, tempUnit);
EEPROM.put(EEPROM_ADDR_GRANULE_QUALITY, granuleQuality);
EEPROM.put(EEPROM_ADDR_CONSO_PERIOD, consoPeriodMode);
EEPROM.write(EEPROM_ADDR_MARKER, EEPROM_MARKER_VALUE);
}
void loadSettings() {
byte marker;
EEPROM.get(EEPROM_ADDR_MARKER, marker);
if (marker != EEPROM_MARKER_VALUE) {
Granules_Time_ON = 60;
Granules_Time_OFF = 1;
Vibreur_Time_ON = 5;
Vibreur_Time_OFF = 10;
saveSettings();
} else {
EEPROM.get(EEPROM_ADDR_GRANULES_TON, Granules_Time_ON);
EEPROM.get(EEPROM_ADDR_GRANULES_TOFF, Granules_Time_OFF);
EEPROM.get(EEPROM_ADDR_VIBREUR_TON, Vibreur_Time_ON);
EEPROM.get(EEPROM_ADDR_VIBREUR_TOFF, Vibreur_Time_OFF);
EEPROM.get(EEPROM_ADDR_TEMP_UNIT, tempUnit);
EEPROM.get(EEPROM_ADDR_GRANULE_QUALITY, granuleQuality);
EEPROM.get(EEPROM_ADDR_CONSO_PERIOD, consoPeriodMode);
if (tempUnit > 1) tempUnit = 0;
if (granuleQuality >= NOMBRE_QUALITE_GRANULES) granuleQuality = 0;
if (consoPeriodMode >= NOMBRE_OPTIONS_PERIOD) consoPeriodMode = 0;
}
}
void gererMinuteursNavigation(Direction commande) {
long *valeurAEditer = nullptr;
long minValue = 1;
long maxValue = 9999;
if (minuteursSelection == 0) valeurAEditer = &Granules_Time_ON;
else if (minuteursSelection == 1) valeurAEditer = &Granules_Time_OFF;
else if (minuteursSelection == 2) valeurAEditer = &Vibreur_Time_ON;
else if (minuteursSelection == 3) valeurAEditer = &Vibreur_Time_OFF;
if (currentState == SUB_MENU_MINUTEURS) {
if (commande == UP) {
if (minuteursSelection > 0) minuteursSelection--;
afficherReglagesMinuteurs();
} else if (commande == DOWN) {
if (minuteursSelection < NOMBRE_OPTIONS_MINUT - 1) minuteursSelection++;
afficherReglagesMinuteurs();
} else if (commande == SELECT) {
currentState = EDITING_MINUTERIE;
afficherReglagesMinuteurs();
} else if (commande == LEFT) {
currentState = MAIN_MENU;
afficherMenuPrincipal();
}
}
else if (currentState == EDITING_MINUTERIE) {
if (commande == UP) {
if (*valeurAEditer < maxValue) (*valeurAEditer)++;
afficherReglagesMinuteurs();
} else if (commande == DOWN) {
if (*valeurAEditer > minValue) (*valeurAEditer)--;
afficherReglagesMinuteurs();
} else if (commande == SELECT || commande == LEFT) {
saveSettings();
currentState = SUB_MENU_MINUTEURS;
afficherReglagesMinuteurs();
} else if (commande == RIGHT) {
if (*valeurAEditer + 10 <= maxValue) *valeurAEditer += 10;
else *valeurAEditer = maxValue;
afficherReglagesMinuteurs();
}
}
}
/**
* Gestion de la navigation dans le sous-menu Parametres
*/
void gererParametresNavigation(Direction commande) {
if (currentState == SUB_MENU_PARAMETRES) {
if (commande == UP) {
if (parametresSelection > 0) parametresSelection--;
afficherParametres();
} else if (commande == DOWN) {
if (parametresSelection < NOMBRE_OPTIONS_PARAMETRES - 1) parametresSelection++;
afficherParametres();
} else if (commande == SELECT) {
if (parametresSelection == 0) { // 1. Reglage RTC
currentState = SUB_MENU_RTC;
rtcSelection = RTC_YEAR;
afficherReglagesRTC();
return;
} else if (parametresSelection == 3) { // 4. Conso/Unit
currentState = SUB_MENU_CONSO_UNIT;
consoUnitSelection = 0;
afficherReglagesConsoUnit();
return;
} else if (parametresSelection == 4) { // 5. Reglage T.Cibles
currentState = SUB_MENU_TCIBLES;
afficherReglagesTCibles();
return;
}
currentState = EDITING_PARAMETRE;
afficherParametres();
} else if (commande == LEFT) {
currentState = MAIN_MENU;
afficherMenuPrincipal();
}
}
else if (currentState == EDITING_PARAMETRE) {
bool valueChanged = false;
if (parametresSelection == 1 || parametresSelection == 2) {
if (commande == UP || commande == DOWN) {
int delta = (commande == UP) ? 1 : -1;
if (parametresSelection == 1) { // 2. Unité Temp.
tempUnit = (tempUnit == 0) ? 1 : 0;
} else if (parametresSelection == 2) { // 3. Qualité Granules
int newVal = granuleQuality + delta;
if (newVal >= 0 && newVal < NOMBRE_QUALITE_GRANULES) {
granuleQuality = newVal;
} else if (newVal < 0) {
granuleQuality = NOMBRE_QUALITE_GRANULES - 1;
} else if (newVal >= NOMBRE_QUALITE_GRANULES) {
granuleQuality = 0;
}
}
valueChanged = true;
}
}
if (valueChanged) {
afficherParametres();
}
if (commande == SELECT || commande == LEFT) {
saveSettings();
currentState = SUB_MENU_PARAMETRES;
afficherParametres();
}
}
}
/**
* Gestion de la navigation dans le sous-menu Reglage Conso/Unit
*/
void gererReglagesConsoUnitNavigation(Direction commande) {
if (currentState == SUB_MENU_CONSO_UNIT) {
if (commande == SELECT) {
currentState = EDITING_CONSO_UNIT;
afficherReglagesConsoUnit();
} else if (commande == LEFT) {
currentState = SUB_MENU_PARAMETRES;
afficherParametres();
}
}
else if (currentState == EDITING_CONSO_UNIT) {
bool valueChanged = false;
if (commande == UP || commande == DOWN) {
int delta = (commande == UP) ? 1 : -1;
// Réglage de la Période de Consommation (consoPeriodMode)
int newVal = consoPeriodMode + delta;
if (newVal >= 0 && newVal < NOMBRE_OPTIONS_PERIOD) {
consoPeriodMode = newVal;
} else if (newVal < 0) {
consoPeriodMode = NOMBRE_OPTIONS_PERIOD - 1;
} else if (newVal >= NOMBRE_OPTIONS_PERIOD) {
consoPeriodMode = 0;
}
valueChanged = true;
}
if (valueChanged) {
afficherReglagesConsoUnit();
}
if (commande == SELECT || commande == LEFT) {
saveSettings();
currentState = SUB_MENU_CONSO_UNIT;
afficherReglagesConsoUnit();
}
}
}
/**
* Gestion de la navigation dans le sous-menu Reglage T.Cibles
*/
void gererReglagesTCiblesNavigation(Direction commande) {
if (commande == LEFT || commande == SELECT) {
currentState = SUB_MENU_PARAMETRES;
afficherParametres();
}
}
void desactiverTousActionneurs() {
digitalWrite(PIN_RELAIS_GRANULES, LOW);
digitalWrite(PIN_RELAIS_VIBRATEUR, LOW);
noTone(PIN_BUZZER);
test_granules_state = false;
test_vibreur_state = false;
test_buzzer_state = false;
}
void gererTestActionneursNavigation(Direction commande) {
if (commande == UP) {
if (actionneursSelection > 0) {
actionneursSelection--;
afficherTestactionneurs();
}
} else if (commande == DOWN) {
if (actionneursSelection < NOMBRE_OPTIONS_ACTIONNEURS - 1) {
actionneursSelection++;
afficherTestactionneurs();
}
} else if (commande == SELECT) {
if (actionneursSelection == 0) {
test_granules_state = !test_granules_state;
digitalWrite(PIN_RELAIS_GRANULES, test_granules_state ? HIGH : LOW);
} else if (actionneursSelection == 1) {
test_vibreur_state = !test_vibreur_state;
digitalWrite(PIN_RELAIS_VIBRATEUR, test_vibreur_state ? HIGH : LOW);
} else if (actionneursSelection == 2) {
test_buzzer_state = !test_buzzer_state;
if (test_buzzer_state) {
tone(PIN_BUZZER, 1000);
} else {
noTone(PIN_BUZZER);
}
}
afficherTestactionneurs();
} else if (commande == LEFT) {
desactiverTousActionneurs();
currentState = MAIN_MENU;
afficherMenuPrincipal();
}
}
/**
* Gère la navigation et l'édition dans le sous-menu de réglage RTC.
*/
void gererReglagesRTCNavigation(Direction commande) {
DateTime now = rtc.now();
int annee = now.year();
int mois = now.month();
int jour = now.day();
int heure = now.hour();
int minute = now.minute();
int seconde = now.second();
bool aChange = false;
if (commande == RIGHT) {
rtcSelection = (RTCField)((rtcSelection + 1) % RTC_FIELD_COUNT);
afficherReglagesRTC();
} else if (commande == LEFT) {
if (rtcSelection == RTC_YEAR) {
currentState = SUB_MENU_PARAMETRES;
afficherParametres();
return;
}
rtcSelection = (RTCField)((rtcSelection - 1 + RTC_FIELD_COUNT) % RTC_FIELD_COUNT);
afficherReglagesRTC();
} else if (commande == UP || commande == DOWN) {
aChange = true;
int delta = (commande == UP) ? 1 : -1;
int max_val, min_val;
switch (rtcSelection) {
case RTC_YEAR:
annee += delta;
min_val = 2024; max_val = 2099;
if (annee < min_val) annee = max_val;
if (annee > max_val) annee = min_val;
break;
case RTC_MONTH:
mois += delta;
min_val = 1; max_val = 12;
if (mois < min_val) mois = max_val;
if (mois > max_val) mois = min_val;
break;
case RTC_DAY:
jour += delta;
min_val = 1; max_val = 31;
if (jour < min_val) jour = max_val;
if (jour > max_val) jour = min_val;
break;
case RTC_HOUR:
heure += delta;
min_val = 0; max_val = 23;
if (heure < min_val) heure = max_val;
if (heure > max_val) heure = min_val;
break;
case RTC_MINUTE:
minute += delta;
min_val = 0; max_val = 59;
if (minute < min_val) minute = max_val;
if (minute > max_val) minute = min_val;
break;
case RTC_SECOND:
seconde = 0;
break;
}
if (aChange) {
rtc.adjust(DateTime(annee, mois, jour, heure, minute, seconde));
afficherReglagesRTC();
}
} else if (commande == SELECT) {
currentState = SUB_MENU_PARAMETRES;
afficherParametres();
}
}
void executerOptionMenu(int index) {
lcd_Menus.clear();
switch (index) {
case 0:
currentState = MAIN_MENU;
afficherMenuPrincipal();
break;
case 1: // 2. Test Actionneurs
digitalWrite(PIN_RELAIS_GRANULES, LOW);
digitalWrite(PIN_RELAIS_VIBRATEUR, LOW);
currentState = SUB_MENU_ACTIONNEURS;
actionneursSelection = 0;
afficherTestactionneurs();
break;
case 2: // 3. Minuteurs
currentState = SUB_MENU_MINUTEURS;
minuteursSelection = 0;
afficherReglagesMinuteurs();
break;
case 3: // 4. Parametres
currentState = SUB_MENU_PARAMETRES;
parametresSelection = 0;
afficherParametres();
break;
}
}
/**
* Traite une commande de direction (UP, DOWN, SELECT, etc.) et met à jour l'état.
* Cette fonction est appelée par le joystick physique ET le simulateur virtuel.
* @param commande La direction à traiter.
* @param estPhysique Vrai si la commande vient du joystick réel.
*/
void traiterCommande(Direction commande, bool estPhysique) {
// --- Logique du MENU PRINCIPAL ---
if (currentState == MAIN_MENU) {
// Navigation des Rapports (LEFT/RIGHT) est déclenchée uniquement par le joystick physique
if (estPhysique) {
if (commande == RIGHT) {
scrollLeftRapports();
rapportMode = (rapportMode + 1) % 3;
} else if (commande == LEFT) {
scrollRightRapports();
rapportMode = (rapportMode + 2) % 3;
}
}
// Navigation (UP/DOWN/SELECT) est la même pour les deux
if (commande == UP || commande == DOWN || commande == SELECT) {
gererMenuPrincipalNavigation(commande);
}
}
// --- Logique des autres sous-menus ---
else if (currentState == SUB_MENU_MINUTEURS || currentState == EDITING_MINUTERIE) {
gererMinuteursNavigation(commande);
}
else if (currentState == SUB_MENU_ACTIONNEURS) {
gererTestActionneursNavigation(commande);
}
else if (currentState == SUB_MENU_RTC) {
gererReglagesRTCNavigation(commande);
}
else if (currentState == SUB_MENU_TCIBLES) {
gererReglagesTCiblesNavigation(commande);
}
else if (currentState == SUB_MENU_PARAMETRES || currentState == EDITING_PARAMETRE) {
gererParametresNavigation(commande);
}
else if (currentState == SUB_MENU_CONSO_UNIT || currentState == EDITING_CONSO_UNIT) {
gererReglagesConsoUnitNavigation(commande);
}
}
/**
* Gère l'entrée du joystick réel (avec anti-rebond) ET du simulateur série (Processing).
*/
void gererJoystick() {
Direction commandeVirtuelle = NONE;
// 1. LECTURE ET TRAITEMENT DU JOYSTICK VIRTUEL (PROCESSING)
if (Serial.available() > 0) {
char serialCommand = Serial.read();
if (serialCommand == 'U') commandeVirtuelle = UP;
else if (serialCommand == 'D') commandeVirtuelle = DOWN;
else if (serialCommand == 'L') commandeVirtuelle = LEFT;
else if (serialCommand == 'R') commandeVirtuelle = RIGHT;
else if (serialCommand == 'J') commandeVirtuelle = SELECT;
// Si une commande série valide est reçue, nous la traitons immédiatement
if (commandeVirtuelle != NONE) {
traiterCommande(commandeVirtuelle, false); // Faux = Commande Virtuelle
return; // Le traitement virtuel a été effectué, on sort pour cette boucle loop().
}
}
// 2. GESTION DU JOYSTICK PHYSIQUE (avec anti-rebond)
Direction commandePhysique = lireJoystick();
unsigned long tempsActuel = millis();
if (commandePhysique != NONE) {
// Application de l'anti-rebond UNIQUEMENT pour le joystick physique
if (tempsActuel - dernierMouvement > INTERVALLE_ANTI_REBOND) {
dernierMouvement = tempsActuel;
traiterCommande(commandePhysique, true); // Vrai = Commande Physique
}
}
}
// -----------------------------------------------------------------------------
// VI. FONCTION SETUP (Initialisation unique)
// -----------------------------------------------------------------------------
void setup() {
// 0. Initialisation du port Série pour le simulateur Processing
Serial.begin(115200);
// 1. Initialisation LCD & Test (Utilise le buffer et syncBufferToPhysicalLCD)
init_lcd(lcd_Menus, lcd_Menus_Buffer, "Ecran Menus (0x27)", 0x27);
init_lcd(lcd_Rapports, lcd_Rapports_Buffer, "Ecran Rapports (0x26)", 0x26);
init_lcd(lcd_Infos, lcd_Infos_Buffer, "Ecran Infos (0x20)", 0x20);
// 2. Configuration des broches
pinMode(PIN_RELAIS_GRANULES, OUTPUT);
digitalWrite(PIN_RELAIS_GRANULES, LOW);
pinMode(PIN_RELAIS_VIBRATEUR, OUTPUT);
digitalWrite(PIN_RELAIS_VIBRATEUR, LOW);
pinMode(PIN_BUZZER, OUTPUT);
noTone(PIN_BUZZER);
pinMode(PIN_JOY_SEL, INPUT_PULLUP);
// 3. CHARGEMENT DES PARAMÈTRES EEPROM
loadSettings();
// 4. Initialisation RTC
if (!rtc.begin()) {
// Si l'RTC n'est pas trouvé, affiche un message d'erreur sur l'écran physique
lcd_Menus.clear();
lcd_Menus.print("ERREUR: RTC NON TROUVE");
while (1);
}
// 5. Initialisation Capteurs de Température
sensors1.begin();
sensors2.begin();
sensors3.begin();
sensors4.begin();
// Utiliser la méthode non bloquante
sensors1.setWaitForConversion(false);
sensors2.setWaitForConversion(false);
sensors3.setWaitForConversion(false);
sensors4.setWaitForConversion(false);
// Initialisation du libellé de l'option 1
strcpy(menuOptions[0], "1. System OFF");
// AFFICHAGE DU MENU PRINCIPAL AU DÉMARRAGE (remplit le buffer et l'écran physique)
afficherMenuPrincipal();
}
// -----------------------------------------------------------------------------
// VII. FONCTION LOOP (Logique principale non bloquante)
// -----------------------------------------------------------------------------
void loop() {
// Calcule la consommation et l'énergie en fonction du mode de période
calculerConsommation();
// Gestionnaires périodiques (mettent à jour les buffers et les écrans physiques)
gererAffichageRapports();
gererLecturesTemperature(); // <<< C'est ici que la correction s'applique
// Gestionnaire d'entrées
gererJoystick();
// Logique de contrôle des cycles ON/OFF
if (currentState != SUB_MENU_ACTIONNEURS) {
gererCycleGranules();
gererCycleVibreur();
}
// NOUVEAU: Envoi périodique des données vers Processing
if (millis() - dernierEnvoiSerie >= INTERVALLE_ENVOI_SERIE) {
envoyerContenuLCDs();
dernierEnvoiSerie = millis();
}
}Relais
Vibreur
Relais
Granules
Lcd_Menu
Lcd_Rapport
Lcd_Infos