// -----------------------------------------------------------------------------
// 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)
// Seuils élargis pour meilleure tolérance matérielle
const int SEUIL_CENTRE_BAS = 300;
const int SEUIL_CENTRE_HAUT = 700;
const int SEUIL_PRESSION = LOW;
// Intervalle d'anti-rebond (Conservé à 200 ms pour la réactivité du menu)
const long INTERVALLE_ANTI_REBOND = 500;
// 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 (Chaque long prend 4 octets)
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
// -----------------------------------------------------------------------------
// Variables de lecture
float temp_1 = 0.0, temp_2 = 0.0, temp_3 = 0.0, temp_4 = 0.0;
DateTime maintenant;
// Variables de consommation (float) pour l'affichage (valeurs calculées)
float consoGranules = 0.0;
float consoEnergie = 0.0;
float puissanceMoyenneKW = 0.0;
// Constantes de base de consommation.
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;
unsigned long derniereLectureRTC = 0;
const long INTERVALLE_LECTURE_RTC = 1000;
unsigned long dernierMouvement = 0;
// Variables pour la lecture de température non-bloquante
const long TEMPS_CONVERSION_MAX = 800; // 750 ms pour 12-bit + marge
unsigned long tempsDebutConversion = 0;
bool tempsReadyToRead = false; // TRUE si la demande a été envoyée, FALSE sinon.
// VARIABLES GLOBALES DES MINUTERIES (Valeurs par défaut, en secondes)
long Granules_Time_ON = 60;
long Granules_Time_OFF = 1;
long Vibreur_Time_ON = 5;
long Vibreur_Time_OFF = 10;
// NOUVELLES VARIABLES DE PARAMÈTRES
byte tempUnit = 0;
byte granuleQuality = 0;
byte consoPeriodMode = 0;
// Libellé long pour le menu Parametres (lcd_Menus)
const char* TEMP_UNIT_LABELS_FULL[] = {"CELSIUS", "FAHRENHEIT"};
// Libellé court pour l'écran Rapports (lcd_Rapports)
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;
// NOUVEAU: Enumération pour les paramètres (incluant RTC)
enum ParametreField {
PARAM_RTC,
PARAM_TEMP_UNIT,
PARAM_GRANULE_QUALITY,
PARAM_CONSO_UNIT,
PARAM_TCIBLES, // NOUVEAU
PARAM_FIELD_COUNT
};
// MODIFIÉ: La taille du menu Paramètres est maintenant 5
const int NOMBRE_OPTIONS_PARAMETRES = 5;
// Libellés pour le sous-menu Parametres
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;
// État global du système
bool isSystemRunning = false;
// Mode d'affichage de l'écran Rapports
byte rapportMode = 0;
byte ancienRapportMode = 255;
// Variables pour les minuteries non bloquantes (Gestion des cycles)
unsigned long granules_lastSwitchTime = 0;
bool granules_isON = false;
unsigned long vibreur_lastSwitchTime = 0;
bool vibreur_isON = false;
// Variables pour l'état du menu de test des actionneurs
bool test_granules_state = false;
bool test_vibreur_state = false;
bool test_buzzer_state = false;
// ÉNUMÉRATION DES COMMANDES DE NAVIGATION
enum Direction { NONE, UP, DOWN, LEFT, RIGHT, SELECT };
// STRUCTURE DU MENU
char menuOptions[4][20] = {
"1. System OFF",
"2. Test Actionneurs",
"3. Minuteurs",
"4. Parametres"
};
const int NOMBRE_OPTIONS_MENU = 4;
int menuSelection = 0;
// Énumération des états de l'application
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;
// Libellés pour les minuteries
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;
// Libellés pour le sous-menu Test Actionneurs
char actionneursOptions[3][20] = {
"1. Granules : OFF",
"2. Vibreur : OFF",
"3. Buzzer : OFF"
};
const int NOMBRE_OPTIONS_ACTIONNEURS = 3;
int actionneursSelection = 0;
// ÉNUMÉRATION ET VARIABLE POUR L'ÉDITION RTC
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)
// -----------------------------------------------------------------------------
/**
* Initialise un écran LCD I2C, affiche un message de test, puis l'efface.
*/
void init_lcd(LiquidCrystal_I2C& lcd, const char* message, uint8_t addr) {
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Init OK - Adresse:");
lcd.setCursor(0, 1);
lcd.print("0x");
if (addr < 16) lcd.print('0');
lcd.print(addr, HEX);
lcd.setCursor(0, 2);
lcd.print(message);
//delay(1500);
lcd.clear();
}
void printCentered(LiquidCrystal_I2C& lcd, int line, const char* message) {
int len = strlen(message);
int pos = (20 - len) / 2;
if (pos < 0) pos = 0;
lcd.setCursor(pos, line);
lcd.print(message);
}
/**
* 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);
}
lcd_Rapports.clear();
}
/**
* 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);
}
lcd_Rapports.clear();
}
/**
* Mise à jour de l'affichage de lcd_Rapports en fonction du mode.
*/
void miseAJourAfficheRapports() {
// Le rapport des températures T1 à T4 est maintenant toujours sur lcd_Infos.
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;
}
char lineBuffer[21];
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());
lcd_Rapports.setCursor(0, 0);
printCentered(lcd_Rapports, 0, dateTimeString);
// L'affichage utilise maintenant les valeurs CALCULÉES (consoGranules)
char floatBuffer[10];
char content1[21];
// LIGNE 1: Consommation Granulés (Dépend de la période)
dtostrf(consoGranules, 7, 2, floatBuffer);
// 2. Formater la ligne complète (sprintf)
sprintf(content1, "Conso: %s g", floatBuffer);
snprintf(lineBuffer, 21, "%-20s", content1);
lcd_Rapports.setCursor(0, 1);
lcd_Rapports.print(lineBuffer);
// LIGNE 2: Consommation Énergie (en kWh, DÉPEND de la période)
char content2[21];
// Utilise consoEnergie (la bonne variable, AVEC periodFactor)
dtostrf(consoEnergie, 7, 4, floatBuffer);
// 2. Formater la ligne complète (sprintf)
sprintf(content2, "Energie: %s kWh", floatBuffer);
snprintf(lineBuffer, 21, "%-20s", content2);
lcd_Rapports.setCursor(0, 2);
lcd_Rapports.print(lineBuffer);
// LIGNE 3: Unité de consommation
const char* consoLabel = CONSO_PERIOD_LABELS[consoPeriodMode];
lcd_Rapports.setCursor(0, 3);
lcd_Rapports.print(" "); // 20 espaces
char consoLine[21];
sprintf(consoLine, "%s", consoLabel);
printCentered(lcd_Rapports, 3, consoLine);
} else if (rapportMode == 1) {
// Mode 1: État du Système et Minuteries
// Ligne 0: État Général & Qualité Granules
char content0[21];
sprintf(content0, "System:%s | Qualite:%s",
isSystemRunning ? " ON" : "OFF",
QUALITE_GRANULES_LABELS[granuleQuality]);
snprintf(lineBuffer, 21, "%-20s", content0);
lcd_Rapports.setCursor(0, 0);
lcd_Rapports.print(lineBuffer);
// Ligne 1: Cycles Granules
char content1[21];
sprintf(content1, "Gr: %s (%ld / %ld s)",
granules_isON ? "ON" : "OFF",
Granules_Time_ON, Granules_Time_OFF);
snprintf(lineBuffer, 21, "%-20s", content1);
lcd_Rapports.setCursor(0, 1);
lcd_Rapports.print(lineBuffer);
// Ligne 2: Cycles Vibreur
char content2[21];
sprintf(content2, "Vib: %s (%ld / %ld s)",
vibreur_isON ? "ON" : "OFF",
Vibreur_Time_ON, Vibreur_Time_OFF);
snprintf(lineBuffer, 21, "%-20s", content2);
lcd_Rapports.setCursor(0, 2);
lcd_Rapports.print(lineBuffer);
// Ligne 3: Instruction de navigation
snprintf(lineBuffer, 21, " <L/R: Nav> ");
lcd_Rapports.setCursor(0, 3);
lcd_Rapports.print(lineBuffer);
} else if (rapportMode == 2) {
// Mode 2: Infos Paramètres
// Ligne 0: Titre centré
snprintf(lineBuffer, 21, " Infos System ");
lcd_Rapports.setCursor(0, 0);
lcd_Rapports.print(lineBuffer);
// Ligne 1: Qualité des Granules
char content1[21];
sprintf(content1, "Qualite: %s", QUALITE_GRANULES_LABELS[granuleQuality]);
snprintf(lineBuffer, 21, " %-18s ", content1);
lcd_Rapports.setCursor(0, 1);
lcd_Rapports.print(lineBuffer);
// Ligne 2: Période de Consommation
char content2[21];
sprintf(content2, "Conso: %s", CONSO_PERIOD_LABELS[consoPeriodMode]);
snprintf(lineBuffer, 21, " %-18s ", content2);
lcd_Rapports.setCursor(0, 2);
lcd_Rapports.print(lineBuffer);
// Ligne 3: Unité de Température
char content3[21];
sprintf(content3, "Unite: %s", TEMP_UNIT_LABELS_SHORT[tempUnit]);
snprintf(lineBuffer, 21, " %-18s ", content3);
lcd_Rapports.setCursor(0, 3);
lcd_Rapports.print(lineBuffer);
}
}
}
/**
* Mise à jour de l'affichage des températures sur l'écran Infos (0x20).
* REMARQUE : Cette fonction est maintenant NON-BLOQUANTE.
*/
void miseAJourAfficheInfos() {
unsigned long now = millis();
// PHASE 1: Demande de conversion (toutes les 5000ms)
if (!tempsReadyToRead && (now - derniereLectureTemp >= INTERVALLE_LECTURE_TEMP)) {
derniereLectureTemp = now; // Enregistre le début du cycle
// Les appels sont NON-BLOQUANTS grâce à setWaitForConversion(false)
sensors1.requestTemperatures();
sensors2.requestTemperatures();
sensors3.requestTemperatures();
sensors4.requestTemperatures();
tempsDebutConversion = now; // Enregistre l'heure de la demande
tempsReadyToRead = true; // Passe à l'état d'attente/lecture
}
// PHASE 2: Lecture et affichage des résultats (après 800ms)
if (tempsReadyToRead && (now - tempsDebutConversion >= TEMPS_CONVERSION_MAX)) {
// Lecture des températures (non-bloquante, car elles sont 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";
lcd_Infos.clear();
// T1 sur Ligne 0
lcd_Infos.setCursor(0, 0);
lcd_Infos.print("T1:"); lcd_Infos.print(t1, 1); lcd_Infos.print(unit);
// T2 sur Ligne 1
lcd_Infos.setCursor(0, 1);
lcd_Infos.print("T2:"); lcd_Infos.print(t2, 1); lcd_Infos.print(unit);
// T3 sur Ligne 2
lcd_Infos.setCursor(0, 2);
lcd_Infos.print("T3:"); lcd_Infos.print(t3, 1); lcd_Infos.print(unit);
// T4 sur Ligne 3
lcd_Infos.setCursor(0, 3);
lcd_Infos.print("T4:"); lcd_Infos.print(t4, 1); lcd_Infos.print(unit);
tempsReadyToRead = false; // Réinitialisation pour le prochain cycle
}
}
/**
* Affiche le menu principal sur le LCD_Menus (0x27)
*/
void afficherMenuPrincipal() {
lcd_Menus.clear();
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) {
lcd_Menus.setCursor(0, i);
if (indexOption == menuSelection) {
lcd_Menus.print(">");
lcd_Menus.print(menuOptions[indexOption]);
} else {
lcd_Menus.print(" ");
lcd_Menus.print(menuOptions[indexOption]);
}
}
}
}
// Affichage Minuteurs
void afficherReglagesMinuteurs() {
// 1. Effacer tout l'écran.
lcd_Menus.clear();
char lineBuffer[21];
char valueBuffer[5];
const int LINE_WIDTH = 20;
// Afficher les 4 options (i=0 à 3) sur les lignes 0 à 3
for (int i = 0; i < NOMBRE_OPTIONS_MINUT; i++) {
int ligne = i;
char pointer = ' ';
if (i == minuteursSelection) {
pointer = (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);
memset(lineBuffer, ' ', LINE_WIDTH);
lineBuffer[LINE_WIDTH] = '\0';
lineBuffer[0] = pointer;
strncpy(&lineBuffer[1], minuteursOptions_V8[i], strlen(minuteursOptions_V8[i]));
lineBuffer[13] = ' ';
lineBuffer[14] = ':';
lineBuffer[15] = ' ';
strncpy(&lineBuffer[16], valueBuffer, 4);
lcd_Menus.setCursor(0, ligne);
lcd_Menus.print(lineBuffer);
}
}
/**
* Affiche le sous-menu de Test des Actionneurs
*/
void afficherTestactionneurs() {
lcd_Menus.clear();
// 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++) {
lcd_Menus.setCursor(0, i);
if (i == actionneursSelection) {
lcd_Menus.print(">");
} else {
lcd_Menus.print(" ");
}
lcd_Menus.print(actionneursOptions[i]);
}
// Afficher l'état du système principal sur la 4ème ligne
lcd_Menus.setCursor(0, 3);
lcd_Menus.print("Etat System: ");
lcd_Menus.print(isSystemRunning ? "ON" : "OFF");
}
/**
* Affiche le sous-menu de réglage de l'heure/date du RTC sur lcd_Menus.
*/
void afficherReglagesRTC() {
lcd_Menus.clear();
// Récupérer l'heure actuelle du RTC pour les manipulations
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
// -----------------------------------------------------------
lcd_Menus.setCursor(0, 0);
// Année (YYYY)
if (rtcSelection == RTC_YEAR) { lcd_Menus.print("["); } else { lcd_Menus.print(" "); }
lcd_Menus.print(y);
if (rtcSelection == RTC_YEAR) { lcd_Menus.print("]"); } else { lcd_Menus.print(" "); }
lcd_Menus.print("/");
// Mois (MM)
if (rtcSelection == RTC_MONTH) { lcd_Menus.print("["); } else { lcd_Menus.print(" "); }
if (m < 10) lcd_Menus.print('0');
lcd_Menus.print(m);
if (rtcSelection == RTC_MONTH) { lcd_Menus.print("]"); } else { lcd_Menus.print(" "); }
lcd_Menus.print("/");
// Jour (JJ)
if (rtcSelection == RTC_DAY) { lcd_Menus.print("["); } else { lcd_Menus.print(" "); }
if (d < 10) lcd_Menus.print('0');
lcd_Menus.print(d);
if (rtcSelection == RTC_DAY) { lcd_Menus.print("]"); } else { lcd_Menus.print(" "); }
// -----------------------------------------------------------
// LIGNE 1: HH:MM:SS
// -----------------------------------------------------------
lcd_Menus.setCursor(0, 1);
// Heure (HH)
if (rtcSelection == RTC_HOUR) { lcd_Menus.print("["); } else { lcd_Menus.print(" "); }
if (h < 10) lcd_Menus.print('0');
lcd_Menus.print(h);
if (rtcSelection == RTC_HOUR) { lcd_Menus.print("]"); } else { lcd_Menus.print(" "); }
lcd_Menus.print(":");
// Minute (MM)
if (rtcSelection == RTC_MINUTE) { lcd_Menus.print("["); } else { lcd_Menus.print(" "); }
if (min < 10) lcd_Menus.print('0');
lcd_Menus.print(min);
if (rtcSelection == RTC_MINUTE) { lcd_Menus.print("]"); } else { lcd_Menus.print(" "); }
lcd_Menus.print(":");
// Seconde (SS)
if (rtcSelection == RTC_SECOND) { lcd_Menus.print("["); } else { lcd_Menus.print(" "); }
if (s < 10) lcd_Menus.print('0');
lcd_Menus.print(s);
if (rtcSelection == RTC_SECOND) { lcd_Menus.print("]"); } else { lcd_Menus.print(" "); }
// -----------------------------------------------------------
// LIGNES 2 & 3: Maintenant vides
// -----------------------------------------------------------
lcd_Menus.setCursor(0, 2);
lcd_Menus.print(" "); // 20 espaces
lcd_Menus.setCursor(0, 3);
lcd_Menus.print(" "); // 20 espaces
}
/**
* Affiche le sous-menu Parametres (RTC, Unite Temp, Qualite Gr, Conso/Unit, T.Cibles)
*/
void afficherParametres() {
lcd_Menus.clear();
char lineBuffer[21];
const int LINE_WIDTH = 20;
bool isEditing = (currentState == EDITING_PARAMETRE);
// Déterminer la première option à afficher pour le défilement
int debutAffichage = parametresSelection;
if (parametresSelection > 2) { // Commence le défilement après l'option 2 (ligne 3)
debutAffichage = parametresSelection - 2;
} else {
debutAffichage = 0;
}
for (int i = 0; i < 4; i++) {
int indexOption = debutAffichage + i;
if (indexOption < NOMBRE_OPTIONS_PARAMETRES) {
lcd_Menus.setCursor(0, i);
char pointer = (indexOption == parametresSelection) ? '>' : ' ';
// Options qui mènent à un sous-sous-menu (RTC, Conso/Unit, T.Cibles)
if (indexOption == 0 || indexOption == 3 || indexOption == 4) {
snprintf(lineBuffer, 21, "%c%s", pointer, PARAMETRES_LABELS[indexOption]);
lcd_Menus.print(lineBuffer);
}
// Options qui sont éditables directement (Unite Temp, Qualite Granules)
else {
const char* label = PARAMETRES_LABELS[indexOption];
const char* valueStr;
if (indexOption == 1) { // Unite Température
valueStr = TEMP_UNIT_LABELS_FULL[tempUnit];
} else { // Qualité Granules
valueStr = QUALITE_GRANULES_LABELS[granuleQuality];
}
// Retirer le préfixe "2. " ou "3. " du label pour simplifier la mise en forme
const char* simpleLabel = (indexOption == 1) ? "Unite:" : "Qualite:";
if (indexOption == parametresSelection && isEditing) {
snprintf(lineBuffer, 21, "%c%d. %s [%s]", pointer, indexOption + 1, simpleLabel, valueStr);
} else {
snprintf(lineBuffer, 21, "%c%d. %s %s", pointer, indexOption + 1, simpleLabel, valueStr);
}
lcd_Menus.print(lineBuffer);
}
}
}
}
/**
* Affiche le sous-menu Reglage Conso/Unit
*/
void afficherReglagesConsoUnit() {
// Le clear efface tout l'écran
lcd_Menus.clear();
char lineBuffer[21];
const char* periodLabel = CONSO_PERIOD_LABELS[consoPeriodMode];
// LIGNE 0 (Index 0): Titre centré
printCentered(lcd_Menus, 0, "CONSO/UNIT");
// LIGNE 1 (Index 1): Option à éditer
char pointer = (currentState == EDITING_CONSO_UNIT) ? 'E' : '>';
snprintf(lineBuffer, 21, "%c Periode: %s",
pointer,
periodLabel);
lcd_Menus.setCursor(0, 1);
lcd_Menus.print(lineBuffer);
// LIGNES 2 & 3: Laissées vides (pas d'instructions)
lcd_Menus.setCursor(0, 2);
lcd_Menus.print(" ");
lcd_Menus.setCursor(0, 3);
lcd_Menus.print(" <LEFT: Retour> ");
}
/**
* Affiche le sous-menu T.Cibles (À implémenter)
*/
void afficherReglagesTCibles() {
lcd_Menus.clear();
// C'est ici que le code d'édition des Températures Cibles sera implémenté.
// Pour l'instant, on affiche un message de placeholder.
printCentered(lcd_Menus, 0, "REGLAGE T.CIBLES");
printCentered(lcd_Menus, 1, "(A IMPLEMENTER)");
lcd_Menus.setCursor(0, 3);
lcd_Menus.print(" <LEFT: Retour> ");
}
// -----------------------------------------------------------------------------
// V. FONCTIONS GESTIONNAIRES (Logique & Non-Bloquante)
// -----------------------------------------------------------------------------
void gererAffichageRapports() {
miseAJourAfficheRapports();
}
void gererLecturesTemperature() {
miseAJourAfficheInfos();
}
/**
* Calcule la consommation et l'énergie en fonction du minuteur granules
* ET du mode de période sélectionné.
*/
void calculerConsommation() {
// 1. Calcul du Facteur de Cycle (Duty Cycle)
float cycleTotalTime = (float)Granules_Time_ON + (float)Granules_Time_OFF;
float dutyCycleFactor = 0.0;
if (cycleTotalTime > 0.0) {
dutyCycleFactor = (float)Granules_Time_ON / cycleTotalTime;
}
// 2. Application du Facteur de Qualité.
float qualiteFactor;
// Le facteur de qualité est appliqué pour réduire l'énergie (PCI)
// des granulés de qualité inférieure.
if (granuleQuality == 0) {
qualiteFactor = 1.0; // Supreme (100% du PCI de base)
} else if (granuleQuality == 1) {
qualiteFactor = 0.95; // Moyen (95% du PCI de base)
} else {
qualiteFactor = 0.90; // Commun (90% du PCI de base)
}
// La consommation en masse (g/jour) n'est pas affectée par la qualité
float baseGranules = BASE_CONSO_GRANULES_PER_DAY;
// Seule l'énergie est réduite/augmentée par le facteur de qualité
float baseEnergie = BASE_CONSO_ENERGIE_PER_DAY * qualiteFactor;
// 3. Calcul de la Consommation Journalière AJUSTÉE par le Duty Cycle ET le Facteur Qualité
float adjustedDailyGranules = baseGranules * dutyCycleFactor;
float adjustedDailyEnergie = baseEnergie * dutyCycleFactor;
// 4. Calcul de la Puissance Moyenne en Kilowatts (kW)
puissanceMoyenneKW = adjustedDailyEnergie / 24.0;
// 5. Application du Facteur de Période (Minute, Heure, Semaine, etc.)
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;
}
// 6. Attribution des valeurs finales
consoGranules = adjustedDailyGranules * periodFactor;
consoEnergie = adjustedDailyEnergie * periodFactor;
}
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;
// [CORRECTIF APPLIQUÉ] Inversion des commandes LEFT/RIGHT pour le joystick physique
if (valHorz < SEUIL_CENTRE_BAS) return RIGHT; // Lecture basse -> Joystick physique tiré à droite
if (valHorz > SEUIL_CENTRE_HAUT) return LEFT; // Lecture haute -> Joystick physique tiré à gauche
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) {
// Si aucune donnée EEPROM n'est trouvée (Marker invalide), on utilise
// les valeurs par défaut (60s ON / 1s OFF)
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;
}
// Options 1 (Unite Temp) et 2 (Qualite Granules) passent en EDITING_PARAMETRE
currentState = EDITING_PARAMETRE;
afficherParametres();
} else if (commande == LEFT) {
currentState = MAIN_MENU;
afficherMenuPrincipal();
}
}
else if (currentState == EDITING_PARAMETRE) {
bool valueChanged = false;
// L'édition ne concerne que les options 1 (Unité Temp) et 2 (Qualité Granules)
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; // Retour au 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();
}
// Quitter l'édition (SELECT ou LEFT)
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) {
// Retour au menu Parametres
currentState = SUB_MENU_PARAMETRES;
afficherParametres();
}
// Ajoutez ici la logique UP/DOWN/SELECT/RIGHT pour éditer les valeurs des T.Cibles
// ...
}
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) {
// Récupérer l'heure actuelle du RTC pour les manipulations
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) {
// Passer au champ suivant (Année -> Mois -> ...)
rtcSelection = (RTCField)((rtcSelection + 1) % RTC_FIELD_COUNT);
afficherReglagesRTC();
} else if (commande == LEFT) {
// Revenir au champ précédent / Quitter
if (rtcSelection == RTC_YEAR) {
// Retour au sous-menu Parametres
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;
// Note: La vérification de la validité du jour selon le mois n'est pas implémentée ici
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; // On réinitialise les secondes lors de l'édition
break;
}
// Mettre à jour l'heure du RTC
if (aChange) {
rtc.adjust(DateTime(annee, mois, jour, heure, minute, seconde));
afficherReglagesRTC();
}
} else if (commande == SELECT) {
// Quitter et retourner au sous-menu Parametres
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;
}
}
void gererJoystick() {
Direction commande = lireJoystick();
unsigned long tempsActuel = millis();
if (commande != NONE) {
// Le test anti-rebond : vérifie si l'intervalle est passé
if (tempsActuel - dernierMouvement > INTERVALLE_ANTI_REBOND) {
dernierMouvement = tempsActuel;
if (currentState == MAIN_MENU) {
// Le LEFT/RIGHT sur le menu principal gère la navigation des rapports sur lcd_Rapports
if (commande == RIGHT) {
scrollLeftRapports();
rapportMode = (rapportMode + 1) % 3;
} else if (commande == LEFT) {
scrollRightRapports();
rapportMode = (rapportMode + 2) % 3;
}
if (commande == UP || commande == DOWN || commande == SELECT) {
gererMenuPrincipalNavigation(commande);
}
}
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) { // NOUVEAU
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);
}
}
}
}
// -----------------------------------------------------------------------------
// VI. FONCTION SETUP (Initialisation unique)
// -----------------------------------------------------------------------------
void setup() {
// 0. Initialisation du port Série pour le débogage (OPTIONNEL)
// Serial.begin(9600);
// 1. Initialisation LCD & Test
init_lcd(lcd_Menus, "Ecran Menus (0x27)", 0x27);
init_lcd(lcd_Rapports, "Ecran Rapports (0x26)", 0x26);
init_lcd(lcd_Infos, "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()) {
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();
// Désactivation de l'attente de conversion (rend les lectures non-bloquantes)
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
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
gererAffichageRapports();
// [NON-BLOQUANT] Gère l'état de la lecture des températures
gererLecturesTemperature();
// Gestionnaire d'entrées (ne sera plus bloqué)
gererJoystick();
// Logique de contrôle des cycles ON/OFF
if (currentState != SUB_MENU_ACTIONNEURS) {
gererCycleGranules();
gererCycleVibreur();
}
}Relais
Vibreur
Relais
Granules
Lcd_Menu
Lcd_Rapport
Lcd_Infos