#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>
#include <time.h>
#include <sys/time.h>
// === CONFIG OLED I2C ===
#define SDA_PIN 21
#define SCL_PIN 22
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// === RTC ===
RTC_DS3231 rtc;
bool externalRTC = false;
int h, m, s; // Variabili per contenere l'ora corrente
// === PIN BUZZER ===
const int buzzerPin = 23;
// === PIN RGB LED ===
const int ledR = 25;
const int ledG = 26;
const int ledB = 27;
// === PULSANTI (ROSSO, VERDE, BLU, GIALLO) ===
const int buttonPins[4] = {32, 33, 34, 35};
// === VARIABILI DI GIOCO ===
unsigned long gameDuration = 0;
unsigned long gameStart;
bool gameOver = false;
int numTeams = 0;
int currentTeam = -1;
unsigned long teamStartTime = 0;
// === reset giocata bandiera ===
bool roundOver = false;
// === Punteggio Team ===
int score[4] = {0, 0, 0, 0};
// === Tempo dei Team ===
unsigned long activeTime[4] = {0, 0, 0, 0};
// === GAME MODE ===
enum GameMode { Mantenimento, Punteggio,Bandiera };
GameMode gameMode = Mantenimento;
// === BLINKING / HOLD ===
bool holding = false;
int holdTeam = -1;
unsigned long holdStart = 0;
// === RTC SCHEDULING ===
bool useRTCMode = false;
int startHour = 0, startMinute = 0;
int stopHour = 0, stopMinute = 0;
bool gameStarted = false;
// === NOMI SQUADRE ===
const char* teamNames[4] = {"ROSSO", "VERDE", "BLU", "GIALLO"};
// === FUNZIONI LED RGB ===
void setRGBColor(int team) {
digitalWrite(ledR, LOW);
digitalWrite(ledG, LOW);
digitalWrite(ledB, LOW);
switch (team) {
case 0: digitalWrite(ledR, HIGH); break;
case 1: digitalWrite(ledG, HIGH); break;
case 2: digitalWrite(ledB, HIGH); break;
case 3: digitalWrite(ledR, HIGH); digitalWrite(ledG, HIGH); break; // GIALLO
default: break;
}
}
// === RESET LED RGB ===
void clearRGB() {
digitalWrite(ledR, LOW);
digitalWrite(ledG, LOW);
digitalWrite(ledB, LOW);
}
// === FUNZIONI DISPLAY ===
void showMessage(String msg) {
display.clearDisplay();
display.setCursor(0, 0);
display.println(msg);
display.display();
}
// === Countdown acquisizione bandiera ===
void showCountdown(int seconds, const char* team) {
display.clearDisplay();
display.setCursor(0, 0);
display.println("Hold per catturare!");
display.print(team);
display.setCursor(0, 20);
display.print("Countdown: ");
display.print(seconds);
display.display();
}
// === FUNZIONE VISUALIZZAZIONE VINCITORE ===
void showResults(bool tie, int winner) {
display.clearDisplay();
display.setCursor(0, 0);
display.println(" === GARA FINITA ===");
display.setCursor(0, 9);
if (tie) display.print("PAREGGIO");
else {
display.print("Vince: ");
display.print(teamNames[winner]);
}
for (int i = 0; i < numTeams; i++) {
display.setCursor(0, 19 + i * 11);
display.print(teamNames[i]);
// Allineamenti come prima
if (i == 2) display.print(" P:");
else if (i == 3) display.print(" P:");
else display.print(" P:");
display.print(score[i]);
if (gameMode == Mantenimento) {
display.print(" T:");
unsigned long t = activeTime[i];
display.print(t / 1000);
display.print("s");
}
}
display.display();
}
// === SETUP ===
void setup() {
Serial.begin(115200);
// OLED
Wire.begin(SDA_PIN, SCL_PIN);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Display non trovato");
for (;;);
}
// Pulisci display
display.clearDisplay();
// setto dimensione testo
display.setTextSize(1);
// Impostazione colore display
display.setTextColor(SSD1306_WHITE);
// LED
pinMode(ledR, OUTPUT);
pinMode(ledG, OUTPUT);
pinMode(ledB, OUTPUT);
clearRGB();
// Pulsanti
for (int i = 0; i < 4; i++) pinMode(buttonPins[i], INPUT_PULLUP);
// Buzzer
pinMode(buzzerPin, OUTPUT);
digitalWrite(buzzerPin, LOW);
// RTC
if (rtc.begin()) {
// RTC esterno
externalRTC = true;
Serial.println("Modulo RTC trovato");
} else {
// RTC interno
externalRTC = false;
Serial.println("Modulo RTC non trovato");
}
if (externalRTC) {
// Mostra ora e data RTC esterno e chiedi conferma
DateTime now = rtc.now();
bool confirmed = false;
Serial.print("Data: "); if(now.day()<10) Serial.print("0"); Serial.print(now.day()); Serial.print("/"); Serial.print(now.month()); Serial.print("/"); Serial.println(now.year());
Serial.print("Ora: "); if(now.hour()<10) Serial.print("0"); Serial.print(now.hour()); Serial.print(":"); if(now.minute()<10) Serial.print("0"); Serial.print(now.minute()); Serial.print(":"); if(now.second()<10) Serial.print("0"); Serial.println(now.second());
while (!confirmed) {
display.clearDisplay();
display.setCursor(0,0);
display.println("RTC esterno trovato");
display.println("");
display.print("Data: "); if(now.day()<10) display.print("0"); display.print(now.day()); display.print("/"); display.print(now.month()); display.print("/"); display.println(now.year());
display.print("Ora: "); if(now.hour()<10) display.print("0"); display.print(now.hour()); display.print(":"); if(now.minute()<10) display.print("0"); display.print(now.minute()); display.print(":"); if(now.second()<10) display.print("0"); display.println(now.second());
display.println("");
display.println("ROSSO=Modifica");
display.println("GIALLO=Conferma");
display.display();
if (digitalRead(buttonPins[3])==LOW) { confirmed=true; delay(200); } // GIALLO
if (digitalRead(buttonPins[0])==LOW) { // ROSSO → modifica
setRTCDateTime(&now);
confirmed = true;
}
}
} else {
showMessage("RTC esterno assente!");
delay(1500);
DateTime now = DateTime(2025,1,1,0,0,0);
setRTCDateTime(&now); // impostazione manuale
}
// === MENU MODALITA' DI GIOCO ===
showMessage("Seleziona modalita':\n\nRosso=Mantenimento\nVerde=Punteggio\nBlu=Bandiera");
bool modeSelected = false;
while(!modeSelected) {
if (digitalRead(buttonPins[0]) == LOW) { gameMode = Mantenimento; modeSelected = true; delay(200); }
if (digitalRead(buttonPins[1]) == LOW) { gameMode = Punteggio; modeSelected = true; delay(200); }
if (digitalRead(buttonPins[2]) == LOW) { gameMode = Bandiera; modeSelected = true; delay(200); }
}
showMessage(String("Mod.: ") + (gameMode==Mantenimento ? "Mantenimento" : (gameMode==Punteggio ? "Punteggio" : "Bandiera")));
delay(1000);
if (gameMode == Bandiera) {
numTeams = 1;
showMessage("Modalita' Bandiera\nTeam: ROSSO");
delay(1500);
} else {
// menu squadre come già presente
// === MENU SQUADRE ===
showMessage("Seleziona num squadre\n\nRosso=2\nVerde=3\nBlu=4");
while (numTeams == 0) {
if (digitalRead(buttonPins[0]) == LOW) numTeams = 2;
if (digitalRead(buttonPins[1]) == LOW) numTeams = 3;
if (digitalRead(buttonPins[2]) == LOW) numTeams = 4;
delay(100);
}
showMessage("Squadre: " + String(numTeams));
delay(1000);
}
// === MENU TEMPO ===
showMessage("Seleziona tempo:\n\nRosso=Prefissato\nVerde=Imposta manuale");
bool selected = false;
while (!selected) {
if (digitalRead(buttonPins[0]) == LOW) { selected=true; useRTCMode=false; delay(200); }
if (digitalRead(buttonPins[1]) == LOW) { selected=true; useRTCMode=true; delay(200); }
}
if (!useRTCMode) {
showMessage("Tempo:\n\nRosso=10 min\nVerde=15 min\nBlu=20 min\nGiallo=Personalizzato");
bool done=false;
while (!done) {
if (digitalRead(buttonPins[0])==LOW){gameDuration=10*60000UL; done=true;}
if (digitalRead(buttonPins[1])==LOW){gameDuration=15*60000UL; done=true;}
if (digitalRead(buttonPins[2])==LOW){gameDuration=20*60000UL; done=true;}
if (digitalRead(buttonPins[3])==LOW){setPresetPersonalizzato(); done=true;}
delay(100);
}
gameStart=millis();
} else {
if (!externalRTC) {
showMessage("RTC interno.");
delay(1500);
}
setStartStopTime();
}
}
// === LOOP (VERSIONE FINALE CORRETTA) ===
void loop() {
if (gameOver) return;
if (gameMode == Bandiera && roundOver) {
if (digitalRead(buttonPins[3]) == LOW) { // GIALLO
// reset variabili per nuova giocata
roundOver = false;
gameOver = false;
holding = false;
holdTeam = -1;
currentTeam = -1;
clearRGB();
gameStart = millis(); // riparte il timer
showMessage("Gara iniziata!");
delay(1000);
}
showMessage("Premi il tasto GIALLO\nper riavvio partita");
return; // finché non si preme giallo, non esegue altro
}
if (useRTCMode) {
//int h, m, s; // Variabili per contenere l'ora corrente
if (externalRTC) {
// Se l'RTC esterno è presente, leggiamo da quello
DateTime now = rtc.now();
h = now.hour();
m = now.minute();
s = now.second();
} else {
// Altrimenti, usiamo l'orologio interno dell'ESP32
time_t now_time;
time(&now_time);
struct tm* timeinfo = localtime(&now_time);
h = timeinfo->tm_hour;
m = timeinfo->tm_min;
s = timeinfo->tm_sec;
}
// --- LOGICA DI STATO (invariata ma ora usa l'ora corretta) ---
// 1. Controlla se è ora di INIZIARE la partita
if (!gameStarted && (h > startHour || (h == startHour && m >= startMinute))) {
gameStarted = true;
gameStart = millis();
showMessage("Gara iniziata!");
delay(1000);
}
// 2. Controlla se è ora di FINIRE la partita
if (gameStarted && (h > stopHour || (h == stopHour && m >= stopMinute))) {
endGame();
return;
}
// --- GESTIONE DISPLAY E LOGICA ---
if (!gameStarted) {
// 3a. Se la partita non è ancora iniziata, MOSTRA IL COUNTDOWN
display.clearDisplay();
display.setCursor(0, 0);
display.println("Inizio gara tra:");
long nowSeconds = h * 3600L + m * 60L + s; // Ora usiamo anche i secondi 's'
long startSeconds = startHour * 3600L + startMinute * 60L;
long secondsLeft = startSeconds - nowSeconds;
if (secondsLeft < 0) {
secondsLeft += 24 * 3600L; // Gestisce il caso a cavallo della mezzanotte
}
int minLeft = secondsLeft / 60;
int secLeft = secondsLeft % 60;
display.setCursor(0, 20);
display.setTextSize(2);
display.print(minLeft);
display.print(":");
if (secLeft < 10) display.print("0");
display.print(secLeft);
display.setTextSize(1);
display.display();
delay(200);
} else {
// 3b. Se la partita è iniziata, esegui la normale logica di gioco
handleButtons();
handleHold();
if (holding == false) {
updateDisplay();
}
}
} else {
// Modalità a durata fissa (rimane invariata)
unsigned long elapsed = millis() - gameStart;
if (elapsed >= gameDuration) {
endGame();
return;
}
handleButtons();
handleHold();
if (holding == false) {
updateDisplay();
}
}
}
// === FUNZIONI DI GESTIONE ===
void handleButtons() {
if (gameMode == Mantenimento || gameMode == Punteggio) {
// logica già esistente
for (int i=0; i<numTeams; i++) {
if (digitalRead(buttonPins[i]) == LOW) {
if (i != currentTeam && !holding) {
holding = true;
holdTeam = i;
holdStart = millis();
}
}
}
} else if (gameMode == Bandiera) {
if (digitalRead(buttonPins[0]) == LOW && !holding) { // solo ROSSO
holding = true;
holdTeam = 0;
holdStart = millis();
}
}
}
void handleHold() {
if (!holding) return;
unsigned long elapsed = millis() - holdStart;
int secondsLeft = 5 - (elapsed / 1000);
if (elapsed < 5000) {
if ((elapsed/500)%2==0) setRGBColor(holdTeam);
else clearRGB();
showCountdown(secondsLeft, teamNames[holdTeam]);
if (digitalRead(buttonPins[holdTeam]) == HIGH) {
holding = false; holdTeam = -1; clearRGB();
}
} else {
if (gameMode == Mantenimento) {
// logica originale
if (currentTeam != -1) activeTime[currentTeam] += millis() - teamStartTime;
currentTeam = holdTeam; teamStartTime = millis(); score[currentTeam]++;
setRGBColor(currentTeam);
digitalWrite(buzzerPin, HIGH);
delay(200);
digitalWrite(buzzerPin, LOW);
} else if (gameMode == Punteggio) {
score[holdTeam]++;
setRGBColor(holdTeam);
clearRGB();
for (int i=0; i<3; i++) {
digitalWrite(buzzerPin, HIGH); delay(1500);
digitalWrite(buzzerPin, LOW); delay(500);
}
} else if (gameMode == Bandiera) {
// vittoria immediata
roundOver = true;
showMessage("Bandiera acquisita!\nVince ROSSO");
for (int i=0; i<3; i++) {
digitalWrite(buzzerPin, HIGH); delay(1500);
digitalWrite(buzzerPin, LOW); delay(500);
}
}
holding = false; holdTeam = -1;
}
}
void updateDisplay() {
display.clearDisplay();
// Intestazione tempo: invariata
if (!useRTCMode) {
unsigned long timeLeft = (gameDuration - (millis() - gameStart)) / 1000;
int minLeft = timeLeft / 60;
int secLeft = timeLeft % 60;
display.setCursor(0,0);
display.print("Tempo: "); display.print(minLeft); display.print(":"); if (secLeft < 10) display.print("0"); display.print(secLeft);
} else {
display.setCursor(0,0);
display.print("Fine:");
display.print(stopHour); display.print(":"); if (stopMinute < 10) display.print("0"); display.print(stopMinute);
display.print(" Ora:");
display.print(h); display.print(":"); if (m < 10) display.print("0"); display.print(m);
}
// Corpo: punteggio vs mantenimento
for (int i=0; i<numTeams; i++) {
display.setCursor(0,19 + i * 11);
display.print(teamNames[i]);
display.print(" P:");
display.print(score[i]);
if (gameMode == Mantenimento) {
display.print(" T:");
unsigned long t = activeTime[i];
if (i == currentTeam) t += millis() - teamStartTime;
display.print(t/1000);
display.print("s");
}
}
display.display();
}
void endGame() {
if (gameMode == Bandiera) {
roundOver = true;
showMessage("Tempo scaduto!\nBandiera nn acquisita");
} else {
gameOver = true;
if (gameMode == Mantenimento && currentTeam != -1) {
activeTime[currentTeam] += millis() - teamStartTime;
}
clearRGB();
int winner = -1;
bool tie = false;
if (gameMode == Mantenimento) {
// Logica originale con tie-break su tempo attivo
for (int i=0; i<numTeams; i++) {
if (winner == -1) winner = i;
else {
if (score[i] > score[winner]) { winner = i; tie = false; }
else if (score[i] == score[winner]) {
if (activeTime[i] > activeTime[winner]) { winner = i; tie = false; }
else if (activeTime[i] == activeTime[winner]) tie = true;
}
}
}
} else {
// Punteggio: solo score
for (int i=0; i<numTeams; i++) {
if (winner == -1) winner = i;
else {
if (score[i] > score[winner]) { winner = i; tie = false; }
else if (score[i] == score[winner]) {
tie = (score[i] == score[winner]); // se c'è parità, tie rimane true
}
}
}
}
showResults(tie, winner);
}
// Buzzer finale: invariato
for (int i=0; i<3; i++) {
digitalWrite(buzzerPin, HIGH); delay(500);
digitalWrite(buzzerPin, LOW); delay(500);
digitalWrite(buzzerPin, HIGH); delay(500);
digitalWrite(buzzerPin, LOW); delay(500);
}
}
// === FUNZIONI RTC MANUALE ===
void setRTCDateTime(DateTime* dt) {
int step=0;
int hr=dt->hour(), mn=dt->minute(), sc=dt->second();
int day=dt->day(), mon=dt->month(), yr=dt->year();
while(step<6){
display.clearDisplay();
display.setCursor(0,0);
switch(step){
case 0: display.println("Imposta ORE\n"); break;
case 1: display.println("Imposta MINUTI\n"); break;
case 2: display.println("Imposta SECODI\n"); break;
case 3: display.println("Imposta GIORNO\n"); break;
case 4: display.println("Imposta MESE\n"); break;
case 5: display.println("Imposta ANNO\n"); break;
}
switch(step){
case 0: display.print(hr); break;
case 1: display.print(mn); break;
case 2: display.print(sc); break;
case 3: display.print(day); break;
case 4: display.print(mon); break;
case 5: display.print(yr); break;
}
display.print("\n\n");
display.println("Rosso: +1");
display.println("Verde: -1");
display.println("Giallo: OK");
display.display();
if(digitalRead(buttonPins[0])==LOW){ // ROSSO incrementa
switch(step){ case 0: hr++; if(hr>23) hr=0; break;
case 1: mn++; if(mn>59) mn=0; break;
case 2: sc++; if(sc>59) sc=0; break;
case 3: day++; if(day>31) day=1; break;
case 4: mon++; if(mon>12) mon=1; break;
case 5: yr++; if(yr>2200) yr=2000; break;
}
delay(200);
}
if(digitalRead(buttonPins[1])==LOW){ // VERDE decrementa
switch(step){ case 0: hr--; if(hr<0) hr=23; break;
case 1: mn--; if(mn<0) mn=59; break;
case 2: sc--; if(sc<0) sc=59; break;
case 3: day--; if(day<1) day=31; break;
case 4: mon--; if(mon<1) mon=12; break;
case 5: yr--; if(yr<2000) yr=2200; break;
}
delay(200);
}
if(digitalRead(buttonPins[3])==LOW){ step++; delay(200);} // GIALLO conferma e passa
}
if (externalRTC) {
rtc.adjust(DateTime(yr,mon,day,hr,mn,sc));
} else {
struct tm t;
t.tm_year = yr - 1900;
t.tm_mon = mon - 1;
t.tm_mday = day;
t.tm_hour = hr;
t.tm_min = mn;
t.tm_sec = sc;
t.tm_isdst = 0;
time_t tt = mktime(&t);
struct timeval tv;
tv.tv_sec = tt;
tv.tv_usec = 0;
settimeofday(&tv, NULL); // Imposta l'orologio interno ESP32
}
}
// === FUNZIONE START/STOP RTC (MODIFICATA) ===
void setStartStopTime() {
int step=0;
startHour=0; startMinute=0; stopHour=0; stopMinute=0;
while(step<4){
display.clearDisplay();
display.setCursor(0,0);
switch(step){
case 0: display.println("Imposta START\n"); break;
case 1: display.println("Imposta START\n"); break;
case 2: display.println("Imposta STOP\n"); break;
case 3: display.println("Imposta STOP\n"); break;
}
switch(step){
case 0: display.print("Ore: "); display.print(startHour); break;
case 1: display.print("Minuti: "); display.print(startMinute); break;
case 2: display.print("Ore: "); display.print(stopHour); break;
case 3: display.print("Minuti: "); display.print(stopMinute); break;
}
display.print("\n\n");
display.println("Rosso: +1");
display.println("Verde: -1");
display.println("Giallo: OK");
display.display();
if(digitalRead(buttonPins[0])==LOW){ // ROSSO incrementa
switch(step){ case 0: startHour++; if(startHour>23) startHour=0; break;
case 1: startMinute++; if(startMinute>59) startMinute=0; break;
case 2: stopHour++; if(stopHour>23) stopHour=0; break;
case 3: stopMinute++; if(stopMinute>59) stopMinute=0; break;
}
delay(200);
}
if(digitalRead(buttonPins[1])==LOW){ // VERDE decrementa
switch(step){ case 0: startHour--; if(startHour<0) startHour=23; break;
case 1: startMinute--; if(startMinute<0) startMinute=59; break;
case 2: stopHour--; if(stopHour<0) stopHour=23; break;
case 3: stopMinute--; if(stopMinute<0) stopMinute=59; break;
}
delay(200);
}
if(digitalRead(buttonPins[3])==LOW){ step++; delay(200);} // GIALLO conferma e passa
}
}
// === FUNZIONI Preset Personalizzato ===
void setPresetPersonalizzato() {
delay(200);
int step=0;
int minuti_gioco=0;
while(step<1){
display.clearDisplay();
display.setCursor(0,0);
switch(step){
case 0: display.println("Imposta MINUTI\n"); break;
}
switch(step){
case 0: display.print(minuti_gioco); break;
}
display.print("\n\n");
display.println("Rosso: +1");
display.println("Verde: -1");
display.println("Giallo: OK");
display.display();
if(digitalRead(buttonPins[0])==LOW){ // ROSSO incrementa
switch(step){
case 0: minuti_gioco++; break;
}
delay(200);
}
if(digitalRead(buttonPins[1])==LOW){ // VERDE decrementa
switch(step){
case 0: minuti_gioco--; break;
}
delay(200);
}
if(digitalRead(buttonPins[3])==LOW){ step++; delay(200);} // GIALLO conferma e passa
}
gameDuration=minuti_gioco*60000UL;
}