Guide · 20 min di lettura

Antifurto con Arduino: PIR, sensore porta, tastierino e sirena

Come costruire un antifurto con Arduino: PIR, contatto magnetico, sensori di vibrazione e laser, codice da tastierino, ritardo di ingresso, sirena e Telegram.

Un antifurto con Arduino è un sistema di sicurezza che puoi costruire da solo: fa suonare una sirena quando qualcuno entra senza permesso in casa o nella tua stanza, ti lascia qualche secondo per digitare il codice quando la porta si apre e, se vuoi, ti manda una notifica istantanea sul telefono. In questa guida costruirai un allarme a quattro zone con un sensore di movimento PIR, un contatto reed sulla porta, un sensore di vibrazione che rileva i colpi sul vetro della finestra e una barriera laser che attraversa il corridoio. Lo inserirai e disinserirai con un codice su un tastierino 4x4, comanderai una sirena a 12 V tramite un relè e gestirai tutto con un solo Arduino Uno.

Passo dopo passo realizzeremo una piccola copia di quello che fanno le centrali d'allarme commerciali: ritardo di ingresso e di uscita, inserimento parziale (modalità notte), blocco dopo codici errati, durata limitata della sirena e memoria d'allarme. Invece di blocchi if annidati scriveremo il codice come una macchina a stati, così sarà sempre chiaro che cosa sta facendo l'allarme in ogni momento. Poi porterai la stessa logica su un ESP32 per inserire, disinserire e ricevere gli avvisi via Telegram, scriverai una versione MicroPython per il Raspberry Pi Pico e vedrai come ridurre il problema più fastidioso di tutti: i falsi allarmi.

Sensore di movimento PIR HC-SR501
Il sensore di movimento PIR HC-SR501 è la zona interna dell'allarme: la lente di Fresnel bianca divide il campo visivo in decine di piccole zone e i due trimmer accanto regolano la portata e il tempo di uscita.

Come funziona un antifurto con Arduino?

Ogni sistema d'allarme ha tre parti: gli ingressi (i sensori), un cervello che decide (l'Arduino) e le uscite (sirena, buzzer, notifica). Ogni sensore sorveglia una zona. Le centrali professionali classificano le zone in base a come si comportano quando scattano, ed è proprio questa distinzione a separare un buon allarme da un aggeggio fastidioso che suona a ogni apertura della porta. Il nostro sistema avrà quattro zone:

ZonaSensoreTipo di zonaSe scatta con l'allarme inserito
Porta d'ingressoContatto reed (MC-38)RitardataParte un ritardo di ingresso di 20 s; allarme se non si digita il codice
Soggiorno, internoPIR (HC-SR501)Interna (a seguire)Allarme immediato; attende se il ritardo di ingresso è in corso; esclusa nell'inserimento parziale
FinestraVibrazione (SW-420)IstantaneaAllarme immediato dopo 3 colpi in 3 s
Corridoio, scaleLaser + LDR (KY-008)Interna (a seguire)Allarme immediato; attende se il ritardo di ingresso è in corso
Gas, incendio, manomissioneMQ-2, sensore di fiamma, interruttore della scatola24 oreAllarme anche da disinserito (idea di miglioramento)

Grazie alla zona ritardata, la sirena non suona appena rientri: la porta si apre, il buzzer emette dei bip per avvisarti e hai 20 secondi per digitare il codice. Una zona interna a seguire resta paziente finché il ritardo di ingresso è in corso, quindi passare davanti al PIR mentre vai dalla porta al tastierino non fa scattare nulla. Ma se qualcuno entra senza aprire la porta, per esempio da una finestra, e passa davanti al PIR, nessun ritardo è partito e l'allarme suona subito. Una zona istantanea scatta sempre immediatamente; non ha senso regalare 20 secondi a chi forza una finestra. All'uscita vale un principio simile: quando inserisci il sistema parte un ritardo di uscita di 30 secondi e uscire dalla porta in quel tempo non fa scattare niente.

Lista dei componenti per l'antifurto con Arduino

ComponenteQ.tàFunzione
Arduino Uno (o Nano)1Il cervello del sistema; i pin di questa guida sono uguali su Uno e Nano
Sensore di movimento PIR HC-SR5011Rilevamento del movimento interno, 3–7 m, circa 110°
Contatto magnetico MC-38 (o reed + magnete)1Apre il circuito quando una porta o una finestra si apre
Modulo sensore di vibrazione SW-4201Rileva i colpi sul vetro o sul telaio
Modulo laser KY-008 + LDR + resistenza da 10 kΩ1 setBarriera di luce attraverso il corridoio
Tastierino a membrana 4x41Inserimento e disinserimento con codice
Buzzer attivo (5 V)1Bip dei tasti, avviso di ingresso e uscita, sirena interna
Modulo relè a 1 canale da 5 V1Comanda la sirena a 12 V isolandola dall'Arduino
Sirena a 12 V (meglio con lampeggiante)1Il suono d'allarme principale, di solito 100–120 dB
LED rosso e verde + 2 resistenze da 220 Ω1 setSpie di inserito e pronto
Alimentatore 12 V, modulo LM2596, batteria 12 V (opzionale)1 setAlimentazione e riserva durante i blackout
Breadboard, cavetti jumper, cavo per allarmi a 4 poli-Collegamenti e sensori lontani
Arduino Uno
Arduino Uno
Reed
Reed
SW-420
SW-420
KY-008
KY-008
Keypad 4x4
Keypad 4x4
Buzzer
Buzzer
Relay 5 V
Relay 5 V

I sensori: che cosa rileva ogni zona?

  • PIR (HC-SR501): Rileva le variazioni della radiazione infrarossa emessa dal corpo umano e vede il movimento a 3–7 metri con un angolo di circa 110 gradi. La sua uscita è a 3,3 V, ma va alimentato a 5 V. Nei 30–60 secondi dopo l'accensione impara l'ambiente e può scattare da solo; per questo il codice ignora il primo minuto. Trovi i dettagli nell'articolo sensore di movimento PIR.
  • Contatto magnetico per porta (MC-38): Due corpi in plastica, uno avvitato al telaio e uno all'anta. Con la porta chiusa il magnete è vicino e il contatto è chiuso; quando la porta si apre di oltre 15–25 mm il contatto si apre. Si legge con un solo pin e il pull-up interno; spieghiamo cosa c'è dentro nell'articolo reed switch.
  • Sensore di vibrazione (SW-420): Un interruttore normalmente chiuso con un'asta metallica circondata da una molla. Quando il vetro o il telaio ricevono un colpo, il contatto si apre per un istante e il comparatore LM393 sulla scheda lo trasforma in un impulso digitale. Nella maggior parte dei moduli l'uscita è LOW a riposo e diventa HIGH per un attimo a ogni colpo; verifica il tuo modulo con il test dei sensori. La sensibilità si regola con il trimmer; i dettagli sono nell'articolo sensore di vibrazione SW-420.
  • Barriera laser (KY-008 + LDR): Un laser da 650 nm e circa 5 mW colpisce di continuo una LDR dall'altra parte del corridoio. Quando il raggio viene interrotto, la resistenza della LDR sale da qualche centinaio di ohm a decine di kiloohm e la lettura analogica cala nettamente. Consigli di montaggio e allineamento nell'articolo modulo laser.
  • Tastierino 4x4: Una matrice di 16 tasti formata da 4 linee di riga e 4 di colonna; usa 8 pin e si legge in una riga con la libreria Keypad. Il funzionamento della scansione righe-colonne è spiegato nella guida al tastierino.
  • Sirena e buzzer: Un buzzer attivo ha un oscillatore interno e suona appena porti il pin a HIGH; è perfetto per il bip dei tasti e gli avvisi di ingresso e uscita. Il suono davvero dissuasivo arriva dalla sirena a 12 V, che comandi tramite un modulo relè. La differenza tra buzzer attivo e passivo è spiegata in cos'è un buzzer.
Il KY-008 è un laser di classe 3R; guardare il raggio direttamente o attraverso uno specchio può danneggiarti gli occhi. Tieni sempre il raggio sotto l'altezza degli occhi, per esempio a 20–40 cm da terra. Questa altezza è sicura e garantisce anche che chiunque entri interrompa il raggio con il passo.

Schema di collegamento e tabella dei pin

La tabella riunisce i collegamenti delle tre schede. La versione per Arduino Uno usa tutte e quattro le zone e il tastierino. Poiché la versione ESP32 si inserisce e disinserisce con comandi Telegram invece del tastierino e la versione Pico è volutamente breve, la barriera laser c'è solo sull'Uno. Per vedere i pin della tua scheda usa lo strumento Pinout, e per calcolare le resistenze dei LED il calcolatore di resistenza per LED.

CollegamentoArduino UnoESP32Raspberry Pi Pico
Tastierino R1–R4 (righe)D9, D8, D7, D6– (Telegram)GP2, GP3, GP4, GP5
Tastierino C1–C4 (colonne)D5, D4, D3, D2– (Telegram)GP6, GP7, GP8, GP9
PIR VCC / OUT / GND5V / D10 / GNDVIN (5V) / GPIO 27 / GNDVBUS / GP16 / GND
Contatto reed (2 fili)D11 e GNDGPIO 33 e GNDGP15 e GND
SW-420 VCC / DO / GND5V / D12 / GND3V3 / GPIO 32 / GND3V3(OUT) / GP17 / GND
KY-008 S / − (laser sempre acceso)5V / GND––
LDR tra 5V e A0, 10 kΩ tra A0 e GNDA0––
Buzzer attivo + (− a GND)A1GPIO 25GP14
IN del modulo relèA2GPIO 26GP13
LED verde (con 220 Ω): prontoA3––
LED rosso (con 220 Ω): inseritoD13GPIO 2 (LED integrato)LED integrato
Non collegare mai la sirena a 12 V direttamente a un pin dell'Arduino. Il positivo dell'alimentatore va al morsetto COM del relè, il morsetto NO del relè al positivo della sirena e il negativo della sirena torna al negativo dell'alimentatore; così la corrente della sirena non passa mai dall'Arduino. I pin a 3,3 V di ESP32 e Pico non sempre riescono a rilasciare alcuni moduli relè da 5 V, e il relè può restare eccitato; su queste schede usa un modulo compatibile con 3,3 V o con attivazione a livello alto, oppure un driver a transistor NPN.

Un allarme serve soprattutto quando manca la corrente, quindi pensa all'alimentazione fin dall'inizio. Una soluzione pratica: come nelle centrali professionali, un alimentatore per allarmi da 13,8 V con collegamento alla batteria alimenta la sirena e, ridotto a 5 V con un convertitore step-down LM2596, il pin 5V dell'Arduino. Se manca la rete, subentra una batteria al piombo da 12 V e 7 Ah. Il calcolo è semplice: Uno, PIR, laser, LED e SW-420 assorbono circa 100 mA a 5 V a riposo; sono 0,5 W, cioè circa 50 mA lato batteria con un convertitore all'85 % di rendimento. Anche usando solo l'80 % della capacità, una batteria da 7 Ah tiene in funzione il sistema per 4–5 giorni; gli 0,2–0,5 A della sirena mentre suona riducono un po' questo tempo. Prova i tuoi valori con il calcolatore di durata della batteria e leggi come alimentare Arduino per le opzioni di alimentazione.

Montaggio passo dopo passo

  1. Nell'Arduino IDE apri il Gestore librerie, scrivi Keypad e installa la libreria di Mark Stanley e Alexander Brevig. Senza di essa, in compilazione otterrai l'errore Keypad.h: No such file or directory.
  2. Tieni il tastierino con i tasti rivolti verso di te; collega i primi quattro fili da sinistra (R1–R4) a D9, D8, D7, D6 e i quattro successivi (C1–C4) a D5, D4, D3, D2.
  3. Collega il PIR a 5V, D10 e GND; ruota entrambi i trimmer completamente in senso antiorario e metti il ponticello di trigger su H.
  4. Collega i due fili del contatto reed a D11 e GND, e il SW-420 a 5V, D12 e GND.
  5. Metti la LDR tra 5V e A0 e la resistenza da 10 kΩ tra A0 e GND; inserisci la LDR in fondo a un tubetto nero e punta il laser al centro del tubetto.
  6. Collega il buzzer ad A1, l'IN del modulo relè ad A2 e i suoi VCC e GND ai 5V e GND dell'Arduino; collega i LED ad A3 e D13 con resistenze da 220 Ω.
  7. Carica prima il test dei sensori qui sotto, apri il Monitor seriale a 9600 baud e attiva i sensori uno alla volta.
  8. Poi carica il codice completo dell'allarme, cambia il codice di accesso e prova il sistema per qualche giorno solo con il buzzer prima di collegare la sirena.

Primo test: vedere tutti i sensori nel Monitor seriale

Prima di passare al codice dell'allarme, assicurati che ogni sensore si comporti come ti aspetti. Questo piccolo sketch stampa due volte al secondo lo stato del PIR, se la porta è aperta o chiusa, il numero di impulsi di vibrazione dell'ultimo mezzo secondo e il valore analogico della LDR. Il pin della vibrazione viene letto di continuo, perché gli impulsi del SW-420 possono durare pochi millisecondi e un codice con delay() tra una lettura e l'altra li perderebbe.

C++ (Arduino IDE)
1// Alarm sensör testi: tüm girişleri saniyede iki kez yazdırır / Alarm sensor test: prints every input twice a second
2const int PIR_PIN = 10;        // HC-SR501 OUT
3const int KAPI_PIN = 11;       // reed kapı kontağı, diğer ucu GND / reed door contact, other leg to GND
4const int TITRESIM_PIN = 12;   // SW-420 DO
5const int LAZER_PIN = A0;      // LDR bölücünün orta noktası / LDR divider midpoint
6
7int titresimSayisi = 0;        // yarım saniyedeki darbe sayısı / pulses in the last half second
8int oncekiTitresim = LOW;
9unsigned long sonYazma = 0;
10
11void setup() {
12  Serial.begin(9600);
13  pinMode(PIR_PIN, INPUT);
14  pinMode(KAPI_PIN, INPUT_PULLUP);   // kapı kapalı = LOW, açık = HIGH / door closed = LOW, open = HIGH
15  pinMode(TITRESIM_PIN, INPUT);
16}
17
18void loop() {
19  int t = digitalRead(TITRESIM_PIN);  // kısa darbeleri kaçırmamak için sürekli oku / read constantly so short pulses are not missed
20  if (t == HIGH && oncekiTitresim == LOW) titresimSayisi++;
21  oncekiTitresim = t;
22
23  if (millis() - sonYazma >= 500) {
24    sonYazma = millis();
25    Serial.print(F("PIR: "));
26    Serial.print(digitalRead(PIR_PIN));
27    Serial.print(F("  Kapi / door: "));
28    Serial.print(digitalRead(KAPI_PIN) == HIGH ? F("ACIK / open") : F("kapali / closed"));
29    Serial.print(F("  Titresim / vibration: "));
30    Serial.print(titresimSayisi);
31    Serial.print(F("  Lazer / laser: "));
32    Serial.println(analogRead(LAZER_PIN));
33    titresimSayisi = 0;
34  }
35}
Con questo test trovi anche la soglia del laser: annota il valore quando il raggio colpisce la LDR (es. 950) e quello con la mano davanti al raggio (es. 250), poi scrivi in LAZER_ESIK del codice dell'allarme il valore intermedio (600). Se la differenza tra i due è inferiore a 300, allunga il tubetto o riallinea il laser.

Macchina a stati: il cervello dell'allarme

Se provi a scrivere un allarme con blocchi if e delay(), diventa presto un groviglio: mentre si aspetta il ritardo di ingresso nessuno legge il tastierino, e mentre suona la sirena non si può digitare il codice. La soluzione è una macchina a stati, in cui il sistema si trova sempre in un solo stato e passa a un altro solo in presenza di eventi precisi. Nel codice gli stati sono definiti con un enum, loop() applica a ogni giro solo le regole dello stato attuale e le attese si misurano con millis() invece di delay(). Così il ciclo gira migliaia di volte al secondo e non si perde nessuna pressione di tasto né alcun evento dei sensori.

StatoQuando si entra?Che cosa fa?Quando si esce?
DISINSERITOAll'accensione e dopo codice + #Osserva i sensori ma non dà mai allarme; il LED verde si accende se tutto è prontoCodice + A o codice + B
RITARDO DI USCITACodice + A (totale) o B (parziale)Conta 30 s; il buzzer suona ogni secondo e accelera negli ultimi 10 sINSERITO allo scadere; DISINSERITO con codice + #
INSERITOAllo scadere del ritardo di uscitaSorveglia tutte le zone; il LED rosso resta accesoINGRESSO se si apre la porta; ALLARME su zona istantanea o interna
RITARDO DI INGRESSOPorta aperta con allarme inseritoIl buzzer suona a intermittenza per 20 s; PIR e laser attendonoDISINSERITO con codice + #; ALLARME allo scadere
ALLARMEUna zona è scattataSuonano sirena e buzzer; la sirena tace dopo 3 min, il LED rosso lampeggia veloceSolo DISINSERITO con codice + #

Inserire, disinserire e il codice sul tastierino

TastiFunzione
2580 + AInserimento totale: tutte e quattro le zone attive, 30 s di ritardo di uscita
2580 + BInserimento parziale (notte): PIR escluso; porta, finestra e laser attivi
2580 + #Disinserisce; se c'è stato un allarme, mostra nel Monitor seriale la zona che è scattata
* o CCancella un codice digitato male
DTest delle zone: stampa nel Monitor seriale i valori di porta, PIR e laser
3 codici erratiIl tastierino si blocca per 60 s; in quel tempo viene rifiutato anche il codice giusto

Chiedere il codice anche per inserire impedisce a bambini o ospiti di attivare il sistema per errore. Il codice esegue anche un controllo di non pronto: se provi a inserire con la porta aperta o il raggio laser interrotto, senti un bip lungo e il sistema non si inserisce, proprio come nelle centrali commerciali. Non lasciare il codice predefinito 2580 ed evita combinazioni facili come 1234, 0000 o il tuo anno di nascita. Se vuoi cambiare il codice senza ricaricare lo sketch, salvalo nella EEPROM e aggiungi un menu che ti permetta di digitarne uno nuovo dal tastierino. Con un display LCD I2C accanto al tastierino vedrai anche il conto alla rovescia e i messaggi di stato; per questo i pin A4 e A5 sono rimasti liberi.

Codice dell'antifurto con Arduino

Il codice qui sotto è il cuore di questa guida. Nella sezione delle impostazioni in alto puoi cambiare il codice di accesso, i ritardi, la durata della sirena, la soglia del laser e il tipo di relè; il resto non va toccato. Su un Arduino Uno occupa circa 6 KB di memoria programma e 620 byte di RAM, quindi resta molto spazio per aggiungere un LCD o un lettore RFID.

C++ (Arduino IDE)
1// Arduino ev alarm sistemi: 4 bölge, keypad, giriş/çıkış gecikmesi, siren / Arduino home alarm: 4 zones, keypad, entry/exit delay, siren
2#include <Keypad.h>
3
4// ---- Ayarlar / Settings ----
5const char SIFRE[] = "2580";                 // kendi şifreni yaz / set your own code
6const unsigned long CIKIS_SURE = 30000;      // çıkış gecikmesi 30 sn / exit delay 30 s
7const unsigned long GIRIS_SURE = 20000;      // giriş gecikmesi 20 sn / entry delay 20 s
8const unsigned long SIREN_SURE = 180000;     // siren en fazla 3 dk çalar / siren sounds for at most 3 min
9const unsigned long PIR_ISINMA = 60000;      // PIR ilk 60 sn yok sayılır / PIR is ignored for the first 60 s
10const unsigned long KILIT_SURE = 60000;      // 3 yanlış şifrede 60 sn kilit / 60 s lockout after 3 wrong codes
11const int LAZER_ESIK = 600;                  // ışınlı ve ışınsız değerin ortası / midpoint of beam and no-beam readings
12const bool LAZER_VAR = true;                 // lazer bariyerin yoksa false yap / set to false without a laser barrier
13const bool ROLE_AKTIF_LOW = true;            // çoğu röle modülü LOW'da çeker / most relay modules switch on with LOW
14
15// ---- Pinler / Pins ----
16const int PIR_PIN = 10;        // HC-SR501 OUT
17const int KAPI_PIN = 11;       // reed, diğer ucu GND / reed, other leg to GND
18const int TITRESIM_PIN = 12;   // SW-420 DO
19const int LAZER_PIN = A0;      // LDR bölücü / LDR divider
20const int BUZZER_PIN = A1;     // aktif buzzer / active buzzer
21const int ROLE_PIN = A2;       // siren rölesi / siren relay
22const int YESIL_LED = A3;      // hazır / ready
23const int KIRMIZI_LED = 13;    // kurulu / armed
24
25const byte SATIR = 4, SUTUN = 4;
26char tuslar[SATIR][SUTUN] = {
27  {'1', '2', '3', 'A'},
28  {'4', '5', '6', 'B'},
29  {'7', '8', '9', 'C'},
30  {'*', '0', '#', 'D'}
31};
32byte satirPinleri[SATIR] = {9, 8, 7, 6};     // R1–R4
33byte sutunPinleri[SUTUN] = {5, 4, 3, 2};     // C1–C4
34Keypad keypad = Keypad(makeKeymap(tuslar), satirPinleri, sutunPinleri, SATIR, SUTUN);
35
36enum Durum { COZULU, CIKIS_GECIKMESI, KURULU, GIRIS_GECIKMESI, ALARM };
37const char* const DURUM_ADI[] = { "COZULU / DISARMED", "CIKIS / EXIT DELAY", "KURULU / ARMED", "GIRIS / ENTRY DELAY", "ALARM" };
38
39struct Giris {                 // gürültü süzgeçli giriş / debounced input
40  bool ham;
41  bool kararli;
42  unsigned long degisim;
43};
44Giris gKapi = {false, false, 0};
45Giris gPir = {false, false, 0};
46Giris gLazer = {false, false, 0};
47
48Durum durum = COZULU;
49unsigned long durumZamani = 0;     // duruma giriş anı / when the state began
50bool evdeModu = false;             // true: PIR devre dışı / true: PIR bypassed
51bool sirenAcik = false;
52bool pirHazir = false;
53const char* tetikleyen = "";       // alarmı başlatan bölge / zone that caused the alarm
54char girilen[9] = "";              // girilen şifre, en fazla 8 hane / typed code, max 8 digits
55byte haneSayisi = 0;
56byte yanlisSayisi = 0;
57bool kilitli = false;
58unsigned long kilitZamani = 0;
59unsigned long bipZamani = 0, bipSuresi = 0;
60unsigned long darbe[3] = {0, 0, 0};          // son 3 titreşim olayı / last 3 vibration events
61int oncekiTitresim = LOW;
62
63// Okuma "sure" ms boyunca aynı kalırsa kabul edilir / a reading is accepted once it stays the same for "sure" ms
64bool suz(Giris &g, bool okuma, unsigned long sure) {
65  unsigned long simdi = millis();
66  if (okuma != g.ham) {
67    g.ham = okuma;
68    g.degisim = simdi;
69  }
70  if (simdi - g.degisim >= sure) g.kararli = g.ham;
71  return g.kararli;
72}
73
74// 3 sn içinde 3 ayrı darbe = zorlama; tek vuruş alarm vermez / 3 separate hits within 3 s = forcing; a single knock is ignored
75bool titresimVar(unsigned long simdi) {
76  int okuma = digitalRead(TITRESIM_PIN);
77  bool sonuc = false;
78  if (okuma == HIGH && oncekiTitresim == LOW && simdi - darbe[2] > 150) {   // 150 ms içindeki sekmeler tek olay / bounces within 150 ms count once
79    darbe[0] = darbe[1];
80    darbe[1] = darbe[2];
81    darbe[2] = simdi;
82    if (darbe[0] != 0 && simdi - darbe[0] <= 3000) sonuc = true;
83  }
84  oncekiTitresim = okuma;
85  return sonuc;
86}
87
88void bip(unsigned long ms) {       // kısa = tuş sesi, uzun = hata / short = key click, long = error
89  bipZamani = millis();
90  bipSuresi = ms;
91}
92
93void sirenYaz(bool acik) {
94  sirenAcik = acik;
95  if (ROLE_AKTIF_LOW) digitalWrite(ROLE_PIN, acik ? LOW : HIGH);
96  else                digitalWrite(ROLE_PIN, acik ? HIGH : LOW);
97}
98
99void durumDegis(Durum yeni) {
100  durum = yeni;
101  durumZamani = millis();
102  Serial.print(F("Durum / state: "));
103  Serial.println(DURUM_ADI[yeni]);
104}
105
106void alarmVer(const char* bolge) {
107  tetikleyen = bolge;
108  sirenYaz(true);
109  durumDegis(ALARM);
110  Serial.print(F("ALARM! Bolge / zone: "));
111  Serial.println(bolge);
112}
113
114bool kurulabilirMi() {             // açık kapı ya da kesik ışınla kurulmaz / never arm with an open door or a broken beam
115  if (gKapi.kararli) {
116    Serial.println(F("Kapi acik, kurulamaz / door open, cannot arm"));
117    return false;
118  }
119  if (LAZER_VAR && gLazer.kararli) {
120    Serial.println(F("Lazer kesik, kurulamaz / beam broken, cannot arm"));
121    return false;
122  }
123  return true;
124}
125
126void kur(bool evde) {
127  if (!kurulabilirMi()) {
128    bip(800);
129    return;
130  }
131  evdeModu = evde;
132  for (int i = 0; i < 3; i++) darbe[i] = 0;
133  tetikleyen = "";
134  durumDegis(CIKIS_GECIKMESI);
135  Serial.println(evde ? F("Evde modu: PIR devre disi / stay mode: PIR bypassed") : F("Tam kurulum / away mode"));
136}
137
138void coz() {
139  sirenYaz(false);
140  if (tetikleyen[0] != '\0') {     // alarm hafızası / alarm memory
141    Serial.print(F("Alarm hafizasi / alarm memory: "));
142    Serial.println(tetikleyen);
143  }
144  durumDegis(COZULU);
145  bip(150);
146}
147
148void sensorRaporu() {
149  Serial.print(F("Kapi / door: "));
150  Serial.print(gKapi.kararli ? F("ACIK / open") : F("kapali / closed"));
151  Serial.print(F("  PIR: "));
152  Serial.print(digitalRead(PIR_PIN));
153  Serial.print(F("  Lazer / laser: "));
154  Serial.println(analogRead(LAZER_PIN));
155}
156
157void tusIsle(char t) {
158  if (t >= '0' && t <= '9') {
159    if (haneSayisi < 8) {
160      girilen[haneSayisi++] = t;
161      girilen[haneSayisi] = '\0';
162    }
163    bip(30);
164    return;
165  }
166  if (t == '*' || t == 'C') {      // girilen kodu sil / clear the typed code
167    haneSayisi = 0;
168    girilen[0] = '\0';
169    bip(30);
170    return;
171  }
172  if (t == 'D') {                  // bölge testi / zone test
173    sensorRaporu();
174    return;
175  }
176
177  // A, B ve # önce şifre ister / A, B and # need the code first
178  bool dogru = strcmp(girilen, SIFRE) == 0;
179  haneSayisi = 0;
180  girilen[0] = '\0';
181  if (kilitli) {
182    Serial.println(F("Tus takimi kilitli / keypad locked"));
183    bip(800);
184    return;
185  }
186  if (!dogru) {
187    Serial.println(F("Yanlis sifre / wrong code"));
188    if (++yanlisSayisi >= 3) {
189      kilitli = true;
190      kilitZamani = millis();
191      yanlisSayisi = 0;
192    }
193    bip(800);
194    return;
195  }
196  yanlisSayisi = 0;
197  if (durum == COZULU && t == 'A')      kur(false);   // tam kurulum / away
198  else if (durum == COZULU && t == 'B') kur(true);    // evde modu / stay
199  else if (durum != COZULU && t == '#') coz();        // çöz / disarm
200  else bip(800);
201}
202
203void cikislariGuncelle() {
204  unsigned long simdi = millis();
205  unsigned long gecen = simdi - durumZamani;
206  bool buzzer = simdi - bipZamani < bipSuresi;   // tuş sesi / key click
207  bool kirmizi = false;
208
209  switch (durum) {
210    case CIKIS_GECIKMESI: {
211      unsigned long periyot = (gecen > CIKIS_SURE - 10000) ? 250 : 1000;   // son 10 sn hızlanır / faster in the last 10 s
212      buzzer = buzzer || (gecen % periyot) < 60;
213      kirmizi = (gecen % 1000) < 500;
214      break;
215    }
216    case KURULU:
217      kirmizi = true;
218      break;
219    case GIRIS_GECIKMESI:
220      buzzer = buzzer || (gecen % 400) < 200;    // "şifreni gir" uyarısı / "enter your code" warning
221      kirmizi = (gecen % 400) < 200;
222      break;
223    case ALARM:
224      buzzer = buzzer || sirenAcik;
225      kirmizi = (gecen % 200) < 100;             // hızlı yanıp sönme = alarm hafızası / fast blink = alarm memory
226      break;
227    default:
228      break;
229  }
230  bool hazir = !gKapi.kararli && !(LAZER_VAR && gLazer.kararli);
231  digitalWrite(BUZZER_PIN, buzzer ? HIGH : LOW);
232  digitalWrite(KIRMIZI_LED, kirmizi ? HIGH : LOW);
233  digitalWrite(YESIL_LED, (durum == COZULU && hazir) ? HIGH : LOW);
234}
235
236void setup() {
237  Serial.begin(9600);
238  sirenYaz(false);                   // pin çıkış olmadan önce "kapalı" yaz, açılışta röle çekmez / write "off" first so the relay doesn't click at boot
239  pinMode(ROLE_PIN, OUTPUT);
240  pinMode(BUZZER_PIN, OUTPUT);
241  pinMode(KIRMIZI_LED, OUTPUT);
242  pinMode(YESIL_LED, OUTPUT);
243  pinMode(PIR_PIN, INPUT);
244  pinMode(KAPI_PIN, INPUT_PULLUP);   // kapı kapalı = LOW; kablo kesilirse HIGH / door closed = LOW; a cut wire reads HIGH
245  pinMode(TITRESIM_PIN, INPUT);
246  keypad.setDebounceTime(20);
247  durumDegis(COZULU);
248  Serial.println(F("PIR 60 sn isiniyor / PIR warming up for 60 s"));
249}
250
251void loop() {
252  char t = keypad.getKey();
253  if (t) tusIsle(t);
254
255  unsigned long simdi = millis();
256  if (kilitli && simdi - kilitZamani >= KILIT_SURE) kilitli = false;
257  if (!pirHazir && simdi >= PIR_ISINMA) pirHazir = true;
258
259  bool kapiAcik = suz(gKapi, digitalRead(KAPI_PIN) == HIGH, 50);
260  bool hareket = suz(gPir, digitalRead(PIR_PIN) == HIGH, 200) && pirHazir;
261  bool isinKesik = LAZER_VAR && suz(gLazer, analogRead(LAZER_PIN) < LAZER_ESIK, 30);
262  bool titresim = titresimVar(simdi);
263
264  switch (durum) {
265    case CIKIS_GECIKMESI:
266      if (simdi - durumZamani >= CIKIS_SURE) durumDegis(KURULU);
267      break;
268    case KURULU:                                   // anlık bölgeler önce / instant zones first
269      if (titresim)                  alarmVer("Pencere (titresim) / window (vibration)");
270      else if (isinKesik)            alarmVer("Lazer bariyeri / laser beam");
271      else if (hareket && !evdeModu) alarmVer("Ic mekan (PIR) / interior (PIR)");
272      else if (kapiAcik)             durumDegis(GIRIS_GECIKMESI);
273      break;
274    case GIRIS_GECIKMESI:                          // PIR ve lazer giriş süresince bekler / PIR and laser wait during the entry delay
275      if (titresim)                                 alarmVer("Pencere (titresim) / window (vibration)");
276      else if (simdi - durumZamani >= GIRIS_SURE)   alarmVer("Giris kapisi / entry door");
277      break;
278    case ALARM:
279      if (sirenAcik && simdi - durumZamani >= SIREN_SURE) {
280        sirenYaz(false);                           // siren susar, hafıza kalır / siren stops, memory stays
281        Serial.println(F("Siren sustu, sifre bekleniyor / siren off, waiting for the code"));
282      }
283      break;
284    default:
285      break;
286  }
287  cikislariGuncelle();
288}

La spina dorsale del codice è la funzione suz(). Ogni volta che il valore grezzo di un ingresso cambia, viene preso un timestamp, e lo stato stabile non cambia finché il valore non resta uguale per un tempo fisso (50 ms per la porta, 200 ms per il PIR, 30 ms per il laser). Questo semplice filtro evita che brevi disturbi sui cavi lunghi e i rimbalzi dei contatti facciano scattare l'allarme. Anche leggere il pin della porta con INPUT_PULLUP è una scelta voluta: con la porta chiusa il reed collega il pin a GND e si legge LOW; quando la porta si apre o qualcuno taglia il cavo, il pin passa a HIGH. Questa logica a circuito chiuso degli allarmi veri tratta un cavo tagliato esattamente come una porta aperta.

Per la vibrazione si usa un metodo diverso. Anche un solo colpo fa rimbalzare il SW-420 più volte in pochi millisecondi; titresimVar() conta prima come un unico evento tutto ciò che avviene entro 150 ms, poi memorizza gli istanti degli ultimi tre eventi. Se tre eventi stanno in 3 secondi, si considera un vero tentativo di forzare la finestra; il vento che scuote la porta o un pallone contro il vetro non fanno scattare nulla. La sirena tace da sola dopo 3 minuti, ma il sistema resta nello stato ALLARME e il LED rosso lampeggia veloce per avvisare chi rientra che c'è stato un allarme. Quando digiti il codice giusto, il nome della zona salvato nella variabile tetikleyen compare nel Monitor seriale: è una piccola versione della memoria d'allarme delle centrali commerciali.

Come regolare i ritardi di ingresso e di uscita

Se i ritardi sono troppo brevi farai scattare il tuo stesso allarme; se sono troppo lunghi regalerai tempo al ladro. Scegli un ritardo di uscita pari al doppio del tempo che ti serve per andare dal tastierino alla porta e chiuderla a chiave, e un ritardo di ingresso pari a circa il doppio del tempo per aprire la porta, raggiungere il tastierino e digitare il codice. Le centrali commerciali usano di solito 30–60 secondi per l'uscita e 15–30 secondi per l'ingresso. Montare il tastierino accanto alla porta, ma non visibile dall'esterno, ti permette di tenere breve il ritardo di ingresso. Conta anche il comportamento del buzzer: in uscita un bip breve ogni secondo accelera negli ultimi 10 secondi per farti sentire che il tempo sta finendo; in ingresso un bip intermittente da 200 ms ti ricorda di digitare il codice.

Collegare la sirena e il relè

Nel circuito della sirena l'Arduino comanda solo la bobina del relè; la corrente a 12 V della sirena passa dai contatti del relè. La maggior parte dei moduli relè è attiva bassa, cioè si eccita quando il pin IN è LOW; per questo nel codice c'è la costante ROLE_AKTIF_LOW. Il codice scrive il livello di spento prima di configurare il pin come uscita, così all'avvio dell'Arduino il relè non scatta per un istante e la sirena non emette un breve trillo. Quando scegli la sirena guarda l'assorbimento: le piccole sirene a 12 V da 110 dB assorbono di solito 0,1–0,3 A, un carico minimo per un contatto da 10 A. Una sirena esterna con lampeggiante dissuade con suono e luce e mostra ai vicini da dove arriva l'allarme. Le centrali professionali di solito spengono la sirena dopo 3 minuti e in alcuni paesi esiste anche un limite di legge, quindi tieni SIREN_SURE su un valore ragionevole. Se preferisci un interruttore a stato solido al posto del relè, puoi usare un MOSFET logic level e, per le sirene a bobina (elettromagnetiche), aggiungere un diodo in inversa ai capi dei morsetti.

Avvisi Telegram con l'ESP32

Una sirena che nessuno sente non serve a niente; il modo più semplice per sapere che l'allarme è scattato mentre sei fuori è un ESP32 con Telegram. L'ESP32 si collega al Wi-Fi e invia messaggi al server di Telegram con una connessione cifrata, e tu puoi anche mandargli dei comandi. Abbiamo spiegato passo dopo passo come creare un bot con BotFather, ottenere il token e il tuo chat ID e installare le librerie UniversalTelegramBot e ArduinoJson nell'articolo bot Telegram con ESP32. Questa versione non ha il tastierino; si comanda dal telefono con i comandi /kur (inserimento totale), /evde (inserimento parziale), /coz (disinserire) e /durum (stato). Il bot risponde solo al tuo chat ID e ignora in silenzio i comandi di chiunque altro.

C++ (Arduino IDE)
1// ESP32 alarm sistemi + Telegram: kurma/çözme komutları ve anlık bildirim / ESP32 alarm + Telegram: arm/disarm commands and instant alerts
2#include <WiFi.h>
3#include <WiFiClientSecure.h>
4#include <UniversalTelegramBot.h>        // ArduinoJson kütüphanesi de gerekir / also needs ArduinoJson
5#include <Preferences.h>                 // kurulu durumu elektrik kesintisinde unutmamak için / remembers the armed state across power cuts
6
7const char* WIFI_SSID = "AgAdi";         // Wi-Fi adın (yalnız 2,4 GHz) / your Wi-Fi name (2.4 GHz only)
8const char* WIFI_PASS = "Sifre123";      // Wi-Fi şifren / your Wi-Fi password
9#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"   // BotFather'dan / from BotFather
10#define CHAT_ID   "123456789"            // yalnız bu ID komut verebilir / only this ID may send commands
11
12const int PIR_PIN = 27;                  // HC-SR501 OUT
13const int KAPI_PIN = 33;                 // reed, diğer ucu GND / reed, other leg to GND
14const int TITRESIM_PIN = 32;             // SW-420 DO
15const int BUZZER_PIN = 25;               // aktif buzzer / active buzzer
16const int ROLE_PIN = 26;                 // siren rölesi / siren relay
17const int LED_PIN = 2;                   // dahili LED: kurulu / onboard LED: armed
18const bool ROLE_AKTIF_LOW = true;        // çoğu röle modülü LOW'da çeker / most relay modules switch on with LOW
19
20const unsigned long CIKIS_SURE = 30000;       // çıkış gecikmesi / exit delay
21const unsigned long GIRIS_SURE = 20000;       // giriş gecikmesi / entry delay
22const unsigned long SIREN_SURE = 180000;      // siren en fazla 3 dk / siren for at most 3 min
23const unsigned long PIR_ISINMA = 60000;       // PIR ısınma süresi / PIR warm-up time
24const unsigned long KONTROL_ARALIGI = 2000;   // Telegram'a 2 sn'de bir sor / poll Telegram every 2 s
25
26WiFiClientSecure client;
27UniversalTelegramBot bot(BOT_TOKEN, client);
28Preferences ayar;
29
30enum Durum { COZULU, CIKIS_GECIKMESI, KURULU, GIRIS_GECIKMESI, ALARM };
31const char* const DURUM_ADI[] = { "ÇÖZÜLÜ / DISARMED", "ÇIKIŞ GECİKMESİ / EXIT DELAY", "KURULU / ARMED", "GİRİŞ GECİKMESİ / ENTRY DELAY", "ALARM" };
32
33struct Giris {                           // gürültü süzgeçli giriş / debounced input
34  bool ham;
35  bool kararli;
36  unsigned long degisim;
37};
38Giris gKapi = {false, false, 0};
39Giris gPir = {false, false, 0};
40
41Durum durum = COZULU;
42unsigned long durumZamani = 0;
43bool evdeModu = false;
44bool sirenAcik = false;
45bool pirHazir = false;
46String tetikleyen = "";
47String bekleyen = "";                    // gönderilemeyen bildirimler / notifications not sent yet
48unsigned long sonDeneme = 0;
49unsigned long sonKontrol = 0;
50
51volatile unsigned long darbe[3] = {0, 0, 0};   // son 3 titreşim olayı / last 3 vibration events
52volatile bool zorlama = false;                 // 3 sn'de 3 darbe oldu / 3 hits within 3 s
53
54// Kesme: Telegram beklerken de kısa darbeler kaçmaz / interrupt: short pulses are caught even while Telegram blocks
55void IRAM_ATTR titresimKesmesi() {
56  unsigned long simdi = millis();
57  if (simdi - darbe[2] > 150) {          // 150 ms içindeki sekmeler tek olay / bounces within 150 ms count once
58    darbe[0] = darbe[1];
59    darbe[1] = darbe[2];
60    darbe[2] = simdi;
61    if (darbe[0] != 0 && simdi - darbe[0] <= 3000) zorlama = true;
62  }
63}
64
65bool suz(Giris &g, bool okuma, unsigned long sure) {
66  unsigned long simdi = millis();
67  if (okuma != g.ham) {
68    g.ham = okuma;
69    g.degisim = simdi;
70  }
71  if (simdi - g.degisim >= sure) g.kararli = g.ham;
72  return g.kararli;
73}
74
75void sirenYaz(bool acik) {
76  sirenAcik = acik;
77  if (ROLE_AKTIF_LOW) digitalWrite(ROLE_PIN, acik ? LOW : HIGH);
78  else                digitalWrite(ROLE_PIN, acik ? HIGH : LOW);
79}
80
81void bekleyeniGonder() {
82  sonDeneme = millis();
83  if (WiFi.status() != WL_CONNECTED) {
84    WiFi.reconnect();                    // bağlantı koptuysa yeniden dene / reconnect if the link dropped
85    return;
86  }
87  if (bot.sendMessage(CHAT_ID, bekleyen, "")) bekleyen = "";
88}
89
90void bildir(const String& metin) {       // gönderemezse 15 sn sonra yeniden dener / retries after 15 s if sending fails
91  Serial.println(metin);
92  bekleyen += metin + "\n";
93  if (bekleyen.length() > 1200) bekleyen = bekleyen.substring(bekleyen.length() - 1200);
94  bekleyeniGonder();
95}
96
97void durumDegis(Durum yeni) {
98  durum = yeni;
99  durumZamani = millis();
100  if (yeni == KURULU || yeni == COZULU) {   // yalnız kurma ve çözmede yazılır / written only on arm and disarm
101    ayar.putBool("kurulu", yeni == KURULU);
102    ayar.putBool("evde", evdeModu);
103  }
104}
105
106void alarmVer(const String& bolge) {
107  tetikleyen = bolge;
108  sirenYaz(true);                        // önce siren, sonra mesaj / siren first, message second
109  durumDegis(ALARM);
110  bildir("ALARM! " + bolge);
111}
112
113void kur(bool evde) {
114  if (gKapi.kararli) {
115    bildir("Kapı açık, kurulamadı / door open, not armed");
116    return;
117  }
118  evdeModu = evde;
119  for (int i = 0; i < 3; i++) darbe[i] = 0;
120  zorlama = false;
121  tetikleyen = "";
122  durumDegis(CIKIS_GECIKMESI);
123  bildir(evde ? "Evde modu, 30 sn sonra kurulu (PIR kapalı) / stay mode, armed in 30 s (PIR off)"
124              : "Tam kurulum, 30 sn içinde çık / away mode, leave within 30 s");
125}
126
127void coz() {
128  sirenYaz(false);
129  String m = "Alarm çözüldü / disarmed";
130  if (tetikleyen.length()) m += "\nAlarm hafızası / memory: " + tetikleyen;
131  tetikleyen = "";
132  durumDegis(COZULU);
133  bildir(m);
134}
135
136String durumMetni() {
137  String s = String("Durum / state: ") + DURUM_ADI[durum];
138  if (evdeModu && durum != COZULU) s += " (evde / stay)";
139  s += "\nKapı / door: ";
140  s += gKapi.kararli ? "AÇIK / open" : "kapalı / closed";
141  s += "\nWi-Fi: " + String(WiFi.RSSI()) + " dBm";
142  return s;
143}
144
145void mesajlariIsle(int adet) {
146  for (int i = 0; i < adet; i++) {
147    String chatId = bot.messages[i].chat_id;
148    String metin = bot.messages[i].text;
149    if (chatId != CHAT_ID) continue;     // yabancıya yanıt yok / strangers get no answer
150
151    int at = metin.indexOf('@');         // "/kur@bot_adi" -> "/kur"
152    if (at > 0) metin = metin.substring(0, at);
153
154    if (metin == "/kur" && durum == COZULU)        kur(false);
155    else if (metin == "/evde" && durum == COZULU)  kur(true);
156    else if (metin == "/coz" && durum != COZULU)   coz();
157    else if (metin == "/durum")                    bildir(durumMetni());
158    else bot.sendMessage(chatId, "/kur  /evde  /coz  /durum\n" + durumMetni(), "");
159  }
160}
161
162void setup() {
163  Serial.begin(115200);
164  pinMode(ROLE_PIN, OUTPUT);
165  sirenYaz(false);                       // açılışta siren kapalı / siren off at boot
166  pinMode(BUZZER_PIN, OUTPUT);
167  pinMode(LED_PIN, OUTPUT);
168  pinMode(PIR_PIN, INPUT);
169  pinMode(KAPI_PIN, INPUT_PULLUP);       // kapı kapalı = LOW, açık ya da kablo kesik = HIGH / closed = LOW, open or cut = HIGH
170  pinMode(TITRESIM_PIN, INPUT);
171  attachInterrupt(digitalPinToInterrupt(TITRESIM_PIN), titresimKesmesi, RISING);
172
173  ayar.begin("alarm", false);
174  if (ayar.getBool("kurulu", false)) {   // kesintiden önce kuruluysa kurulu başla / start armed if it was armed before the outage
175    evdeModu = ayar.getBool("evde", false);
176    durumDegis(KURULU);
177  }
178
179  WiFi.mode(WIFI_STA);
180  WiFi.begin(WIFI_SSID, WIFI_PASS);
181  unsigned long bas = millis();
182  while (WiFi.status() != WL_CONNECTED && millis() - bas < 20000) delay(500);   // Wi-Fi yoksa alarm yine çalışır / the alarm works without Wi-Fi too
183  client.setCACert(TELEGRAM_CERTIFICATE_ROOT);   // api.telegram.org kök sertifikası / root certificate of api.telegram.org
184  if (WiFi.status() == WL_CONNECTED) {
185    while (bot.getUpdates(bot.last_message_received + 1)) { }   // kapalıyken gelen eski komutları atla / skip commands sent while off
186  }
187  bildir(String("Sistem açıldı (elektrik geldi mi?) / system started (power restored?)\n") + durumMetni());
188}
189
190void loop() {
191  unsigned long simdi = millis();
192  if (!pirHazir && simdi >= PIR_ISINMA) pirHazir = true;
193  bool kapiAcik = suz(gKapi, digitalRead(KAPI_PIN) == HIGH, 50);
194  bool hareket = suz(gPir, digitalRead(PIR_PIN) == HIGH, 200) && pirHazir;
195  bool titresim = zorlama;
196  zorlama = false;
197
198  switch (durum) {
199    case CIKIS_GECIKMESI:
200      if (simdi - durumZamani >= CIKIS_SURE) {
201        durumDegis(KURULU);
202        bildir("Sistem kuruldu / system armed");
203      }
204      break;
205    case KURULU:
206      if (titresim)                  alarmVer("Pencere (titreşim) / window (vibration)");
207      else if (hareket && !evdeModu) alarmVer("İç mekân (PIR) / interior (PIR)");
208      else if (kapiAcik) {
209        durumDegis(GIRIS_GECIKMESI);
210        bildir("Kapı açıldı, 20 sn giriş süresi / door opened, 20 s entry delay");
211      }
212      break;
213    case GIRIS_GECIKMESI:
214      if (titresim)                                alarmVer("Pencere (titreşim) / window (vibration)");
215      else if (simdi - durumZamani >= GIRIS_SURE)  alarmVer("Giriş kapısı / entry door");
216      break;
217    case ALARM:
218      if (sirenAcik && simdi - durumZamani >= SIREN_SURE) {
219        sirenYaz(false);
220        bildir("Siren sustu, alarm hafızada / siren stopped, alarm kept in memory");
221      }
222      break;
223    default:
224      break;
225  }
226
227  unsigned long gecen = millis() - durumZamani;
228  bool buzzer = false;
229  if (durum == CIKIS_GECIKMESI)      buzzer = (gecen % 1000) < 60;
230  else if (durum == GIRIS_GECIKMESI) buzzer = (gecen % 400) < 200;
231  else if (durum == ALARM)           buzzer = sirenAcik;
232  digitalWrite(BUZZER_PIN, buzzer ? HIGH : LOW);
233  digitalWrite(LED_PIN, (durum == KURULU || durum == ALARM) ? HIGH : LOW);
234
235  if (millis() - sonKontrol > KONTROL_ARALIGI && WiFi.status() == WL_CONNECTED) {
236    int adet = bot.getUpdates(bot.last_message_received + 1);
237    while (adet) {
238      mesajlariIsle(adet);
239      adet = bot.getUpdates(bot.last_message_received + 1);
240    }
241    sonKontrol = millis();
242  }
243  if (bekleyen.length() && millis() - sonDeneme > 15000) bekleyeniGonder();
244}

Il codice per ESP32 ha tre differenze importanti. La prima: inviare un messaggio a Telegram o chiedere i nuovi comandi può richiedere 1–2 secondi, e intanto loop() aspetta. Una porta che resta aperta e un PIR che resta HIGH per almeno qualche secondo non ne risentono, ma gli impulsi di pochi millisecondi del SW-420 potrebbero andare persi; per questo la vibrazione viene catturata con un interrupt (attachInterrupt). La seconda: alarmVer() accende prima la sirena e solo dopo invia il messaggio, quindi per quanto lenta sia la connessione la sirena non è mai in ritardo. Le notifiche non inviate finiscono in una coda e vengono ritentate ogni 15 secondi. La terza: la libreria Preferences salva nella memoria flash se il sistema è inserito; se la corrente salta e torna mentre è inserito, l'ESP32 riparte inserito e ti avvisa che la corrente è tornata. Se relè e sirena condividono con l'ESP32 un alimentatore debole, la scheda può riavviarsi con l'errore Brownout detector was triggered; la soluzione è nell'articolo l'ESP32 si riavvia di continuo.

Il Wi-Fi non è sempre affidabile: durante un blackout si spegne anche il router, e un ladro può semplicemente staccarlo. Collegare il router a un piccolo UPS o inviare SMS come canale di riserva con un modulo GSM SIM800L fa sì che l'avviso arrivi in ogni caso. Se vuoi anche una foto quando viene rilevato un movimento, guarda inviare foto su Telegram con l'ESP32-CAM.

Allarme con Raspberry Pi Pico e MicroPython

Puoi realizzare la stessa macchina a stati su un Raspberry Pi Pico con un breve programma MicroPython. Il tastierino si legge con la funzione di scansione righe-colonne della guida al tastierino; le righe sono su GP2–GP5 e le colonne, con pull-up interno, su GP6–GP9. Alimenta il PIR da VBUS (5 V); la sua uscita è già a 3,3 V, quindi va direttamente su GP16. Il SW-420 invece va alimentato sempre dal pin 3V3(OUT), perché l'uscita di un modulo alimentato a 5 V può danneggiare l'ingresso del Pico. Gli impulsi di vibrazione vengono catturati con Pin.irq e i loro istanti salvati in una lista, mentre gli stati sono gestiti con semplici stringhe. I tempi si calcolano con time.ticks_diff(), perché questa funzione dà la differenza corretta anche quando il contatore ricomincia da capo.

MicroPython
1# Raspberry Pi Pico alarm: keypad + PIR + kapı + titreşim, durum makinesi / Pico alarm: keypad + PIR + door + vibration, state machine
2from machine import Pin
3import time
4
5SIFRE = "2580"                            # kendi şifreni yaz / set your own code
6CIKIS_MS, GIRIS_MS, SIREN_MS = 30000, 20000, 180000
7
8KEYS = ["123A", "456B", "789C", "*0#D"]
9rows = [Pin(n, Pin.OUT, value=1) for n in (2, 3, 4, 5)]       # R1–R4
10cols = [Pin(n, Pin.IN, Pin.PULL_UP) for n in (6, 7, 8, 9)]    # C1–C4
11pir = Pin(16, Pin.IN)                     # HC-SR501 OUT (3,3 V)
12kapi = Pin(15, Pin.IN, Pin.PULL_UP)       # reed, diğer ucu GND / reed, other leg to GND
13titresim = Pin(17, Pin.IN)                # SW-420 DO, modül 3V3'ten / module powered from 3V3
14buzzer = Pin(14, Pin.OUT, value=0)        # aktif buzzer / active buzzer
15role = Pin(13, Pin.OUT, value=1)          # aktif LOW röle: 1 = kapalı / active-LOW relay: 1 = off
16led = Pin("LED", Pin.OUT)                 # dahili LED: kurulu / onboard LED: armed
17
18darbeler = []                             # son titreşim zamanları / recent vibration times
19son_darbe = 0
20
21
22def titresim_irq(p):
23    global son_darbe
24    simdi = time.ticks_ms()
25    if time.ticks_diff(simdi, son_darbe) > 150:     # sekmeler tek olay / bounces count once
26        son_darbe = simdi
27        darbeler.append(simdi)
28
29
30titresim.irq(trigger=Pin.IRQ_RISING, handler=titresim_irq)
31
32
33def tus_oku():
34    for r, row in enumerate(rows):
35        row.value(0)                      # bu satırı etkinleştir / activate this row
36        for c, col in enumerate(cols):
37            if col.value() == 0:
38                row.value(1)
39                return KEYS[r][c]
40        row.value(1)
41    return None
42
43
44durum = "COZULU"
45zaman = time.ticks_ms()
46acilis = time.ticks_ms()
47pir_hazir = False
48evde = False
49girilen = ""
50son_tus = None
51
52
53def degis(yeni):
54    global durum, zaman
55    durum, zaman = yeni, time.ticks_ms()
56    print("Durum / state:", yeni)
57
58
59def alarm(bolge):
60    role.value(0)                         # siren açık / siren on
61    degis("ALARM")
62    print("ALARM! Bolge / zone:", bolge)
63
64
65while True:
66    tus = tus_oku()
67    if tus != son_tus:                    # değişimi 20 ms sonra doğrula / confirm the change after 20 ms
68        time.sleep_ms(20)
69        if tus_oku() != tus:
70            tus = son_tus                 # sekme, yok say / contact bounce, ignore it
71    if tus and tus != son_tus:            # her basış bir kez / each press once
72        if tus in "0123456789":
73            girilen = (girilen + tus)[-8:]
74        elif tus in "*C":
75            girilen = ""
76        else:
77            dogru = girilen == SIFRE
78            girilen = ""
79            if not dogru:
80                print("Yanlis sifre / wrong code")
81            elif durum == "COZULU" and tus in "AB":
82                if kapi.value():
83                    print("Kapi acik, kurulamaz / door open, cannot arm")
84                else:
85                    evde = tus == "B"     # B = evde modu, PIR kapalı / B = stay mode, PIR off
86                    darbeler.clear()
87                    degis("CIKIS")
88            elif durum != "COZULU" and tus == "#":
89                role.value(1)
90                degis("COZULU")
91    son_tus = tus
92
93    simdi = time.ticks_ms()
94    gecen = time.ticks_diff(simdi, zaman)
95    if not pir_hazir and time.ticks_diff(simdi, acilis) > 60000:
96        pir_hazir = True                  # PIR ısındı / PIR warmed up
97    kapi_acik = kapi.value() == 1
98    hareket = pir_hazir and pir.value() == 1 and not evde
99    darbeler[:] = [t for t in darbeler if time.ticks_diff(simdi, t) < 3000]
100    zorlama = len(darbeler) >= 3          # 3 sn'de 3 darbe / 3 hits in 3 s
101
102    if durum == "CIKIS" and gecen >= CIKIS_MS:
103        degis("KURULU")
104    elif durum == "KURULU":
105        if zorlama:
106            alarm("Pencere / window")
107        elif hareket:
108            alarm("PIR")
109        elif kapi_acik:
110            degis("GIRIS")
111    elif durum == "GIRIS":
112        if zorlama:
113            alarm("Pencere / window")
114        elif gecen >= GIRIS_MS:
115            alarm("Kapi / door")
116    elif durum == "ALARM" and role.value() == 0 and gecen >= SIREN_MS:
117        role.value(1)                     # siren susar, hafıza kalır / siren stops, memory stays
118
119    gecen = time.ticks_diff(time.ticks_ms(), zaman)
120    if durum == "CIKIS":
121        buzzer.value(1 if gecen % 1000 < 60 else 0)
122    elif durum == "GIRIS":
123        buzzer.value(1 if gecen % 400 < 200 else 0)
124    else:
125        buzzer.value(1 if durum == "ALARM" and role.value() == 0 else 0)
126    led.value(1 if durum in ("KURULU", "ALARM") else 0)
127    time.sleep_ms(10)

Come ridurre i falsi allarmi

Il peggior nemico di un sistema d'allarme non è il ladro, ma il falso allarme: una sirena che ha suonato tre volte per niente non la prende più sul serio nessuno, né tu né i vicini. Una parte delle misure seguenti è già nel codice; il resto riguarda montaggio e cablaggio:

  • Aspetta il riscaldamento: Il PIR può scattare da solo nei primi 30–60 secondi dopo l'accensione; il codice ignora il primo minuto.
  • Usa un filtro: Aspettare che ogni ingresso resti stabile per un certo tempo (antirimbalzo) elimina i brevi disturbi sui cavi.
  • Conta i colpi: Cercare 3 colpi in 3 secondi invece di uno solo scarta il vento, i camion che passano e le porte sbattute. Se serve, porta il conteggio a 4–5 o abbassa la sensibilità con il trimmer.
  • Posiziona bene il PIR: Non puntarlo verso termosifoni, condizionatori, finestre soleggiate o tende; rileva meglio quando il movimento attraversa il campo visivo di lato. Se hai animali, montalo in alto, inclinalo un po' verso il basso e abbassa la sensibilità.
  • Richiedi due sensori: Nelle zone critiche, pretendere che PIR e laser, o PIR e porta, scattino a pochi secondi di distanza (zone incrociate) impedisce che un singolo sensore difettoso provochi un allarme.
  • Pulisci l'alimentazione: Il calo di tensione quando il relè si eccita può far scattare il PIR. Metti condensatori da 100 µF e 100 nF tra VCC e GND di ogni sensore e fai passare i cavi di relè e sirena lontano da quelli dei sensori.
  • Usa pull-up esterne sui cavi lunghi: La pull-up interna dell'Arduino è di 20–50 kΩ e raccoglie disturbi su 10 metri di cavo; una resistenza esterna da 4,7 kΩ e un condensatore da 100 nF sul pin rendono la linea molto più robusta.
  • Proteggi il laser dalla luce ambiente: Metti la LDR dentro un tubetto nero e prova la soglia sia di giorno sia di notte.
Prima di affidarti al sistema, fai un walk test: scollega la sirena dal morsetto COM del relè, inserisci l'allarme e percorri la casa come farebbe un ladro. Osserva nel Monitor seriale quale zona scatta e quando, poi lascialo funzionare qualche giorno solo con il buzzer per controllare che non scatti da solo.

Errori comuni e soluzioni

  • L'allarme scatta appena inserito: Il PIR si sta ancora scaldando o il contatto della porta è collegato male. Nel test dei sensori la porta chiusa deve leggere LOW; se legge HIGH, il magnete è troppo lontano o un filo è interrotto.
  • Il tastierino non risponde o legge il tasto sbagliato: I fili di righe e colonne sono invertiti. Invece di ricablare, cambia l'ordine degli array satirPinleri e sutunPinleri nel codice.
  • Si sente un bip lungo quando provi a inserire: Non è un guasto ma l'avviso di non pronto: la porta è aperta o il raggio laser non arriva alla LDR. Premi D per vedere lo stato delle zone.
  • Il relè scatta per un attimo all'accensione: Il livello di spento non è stato scritto prima di impostare il pin come uscita, oppure ROLE_AKTIF_LOW è rimasto true con un relè attivo alto.
  • L'Arduino si resetta quando suona la sirena: Sirena e Arduino condividono un alimentatore debole e il calo di tensione riavvia la scheda. Usa un alimentatore più potente, un convertitore separato e cavi più spessi; dettagli in Arduino si resetta da solo.
  • Il sensore di vibrazione non scatta mai o scatta sempre: Ruota lentamente il trimmer per regolare la soglia e incolla bene il sensore al vetro o al telaio; un sensore fissato male non trasmette il colpo.
  • L'ESP32 non invia messaggi: Il token o il chat ID sono sbagliati, la rete è a 5 GHz o non hai mai avviato il bot su Telegram. Un bot non può scriverti finché non gli hai mandato /start almeno una volta.
  • Il laser va bene di giorno ma di notte dà falsi allarmi (o il contrario): La soglia è troppo vicina al livello di luce ambiente. Misura i valori con e senza raggio in entrambe le condizioni e scegli un nuovo LAZER_ESIK a metà.

Idee per migliorare il tuo antifurto

  • Inserire e disinserire con una tessera: Aggiungi un lettore RFID RC522 accanto al tastierino e disinserisci con un portachiavi.
  • Zone 24 ore: Con un sensore di gas MQ-2 o un sensore di fiamma crea una zona gas e incendio che dà l'allarme anche a sistema disinserito.
  • Contatti porta senza fili: Realizza contatti a batteria per le finestre lontane con moduli RF a 433 MHz.
  • Pannello con display: Mostra conto alla rovescia, stato e memoria d'allarme su un LCD I2C.
  • Integrazione domotica: Collega l'ESP32 a MQTT e Home Assistant e gestisci l'allarme insieme a luci e telecamere.
  • Contatto antimanomissione (tamper): Metti un microinterruttore sul coperchio della scatola, così il buzzer suona se qualcuno la apre, anche a sistema disinserito.
  • Progetto con plastico: Riunisci tutte queste zone in una casa in miniatura come livello di sicurezza di un progetto di casa intelligente.

Poiché unisce lettura dei sensori, gestione dei tempi, macchina a stati e progettazione dell'alimentazione in un solo progetto, un antifurto con Arduino è anche un ottimo modo per imparare. Inizia dal test dei sensori, prova poi l'allarme per qualche giorno solo con il buzzer e collega la sirena per ultima. Nell'app BlueGrays trovi progetti come il sensore porta con reed, il sistema di sicurezza laser, la serratura a codice con tastierino 4x4 e LCD e il sistema d'allarme di movimento ESP32 con avvisi Telegram, ognuno con schemi e codice passo dopo passo.

Domande frequenti

Come si fa un antifurto con Arduino?

Colleghi a un Arduino Uno un sensore di movimento PIR, un contatto reed per la porta, un tastierino, un buzzer attivo e un modulo relè che comanda una sirena. Il codice è una macchina a stati che porta il sistema tra disinserito, ritardo di uscita, inserito, ritardo di ingresso e allarme. Si inserisce e disinserisce con un codice sul tastierino e, se un sensore scatta con l'allarme inserito, suona la sirena.

Quali sensori servono per un allarme con Arduino?

I più usati sono un sensore di movimento PIR (HC-SR501) per gli interni, contatti magnetici reed (MC-38) per porte e finestre, un sensore di vibrazione SW-420 per i colpi sui vetri e una barriera laser per i corridoi. Si possono aggiungere un MQ-2 per il gas e un sensore di fiamma per gli incendi. Ogni sensore è definito come una zona separata con un proprio comportamento.

Un antifurto con Arduino funziona se manca la corrente?

No, se è alimentato solo da un alimentatore da rete. Con un alimentatore per allarmi da 13,8 V con batteria e una batteria da 12 V e 7 Ah, un sistema con Uno che assorbe circa 100 mA funziona per 4–5 giorni. Nella versione ESP32 lo stato inserito viene salvato in flash, quindi al ritorno della corrente il sistema riparte inserito; perché gli avvisi partano, anche il router deve essere sotto UPS.

Un allarme con Arduino può mandare notifiche al telefono?

Un Arduino Uno da solo non può, ma un ESP32 invia messaggi Telegram istantanei via Wi-Fi e riceve dal telefono comandi come /kur e /coz. Dove non c'è internet, un modulo GSM SIM800L può inviare SMS o fare una chiamata. Usare entrambi i metodi insieme è la soluzione più affidabile.

Perché il sensore PIR dà falsi allarmi?

Le cause più comuni sono il riscaldamento di 30–60 secondi dopo l'accensione, fonti di calore come termosifoni e condizionatori, tende che si muovono scaldate dal sole e i disturbi sull'alimentazione quando commuta un relè. Ignorare il riscaldamento nel codice, tenere il sensore lontano dalle fonti di calore e mettere un condensatore tra VCC e GND risolve la maggior parte dei casi.

Un antifurto con Arduino si può usare in una casa vera?

Funziona bene per imparare e per sorvegliare una stanza, un garage o una cantina, ma non sostituisce un sistema d'allarme certificato. I sistemi commerciali sono collaudati secondo norme come la EN 50131 per antimanomissione, alimentazione di riserva, comunicazioni radio cifrate e collegamento a una centrale di vigilanza. Se usi un sistema con Arduino in casa, ti consigliamo di aggiungere una batteria di riserva, un contatto antimanomissione e un secondo canale di avviso.

Vedi tutti i progetti
Sistema di Allarme Movimento ESP32 (PIR + Buzzer + LED + Telegram)
#145Avanzato
Premium

Sistema di Allarme Movimento ESP32 (PIR + Buzzer + LED + Telegram)

Un sistema di allarme di sicurezza intelligente che attiva buzzer e LED quando il sensore PIR rileva movimento, inviando contemporaneamente una notifica istantanea al proprietario tramite Telegram. Dispone di un tempo di attesa di 30 secondi per limitare la frequenza degli allarmi ripetuti; questo meccanismo non verifica la validità dei rilevamenti del sensore PIR né elimina le false attivazioni. Adatto a sicurezza domestica, monitoraggio di garage, magazzini o studi. Con l'ESP32-CAM è possibile

0.0(0)
9
Sistema di sicurezza laser
#23Avanzato
Premium

Sistema di sicurezza laser

Come nelle scene di furti con i laser dei film: un raggio laser colpisce un sensore di luce, e appena qualcuno ci passa in mezzo e interrompe il raggio, si accende il LED rosso e il buzzer comincia a

4.8(60)
77
Sistema di allarme laser
#22Intermedio
Premium

Sistema di allarme laser

Costruisci una barriera di raggi invisibile, proprio come nei film. Un laser punta su un sensore di luce dall'altra parte; appena qualcuno passa e interrompe il raggio, il LED si accende e il buzzer s

4.8(64)
83
Sensore porta con contatto reed
#38Principiante
Premium

Sensore porta con contatto reed

Capisci se la tua porta è aperta o chiusa guardando una luce. Metti un piccolo magnete sulla porta e sul telaio un interruttore che sente i magneti; a porta chiusa stanno vicini e si accende il LED ve

4.7(34)
44
Serratura con password, tastierino 4x4 e LCD
#64Avanzato
Premium

Serratura con password, tastierino 4x4 e LCD

Una piccola serratura elettronica che non fa entrare chi non conosce il codice. Premi il tasto cancelletto e digiti il codice sul tastierino; se è giusto, lo schermo indica accesso consentito, si acce

4.8(59)
75
Rilevatore di movimento con sensore di movimento
#24Principiante
Premium

Rilevatore di movimento con sensore di movimento

Un semplice guardiano che ti avvisa quando qualcuno entra nella stanza. Il sensore di movimento percepisce il calore emesso dal corpo umano, e l'Arduino tiene acceso il LED finché c'è movimento e lo s

4.6(47)
67
Cassaforte elettronica intelligente
#92Avanzato
Premium

Cassaforte elettronica intelligente

Una piccola cassaforte per le tue cose di valore che si apre con un codice. Digiti il tuo codice a quattro cifre sul tastierino; se è giusto, il servomotore apre la serratura, si accende la luce verde

4.7(42)
63