// Réveil intelligent Maestro Version 2025-05-26 06:57
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <EEPROM.h>
// Creez les objets LiquidCrystal_I2C pour chaque ecran
LiquidCrystal_I2C lcd1(0x27, 20, 4);
LiquidCrystal_I2C lcd2(0x26, 20, 4);
RTC_DS1307 rtc; // Creez un objet RTC pour le DS1307
// --- DECLARATIONS GLOBALES ---
char daysOfTheWeek[7][12] = {"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"};
char monthsOfTheYear[12][12] = {"Janvier", "Fevrier", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Decembre"};
// --- Variables du menu et du reveil ---
enum MenuState {
STATE_NORMAL_DISPLAY,
STATE_MAIN_MENU,
STATE_SET_ALARM_HOUR,
STATE_SET_ALARM_MINUTE,
STATE_ALARM_ENABLE_DISABLE,
STATE_ALARM_RINGING // Etat pour l'alarme qui sonne
};
MenuState currentMenuState = STATE_NORMAL_DISPLAY;
int menuCursor = 0; // Pour naviguer dans les options du menu principal
int alarmMenuCursor = 0; // Pour la selection ACTIF/DESACTIF (0=ACTIF, 1=DESACTIF)
// Declaration unique de currentSetPart
int currentSetPart = 0; // 0=Heure, 1=Minute pour reglage alarme
// Variables du reveil (maintenant de type int)
#define ALARM_HOUR_ADDR 0
#define ALARM_MINUTE_ADDR 1
#define ALARM_ENABLED_ADDR 2
int alarmHour = 7; // Change de byte a int
int alarmMinute = 0; // Change de byte a int
bool alarmEnabled = false;
bool alarmTriggeredToday = false;
unsigned long alarmStartTime = 0; // Pour la duree de la sonnerie
// NOUVELLES VARIABLES POUR LA GESTION DU SNOOZE (MISES A JOUR)
// Definition de la duree du snooze en millisecondes pour eviter les problemes de calcul
const unsigned long SNOOZE_DURATION_MILLIS = 2UL * 60UL * 1000UL; // 2 minutes en millisecondes
bool snoozeActive = false; // Vrai si le snooze est en cours
unsigned long snoozeEndTimeMillis = 0; // Temps absolu en millis() quand le snooze doit se terminer
// --- Definitions pour la melodie ---
#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523
int melody[] = {
NOTE_C4, NOTE_E4, NOTE_G4, NOTE_C5, // Un petit arpeggio
NOTE_G4, NOTE_E4, NOTE_C4, // qui redescend
NOTE_C4, NOTE_C4, // deux notes pour finir
0 // 0 = pause
};
int noteDurations[] = {
4, 4, 4, 4,
4, 4, 4,
8, 8,
4
};
// Variables pour controler la lecture de la melodie
int noteIndex = 0;
unsigned long noteStartTime = 0;
// Definition des broches pour l'encodeur et le bouton MODE
const int ENCODER_CLK_PIN = 2; // Broche CLK de l'encodeur -> connectez a D2
const int ENCODER_DT_PIN = 3; // Broche DT de l'encodeur -> connectez a D3
const int ENCODER_SW_PIN = 4; // Broche SW (bouton) de l'encodeur -> connectez a D4
const int MODE_BUTTON_PIN = 9; // Un bouton MODE separe -> connectez a D9
const int BUZZER_PIN = 12; // Buzzer piezo -> connectez a D12
// --- Variables pour la lecture de l'encodeur via interruption ---
volatile long encoderCount = 0;
const int ENCODER_SENSITIVITY_THRESHOLD = 2; // Il faut 2 "tics" pour 1 incrementation/decrementation
// Variables de debordement pour les boutons poussoirs (SW de l'encodeur et MODE)
long lastDebounceTime[2] = {0, 0};
long debounceDelay = 50;
byte lastSteadyState[2] = {HIGH, HIGH};
byte lastReading[2] = {HIGH, HIGH};
// --- Definition du caractere personnalise (Cloche) ---
byte bellChar[] = {
0b00100, // *
0b01110, // ***
0b01110, // ***
0b01110, // ***
0b11111, // *****
0b00000, //
0b00100, // * (battant de la cloche)
0b00000 //
};
// Le caractere sera stocke a l'adresse 0 dans la CGRAM du LCD.
// --- Declarations de fonctions (Prototypes) ---
void displayTimeAndDateOnLCD1(DateTime now);
void displayMainMenu();
void displaySetAlarmTime(int selectedPart);
void displayAlarmEnableDisable();
void saveAlarmSettings();
void loadAlarmSettings();
void handleEncoderChange(bool up, bool down, int* value, int max_value);
byte readButton(int buttonPin, int buttonIndex);
void displayAlarmStatusOnLCD2(); // Nouvelle fonction pour l'affichage LCD2 en mode normal
// --- Fonctions d'aide ---
byte readButton(int buttonPin, int buttonIndex) {
byte reading = digitalRead(buttonPin);
if (reading != lastReading[buttonIndex]) {
lastDebounceTime[buttonIndex] = millis();
}
if ((millis() - lastDebounceTime[buttonIndex]) > debounceDelay) {
if (reading != lastSteadyState[buttonIndex]) {
lastSteadyState[buttonIndex] = reading;
if (lastSteadyState[buttonIndex] == LOW) {
return true;
}
}
}
lastReading[buttonIndex] = reading;
return false;
}
// Routine de Service d'Interruption (ISR) pour l'encodeur CLK
void encoderISR() {
int CLK_state = digitalRead(ENCODER_CLK_PIN);
int DT_state = digitalRead(ENCODER_DT_PIN);
if (CLK_state == HIGH) {
if (DT_state == LOW) {
encoderCount++;
} else {
encoderCount--;
}
}
}
void saveAlarmSettings() {
EEPROM.update(ALARM_HOUR_ADDR, (byte)alarmHour);
EEPROM.update(ALARM_MINUTE_ADDR, (byte)alarmMinute);
EEPROM.update(ALARM_ENABLED_ADDR, (byte)alarmEnabled);
lcd2.clear();
lcd2.setCursor(0,0);
lcd2.print("Sauvegarde OK!");
delay(1000);
}
void loadAlarmSettings() {
alarmHour = (int)EEPROM.read(ALARM_HOUR_ADDR);
alarmMinute = (int)EEPROM.read(ALARM_MINUTE_ADDR);
alarmEnabled = (bool)EEPROM.read(ALARM_ENABLED_ADDR);
if (alarmHour < 0 || alarmHour > 23 || alarmMinute < 0 || alarmMinute > 59) {
lcd2.clear();
lcd2.setCursor(0,0);
lcd2.print("EEPROM reinitialisee");
delay(1500);
alarmHour = 7;
alarmMinute = 0;
alarmEnabled = false;
saveAlarmSettings();
}
}
// MISE A JOUR : Utilisation de sprintf pour l'affichage du menu
void displayMainMenu() {
lcd2.clear();
char lineBuffer[21]; // Buffer pour une ligne (20 chars + null terminator)
sprintf(lineBuffer, "%cRegler Reveil ", (menuCursor == 0) ? '>' : ' ');
lcd2.setCursor(0, 0);
lcd2.print(lineBuffer);
sprintf(lineBuffer, "%cActiver/Desactiver ", (menuCursor == 1) ? '>' : ' ');
lcd2.setCursor(0, 1);
lcd2.print(lineBuffer);
sprintf(lineBuffer, "%cQuitter ", (menuCursor == 2) ? '>' : ' ');
lcd2.setCursor(0, 2);
lcd2.print(lineBuffer);
}
void displaySetAlarmTime(int selectedPart) {
lcd2.clear();
char lineBuffer[21];
sprintf(lineBuffer, "Regler Reveil: ");
lcd2.setCursor(0,0);
lcd2.print(lineBuffer);
char timeBuffer[6];
sprintf(timeBuffer, "%02d:%02d", alarmHour, alarmMinute);
sprintf(lineBuffer, " %s ", timeBuffer); // Ajout d'espaces pour nettoyer
lcd2.setCursor(0, 1);
lcd2.print(lineBuffer);
lcd2.cursor();
lcd2.blink();
if (selectedPart == 0) { // Heure
lcd2.setCursor(2, 1); // Positionner le curseur sur l'heure
} else { // Minute
lcd2.setCursor(5, 1); // Positionner le curseur sur les minutes (offset 2 pour les espaces + 3 pour HH:)
}
}
// MISE A JOUR : Utilisation de sprintf pour l'affichage actif/desactif
void displayAlarmEnableDisable() {
lcd2.clear();
char lineBuffer[21];
sprintf(lineBuffer, "Reveil: ");
lcd2.setCursor(0,0);
lcd2.print(lineBuffer);
sprintf(lineBuffer, "%cACTIF ", (alarmMenuCursor == 0) ? '>' : ' ');
lcd2.setCursor(0,1);
lcd2.print(lineBuffer);
sprintf(lineBuffer, "%cDESACTIVE ", (alarmMenuCursor == 1) ? '>' : ' ');
lcd2.setCursor(0,2);
lcd2.print(lineBuffer);
}
// NOUVELLE FONCTION pour gerer l'affichage de l'etat de l'alarme sur LCD2 en mode normal
void displayAlarmStatusOnLCD2() {
char line1Buffer[21]; // Buffer pour la ligne 1
char line2Buffer[21]; // Buffer pour la ligne 2
if (alarmEnabled) {
char alarmTimeBuffer[6];
sprintf(alarmTimeBuffer, "%02d:%02d", alarmHour, alarmMinute);
sprintf(line1Buffer, "ALARME: %s ", alarmTimeBuffer);
if (snoozeActive) {
long remainingMillis = (long)snoozeEndTimeMillis - (long)millis();
if (remainingMillis < 0) remainingMillis = 0;
int displayMinutes = remainingMillis / (60L * 1000L);
int displaySeconds = (remainingMillis % (60L * 1000L)) / 1000L;
char snoozeCountBuffer[6];
sprintf(snoozeCountBuffer, "%02d:%02d", displayMinutes, displaySeconds);
sprintf(line2Buffer, "SNOOZE: %s ", snoozeCountBuffer);
} else {
sprintf(line2Buffer, " "); // Vider la ligne si pas de snooze
}
} else {
sprintf(line1Buffer, "ALARME DESACTIVE ");
sprintf(line2Buffer, " "); // Vider la ligne
}
// N'afficher que si le contenu a change pour eviter le clignotement
static char lastLine1Buffer[21] = "";
static char lastLine2Buffer[21] = "";
if (strcmp(line1Buffer, lastLine1Buffer) != 0) {
lcd2.setCursor(0,0);
lcd2.print(line1Buffer);
strcpy(lastLine1Buffer, line1Buffer);
}
if (strcmp(line2Buffer, lastLine2Buffer) != 0) {
lcd2.setCursor(0,1);
lcd2.print(line2Buffer);
strcpy(lastLine2Buffer, line2Buffer);
}
}
void setup() {
Wire.begin();
lcd1.init(); lcd1.backlight(); lcd1.clear();
lcd2.init(); lcd2.backlight(); lcd2.print("Chargement..."); delay(1000);
// Charger le caractere personnalise de la cloche (adresse 0)
lcd1.createChar(0, bellChar);
pinMode(ENCODER_SW_PIN, INPUT_PULLUP);
pinMode(MODE_BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK_PIN), encoderISR, CHANGE);
if (!rtc.begin()) {
lcd2.clear(); lcd2.print("RTC introuvable!"); while (1);
}
if (!rtc.isrunning()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
} else {
DateTime checkTime = rtc.now();
if (checkTime.year() < 2024) { // Correction de l'annee pour la reinitialisation RTC
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
}
loadAlarmSettings();
}
void loop() {
DateTime now = rtc.now();
// --- Lecture des entrees ---
bool selectPressed = readButton(ENCODER_SW_PIN, 0);
bool modePressed = readButton(MODE_BUTTON_PIN, 1);
bool encoder_up = false;
bool encoder_down = false;
long currentEncoderCount;
noInterrupts();
currentEncoderCount = encoderCount;
interrupts();
if (currentEncoderCount >= ENCODER_SENSITIVITY_THRESHOLD) {
encoder_up = true;
noInterrupts();
encoderCount -= ENCODER_SENSITIVITY_THRESHOLD;
interrupts();
} else if (currentEncoderCount <= -ENCODER_SENSITIVITY_THRESHOLD) {
encoder_down = true;
noInterrupts();
encoderCount += ENCODER_SENSITIVITY_THRESHOLD;
interrupts();
}
// --- Gestion du mode d'affichage/menu ---
static unsigned long lastDisplayUpdate = 0;
static unsigned long lastAlarmDisplayUpdate = 0; // Renomme pour plus de clarte
static int currentSetPart = 0; // Garde ici pour la coherence, bien que defini globalement
// Gerer l'entree dans le menu principal
if (currentMenuState == STATE_NORMAL_DISPLAY && modePressed) {
currentMenuState = STATE_MAIN_MENU;
menuCursor = 0;
displayMainMenu();
noTone(BUZZER_PIN); // Eteindre le buzzer si on entre dans le menu
lcd2.noCursor();
lcd2.noBlink();
snoozeActive = false; // Desactiver le snooze si on entre dans le menu (nouvelle session)
}
// Gerer la sortie des sous-menus vers le menu principal ou le retour a l'affichage normal
else if (modePressed) {
if (currentMenuState == STATE_MAIN_MENU) {
currentMenuState = STATE_NORMAL_DISPLAY;
lcd2.clear(); // Nettoyage de l'ecran lors du retour a l'affichage normal
} else { // Si on est dans un sous-menu (SET_HOUR, SET_MINUTE, ENABLE_DISABLE, RINGING)
currentMenuState = STATE_MAIN_MENU;
displayMainMenu();
}
noTone(BUZZER_PIN); // Eteindre le buzzer si on quitte un sous-menu
lcd2.noCursor();
lcd2.noBlink();
currentSetPart = 0; // Reinitalise a 0 (heures) quand on quitte le mode reglage
snoozeActive = false; // Desactiver le snooze si on quitte le menu (arret complet)
}
// --- Machine a etats du menu ---
switch (currentMenuState) {
case STATE_NORMAL_DISPLAY:
if (millis() - lastDisplayUpdate >= 1000) {
displayTimeAndDateOnLCD1(now);
lastDisplayUpdate = millis();
}
// Rafraichir LCD2 (etat de l'alarme et decompte snooze) plus frequemment si snooze actif
// ou si les etats changent.
// Appel de la nouvelle fonction centralisee :
if (millis() - lastAlarmDisplayUpdate >= (snoozeActive ? 250 : 1000)) { // Rafraichissement plus rapide si snooze
displayAlarmStatusOnLCD2();
lastAlarmDisplayUpdate = millis();
}
// Logique de declenchement de l'alarme (normale ou snooze)
// Correction ici : L'alarme se declenche si non declenchee AUJOURD'HUI ET (pas en snooze OU snooze termine)
if (alarmEnabled) { // L'alarme doit etre activee
bool shouldRingNow = false;
if (!snoozeActive) { // Declenchement normal
if (now.hour() == alarmHour && now.minute() == alarmMinute && now.second() == 0 && !alarmTriggeredToday) {
shouldRingNow = true;
}
} else { // Declenchement apres snooze
if (millis() >= snoozeEndTimeMillis) { // Si le temps de fin du snooze est atteint ou depasse
shouldRingNow = true;
snoozeActive = false; // Le snooze est termine
alarmTriggeredToday = false; // Reinitaliser pour permettre le redeclenchement apres snooze
}
}
if (shouldRingNow) {
currentMenuState = STATE_ALARM_RINGING;
alarmStartTime = millis(); // Enregistrer le debut de la sonnerie
noteIndex = 0; // Reinitaliser l'index de la melodie
noteStartTime = millis(); // Reinitaliser le temps pour la melodie
alarmTriggeredToday = true; // Mettre a jour seulement si l'alarme sonne vraiment
lcd2.clear(); // Nettoyer l'ecran LCD2 pour l'affichage de l'alarme
char alarmMsgBuffer[21];
sprintf(alarmMsgBuffer, "REVEIL ! ");
lcd2.setCursor(0,0); lcd2.print(alarmMsgBuffer);
char alarmTimeMsgBuffer[21];
sprintf(alarmTimeMsgBuffer, "%02d:%02d ", alarmHour, alarmMinute);
lcd2.setCursor(0,1); lcd2.print(alarmTimeMsgBuffer);
}
}
// Reinitaliser le drapeau de declenchement d'alarme a minuit
if (now.hour() == 0 && now.minute() == 0 && now.second() == 1 && alarmTriggeredToday) { // Apres minuit et si l'alarme a sonnee
alarmTriggeredToday = false;
snoozeActive = false; // Assurez-vous que le snooze est aussi desactive au changement de jour
}
break;
case STATE_ALARM_RINGING:
// Logique de melodie non bloquante
{
int currentNote = melody[noteIndex];
int currentNoteDuration = 1000 / noteDurations[noteIndex];
if (millis() - noteStartTime >= currentNoteDuration + 30) { // Ajouter une petite pause entre les notes
noTone(BUZZER_PIN); // Arreter la note precedente ou la pause
noteIndex++; // Passer a la note suivante
if (noteIndex >= sizeof(melody)/sizeof(melody[0])) {
noteIndex = 0; // Recommencer la melodie
}
noteStartTime = millis();
if (melody[noteIndex] != 0) {
tone(BUZZER_PIN, melody[noteIndex], 0); // Jouer la note sans duree (sera arretee par noTone ou la prochaine note)
}
}
}
// Afficher le message de reveil (mise a jour pour sprintf et clarte)
char alarmRingLine1[21];
char alarmRingLine2[21];
sprintf(alarmRingLine1, "REVEIL ! ");
sprintf(alarmRingLine2, "%02d:%02d ", alarmHour, alarmMinute);
lcd2.setCursor(0,0); lcd2.print(alarmRingLine1);
lcd2.setCursor(0,1); lcd2.print(alarmRingLine2);
// Arreter l'alarme apres la duree maximale de sonnerie (2 minutes)
if (millis() - alarmStartTime > SNOOZE_DURATION_MILLIS) {
noTone(BUZZER_PIN);
currentMenuState = STATE_NORMAL_DISPLAY;
lcd2.clear();
alarmTriggeredToday = true; // Empeche l'alarme de sonner a nouveau le meme jour (sauf si snooze la reactive)
snoozeActive = false; // S'assurer que le snooze est desactive
}
// Arreter l'alarme si l'utilisateur appuie sur SELECT (SNOOZE) ou MODE (ARRET TOTAL)
if (selectPressed) { // Bouton SELECT pour SNOOZE
noTone(BUZZER_PIN);
snoozeActive = true; // Activer le mode snooze
currentMenuState = STATE_NORMAL_DISPLAY; // Revenir a l'affichage normal
lcd2.clear(); // Effacer le message d'alarme
// Calculer et stocker l'heure de fin du snooze en MILLISECONDES
snoozeEndTimeMillis = millis() + SNOOZE_DURATION_MILLIS;
} else if (modePressed) { // Bouton MODE pour arreter l'alarme completement
noTone(BUZZER_PIN);
currentMenuState = STATE_NORMAL_DISPLAY;
lcd2.clear();
alarmEnabled = false; // Desactiver l'alarme apres arret complet
snoozeActive = false;
alarmTriggeredToday = true; // L'alarme est consideree comme "declenchee" pour la journee
saveAlarmSettings(); // Sauvegarder l'etat desactive
}
break;
case STATE_MAIN_MENU:
if (encoder_up) {
menuCursor = (menuCursor + 1) % 3;
displayMainMenu();
}
if (encoder_down) {
menuCursor = (menuCursor == 0) ? 2 : menuCursor - 1;
displayMainMenu();
}
if (selectPressed) {
if (menuCursor == 0) {
currentMenuState = STATE_SET_ALARM_HOUR;
currentSetPart = 0;
displaySetAlarmTime(currentSetPart);
} else if (menuCursor == 1) {
currentMenuState = STATE_ALARM_ENABLE_DISABLE;
alarmMenuCursor = alarmEnabled ? 0 : 1;
displayAlarmEnableDisable();
} else if (menuCursor == 2) {
currentMenuState = STATE_NORMAL_DISPLAY;
lcd2.clear(); // Nettoyage de l'ecran lors du retour a l'affichage normal
}
}
break;
case STATE_SET_ALARM_HOUR:
if (encoder_up) {
handleEncoderChange(true, false, &alarmHour, 24);
displaySetAlarmTime(currentSetPart);
} else if (encoder_down) {
handleEncoderChange(false, true, &alarmHour, 24);
displaySetAlarmTime(currentSetPart);
}
if (selectPressed) {
currentMenuState = STATE_SET_ALARM_MINUTE;
currentSetPart = 1;
displaySetAlarmTime(currentSetPart);
}
break;
case STATE_SET_ALARM_MINUTE:
if (encoder_up) {
handleEncoderChange(true, false, &alarmMinute, 60);
displaySetAlarmTime(currentSetPart);
} else if (encoder_down) {
handleEncoderChange(false, true, &alarmMinute, 60);
displaySetAlarmTime(currentSetPart);
}
if (selectPressed) {
saveAlarmSettings();
currentMenuState = STATE_MAIN_MENU;
displayMainMenu();
lcd2.noCursor();
lcd2.noBlink();
}
break;
case STATE_ALARM_ENABLE_DISABLE:
if (encoder_up || encoder_down) {
alarmMenuCursor = (alarmMenuCursor == 0) ? 1 : 0;
displayAlarmEnableDisable();
}
if (selectPressed) {
alarmEnabled = (alarmMenuCursor == 0);
saveAlarmSettings();
currentMenuState = STATE_MAIN_MENU;
displayMainMenu();
}
break;
}
}
// --- Fonctions d'aide (definitions) ---
void displayTimeAndDateOnLCD1(DateTime now) {
char timeBuffer[9];
sprintf(timeBuffer, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
int timeLength = strlen(timeBuffer);
int startColTime = (20 - timeLength) / 2;
lcd1.setCursor(startColTime, 0);
lcd1.print(timeBuffer);
// Efface le reste de la ligne si necessaire
for (int i = startColTime + timeLength; i < 20; i++) lcd1.print(' ');
char dayOfWeekBuffer[13];
sprintf(dayOfWeekBuffer, "%s", daysOfTheWeek[now.dayOfTheWeek()]);
int dayOfWeekLength = strlen(dayOfWeekBuffer);
int startColDayOfWeek = (20 - dayOfWeekLength) / 2;
lcd1.setCursor(startColDayOfWeek, 1);
lcd1.print(dayOfWeekBuffer);
for (int i = startColDayOfWeek + dayOfWeekLength; i < 20; i++) lcd1.print(' ');
char dateOnlyBuffer[17];
sprintf(dateOnlyBuffer, "%02d %s %04d", now.day(), monthsOfTheYear[now.month() - 1], now.year());
int dateOnlyLength = strlen(dateOnlyBuffer);
int startColDateOnly = (20 - dateOnlyLength) / 2;
lcd1.setCursor(startColDateOnly, 2);
lcd1.print(dateOnlyBuffer);
for (int i = startColDateOnly + dateOnlyLength; i < 20; i++) lcd1.print(' ');
// --- MODIFICATION ICI : Centrage avec deux cloches et heure de l'alarme via sprintf ---
// Nous aurons besoin de manipuler les caracteres personnalises separement,
// car sprintf ne peut pas inserer directement des caracteres personnalises.
// La strategie est de creer la partie texte avec sprintf, puis d'imprimer les cloches autour.
if (alarmEnabled) {
char alarmTimeOnlyBuffer[6]; // Pour "HH:MM"
sprintf(alarmTimeOnlyBuffer, "%02d:%02d", alarmHour, alarmMinute);
// Longueur totale de l'affichage incluant les deux cloches et les espaces
// Cloche (1) + Espaces (4) + HH:MM (5) + Espaces (4) + Cloche (1) = 15
int totalDisplayLength = 15; // MIS A JOUR POUR 4 ESPACES DE CHAQUE COTE
// Calcule la colonne de depart pour centrer
int startColAlarmDisplay = (20 - totalDisplayLength) / 2;
// Positionne le curseur et imprime les elements
lcd1.setCursor(startColAlarmDisplay, 3);
lcd1.write(0); // Premiere cloche
lcd1.print(" "); // QUATRE ESPACES ICI
lcd1.print(alarmTimeOnlyBuffer); // Heure de l'alarme
lcd1.print(" "); // QUATRE ESPACES ICI
lcd1.write(0); // Deuxieme cloche
// Efface le reste de la ligne a droite
for (int i = startColAlarmDisplay + totalDisplayLength; i < 20; i++) lcd1.print(' ');
} else {
// Si l'alarme est desactivee, affiche "Reveil desactive" au centre de la ligne 3
// Utilisation d'un buffer pour eviter le clignotement
char line3Buffer[21]; // Buffer pour stocker la ligne entiere (20 caracteres + null-terminateur)
const char* disabledMessage = "Reveil desactive";
int messageLength = strlen(disabledMessage);
int startColMessage = (20 - messageLength) / 2;
// Remplir le buffer avec des espaces
for (int i = 0; i < 20; i++) {
line3Buffer[i] = ' ';
}
line3Buffer[20] = '\0'; // Null-terminer la chaine
// Copier le message au centre du buffer
memcpy(&line3Buffer[startColMessage], disabledMessage, messageLength);
// Afficher la ligne entiere en une seule fois
lcd1.setCursor(0, 3);
lcd1.print(line3Buffer);
}
}
void handleEncoderChange(bool up, bool down, int* value, int max_value) {
if (up) {
(*value)++;
if (*value >= max_value) {
*value = 0;
}
} else if (down) {
(*value)--;
if (*value < 0) {
*value = max_value - 1;
}
}
}