// ===================================================
// Projet HomeHub – Horloge numérique corrigée
// Date : 2025-07-07 Version : 3.023
// ===================================================
bool horlogeNumeriqueActive = false;
// === Librairies ===
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
#include <Wire.h> // Nécessaire pour I2C (RTC et tactile capacitif)
#include <RTClib.h>
#include <DHT.h>
#include <Adafruit_FT6206.h> // Nouvelle librairie pour le tactile capacitif
#include <math.h> // Pour isnan(), sin(), cos()
#include <stdlib.h> // Pour dtostrf()
// === Constantes ===
#define DHTPIN_INT 3
#define DHTPIN_EXT 4
#define DHTTYPE DHT22
// === LCD_MENU : écran avec tactile capacitif ===
#define LCD_MENU_CS 10
#define LCD_MENU_DC 9
#define LCD_MENU_RST 8
Adafruit_ILI9341 lcd_Menu = Adafruit_ILI9341(LCD_MENU_CS, LCD_MENU_DC, LCD_MENU_RST);
// === Contrôleur tactile capacitif (pour LCD_MENU) ===
Adafruit_FT6206 ts = Adafruit_FT6206(); // L'objet tactile capacitif
// === LCD_INFOS : écran secondaire ===
#define LCD_INFOS_CS 40
#define LCD_INFOS_DC 41
#define LCD_INFOS_RST 42
Adafruit_ILI9341 lcd_Infos = Adafruit_ILI9341(LCD_INFOS_CS, LCD_INFOS_DC, LCD_INFOS_RST);
// === RTC ===
RTC_DS1307 rtc;
// === DHT ===
DHT dhtInt(DHTPIN_INT, DHTTYPE);
DHT dhtExt(DHTPIN_EXT, DHTTYPE);
// === Joystick ===
#define JOY_X A0 // Broche HORZ du joystick
#define JOY_Y A1 // Broche VERT du joystick
#define JOY_SEL 2 // Broche SEL du joystick
// Seuils pour la lecture analogique du joystick
#define JOY_THRESHOLD_HIGH 900
#define JOY_THRESHOLD_LOW 100
// === Variables globales ===
// === États précédents pour horloge digitale ===
int prevDigitHour = -1;
int prevDigitMinute = -1;
int prevDigitSecond = -1;
int prevDigitDayOfWeek = -1;
float prevTempDigit = -999.0;
bool horlogeDigitaleInitialized = false;
// === Variables pour l'horloge digitale ===
unsigned long previousMillisDigital = 0;
const unsigned long intervalDigital = 1000;
bool blinkDots = true;
int lastSecondDisplayed = -1;
// === Gestion du rafraîchissement par millis() pour l'horloge analogique ===
unsigned long previousMillisAiguilles = 0;
const unsigned long intervalAiguilles = 1000;
bool horlogeAffichee = false; // Pour éviter redessin inutile de l'horloge
float tempInt, tempExt;
float humInt, humExt;
DateTime now;
// États de l'écran principal (lcd_Menu)
int currentScreen = 0; // 0: Menu principal, 1: Heure/Date Detail, 2: Temperature Detail, 3: Reglages Menu, 5: Outils
// Sous-états du menu de réglages
// currentSettingsMenu (pour lcd_Infos) : 0: Aucun réglage spécifique (affiche le sous-menu de réglages sur lcd_Menu), 1: Réglage heure, 2: Réglage date
int currentSettingsMenu = 0;
// NOUVEAU: Sous-états du menu Outils
// currentToolsMenu : 0: Aucun outil spécifique (affiche le sous-menu d'outils sur lcd_Menu), 1: Horloge analogique, 2: Calendrier
int currentToolsMenu = 0;
// Variables pour suivre les changements pour rafraîchissement partiel sur lcd_Infos
int prevHour = -1, prevMinute = -1, prevSecond = -1;
int prevDay = -1, prevMonth = -1, prevYear = -1;
float prevTempInt = -999.0, prevTempExt = -999.0;
float prevHumInt = -999.0, prevHumExt = -999.0; // Variables pour l'humidité
// Variables d'état pour savoir si l'écran a déjà été dessiné (pour éviter de tout redessiner)
bool heureDateDetailInitialized = false;
bool reglagesSousMenuInitialized = false;
bool reglerHeureInitialized = false;
bool reglerDateInitialized = false;
bool temperaturePageInitialized = false;
bool calendrierPageInitialized = false; // Nouvelle variable pour le calendrier
bool outilsSousMenuInitialized = false; // Nouvelle variable pour le sous-menu Outils
bool horlogeAnalogiqueInitialized = false; // Nouvelle variable pour l'horloge analogique
// Variables pour le réglage d'heure/date avec le joystick
int tempAdjHour, tempAdjMinute, tempAdjSecond;
int tempAdjYear, tempAdjMonth, tempAdjDay;
// NOUVEAU: Constantes pour les identifiants de champ
const int FIELD_DAY = 0;
const int FIELD_MONTH = 1;
const int FIELD_YEAR = 2;
const int FIELD_HOUR = 3;
const int FIELD_MINUTE = 4;
const int FIELD_SECOND = 5;
int selectedField = FIELD_DAY; // Initialisation par défaut, sera mis à jour.
int prevSelectedField = -1; // NOUVEAU: Pour le rafraîchissement ciblé des champs de réglage
// Variables pour le debounce du joystick
unsigned long lastJoyMoveTime = 0;
const long joyDebounceDelay = 200; // ms
unsigned long lastJoyClickTime = 0;
const long joyClickDebounceDelay = 300; // ms
// Variable pour gérer l'affichage du bouton retour sur lcd_Menu
bool returnButtonVisible = false;
// CONSTANTES GLOBALES POUR LE MENU PRINCIPAL
const int MENU_START_Y = 70;
const int MENU_OPTION_HEIGHT = 20; // Réduit de 30 à 20
const int MENU_SPACING = 5; // Réduit de 10 à 5
const int MENU_TOUCHABLE_WIDTH = 320 - 20; // Largeur de l'écran - (2*décalage X)
// CONSTANTES GLOBALES POUR LE SOUS-MENU DE RÉGLAGES (SUR LCD_MENU)
const int SETTINGS_SUBMENU_START_Y = 60;
const int SETTINGS_SUBMENU_OPTION_HEIGHT = 20; // Réduit de 30 à 20
const int SETTINGS_SUBMENU_SPACING = 5; // Réduit de 10 à 5
const int SETTINGS_SUBMENU_TEXT_HEIGHT = 16; // Hauteur du texte pour setTextSize(2) = 8*2 = 16px
const uint16_t BACKGROUND_COLOR_REG_MENU = ILI9341_BLACK; // Couleur de fond pour le sous-menu Reglages
// NOUVEAU: CONSTANTES GLOBALES POUR LE SOUS-MENU D'OUTILS (SUR LCD_MENU)
const int TOOLS_SUBMENU_START_Y = 60;
const int TOOLS_SUBMENU_OPTION_HEIGHT = 20;
const int TOOLS_SUBMENU_SPACING = 5;
const int TOOLS_SUBMENU_TEXT_HEIGHT = 16;
const uint16_t BACKGROUND_COLOR_TOOLS_MENU = ILI9341_DARKCYAN; // Une couleur distincte pour les outils
// --- Constantes pour l'horloge analogique (déplacées ici pour la portée globale) ---
const int CLOCK_CENTER_X = 160; // Assumer le centre de lcd_Infos (320/2)
const int CLOCK_CENTER_Y = 120; // Assumer le centre de lcd_Infos (240/2)
const int CLOCK_RADIUS = 120; // Rayon du cadran de l'horloge (MAX pour un écran 240 de haut)
// Longueurs des aiguilles (pointe la plus éloignée du centre)
const int SEC_HAND_LENGTH = CLOCK_RADIUS - 25;
const int MIN_HAND_LENGTH = CLOCK_RADIUS - 40;
const int HOUR_HAND_LENGTH = CLOCK_RADIUS - 70;
// Largeur de la base des triangles pour les aiguilles
const int SEC_HAND_BASE_WIDTH = 2;
const int MIN_HAND_BASE_WIDTH = 4;
const int HOUR_HAND_BASE_WIDTH = 6;
// Couleurs pour l'horloge
const uint16_t FACE_COLOR = ILI9341_BLACK; // Couleur du cadran pour effacer
const uint16_t MARKER_COLOR = ILI9341_WHITE;
const uint16_t HOUR_HAND_COLOR = ILI9341_RED;
const uint16_t MIN_HAND_COLOR = ILI9341_YELLOW;
const uint16_t SEC_ON_COLOR = ILI9341_CYAN; // Couleur du point des secondes actif
const uint16_t SEC_OFF_COLOR = ILI9341_WHITE; // Couleur des points des secondes inactifs
const int SEC_DOT_RADIUS = 3; // Rayon des points des secondes
const int SEC_DOT_LENGTH = CLOCK_RADIUS - 6; // Position des points des secondes
// --- Variables pour l'horloge analogique (précédentes positions des aiguilles) ---
// Ces variables stockeront les 3 points du TRIANGLE précédent
static int prevSX1 = 0, prevSY1 = 0, prevSX2 = 0, prevSY2 = 0, prevSX3 = 0, prevSY3 = 0; // Seconde
static int prevMX1 = 0, prevMY1 = 0, prevMX2 = 0, prevMY2 = 0, prevMX3 = 0, prevMY3 = 0; // Minute
static int prevHX1 = 0, prevHY1 = 0, prevHX2 = 0, prevHY2 = 0, prevHX3 = 0, prevHY3 = 0; // Heure
// Variables pour l'optimisation du rafraîchissement des aiguilles
static int prevCurrentHour = -1, prevCurrentMinute = -1, prevCurrentSecond = -1;
// === Fonctions d'affichage ===
// Déclarations anticipées (prototypes) pour les fonctions appelées avant leur définition
void actualiserAiguillesHorloge();
void drawNumericField(Adafruit_ILI9341 &lcd, int fieldType, int value, int x, int y, int textSize, uint16_t normalColor, uint16_t highlightColor, uint16_t bgColor);
void drawReglerHeureFields();
void drawReglerDateFields();
// Fonction utilitaire pour imprimer 2 chiffres
void print2Digits(Adafruit_ILI9341 &lcd, int number) {
if (number < 10) lcd.print("0");
lcd.print(number);
}
void afficherMenu() {
lcd_Menu.fillScreen(ILI9341_BLACK);
String title = "HomeHub Menu";
lcd_Menu.setTextSize(3);
int16_t x1, y1;
uint16_t w_title, h_title;
lcd_Menu.getTextBounds(title, 0, 0, &x1, &y1, &w_title, &h_title);
int cursorX = (lcd_Menu.width() - w_title) / 2;
int cursorY = 20;
lcd_Menu.setCursor(cursorX, cursorY);
lcd_Menu.setTextColor(ILI9341_WHITE);
lcd_Menu.setTextSize(3);
lcd_Menu.println(title);
lcd_Menu.drawLine(cursorX, cursorY + h_title + 2, cursorX + w_title, cursorY + h_title + 2, ILI9341_WHITE);
// Utilisation des constantes globales pour les positions du menu
lcd_Menu.setTextSize(2);
lcd_Menu.setTextColor(ILI9341_YELLOW);
lcd_Menu.setCursor(20, MENU_START_Y);
lcd_Menu.println("1. Heure/Date");
lcd_Menu.setCursor(20, MENU_START_Y + (MENU_OPTION_HEIGHT + MENU_SPACING) * 1);
lcd_Menu.println("2. Temperature");
lcd_Menu.setCursor(20, MENU_START_Y + (MENU_OPTION_HEIGHT + MENU_SPACING) * 2);
lcd_Menu.println("3. Reglages");
// Ancienne Option 4. Calendrier retirée d'ici
lcd_Menu.setCursor(20, MENU_START_Y + (MENU_OPTION_HEIGHT + MENU_SPACING) * 3); // L'option 5 devient 4
lcd_Menu.println("4. Outils"); // Renommée "Outils" en 4ème position
returnButtonVisible = false;
}
// Fonction pour afficher le bouton "Retour" sur lcd_Menu
void drawReturnButton_Menu() {
if (!returnButtonVisible) {
lcd_Menu.drawRect(10, 200, 80, 30, ILI9341_RED);
lcd_Menu.setCursor(20, 208);
lcd_Menu.setTextSize(2);
lcd_Menu.setTextColor(ILI9341_WHITE);
lcd_Menu.println("Retour");
returnButtonVisible = true;
}
}
// Fonction pour cacher le bouton "Retour" sur lcd_Menu
void hideReturnButton_Menu() {
if (returnButtonVisible) {
lcd_Menu.fillRect(10, 200, 80, 30, ILI9341_BLACK);
returnButtonVisible = false;
}
}
// Nouvelle fonction pour afficher un écran "vide" ou de bienvenue sur lcd_Infos
void clearAndDisplayDefaultInfos() {
lcd_Infos.fillScreen(ILI9341_BLACK);
lcd_Infos.setTextColor(ILI9341_CYAN); // COULEUR LISIBLE
// Message de bienvenue et instructions
String line1 = "Bienvenue sur";
String line2 = "votre HomeHub!";
String line3 = ""; // Ligne vide
String line4 = "Selectionnez une";
String line5 = "option sur";
String line6 = "l'ecran de menu.";
lcd_Infos.setTextSize(2); // Taille du texte
int textHeight = 8 * 2; // Hauteur d'une ligne de texte
// Calculer les positions Y de chaque ligne
int startY = 10; // Point de départ initial
int lineSpacing = textHeight + 5; // Hauteur du texte + espacement entre les lignes
// Centrer et afficher chaque ligne
int cursorX;
// Ligne 1
int16_t x1, y1; uint16_t w_line, h_line;
lcd_Infos.getTextBounds(line1, 0, 0, &x1, &y1, &w_line, &h_line);
cursorX = (lcd_Infos.width() - w_line) / 2;
lcd_Infos.setCursor(cursorX, startY);
lcd_Infos.println(line1);
// Ligne 2
lcd_Infos.getTextBounds(line2, 0, 0, &x1, &y1, &w_line, &h_line);
cursorX = (lcd_Infos.width() - w_line) / 2;
lcd_Infos.setCursor(cursorX, startY + lineSpacing);
lcd_Infos.println(line2);
// Ligne 3 (vide)
// Pas d'affichage, juste un saut de ligne virtuel pour l'espacement
// Ligne 4
lcd_Infos.getTextBounds(line4, 0, 0, &x1, &y1, &w_line, &h_line);
cursorX = (lcd_Infos.width() - w_line) / 2;
lcd_Infos.setCursor(cursorX, startY + lineSpacing * 3); // *3 pour sauter la ligne vide
lcd_Infos.println(line4);
// Ligne 5
lcd_Infos.getTextBounds(line5, 0, 0, &x1, &y1, &w_line, &h_line);
cursorX = (lcd_Infos.width() - w_line) / 2;
lcd_Infos.setCursor(cursorX, startY + lineSpacing * 4);
lcd_Infos.println(line5);
// Ligne 6
lcd_Infos.getTextBounds(line6, 0, 0, &x1, &y1, &w_line, &h_line);
cursorX = (lcd_Infos.width() - w_line) / 2;
lcd_Infos.setCursor(cursorX, startY + lineSpacing * 5);
lcd_Infos.println(line6);
heureDateDetailInitialized = false;
reglagesSousMenuInitialized = false;
reglerHeureInitialized = false;
reglerDateInitialized = false;
temperaturePageInitialized = false;
calendrierPageInitialized = false;
outilsSousMenuInitialized = false; // Réinitialiser le drapeau du sous-menu Outils
horlogeAnalogiqueInitialized = false; // Réinitialiser le drapeau de l'horloge analogique
horlogeAffichee = false; // Réinitialiser ce drapeau aussi
currentSettingsMenu = 0;
currentToolsMenu = 0; // Réinitialiser le sous-menu outils
hideReturnButton_Menu();
prevHour = -1; prevMinute = -1; prevSecond = -1;
prevDay = -1; prevMonth = -1; prevYear = -1;
// Forcer le premier affichage des températures et de l'humidité
prevTempInt = -999.0;
prevTempExt = -999.0;
prevHumInt = -999.0;
prevHumExt = -999.0;
prevSelectedField = -1;
// Réinitialiser les positions des aiguilles pour l'horloge
prevHX1 = 0; prevHY1 = 0; prevHX2 = 0; prevHY2 = 0; prevHX3 = 0; prevHY3 = 0;
prevMX1 = 0; prevMY1 = 0; prevMX2 = 0; prevMY2 = 0; prevMX3 = 0; prevMY3 = 0;
prevSX1 = 0; prevSY1 = 0; prevSX2 = 0; prevSY2 = 0; prevSX3 = 0; prevSY3 = 0;
prevCurrentHour = -1; // Réinitialiser pour forcer le premier dessin complet de l'horloge
prevCurrentMinute = -1;
prevCurrentSecond = -1;
}
// Fonctions d'affichage détaillées pour lcd_Infos
void afficherHeureDateDetail_Infos() {
// Déclarer les constantes locales pour les calculs de positionnement
const int CHAR_WIDTH_SIZE4 = 6 * 4;
const int CHAR_HEIGHT_SIZE4 = 8 * 4;
const int COLON_WIDTH_SIZE4 = 6 * 4;
int hourY = 60;
int hourX_pos_overall = (lcd_Infos.width() / 2) - (6 * CHAR_WIDTH_SIZE4 + 2 * COLON_WIDTH_SIZE4) / 2;
int firstColonX = hourX_pos_overall + (2 * CHAR_WIDTH_SIZE4);
int secondColonX = firstColonX + COLON_WIDTH_SIZE4 + (2 * CHAR_WIDTH_SIZE4);
int hourX_pos = (lcd_Infos.width() / 2) - (6 * CHAR_WIDTH_SIZE4 + 2 * COLON_WIDTH_SIZE4) / 2;
int minuteX_pos = hourX_pos + (2 * CHAR_WIDTH_SIZE4) + COLON_WIDTH_SIZE4;
int secondX_pos = minuteX_pos + (2 * CHAR_WIDTH_SIZE4) + COLON_WIDTH_SIZE4;
int hourY_pos = 60;
if (!heureDateDetailInitialized) { // Dessiner les éléments statiques une seule fois
lcd_Infos.fillScreen(ILI9341_BLUE);
lcd_Infos.setTextColor(ILI9341_WHITE);
String title = "Heure & Date";
lcd_Infos.setTextSize(3);
int16_t x1, y1;
uint16_t w_title, h_title;
lcd_Infos.getTextBounds(title, 0, 0, &x1, &y1, &w_title, &h_title);
int cursorX_title = (lcd_Infos.width() - w_title) / 2;
int cursorY_title = 10;
lcd_Infos.setCursor(cursorX_title, cursorY_title);
lcd_Infos.println(title);
lcd_Infos.drawLine(cursorX_title, cursorY_title + h_title + 2, cursorX_title + w_title, cursorY_title + h_title + 2, ILI9341_WHITE);
lcd_Infos.setTextSize(4);
lcd_Infos.setTextColor(ILI9341_YELLOW); // Couleur pour les ":"
lcd_Infos.setCursor(firstColonX, hourY);
lcd_Infos.print(":");
lcd_Infos.setCursor(secondColonX, hourY);
lcd_Infos.print(":");
heureDateDetailInitialized = true;
}
now = rtc.now(); // Assurez-vous que 'now' est à jour pour l'initialisation des tempAdj
// Initialisation des tempAdj la première fois que la page est ouverte
// C'est la logique pour les écrans de réglage, pas pour l'affichage simple de l'heure.
// Cette partie était dans afficherHeureDateDetail_Infos() mais appartient logiquement à afficherReglerHeure_Infos()
// et afficherReglerDate_Infos(). Je la laisse ici telle que vous l'avez soumise, mais il est bon de le noter.
if(prevSelectedField == -1) { // Cela signifie que c'est la première entrée dans la page de réglage
tempAdjHour = now.hour();
tempAdjMinute = now.minute();
tempAdjSecond = now.second();
selectedField = FIELD_HOUR; // Sélectionne l'heure par défaut
}
if (now.hour() != prevHour) {
char hourStr[3];
sprintf(hourStr, "%02d", now.hour());
lcd_Infos.setTextSize(4);
lcd_Infos.setTextColor(ILI9341_YELLOW);
lcd_Infos.setCursor(hourX_pos, hourY_pos);
lcd_Infos.fillRect(hourX_pos, hourY_pos, CHAR_WIDTH_SIZE4 * 2, CHAR_HEIGHT_SIZE4, ILI9341_BLUE);
lcd_Infos.print(hourStr);
prevHour = now.hour();
}
if (now.minute() != prevMinute) {
char minuteStr[3];
sprintf(minuteStr, "%02d", now.minute());
lcd_Infos.setTextSize(4);
lcd_Infos.setTextColor(ILI9341_YELLOW);
lcd_Infos.setCursor(minuteX_pos, hourY_pos);
lcd_Infos.fillRect(minuteX_pos, hourY_pos, CHAR_WIDTH_SIZE4 * 2, CHAR_HEIGHT_SIZE4, ILI9341_BLUE);
lcd_Infos.print(minuteStr);
prevMinute = now.minute();
}
if (now.second() != prevSecond) {
char secondStr[3];
sprintf(secondStr, "%02d", now.second());
lcd_Infos.setTextSize(4);
lcd_Infos.setTextColor(ILI9341_YELLOW);
lcd_Infos.setCursor(secondX_pos, hourY_pos);
lcd_Infos.fillRect(secondX_pos, hourY_pos, CHAR_WIDTH_SIZE4 * 2, CHAR_HEIGHT_SIZE4, ILI9341_BLUE);
lcd_Infos.print(secondStr);
prevSecond = now.second();
}
if (now.day() != prevDay || now.month() != prevMonth || now.year() != prevYear) {
char dateStr[100];
const char* daysOfWeek[7] = {"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"};
sprintf(dateStr, "%s, %02d/%02d/%d", daysOfWeek[now.dayOfTheWeek()], now.day(), now.month(), now.year());
lcd_Infos.setTextSize(2);
lcd_Infos.setTextColor(ILI9341_GREEN);
int dateY_pos = 140;
int dateX_pos = (lcd_Infos.width() - (strlen(dateStr) * 6 * 2)) / 2;
lcd_Infos.setCursor(dateX_pos, dateY_pos);
lcd_Infos.fillRect(0, dateY_pos, lcd_Infos.width(), 30, ILI9341_BLUE);
lcd_Infos.print(dateStr);
prevDay = now.day();
prevMonth = now.month();
prevYear = now.year();
}
}
void afficherTemperatureDetail_Infos() {
// Constants for text sizing and spacing
const int TEXT_SIZE_VALUES = 2; // Size for "XX.X" values
const int TEXT_SIZE_LABELS = 2; // Size for "Temperature:" / "Humidite:"
const int TEXT_SIZE_SECTION_TITLE = 2; // Size for "Capteur Interieur"
const int CHAR_WIDTH_SIZE2 = 6 * 2; // 6 pixels/char * size 2 (for setTextSize(2))
const int LINE_HEIGHT = 8 * TEXT_SIZE_LABELS; // Base height of a line of text (8 pixels/char * size)
const int SECTION_SPACING = 20; // Space between sensor blocks
const int LINE_SPACING_WITHIN_SECTION = 5; // Space between lines within a sensor block
const uint16_t BACKGROUND_CLR = ILI9341_DARKCYAN;
const uint16_t MAIN_TITLE_COLOR = ILI9341_WHITE;
const uint16_t SECTION_TITLE_COLOR = ILI9341_CYAN;
const uint16_t LABEL_COLOR = ILI9341_YELLOW;
const uint16_t VALUE_COLOR = ILI9341_WHITE; // Changed value color for better contrast
// Déclarer les variables de positionnement du titre principal ICI
// pour qu'elles soient disponibles dans toute la fonction.
int16_t x1, y1;
uint16_t w_title, h_title;
String mainTitle = "Temperatures & Humidite"; // Le texte du titre
// Calculer les dimensions du titre principal une seule fois
lcd_Infos.setTextSize(2); // Taille du titre principal
lcd_Infos.getTextBounds(mainTitle, 0, 0, &x1, &y1, &w_title, &h_title);
int cursorX_title = (lcd_Infos.width() - w_title) / 2; // Centrer le titre
int cursorY_title = 10; // Position Y du titre principal
// Constante pour la colonne de départ des labels (ex: "Temperature:")
const int LABEL_COLUMN_START_X = 20; // Marge gauche pour les labels
// Nouvelle constante pour la colonne de départ des UNITES (C ou %) pour l'alignement
const int UNIT_ALIGN_X = 250; // Ajustez cette valeur pour aligner les unités comme désirées.
// Ex: 250px à partir du bord gauche pour le début du caractère ' ' avant 'C' ou '%'.
// La largeur de l'écran est 320px.
// Largeur d'effacement pour les valeurs numériques (doit couvrir le nombre ET l'unité)
// Max nombre est "100.0" (5 caractères) + " %" (2 caractères) = 7 caractères.
// 7 chars * 12 pixels/char (size 2) = 84 pixels. Prenons un peu plus large pour être sûr.
const int VALUE_UNIT_RECT_WIDTH = 90; // Largeur pour effacer l'ancienne valeur + unité
// Calcul explicite des positions Y de chaque ligne de contenu
const int Y_MAIN_TITLE_BOTTOM_LINE = cursorY_title + h_title + 2;
const int Y_SECTION_INT_TITLE = Y_MAIN_TITLE_BOTTOM_LINE + 15;
const int Y_TEMP_INT_ROW = Y_SECTION_INT_TITLE + LINE_HEIGHT + LINE_SPACING_WITHIN_SECTION;
const int Y_HUM_INT_ROW = Y_TEMP_INT_ROW + LINE_HEIGHT + LINE_SPACING_WITHIN_SECTION;
const int Y_SECTION_EXT_TITLE = Y_HUM_INT_ROW + LINE_HEIGHT + SECTION_SPACING;
const int Y_TEMP_EXT_ROW = Y_SECTION_EXT_TITLE + LINE_HEIGHT + LINE_SPACING_WITHIN_SECTION;
const int Y_HUM_EXT_ROW = Y_TEMP_EXT_ROW + LINE_HEIGHT + LINE_SPACING_WITHIN_SECTION;
if (!temperaturePageInitialized) {
lcd_Infos.fillScreen(BACKGROUND_CLR); // Efface tout l'écran Infos
lcd_Infos.setTextColor(MAIN_TITLE_COLOR);
lcd_Infos.setTextSize(2); // Taille du titre principal
lcd_Infos.setCursor(cursorX_title, cursorY_title);
lcd_Infos.println(mainTitle);
lcd_Infos.drawLine(cursorX_title, cursorY_title + h_title + 2, cursorX_title + w_title, cursorY_title + h_title + 2, MAIN_TITLE_COLOR);
// --- Initial drawing of static labels for both sensors ---
lcd_Infos.setTextSize(TEXT_SIZE_SECTION_TITLE);
lcd_Infos.setTextColor(SECTION_TITLE_COLOR); // Utilisation correcte de SECTION_TITLE_COLOR
lcd_Infos.setCursor(LABEL_COLUMN_START_X, Y_SECTION_INT_TITLE); // Capteur Interieur Section
lcd_Infos.println("Capteur Interieur:"); // Pas d'accents
lcd_Infos.setTextSize(TEXT_SIZE_LABELS);
lcd_Infos.setTextColor(LABEL_COLOR);
lcd_Infos.setCursor(LABEL_COLUMN_START_X, Y_TEMP_INT_ROW); // Temperature Interieure Label
lcd_Infos.print("Temperature:"); // Pas d'accents
lcd_Infos.setCursor(LABEL_COLUMN_START_X, Y_HUM_INT_ROW); // Humidite Interieure Label
lcd_Infos.print("Humidite:"); // Pas d'accents
lcd_Infos.setTextSize(TEXT_SIZE_SECTION_TITLE);
lcd_Infos.setTextColor(SECTION_TITLE_COLOR); // Utilisation correcte de SECTION_TITLE_COLOR
lcd_Infos.setCursor(LABEL_COLUMN_START_X, Y_SECTION_EXT_TITLE); // Capteur Exterieur Section
lcd_Infos.println("Capteur Exterieur:"); // Pas d'accents
lcd_Infos.setTextSize(TEXT_SIZE_LABELS);
lcd_Infos.setTextColor(LABEL_COLOR);
lcd_Infos.setCursor(LABEL_COLUMN_START_X, Y_TEMP_EXT_ROW); // Temperature Exterieure Label
lcd_Infos.print("Temperature:"); // Pas d'accents
lcd_Infos.setCursor(LABEL_COLUMN_START_X, Y_HUM_EXT_ROW); // Humidite Exterieure Label
lcd_Infos.print("Humidite:"); // Pas d'accents
temperaturePageInitialized = true; // Flag set, static elements drawn
}
// --- Dynamic update of values ---
lcd_Infos.setTextSize(TEXT_SIZE_VALUES);
lcd_Infos.setTextColor(VALUE_COLOR);
// Interior Sensor Values (utilisent maintenant les constantes Y_..._ROW)
char tempStr[10]; // Buffer pour la température
float displayTempInt = tempInt;
if (isnan(tempInt) || tempInt < -50 || tempInt > 100) { // Plage réaliste pour un DHT22
displayTempInt = NAN; // Forcer NaN pour l'affichage "-- C"
}
if (abs(displayTempInt - prevTempInt) > 0.1 || isnan(displayTempInt) != isnan(prevTempInt)) {
// Effacer la zone de l'ancienne valeur et de l'unité
lcd_Infos.fillRect(UNIT_ALIGN_X - VALUE_UNIT_RECT_WIDTH + (2 * CHAR_WIDTH_SIZE2), Y_TEMP_INT_ROW, VALUE_UNIT_RECT_WIDTH, LINE_HEIGHT, BACKGROUND_CLR);
if (isnan(displayTempInt)) {
lcd_Infos.setCursor(UNIT_ALIGN_X - (4 * CHAR_WIDTH_SIZE2), Y_TEMP_INT_ROW); // Position pour "-- C"
lcd_Infos.print("-- C");
} else {
dtostrf(displayTempInt, 0, 1, tempStr); // width=0 pour pas de padding
int numWidthPixels = strlen(tempStr) * CHAR_WIDTH_SIZE2; // Largeur du nombre en pixels
lcd_Infos.setCursor(UNIT_ALIGN_X - numWidthPixels - CHAR_WIDTH_SIZE2, Y_TEMP_INT_ROW); // Reculer pour aligner l'unité
lcd_Infos.print(tempStr);
lcd_Infos.print(" C"); // Ajouter l'unité (sera à UNIT_ALIGN_X - CHAR_WIDTH_SIZE2)
}
prevTempInt = displayTempInt;
}
char humStr[10]; // Buffer pour l'humidité
float displayHumInt = humInt;
if (isnan(humInt) || humInt < 0 || humInt > 100) { // Plage réaliste pour un DHT22
displayHumInt = NAN; // Forcer NaN pour l'affichage "-- %"
}
if (abs(displayHumInt - prevHumInt) > 0.1 || isnan(displayHumInt) != isnan(prevHumInt)) {
// Effacer la zone de l'ancienne valeur et de l'unité
lcd_Infos.fillRect(UNIT_ALIGN_X - VALUE_UNIT_RECT_WIDTH + (2 * CHAR_WIDTH_SIZE2), Y_HUM_INT_ROW, VALUE_UNIT_RECT_WIDTH, LINE_HEIGHT, BACKGROUND_CLR);
if (isnan(displayHumInt)) {
lcd_Infos.setCursor(UNIT_ALIGN_X - (4 * CHAR_WIDTH_SIZE2), Y_HUM_INT_ROW); // Position pour "-- %"
lcd_Infos.print("-- %");
} else {
dtostrf(displayHumInt, 0, 1, humStr); // width=0 pour pas de padding
int numWidthPixels = strlen(humStr) * CHAR_WIDTH_SIZE2; // Largeur du nombre en pixels
lcd_Infos.setCursor(UNIT_ALIGN_X - numWidthPixels - CHAR_WIDTH_SIZE2, Y_HUM_INT_ROW); // Reculer pour aligner l'unité
lcd_Infos.print(humStr);
lcd_Infos.print(" %"); // Ajouter l'unité
}
prevHumInt = displayHumInt;
}
// Exterior Sensor Values (utilisent maintenant les constantes Y_..._ROW)
float displayTempExt = tempExt;
if (isnan(tempExt) || tempExt < -50 || tempExt > 100) { // Plage réaliste
displayTempExt = NAN;
}
if (abs(displayTempExt - prevTempExt) > 0.1 || isnan(displayTempExt) != isnan(prevTempExt)) {
// Effacer la zone de l'ancienne valeur et de l'unité
lcd_Infos.fillRect(UNIT_ALIGN_X - VALUE_UNIT_RECT_WIDTH + (2 * CHAR_WIDTH_SIZE2), Y_TEMP_EXT_ROW, VALUE_UNIT_RECT_WIDTH, LINE_HEIGHT, BACKGROUND_CLR);
if (isnan(displayTempExt)) {
lcd_Infos.setCursor(UNIT_ALIGN_X - (4 * CHAR_WIDTH_SIZE2), Y_TEMP_EXT_ROW); // Position pour "-- C"
lcd_Infos.print("-- C");
} else {
dtostrf(displayTempExt, 0, 1, tempStr); // width=0 pour pas de padding
int numWidthPixels = strlen(tempStr) * CHAR_WIDTH_SIZE2; // Largeur du nombre en pixels
lcd_Infos.setCursor(UNIT_ALIGN_X - numWidthPixels - CHAR_WIDTH_SIZE2, Y_TEMP_EXT_ROW); // Reculer pour aligner l'unité
lcd_Infos.print(tempStr);
lcd_Infos.print(" C"); // Ajouter l'unité
}
prevTempExt = displayTempExt;
}
float displayHumExt = humExt;
if (isnan(humExt) || humExt < 0 || humExt > 100) { // Plage réaliste
displayHumExt = NAN;
}
if (abs(displayHumExt - prevHumExt) > 0.1 || isnan(displayHumExt) != isnan(prevHumExt)) {
// Effacer la zone de l'ancienne valeur et de l'unité
lcd_Infos.fillRect(UNIT_ALIGN_X - VALUE_UNIT_RECT_WIDTH + (2 * CHAR_WIDTH_SIZE2), Y_HUM_EXT_ROW, VALUE_UNIT_RECT_WIDTH, LINE_HEIGHT, BACKGROUND_CLR);
if (isnan(displayHumExt)) {
lcd_Infos.setCursor(UNIT_ALIGN_X - (4 * CHAR_WIDTH_SIZE2), Y_HUM_EXT_ROW); // Position pour "-- %"
lcd_Infos.print("-- %");
} else {
dtostrf(displayHumExt, 0, 1, humStr); // width=0 pour pas de padding
int numWidthPixels = strlen(humStr) * CHAR_WIDTH_SIZE2; // Largeur du nombre en pixels
lcd_Infos.setCursor(UNIT_ALIGN_X - numWidthPixels - CHAR_WIDTH_SIZE2, Y_HUM_EXT_ROW); // Reculer pour aligner l'unité
lcd_Infos.print(humStr);
lcd_Infos.print(" %"); // Ajouter l'unité
}
prevHumExt = displayHumExt;
}
}
// Fonction utilitaire pour dessiner une option du sous-menu de réglages
void drawSettingsMenuOption(int optionIndex, bool isSelected) {
const char* optionTexts[] = {"1. Regler l'heure", "2. Regler la date"};
int currentY = SETTINGS_SUBMENU_START_Y + (SETTINGS_SUBMENU_OPTION_HEIGHT + SETTINGS_SUBMENU_SPACING) * optionIndex;
int textY_offset = (SETTINGS_SUBMENU_OPTION_HEIGHT - SETTINGS_SUBMENU_TEXT_HEIGHT) / 2; // Centrer le texte verticalement
lcd_Menu.setCursor(20, currentY + textY_offset);
lcd_Menu.setTextSize(2);
// Effacer uniquement la zone du texte pour éviter le clignotement excessif
lcd_Menu.fillRect(20, currentY + textY_offset, lcd_Menu.width() - 40, SETTINGS_SUBMENU_TEXT_HEIGHT, BACKGROUND_COLOR_REG_MENU);
if (isSelected) {
lcd_Menu.setTextColor(ILI9341_CYAN); // Couleur sélectionnée
} else {
lcd_Menu.setTextColor(ILI9341_YELLOW); // Couleur non sélectionnée
}
lcd_Menu.println(optionTexts[optionIndex]);
}
// Fonction pour afficher le SOUS-MENU des réglages sur lcd_Menu (l'écran tactile)
void afficherSousMenuReglages_Menu() {
if (!reglagesSousMenuInitialized) { // Dessiner le fond et le titre une seule fois
lcd_Menu.fillScreen(BACKGROUND_COLOR_REG_MENU); // Fond du sous-menu de réglages
String title = "Sous-menu Reglages";
lcd_Menu.setTextSize(2);
int16_t x1, y1;
uint16_t w_title, h_title;
lcd_Menu.getTextBounds(title, 0, 0, &x1, &y1, &w_title, &h_title);
int cursorX_title = (lcd_Menu.width() - w_title) / 2;
int cursorY_title = 10;
lcd_Menu.setCursor(cursorX_title, cursorY_title);
lcd_Menu.setTextColor(ILI9341_WHITE);
lcd_Menu.println(title);
lcd_Menu.drawLine(cursorX_title, cursorY_title + h_title + 2, cursorX_title + w_title, cursorY_title + h_title + 2, ILI9341_WHITE);
reglagesSousMenuInitialized = true;
// Dessiner toutes les options une première fois (non sélectionnées au départ)
drawSettingsMenuOption(0, false);
drawSettingsMenuOption(1, false);
}
}
// NOUVELLE FONCTION : afficherSousMenuOutils_Menu()
void afficherSousMenuOutils_Menu() {
if (!outilsSousMenuInitialized) {
lcd_Menu.fillScreen(BACKGROUND_COLOR_TOOLS_MENU);
String title = "Sous-menu Outils";
lcd_Menu.setTextSize(2);
int16_t x1, y1;
uint16_t w_title, h_title;
lcd_Menu.getTextBounds(title, 0, 0, &x1, &y1, &w_title, &h_title);
int cursorX_title = (lcd_Menu.width() - w_title) / 2;
int cursorY_title = 10;
lcd_Menu.setCursor(cursorX_title, cursorY_title);
lcd_Menu.setTextColor(ILI9341_WHITE);
lcd_Menu.println(title);
lcd_Menu.drawLine(cursorX_title, cursorY_title + h_title + 2, cursorX_title + w_title, cursorY_title + h_title + 2, ILI9341_WHITE);
// Options des outils
lcd_Menu.setTextSize(2);
lcd_Menu.setTextColor(ILI9341_YELLOW);
lcd_Menu.setCursor(20, TOOLS_SUBMENU_START_Y);
lcd_Menu.println("1. Horloge analogique");
lcd_Menu.setCursor(20, TOOLS_SUBMENU_START_Y + (TOOLS_SUBMENU_OPTION_HEIGHT + TOOLS_SUBMENU_SPACING) * 1);
lcd_Menu.println("2. Calendrier");
lcd_Menu.setCursor(20, TOOLS_SUBMENU_START_Y + (TOOLS_SUBMENU_OPTION_HEIGHT + TOOLS_SUBMENU_SPACING) * 2);
lcd_Menu.println("3. Horloge numerique"); // Nouvelle option Calendrier ici
// Autres outils ici...
outilsSousMenuInitialized = true;
}
}
// --- NOUVELLES FONCTIONS D'AFFICHAGE ET DE MISE À JOUR POUR LE RÉGLAGE HEURE/DATE ---
// Fonction pour dessiner un champ numérique unique (heure, minute, seconde, année, mois, jour)
void drawNumericField(Adafruit_ILI9341 &lcd, int fieldType, int value, int x, int y, int textSize, uint16_t normalColor, uint16_t highlightColor, uint16_t bgColor) {
char valStr[5]; // Suffisamment grand pour l'année (4 chiffres + null)
int widthMultiplier; // Multiplicateur pour la largeur du champ (ex: 2 pour HH, MM, SS, DD, MM; 4 pour année)
// Déterminer la largeur du champ en fonction du type de champ
if (fieldType == FIELD_YEAR) {
sprintf(valStr, "%d", value);
widthMultiplier = 4; // 4 caractères pour l'année
} else { // Jour, Mois, Heure, Minute, Seconde (tous sont sur 2 chiffres)
sprintf(valStr, "%02d", value);
widthMultiplier = 2; // 2 caractères pour les autres champs
}
int charWidth = 6 * textSize;
int charHeight = 8 * textSize;
// Effacer l'ancienne valeur
lcd.fillRect(x, y, charWidth * widthMultiplier, charHeight, bgColor);
// Dessiner la nouvelle valeur
lcd.setCursor(x, y);
lcd.setTextColor((selectedField == fieldType) ? highlightColor : normalColor);
lcd.print(valStr);
}
// Fonction pour afficher l'interface de réglage de l'heure sur lcd_Infos
void afficherReglerHeure_Infos() {
// Déclarer les constantes locales pour les calculs de positionnement
const int CHAR_WIDTH_SIZE4 = 6 * 4;
const int CHAR_HEIGHT_SIZE4 = 8 * 4;
const int COLON_WIDTH_SIZE4 = 6 * 4;
int hourY = 60;
int hourX_pos_overall = (lcd_Infos.width() / 2) - (6 * CHAR_WIDTH_SIZE4 + 2 * COLON_WIDTH_SIZE4) / 2;
int firstColonX = hourX_pos_overall + (2 * CHAR_WIDTH_SIZE4);
int secondColonX = firstColonX + COLON_WIDTH_SIZE4 + (2 * CHAR_WIDTH_SIZE4);
int hourX_pos = (lcd_Infos.width() / 2) - (6 * CHAR_WIDTH_SIZE4 + 2 * COLON_WIDTH_SIZE4) / 2;
int minuteX_pos = hourX_pos + (2 * CHAR_WIDTH_SIZE4) + COLON_WIDTH_SIZE4;
int secondX_pos = minuteX_pos + (2 * CHAR_WIDTH_SIZE4) + COLON_WIDTH_SIZE4;
int hourY_pos = 60;
if (!reglerHeureInitialized) { // Dessiner les éléments statiques une seule fois
lcd_Infos.fillScreen(ILI9341_BLACK);
lcd_Infos.setTextColor(ILI9341_WHITE);
lcd_Infos.setTextSize(2);
lcd_Infos.setCursor(10, 10);
lcd_Infos.println("Regler l'heure:");
// Dessiner le bouton "Select pour confirmer" (texte ajusté)
char confirmText[] = "Select pour confirmer";
lcd_Infos.setTextSize(2);
int confirmTextWidth = strlen(confirmText) * 6 * 2; // 6px/char * size 2
int confirmTextX = (lcd_Infos.width() - confirmTextWidth) / 2; // Centrer le texte
int confirmTextY = 188; // Y position of the text
lcd_Infos.setCursor(confirmTextX, confirmTextY);
lcd_Infos.setTextColor(ILI9341_GREEN); // Couleur du texte de confirmation
lcd_Infos.print(confirmText);
// Dessiner les séparateurs de l'heure fixes (comme dans afficherHeureDateDetail_Infos)
lcd_Infos.setTextSize(4);
lcd_Infos.setTextColor(ILI9341_YELLOW); // Couleur pour les ":"
lcd_Infos.setCursor(firstColonX, hourY);
lcd_Infos.print(":");
lcd_Infos.setCursor(secondColonX, hourY);
lcd_Infos.print(":");
reglerHeureInitialized = true;
prevSelectedField = -1; // Réinitialiser pour forcer le dessin de tous les champs la première fois
}
now = rtc.now(); // Assurez-vous que 'now' est à jour pour l'initialisation des tempAdj
// Initialisation des tempAdj la première fois que la page est ouverte
if(prevSelectedField == -1) { // Cela signifie que c'est la première entrée dans la page de réglage
tempAdjHour = now.hour();
tempAdjMinute = now.minute();
tempAdjSecond = now.second();
selectedField = FIELD_HOUR; // Sélectionne l'heure par défaut
}
// Appel initial des valeurs numériques
drawReglerHeureFields();
}
// Fonction pour afficher l'interface de réglage de la date sur lcd_Infos
void afficherReglerDate_Infos() {
// Largeurs pour setTextSize(3)
const int CHAR_WIDTH_SIZE3 = 6 * 3;
const int CHAR_HEIGHT_SIZE3 = 8 * 3;
const int SLASH_WIDTH_SIZE3 = 6 * 3; // Largeur pour un "/"
// Positions de base pour la ligne DD/MM/YYYY
int dateY = 60;
// Calcul de la largeur totale de "DD/MM/YYYY" (2+1+2+1+4 = 10 caractères * 18 pixels/char = 180 pixels)
int totalDateWidth = (2 * CHAR_WIDTH_SIZE3) + SLASH_WIDTH_SIZE3 + (2 * CHAR_WIDTH_SIZE3) + SLASH_WIDTH_SIZE3 + (4 * CHAR_WIDTH_SIZE3);
int dateLineStartX = (lcd_Infos.width() - totalDateWidth) / 2; // Centrer la ligne
// Positions spécifiques pour chaque champ et séparateur (DD/MM/YYYY)
int dayX = dateLineStartX;
int slash1X = dayX + (2 * CHAR_WIDTH_SIZE3);
int monthX = slash1X + SLASH_WIDTH_SIZE3;
int slash2X = monthX + (2 * CHAR_WIDTH_SIZE3);
int yearX = slash2X + SLASH_WIDTH_SIZE3;
if (!reglerDateInitialized) { // NOUVEAU: Dessiner les éléments statiques une seule fois
lcd_Infos.fillScreen(ILI9341_BLACK);
lcd_Infos.setTextColor(ILI9341_WHITE);
lcd_Infos.setTextSize(2);
lcd_Infos.setCursor(10, 10);
lcd_Infos.println("Regler la date:");
// Dessiner le bouton "Select pour confirmer" (texte ajusté)
char confirmText[] = "Select pour confirmer";
lcd_Infos.setTextSize(2);
int confirmTextWidth = strlen(confirmText) * 6 * 2; // 6px/char * size 2
int confirmTextX = (lcd_Infos.width() - confirmTextWidth) / 2; // Centrer le texte
int confirmTextY = 188; // Y position of the text
lcd_Infos.setCursor(confirmTextX, confirmTextY);
lcd_Infos.setTextColor(ILI9341_GREEN); // Couleur du texte de confirmation
lcd_Infos.print(confirmText);
// Dessiner les séparateurs '/' fixes
lcd_Infos.setTextSize(3); // Taille pour les séparateurs
lcd_Infos.setTextColor(ILI9341_WHITE); // Couleur des séparateurs
lcd_Infos.setCursor(slash1X, dateY);
lcd_Infos.print("/");
lcd_Infos.setCursor(slash2X, dateY);
lcd_Infos.print("/");
reglerDateInitialized = true;
prevSelectedField = -1; // Réinitialiser pour force le dessin de tous les champs la première fois
}
now = rtc.now(); // Assurez-vous que 'now' est à jour pour l'initialisation des tempAdj
// Initialisation des tempAdj la première fois que la page est ouverte
if(prevSelectedField == -1) { // Cela signifie que c'est la première entrée dans la page de réglage
tempAdjYear = now.year();
tempAdjMonth = now.month();
tempAdjDay = now.day();
selectedField = FIELD_DAY; // Sélectionne le jour par défaut
}
// Appel initial des valeurs numériques
drawReglerDateFields();
}
// === Fonctions de dessin des champs dynamiques (précédemment updateRegler...Display) ===
void drawReglerHeureFields() { // Renommée pour la clarté
const int CHAR_WIDTH_SIZE4 = 6 * 4;
const int CHAR_HEIGHT_SIZE4 = 8 * 4;
const int COLON_WIDTH_SIZE4 = 6 * 4;
int hourX_pos = (lcd_Infos.width() / 2) - (6 * CHAR_WIDTH_SIZE4 + 2 * COLON_WIDTH_SIZE4) / 2;
int minuteX_pos = hourX_pos + (2 * CHAR_WIDTH_SIZE4) + COLON_WIDTH_SIZE4;
int secondX_pos = minuteX_pos + (2 * CHAR_WIDTH_SIZE4) + COLON_WIDTH_SIZE4;
int hourY_pos = 60;
// Redessiner uniquement le champ précédent si la sélection a changé
if (prevSelectedField != -1 && prevSelectedField != selectedField) {
// Redessiner l'ancien champ sélectionné avec sa couleur normale
if (prevSelectedField == FIELD_HOUR) drawNumericField(lcd_Infos, FIELD_HOUR, tempAdjHour, hourX_pos, hourY_pos, 4, ILI9341_YELLOW, ILI9341_RED, ILI9341_BLACK);
else if (prevSelectedField == FIELD_MINUTE) drawNumericField(lcd_Infos, FIELD_MINUTE, tempAdjMinute, minuteX_pos, hourY_pos, 4, ILI9341_YELLOW, ILI9341_RED, ILI9341_BLACK);
else if (prevSelectedField == FIELD_SECOND) drawNumericField(lcd_Infos, FIELD_SECOND, tempAdjSecond, secondX_pos, hourY_pos, 4, ILI9341_YELLOW, ILI9341_RED, ILI9341_BLACK);
}
// Redessiner le champ actuel
// Même si le selectedField n'a pas changé, sa valeur a pu changer
drawNumericField(lcd_Infos, FIELD_HOUR, tempAdjHour, hourX_pos, hourY_pos, 4, ILI9341_YELLOW, ILI9341_RED, ILI9341_BLACK);
drawNumericField(lcd_Infos, FIELD_MINUTE, tempAdjMinute, minuteX_pos, hourY_pos, 4, ILI9341_YELLOW, ILI9341_RED, ILI9341_BLACK);
drawNumericField(lcd_Infos, FIELD_SECOND, tempAdjSecond, secondX_pos, hourY_pos, 4, ILI9341_YELLOW, ILI9341_RED, ILI9341_BLACK);
// Mettre à jour prevSelectedField (déplacé ici pour être juste après le dessin)
prevSelectedField = selectedField;
}
void drawReglerDateFields() { // Renommée pour la clarté
// Largeurs pour setTextSize(3)
const int CHAR_WIDTH_SIZE3 = 6 * 3;
const int CHAR_HEIGHT_SIZE3 = 8 * 3;
const int SLASH_WIDTH_SIZE3 = 6 * 3; // Largeur pour un "/"
// Positions de base pour la ligne DD/MM/YYYY
int dateY = 60;
// Calcul de la largeur totale de "DD/MM/YYYY" (2+1+2+1+4 = 10 caractères * 18 pixels/char = 180 pixels)
int totalDateWidth = (2 * CHAR_WIDTH_SIZE3) + SLASH_WIDTH_SIZE3 + (2 * CHAR_WIDTH_SIZE3) + SLASH_WIDTH_SIZE3 + (4 * CHAR_WIDTH_SIZE3);
int dateLineStartX = (lcd_Infos.width() - totalDateWidth) / 2; // Centrer la ligne
// Positions spécifiques pour chaque champ et séparateur (DD/MM/YYYY)
int dayX = dateLineStartX;
int slash1X = dayX + (2 * CHAR_WIDTH_SIZE3);
int monthX = slash1X + SLASH_WIDTH_SIZE3;
int slash2X = monthX + (2 * CHAR_WIDTH_SIZE3);
int yearX = slash2X + SLASH_WIDTH_SIZE3;
// Redessiner uniquement le champ précédent si la sélection a changé
if (prevSelectedField != -1 && prevSelectedField != selectedField) {
// Redessiner l'ancien champ sélectionné avec sa couleur normale
if (prevSelectedField == FIELD_DAY) drawNumericField(lcd_Infos, FIELD_DAY, tempAdjDay, dayX, dateY, 3, ILI9341_WHITE, ILI9341_RED, ILI9341_BLACK); // Jour
else if (prevSelectedField == FIELD_MONTH) drawNumericField(lcd_Infos, FIELD_MONTH, tempAdjMonth, monthX, dateY, 3, ILI9341_WHITE, ILI9341_RED, ILI9341_BLACK); // Mois
else if (prevSelectedField == FIELD_YEAR) drawNumericField(lcd_Infos, FIELD_YEAR, tempAdjYear, yearX, dateY, 3, ILI9341_WHITE, ILI9341_RED, ILI9341_BLACK); // Année
}
// Redessiner le champ actuel
drawNumericField(lcd_Infos, FIELD_DAY, tempAdjDay, dayX, dateY, 3, ILI9341_WHITE, ILI9341_RED, ILI9341_BLACK); // DD
drawNumericField(lcd_Infos, FIELD_MONTH, tempAdjMonth, monthX, dateY, 3, ILI9341_WHITE, ILI9341_RED, ILI9341_BLACK); // MM
drawNumericField(lcd_Infos, FIELD_YEAR, tempAdjYear, yearX, dateY, 3, ILI9341_WHITE, ILI9341_RED, ILI9341_BLACK); //
// Mettre à jour prevSelectedField
prevSelectedField = selectedField;
}
// Reading joystick input
int readJoystickX() {
int x = analogRead(JOY_X);
if (x < JOY_THRESHOLD_LOW) return -1; // Physical Left
if (x > JOY_THRESHOLD_HIGH) return 1; // Physical Right
return 0; // Center
}
int readJoystickY() {
int y = analogRead(JOY_Y);
if (y < JOY_THRESHOLD_LOW) return 1; // Physical Up
if (y > JOY_THRESHOLD_HIGH) return -1; // Physical Down
return 0; // Center
}
bool readJoystickClick() {
return (digitalRead(JOY_SEL) == LOW); // LOW when button is pressed
}
// Calibration function (unchanged)
void calibrateTouchScreen() {
lcd_Menu.fillScreen(ILI9341_BLACK);
lcd_Menu.setCursor(10, 10);
lcd_Menu.setTextColor(ILI9341_WHITE);
lcd_Menu.setTextSize(2);
lcd_Menu.println("Test de Calibration Tactile");
lcd_Menu.println(" ");
lcd_Menu.println("Touchez l'ecran.");
lcd_Menu.println(" ");
lcd_Menu.print("X: ");
lcd_Menu.print("Y: ");
while (true) {
if (ts.touched()) {
TS_Point p = ts.getPoint();
int touchX = p.y;
int touchY = 240 - p.x;
lcd_Menu.fillRect(40, 70, 100, 20, ILI9341_BLACK);
lcd_Menu.setCursor(40, 70);
lcd_Menu.print(touchX);
lcd_Menu.fillRect(40, 90, 100, 20, ILI9341_BLACK);
lcd_Menu.setCursor(40, 90);
lcd_Menu.print(touchY);
}
delay(50);
}
}
// === Initialisation ===
void setup() {
pinMode(JOY_SEL, INPUT_PULLUP); // Activer la résistance de pull-up pour le bouton
lcd_Menu.begin();
lcd_Infos.begin();
lcd_Menu.setRotation(1);
lcd_Infos.setRotation(1);
lcd_Menu.fillScreen(ILI9341_BLACK);
lcd_Infos.fillScreen(ILI9341_BLACK);
if (!rtc.begin()) {
while (1);
}
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Optionnel: pour régler l'heure du RTC
if (!ts.begin()) {
while (1);
}
dhtInt.begin();
dhtExt.begin();
// calibrateTouchScreen(); // Décommenter pour calibration
afficherMenu();
clearAndDisplayDefaultInfos();
}
// NOUVELLE FONCTION: afficherCalendrierDetail_Infos()
void afficherCalendrierDetail_Infos() {
// Constantes pour la disposition du calendrier
const int CAL_TITLE_Y = 10; // Y pour le titre "Calendrier"
const int CAL_MONTH_YEAR_Y = CAL_TITLE_Y + 30; // Y pour le mois et l'année, ajusté
const int CAL_DAY_NAMES_Y = CAL_MONTH_YEAR_Y + 25; // Y pour "Dim, Lun, etc.", ajusté
const int CAL_DAYS_GRID_START_Y = CAL_DAY_NAMES_Y + (8 * 2) + 5; // Y de départ de la grille des jours
const int CAL_CELL_WIDTH = lcd_Infos.width() / 7;
// Ajustement de la hauteur des cellules pour laisser de la place en bas si nécessaire
// (240 - CAL_DAYS_GRID_START_Y - marge_bas) / 6
const int CAL_CELL_HEIGHT = (lcd_Infos.height() - CAL_DAYS_GRID_START_Y - 20) / 6;
const int TEXT_SIZE_CAL_DAY_NAME = 2;
const int TEXT_SIZE_CAL_DAY_NUM = 2;
// Tableau des noms des mois (déclaré ici pour être accessible)
const char* months[] = {"", "Janvier", "Fevrier", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Decembre"};
// Variables statiques pour l'optimisation du rafraîchissement
static int prevCalMonth = -1;
static int prevCalYear = -1;
static int prevCalDay = -1; // Pour surligner le jour actuel
DateTime currentRTC = rtc.now(); // Obtenir la date et l'heure actuelles du RTC
// Redessiner le calendrier complet si le mois ou l'année change, ou si c'est la première initialisation
if (!calendrierPageInitialized || currentRTC.month() != prevCalMonth || currentRTC.year() != prevCalYear) {
lcd_Infos.fillScreen(ILI9341_DARKCYAN); // <<< CHANGEMENT ICI : Nouvelle couleur de fond pour le calendrier
// --- Titre principal du calendrier ---
String title = "Calendrier";
lcd_Infos.setTextSize(3);
int16_t x1, y1; uint16_t w_title, h_title;
lcd_Infos.getTextBounds(title, 0, 0, &x1, &y1, &w_title, &h_title);
int cursorX_title = (lcd_Infos.width() - w_title) / 2;
lcd_Infos.setCursor(cursorX_title, CAL_TITLE_Y);
lcd_Infos.setTextColor(ILI9341_WHITE);
lcd_Infos.println(title);
lcd_Infos.drawLine(cursorX_title, CAL_TITLE_Y + h_title + 2, cursorX_title + w_title, CAL_TITLE_Y + h_title + 2, ILI9341_WHITE);
// --- Affichage du mois et de l'année ---
lcd_Infos.setTextSize(2);
// Calcul de la largeur du texte du mois+année pour le centrer
String monthYearStr = String(months[currentRTC.month()]) + " " + String(currentRTC.year());
int monthYearStrWidth = monthYearStr.length() * 6 * 2; // 6px/char * size 2
lcd_Infos.setCursor((lcd_Infos.width() - monthYearStrWidth) / 2, CAL_MONTH_YEAR_Y);
lcd_Infos.setTextColor(ILI9341_YELLOW);
lcd_Infos.print(months[currentRTC.month()]); // Utilisation correcte du tableau months
lcd_Infos.print(" ");
lcd_Infos.println(currentRTC.year());
// --- Noms des jours de la semaine ---
lcd_Infos.setTextSize(TEXT_SIZE_CAL_DAY_NAME);
lcd_Infos.setTextColor(ILI9341_LIGHTGREY);
const char* dayNames[] = {"Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"}; // RTC dayOfTheWeek() est 0=Sunday
for (int i = 0; i < 7; i++) {
// Centrer les noms des jours dans chaque cellule
int dayNameTextWidth = strlen(dayNames[i]) * 6 * TEXT_SIZE_CAL_DAY_NAME;
int dayNameX = (i * CAL_CELL_WIDTH) + (CAL_CELL_WIDTH - dayNameTextWidth) / 2;
lcd_Infos.setCursor(dayNameX, CAL_DAY_NAMES_Y);
lcd_Infos.print(dayNames[i]);
}
// --- Grille des jours du mois ---
lcd_Infos.setTextSize(TEXT_SIZE_CAL_DAY_NUM);
int daysInMonthArr[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// Vérifier si l'année est bissextile pour Février (correction de isLeapYear())
if ((currentRTC.year() % 4 == 0 && currentRTC.year() % 100 != 0) || (currentRTC.year() % 400 == 0)) {
daysInMonthArr[2] = 29;
}
DateTime firstDayOfMonth(currentRTC.year(), currentRTC.month(), 1, 0, 0, 0);
int startingDayOfWeek = firstDayOfMonth.dayOfTheWeek(); // Jour de la semaine du 1er du mois (0=Dimanche, 1=Lundi...)
int dayCounter = 1;
for (int row = 0; row < 6; row++) {
for (int col = 0; col < 7; col++) {
int cellX = col * CAL_CELL_WIDTH;
int cellY = CAL_DAYS_GRID_START_Y + row * CAL_CELL_HEIGHT;
if (row == 0 && col < startingDayOfWeek) {
// Cellules vides avant le 1er jour du mois
lcd_Infos.fillRect(cellX, cellY, CAL_CELL_WIDTH, CAL_CELL_HEIGHT, ILI9341_DARKCYAN); // S'assurer que c'est vide
continue;
}
if (dayCounter > daysInMonthArr[currentRTC.month()]) {
// Cellules vides après le dernier jour du mois
lcd_Infos.fillRect(cellX, cellY, CAL_CELL_WIDTH, CAL_CELL_HEIGHT, ILI9341_DARKCYAN); // S'assurer que c'est vide
continue;
}
// Centrer le numéro du jour dans la cellule
char dayStr[3];
sprintf(dayStr, "%d", dayCounter);
int dayNumTextWidth = strlen(dayStr) * 6 * TEXT_SIZE_CAL_DAY_NUM;
int dayX_pos = cellX + (CAL_CELL_WIDTH - dayNumTextWidth) / 2;
int dayY_pos = cellY + (CAL_CELL_HEIGHT - (8 * TEXT_SIZE_CAL_DAY_NUM)) / 2;
// Mettre en surbrillance le jour actuel
// MODIFICATION ICI: Surligner le jour actuel si c'est la première initialisation OU si c'est le jour actuel du RTC.
if (dayCounter == currentRTC.day() ) { // Simplifié pour toujours surligner le jour actuel
lcd_Infos.fillRoundRect(cellX + 2, cellY + 2, CAL_CELL_WIDTH - 4, CAL_CELL_HEIGHT - 4, 3, ILI9341_BLUE); // Highlight
lcd_Infos.setTextColor(ILI9341_WHITE); // Texte blanc sur fond bleu
} else {
// Lors d'un rafraîchissement complet, s'assurer que les jours non actuels ne sont pas surlignés
lcd_Infos.fillRect(cellX, cellY, CAL_CELL_WIDTH, CAL_CELL_HEIGHT, ILI9341_DARKCYAN);
lcd_Infos.setTextColor(ILI9341_WHITE); // Couleur par default pour les autres jours
}
lcd_Infos.setCursor(dayX_pos, dayY_pos);
lcd_Infos.print(dayStr);
dayCounter++;
}
}
calendrierPageInitialized = true;
prevCalMonth = currentRTC.month();
prevCalYear = currentRTC.year();
prevCalDay = currentRTC.day(); // Mémoriser le jour actuel pour le prochain cycle
} else {
// Si le mois et l'année n'ont pas changé, mettre à jour uniquement le surlignage du jour
if (currentRTC.day() != prevCalDay) {
// Obtenir le jour de la semaine du 1er du mois (important pour le calcul des cellules)
DateTime firstDayOfMonth(currentRTC.year(), currentRTC.month(), 1, 0, 0, 0);
int startingDayOfWeek = firstDayOfMonth.dayOfTheWeek();
// --- Effacer la surbrillance du jour précédent (prevCalDay) ---
int prevDayCellIndex = prevCalDay - 1 + startingDayOfWeek; // Index de cellule (0-based)
int prevDayCol = prevDayCellIndex % 7;
int prevDayRow = prevDayCellIndex / 7;
int prevCellX = prevDayCol * CAL_CELL_WIDTH;
int prevCellY = CAL_DAYS_GRID_START_Y + prevDayRow * CAL_CELL_HEIGHT;
lcd_Infos.fillRect(prevCellX + 2, prevCellY + 2, CAL_CELL_WIDTH - 4, CAL_CELL_HEIGHT - 4, ILI9341_DARKCYAN); // Effacer highlight
lcd_Infos.setTextColor(ILI9341_WHITE); // Restaurer la couleur normale du texte
char prevDayStr[3];
sprintf(prevDayStr, "%d", prevCalDay);
int prevDayNumTextWidth = strlen(prevDayStr) * 6 * TEXT_SIZE_CAL_DAY_NUM;
int prevDayX_pos = prevCellX + (CAL_CELL_WIDTH - prevDayNumTextWidth) / 2;
int prevDayY_pos = prevCellY + (CAL_CELL_HEIGHT - (8 * TEXT_SIZE_CAL_DAY_NUM)) / 2;
lcd_Infos.setCursor(prevDayX_pos, prevDayY_pos);
lcd_Infos.print(prevDayStr); // Redessiner le jour précédent normalement
// --- Dessiner la surbrillance du nouveau jour actuel (currentRTC.day()) ---
int currentDayCellIndex = currentRTC.day() - 1 + startingDayOfWeek;
int currentDayCol = currentDayCellIndex % 7;
int currentDayRow = currentDayCellIndex / 7;
int currentCellX = currentDayCol * CAL_CELL_WIDTH;
int currentCellY = CAL_DAYS_GRID_START_Y + currentDayRow * CAL_CELL_HEIGHT;
lcd_Infos.fillRoundRect(currentCellX + 2, currentCellY + 2, CAL_CELL_WIDTH - 4, CAL_CELL_HEIGHT - 4, 3, ILI9341_BLUE); // Surligner
lcd_Infos.setTextColor(ILI9341_WHITE); // Texte blanc sur fond bleu
char currentDayStr[3];
sprintf(currentDayStr, "%d", currentRTC.day());
int currentDayNumTextWidth = strlen(currentDayStr) * 6 * TEXT_SIZE_CAL_DAY_NUM;
int currentDayX_pos = currentCellX + (CAL_CELL_WIDTH - currentDayNumTextWidth) / 2;
int currentDayY_pos = currentCellY + (CAL_CELL_HEIGHT - (8 * TEXT_SIZE_CAL_DAY_NUM)) / 2;
lcd_Infos.setCursor(currentDayX_pos, currentDayY_pos);
lcd_Infos.print(currentDayStr);
prevCalDay = currentRTC.day(); // Mettre à jour prevCalDay pour le prochain cycle
}
}
}
// NOUVELLE FONCTION: afficherHorlogeAnalogique_Infos()
void afficherHorlogeAnalogique_Infos() {
// Les constantes de positionnement et de taille des aiguilles sont maintenant globales.
if (!horlogeAffichee) { // horlogeAffichee est utilisé pour le dessin initial du cadran
horlogeAffichee = true;
lcd_Infos.fillScreen(ILI9341_DARKCYAN); // La couleur de fond de l'écran entier
// AJOUT IMPORTANT : Dessiner le cercle du cadran en noir
lcd_Infos.fillCircle(CLOCK_CENTER_X, CLOCK_CENTER_Y, CLOCK_RADIUS, FACE_COLOR); // FACE_COLOR est ILI9341_BLACK
// Dessiner les marqueurs et les chiffres des heures
lcd_Infos.setTextSize(2); // Taille du texte (8*2 = 16 pixels de hauteur)
lcd_Infos.setTextColor(MARKER_COLOR);
for (int h = 1; h <= 12; h++) {
float angle = (h * 30 - 90) * PI / 180;
// Position des chiffres des heures
const int NUM_OFFSET_FROM_RADIUS = 25;
int x_num = CLOCK_CENTER_X + (int)((CLOCK_RADIUS - NUM_OFFSET_FROM_RADIUS) * cos(angle));
int y_num = CLOCK_CENTER_Y + (int)((CLOCK_RADIUS - NUM_OFFSET_FROM_RADIUS) * sin(angle));
char hourNumStr[3];
sprintf(hourNumStr, "%d", h);
int textWidth = strlen(hourNumStr) * 6 * 2; // Calcul pour setTextSize(2)
int textHeight = 8 * 2; // Calcul pour setTextSize(2)
// Centrer le texte précisément sur son point calculé
lcd_Infos.setCursor(x_num - textWidth / 2, y_num - textHeight / 2);
lcd_Infos.print(hourNumStr);
}
horlogeAnalogiqueInitialized = true; // Indique que le cadran est dessiné
}
}
// === Boucle principale ===
// FONCTION CORRIGÉE : afficherHorlogeDigitale_Infos()
void afficherHorlogeDigitale_Infos() {
if (!horlogeNumeriqueActive) return;
DateTime now = rtc.now();
int hour = now.hour();
int minute = now.minute();
int second = now.second();
float tempInt = dhtInt.readTemperature();
if (isnan(tempInt)) tempInt = prevTempDigit;
const char* jours[] = {"DIM", "LUN", "MAR", "MER", "JEU", "VEN", "SAM"};
const char* mois[] = {"", "Janvier", "Fevrier", "Mars", "Avril", "Mai", "Juin",
"Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Decembre"};
if (!horlogeDigitaleInitialized) {
lcd_Infos.fillScreen(ILI9341_BLACK);
horlogeDigitaleInitialized = true;
prevHour = prevMinute = prevSecond = -1;
prevTempDigit = -1000;
}
// --- Ligne des jours ---
lcd_Infos.setTextSize(2);
int yJours = 10;
int startX = 10;
for (int i = 0; i < 7; i++) {
lcd_Infos.setCursor(startX + i * 44, yJours);
lcd_Infos.setTextColor((i == now.dayOfTheWeek()) ? ILI9341_RED : ILI9341_WHITE, ILI9341_BLACK);
lcd_Infos.print(jours[i]);
}
// --- Date : "07 Juillet 2025" juste sous les jours ---
lcd_Infos.setTextSize(3);
lcd_Infos.setTextColor(ILI9341_RED, ILI9341_BLACK);
char dateStr[30];
sprintf(dateStr, "%02d %s %d", now.day(), mois[now.month()], now.year());
int pixelWidth = strlen(dateStr) * 6 * 3;
lcd_Infos.setCursor((lcd_Infos.width() - pixelWidth) / 2, 40);
lcd_Infos.print(dateStr);
// --- Heure HH : MM : SS centrée ---
lcd_Infos.setTextSize(6);
lcd_Infos.setTextColor(ILI9341_RED, ILI9341_BLACK);
char timeStr[9];
sprintf(timeStr, "%02d%02d%02d", hour, minute, second);
int totalTimeWidth = (6 * 6 * 6) + (2 * 6 * 3);
int baseX = (lcd_Infos.width() - totalTimeWidth) / 2;
int baseY = 100;
lcd_Infos.setCursor(baseX, baseY);
lcd_Infos.print(timeStr[0]); lcd_Infos.print(timeStr[1]);
lcd_Infos.setTextSize(3);
lcd_Infos.setCursor(baseX + 6 * 6 * 2, baseY + 10);
lcd_Infos.print(":");
lcd_Infos.setTextSize(6);
lcd_Infos.setCursor(baseX + 6 * 6 * 2 + 6 * 3, baseY);
lcd_Infos.print(timeStr[2]); lcd_Infos.print(timeStr[3]);
lcd_Infos.setTextSize(3);
lcd_Infos.setCursor(baseX + 6 * 6 * 4 + 6 * 3, baseY + 10);
lcd_Infos.print(":");
lcd_Infos.setTextSize(6);
lcd_Infos.setCursor(baseX + 6 * 6 * 4 + 6 * 3 + 6 * 3, baseY);
lcd_Infos.print(timeStr[4]); lcd_Infos.print(timeStr[5]);
// --- Température TEMP 28.0°C ---
lcd_Infos.setTextSize(2);
lcd_Infos.setCursor(180, 210);
lcd_Infos.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
lcd_Infos.print("TEMP ");
lcd_Infos.setTextColor(ILI9341_RED, ILI9341_BLACK);
lcd_Infos.print(tempInt, 1);
lcd_Infos.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
lcd_Infos.print((char)247); lcd_Infos.print("C");
prevHour = hour;
prevMinute = minute;
prevSecond = second;
prevTempDigit = tempInt;
}
void loop() {
unsigned long currentMillis = millis();
// Rafraîchissement automatique de l'horloge numérique avec clignotement
if (currentScreen == 5 && currentToolsMenu == 3) {
if (currentMillis - previousMillisDigital >= intervalDigital) {
previousMillisDigital = currentMillis;
blinkDots = !blinkDots;
afficherHorlogeDigitale_Infos();
}
}
// Gestion du rafraîchissement des aiguilles de l'horloge analogique
if (currentMillis - previousMillisAiguilles >= intervalAiguilles) {
previousMillisAiguilles = currentMillis;
if (currentScreen == 5 && currentToolsMenu == 1) { // Si l'écran d'horloge analogique est actif
actualiserAiguillesHorloge(); // Cette fonction gère maintenant l'effacement et le redessin des aiguilles
}
}
now = rtc.now(); // Mise à jour de l'heure RTC
tempInt = dhtInt.readTemperature();
humInt = dhtInt.readHumidity(); // Lecture de l'humidité intérieure
tempExt = dhtExt.readTemperature();
humExt = dhtExt.readHumidity(); // Lecture de l'humidité extérieure
// Variables pour la lecture du joystick
int joyX = readJoystickX();
int joyY = readJoystickY();
bool joyClick = readJoystickClick();
// Gestion du debounce pour le joystick
bool joyMoveHandled = false;
if (millis() - lastJoyMoveTime > joyDebounceDelay) {
if (joyX != 0 || joyY != 0) {
joyMoveHandled = true;
lastJoyMoveTime = millis();
}
}
bool joyClickHandled = false;
if (millis() - lastJoyClickTime > joyClickDebounceDelay) {
if (joyClick) {
joyClickHandled = true;
lastJoyClickTime = millis();
}
}
// Gérer le tactile pour lcd_Menu (menu principal et bouton Retour)
if (ts.touched()) {
TS_Point p = ts.getPoint();
int touchX = p.y;
int touchY = 240 - p.x;
// GESTION OPTION 3 : Horloge numérique
if (currentScreen == 5 && outilsSousMenuInitialized && touchX > 20 && touchX < 300) {
if (touchY > TOOLS_SUBMENU_START_Y + (TOOLS_SUBMENU_OPTION_HEIGHT + TOOLS_SUBMENU_SPACING) * 2 &&
touchY < TOOLS_SUBMENU_START_Y + (TOOLS_SUBMENU_OPTION_HEIGHT + TOOLS_SUBMENU_SPACING) * 2 + TOOLS_SUBMENU_OPTION_HEIGHT) {
currentToolsMenu = 3;
afficherHorlogeDigitale_Infos();
}
}
// --- GESTION DU BOUTON "RETOUR" (prioritaire sur les autres touches) ---
// Coordonnées du bouton "Retour" sur lcd_Menu (10, 200, 80, 30)
if (touchX > 10 && touchX < (10 + 80) && touchY > 200 && touchY < (200 + 30)) {
// Retour depuis les réglages (Regler Heure/Date ou Sous-menu Reglages)
if (currentScreen == 3) {
currentScreen = 0; // Retour au menu principal
horlogeNumeriqueActive = false; afficherMenu();
clearAndDisplayDefaultInfos();
}
// Retour depuis les outils (Horloge Analogique, Calendrier, ou Sous-menu Outils)
else if (currentScreen == 5) {
currentScreen = 0; // Retour au menu principal
horlogeNumeriqueActive = false; afficherMenu();
clearAndDisplayDefaultInfos();
}
// Retour depuis les autres écrans de détail (Heure/Date, Température)
else if (currentScreen == 1 || currentScreen == 2) {
currentScreen = 0; // Retour au menu principal
horlogeNumeriqueActive = false; afficherMenu();
clearAndDisplayDefaultInfos();
}
// Si currentScreen == 4 (Calendrier) et qu'il était directement accessible depuis le menu principal avant le déplacement
// Cette condition est maintenant gérée par le block `currentScreen == 5`
delay(300); // Debounce pour éviter les doubles clics
return; // TRÈS IMPORTANT : Sortir de la fonction pour ne pas traiter d'autres zones tactiles
}
// --- Logique de sélection des options (uniquement si le bouton Retour n'a pas été touché) ---
if (currentScreen == 0) { // Menu principal (lcd_Menu)
if (touchX > 20 && touchX < (20 + MENU_TOUCHABLE_WIDTH)) {
if (touchY > MENU_START_Y && touchY < (MENU_START_Y + MENU_OPTION_HEIGHT)) { // Option "1. Heure/Date"
currentScreen = 1;
heureDateDetailInitialized = false;
prevHour = -1; prevMinute = -1; prevSecond = -1;
prevDay = -1; prevMonth = -1; prevYear = -1;
afficherHeureDateDetail_Infos();
delay(300);
} else if (touchY > (MENU_START_Y + (MENU_OPTION_HEIGHT + MENU_SPACING) * 1) && touchY < (MENU_START_Y + (MENU_OPTION_HEIGHT + MENU_SPACING) * 1 + MENU_OPTION_HEIGHT)) { // Option "2. Temperature"
currentScreen = 2;
temperaturePageInitialized = false; // Force le redessin initial de la page température/humidité
afficherTemperatureDetail_Infos(); // Appel initial
delay(300);
} else if (touchY > (MENU_START_Y + (MENU_OPTION_HEIGHT + MENU_SPACING) * 2) && touchY < (MENU_START_Y + (MENU_OPTION_HEIGHT + MENU_SPACING) * 2 + MENU_OPTION_HEIGHT)) { // Option "3. Reglages
currentScreen = 3;
currentSettingsMenu = 0; // Aller au sous-menu de réglages
reglagesSousMenuInitialized = false; // Force le redessin initial des options de réglages
afficherSousMenuReglages_Menu(); // Afficher le sous-menu de réglages sur lcd_Menu
delay(300);
}
// Ancienne Option 4. Calendrier supprimée de la logique tactile ici
else if (touchY > (MENU_START_Y + (MENU_OPTION_HEIGHT + MENU_SPACING) * 3) && touchY < (MENU_START_Y + (MENU_OPTION_HEIGHT + MENU_SPACING) * 3 + MENU_OPTION_HEIGHT)) { // Option "4. Outils" (anciennement 5)
currentScreen = 5;
currentToolsMenu = 0; // Aller au sous-menu d'outils
outilsSousMenuInitialized = false; // Force le redessin initial des options d'outils
afficherSousMenuOutils_Menu(); // Afficher le sous-menu d'outils sur lcd_Menu
delay(300);
}
}
} else if (currentScreen == 3 && currentSettingsMenu == 0) { // Si nous sommes sur le sous-menu "Réglages"
int touchableWidth_sub = lcd_Menu.width() - 40;
int startY_sub = SETTINGS_SUBMENU_START_Y;
int optionHeight_sub = SETTINGS_SUBMENU_OPTION_HEIGHT;
int spacing_sub = SETTINGS_SUBMENU_SPACING;
if (touchX > 20 && touchX < (20 + touchableWidth_sub)) {
// Option "1. Regler l'heure"
if (touchY > startY_sub && touchY < (startY_sub + optionHeight_sub)) {
currentSettingsMenu = 1;
tempAdjHour = now.hour(); tempAdjMinute = now.minute(); tempAdjSecond = now.second();
selectedField = FIELD_HOUR; // Heure (nouveau mapping)
prevSelectedField = -1; // Force le redessin initial des champs
reglerHeureInitialized = false;
afficherReglerHeure_Infos(); // Initialise l'écran de réglage d'heure sur lcd_Infos
delay(300); // Debounce
}
// Option "2. Regler la date"
else if (touchY > (startY_sub + (optionHeight_sub + spacing_sub) * 1) && touchY < (startY_sub + (optionHeight_sub + spacing_sub) * 1 + optionHeight_sub)) {
currentSettingsMenu = 2;
tempAdjYear = now.year(); tempAdjMonth = now.month(); tempAdjDay = now.day();
selectedField = FIELD_DAY; // Jour (nouveau mapping)
prevSelectedField = -1; // Force le redessin initial des champs
reglerDateInitialized = false;
afficherReglerDate_Infos(); // Initialise l'écran de réglage de date sur lcd_Infos
delay(300); // Debounce
}
}
} else if (currentScreen == 5 && currentToolsMenu == 0) { // Si nous sommes sur le sous-menu "Outils"
int touchableWidth_tools = lcd_Menu.width() - 40;
int startY_tools = TOOLS_SUBMENU_START_Y;
int optionHeight_tools = TOOLS_SUBMENU_OPTION_HEIGHT;
int spacing_tools = TOOLS_SUBMENU_SPACING;
if (touchX > 20 && touchX < (20 + touchableWidth_tools)) {
// Option "1. Horloge analogique"
if (touchY > startY_tools && touchY < (startY_tools + optionHeight_tools)) {
currentToolsMenu = 1;
horlogeAnalogiqueInitialized = false; // Force le redessin initial de l'horloge
horlogeAffichee = false; // Réinitialiser ce drapeau
// Réinitialiser les positions des aiguilles pour l'horloge afin de forcer leur dessin initial
prevHX1 = 0; prevHY1 = 0; prevHX2 = 0; prevHY2 = 0; prevHX3 = 0; prevHY3 = 0;
prevMX1 = 0; prevMY1 = 0; prevMX2 = 0; prevMY2 = 0; prevMX3 = 0; prevMY3 = 0;
prevSX1 = 0; prevSY1 = 0; prevSX2 = 0; prevSY2 = 0; prevSX3 = 0; prevSY3 = 0;
prevCurrentHour = -1; // Force le dessin initial des aiguilles
prevCurrentMinute = -1;
prevCurrentSecond = -1;
afficherHorlogeAnalogique_Infos(); // Initialise l'écran de l'horloge analogique sur lcd_Infos
delay(300); // Debounce
}
// Nouvelle Option "2. Calendrier"
else if (touchY > (startY_tools + (optionHeight_tools + spacing_tools) * 1) && touchY < (startY_tools + (optionHeight_tools + spacing_tools) * 1 + optionHeight_tools)) {
currentToolsMenu = 2; // Nouvelle valeur pour le calendrier dans le sous-menu Outils
calendrierPageInitialized = false; // Force le redessin initial de la page calendrier
afficherCalendrierDetail_Infos(); // Appel initial
delay(300);
}
}
}
}
// --- Logique d'affichage et de navigation (Joystick) ---
if (currentScreen == 1) { // Affichage Heure/Date sur lcd_Infos
afficherHeureDateDetail_Infos(); // Rafraîchissement optimisé
} else if (currentScreen == 2) { // Affichage Température sur lcd_Infos
afficherTemperatureDetail_Infos();
} else if (currentScreen == 3) { // Menu "Réglages" (géré par lcd_Menu pour la navigation, lcd_Infos pour le contenu des réglages)
if (currentSettingsMenu == 0) { // Sous-menu de réglages (choix Regler Heure/Date) - AFFICHÉ SUR LCD_MENU
if (!reglagesSousMenuInitialized) {
afficherSousMenuReglages_Menu();
}
// Pas de navigation Joystick ici, seulement tactile.
} else if (currentSettingsMenu == 1) { // Réglage heure (sur lcd_Infos)
if (!reglerHeureInitialized) {
afficherReglerHeure_Infos();
}
if (joyMoveHandled) {
// Mettre à jour la valeur du champ sélectionné
if (joyY == 1) { // Joystick vers le haut (incrémente)
if (selectedField == FIELD_HOUR) tempAdjHour = (tempAdjHour + 1) % 24;
else if (selectedField == FIELD_MINUTE) tempAdjMinute = (tempAdjMinute + 1) % 60;
else if (selectedField == FIELD_SECOND) tempAdjSecond = (tempAdjSecond + 1) % 60;
} else if (joyY == -1) { // Joystick vers le bas (décrémente)
if (selectedField == FIELD_HOUR) tempAdjHour = (tempAdjHour - 1 + 24) % 24;
else if (selectedField == FIELD_MINUTE) tempAdjMinute = (tempAdjMinute - 1 + 60) % 60;
else if (selectedField == FIELD_SECOND) tempAdjSecond = (tempAdjSecond - 1 + 60) % 60;
}
// Mettre à jour la sélection du champ
if (joyX == -1) { // Joystick vers la GAUCHE (déplacer la sélection vers la gauche)
selectedField = (selectedField == FIELD_HOUR) ? FIELD_SECOND : (selectedField - 1); // Wrap from hour to second
} else if (joyX == 1) { // Joystick vers la DROITE (déplacer la sélection vers la droite)
selectedField = (selectedField == FIELD_SECOND) ? FIELD_HOUR : (selectedField + 1); // Wrap from second to hour
}
drawReglerHeureFields();
}
if (joyClickHandled) { // Joystick clic -> Confirmer ou sauvegarder
rtc.adjust(DateTime(now.year(), now.month(), now.day(), tempAdjHour, tempAdjMinute, tempAdjSecond));
currentScreen = 0; // Retour au menu principal
horlogeNumeriqueActive = false; afficherMenu();
clearAndDisplayDefaultInfos();
}
} else if (currentSettingsMenu == 2) { // Réglage date (sur lcd_Infos)
if (!reglerDateInitialized) {
afficherReglerDate_Infos();
}
if (joyMoveHandled) {
// Mettre à jour la valeur du champ sélectionné
if (joyY == 1) { // Joystick vers le haut (incrémente)
if (selectedField == FIELD_DAY) {
int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (tempAdjMonth == 2 && tempAdjYear % 4 == 0 && (tempAdjYear % 100 != 0 || tempAdjYear % 400 == 0)) {
daysInMonth[2] = 29;
}
tempAdjDay++;
if (tempAdjDay > daysInMonth[tempAdjMonth]) tempAdjDay = 1;
}
else if (selectedField == FIELD_MONTH) {
tempAdjMonth++;
if (tempAdjMonth > 12) tempAdjMonth = 1;
}
else if (selectedField == FIELD_YEAR) {
tempAdjYear++;
}
} else if (joyY == -1) { // Joystick vers le bas (décrémente)
if (selectedField == FIELD_DAY) {
int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (tempAdjMonth == 2 && tempAdjYear % 4 == 0 && (tempAdjYear % 100 != 0 || tempAdjYear % 400 == 0)) {
daysInMonth[2] = 29;
}
tempAdjDay--;
if (tempAdjDay < 1) tempAdjDay = daysInMonth[tempAdjMonth];
}
else if (selectedField == FIELD_MONTH) {
tempAdjMonth--;
if (tempAdjMonth < 1) tempAdjMonth = 12;
}
else if (selectedField == FIELD_YEAR) {
tempAdjYear--;
}
}
// Mettre à jour la sélection du champ
if (joyX == -1) {
selectedField = (selectedField == FIELD_DAY) ? FIELD_YEAR : (selectedField - 1);
} else if (joyX == 1) {
selectedField = (selectedField == FIELD_YEAR) ? FIELD_DAY : (selectedField + 1);
}
drawReglerDateFields();
}
if (joyClickHandled) { // Joystick clic -> Confirmer
rtc.adjust(DateTime(tempAdjYear, tempAdjMonth, tempAdjDay, now.hour(), now.minute(), now.second()));
currentScreen = 0; // Retour au menu principal
horlogeNumeriqueActive = false; afficherMenu();
clearAndDisplayDefaultInfos();
}
}
} else if (currentScreen == 5) { // Menu "Outils"
if (currentToolsMenu == 0) { // Sous-menu d'outils
if (!outilsSousMenuInitialized) {
afficherSousMenuOutils_Menu();
}
// Pas de navigation Joystick ici, seulement tactile.
} else if (currentToolsMenu == 1) { // Horloge analogique (sur lcd_Infos)
// La mise à jour des aiguilles est gérée par actualiserAiguillesHorloge() dans loop()
// et drawReturnButton_Menu() est appelée dans afficherHorlogeAnalogique_Infos()
// lors de l'initialisation de la page.
} else if (currentToolsMenu == 2) { // Calendrier (sur lcd_Infos), nouvelle position
afficherCalendrierDetail_Infos(); // Appel pour le rafraîchissement
}
}
delay(50);
}
// === Fonction appelée toutes les 1000 ms pour mettre à jour les aiguilles ===
void actualiserAiguillesHorloge() {
DateTime nowRTC = rtc.now(); // Utiliser un nom distinct pour éviter la confusion avec la variable globale 'now'
int currentHour = nowRTC.hour();
int currentMinute = nowRTC.minute();
int currentSecond = nowRTC.second();
// --- LOGIQUE D'AFFICHAGE OPTIMISÉE (anti-scintillement) ---
// Gestion de l'aiguille des SECONDES (POINTS)
// Uniquement si la seconde a changé, ou à la première initialisation
if (currentSecond != prevCurrentSecond || prevCurrentSecond == -1) {
// 1. Effacer l'ancien point des secondes (le redessiner avec la couleur de fond du cadran)
if (prevCurrentSecond != -1) { // Ne pas effacer si c'est la toute première fois
float prevSecAngle = (prevCurrentSecond * 6 - 90) * PI / 180;
int oldX = CLOCK_CENTER_X + round(SEC_DOT_LENGTH * cos(prevSecAngle));
int oldY = CLOCK_CENTER_Y + round(SEC_DOT_LENGTH * sin(prevSecAngle));
// Effacer avec la couleur du cadran, qui est maintenant explicitement dessinée
lcd_Infos.fillCircle(oldX, oldY, SEC_DOT_RADIUS, FACE_COLOR);
}
// 2. Dessiner le nouveau point des secondes (en cyan/actif)
float newSecAngle = (currentSecond * 6 - 90) * PI / 180;
int newX = CLOCK_CENTER_X + round(SEC_DOT_LENGTH * cos(newSecAngle));
int newY = CLOCK_CENTER_Y + round(SEC_DOT_LENGTH * sin(newSecAngle));
lcd_Infos.fillCircle(newX, newY, SEC_DOT_RADIUS, SEC_ON_COLOR);
prevCurrentSecond = currentSecond; // Mettre à jour la seconde précédente
}
// --- CALCUL DES POINTS DES TRIANGLES POUR LES AIGUILLES (Minute et Heure) ---
// Angle pour l'aiguille des minutes (inclut la contribution des secondes pour la fluidité)
float minAngle = (currentMinute * 6 + currentSecond * 0.1 - 90) * PI / 180;
// Angle pour l'aiguille des heures (inclut la contribution des minutes et des secondes pour la fluidité)
float hourAngle = ((currentHour % 12) * 30 + currentMinute * 0.5 + currentSecond * (0.5/60.0) - 90) * PI / 180;
// Aiguille des MINUTES (toujours redessinée pour le mouvement fluide)
// Effacer l'ancienne aiguille des minutes
if (prevMX1 != 0 || prevCurrentMinute == -1) { // Ajoutez prevCurrentMinute == -1 pour le premier dessin
lcd_Infos.fillTriangle(prevMX1, prevMY1, prevMX2, prevMY2, prevMX3, prevMY3, FACE_COLOR);
}
// Calcul des 3 points pour la NOUVELLE aiguille des MINUTES
int newMX1 = CLOCK_CENTER_X + (int)(MIN_HAND_LENGTH * cos(minAngle));
int newMY1 = CLOCK_CENTER_Y + (int)(MIN_HAND_LENGTH * sin(minAngle));
int newMX2 = CLOCK_CENTER_X + (int)(MIN_HAND_BASE_WIDTH * cos(minAngle - PI/2));
int newMY2 = CLOCK_CENTER_Y + (int)(MIN_HAND_BASE_WIDTH * sin(minAngle - PI/2));
int newMX3 = CLOCK_CENTER_X + (int)(MIN_HAND_BASE_WIDTH * cos(minAngle + PI/2));
int newMY3 = CLOCK_CENTER_Y + (int)(MIN_HAND_BASE_WIDTH * sin(minAngle + PI/2));
// Dessiner la NOUVELLE aiguille des minutes
lcd_Infos.fillTriangle(newMX1, newMY1, newMX2, newMY2, newMX3, newMY3, MIN_HAND_COLOR);
// Mettre à jour les variables prev... pour la prochaine effacement
prevMX1 = newMX1; prevMY1 = newMY1; prevMX2 = newMX2; prevMY2 = newMY2; prevMX3 = newMX3; prevMY3 = newMY3;
prevCurrentMinute = currentMinute;
// Aiguille des HEURES (toujours redessinée pour le mouvement fluide)
// Effacer l'ancienne aiguille des heures
if (prevHX1 != 0 || prevCurrentHour == -1) { // Ajoutez prevCurrentHour == -1 pour le premier dessin
lcd_Infos.fillTriangle(prevHX1, prevHY1, prevHX2, prevHY2, prevHX3, prevHY3, FACE_COLOR);
}
// Calcul des 3 points pour la NOUVELLE aiguille des HEURES
int newHX1 = CLOCK_CENTER_X + (int)(HOUR_HAND_LENGTH * cos(hourAngle));
int newHY1 = CLOCK_CENTER_Y + (int)(HOUR_HAND_LENGTH * sin(hourAngle));
int newHX2 = CLOCK_CENTER_X + (int)(HOUR_HAND_BASE_WIDTH * cos(hourAngle - PI/2));
int newHY2 = CLOCK_CENTER_Y + (int)(HOUR_HAND_BASE_WIDTH * sin(hourAngle - PI/2));
int newHX3 = CLOCK_CENTER_X + (int)(HOUR_HAND_BASE_WIDTH * cos(hourAngle + PI/2));
int newHY3 = CLOCK_CENTER_Y + (int)(HOUR_HAND_BASE_WIDTH * sin(hourAngle + PI/2));
// Dessiner la NOUVELLE aiguille des heures
lcd_Infos.fillTriangle(newHX1, newHY1, newHX2, newHY2, newHX3, newHY3, HOUR_HAND_COLOR);
// Mettre à jour les variables prev... pour la prochaine effacement
prevHX1 = newHX1; prevHY1 = newHY1; prevHX2 = newHX2; prevHY2 = newHY2; prevHX3 = newHX3; prevHY3 = newHY3;
prevCurrentHour = currentHour;
// Dessiner un petit cercle au centre de l'horloge pour masquer les jointures des aiguilles
lcd_Infos.fillCircle(CLOCK_CENTER_X, CLOCK_CENTER_Y, MIN_HAND_BASE_WIDTH + 2, ILI9341_DARKGREY);
}
// Affiche une horloge digitale simple : HH : MM : SS
void afficherHeureDate_Infos() {
lcd_Infos.fillScreen(ILI9341_BLACK);
lcd_Infos.setTextColor(ILI9341_RED, ILI9341_BLACK);
lcd_Infos.setTextSize(4);
lcd_Infos.setCursor(30, 100);
DateTime now = rtc.now();
if (now.hour() < 10) lcd_Infos.print("0");
lcd_Infos.print(now.hour());
lcd_Infos.print(" : ");
if (now.minute() < 10) lcd_Infos.print("0");
lcd_Infos.print(now.minute());
lcd_Infos.print(" : ");
if (now.second() < 10) lcd_Infos.print("0");
lcd_Infos.print(now.second());
}
Loading
ili9341-cap-touch
ili9341-cap-touch
Temp.
Intérieur
Temp.
Extérieur