/*
Projet HomeHub - 2e Partie
Version : 2025-06-07.31
Auteur : Daniel Talbot
Adaptation : Mega + DS1307 + double ILI9341 + Joystick + Tactile
*/
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <Wire.h>
#include <RTClib.h>
#include <Adafruit_FT6206.h> // Ajout pour le tactile capacitif
// === Brochage écran tactile (lcd1 - écran de menu) ===
#define TFT_MENU_CS 10
#define TFT_MENU_DC 9
#define TFT_MENU_RST 8
// === Brochage écran infos (lcd2 - écran d'affichage date/heure) ===
#define TFT_INFO_CS 40
#define TFT_INFO_DC 41
#define TFT_INFO_RST 42
// === Joystick ===
#define JOY_VERT A1
#define JOY_HORZ A0
#define JOY_SEL 2
// === Objets ===
Adafruit_ILI9341 tft_Menu(TFT_MENU_CS, TFT_MENU_DC, TFT_MENU_RST);
Adafruit_ILI9341 tft_Infos(TFT_INFO_CS, TFT_INFO_DC, TFT_INFO_RST);
RTC_DS1307 rtc;
Adafruit_FT6206 ctp = Adafruit_FT6206();
// === Etats ===
enum ScreenState { MAIN_MENU, SET_TIME };
ScreenState currentScreen = MAIN_MENU;
// Utilisé uniquement pendant SET_TIME
// Les champs sont maintenant plus granulaires pour le réglage
enum TimeField { SET_HOUR, SET_MINUTE, SET_SECOND, SET_DAY, SET_MONTH, SET_YEAR, NUM_FIELDS };
int selectedField = SET_HOUR; // Commence par le réglage de l'heure
DateTime tempTime; // Utilisé pour stocker les valeurs de l'heure/date en cours de modification
bool showBlink = true; // Gère l'état du clignotement
unsigned long lastBlinkTime = 0; // Dernier moment où le clignotement a changé d'état
unsigned long lastUpdate = 0; // Dernier moment où l'écran d'infos a été mis à jour (pour l'heure/date)
// --- GLOBAL: lastSelected pour drawSetTimeScreen ---
int lastSelectedFieldInSetTime = -1; // Pour track le changement de selection dans drawSetTimeScreen
// === Constantes ===
const unsigned long BLINK_INTERVAL = 500; // Intervalle de clignotement en ms
const int JOY_THRESHOLD = 200; // Seuil pour la détection du mouvement du joystick
const int TEXT_SIZE_TIME = 5; // Taille du texte pour l'heure principale
const int TEXT_SIZE_DATE = 3; // Taille du texte pour le jour et la date principale
// Espacements verticaux globaux pour l'affichage principal
const int VERTICAL_SPACING_TIME_DAY = 25; // Espace entre l'heure et le jour (page principale)
const int VERTICAL_SPACING_DAY_DATE = 15; // Espace entre le jour et la date (page principale)
// Espacements verticaux pour l'écran de réglage (Set Time)
const int SET_TIME_GROUP_SPACING = 25; // Espace entre les groupes (Heure, Jour, Date)
// === Variables d'optimisation d'affichage ===
const char* jours[] = {"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"};
char lastHourStr[5] = ""; // HH: (pour la page principale)
char lastMinuteStr[3] = ""; // MM (pour la page principale)
char lastSecondStr[3] = ""; // SS (pour la page principale)
char lastDateStr[20] = ""; // Stocke la dernière chaîne de date affichée (page principale)
char lastDayStr[15] = ""; // Stocke la dernière chaîne de jour affichée (page principale)
// Variables pour le mode SET_TIME pour éviter le redessin complet
char lastSetTimeHourStr[3] = ""; // HH
char lastSetTimeMinuteStr[3] = ""; // MM
char lastSetTimeSecondStr[3] = ""; // SS
char lastSetTimeDayStr[3] = ""; // JJ
char lastSetTimeMonthStr[3] = ""; // MM
char lastSetTimeYearStr[5] = ""; // AAAA
char lastSetTimeDayOfWeekStr[15] = ""; // Nom du jour de la semaine
// Flag pour force un redessin complet (pour le démarrage ou après le réglage)
bool forceFullInfoRedraw = true;
bool forceFullSetTimeRedraw = true; // Nouveau flag pour forcer un redessin complet du SetTime
// === Fonctions d'affichage ===
/**
* @brief Dessine l'écran d'informations (heure, jour, date) sur tft_Infos.
* N'actualise les éléments que s'ils ont changé pour optimiser.
*/
void drawInfoScreen() {
int16_t x1, y1;
uint16_t w, h;
uint16_t currentTextWidth; // Pour stocker la largeur du texte courant
uint16_t currentTextHeight; // Pour stocker la hauteur du texte courant
DateTime now = rtc.now(); // Récupère l'heure et la date actuelles du RTC
char hourStr[5]; // HH:
char minuteStr[3]; // MM
char secondStr[3]; // SS
char dateStr[20];
char dayStr[15];
// Formatage des chaînes de caractères de l'heure
sprintf(hourStr, "%02d:", now.hour());
sprintf(minuteStr, "%02d", now.minute());
sprintf(secondStr, "%02d", now.second());
sprintf(dateStr, "%02d/%02d/%04d", now.day(), now.month(), now.year());
strcpy(dayStr, jours[now.dayOfTheWeek()]);
// Détection des changements pour un redessin partiel ou complet
bool hourChanged = strcmp(hourStr, lastHourStr) != 0;
bool minuteChanged = strcmp(minuteStr, lastMinuteStr) != 0;
bool secondChanged = strcmp(secondStr, lastSecondStr) != 0;
bool dateChanged = strcmp(dateStr, lastDateStr) != 0;
bool dayChanged = strcmp(dayStr, lastDayStr) != 0;
// Calcul des dimensions de chaque ligne pour le centrage vertical
tft_Infos.setTextSize(TEXT_SIZE_TIME);
tft_Infos.getTextBounds("00:00:00", 0, 0, &x1, &y1, &w, &h); // Hauteur de l'heure (estimation)
uint16_t lineHeightTime = h;
tft_Infos.setTextSize(TEXT_SIZE_DATE);
tft_Infos.getTextBounds("Mercredi", 0, 0, &x1, &y1, &w, &h); // Hauteur du jour
uint16_t lineHeightDay = h;
tft_Infos.getTextBounds("00/00/0000", 0, 0, &x1, &y1, &w, &h); // Hauteur de la date
uint16_t lineHeightDate = h;
// Calcul de la hauteur totale du bloc d'informations
uint16_t totalBlockHeight = lineHeightTime + VERTICAL_SPACING_TIME_DAY +
lineHeightDay + VERTICAL_SPACING_DAY_DATE +
lineHeightDate;
// Calcul de la position Y de départ pour centrer le bloc
int startY = (tft_Infos.height() - totalBlockHeight) / 2;
// Calcul des positions Y pour chaque ligne basée sur 'startY'
int y_time_line = startY;
int y_day_line = y_time_line + lineHeightTime + VERTICAL_SPACING_TIME_DAY;
int y_date_line = y_day_line + lineHeightDay + VERTICAL_SPACING_DAY_DATE;
// --- Mesure des largeurs des composants de l'heure ---
tft_Infos.setTextSize(TEXT_SIZE_TIME);
tft_Infos.getTextBounds("00", 0, 0, &x1, &y1, &w, &h);
uint16_t widthTwoDigits = w; // Largeur de "00"
tft_Infos.getTextBounds(":", 0, 0, &x1, &y1, &w, &h);
uint16_t widthColon = w; // Largeur de ":"
// Largeur totale de l'heure "HH:MM:SS" pour centrage global
uint16_t totalTimeWidth = (widthTwoDigits * 2) + (widthColon * 2) + widthTwoDigits; // HH + : + MM + : + SS
// Position X de départ pour l'heure afin de la centrer horizontalement
int startX_time = (tft_Infos.width() - totalTimeWidth) / 2;
// Si un redessin complet est forcé (démarrage, retour du réglage)
if (forceFullInfoRedraw) {
tft_Infos.fillScreen(ILI9341_NAVY); // CHANGER LE FOND ICI (Bleu marine foncé)
// Cadre autour des informations (ajusté pour inclure tout)
// Le cadre sera centré autour du bloc de texte
uint16_t framePadding = 8; // Marge entre le texte et le cadre
tft_Infos.drawRect(
(tft_Infos.width() - (totalTimeWidth + framePadding * 2)) / 2, // X début du cadre
startY - framePadding, // Y début du cadre
totalTimeWidth + framePadding * 2, // Largeur du cadre
totalBlockHeight + framePadding * 2, // Hauteur du cadre
ILI9341_YELLOW // COULEUR DU CADRE ICI
);
// Lignes de séparation (positions calculées dynamiquement)
tft_Infos.drawFastHLine(startX_time + 10, y_time_line + lineHeightTime + (VERTICAL_SPACING_TIME_DAY/2), totalTimeWidth - 20, ILI9341_YELLOW); // COULEUR LIGNE 1
tft_Infos.drawFastHLine(startX_time + 10, y_day_line + lineHeightDay + (VERTICAL_SPACING_DAY_DATE/2), totalTimeWidth - 20, ILI9341_YELLOW); // COULEUR LIGNE 2
forceFullInfoRedraw = false; // Réinitialise le flag après le redessin
}
// --- Affichage de l'heure (HH) ---
if (strcmp(hourStr, lastHourStr) != 0 || forceFullInfoRedraw) { // Change si HH: change ou si redrawAll
tft_Infos.setTextSize(TEXT_SIZE_TIME);
tft_Infos.setTextColor(ILI9341_CYAN, ILI9341_NAVY);
// Efface l'ancien HH (sans le :)
tft_Infos.getTextBounds(lastHourStr, 0, 0, &x1, &y1, &w, &h); // Mesure l'ancien texte HH:
tft_Infos.fillRect(startX_time, y_time_line, w, h, ILI9341_NAVY); // Efface HH:
// Dessine le nouveau HH
tft_Infos.getTextBounds(hourStr, 0, 0, &x1, &y1, &w, &h); // Mesure HH:
tft_Infos.setCursor(startX_time, y_time_line);
tft_Infos.print(hourStr); // Imprime HH:
strcpy(lastHourStr, hourStr);
}
// --- Affichage des minutes (MM) ---
// Position pour MM:
// Calculer la largeur de la partie "HH:"
tft_Infos.getTextBounds(hourStr, 0, 0, &x1, &y1, &w, &h);
uint16_t width_hh_colon = w;
uint16_t mm_x_pos = startX_time + width_hh_colon;
if (strcmp(minuteStr, lastMinuteStr) != 0 || forceFullInfoRedraw) { // Change si MM change ou si redrawAll
tft_Infos.setTextSize(TEXT_SIZE_TIME);
tft_Infos.setTextColor(ILI9341_CYAN, ILI9341_NAVY);
// Efface l'ancien MM
tft_Infos.getTextBounds(lastMinuteStr, 0, 0, &x1, &y1, &w, &h); // Mesure l'ancien MM
tft_Infos.fillRect(mm_x_pos, y_time_line, w, h, ILI9341_NAVY); // Efface MM
// Dessine le nouveau MM
tft_Infos.getTextBounds(minuteStr, 0, 0, &x1, &y1, &w, &h);
tft_Infos.setCursor(mm_x_pos, y_time_line);
tft_Infos.print(minuteStr);
strcpy(lastMinuteStr, minuteStr);
}
// --- Affichage des secondes (:SS) ---
// Position pour SS:
// Calculer la largeur de la partie "HH:MM"
tft_Infos.getTextBounds(hourStr, 0, 0, &x1, &y1, &w, &h);
width_hh_colon = w; // Largeur de HH:
tft_Infos.getTextBounds(minuteStr, 0, 0, &x1, &y1, &w, &h);
uint16_t width_mm = w; // Largeur de MM
uint16_t ss_x_pos = startX_time + width_hh_colon + width_mm;
if (strcmp(secondStr, lastSecondStr) != 0 || forceFullInfoRedraw) { // Change si SS change ou si redrawAll
tft_Infos.setTextSize(TEXT_SIZE_TIME);
tft_Infos.setTextColor(ILI9341_CYAN, ILI9341_NAVY);
// Efface l'ancien SS (avec le : devant)
tft_Infos.getTextBounds(":" + String(lastSecondStr), 0, 0, &x1, &y1, &w, &h); // Mesure l'ancien :SS
tft_Infos.fillRect(ss_x_pos, y_time_line, w, h, ILI9341_NAVY); // Efface :SS
// Dessine le nouveau SS (avec le : devant)
tft_Infos.getTextBounds(":" + String(secondStr), 0, 0, &x1, &y1, &w, &h);
tft_Infos.setCursor(ss_x_pos, y_time_line);
tft_Infos.print(":"); // Ajoute le ":" avant les secondes
tft_Infos.print(secondStr);
strcpy(lastSecondStr, secondStr);
}
// --- Affichage du jour de la semaine ---
if (dayChanged || forceFullInfoRedraw) {
tft_Infos.setTextSize(TEXT_SIZE_DATE); // Utilise la taille pour la date
tft_Infos.setTextColor(ILI9341_MAGENTA, ILI9341_NAVY); // CHANGER COULEUR TEXTE JOUR ET FOND LOCAL
tft_Infos.getTextBounds(lastDayStr, 0, 0, &x1, &y1, &w, &h); // Mesure l'ancien texte
currentTextWidth = w;
currentTextHeight = h;
// La position X est recalculée pour centrer le jour
tft_Infos.fillRect((tft_Infos.width() - currentTextWidth) / 2, y_day_line, currentTextWidth, currentTextHeight, ILI9341_NAVY);
tft_Infos.getTextBounds(dayStr, 0, 0, &x1, &y1, &w, &h); // Mesure le nouveau texte
tft_Infos.setCursor((tft_Infos.width() - w) / 2, y_day_line);
tft_Infos.print(dayStr);
strcpy(lastDayStr, dayStr);
}
// --- Affichage de la date ---
if (dateChanged || forceFullInfoRedraw) {
tft_Infos.setTextSize(TEXT_SIZE_DATE); // Utilise la taille pour la date
tft_Infos.setTextColor(ILI9341_LIGHTGREY, ILI9341_NAVY); // CHANGER COULEUR TEXTE DATE ET FOND LOCAL
tft_Infos.getTextBounds(lastDateStr, 0, 0, &x1, &y1, &w, &h); // Mesure l'ancien texte
currentTextWidth = w;
currentTextHeight = h;
// La position X est recalculée pour centrer la date
tft_Infos.fillRect((tft_Infos.width() - currentTextWidth) / 2, y_date_line, currentTextWidth, currentTextHeight, ILI9341_NAVY);
tft_Infos.getTextBounds(dateStr, 0, 0, &x1, &y1, &w, &h); // Mesure le nouveau texte
tft_Infos.setCursor((tft_Infos.width() - w) / 2, y_date_line);
tft_Infos.print(dateStr);
strcpy(lastDateStr, dateStr);
}
}
/**
* @brief Dessine le menu principal sur tft_Menu.
*/
void drawMainMenu() {
tft_Menu.fillScreen(ILI9341_BLUE); // Fond bleu
tft_Menu.setTextColor(ILI9341_WHITE); // Texte blanc
tft_Menu.setTextSize(2); // Taille du texte
tft_Menu.setCursor(20, 40); // Position du curseur
tft_Menu.print("1. Regler l'heure/date"); // Option du menu
}
/**
* @brief Dessine l'écran de réglage de l'heure/date sur tft_Infos.
* Reproduit l'esthétique de la page d'accueil avec des champs regroupés et clignotement ciblé.
*/
void drawSetTimeScreen() {
int16_t x1, y1_text;
uint16_t w, h;
// --- Déclarations des variables de largeur/hauteur de caractère pour les calculs ---
tft_Infos.setTextSize(TEXT_SIZE_TIME);
tft_Infos.getTextBounds("0",0,0,&x1,&y1_text,&w,&h); uint16_t charWidthHour = w; uint16_t charHeightHour = h;
tft_Infos.getTextBounds(":",0,0,&x1,&y1_text,&w,&h); uint16_t colonWidth_val = w;
tft_Infos.setTextSize(TEXT_SIZE_DATE);
tft_Infos.getTextBounds("0",0,0,&x1,&y1_text,&w,&h); uint16_t charWidthDate = w; uint16_t charHeightDate = h;
tft_Infos.getTextBounds("/",0,0,&x1,&y1_text,&w,&h); uint16_t slashWidth_val = w;
// --- Obtenir les valeurs actuelles pour l'affichage ---
DateTime now = tempTime; // Utilise tempTime pour les valeurs de réglage
char hourPart[3], minutePart[3], secondPart[3]; // HH, MM, SS
char dayPart[3], monthPart[3], yearPart[5]; // JJ, MM, AAAA
char dayOfWeekName[15];
sprintf(hourPart, "%02d", now.hour());
sprintf(minutePart, "%02d", now.minute());
sprintf(secondPart, "%02d", now.second());
sprintf(dayPart, "%02d", now.day());
sprintf(monthPart, "%02d", now.month());
sprintf(yearPart, "%04d", now.year());
strcpy(dayOfWeekName, jours[now.dayOfTheWeek()]);
// --- Calcul des dimensions pour le centrage des éléments ---
// Hauteur des lignes de contenu
tft_Infos.setTextSize(TEXT_SIZE_TIME); // Heure utilise TEXT_SIZE_TIME
tft_Infos.getTextBounds("00:00:00", 0, 0, &x1, &y1_text, &w, &h);
uint16_t lineHeightHourGroup = h; // Hauteur de la ligne d'heure
tft_Infos.setTextSize(TEXT_SIZE_DATE); // Jour et date utilisent TEXT_SIZE_DATE
tft_Infos.getTextBounds("Mercredi", 0, 0, &x1, &y1_text, &w, &h);
uint16_t lineHeightDayGroup = h; // Hauteur de la ligne du jour
tft_Infos.getTextBounds("00/00/0000", 0, 0, &x1, &y1_text, &w, &h);
uint16_t lineHeightDateGroup = h; // Hauteur de la ligne de date
// Définir les hauteurs de chaque ligne de contenu (pour le centrage vertical)
uint16_t totalContentHeight = lineHeightHourGroup + SET_TIME_GROUP_SPACING +
lineHeightDayGroup + SET_TIME_GROUP_SPACING +
lineHeightDateGroup;
// Calcul de la position Y de départ pour centrer le bloc complet
int startY_content_block = (tft_Infos.height() - totalContentHeight) / 2;
// Positions Y de chaque ligne de contenu
int y_line_hour = startY_content_block;
int y_line_day = y_line_hour + lineHeightHourGroup + SET_TIME_GROUP_SPACING;
int y_line_date = y_line_day + lineHeightDayGroup + SET_TIME_GROUP_SPACING;
// Calcul de la largeur max pour le cadre et les lignes de séparation
tft_Infos.setTextSize(TEXT_SIZE_TIME);
tft_Infos.getTextBounds("00:00:00", 0, 0, &x1, &y1_text, &w, &h);
uint16_t maxWidthHour = w;
tft_Infos.setTextSize(TEXT_SIZE_DATE);
tft_Infos.getTextBounds("Mercredi", 0, 0, &x1, &y1_text, &w, &h); // Max longueur du jour
uint16_t maxWidthDay = w;
tft_Infos.getTextBounds("00/00/0000", 0, 0, &x1, &y1_text, &w, &h); // Max longueur date
uint16_t maxWidthDate = w;
uint16_t maxContentWidth = max(max(maxWidthHour, maxWidthDay), maxWidthDate);
// --- Premier dessin (fond, cadre, lignes fixes) ---
// On utilise forceFullSetTimeRedraw pour s'assurer que le fond et le cadre sont dessinés une seule fois à l'entrée du mode
if (forceFullSetTimeRedraw) { // Se déclenche une fois quand on entre en mode SET_TIME
tft_Infos.fillScreen(ILI9341_NAVY); // Efface l'écran avec le fond bleu marine
uint16_t framePadding = 8; // Marge pour le cadre
tft_Infos.drawRect(
(tft_Infos.width() - (maxContentWidth + framePadding * 2)) / 2, // X début du cadre
startY_content_block - framePadding,
maxContentWidth + framePadding * 2,
totalContentHeight + framePadding * 2,
ILI9341_YELLOW
);
// Dessine les lignes de séparation statiques
tft_Infos.drawFastHLine((tft_Infos.width() - maxContentWidth) / 2, y_line_hour + lineHeightHourGroup + (SET_TIME_GROUP_SPACING/2), maxContentWidth, ILI9341_DARKCYAN);
tft_Infos.drawFastHLine((tft_Infos.width() - maxContentWidth) / 2, y_line_day + lineHeightDayGroup + (SET_TIME_GROUP_SPACING/2), maxContentWidth, ILI9341_DARKCYAN);
// Après le premier dessin, le flag est à false
forceFullSetTimeRedraw = false;
}
// --- LOGIQUE DE RAFRAÎCHISSEMENT GRANULAIRE DES CHAMPS ---
// Cette logique est appelée à chaque loop pour gérer les changements de valeur et le clignotement.
// --- Affichage de l'heure (HH:MM:SS) ---
tft_Infos.setTextSize(TEXT_SIZE_TIME);
// Calcul de la position X pour centrer HH:MM:SS
tft_Infos.getTextBounds("00:00:00", 0, 0, &x1, &y1_text, &w, &h);
int startX_hour_group = (tft_Infos.width() - w) / 2;
// HH
// Redessine si la valeur HH a changé OU si HH est le champ sélectionné (pour le clignotement) OU si on vient de changer de champ (pour l'ancien champ qui doit s'éteindre et le nouveau qui s'allume)
if (strcmp(hourPart, lastSetTimeHourStr) != 0 || selectedField == SET_HOUR || lastSelectedFieldInSetTime == SET_HOUR) {
uint32_t textColor = ILI9341_CYAN;
if (selectedField == SET_HOUR) {
textColor = showBlink ? ILI9341_YELLOW : ILI9341_NAVY; // Clignote en jaune sur fond si sélectionné
}
tft_Infos.getTextBounds(lastSetTimeHourStr, 0, 0, &x1, &y1_text, &w, &h);
tft_Infos.fillRect(startX_hour_group, y_line_hour, w, h, ILI9341_NAVY);
tft_Infos.setTextColor(textColor, ILI9341_NAVY);
tft_Infos.setCursor(startX_hour_group, y_line_hour);
tft_Infos.print(hourPart);
strcpy(lastSetTimeHourStr, hourPart);
}
// Deux points après HH (couleur fixe)
tft_Infos.setTextColor(ILI9341_CYAN, ILI9341_NAVY);
tft_Infos.getTextBounds(hourPart, 0, 0, &x1, &y1_text, &w, &h); // Largeur de HH
uint16_t offset_after_hh_val = w;
tft_Infos.setCursor(startX_hour_group + offset_after_hh_val, y_line_hour); tft_Infos.print(":");
uint16_t current_x_pos_after_hh = startX_hour_group + offset_after_hh_val + colonWidth_val; // Position après HH:
// MM
if (strcmp(minutePart, lastSetTimeMinuteStr) != 0 || selectedField == SET_MINUTE || lastSelectedFieldInSetTime == SET_MINUTE) {
uint32_t textColor = ILI9341_CYAN;
if (selectedField == SET_MINUTE) {
textColor = showBlink ? ILI9341_YELLOW : ILI9341_NAVY;
}
tft_Infos.getTextBounds(lastSetTimeMinuteStr, 0, 0, &x1, &y1_text, &w, &h);
tft_Infos.fillRect(current_x_pos_after_hh, y_line_hour, w, h, ILI9341_NAVY);
tft_Infos.setTextColor(textColor, ILI9341_NAVY);
tft_Infos.setCursor(current_x_pos_after_hh, y_line_hour); tft_Infos.print(minutePart);
strcpy(lastSetTimeMinuteStr, minutePart);
}
// Deux points après MM (couleur fixe)
tft_Infos.setTextColor(ILI9341_CYAN, ILI9341_NAVY);
tft_Infos.getTextBounds(minutePart, 0, 0, &x1, &y1_text, &w, &h);
uint16_t offset_after_mm_val = w;
uint16_t current_x_pos_after_mm_val = current_x_pos_after_hh + offset_after_mm_val;
tft_Infos.setCursor(current_x_pos_after_mm_val, y_line_hour); tft_Infos.print(":");
uint16_t current_x_pos_after_mm = current_x_pos_after_mm_val + colonWidth_val;
// SS
if (strcmp(secondPart, lastSetTimeSecondStr) != 0 || selectedField == SET_SECOND || lastSelectedFieldInSetTime == SET_SECOND) {
uint32_t textColor = ILI9341_CYAN;
if (selectedField == SET_SECOND) {
textColor = showBlink ? ILI9341_YELLOW : ILI9341_NAVY;
}
tft_Infos.getTextBounds(lastSetTimeSecondStr, 0, 0, &x1, &y1_text, &w, &h);
tft_Infos.fillRect(current_x_pos_after_mm, y_line_hour, w, h, ILI9341_NAVY);
tft_Infos.setTextColor(textColor, ILI9341_NAVY);
tft_Infos.setCursor(current_x_pos_after_mm, y_line_hour); tft_Infos.print(secondPart);
strcpy(lastSetTimeSecondStr, secondPart);
}
// --- Affichage du jour de la semaine (Nom du jour) ---
tft_Infos.setTextSize(TEXT_SIZE_DATE);
if (strcmp(dayOfWeekName, lastSetTimeDayOfWeekStr) != 0 || selectedField == SET_DAY || lastSelectedFieldInSetTime == SET_DAY) {
uint32_t textColor = ILI9341_MAGENTA;
if (selectedField == SET_DAY) {
textColor = showBlink ? ILI9341_YELLOW : ILI9341_NAVY;
}
tft_Infos.getTextBounds(lastSetTimeDayOfWeekStr, 0, 0, &x1, &y1_text, &w, &h);
tft_Infos.fillRect((tft_Infos.width() - w) / 2, y_line_day, w, h, ILI9341_NAVY);
tft_Infos.setTextColor(textColor, ILI9341_NAVY);
tft_Infos.getTextBounds(dayOfWeekName, 0, 0, &x1, &y1_text, &w, &h);
tft_Infos.setCursor((tft_Infos.width() - w) / 2, y_line_day); tft_Infos.print(dayOfWeekName);
strcpy(lastSetTimeDayOfWeekStr, dayOfWeekName);
}
// --- Affichage de la date (JJ/MM/AAAA) ---
tft_Infos.setTextSize(TEXT_SIZE_DATE);
// Largeurs des segments pour un alignement précis
tft_Infos.getTextBounds("00", 0, 0, &x1, &y1_text, &w, &h);
uint16_t widthTwoDigitsDate = w; // Largeur de "00" pour la date
tft_Infos.getTextBounds("0000", 0, 0, &x1, &y1_text, &w, &h);
uint16_t widthFourDigitsYear = w; // Largeur de "0000" pour l'année
// Largeur totale de "JJ/MM/AAAA" pour le centrage global
uint16_t totalWidthDateString = widthTwoDigitsDate + slashWidth_val + widthTwoDigitsDate + slashWidth_val + widthFourDigitsYear;
// Position X de départ pour centrer le groupe JJ/MM/AAAA
int startX_date_group = (tft_Infos.width() - totalWidthDateString) / 2;
// JJ
if (strcmp(dayPart, lastSetTimeDayStr) != 0 || selectedField == SET_DAY || lastSelectedFieldInSetTime == SET_DAY) {
uint32_t textColor = ILI9341_LIGHTGREY;
if (selectedField == SET_DAY) {
textColor = showBlink ? ILI9341_YELLOW : ILI9341_NAVY;
}
tft_Infos.getTextBounds(lastSetTimeDayStr, 0, 0, &x1, &y1_text, &w, &h);
tft_Infos.fillRect(startX_date_group, y_line_date, w, h, ILI9341_NAVY);
tft_Infos.setTextColor(textColor, ILI9341_NAVY);
tft_Infos.setCursor(startX_date_group, y_line_date); tft_Infos.print(dayPart);
strcpy(lastSetTimeDayStr, dayPart);
}
// Pos après JJ (pour la barre oblique)
uint16_t pos_after_jj_val = startX_date_group + widthTwoDigitsDate;
// Barre après JJ (couleur fixe)
tft_Infos.setTextColor(ILI9341_LIGHTGREY, ILI9341_NAVY);
tft_Infos.setCursor(pos_after_jj_val, y_line_date); tft_Infos.print("/");
// MM (Mois numérique)
uint16_t mm_date_x_pos = pos_after_jj_val + slashWidth_val; // Position après JJ/
if (strcmp(monthPart, lastSetTimeMonthStr) != 0 || selectedField == SET_MONTH || lastSelectedFieldInSetTime == SET_MONTH) {
uint32_t textColor = ILI9341_LIGHTGREY;
if (selectedField == SET_MONTH) {
textColor = showBlink ? ILI9341_YELLOW : ILI9341_NAVY;
}
tft_Infos.getTextBounds(lastSetTimeMonthStr, 0, 0, &x1, &y1_text, &w, &h);
tft_Infos.fillRect(mm_date_x_pos, y_line_date, w, h, ILI9341_NAVY);
tft_Infos.setTextColor(textColor, ILI9341_NAVY);
tft_Infos.setCursor(mm_date_x_pos, y_line_date); tft_Infos.print(monthPart);
strcpy(lastSetTimeMonthStr, monthPart);
}
// Pos après MM
uint16_t pos_after_mm_date_val = mm_date_x_pos + widthTwoDigitsDate;
// Barre après MM (couleur fixe)
tft_Infos.setTextColor(ILI9341_LIGHTGREY, ILI9341_NAVY);
tft_Infos.setCursor(pos_after_mm_date_val, y_line_date); tft_Infos.print("/");
// AAAA
uint16_t year_x_pos = pos_after_mm_date_val + slashWidth_val; // Position après MM/
if (strcmp(yearPart, lastSetTimeYearStr) != 0 || selectedField == SET_YEAR || lastSelectedFieldInSetTime == SET_YEAR) {
uint32_t textColor = ILI9341_LIGHTGREY;
if (selectedField == SET_YEAR) {
textColor = showBlink ? ILI9341_YELLOW : ILI9341_NAVY;
}
tft_Infos.getTextBounds(lastSetTimeYearStr, 0, 0, &x1, &y1_text, &w, &h);
tft_Infos.fillRect(year_x_pos, y_line_date, w, h, ILI9341_NAVY);
tft_Infos.setTextColor(textColor, ILI9341_NAVY);
tft_Infos.setCursor(year_x_pos, y_line_date); tft_Infos.print(yearPart);
strcpy(lastSetTimeYearStr, yearPart);
}
// --- Nettoyage des cadres clignotants si la sélection a changé ---
// On efface l'ancien cadre (si la sélection a changé) en le redessinant en couleur de fond
if (lastSelectedFieldInSetTime != selectedField) {
// Pour les calculs de taille des cadres (dimensions des "0" et ":", "/")
tft_Infos.setTextSize(TEXT_SIZE_TIME);
tft_Infos.getTextBounds("0",0,0,&x1,&y1_text,&w,&h); uint16_t charWidthHour = w; uint16_t charHeightHour = h;
tft_Infos.getTextBounds(":",0,0,&x1,&y1_text,&w,&h); uint16_t colonWidth_temp = w;
tft_Infos.setTextSize(TEXT_SIZE_DATE);
tft_Infos.getTextBounds("0",0,0,&x1,&y1_text,&w,&h); uint16_t charWidthDate = w; uint16_t charHeightDate = h;
tft_Infos.getTextBounds("/",0,0,&x1,&y1_text,&w,&h); uint16_t slashWidth_temp = w;
// Fonction utilitaire pour effacer un cadre précis
auto clearOldFrame = [&](int field) { // Lambda function to clear a frame
int prev_x = 0, prev_y = 0, prev_w = 0, prev_h = 0;
// Recalculer les offsets pour les champs de l'heure
tft_Infos.setTextSize(TEXT_SIZE_TIME); // Assurer la bonne taille de texte pour la mesure
tft_Infos.getTextBounds(lastSetTimeHourStr,0,0,&x1,&y1_text,&w,&h); // lastSetTimeHourStr est HH
uint16_t offset_hh_for_frame = w; // Largeur de HH
tft_Infos.getTextBounds(":",0,0,&x1,&y1_text,&w,&h); // Largeur du premier :
offset_hh_for_frame += w;
tft_Infos.getTextBounds(lastSetTimeMinuteStr,0,0,&x1,&y1_text,&w,&h); // Largeur de MM
uint16_t offset_mm_for_frame = offset_hh_for_frame + w; // Position juste avant le 2e :
tft_Infos.getTextBounds(":",0,0,&x1,&y1_text,&w,&h); // Largeur du second :
offset_mm_for_frame += w; // Position juste après le 2e :
// Recalculer les offsets pour les champs de la date
tft_Infos.setTextSize(TEXT_SIZE_DATE); // Assurer la bonne taille de texte pour la mesure
tft_Infos.getTextBounds(lastSetTimeDayStr,0,0,&x1,&y1_text,&w,&h); // Largeur de JJ
uint16_t offset_jj_for_frame = w;
tft_Infos.getTextBounds("/",0,0,&x1,&y1_text,&w,&h); // Largeur du premier /
offset_jj_for_frame += w;
tft_Infos.getTextBounds(lastSetTimeMonthStr,0,0,&x1,&y1_text,&w,&h); // Largeur de MM
uint16_t offset_mm_date_for_frame = offset_jj_for_frame + w;
tft_Infos.getTextBounds("/",0,0,&x1,&y1_text,&w,&h); // Largeur du second /
offset_mm_date_for_frame += w;
switch (field) {
case SET_HOUR:
prev_x = startX_hour_group - 2; prev_y = y_line_hour - 2; prev_w = (charWidthHour * 2) + 4; prev_h = charHeightHour + 4;
break;
case SET_MINUTE:
prev_x = startX_hour_group + (charWidthHour * 2) + colonWidth_val - 2; // X apres HH:
prev_y = y_line_hour - 2; prev_w = (charWidthHour * 2) + 4; prev_h = charHeightHour + 4;
break;
case SET_SECOND:
prev_x = startX_hour_group + (charWidthHour * 4) + (colonWidth_val * 2) - 2; // X apres HH:MM:
prev_y = y_line_hour - 2; prev_w = (charWidthHour * 2) + 4; prev_h = charHeightHour + 4;
break;
case SET_DAY:
prev_x = startX_date_group - 2; prev_y = y_line_date - 2; prev_w = (charWidthDate * 2) + 4; prev_h = charHeightDate + 4;
break;
case SET_MONTH:
prev_x = startX_date_group + (charWidthDate * 2) + slashWidth_val - 2; // X apres JJ/
prev_y = y_line_date - 2; prev_w = (charWidthDate * 2) + 4; prev_h = charHeightDate + 4;
break;
case SET_YEAR:
prev_x = startX_date_group + (charWidthDate * 4) + (slashWidth_val * 2) - 2; // X apres JJ/MM/
prev_y = y_line_date - 2; prev_w = (charWidthDate * 4) + 4; prev_h = charHeightDate + 4;
break;
}
// Dessine un rectangle de la couleur de fond pour effacer le cadre
if (prev_w > 0) tft_Infos.drawRect(prev_x, prev_y, prev_w, prev_h, ILI9341_NAVY);
};
// Efface le cadre de l'ancien champ sélectionné si la sélection a changé
if (lastSelectedFieldInSetTime != -1 && lastSelectedFieldInSetTime != selectedField) {
clearOldFrame(lastSelectedFieldInSetTime);
}
// Il n'y a plus de logique pour dessiner le cadre jaune ici, car nous l'avons supprimé.
}
lastSelectedFieldInSetTime = selectedField; // Met à jour la variable globale pour la prochaine itération
}
/**
* @brief Gère les entrées du joystick pour la modification de l'heure/date.
*/
void updateSetTime() {
int x = analogRead(JOY_HORZ); // Lecture axe horizontal du joystick
int y = analogRead(JOY_VERT); // Lecture axe vertical du joystick
bool selPressed = digitalRead(JOY_SEL) == LOW; // Lecture bouton de sélection (LOW = pressé avec INPUT_PULLUP)
static unsigned long lastMove = 0;
// Limite la vitesse des actions du joystick pour éviter les défilements trop rapides
if (millis() - lastMove < 200) return;
int delta = 0;
if (y < JOY_THRESHOLD) delta = 1; // Joystick poussé vers le haut
else if (y > 1023 - JOY_THRESHOLD) delta = -1; // Joystick poussé vers le bas
// Si le joystick est déplacé verticalement (changement de valeur)
if (delta != 0) {
switch (selectedField) {
case SET_HOUR:
tempTime = DateTime(tempTime.year(), tempTime.month(), tempTime.day(),
(tempTime.hour() + delta + 24) % 24, tempTime.minute(), tempTime.second());
break;
case SET_MINUTE:
tempTime = DateTime(tempTime.year(), tempTime.month(), tempTime.day(),
tempTime.hour(), (tempTime.minute() + delta + 60) % 60, tempTime.second());
break;
case SET_SECOND:
tempTime = DateTime(tempTime.year(), tempTime.month(), tempTime.day(),
tempTime.hour(), tempTime.minute(), (tempTime.second() + delta + 60) % 60);
break;
case SET_DAY:
tempTime = tempTime + TimeSpan(delta, 0, 0, 0); // Ajoute/retire un jour
break;
case SET_MONTH:
tempTime = DateTime(tempTime.year(), (tempTime.month() + delta - 1 + 12) % 12 + 1, tempTime.day(),
tempTime.hour(), tempTime.minute(), tempTime.second());
break;
case SET_YEAR:
tempTime = DateTime(tempTime.year() + delta, tempTime.month(), tempTime.day(),
tempTime.hour(), tempTime.minute(), tempTime.second());
break;
}
drawSetTimeScreen(); // Redessine l'écran avec la nouvelle valeur
lastMove = millis(); // Met à jour le temps du dernier mouvement
return;
}
// If the joystick is moved horizontally (field change)
if (x < JOY_THRESHOLD) { // Joystick pushed left
selectedField = (selectedField - 1 + NUM_FIELDS) % NUM_FIELDS; // Move to previous field, wrap around
drawSetTimeScreen(); // Redraw to show new selected field
lastMove = millis();
return;
} else if (x > 1023 - JOY_THRESHOLD) { // Joystick pushed right
selectedField = (selectedField + 1) % NUM_FIELDS; // Move to next field, wrap around
drawSetTimeScreen();
lastMove = millis();
return;
}
// If the select button is pressed (validation)
if (selPressed) {
rtc.adjust(tempTime); // Save the new time/date to RTC
// Display a confirmation message
tft_Infos.fillRect(40, 80, 240, 30, ILI9341_GREEN);
tft_Infos.setTextColor(ILI9341_BLACK);
tft_Infos.setTextSize(2);
tft_Infos.setCursor(60, 90);
tft_Infos.print("Heure Sauvee!");
delay(1000);
tft_Infos.fillScreen(ILI9341_BLACK); // Clear info screen for clean transition
currentScreen = MAIN_MENU; // Return to main menu
drawMainMenu(); // Redraw main menu
// Reset lastXStr variables to force full redraw of info screen
strcpy(lastHourStr, "");
strcpy(lastMinuteStr, "");
strcpy(lastSecondStr, "");
strcpy(lastDateStr, "");
strcpy(lastDayStr, "");
// Reset lastSetTimeXStr variables for next time entering set time mode
strcpy(lastSetTimeHourStr, "");
strcpy(lastSetTimeMinuteStr, "");
strcpy(lastSetTimeSecondStr, "");
strcpy(lastSetTimeDayStr, "");
strcpy(lastSetTimeMonthStr, "");
strcpy(lastSetTimeYearStr, "");
strcpy(lastSetTimeDayOfWeekStr, "");
forceFullInfoRedraw = true; // Force full redraw of info page
drawInfoScreen(); // Redraw info screen with new time
lastUpdate = millis();
lastMove = millis();
// Wait for button release
while (digitalRead(JOY_SEL) == LOW) {
delay(10);
}
// Wait for touch release
while (ctp.touched()) {
delay(10);
}
}
}
// === Fonction d'initialisation ===
void setup() {
pinMode(JOY_SEL, INPUT_PULLUP); // Activate internal pull-up resistor for joystick button
tft_Menu.begin(); // Initialize menu screen
tft_Infos.begin(); // Initialize info screen
// Set screen rotation once in setup
tft_Menu.setRotation(1);
tft_Infos.setRotation(1);
// Check RTC initialization
if (!rtc.begin()) {
tft_Menu.fillScreen(ILI9341_RED);
tft_Menu.setTextColor(ILI9341_WHITE);
tft_Menu.setTextSize(2);
tft_Menu.setCursor(10, 10);
tft_Menu.print("Erreur RTC !");
while (1); // Block execution if RTC not detected
}
// If RTC is not running (new or dead battery), set time to compile time
if (!rtc.isrunning()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// Check touchscreen controller initialization
if (!ctp.begin()) {
tft_Menu.fillScreen(ILI9341_RED);
tft_Menu.setTextColor(ILI9341_WHITE);
tft_Menu.setTextSize(2);
tft_Menu.setCursor(10, 50);
tft_Menu.print("Erreur Tactile !");
while (1); // Block execution if touchscreen not detected
}
// Draw initial screens
drawMainMenu(); // Draw main menu on menu screen
// Force full redraw of drawInfoScreen on startup
forceFullInfoRedraw = true;
drawInfoScreen(); // Draw info screen with time, day, and date on startup
}
// === Boucle principale ===
void loop() {
// Handle blinking fields in SET_TIME mode
if (millis() - lastBlinkTime > BLINK_INTERVAL) {
showBlink = !showBlink; // Toggle blink state
lastBlinkTime = millis();
if (currentScreen == SET_TIME) {
drawSetTimeScreen(); // Redraw set time screen to apply blinking
}
}
// Update info screen (time/date) only in MAIN_MENU mode and every second
// No full screen fill in drawInfoScreen anymore, so "sweeping" is minimized, only text changes.
if (millis() - lastUpdate > 1000 && currentScreen == MAIN_MENU) {
drawInfoScreen(); // Call to update time/date if necessary
lastUpdate = millis();
}
// Handle interactions based on current screen state
if (currentScreen == SET_TIME) {
updateSetTime(); // Call function to update time settings
}
// Detect touch in MAIN_MENU mode
if (currentScreen == MAIN_MENU && ctp.touched()) {
TS_Point p = ctp.getPoint(); // Get touch point
// Touch coordinates are often inverted or require rotation
// These transformations (p.y and 240-p.x) are based on a typical configuration.
// You may need to adjust them based on your screen and its calibration.
int x = p.y;
int y = 240 - p.x;
// If touch is within the "Set time/date" option area (between Y=30 and Y=70)
if (y > 30 && y < 70) {
currentScreen = SET_TIME; // Change screen state
tempTime = rtc.now(); // Initialize tempTime with current RTC time for modifications
// Force full redraw of set time page on entry
forceFullSetTimeRedraw = true;
drawSetTimeScreen(); // Dessine la page de réglage
selectedField = SET_HOUR; // Reset selection to "Hour" field
lastUpdate = millis(); // Reset timers for clean transition
lastBlinkTime = millis();
showBlink = true; // Ensure blinking field is visible
// Wait for touch release before continuing
while (ctp.touched()) {
delay(10);
}
}
}
}