Files
2025-08-15 16:22:58 +02:00

336 lines
10 KiB
C++

/**
* ESP32 P10 RGB Fullscreen Countdown
* Compte à rebours plein écran en vert
*
* Ce programme affiche un compte à rebours en plein écran avec :
* - Chiffres de grande taille (en vert)
* - Occupation de toute la largeur de l'écran
* - Compatible avec toutes les configurations de cascade
*
* Auteur: GitHub Copilot
* Date: Août 2025
*/
#define PxMATRIX_SPI_FREQUENCY 10000000
#include <Arduino.h>
#include <PxMatrix.h>
#include <RTClib.h>
#include <Fonts/FreeSansBold9pt7b.h> // Police plus grande pour le compte à rebours
// Pins pour la matrice LED
#define P_LAT 5
#define P_A 19
#define P_B 23
#define P_C 18
#define P_OE 4
// Configuration des panneaux - définie par les build flags
#ifndef MATRIX_WIDTH
#define MATRIX_WIDTH 32
#endif
#ifndef MATRIX_HEIGHT
#define MATRIX_HEIGHT 16
#endif
#ifndef MATRIX_PANELS_X
#define MATRIX_PANELS_X 3
#endif
#ifndef MATRIX_PANELS_Y
#define MATRIX_PANELS_Y 1
#endif
// Calcul des dimensions totales
#define TOTAL_WIDTH (MATRIX_WIDTH * MATRIX_PANELS_X)
#define TOTAL_HEIGHT (MATRIX_HEIGHT * MATRIX_PANELS_Y)
// Configuration du timer
hw_timer_t * timer = nullptr;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
// Temps d'affichage ajusté selon le nombre de panneaux
uint8_t display_draw_time = (MATRIX_PANELS_X * MATRIX_PANELS_Y > 4) ? 20 : 30;
// Objet matrice
PxMATRIX display(TOTAL_WIDTH, TOTAL_HEIGHT, P_LAT, P_OE, P_A, P_B, P_C);
// RTC
RTC_DS3231 rtc;
// Couleurs
uint16_t myGREEN = display.color565(0, 255, 0); // Vert vif pour le compte à rebours
uint16_t myDARKGREEN = display.color565(0, 150, 0); // Vert foncé pour les séparateurs
uint16_t myBLACK = display.color565(0, 0, 0);
uint16_t myRED = display.color565(255, 0, 0); // Rouge pour les dernières secondes
// Variables pour le countdown
DateTime countdownTarget;
bool countdownExpired = false;
bool blinkLastSeconds = false; // Clignotement pour les 10 dernières secondes
bool blinkState = true;
unsigned long lastBlinkTime = 0;
const int BLINK_INTERVAL = 500; // Intervalle de clignotement 0.5 seconde
// Format d'affichage (0=jours, 1=heures, 2=minutes, 3=secondes uniquement)
int displayFormat = 0;
// Gestionnaire d'interruption pour l'affichage
void IRAM_ATTR display_updater() {
portENTER_CRITICAL_ISR(&timerMux);
display.display(display_draw_time);
portEXIT_CRITICAL_ISR(&timerMux);
}
// Activation du timer d'affichage
void display_update_enable() {
timer = timerBegin(0, 80, true);
if (timer != NULL) {
timerAttachInterrupt(timer, &display_updater, true);
timerAlarmWrite(timer, 1500, true);
timerAlarmEnable(timer);
Serial.println("Display timer enabled successfully");
} else {
Serial.println("ERROR: Failed to create display timer");
}
}
// Calcul du temps restant
void updateCountdown(int &days, int &hours, int &minutes, int &seconds) {
DateTime now = rtc.now();
// Vérifier si le countdown est expiré
if (now >= countdownTarget) {
countdownExpired = true;
days = hours = minutes = 0;
seconds = 0;
return;
}
countdownExpired = false;
// Calculer la différence
TimeSpan diff = countdownTarget - now;
long totalSeconds = diff.totalseconds();
days = totalSeconds / 86400;
hours = (totalSeconds % 86400) / 3600;
minutes = (totalSeconds % 3600) / 60;
seconds = totalSeconds % 60;
// Activer le clignotement pour les 10 dernières secondes
blinkLastSeconds = (days == 0 && hours == 0 && minutes == 0 && seconds <= 10);
}
// Détermine le format d'affichage en fonction du temps restant
void updateDisplayFormat(int days, int hours, int minutes, int seconds) {
if (days > 0) {
displayFormat = 0; // Format jours
} else if (hours > 0) {
displayFormat = 1; // Format heures
} else if (minutes > 0) {
displayFormat = 2; // Format minutes
} else {
displayFormat = 3; // Format secondes uniquement
}
}
// Fonction pour obtenir la largeur du texte
uint16_t getTextWidth(const char* text, const GFXfont* font = NULL) {
int16_t x1, y1;
uint16_t w, h;
if (font) display.setFont(font);
display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
display.setFont(); // Reset to default font
return w;
}
// Dessine les deux points du séparateur d'horloge
void drawColon(int16_t x, int16_t y, uint16_t colonColor) {
display.fillRect(x, y, 2, 2, colonColor);
display.fillRect(x, y+4, 2, 2, colonColor);
}
// Affichage du compte à rebours en plein écran
void displayFullscreenCountdown(int days, int hours, int minutes, int seconds) {
display.clearDisplay();
// Choisir la couleur
uint16_t countdownColor = myGREEN;
// Si on est dans les 10 dernières secondes et qu'il faut clignoter
if (blinkLastSeconds) {
unsigned long currentTime = millis();
if (currentTime - lastBlinkTime >= BLINK_INTERVAL) {
lastBlinkTime = currentTime;
blinkState = !blinkState;
}
if (!blinkState) {
countdownColor = myBLACK; // Faire clignoter en éteignant
} else {
countdownColor = myRED; // Clignoter en rouge
}
}
// Si le countdown est expiré
if (countdownExpired) {
display.setFont(); // Petite police pour le message expiré
display.setTextSize(1);
const char* expiredMsg = "TEMPS ECOULE!";
int16_t textWidth = getTextWidth(expiredMsg);
int16_t textX = (TOTAL_WIDTH - textWidth) / 2;
display.setCursor(textX, (TOTAL_HEIGHT - 8) / 2);
display.setTextColor(myRED);
display.print(expiredMsg);
return;
}
char countdownText[20];
int16_t textWidth, textX;
// Utiliser différents formats selon le temps restant
switch (displayFormat) {
case 0: // Format jours
sprintf(countdownText, "%dd %02d:%02d", days, hours, minutes);
display.setFont();
display.setTextSize(2);
textWidth = getTextWidth(countdownText);
textX = (TOTAL_WIDTH - textWidth) / 2;
display.setTextColor(countdownColor);
display.setCursor(textX, (TOTAL_HEIGHT - 16) / 2);
display.print(countdownText);
break;
case 1: // Format heures
sprintf(countdownText, "%02d:%02d:%02d", hours, minutes, seconds);
display.setFont();
display.setTextSize(2);
textWidth = getTextWidth(countdownText);
textX = (TOTAL_WIDTH - textWidth) / 2;
display.setTextColor(countdownColor);
display.setCursor(textX, (TOTAL_HEIGHT - 16) / 2);
display.print(countdownText);
break;
case 2: // Format minutes
sprintf(countdownText, "%02d:%02d", minutes, seconds);
display.setFont();
display.setTextSize(2);
textWidth = getTextWidth(countdownText);
textX = (TOTAL_WIDTH - textWidth) / 2;
display.setTextColor(countdownColor);
display.setCursor(textX, (TOTAL_HEIGHT - 16) / 2);
display.print(countdownText);
break;
case 3: // Format secondes uniquement
sprintf(countdownText, "%02d", seconds);
// Si l'écran est suffisamment grand et qu'on a une seule rangée de panneaux
if (TOTAL_WIDTH >= 64 && MATRIX_PANELS_Y == 1) {
display.setFont(&FreeSansBold9pt7b);
textWidth = getTextWidth(countdownText, &FreeSansBold9pt7b);
textX = (TOTAL_WIDTH - textWidth) / 2;
display.setTextColor(countdownColor);
display.setCursor(textX, TOTAL_HEIGHT - 4);
display.print(countdownText);
} else {
// Sinon utiliser une police standard mais plus grande
display.setFont();
display.setTextSize(2);
textWidth = getTextWidth(countdownText);
textX = (TOTAL_WIDTH - textWidth) / 2;
display.setTextColor(countdownColor);
display.setCursor(textX, (TOTAL_HEIGHT - 16) / 2);
display.print(countdownText);
}
break;
}
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n=== ESP32 P10 RGB FULLSCREEN COUNTDOWN ===");
Serial.printf("Configuration: %dx%d panels (%dx%d total resolution)\n",
MATRIX_PANELS_X, MATRIX_PANELS_Y, TOTAL_WIDTH, TOTAL_HEIGHT);
// Initialisation du RTC
Serial.println("Initializing RTC...");
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
while (1) delay(10);
}
Serial.println("RTC initialized successfully");
// Initialisation de l'affichage avec configuration P10 optimisée
display.begin(4); // 1/8 scan pour P10
display.setScanPattern(ZAGZIG);
display.setMuxPattern(BINARY);
const int muxdelay = 10; // Délai de multiplexage
display.setMuxDelay(muxdelay, muxdelay, muxdelay, muxdelay, muxdelay);
delay(100);
display_update_enable();
display.clearDisplay();
// Luminosité adaptée au nombre de panneaux
int brightness = 150;
if (MATRIX_PANELS_X > 2) brightness = 100;
if (MATRIX_PANELS_X > 4) brightness = 80;
if (MATRIX_PANELS_X > 6) brightness = 60;
display.setBrightness(brightness);
Serial.printf("Brightness set to: %d\n", brightness);
display.setTextWrap(false);
display.setRotation(0);
// Configurer le compte à rebours pour 1 minute dans le futur (test)
DateTime now = rtc.now();
countdownTarget = now + TimeSpan(0, 0, 1, 0); // +1 minute
Serial.println("Countdown target set to: ");
Serial.printf("%02d-%02d-%d %02d:%02d:%02d\n",
countdownTarget.day(), countdownTarget.month(), countdownTarget.year(),
countdownTarget.hour(), countdownTarget.minute(), countdownTarget.second());
// Message de démarrage
display.setTextColor(myGREEN);
display.setTextSize(1);
display.setCursor(0, 0);
display.print("COUNTDOWN");
display.setCursor(0, 8);
display.print("FULLSCREEN");
delay(2000);
display.clearDisplay();
Serial.println("Starting fullscreen countdown...");
}
void loop() {
// Mise à jour du countdown chaque 100ms pour une réactivité accrue
static unsigned long lastUpdate = 0;
unsigned long currentTime = millis();
if (currentTime - lastUpdate >= 100) {
lastUpdate = currentTime;
int days, hours, minutes, seconds;
updateCountdown(days, hours, minutes, seconds);
updateDisplayFormat(days, hours, minutes, seconds);
displayFullscreenCountdown(days, hours, minutes, seconds);
}
// Délai minimal pour éviter la saturation du CPU
delay(10);
}