/* Debounce software: lettura analogica con media mobile su 8 campioni e debounce di 250 ms prima di considerare una zona violata.
Fix scritte LCD: tutte le stringhe sono state verificate e accorciate dove necessario (es. "Allarme: DISATT" invece di "Allarme: DISATTIVATO").
Visualizzazione stato zone: ora mostra compatte le zone 1‑4 su due righe e il tamper sulla terza, utilizzando al massimo 20 caratteri per riga.
Aggiunta voce "DEOL Config" nel menu Impostazioni (opzione 8) – attualmente è un placeholder che mostra un messaggio e torna indietro, pronto per
future implementazioni.
Nessuna altra modifica al resto della logica.
*/
/*
GTEK LAB TECHNOLOGIES - #ISSUE: 11/03/2026
ANTIFURTO PRO5 GSM VERSIONE VER. 5.2.0 (STABLE)
SYSTEM VER.1.35
******** MODIFICATO: TAMPER 24H con ciclo continuo - Switch N.C. = OK, N.O. = VIOLATO ********
******** FIX: Durata sirene funzionante ********
******** FIX: Codice funziona sempre durante allarme/tamper ********
******** MODIFICHE ULTERIORI: ********
* Debounce analogico con media mobile (8 campioni) e tempo di stabilizzazione 250ms
* Fix scritte LCD >20 caratteri
* Layout stato zone compatto (2 zone per riga)
* Aggiunta voce "DEOL Config" nel menu Impostazioni (placeholder)
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <EEPROM.h>
#include "RTClib.h"
// ==================== DEFINIZIONE PIN ====================
// Tasti aggiuntivi
#define BTN_UP 30
#define BTN_DOWN 31
#define BTN_ENTER 32
#define BTN_ESC 33
// LED di stato
#define LED_RETE 34
#define LED_ZONA1 35
#define LED_ZONA2 36
#define LED_ZONA3 37
#define LED_ZONA4 38
#define LED_ZONA5 39
#define LED_ENTRATA 40
#define LED_USCITA 41
#define LED_ALLCORSO 42
#define LED_ALLINSERITO 43
// Ingressi analogici zone
#define ZONE_PIN_1 A0
#define ZONE_PIN_2 A1
#define ZONE_PIN_3 A2
#define ZONE_PIN_4 A3
#define ZONE_PIN_5 A4
// Rilevatore presenza 230V
#define RILEVATORE_230V A8
// Pin buzzer e relè
#define BUZZER_PIN 44
#define RELAY_USCITA 45
#define RELAY_SIRENA_TAMPER 46 // USCITA SIRENA INDIPENDENTE PER TAMPER
#define INTERRUTTORE_CHIAVE 47
// LED di lampeggio (pin libero)
#define PIN_LED 48
// ==================== COSTANTI PER FILTRAGGIO ====================
#define NUM_ZONES 5
#define SAMPLE_BUFFER_SIZE 8
#define DEBOUNCE_TIME 250 // ms
// Definizione strutture dati
struct Evento {
char descrizione[50];
DateTime timestamp;
};
struct ZoneConfig {
char nome[16];
bool abilitata;
bool ritardata;
bool tamper;
bool ventiquattro_ore;
int resistenza_min;
int resistenza_max;
int resistenza_nominale;
};
struct SystemConfig {
int tempo_entrata;
int tempo_uscita;
int durata_allarme;
int durata_sirena_tamper;
char numero_telefono[16];
char password[5];
bool notifica_rete;
bool notifica_allarme;
bool feedback_acustico;
unsigned int magic_number;
ZoneConfig zone[5];
};
// Variabili globali
LiquidCrystal_I2C lcd(0x27, 20, 4);
RTC_DS3231 rtc;
SystemConfig config;
// Tastiera 4x4
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {22, 23, 24, 25};
byte colPins[COLS] = {26, 27, 28, 29};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Variabili sistema
enum SystemState { DISINSERITO, IN_ENTRATA, IN_USCITA, ALLARME, ATTIVO_DOPO_DURATA };
SystemState systemState = DISINSERITO;
unsigned long timerAllarme = 0;
unsigned long timerEntrataUscita = 0;
bool rete230V_OK = true;
bool inMenu = false;
int menuSelection = 0;
bool inMenuPrincipale = false;
bool zoneViolate[4] = {false, false, false, false};
bool zoneInAllarme[4] = {false, false, false, false};
bool chiaveInseritaStato = false;
bool tutteZoneAperteVisualizzato = false;
// Variabili TAMPER indipendente
bool tamperViolato = false;
unsigned long tamperCicloStartTime = 0;
bool tamperSirenaAttiva = false;
bool tamperInCiclo = false;
// Variabili per gestione codice durante allarme
bool attesaCodice = false;
// Variabili per buzzer
int buzzerTone = 800;
unsigned long buzzerStartTime = 0;
bool buzzerActive = false;
int buzzerPattern = 0;
unsigned long lastBeepTime = 0;
// Variabili per scansione
unsigned long ultimoAggiornamentoDisplay = 0;
const unsigned long INTERVALLO_SCANSIONE_LENTA = 2000;
const unsigned long INTERVALLO_SCANSIONE_VELOCE = 100;
bool scansioneVeloce = false;
unsigned long tempoUscitaInizio = 0;
// Gestione eventi
#define MAX_EVENTI 50
Evento eventi[MAX_EVENTI];
int indiceEvento = 0;
bool eventiInizializzati = false;
// Magic number per verifica EEPROM
const unsigned int MAGIC_NUMBER_CONFIG = 0x4C4D;
// Variabili per la gestione del menu impostazioni
int impostazioneSelection = 0;
bool inMenuImpostazioni = false;
bool inSottoMenu = false;
// Variabili per la selezione delle funzioni
enum LampeggioStato {
LAMP_OFF,
LAMP_LENTO,
LAMP_VELOCE
};
LampeggioStato lampeggioStato = LAMP_OFF;
// ==================== STRUTTURA PER FILTRAGGIO ZONE ====================
struct ZoneFilter {
int readings[SAMPLE_BUFFER_SIZE];
int index;
long sum;
int average;
bool lastPotential; // ultimo stato potenziale (dall'ultimo campione)
bool currentViolated; // stato dopo debounce (usato da tutto il sistema)
unsigned long debounceTimer;
};
ZoneFilter zoneFilters[NUM_ZONES];
// ==================== FUNZIONI BASE ====================
void feedbackTasto() {
if(config.feedback_acustico) {
tone(BUZZER_PIN, 2000, 50);
}
}
// ==================== GESTIONE RILEVATORE 230V ====================
void aggiornaRilevatore230V() {
int valore230V = analogRead(RILEVATORE_230V);
bool retePresente = (valore230V > 100);
static unsigned long lastReteChange = 0;
const unsigned long DEBOUNCE_RETE = 5000;
if(retePresente != rete230V_OK) {
if(millis() - lastReteChange > DEBOUNCE_RETE) {
rete230V_OK = retePresente;
lastReteChange = millis();
if(!rete230V_OK && config.notifica_rete) {
memorizzaEvento("Manca rete 230V");
} else if(rete230V_OK) {
memorizzaEvento("Rete 230V ripristinata");
}
}
} else {
lastReteChange = millis();
}
digitalWrite(LED_RETE, rete230V_OK);
}
// ==================== GESTIONE EEPROM ====================
bool verificaIntegritaEEPROM() {
unsigned int magic;
EEPROM.get(0, magic);
return (magic == MAGIC_NUMBER_CONFIG);
}
void salvaConfigurazioneEEPROM() {
config.magic_number = MAGIC_NUMBER_CONFIG;
EEPROM.put(0, config);
memorizzaEvento("Configuraz. salvata");
}
void caricaConfigurazioneEEPROM() {
if(verificaIntegritaEEPROM()) {
EEPROM.get(0, config);
if(config.tempo_entrata < 0 || config.tempo_entrata > 300 ||
config.tempo_uscita < 0 || config.tempo_uscita > 300) {
caricaConfigurazioneDefault();
salvaConfigurazioneEEPROM();
}
} else {
caricaConfigurazioneDefault();
salvaConfigurazioneEEPROM();
}
}
// ==================== GESTIONE EVENTI ====================
void inizializzaEventi() {
if(!eventiInizializzati) {
for(int i = 0; i < MAX_EVENTI; i++) {
eventi[i].descrizione[0] = '\0';
}
eventiInizializzati = true;
memorizzaEvento("Sistema avviato");
}
}
void memorizzaEvento(const char* descrizione) {
inizializzaEventi();
DateTime now = rtc.now();
int slotLibero = -1;
for(int i = 0; i < MAX_EVENTI; i++) {
if(eventi[i].descrizione[0] == '\0') {
slotLibero = i;
break;
}
}
if(slotLibero == -1) {
slotLibero = indiceEvento;
indiceEvento = (indiceEvento + 1) % MAX_EVENTI;
}
strncpy(eventi[slotLibero].descrizione, descrizione, 49);
eventi[slotLibero].descrizione[49] = '\0';
eventi[slotLibero].timestamp = now;
}
// ==================== FUNZIONI BUZZER ====================
void gestioneBuzzer() {
if (!buzzerActive) {
noTone(BUZZER_PIN);
return;
}
unsigned long currentTime = millis();
switch(buzzerPattern) {
case 1:
if (currentTime - lastBeepTime >= 1000) {
tone(BUZZER_PIN, buzzerTone, 100);
lastBeepTime = currentTime;
}
break;
case 2:
if (currentTime - lastBeepTime >= 2000) {
tone(BUZZER_PIN, buzzerTone, 500);
lastBeepTime = currentTime;
}
break;
case 3:
tone(BUZZER_PIN, buzzerTone);
break;
case 4:
if (currentTime - lastBeepTime >= 500) {
tone(BUZZER_PIN, buzzerTone, 200);
lastBeepTime = currentTime;
}
break;
case 5:
if (currentTime - lastBeepTime >= 250) {
tone(BUZZER_PIN, buzzerTone, 100);
lastBeepTime = currentTime;
}
break;
default:
noTone(BUZZER_PIN);
break;
}
}
void attivaBuzzer(int pattern, int toneFreq) {
buzzerPattern = pattern;
buzzerTone = toneFreq;
buzzerActive = true;
lastBeepTime = millis() - 1000;
}
void disattivaBuzzer() {
buzzerActive = false;
buzzerPattern = 0;
noTone(BUZZER_PIN);
}
// ==================== INTERRUTTORE CHIAVE ====================
void gestioneInterruttoreChiave() {
static bool ultimoStatoStabile = LOW;
static unsigned long tempoUltimoCambio = 0;
static bool statoPrecedente = LOW;
const int debounceDelay = 50;
bool statoAttuale = digitalRead(INTERRUTTORE_CHIAVE);
if (statoAttuale != statoPrecedente) {
tempoUltimoCambio = millis();
statoPrecedente = statoAttuale;
}
if (millis() - tempoUltimoCambio > debounceDelay) {
if (statoAttuale != ultimoStatoStabile) {
if (statoAttuale == HIGH) {
chiaveInseritaStato = true;
if(config.feedback_acustico) {
tone(BUZZER_PIN, 2000, 200);
}
memorizzaEvento("Chiave inserita");
} else {
chiaveInseritaStato = false;
memorizzaEvento("Chiave rimossa");
if (systemState != DISINSERITO && !tamperViolato) {
disinserisciConChiave();
}
tone(BUZZER_PIN, 1500, 150);
}
ultimoStatoStabile = statoAttuale;
}
}
}
void disinserisciConChiave() {
systemState = DISINSERITO;
digitalWrite(LED_ALLCORSO, LOW);
digitalWrite(LED_ALLINSERITO, LOW);
digitalWrite(LED_ENTRATA, LOW);
digitalWrite(LED_USCITA, LOW);
digitalWrite(RELAY_USCITA, LOW);
for(int i=0; i<4; i++) {
zoneViolate[i] = false;
}
disattivaBuzzer();
tone(BUZZER_PIN, 2000, 150);
delay(200);
tone(BUZZER_PIN, 2500, 300);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("SISTEMA DISINSERITO");
lcd.setCursor(0,1);
lcd.print("CON CHIAVE");
delay(2000);
memorizzaEvento("Sistema DISINSERITO con CHIAVE");
}
// ==================== VERIFICA PASSWORD ====================
bool verificaPassword() {
lcd.clear();
lcd.setCursor(2,0);
lcd.print("INSERIRE CODICE");
lcd.setCursor(7,1);
lcd.print(">____");
lcd.setCursor(0,3);
lcd.print("ESC: per uscire");
char passwordInserita[5] = {0};
int posizione = 0;
while(posizione < 4) {
char key = keypad.getKey();
if(key >= '0' && key <= '9') {
passwordInserita[posizione] = key;
lcd.setCursor(8 + posizione, 1);
lcd.print('*');
posizione++;
if(config.feedback_acustico) tone(BUZZER_PIN, 2000, 30);
delay(300);
}
if(!digitalRead(BTN_ESC)) {
return false;
}
}
if(strcmp(passwordInserita, config.password) == 0) {
lcd.setCursor(2,2);
lcd.print("CODICE CORRETTO");
delay(500);
return true;
} else {
lcd.setCursor(3,2);
lcd.print("CODICE ERRATO!");
attivaBuzzer(2, 800);
delay(1500);
disattivaBuzzer();
return false;
}
}
// ==================== GESTIONE CODICE DURANTE ALLARME ====================
bool gestisciCodiceDuranteAllarme() {
attesaCodice = true;
bool risultato = verificaPassword();
attesaCodice = false;
if(risultato) {
// Disattiva tutto
systemState = DISINSERITO;
digitalWrite(RELAY_USCITA, LOW);
digitalWrite(RELAY_SIRENA_TAMPER, LOW);
tamperSirenaAttiva = false;
tamperInCiclo = false;
tamperViolato = false;
for(int i=0; i<4; i++) {
zoneViolate[i] = false;
}
disattivaBuzzer();
lcd.clear();
lcd.print(" DISINSERITO ");
lcd.setCursor(5,1);
lcd.print("CON CODICE");
delay(2000);
memorizzaEvento("Sistema disinserito con codice durante allarme");
}
return risultato;
}
// ==================== FILTRAGGIO ZONE (MEDIA MOBILE + DEBOUNCE) ====================
void inizializzaFiltriZone() {
for (int i = 0; i < NUM_ZONES; i++) {
zoneFilters[i].index = 0;
zoneFilters[i].sum = 0;
zoneFilters[i].average = 0;
zoneFilters[i].lastPotential = false;
zoneFilters[i].currentViolated = false;
zoneFilters[i].debounceTimer = 0;
for (int j = 0; j < SAMPLE_BUFFER_SIZE; j++) {
zoneFilters[i].readings[j] = 0;
}
}
}
void aggiornaFiltriZone() {
for (int i = 0; i < NUM_ZONES; i++) {
int raw = analogRead(A0 + i);
// aggiorna buffer circolare e somma
zoneFilters[i].sum -= zoneFilters[i].readings[zoneFilters[i].index];
zoneFilters[i].readings[zoneFilters[i].index] = raw;
zoneFilters[i].sum += raw;
zoneFilters[i].index = (zoneFilters[i].index + 1) % SAMPLE_BUFFER_SIZE;
zoneFilters[i].average = zoneFilters[i].sum / SAMPLE_BUFFER_SIZE;
// determina se il valore medio è fuori range (potenziale violazione)
bool outOfRange = (zoneFilters[i].average < config.zone[i].resistenza_min ||
zoneFilters[i].average > config.zone[i].resistenza_max);
// logica di debounce
if (outOfRange != zoneFilters[i].lastPotential) {
zoneFilters[i].debounceTimer = millis();
zoneFilters[i].lastPotential = outOfRange;
}
if (millis() - zoneFilters[i].debounceTimer >= DEBOUNCE_TIME) {
zoneFilters[i].currentViolated = zoneFilters[i].lastPotential;
}
}
}
// ==================== FUNZIONI ZONE 1-4 ====================
bool verificaZoneChiuse() {
for(int i = 0; i < 4; i++) {
if(config.zone[i].abilitata) {
if (zoneFilters[i].currentViolated) {
return false;
}
}
}
return true;
}
void avviaSequenzaUscita() {
if (!chiaveInseritaStato) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("INSERIRE LA CHIAVE");
delay(1500);
return;
}
if(!verificaZoneChiuse()) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("ZONE APERTE!");
delay(1500);
return;
}
systemState = IN_USCITA;
timerEntrataUscita = millis();
tempoUscitaInizio = millis();
attivaBuzzer(5, 1000);
int tempoRimasto = config.tempo_uscita;
unsigned long lastSecond = 0;
while(tempoRimasto > 0 && systemState == IN_USCITA) {
unsigned long currentMillis = millis();
if(currentMillis - lastSecond >= 1000) {
lastSecond = currentMillis;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("USCITA IN CORSO");
lcd.setCursor(0,1);
lcd.print("Tempo: ");
lcd.print(tempoRimasto);
lcd.print("s");
lcd.setCursor(0,2);
lcd.print("Zone: ");
for(int i=0; i<4; i++) {
if(config.zone[i].abilitata) {
if (!zoneFilters[i].currentViolated) {
lcd.print(i+1);
} else {
lcd.print("*");
}
}
}
lcd.setCursor(0,3);
lcd.print("ESC per annullare");
tempoRimasto--;
}
digitalWrite(LED_USCITA, (millis() % 250) < 125);
if(!verificaZoneChiuse()) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("ZONA APERTA!");
lcd.setCursor(0,1);
lcd.print("Uscita annullata");
delay(2000);
systemState = DISINSERITO;
digitalWrite(LED_USCITA, LOW);
disattivaBuzzer();
return;
}
if(!digitalRead(BTN_ESC)) {
systemState = DISINSERITO;
digitalWrite(LED_USCITA, LOW);
disattivaBuzzer();
lcd.clear();
lcd.print("Uscita annullata");
delay(1000);
return;
}
gestioneBuzzer();
}
if(systemState == IN_USCITA) {
systemState = ALLARME;
digitalWrite(LED_ALLINSERITO, HIGH);
digitalWrite(LED_USCITA, LOW);
disattivaBuzzer();
memorizzaEvento("ALLARME INSERITO");
lcd.clear();
lcd.setCursor(0,0);
lcd.print("ALLARME INSERITO");
lcd.setCursor(0,1);
lcd.print("Sistema ATTIVO");
delay(2000);
}
}
void monitoraggioZone() {
if(systemState != ALLARME && systemState != ATTIVO_DOPO_DURATA) return;
for(int i=0; i<4; i++) {
if(config.zone[i].abilitata && !zoneViolate[i]) {
if (zoneFilters[i].currentViolated) {
zoneViolate[i] = true;
char buffer[50];
snprintf(buffer, sizeof(buffer), "Violazione Zona %d", i+1);
memorizzaEvento(buffer);
if(config.zone[i].ritardata && systemState != IN_ENTRATA) {
avviaSequenzaEntrata();
} else {
attivaAllarme(buffer);
}
break;
}
}
}
}
void attivaAllarme(const char* motivo) {
if(systemState == ALLARME) return;
systemState = ALLARME;
timerAllarme = millis();
attivaBuzzer(3, 1500);
digitalWrite(RELAY_USCITA, HIGH);
memorizzaEvento(motivo);
lcd.clear();
lcd.setCursor(1,0);
lcd.print("ALLARME IN CORSO!");
int riga = 1;
for(int i=0; i<4; i++) {
if(zoneViolate[i]) {
lcd.setCursor(0, riga++);
lcd.print("Zona ");
lcd.print(i+1);
}
}
delay(2000);
}
void avviaSequenzaEntrata() {
if(systemState == IN_ENTRATA) return;
systemState = IN_ENTRATA;
timerEntrataUscita = millis();
attivaBuzzer(4, 1200);
int tempoRimasto = config.tempo_entrata;
unsigned long lastSecond = 0;
while(tempoRimasto > 0 && systemState == IN_ENTRATA) {
unsigned long currentMillis = millis();
if(currentMillis - lastSecond >= 1000) {
lastSecond = currentMillis;
lcd.clear();
lcd.setCursor(2,0);
lcd.print("ENTRATA IN CORSO");
lcd.setCursor(0,1);
lcd.print("Tempo: ");
lcd.print(tempoRimasto);
lcd.print("s");
lcd.setCursor(2,2);
lcd.print("Inserire codice");
lcd.setCursor(0,3);
lcd.print("# per disinserire");
tempoRimasto--;
}
digitalWrite(LED_ENTRATA, (millis() % 500) < 250);
// Controllo se viene inserito il codice durante l'entrata
char key = keypad.getKey();
if(key == '#') {
if(verificaPassword()) {
systemState = DISINSERITO;
digitalWrite(LED_ENTRATA, LOW);
disattivaBuzzer();
lcd.clear();
lcd.print(" DISINSERITO ");
lcd.setCursor(5,1);
lcd.print("CON CODICE");
delay(2000);
return;
}
}
gestioneBuzzer();
}
if(systemState == IN_ENTRATA) {
attivaAllarme("Tempo entrata scaduto");
}
}
void gestioneStatiAllarme() {
// Gestione allarme zone 1-4
if(systemState == ALLARME && config.durata_allarme > 0) {
unsigned long elapsed = millis() - timerAllarme;
unsigned long durataMs = (unsigned long)config.durata_allarme * 1000UL;
if(elapsed >= durataMs) {
digitalWrite(RELAY_USCITA, LOW);
systemState = ATTIVO_DOPO_DURATA;
memorizzaEvento("Allarme silenziato - sistema attivo");
// Non disattiviamo il buzzer perché potrebbe essere ancora attivo il tamper
if(!tamperSirenaAttiva) {
disattivaBuzzer();
}
}
}
}
// ==================== GESTIONE TAMPER 24H (N.C. = OK, N.O. = VIOLATO) ====================
void monitoraggioTamper() {
if(!config.zone[4].abilitata) return;
bool violato = zoneFilters[4].currentViolated; // già filtrato
// Rilevamento violazione TAMPER
if(violato && !tamperViolato) {
tamperViolato = true;
tamperInCiclo = true;
tamperCicloStartTime = millis();
tamperSirenaAttiva = true;
digitalWrite(RELAY_SIRENA_TAMPER, HIGH);
attivaBuzzer(2, 2000); // Pattern diverso per tamper
memorizzaEvento("TAMPER 24H VIOLATO");
lcd.clear();
lcd.setCursor(4,1);
lcd.print("TAMPER 24H!");
lcd.setCursor(6,2);
lcd.print("VIOLATO");
delay(2000);
}
// Rilevamento ripristino TAMPER
if(!violato && tamperViolato) {
tamperViolato = false;
tamperInCiclo = false;
tamperSirenaAttiva = false;
digitalWrite(RELAY_SIRENA_TAMPER, LOW);
// Se non c'è allarme zone, disattiva buzzer
if(systemState != ALLARME) {
disattivaBuzzer();
}
memorizzaEvento("TAMPER 24H RIPRISTINATO");
}
// Gestione ciclo continuo TAMPER
if(tamperInCiclo && config.durata_sirena_tamper > 0) {
unsigned long elapsed = millis() - tamperCicloStartTime;
unsigned long durataMs = (unsigned long)config.durata_sirena_tamper * 1000UL;
if(elapsed >= durataMs) {
// Spegni sirena tamper
tamperSirenaAttiva = false;
digitalWrite(RELAY_SIRENA_TAMPER, LOW);
// Se ancora violato, riparte immediatamente
if(tamperViolato) {
tamperCicloStartTime = millis();
tamperSirenaAttiva = true;
digitalWrite(RELAY_SIRENA_TAMPER, HIGH);
memorizzaEvento("TAMPER - Nuovo ciclo");
}
}
}
}
// ==================== FUNZIONI AUSILIARIE ====================
void aggiornaLED() {
// LED zone 1-4
for(int i=0; i<4; i++) {
int ledPin = LED_ZONA1 + i;
if(systemState == ALLARME || systemState == ATTIVO_DOPO_DURATA) {
if(zoneViolate[i]) {
digitalWrite(ledPin, (millis() % 300) < 150);
} else if(config.zone[i].abilitata) {
if (!zoneFilters[i].currentViolated) {
digitalWrite(ledPin, HIGH);
} else {
digitalWrite(ledPin, LOW);
}
} else {
digitalWrite(ledPin, LOW);
}
} else {
digitalWrite(ledPin, LOW);
}
}
// LED TAMPER (zona 5)
if(tamperViolato) {
digitalWrite(LED_ZONA5, (millis() % 300) < 150);
} else if(config.zone[4].abilitata) {
if (!zoneFilters[4].currentViolated) {
digitalWrite(LED_ZONA5, HIGH);
} else {
digitalWrite(LED_ZONA5, LOW);
}
} else {
digitalWrite(LED_ZONA5, LOW);
}
}
void aggiornaLEDStati() {
digitalWrite(LED_RETE, rete230V_OK);
digitalWrite(LED_ALLINSERITO, systemState == ALLARME || systemState == ATTIVO_DOPO_DURATA);
digitalWrite(LED_ALLCORSO, (systemState == ALLARME && buzzerPattern == 3) || (tamperSirenaAttiva && buzzerPattern == 2));
if(systemState != IN_ENTRATA) digitalWrite(LED_ENTRATA, LOW);
if(systemState != IN_USCITA) digitalWrite(LED_USCITA, LOW);
}
void aggiornaDisplay() {
static unsigned long lastUpdate = 0;
if(millis() - lastUpdate < 1000) return;
lastUpdate = millis();
if(!inMenu && !inMenuPrincipale && !inMenuImpostazioni && !attesaCodice) {
lcd.clear();
if(systemState == DISINSERITO) {
lcd.setCursor(0,0);
lcd.print("SISTEMA DISINSERITO");
lcd.setCursor(0,1);
lcd.print(chiaveInseritaStato ? "CHIAVE INSERITA" : "INSERIRE CHIAVE");
lcd.setCursor(0,2);
lcd.print(rete230V_OK ? "230V OK" : "MANCA 230V");
if(tamperViolato) {
if(tamperSirenaAttiva) {
lcd.setCursor(0,3);
lcd.print("TAMPER: ALLARME! ");
} else {
lcd.setCursor(0,3);
lcd.print("TAMPER: VIOLATO! ");
}
} else {
lcd.setCursor(0,3);
lcd.print("TAMPER: OK ");
}
} else if(systemState == ALLARME) {
lcd.setCursor(2,0);
lcd.print("ALLARME IN CORSO");
lcd.setCursor(0,2);
lcd.print("# per DISINSERIRE");
if(tamperViolato) {
lcd.setCursor(0,3);
lcd.print("TAMPER ATTIVO");
}
} else if(systemState == ATTIVO_DOPO_DURATA) {
lcd.setCursor(0,0);
lcd.print("SISTEMA INSERITO");
lcd.setCursor(0,2);
lcd.print("# per DISINSERIRE");
if(tamperViolato) {
lcd.setCursor(0,3);
if(tamperSirenaAttiva) {
lcd.print("TAMPER: ALLARME!");
} else {
lcd.print("TAMPER: VIOLATO!");
}
}
}
}
}
// ==================== FUNZIONI MENU PRINCIPALE ====================
void mostraMenuPrincipale() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("> MENU PRINCIPALE <");
const char* vociMenu[7] = {
"1. INSERISCI",
"2. IMPOSTAZIONI",
"3. STATO ZONE",
"4. EVENTI",
"5. TEST SISTEMA",
"6. EOL CAL. ZONE",
"7. TEST CHIAVE"
};
for(int i=0; i<3; i++) {
lcd.setCursor(0, i+1);
int idx = (i + menuSelection) % 7;
lcd.print(vociMenu[idx]);
}
}
void eseguiSelezioneMenu(int selection) {
switch(selection) {
case 0:
if(verificaPassword()) {
lcd.clear();
avviaSequenzaUscita();
}
break;
case 1:
if(verificaPassword()) {
inMenuImpostazioni = true;
impostazioneSelection = 0;
inMenuPrincipale = false;
mostraMenuImpostazioni();
}
break;
case 2: mostraStatoZone(); break;
case 3: mostraEventi(); break;
case 4: testSistema(); break;
case 5: calibraZone(); break;
case 6: testChiave(); break;
}
}
void gestioneMenuPrincipale(char key, bool up, bool down, bool enter, bool esc) {
if (enter && !inMenuPrincipale && !inMenuImpostazioni && systemState == DISINSERITO) {
inMenuPrincipale = true;
inMenu = true;
menuSelection = 0;
feedbackTasto();
mostraMenuPrincipale();
return;
}
if (key == 'A' && !inMenuPrincipale && !inMenuImpostazioni && systemState == DISINSERITO) {
if(verificaPassword()) {
avviaSequenzaUscita();
}
return;
}
if (inMenuPrincipale) {
if (up) {
menuSelection = (menuSelection - 1 + 7) % 7;
mostraMenuPrincipale();
feedbackTasto();
delay(200);
}
else if (down) {
menuSelection = (menuSelection + 1) % 7;
mostraMenuPrincipale();
feedbackTasto();
delay(200);
}
else if (enter) {
eseguiSelezioneMenu(menuSelection);
inMenuPrincipale = false;
inMenu = false;
}
else if (esc) {
inMenuPrincipale = false;
inMenu = false;
feedbackTasto();
aggiornaDisplay();
}
}
}
// ==================== FUNZIONI MENU IMPOSTAZIONI ====================
void mostraMenuImpostazioni() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("> IMPOSTAZIONI <");
const char* vociMenu[8] = {
"1. Tempo Entrata",
"2. Tempo Uscita",
"3. Durata SIR. Alarm",
"4. Durata SIR.TAMPER",
"5. Lampeggio LED",
"6. Feedback Tasti",
"7. Salva e Esci",
"8. DEOL Config"
};
for(int i=0; i<3; i++) {
lcd.setCursor(0, i+1);
int idx = (i + impostazioneSelection) % 8;
lcd.print(vociMenu[idx]);
}
}
void mostraTempoEntrata() {
lcd.clear();
lcd.setCursor(2,0);
lcd.print("> TEMPO ENTRATA <");
lcd.setCursor(0,1);
lcd.print("Valore: ");
lcd.print(config.tempo_entrata);
lcd.print(" s");
lcd.setCursor(0,2);
lcd.print("SU/GIU per regolare");
lcd.setCursor(0,3);
lcd.print("ENT=Save ESC=Annulla");
}
void modificaTempoEntrata() {
int val = config.tempo_entrata;
mostraTempoEntrata();
while(true) {
if(!digitalRead(BTN_UP)) {
val += 5;
if(val > 300) val = 300;
feedbackTasto();
lcd.setCursor(8, 1);
lcd.print(" ");
lcd.setCursor(8, 1);
lcd.print(val);
delay(200);
}
if(!digitalRead(BTN_DOWN)) {
val -= 5;
if(val < 5) val = 5;
feedbackTasto();
lcd.setCursor(8, 1);
lcd.print(" ");
lcd.setCursor(8, 1);
lcd.print(val);
delay(200);
}
if(!digitalRead(BTN_ENTER)) {
config.tempo_entrata = val;
feedbackTasto();
lcd.setCursor(0,2);
lcd.print("SALVATO! ");
delay(500);
break;
}
if(!digitalRead(BTN_ESC)) {
feedbackTasto();
break;
}
delay(100);
}
}
void mostraTempoUscita() {
lcd.clear();
lcd.setCursor(2,0);
lcd.print("> TEMPO USCITA <");
lcd.setCursor(0,1);
lcd.print("Valore: ");
lcd.print(config.tempo_uscita);
lcd.print(" s");
lcd.setCursor(0,2);
lcd.print("SU/GIU per regolare");
lcd.setCursor(0,3);
lcd.print("ENT=Save ESC=Annulla");
}
void modificaTempoUscita() {
int val = config.tempo_uscita;
mostraTempoUscita();
while(true) {
if(!digitalRead(BTN_UP)) {
val += 5;
if(val > 300) val = 300;
feedbackTasto();
lcd.setCursor(8, 1);
lcd.print(" ");
lcd.setCursor(8, 1);
lcd.print(val);
delay(200);
}
if(!digitalRead(BTN_DOWN)) {
val -= 5;
if(val < 5) val = 5;
feedbackTasto();
lcd.setCursor(8, 1);
lcd.print(" ");
lcd.setCursor(8, 1);
lcd.print(val);
delay(200);
}
if(!digitalRead(BTN_ENTER)) {
config.tempo_uscita = val;
feedbackTasto();
lcd.setCursor(0,2);
lcd.print("SALVATO! ");
delay(500);
break;
}
if(!digitalRead(BTN_ESC)) {
feedbackTasto();
break;
}
delay(100);
}
}
void mostraDurataAllarme() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("> DURATA ALLARME <");
lcd.setCursor(0,1);
if(config.durata_allarme > 0) {
lcd.print("Durata: ");
lcd.print(config.durata_allarme);
lcd.print(" s");
} else {
lcd.print("Allarme: DISATT");
}
lcd.setCursor(0,2);
lcd.print("SU/GIU per regolare");
lcd.setCursor(0,3);
lcd.print("ENT=Save ESC=Annulla");
}
void modificaDurataAllarme() {
int val = config.durata_allarme;
mostraDurataAllarme();
while(true) {
if(!digitalRead(BTN_UP)) {
if(val == 0) val = 30;
else if(val < 1800) val += 30;
feedbackTasto();
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
if(val > 0) {
lcd.print("Durata: ");
lcd.print(val);
lcd.print(" s");
} else {
lcd.print("Allarme: DISATT");
}
delay(200);
}
if(!digitalRead(BTN_DOWN)) {
if(val > 30) val -= 30;
else if(val == 30) val = 0;
feedbackTasto();
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
if(val > 0) {
lcd.print("Durata: ");
lcd.print(val);
lcd.print(" s");
} else {
lcd.print("Allarme: DISATT");
}
delay(200);
}
if(!digitalRead(BTN_ENTER)) {
config.durata_allarme = val;
feedbackTasto();
lcd.setCursor(0,2);
lcd.print("SALVATO! ");
delay(500);
break;
}
if(!digitalRead(BTN_ESC)) {
feedbackTasto();
break;
}
delay(100);
}
}
void mostraDurataSirenaTamper() {
lcd.clear();
lcd.setCursor(1,0);
lcd.print("> DURATA SIRENA <");
lcd.setCursor(5,1);
lcd.print("TAMPER 24H");
lcd.setCursor(0,2);
if(config.durata_sirena_tamper > 0) {
lcd.print("Durata: ");
lcd.print(config.durata_sirena_tamper);
lcd.print(" s");
} else {
lcd.print("Sirena: DISATTIVATA");
}
lcd.setCursor(0,3);
lcd.print("ENT=Save ESC=Annulla");
}
void modificaDurataSirenaTamper() {
int val = config.durata_sirena_tamper;
mostraDurataSirenaTamper();
while(true) {
if(!digitalRead(BTN_UP)) {
if(val == 0) val = 10;
else if(val < 300) val += 10;
feedbackTasto();
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,2);
if(val > 0) {
lcd.print("Durata: ");
lcd.print(val);
lcd.print(" s");
} else {
lcd.print("Sirena: DISATTIVATA");
}
delay(200);
}
if(!digitalRead(BTN_DOWN)) {
if(val > 10) val -= 10;
else if(val == 10) val = 0;
feedbackTasto();
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,2);
if(val > 0) {
lcd.print("Durata: ");
lcd.print(val);
lcd.print(" s");
} else {
lcd.print("Sirena: DISATTIVATA");
}
delay(200);
}
if(!digitalRead(BTN_ENTER)) {
config.durata_sirena_tamper = val;
feedbackTasto();
lcd.setCursor(0,3);
lcd.print("SALVATO! ");
delay(500);
break;
}
if(!digitalRead(BTN_ESC)) {
feedbackTasto();
break;
}
delay(100);
}
}
void mostraLampeggio() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("LAMP. LED Entr./Usc.");
lcd.setCursor(0,1);
lcd.print("Stato: ");
switch(lampeggioStato) {
case LAMP_OFF: lcd.print("OFF"); break;
case LAMP_LENTO: lcd.print("LENTO"); break;
case LAMP_VELOCE: lcd.print("VELOCE"); break;
}
lcd.setCursor(0,2);
lcd.print("SU/GIU per cambiare");
lcd.setCursor(0,3);
lcd.print("ENT=Save ESC=Annulla");
}
void modificaLampeggio() {
LampeggioStato val = lampeggioStato;
mostraLampeggio();
while(true) {
if(!digitalRead(BTN_UP)) {
if(val == LAMP_OFF) val = LAMP_LENTO;
else if(val == LAMP_LENTO) val = LAMP_VELOCE;
else if(val == LAMP_VELOCE) val = LAMP_OFF;
feedbackTasto();
lcd.setCursor(7, 1);
lcd.print(" ");
lcd.setCursor(7, 1);
switch(val) {
case LAMP_OFF: lcd.print("OFF"); break;
case LAMP_LENTO: lcd.print("LENTO"); break;
case LAMP_VELOCE: lcd.print("VELOCE"); break;
}
for(int i=0; i<3; i++) {
digitalWrite(PIN_LED, HIGH);
delay(val == LAMP_VELOCE ? 100 : 300);
digitalWrite(PIN_LED, LOW);
delay(val == LAMP_VELOCE ? 100 : 300);
}
delay(200);
}
if(!digitalRead(BTN_DOWN)) {
if(val == LAMP_OFF) val = LAMP_VELOCE;
else if(val == LAMP_LENTO) val = LAMP_OFF;
else if(val == LAMP_VELOCE) val = LAMP_LENTO;
feedbackTasto();
lcd.setCursor(7, 1);
lcd.print(" ");
lcd.setCursor(7, 1);
switch(val) {
case LAMP_OFF: lcd.print("OFF"); break;
case LAMP_LENTO: lcd.print("LENTO"); break;
case LAMP_VELOCE: lcd.print("VELOCE"); break;
}
for(int i=0; i<3; i++) {
digitalWrite(PIN_LED, HIGH);
delay(val == LAMP_VELOCE ? 100 : 300);
digitalWrite(PIN_LED, LOW);
delay(val == LAMP_VELOCE ? 100 : 300);
}
delay(200);
}
if(!digitalRead(BTN_ENTER)) {
lampeggioStato = val;
feedbackTasto();
lcd.setCursor(0,2);
lcd.print("SALVATO! ");
delay(500);
break;
}
if(!digitalRead(BTN_ESC)) {
feedbackTasto();
break;
}
delay(100);
}
}
void mostraFeedbackTasti() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("> BEEP TASTI <");
lcd.setCursor(0,1);
lcd.print("Stato: ");
lcd.print(config.feedback_acustico ? "ON" : "OFF");
lcd.setCursor(0,2);
lcd.print("SU/GIU per cambiare");
lcd.setCursor(0,3);
lcd.print("ENT=Save ESC=Annulla");
}
void modificaFeedbackTasti() {
bool val = config.feedback_acustico;
mostraFeedbackTasti();
while(true) {
if(!digitalRead(BTN_UP) || !digitalRead(BTN_DOWN)) {
val = !val;
feedbackTasto();
lcd.setCursor(7, 1);
lcd.print(" ");
lcd.setCursor(7, 1);
lcd.print(val ? "ON" : "OFF");
if(val) tone(BUZZER_PIN, 2000, 100);
delay(300);
}
if(!digitalRead(BTN_ENTER)) {
config.feedback_acustico = val;
feedbackTasto();
lcd.setCursor(0,2);
lcd.print("SALVATO! ");
delay(500);
break;
}
if(!digitalRead(BTN_ESC)) {
feedbackTasto();
break;
}
delay(100);
}
}
// ==================== FUNZIONE DEOL CONFIG (REGOLAZIONE MANUALE SOGLIE ZONE) ====================
enum DeolParametro { MIN, MAX, FINITO };
void mostraDeolZona(int zona, int parametro, int valoreMin, int valoreMax) {
lcd.clear();
lcd.setCursor(0,0);
if(zona == 4) lcd.print("TAMPER 24H");
else {
lcd.print("ZONA ");
lcd.print(zona+1);
}
lcd.setCursor(10,0);
lcd.print("LETTO:");
lcd.print(zoneFilters[zona].average);
lcd.setCursor(0,1);
lcd.print("Nominale:");
lcd.print(config.zone[zona].resistenza_nominale);
lcd.setCursor(0,2);
lcd.print("Min:");
lcd.print(config.zone[zona].resistenza_min);
lcd.setCursor(10,2);
lcd.print("Max:");
lcd.print(config.zone[zona].resistenza_max);
lcd.setCursor(0,3);
if(parametro == MIN) {
lcd.print("MODIFICA MIN [");
lcd.print(valoreMin);
lcd.print("] UP/DOWN");
} else if(parametro == MAX) {
lcd.print("MODIFICA MAX [");
lcd.print(valoreMax);
lcd.print("] UP/DOWN");
} else {
lcd.print("ENT per conferma");
}
}
void modificaDeolZona(int zona) {
int parametro = MIN;
int nuovoMin = config.zone[zona].resistenza_min;
int nuovoMax = config.zone[zona].resistenza_max;
bool uscita = false;
while(!uscita) {
mostraDeolZona(zona, parametro, nuovoMin, nuovoMax);
if(!digitalRead(BTN_UP)) {
feedbackTasto();
if(parametro == MIN) {
nuovoMin += 5;
if(nuovoMin > config.zone[zona].resistenza_max) nuovoMin = config.zone[zona].resistenza_max;
if(nuovoMin > 1023) nuovoMin = 1023;
} else if(parametro == MAX) {
nuovoMax += 5;
if(nuovoMax > 1023) nuovoMax = 1023;
}
delay(200);
}
if(!digitalRead(BTN_DOWN)) {
feedbackTasto();
if(parametro == MIN) {
nuovoMin -= 5;
if(nuovoMin < 0) nuovoMin = 0;
} else if(parametro == MAX) {
nuovoMax -= 5;
if(nuovoMax < config.zone[zona].resistenza_min) nuovoMax = config.zone[zona].resistenza_min;
if(nuovoMax < 0) nuovoMax = 0;
}
delay(200);
}
if(!digitalRead(BTN_ENTER)) {
feedbackTasto();
if(parametro == MIN) {
parametro = MAX;
} else if(parametro == MAX) {
// chiedi conferma
lcd.clear();
lcd.setCursor(2,1);
lcd.print("CONFERMI?");
lcd.setCursor(0,2);
lcd.print("ENT=Si ESC=No");
delay(500);
while(true) {
if(!digitalRead(BTN_ENTER)) {
config.zone[zona].resistenza_min = nuovoMin;
config.zone[zona].resistenza_max = nuovoMax;
salvaConfigurazioneEEPROM();
feedbackTasto();
lcd.clear();
lcd.setCursor(3,1);
lcd.print("SALVATO!");
delay(1000);
uscita = true;
break;
}
if(!digitalRead(BTN_ESC)) {
feedbackTasto();
uscita = true;
break;
}
delay(100);
}
}
}
if(!digitalRead(BTN_ESC)) {
feedbackTasto();
uscita = true;
}
delay(100);
}
}
void mostraDeolConfig() {
int selezioneZona = 0;
bool inDeol = true;
while(inDeol) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("> DEOL Config <");
lcd.setCursor(0,1);
lcd.print("Zona ");
lcd.print(selezioneZona+1);
if(selezioneZona == 4) lcd.print(" (TAMPER)");
lcd.setCursor(0,2);
lcd.print("Min:");
lcd.print(config.zone[selezioneZona].resistenza_min);
lcd.setCursor(10,2);
lcd.print("Max:");
lcd.print(config.zone[selezioneZona].resistenza_max);
lcd.setCursor(0,3);
lcd.print("UP/DOWN zona ENT sel");
if(!digitalRead(BTN_UP)) {
feedbackTasto();
selezioneZona = (selezioneZona + 1) % 5;
delay(200);
}
if(!digitalRead(BTN_DOWN)) {
feedbackTasto();
selezioneZona = (selezioneZona - 1 + 5) % 5;
delay(200);
}
if(!digitalRead(BTN_ENTER)) {
feedbackTasto();
modificaDeolZona(selezioneZona);
}
if(!digitalRead(BTN_ESC)) {
feedbackTasto();
inDeol = false;
}
delay(100);
}
}
// ==================== GESTIONE MENU IMPOSTAZIONI CON INTEGRAZIONE DEOL ====================
void gestioneMenuImpostazioni() {
if (!inMenuImpostazioni) return;
static bool inSottoMenu = false;
if (!inSottoMenu) {
bool up = !digitalRead(BTN_UP);
bool down = !digitalRead(BTN_DOWN);
bool enter = !digitalRead(BTN_ENTER);
bool esc = !digitalRead(BTN_ESC);
if (up) {
impostazioneSelection = (impostazioneSelection - 1 + 8) % 8;
mostraMenuImpostazioni();
feedbackTasto();
delay(200);
}
else if (down) {
impostazioneSelection = (impostazioneSelection + 1) % 8;
mostraMenuImpostazioni();
feedbackTasto();
delay(200);
}
else if (enter) {
feedbackTasto();
inSottoMenu = true;
switch(impostazioneSelection) {
case 0: modificaTempoEntrata(); break;
case 1: modificaTempoUscita(); break;
case 2: modificaDurataAllarme(); break;
case 3: modificaDurataSirenaTamper(); break;
case 4: modificaLampeggio(); break;
case 5: modificaFeedbackTasti(); break;
case 6:
salvaConfigurazioneEEPROM();
lcd.clear();
lcd.setCursor(3,1);
lcd.print("CONFIGURAZIONE");
lcd.setCursor(6,2);
lcd.print("SALVATA!");
delay(1500);
inMenuImpostazioni = false;
inMenu = false;
inSottoMenu = false;
aggiornaDisplay();
return;
case 7:
mostraDeolConfig(); // nuova funzione
break;
}
inSottoMenu = false;
mostraMenuImpostazioni();
}
else if (esc) {
feedbackTasto();
inMenuImpostazioni = false;
inMenu = false;
aggiornaDisplay();
}
}
}
// ==================== FUNZIONI DI VISUALIZZAZIONE ====================
void mostraStatoZone() {
lcd.clear();
char line0[21];
char line1[21];
char line2[21];
snprintf(line0, sizeof(line0), "Z1:%4d%c Z2:%4d%c",
zoneFilters[0].average,
zoneFilters[0].currentViolated ? 'A' : 'C',
zoneFilters[1].average,
zoneFilters[1].currentViolated ? 'A' : 'C');
snprintf(line1, sizeof(line1), "Z3:%4d%c Z4:%4d%c",
zoneFilters[2].average,
zoneFilters[2].currentViolated ? 'A' : 'C',
zoneFilters[3].average,
zoneFilters[3].currentViolated ? 'A' : 'C');
snprintf(line2, sizeof(line2), "TAMPER:%4d%c",
zoneFilters[4].average,
zoneFilters[4].currentViolated ? 'A' : 'C');
lcd.setCursor(0,0);
lcd.print(line0);
lcd.setCursor(0,1);
lcd.print(line1);
lcd.setCursor(0,2);
lcd.print(line2);
lcd.setCursor(0,3);
lcd.print("Premere un tasto...");
unsigned long start = millis();
while (millis() - start < 5000) {
if (keypad.getKey() || !digitalRead(BTN_ENTER) || !digitalRead(BTN_ESC) ||
!digitalRead(BTN_UP) || !digitalRead(BTN_DOWN)) {
break;
}
delay(50);
}
}
// ==================== REGISTRO EVENTI CON SCROLLING ====================
void mostraEventi() {
int primoIndice = 0; // indice del primo evento visualizzato (dal più recente)
bool inDettaglio = false;
int eventoSelezionato = 0;
int offsetOrizzontale = 0;
while(true) {
if(!inDettaglio) {
// Visualizzazione lista
lcd.clear();
lcd.setCursor(0,0);
lcd.print("EVENTI (");
lcd.print(indiceEvento);
lcd.print("/");
lcd.print(MAX_EVENTI);
lcd.print(")");
int count = 0;
for(int i=0; i<4; i++) { // 4 righe disponibili (righe 1-4)
int idx = (indiceEvento - 1 - primoIndice - i + MAX_EVENTI) % MAX_EVENTI;
if(eventi[idx].descrizione[0]) {
lcd.setCursor(0, i+1);
// Mostra primi 16 caratteri + ora (HH:MM) se disponibile
char buffer[21];
if(eventi[idx].timestamp.isValid()) {
snprintf(buffer, sizeof(buffer), "%02d:%02d %-16s",
eventi[idx].timestamp.hour(),
eventi[idx].timestamp.minute(),
eventi[idx].descrizione);
} else {
snprintf(buffer, sizeof(buffer), "%-20s", eventi[idx].descrizione);
}
buffer[20] = '\0';
lcd.print(buffer);
count++;
} else {
lcd.setCursor(0, i+1);
lcd.print(" ");
}
}
// Gestione tasti
char key = keypad.getKey();
bool up = !digitalRead(BTN_UP);
bool down = !digitalRead(BTN_DOWN);
bool enter = !digitalRead(BTN_ENTER);
bool esc = !digitalRead(BTN_ESC);
if(up && primoIndice < MAX_EVENTI-1) {
primoIndice++;
feedbackTasto();
delay(200);
}
if(down && primoIndice > 0) {
primoIndice--;
feedbackTasto();
delay(200);
}
if(enter) {
// Seleziona l'evento corrente (primo della lista)
int idx = (indiceEvento - 1 - primoIndice + MAX_EVENTI) % MAX_EVENTI;
if(eventi[idx].descrizione[0]) {
eventoSelezionato = idx;
offsetOrizzontale = 0;
inDettaglio = true;
feedbackTasto();
}
}
if(esc) {
feedbackTasto();
break;
}
delay(100);
} else {
// Visualizzazione dettaglio evento con scrolling orizzontale
lcd.clear();
lcd.setCursor(0,0);
lcd.print("DETTAGLIO EVENTO");
// Mostra timestamp se valido
if(eventi[eventoSelezionato].timestamp.isValid()) {
lcd.setCursor(0,1);
lcd.print(eventi[eventoSelezionato].timestamp.timestamp(DateTime::TIMESTAMP_DATE));
lcd.setCursor(0,2);
lcd.print(eventi[eventoSelezionato].timestamp.timestamp(DateTime::TIMESTAMP_TIME));
} else {
lcd.setCursor(0,1);
lcd.print("Data non disp.");
}
// Mostra descrizione con scrolling orizzontale
int len = strlen(eventi[eventoSelezionato].descrizione);
int maxOffset = max(0, len - 20);
if(offsetOrizzontale > maxOffset) offsetOrizzontale = maxOffset;
lcd.setCursor(0,3);
for(int i=0; i<20; i++) {
int idx = offsetOrizzontale + i;
if(idx < len) {
lcd.print(eventi[eventoSelezionato].descrizione[idx]);
} else {
lcd.print(' ');
}
}
// Gestione tasti in dettaglio
bool up = !digitalRead(BTN_UP);
bool down = !digitalRead(BTN_DOWN);
bool esc = !digitalRead(BTN_ESC);
if(up && offsetOrizzontale > 0) {
offsetOrizzontale--;
feedbackTasto();
delay(150);
}
if(down && offsetOrizzontale < maxOffset) {
offsetOrizzontale++;
feedbackTasto();
delay(150);
}
if(esc) {
inDettaglio = false;
feedbackTasto();
}
delay(100);
}
}
}
void testSistema() {
lcd.clear();
lcd.print("TEST LED&HARDWARE");
for(int i=LED_RETE; i<=LED_ALLINSERITO; i++) {
digitalWrite(i, HIGH);
delay(100);
}
for(int i=LED_RETE; i<=LED_ALLINSERITO; i++) {
digitalWrite(i, LOW);
}
tone(BUZZER_PIN, 800, 300);
delay(400);
tone(BUZZER_PIN, 1200, 300);
delay(400);
tone(BUZZER_PIN, 1600, 300);
digitalWrite(RELAY_SIRENA_TAMPER, HIGH);
delay(500);
digitalWrite(RELAY_SIRENA_TAMPER, LOW);
lcd.setCursor(0,2);
lcd.print("230V: ");
lcd.print(analogRead(RILEVATORE_230V));
delay(2000);
}
void testChiave() {
lcd.clear();
lcd.print("TEST CHIAVE");
unsigned long start = millis();
while(millis()-start<5000) {
lcd.setCursor(0,1);
lcd.print(digitalRead(INTERRUTTORE_CHIAVE) ? "INSERITA" : "RIMOSSA");
lcd.setCursor(0,2);
lcd.print("Stato: ");
lcd.print(chiaveInseritaStato ? "INSERITA" : "RIMOSSA");
if(!digitalRead(BTN_ESC)) break;
delay(100);
}
}
void calibraZone() {
if(!verificaPassword()) return;
lcd.clear();
lcd.setCursor(0,0);
lcd.print(">CALIBRAZIONE EOL<");
lcd.setCursor(6,1);
lcd.print("CHIUDERE");
lcd.setCursor(3,2);
lcd.print("TUTTE LE ZONE");
lcd.setCursor(0,3);
lcd.print("POI >PREMERE ENTER<");
while(digitalRead(BTN_ENTER)) delay(100);
feedbackTasto();
const int CAMPIONI = 5;
long somma[5] = {0};
int vmin[5] = {1023,1023,1023,1023,1023};
int vmax[5] = {0};
for(int c=0; c<CAMPIONI; c++) {
for(int i=0; i<5; i++) {
int v = analogRead(A0+i); // usiamo raw per la calibrazione
somma[i] += v;
if(v < vmin[i]) vmin[i] = v;
if(v > vmax[i]) vmax[i] = v;
}
lcd.setCursor(0,3);
lcd.print("LETTURA LINEE ");
lcd.print(c+1);
lcd.print("/");
lcd.print(CAMPIONI);
delay(200);
}
for(int i=0; i<5; i++) {
int media = somma[i]/CAMPIONI;
int rumore = vmax[i] - vmin[i];
int toll = max(rumore*2, 30);
config.zone[i].resistenza_nominale = media;
config.zone[i].resistenza_min = max(0, media - toll);
config.zone[i].resistenza_max = min(1023, media + toll);
lcd.clear();
if(i==4) {
lcd.print("TAMPER 24H: ");
} else {
lcd.print("Zona ");
lcd.print(i+1);
lcd.print(": ");
}
lcd.print(media);
lcd.setCursor(0,1);
lcd.print("Min:");
lcd.print(config.zone[i].resistenza_min);
lcd.setCursor(10,1);
lcd.print("Max:");
lcd.print(config.zone[i].resistenza_max);
delay(1500);
}
salvaConfigurazioneEEPROM();
lcd.clear();
lcd.setCursor(0,1);
lcd.print("CALIBRAZIONE EOL OK!");
delay(1000);
}
// ==================== CONFIGURAZIONE DEFAULT ====================
void caricaConfigurazioneDefault() {
config.magic_number = MAGIC_NUMBER_CONFIG;
config.tempo_entrata = 20;
config.tempo_uscita = 20;
config.durata_allarme = 300;
config.durata_sirena_tamper = 60;
strcpy(config.numero_telefono, "+391234567890");
strcpy(config.password, "1234");
config.notifica_rete = true;
config.notifica_allarme = true;
config.feedback_acustico = true;
for(int i=0; i<5; i++) {
if(i==4) {
snprintf(config.zone[i].nome, 16, "TAMPER 24H");
} else {
snprintf(config.zone[i].nome, 16, "Zona %d", i+1);
}
config.zone[i].abilitata = true;
config.zone[i].ritardata = (i < 2);
config.zone[i].tamper = (i == 4);
config.zone[i].ventiquattro_ore = (i == 4);
config.zone[i].resistenza_min = 100;
config.zone[i].resistenza_max = 900;
config.zone[i].resistenza_nominale = 512;
}
}
// ==================== SETUP ====================
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BTN_ENTER, INPUT_PULLUP);
pinMode(BTN_ESC, INPUT_PULLUP);
pinMode(INTERRUTTORE_CHIAVE, INPUT_PULLUP);
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, LOW);
for(int i=LED_RETE; i<=LED_ALLINSERITO; i++) {
pinMode(i, OUTPUT);
digitalWrite(i, LOW);
}
pinMode(BUZZER_PIN, OUTPUT);
pinMode(RELAY_USCITA, OUTPUT);
pinMode(RELAY_SIRENA_TAMPER, OUTPUT);
pinMode(RILEVATORE_230V, INPUT);
lcd.init();
lcd.backlight();
lcd.clear();
if(!rtc.begin()) {
lcd.print("ERRORE RTC!");
delay(2000);
}
caricaConfigurazioneEEPROM();
inizializzaFiltriZone();
lcd.setCursor(0,0);
lcd.print("GTEKLAB TECHNOLOGIES");
lcd.setCursor(3,1);
lcd.print("PROTEK5Z-T2 GSM");
lcd.setCursor(1,2);
lcd.print("CENTRALE ANTIFURTO");
lcd.setCursor(4,3);
lcd.print("PROFESSIONALE");
delay(1500);
lcd.clear();
lcd.setCursor(3,1);
lcd.print("H/S Ver. 1.35");
delay(1500);
rete230V_OK = (analogRead(RILEVATORE_230V) > 100);
inizializzaEventi();
}
// ==================== LOOP PRINCIPALE ====================
void loop() {
char key = keypad.getKey();
bool btnUp = !digitalRead(BTN_UP);
bool btnDown = !digitalRead(BTN_DOWN);
bool btnEnter = !digitalRead(BTN_ENTER);
bool btnEsc = !digitalRead(BTN_ESC);
// Aggiornamento filtri zone (media mobile e debounce)
aggiornaFiltriZone();
gestioneInterruttoreChiave();
gestioneBuzzer();
aggiornaRilevatore230V();
// Gestione menu
if(inMenuImpostazioni) {
gestioneMenuImpostazioni();
} else if(systemState == DISINSERITO && !attesaCodice) {
gestioneMenuPrincipale(key, btnUp, btnDown, btnEnter, btnEsc);
}
// Gestione codice durante allarme (priorità massima)
if(key == '#' && (systemState != DISINSERITO || tamperViolato)) {
gestisciCodiceDuranteAllarme();
}
// Monitoraggio TAMPER (sempre attivo 24/7)
monitoraggioTamper();
// Monitoraggio zone 1-4 (solo se sistema inserito)
if(systemState == ALLARME || systemState == ATTIVO_DOPO_DURATA) {
monitoraggioZone();
}
gestioneStatiAllarme();
aggiornaDisplay();
aggiornaLED();
aggiornaLEDStati();
if(lampeggioStato != LAMP_OFF && systemState == ALLARME) {
static unsigned long lastLedBlink = 0;
unsigned long currentMillis = millis();
int interval = (lampeggioStato == LAMP_VELOCE) ? 200 : 800;
if(currentMillis - lastLedBlink >= interval) {
lastLedBlink = currentMillis;
digitalWrite(PIN_LED, !digitalRead(PIN_LED));
}
} else {
digitalWrite(PIN_LED, LOW);
}
delay(50);
}ZONA 1
RETE
ZONA 1
ZONA 2
TAMPER H24
ALLARME IN
!!!..CORSO..!!!
ZONA 4
ZONA 2
USCITA
ZONA 24H
ZONA 3
ENTRATA
ALLARME
!..INSERITO..!
ZONA 3
ZONA 4
SU
ESC
GIU
ENTER
MENU
RELè SIRENA EXT
ON/OFF CENTRALE
CONTATTO CHIAVE
<---DISINSERITO---
<---INSERITO---
4 zone (sensori)
(sensore aperto o
sbilanciato/allarme attivo)
RELè SIRENA TAMPER
BUZZER
ANTIFURTO PRO5 GSM VER. 5.1.35
BUS/ROW
BUS/COLS
BUS/R-C
BUS>SDA/SCL
BUS>SDA/SCL
BUS/PWR +5V
BUS/LED
BUS/PWR +5V
BUS/PWR +5V