// -----------------------------------------------------------------------------
// 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 = 400;
const int SEUIL_CENTRE_HAUT = 600;
const int SEUIL_PRESSION = LOW;
const long INTERVALLE_ANTI_REBOND = 200; // 200 ms entre deux commandes
// 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;
// -----------------------------------------------------------------------------
// 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;
// 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 GLOBALES DES MINUTERIES (Valeurs par défaut, en secondes)
long Granules_Time_ON = 5;
long Granules_Time_OFF = 10;
long Vibreur_Time_ON = 5;
long Vibreur_Time_OFF = 10;
// NOUVELLES VARIABLES DE PARAMÈTRES
byte tempUnit = 0; // 0: Celsius (C), 1: Fahrenheit (F)
byte granuleQuality = 0; // 0: Suprême, 1: Moyen, 2: Commun
// 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 int NOMBRE_QUALITE_GRANULES = 3;
int parametresSelection = 0;
const int NOMBRE_OPTIONS_PARAMETRES = 2; // Constante ajoutée lors de la correction précédente
// État global du système
bool isSystemRunning = false;
// Mode d'affichage de l'écran Rapports
byte rapportMode = 0; // 0: Date/Heure, 1: État du Système, 2: Infos Paramètres
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[7][20] = {
"1. System OFF",
"2. Reglages RTC",
"3. Reglage T.Cibles",
"4. Test Actionneurs",
"5. Etat T.Capteurs",
"6. Minuteurs",
"7. Parametres"
};
const int NOMBRE_OPTIONS_MENU = 7;
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_CAPTEURS,
SUB_MENU_MINUTEURS,
EDITING_MINUTERIE,
SUB_MENU_PARAMETRES,
EDITING_PARAMETRE
};
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.
* Ceci garantit que l'écran est propre avant le lancement du programme principal.
*/
void init_lcd(LiquidCrystal_I2C& lcd, const char* message, uint8_t addr) {
lcd.init();
lcd.backlight();
lcd.clear(); // Efface avant l'affichage du test
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); // Laisse le temps de lire le message
lcd.clear(); // Efface l'écran après le test
}
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 (l'ancien contenu sort à gauche).
*/
void scrollLeftRapports() {
// Fait défiler le contenu vers la gauche
for (int i = 0; i < 4; i++) {
lcd_Rapports.scrollDisplayLeft();
delay(75); // Ralenti pour un meilleur effet visuel
}
lcd_Rapports.clear(); // Nettoyer l'écran après l'animation
}
/**
* Fait défiler le contenu de lcd_Rapports vers la droite (l'ancien contenu sort à droite).
*/
void scrollRightRapports() {
// Fait défiler le contenu vers la droite
for (int i = 0; i < 4; i++) {
lcd_Rapports.scrollDisplayRight();
delay(75); // Ralenti pour un meilleur effet visuel
}
lcd_Rapports.clear(); // Nettoyer l'écran après l'animation
}
/**
* Mise à jour de l'affichage de lcd_Rapports en fonction du mode.
*/
void miseAJourAfficheRapports() {
// Ne pas mettre à jour si l'utilisateur est dans un sous-menu de configuration
if (currentState == SUB_MENU_RTC || currentState == SUB_MENU_TCIBLES || currentState == SUB_MENU_CAPTEURS) return;
if (rapportMode != ancienRapportMode || millis() - derniereLectureRTC >= INTERVALLE_LECTURE_RTC) {
if (millis() - derniereLectureRTC >= INTERVALLE_LECTURE_RTC) {
derniereLectureRTC = millis();
maintenant = rtc.now();
}
if (rapportMode != ancienRapportMode) {
// L'écran est effacé par les fonctions de scroll (scrollLeftRapports/scrollRightRapports)
ancienRapportMode = rapportMode;
}
char lineBuffer[21];
if (rapportMode == 0) {
// Mode 0: Date et Heure
char dateString[11];
char timeString[9];
sprintf(dateString, "%02d/%02d/%d",
maintenant.day(), maintenant.month(), maintenant.year());
snprintf(lineBuffer, 21, " %s ", dateString);
lcd_Rapports.setCursor(0, 0);
lcd_Rapports.print(lineBuffer);
sprintf(timeString, "%02d:%02d:%02d",
maintenant.hour(), maintenant.minute(), maintenant.second());
snprintf(lineBuffer, 21, " %s ", timeString);
lcd_Rapports.setCursor(0, 1);
lcd_Rapports.print(lineBuffer);
lcd_Rapports.setCursor(0, 2);
lcd_Rapports.print(" ");
lcd_Rapports.setCursor(0, 3);
snprintf(lineBuffer, 21, " <L/R: Nav> ");
lcd_Rapports.print(lineBuffer);
} 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: Unité de Température
char content2[21];
sprintf(content2, "Unite: %s", TEMP_UNIT_LABELS_SHORT[tempUnit]);
snprintf(lineBuffer, 21, " %-18s ", 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);
}
}
}
/**
* Mise à jour de l'affichage des températures sur l'écran Infos (0x20).
* Affiche chaque capteur sur une ligne dédiée (T1 à T4 sur L0 à L3).
*/
void miseAJourAfficheInfos() {
if (millis() - derniereLectureTemp >= INTERVALLE_LECTURE_TEMP) {
derniereLectureTemp = millis();
sensors1.requestTemperatures();
sensors2.requestTemperatures();
sensors3.requestTemperatures();
sensors4.requestTemperatures();
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);
}
}
/**
* 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 (Affichage étendu)
// -----------------------------------------------------------
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 (Affichage étendu)
// -----------------------------------------------------------
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: Instructions
// -----------------------------------------------------------
lcd_Menus.setCursor(0, 2);
lcd_Menus.print("UP/DN: Changer Valeur ");
lcd_Menus.setCursor(0, 3);
lcd_Menus.print("L/R: Nav | SELECT: QU>");
}
/**
* Affiche le sous-menu Parametres (Unite Temp, Qualite Gr.)
*/
void afficherParametres() {
lcd_Menus.clear();
char lineBuffer[21]; // Buffer pour 20 caractères + '\0'
const int LINE_WIDTH = 20;
// -----------------------------------------------------------
// LIGNE 0: Unite Température
// -----------------------------------------------------------
char pointer0 = (parametresSelection == 0) ? ((currentState == EDITING_PARAMETRE) ? 'E' : '>') : ' ';
const char* unitName = TEMP_UNIT_LABELS_FULL[tempUnit];
// Correction: Remplir lineBuffer de 20 espaces et le null-terminer.
memset(lineBuffer, ' ', LINE_WIDTH);
lineBuffer[LINE_WIDTH] = '\0';
// Format: P<Unite: CELSIUS>
snprintf(lineBuffer, 21, "%cUnite: %s",
pointer0,
unitName);
lcd_Menus.setCursor(0, 0);
lcd_Menus.print(lineBuffer);
// -----------------------------------------------------------
// LIGNE 1: Qualité Granules
// -----------------------------------------------------------
char pointer1 = (parametresSelection == 1) ? ((currentState == EDITING_PARAMETRE) ? 'E' : '>') : ' ';
const char* qualityLabel = QUALITE_GRANULES_LABELS[granuleQuality];
// Correction: Remplir lineBuffer de 20 espaces et le null-terminer.
memset(lineBuffer, ' ', LINE_WIDTH);
lineBuffer[LINE_WIDTH] = '\0';
// Format: P<Qualite Gr: Supreme>
snprintf(lineBuffer, 21, "%cQualite Gr: %s",
pointer1,
qualityLabel);
lcd_Menus.setCursor(0, 1);
lcd_Menus.print(lineBuffer);
// -----------------------------------------------------------
// LIGNES 2 & 3: Laissées vides comme demandé.
// -----------------------------------------------------------
lcd_Menus.setCursor(0, 2);
lcd_Menus.print(" ");
lcd_Menus.setCursor(0, 3);
lcd_Menus.print(" ");
}
// -----------------------------------------------------------------------------
// V. FONCTIONS GESTIONNAIRES (Logique & Non-Bloquante)
// -----------------------------------------------------------------------------
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 LEFT;
if (valHorz > SEUIL_CENTRE_HAUT) return RIGHT;
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.write(EEPROM_ADDR_MARKER, EEPROM_MARKER_VALUE);
}
void loadSettings() {
byte marker;
EEPROM.get(EEPROM_ADDR_MARKER, marker);
if (marker != EEPROM_MARKER_VALUE) {
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);
if (tempUnit > 1) tempUnit = 0;
if (granuleQuality >= NOMBRE_QUALITE_GRANULES) granuleQuality = 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) {
currentState = EDITING_PARAMETRE;
afficherParametres();
} else if (commande == LEFT) {
currentState = MAIN_MENU;
afficherMenuPrincipal();
}
}
else if (currentState == EDITING_PARAMETRE) {
bool valueChanged = false;
if (commande == UP || commande == DOWN) {
int delta = (commande == UP) ? 1 : -1;
if (parametresSelection == 0) { // 1. Unité Temp.
tempUnit = (tempUnit == 0) ? 1 : 0;
} else if (parametresSelection == 1) { // 2. 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();
}
}
}
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) {
currentState = MAIN_MENU;
afficherMenuPrincipal();
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; // Réinitialiser les secondes à 0
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 menu principal
currentState = MAIN_MENU;
afficherMenuPrincipal();
}
}
void executerOptionMenu(int index) {
lcd_Menus.clear();
switch (index) {
case 0:
currentState = MAIN_MENU;
afficherMenuPrincipal();
break;
case 1:
// Reglage RTC
currentState = SUB_MENU_RTC;
rtcSelection = RTC_YEAR; // Commence par l'édition de l'année
afficherReglagesRTC();
break;
case 2:
currentState = SUB_MENU_TCIBLES;
printCentered(lcd_Menus, 0, "Reglage T.Cibles");
lcd_Menus.setCursor(0, 3);
lcd_Menus.print("LEFT: QUIT");
break;
case 3:
digitalWrite(PIN_RELAIS_GRANULES, LOW);
digitalWrite(PIN_RELAIS_VIBRATEUR, LOW);
currentState = SUB_MENU_ACTIONNEURS;
actionneursSelection = 0;
afficherTestactionneurs();
break;
case 4:
currentState = SUB_MENU_CAPTEURS;
printCentered(lcd_Menus, 0, "Etat T.Capteurs");
lcd_Menus.setCursor(0, 3);
lcd_Menus.print("LEFT: QUIT");
break;
case 5:
currentState = SUB_MENU_MINUTEURS;
minuteursSelection = 0;
afficherReglagesMinuteurs();
break;
case 6: // Option "7. Parametres"
currentState = SUB_MENU_PARAMETRES;
parametresSelection = 0;
afficherParametres();
break;
}
}
void gererJoystick() {
Direction commande = lireJoystick();
unsigned long tempsActuel = millis();
if (commande != NONE) {
if (tempsActuel - dernierMouvement > INTERVALLE_ANTI_REBOND) {
dernierMouvement = tempsActuel;
if (currentState == MAIN_MENU) {
if (commande == RIGHT) {
scrollLeftRapports(); // Animation : L'ancien contenu sort vers la GAUCHE
rapportMode = (rapportMode + 1) % 3;
// Note: miseAJourAfficheRapports() sera appelé dans loop() pour afficher le nouveau mode
} else if (commande == LEFT) {
scrollRightRapports(); // Animation : L'ancien contenu sort vers la DROITE
rapportMode = (rapportMode + 2) % 3;
// Note: miseAJourAfficheRapports() sera appelé dans loop() pour afficher le nouveau mode
}
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_PARAMETRES || currentState == EDITING_PARAMETRE) {
gererParametresNavigation(commande);
}
// Logique de sortie par défaut pour les autres sous-menus (T.Cibles et T.Capteurs)
else if (commande == LEFT) {
currentState = MAIN_MENU;
afficherMenuPrincipal();
}
}
}
}
// -----------------------------------------------------------------------------
// VI. FONCTION SETUP (Initialisation unique)
// -----------------------------------------------------------------------------
void setup() {
// 1. Initialisation LCD & Test
// Chaque appel à init_lcd() affiche un message de test puis efface l'écran (avec un petit délai).
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();
// Prêt pour les fonctions non bloquantes (écrans déjà effacés par init_lcd)
// 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() {
// Gestionnaires périodiques
gererAffichageRapports();
gererLecturesTemperature();
// Gestionnaire d'entrées
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