Ratgeber · 17 Min. Lesezeit

Arduino Alarmanlage bauen: PIR, Türkontakt, Keypad und Sirene

Arduino Alarmanlage selber bauen: PIR, Reedkontakt, Erschütterungs- und Lasermelder, Keypad-PIN, Ein- und Ausgangsverzögerung, Sirene und ESP32-Telegram.

Eine Arduino Alarmanlage ist eine Sicherheitstechnik zum Selberbauen: Sie schaltet eine Sirene ein, wenn jemand unbefugt deine Wohnung oder dein Zimmer betritt, gibt dir beim Öffnen der Tür ein paar Sekunden für deine PIN und schickt dir auf Wunsch sofort eine Nachricht aufs Handy. In dieser Anleitung baust du eine Anlage mit vier Zonen: einen PIR-Bewegungsmelder, einen Reedkontakt an der Tür, einen Erschütterungssensor, der Schläge auf die Fensterscheibe erkennt, und eine Laser-Lichtschranke quer durch den Flur. Du schaltest das System per PIN am 4x4-Keypad scharf und unscharf, steuerst eine 12-V-Sirene über ein Relais und erledigst alles mit einem einzigen Arduino Uno.

Schritt für Schritt bauen wir eine kleine Kopie dessen, was kommerzielle Alarmzentralen können: Ein- und Ausgangsverzögerung, Intern-scharf (Nachtmodus), Sperre nach falschen PINs, begrenzte Sirenendauer und Alarmspeicher. Den Code schreiben wir nicht als verschachtelte if-Blöcke, sondern als Zustandsautomaten; so ist jederzeit klar, was die Anlage gerade tut. Danach überträgst du dieselbe Logik auf den ESP32, um per Telegram scharf und unscharf zu schalten und Alarmmeldungen zu bekommen, schreibst eine MicroPython-Version für den Raspberry Pi Pico und lernst, wie du das lästigste Problem überhaupt in den Griff bekommst: Fehlalarme.

HC-SR501 PIR-Bewegungsmelder
Der PIR-Bewegungsmelder HC-SR501 ist die Innenzone der Alarmanlage: Seine weiße Fresnel-Linse teilt das Sichtfeld in Dutzende kleine Bereiche, mit den beiden Potis daneben stellst du Reichweite und Haltezeit ein.

Wie funktioniert eine Arduino Alarmanlage?

Jede Alarmanlage besteht aus drei Teilen: Eingängen (Sensoren), einem Gehirn, das entscheidet (dem Arduino), und Ausgängen (Sirene, Summer, Benachrichtigung). Jeder Sensor überwacht eine Zone. Professionelle Zentralen teilen Zonen danach ein, wie sie beim Auslösen reagieren, und genau diese Unterscheidung trennt eine gute Alarmanlage von einer nervigen Bastelei, die bei jedem Türöffnen losheult. Unser System hat vier Zonen:

ZoneSensorZonentypWenn ausgelöst im Scharfzustand
EingangstürReedkontakt (MC-38)Verzögert20 s Eingangsverzögerung; Alarm, wenn keine PIN kommt
Wohnzimmer, innenPIR (HC-SR501)Innenzone (Mitläufer)Sofortalarm; wartet, solange die Eingangsverzögerung läuft; intern-scharf aus
FensterErschütterung (SW-420)SofortSofortalarm bei 3 Schlägen innerhalb von 3 s
Flur, TreppeLaser + LDR (KY-008)Innenzone (Mitläufer)Sofortalarm; wartet, solange die Eingangsverzögerung läuft
Gas, Feuer, SabotageMQ-2, Flammensensor, Deckelkontakt24 StundenAlarm auch im unscharfen Zustand (Erweiterungsidee)

Dank der verzögerten Zone heult die Sirene nicht sofort los, wenn du nach Hause kommst: Die Tür geht auf, der Summer piept zur Warnung und du hast 20 Sekunden für deine PIN. Eine Mitläufer-Innenzone bleibt geduldig, solange die Eingangsverzögerung läuft; gehst du auf dem Weg von der Tür zum Keypad am PIR vorbei, gibt es keinen Alarm. Kommt aber jemand herein, ohne die Tür zu öffnen, etwa durchs Fenster, und läuft vor den PIR, wurde keine Eingangsverzögerung gestartet und der Alarm geht sofort los. Eine Sofortzone löst immer direkt aus; wer ein Fenster aufhebelt, bekommt keine 20 Sekunden geschenkt. Beim Verlassen funktioniert es ähnlich: Nach dem Scharfschalten startet eine Ausgangsverzögerung von 30 Sekunden, in der du durch die Tür gehen kannst, ohne etwas auszulösen.

Teileliste für die Arduino Alarmanlage

BauteilAnzahlAufgabe
Arduino Uno (oder Nano)1Das Gehirn des Systems; die Pins dieser Anleitung sind bei Uno und Nano gleich
HC-SR501 PIR-Bewegungsmelder1Bewegungserkennung innen, 3–7 m, etwa 110°
MC-38 Magnetkontakt (oder Reedschalter + Magnet)1Öffnet den Stromkreis, wenn Tür oder Fenster aufgehen
SW-420 Erschütterungssensor-Modul1Erkennt Schläge auf Glas und Rahmen
KY-008 Lasermodul + LDR + 10-kΩ-Widerstand1 SatzLichtschranke quer durch den Flur
4x4-Folientastatur (Keypad)1Scharf- und Unscharfschalten per PIN
Aktiver Summer (5 V)1Tastenton, Ein- und Ausgangswarnung, Innensirene
1-Kanal-Relaismodul 5 V1Schaltet die 12-V-Sirene galvanisch getrennt vom Arduino
12-V-Sirene (am besten mit Blitzleuchte)1Der eigentliche Alarmton, meist 100–120 dB
Rote und grüne LED + 2 × 220-Ω-Widerstand1 SatzAnzeige für scharf und bereit
12-V-Netzteil, LM2596-Modul, 12-V-Akku (optional)1 SatzVersorgung und Notstrom bei Stromausfall
Breadboard, Jumperkabel, 4-adriges Alarmkabel-Verbindungen und entfernte Melder
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

Die Melder: Was erkennt welche Zone?

  • PIR (HC-SR501): Erkennt Änderungen der Infrarotstrahlung, die der menschliche Körper abgibt, und sieht Bewegungen in 3–7 Metern Entfernung in einem Winkel von etwa 110 Grad. Der Ausgang liefert 3,3 V, die Versorgung muss aber 5 V betragen. In den ersten 30–60 Sekunden nach dem Einschalten lernt er den Raum kennen und kann von selbst auslösen; deshalb ignoriert der Code die ersten 60 Sekunden. Mehr dazu im Artikel PIR-Bewegungsmelder.
  • Magnet-Türkontakt (MC-38): Zwei Kunststoffgehäuse, eines am Rahmen, eines am Türflügel festgeschraubt. Bei geschlossener Tür ist der Magnet nah und der Kontakt geschlossen; öffnet sich die Tür um mehr als 15–25 mm, öffnet der Kontakt. Er wird mit einem einzigen Pin und dem internen Pull-up gelesen; was darin steckt, erklären wir im Artikel Reedkontakt.
  • Erschütterungssensor (SW-420): Ein Öffner, in dem ein Metallstift von einer Feder umgeben ist. Bekommt Glas oder Rahmen einen Schlag, öffnet der Kontakt kurz, und der LM393-Komparator auf der Platine macht daraus einen digitalen Impuls. Bei den meisten Modulen ist der Ausgang in Ruhe LOW und bei einem Schlag kurz HIGH; prüfe dein Modul mit dem Sensortest. Die Empfindlichkeit stellst du am Poti ein; Details im Artikel SW-420 Erschütterungssensor.
  • Laser-Lichtschranke (KY-008 + LDR): Ein 650-nm-Laser mit etwa 5 mW leuchtet ständig auf einen LDR auf der anderen Seite des Flurs. Wird der Strahl unterbrochen, steigt der Widerstand des LDR von einigen hundert Ohm auf mehrere zehn Kiloohm und der Analogwert fällt deutlich. Tipps zu Aufbau und Ausrichtung findest du im Artikel Lasermodul.
  • 4x4-Keypad: Eine Matrix mit 16 Tasten aus 4 Zeilen- und 4 Spaltenleitungen; sie belegt 8 Pins und wird mit der Keypad-Bibliothek in einer Zeile ausgelesen. Wie das Zeilen-Spalten-Scannen funktioniert, steht in der Keypad-Anleitung.
  • Sirene und Summer: Ein aktiver Summer hat einen eigenen Oszillator und piept, sobald der Pin HIGH ist; ideal für Tastentöne und Ein- und Ausgangswarnungen. Der eigentlich abschreckende Ton kommt von der 12-V-Sirene, die du über ein Relaismodul schaltest. Den Unterschied zwischen aktivem und passivem Summer erklärt der Artikel Was ist ein Buzzer.
Der KY-008 ist ein Laser der Klasse 3R; direkt oder über einen Spiegel in den Strahl zu schauen, kann deine Augen schädigen. Halte den Strahl immer unterhalb der Augenhöhe, zum Beispiel 20–40 cm über dem Boden. Diese Höhe ist sicher und sorgt außerdem dafür, dass jeder, der hereinkommt, den Strahl mit dem Fuß unterbricht.

Schaltplan und Pinbelegung

Die Tabelle zeigt die Verdrahtung aller drei Boards. Die Arduino-Uno-Version nutzt alle vier Zonen und das Keypad. Weil die ESP32-Version statt mit Keypad per Telegram-Befehl geschaltet wird und die Pico-Version kurz gehalten ist, gibt es die Laser-Lichtschranke nur am Uno. Die Pinnummern deines Boards siehst du im Werkzeug Pinout, LED-Vorwiderstände berechnest du mit dem LED-Vorwiderstandsrechner.

VerbindungArduino UnoESP32Raspberry Pi Pico
Keypad R1–R4 (Zeilen)D9, D8, D7, D6– (Telegram)GP2, GP3, GP4, GP5
Keypad C1–C4 (Spalten)D5, D4, D3, D2– (Telegram)GP6, GP7, GP8, GP9
PIR VCC / OUT / GND5V / D10 / GNDVIN (5V) / GPIO 27 / GNDVBUS / GP16 / GND
Reedkontakt (2 Adern)D11 und GNDGPIO 33 und GNDGP15 und GND
SW-420 VCC / DO / GND5V / D12 / GND3V3 / GPIO 32 / GND3V3(OUT) / GP17 / GND
KY-008 S / − (Laser dauerhaft an)5V / GND––
LDR zwischen 5V und A0, 10 kΩ zwischen A0 und GNDA0––
Aktiver Summer + (− an GND)A1GPIO 25GP14
Relaismodul INA2GPIO 26GP13
Grüne LED (mit 220 Ω): bereitA3––
Rote LED (mit 220 Ω): scharfD13GPIO 2 (Onboard-LED)Onboard-LED
Schließe die 12-V-Sirene niemals direkt an einen Arduino-Pin an. Der Pluspol des Netzteils geht an COM des Relais, der NO-Anschluss des Relais an den Pluspol der Sirene und der Minuspol der Sirene zurück zum Minuspol des Netzteils; so fließt der Sirenenstrom nie durch den Arduino. Die 3,3-V-Pins von ESP32 und Pico können manche 5-V-Relaismodule nicht sicher abschalten, sodass das Relais angezogen bleibt; nimm bei diesen Boards ein 3,3-V-taugliches Modul, eines mit High-Level-Trigger oder eine NPN-Transistorstufe.

Eine Alarmanlage ist bei Stromausfall am wichtigsten, also plane die Versorgung von Anfang an. Ein praktischer Aufbau: Wie bei professionellen Zentralen versorgt ein 13,8-V-Alarmnetzteil mit Akkuanschluss die Sirene und, mit einem LM2596-Abwärtswandler auf 5 V gebracht, den 5V-Pin des Arduino. Fällt der Strom aus, übernimmt ein 12-V-Blei-Gel-Akku mit 7 Ah. Die Rechnung ist einfach: Uno, PIR, Laser, LEDs und SW-420 ziehen im Ruhezustand etwa 100 mA bei 5 V; das sind 0,5 W, mit einem Wandler mit 85 % Wirkungsgrad also rund 50 mA auf der Akkuseite. Selbst wenn du nur 80 % der Kapazität nutzt, hält ein 7-Ah-Akku das System 4–5 Tage am Laufen; die 0,2–0,5 A der Sirene im Alarmfall verkürzen das etwas. Deine eigenen Werte probierst du mit dem Akkulaufzeit-Rechner aus, Versorgungsmöglichkeiten erklärt Arduino richtig mit Strom versorgen.

Schritt-für-Schritt-Aufbau

  1. Öffne in der Arduino IDE den Bibliotheksverwalter, suche nach Keypad und installiere die Bibliothek von Mark Stanley und Alexander Brevig. Fehlt sie, bekommst du beim Kompilieren den Fehler Keypad.h: No such file or directory.
  2. Halte das Keypad mit den Tasten zu dir; verbinde die ersten vier Adern von links (R1–R4) mit D9, D8, D7, D6 und die nächsten vier (C1–C4) mit D5, D4, D3, D2.
  3. Schließe den PIR an 5V, D10 und GND an; dreh beide Potis ganz gegen den Uhrzeigersinn und setze den Trigger-Jumper auf H.
  4. Verbinde die beiden Adern des Reedkontakts mit D11 und GND, den SW-420 mit 5V, D12 und GND.
  5. Setze den LDR zwischen 5V und A0 und den 10-kΩ-Widerstand zwischen A0 und GND; steck den LDR ans Ende eines schwarzen Röhrchens und richte den Laser genau auf dessen Mitte aus.
  6. Schließe den Summer an A1 an, den IN-Pin des Relaismoduls an A2 und dessen VCC und GND an 5V und GND des Arduino; die LEDs kommen über 220-Ω-Widerstände an A3 und D13.
  7. Lade zuerst den Sensortest unten hoch, öffne den seriellen Monitor mit 9600 Baud und löse jeden Sensor einzeln aus.
  8. Lade danach den vollständigen Alarm-Sketch hoch, ändere die PIN und teste das System ein paar Tage nur mit dem Summer, bevor du die Sirene anschließt.

Erster Test: alle Sensoren im seriellen Monitor

Bevor es an den Alarmcode geht, prüfe, ob jeder Sensor sich so verhält wie erwartet. Dieser kleine Sketch gibt zweimal pro Sekunde den PIR-Zustand aus, ob die Tür offen oder zu ist, wie viele Erschütterungsimpulse in der letzten halben Sekunde kamen und den Analogwert des LDR. Der Erschütterungspin wird ständig gelesen, weil die Impulse des SW-420 nur wenige Millisekunden dauern können und Code mit delay() dazwischen sie verpassen würde.

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}
Mit diesem Test findest du auch die Laserschwelle: Notiere den Wert, wenn der Strahl auf den LDR trifft (z. B. 950), und den Wert mit deiner Hand im Strahl (z. B. 250), und trage im Alarmcode bei LAZER_ESIK die Mitte ein (600). Liegen beide Werte weniger als 300 auseinander, verlängere das Röhrchen oder richte den Laser neu aus.

Zustandsautomat: das Gehirn der Alarmanlage

Wer eine Alarmanlage mit if-Blöcken und delay() schreibt, landet schnell in einem undurchschaubaren Knäuel: Während auf die Eingangsverzögerung gewartet wird, liest niemand das Keypad, und während die Sirene läuft, kann man keine PIN eingeben. Die Lösung ist ein Zustandsautomat (State Machine), bei dem das System zu jedem Zeitpunkt in genau einem Zustand ist und nur bei bestimmten Ereignissen wechselt. Im Code werden die Zustände mit einem enum definiert, loop() wendet bei jedem Durchlauf nur die Regeln des aktuellen Zustands an, und Wartezeiten werden mit millis() statt delay() gemessen. So läuft die Schleife tausende Male pro Sekunde und kein Tastendruck und kein Sensorereignis geht verloren.

ZustandWann betreten?Was passiert?Wann verlassen?
UNSCHARFBeim Start und nach PIN + #Beobachtet die Sensoren, löst aber nie aus; grüne LED leuchtet, wenn alles bereit istPIN + A oder PIN + B
AUSGANGSVERZÖGERUNGPIN + A (extern) oder B (intern)Zählt 30 s herunter; Summer piept jede Sekunde, in den letzten 10 s schnellerNach Ablauf SCHARF; mit PIN + # UNSCHARF
SCHARFNach Ablauf der AusgangsverzögerungÜberwacht alle Zonen; rote LED leuchtet dauerhaftTür auf: EINGANG; Sofort- oder Innenzone: ALARM
EINGANGSVERZÖGERUNGTür geöffnet im ScharfzustandSummer piept 20 s lang; PIR und Laser wartenMit PIN + # UNSCHARF; nach Ablauf ALARM
ALARMEine Zone hat ausgelöstSirene und Summer an; Sirene stoppt nach 3 min, rote LED blinkt schnellNur mit PIN + # UNSCHARF

Scharf- und Unscharfschalten mit PIN am Keypad

TastenFunktion
2580 + AExtern-scharf: alle vier Zonen aktiv, 30 s Ausgangsverzögerung
2580 + BIntern-scharf (Nachtmodus): PIR aus; Tür, Fenster und Laser aktiv
2580 + #Unscharf schalten; nach einem Alarm wird die auslösende Zone im seriellen Monitor angezeigt
* oder CFalsch eingetippte PIN löschen
DZonentest: gibt Tür-, PIR- und Laserwerte im seriellen Monitor aus
3 falsche PINsKeypad wird 60 s gesperrt; in dieser Zeit wird auch die richtige PIN abgelehnt

Dass auch zum Scharfschalten die PIN nötig ist, verhindert, dass Kinder oder Gäste das System versehentlich aktivieren. Der Code macht außerdem eine Nicht-bereit-Prüfung: Versuchst du bei offener Tür oder unterbrochenem Laserstrahl scharf zu schalten, hörst du einen langen Piepton und nichts passiert, genau wie bei kommerziellen Zentralen. Lass die PIN nicht auf 2580 und vermeide leicht zu ratende Codes wie 1234, 0000 oder dein Geburtsjahr. Möchtest du die PIN ändern, ohne neu hochzuladen, speichere sie im EEPROM und ergänze ein Menü, in dem du am Keypad eine neue PIN eingibst. Mit einem I2C-LCD neben dem Keypad siehst du auch den Countdown und Statusmeldungen; deshalb sind A4 und A5 frei geblieben.

Code der Arduino Alarmanlage

Der folgende Code ist das Herzstück dieser Anleitung. Im Einstellungsbereich ganz oben änderst du PIN, Verzögerungen, Sirenendauer, Laserschwelle und Relaistyp; den Rest musst du nicht anfassen. Auf einem Arduino Uno belegt er etwa 6 KB Programmspeicher und 620 Byte RAM, es bleibt also reichlich Platz für ein LCD oder einen RFID-Leser.

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}

Das Rückgrat des Codes ist die Funktion suz(). Ändert sich der Rohwert eines Eingangs, wird ein Zeitstempel genommen, und der stabile Zustand ändert sich erst, wenn der Wert eine bestimmte Zeit gleich bleibt (50 ms für die Tür, 200 ms für den PIR, 30 ms für den Laser). Dieser einfache Filter verhindert, dass kurze Störimpulse auf langen Leitungen und Kontaktprellen einen Alarm auslösen. Auch das Lesen des Türpins mit INPUT_PULLUP ist Absicht: Bei geschlossener Tür zieht der Reedkontakt den Pin auf GND und er liest LOW; öffnet sich die Tür oder durchtrennt jemand das Kabel, wird der Pin HIGH. Diese Ruhestromlogik echter Alarmanlagen wertet ein durchgeschnittenes Kabel genauso wie eine geöffnete Tür.

Für die Erschütterung kommt ein anderes Verfahren zum Einsatz. Schon ein einzelner Schlag lässt den SW-420 innerhalb weniger Millisekunden mehrmals prellen; titresimVar() zählt zuerst alles innerhalb von 150 ms als ein Ereignis und merkt sich dann die Zeiten der letzten drei Ereignisse. Passen drei Ereignisse in 3 Sekunden, gilt das als echter Aufbruchversuch; Wind, der an der Tür rüttelt, oder ein einzelner Ball gegen die Scheibe lösen nichts aus. Die Sirene verstummt nach 3 Minuten von selbst, das System bleibt aber im ALARM-Zustand und die rote LED blinkt schnell, damit jeder, der heimkommt, den Alarm bemerkt. Gibst du die richtige PIN ein, erscheint der in der Variablen tetikleyen gespeicherte Zonenname im seriellen Monitor; eine kleine Version des Alarmspeichers kommerzieller Zentralen.

Ein- und Ausgangsverzögerung richtig einstellen

Sind die Verzögerungen zu kurz, löst du deinen eigenen Alarm aus; sind sie zu lang, schenkst du einem Einbrecher Zeit. Wähle die Ausgangsverzögerung doppelt so lang wie den Weg vom Keypad zur Tür samt Abschließen und die Eingangsverzögerung etwa doppelt so lang wie Tür öffnen, zum Keypad gehen und PIN eintippen. Kommerzielle Zentralen nutzen meist 30–60 Sekunden zum Verlassen und 15–30 Sekunden zum Betreten. Hängst du das Keypad direkt neben die Tür, aber von außen nicht sichtbar, kannst du die Eingangsverzögerung kurz halten. Auch das Verhalten des Summers zählt: Beim Verlassen wird der kurze Piepton jede Sekunde in den letzten 10 Sekunden schneller, sodass du spürst, wie die Zeit abläuft; beim Betreten erinnert dich ein 200-ms-Intervallton an die PIN-Eingabe.

Sirene und Relais anschließen

Im Sirenenkreis treibt der Arduino nur die Relaisspule; der 12-V-Sirenenstrom fließt über die Relaiskontakte. Die meisten Relaismodule sind Low-aktiv, ziehen also an, wenn der IN-Pin LOW ist; dafür gibt es im Code die Konstante ROLE_AKTIF_LOW. Der Code schreibt den Aus-Pegel, bevor er den Pin zum Ausgang macht; so zieht das Relais beim Booten nicht kurz an und die Sirene zirpt nicht. Achte bei der Sirene auf den Strom: Kleine 12-V-Sirenen mit 110 dB ziehen meist 0,1–0,3 A, eine winzige Last für einen 10-A-Relaiskontakt. Eine Außensirene mit Blitzleuchte schreckt mit Ton und Licht ab und zeigt den Nachbarn, woher der Alarm kommt. Professionelle Zentralen schalten die Sirene meist nach 3 Minuten ab, und manche Länder schreiben sogar eine Höchstdauer vor; halte SIREN_SURE daher vernünftig. Willst du statt eines Relais einen Halbleiterschalter, nimm einen Logic-Level-MOSFET und setze bei Sirenen mit Spule (elektromagnetisch) eine Diode in Sperrrichtung über die Anschlüsse.

Telegram-Benachrichtigung mit dem ESP32

Eine Sirene, die niemand hört, nützt nichts; der einfachste Weg, unterwegs von einem Alarm zu erfahren, sind ESP32 und Telegram. Der ESP32 verbindet sich mit dem WLAN und schickt Nachrichten über eine verschlüsselte Verbindung an den Telegram-Server, und du kannst ihm auch Befehle senden. Wie du mit BotFather einen Bot anlegst, Token und Chat-ID bekommst und die Bibliotheken UniversalTelegramBot und ArduinoJson installierst, erklären wir Schritt für Schritt im Artikel ESP32 Telegram-Bot. Diese Version hat kein Keypad; du steuerst das System vom Handy mit den Befehlen /kur (extern-scharf), /evde (intern-scharf), /coz (unscharf) und /durum (Status). Der Bot antwortet nur deiner Chat-ID und ignoriert Befehle von allen anderen stillschweigend.

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}

Der ESP32-Code unterscheidet sich in drei wichtigen Punkten. Erstens kann das Senden einer Nachricht oder das Abfragen neuer Befehle 1–2 Sekunden dauern, und so lange wartet loop(). Eine offen stehende Tür und ein PIR, der mindestens einige Sekunden HIGH bleibt, sind davon nicht betroffen, die Millisekunden-Impulse des SW-420 könnten aber verloren gehen; deshalb wird die Erschütterung per Interrupt (attachInterrupt) erfasst. Zweitens schaltet alarmVer() zuerst die Sirene ein und schickt erst danach die Nachricht; egal wie langsam das Internet ist, die Sirene kommt nie zu spät. Nicht zugestellte Meldungen landen in einer Warteschlange und werden alle 15 Sekunden erneut gesendet. Drittens speichert die Bibliothek Preferences im Flash, ob die Anlage scharf ist: Fällt der Strom im Scharfzustand aus und kommt zurück, startet der ESP32 scharf und meldet dir, dass der Strom wieder da ist. Teilen sich Relais und Sirene ein schwaches Netzteil mit dem ESP32, kann das Board mit dem Fehler Brownout detector was triggered neu starten; die Lösung steht im Artikel ESP32 startet ständig neu.

WLAN ist nicht immer zuverlässig: Bei Stromausfall geht auch der Router aus, und ein Einbrecher kann ihn einfach ausstecken. Ein kleiner USV-Puffer für den Router oder SMS als zweiter Kanal über ein SIM800L-GSM-Modul sorgen dafür, dass die Meldung trotzdem ankommt. Wenn du bei Bewegung auch ein Foto willst, schau dir Fotos mit der ESP32-CAM an Telegram senden an.

Alarmanlage mit Raspberry Pi Pico und MicroPython

Denselben Zustandsautomaten baust du auf dem Raspberry Pi Pico mit einem kurzen MicroPython-Programm. Das Keypad wird mit der Zeilen-Spalten-Scanfunktion aus der Keypad-Anleitung gelesen; die Zeilen liegen an GP2–GP5, die Spalten mit internem Pull-up an GP6–GP9. Versorge den PIR aus VBUS (5 V); sein Ausgang hat ohnehin 3,3 V und kommt direkt an GP16. Den SW-420 versorgst du unbedingt aus dem Pin 3V3(OUT), denn der Ausgang eines mit 5 V betriebenen Moduls kann den Pico-Eingang beschädigen. Erschütterungsimpulse werden mit Pin.irq erfasst und ihre Zeiten in einer Liste gespeichert, die Zustände als einfache Zeichenketten geführt. Zeiten werden mit time.ticks_diff() berechnet, weil diese Funktion auch beim Überlauf des Zählers die richtige Differenz liefert.

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)

So vermeidest du Fehlalarme

Der größte Feind einer Alarmanlage ist nicht der Einbrecher, sondern der Fehlalarm: Eine Sirene, die dreimal grundlos losging, nehmen weder du noch deine Nachbarn ernst. Einige der folgenden Maßnahmen stecken schon im Code, der Rest betrifft Montage und Verkabelung:

  • Aufwärmzeit abwarten: Der PIR kann in den ersten 30–60 Sekunden nach dem Einschalten von selbst auslösen; der Code ignoriert die ersten 60 Sekunden.
  • Filtern: Abzuwarten, bis ein Eingang eine bestimmte Zeit stabil ist (Entprellen), sortiert kurze Störimpulse auf den Leitungen aus.
  • Schläge zählen: Statt eines einzelnen Klopfens 3 Schläge in 3 Sekunden zu verlangen, filtert Wind, vorbeifahrende Lkw und zuschlagende Türen heraus. Erhöhe die Zahl bei Bedarf auf 4–5 oder senke die Empfindlichkeit am Poti.
  • PIR richtig platzieren: Nicht auf Heizkörper, Klimaanlage, sonnige Fenster oder Vorhänge richten; am besten erkennt er Bewegungen quer durch das Sichtfeld. Mit Haustieren den Sensor hoch montieren, leicht nach unten neigen und die Empfindlichkeit senken.
  • Zwei Melder verlangen: In kritischen Bereichen zu fordern, dass PIR und Laser oder PIR und Tür innerhalb weniger Sekunden auslösen (Zweimelderabhängigkeit), verhindert, dass ein einzelner fehlerhafter Melder Alarm auslöst.
  • Versorgung sauber halten: Der Spannungseinbruch beim Anziehen des Relais kann den PIR auslösen. Setze 100 µF und 100 nF zwischen VCC und GND jedes Sensors und führe Relais- und Sirenenleitungen getrennt von den Sensorleitungen.
  • Externe Pull-ups an langen Leitungen: Der interne Pull-up des Arduino hat 20–50 kΩ und fängt auf 10 Metern Kabel Störungen ein; ein externer 4,7-kΩ-Widerstand plus 100 nF am Pin machen die Leitung deutlich robuster.
  • Laser vor Umgebungslicht schützen: Setze den LDR in ein schwarzes Röhrchen und teste die Schwelle getrennt bei Tageslicht und nachts.
Bevor du dich auf die Anlage verlässt, mach einen Gehtest: Trenne die Sirene vom COM-Anschluss des Relais, schalte scharf und geh den Weg, den ein Einbrecher durch deine Wohnung nehmen würde. Beobachte im seriellen Monitor, welche Zone wann auslöst, und lass das System danach ein paar Tage nur mit dem Summer laufen, um Selbstauslösungen zu erkennen.

Häufige Fehler und Lösungen

  • Alarm sofort nach dem Scharfschalten: Der PIR wärmt sich noch auf oder der Türkontakt ist falsch angeschlossen. Im Sensortest muss die geschlossene Tür LOW zeigen; steht dort HIGH, ist der Magnet zu weit weg oder eine Ader unterbrochen.
  • Keypad reagiert nicht oder liefert falsche Tasten: Zeilen- und Spaltenadern sind vertauscht. Statt umzustecken, änderst du einfach die Reihenfolge in den Arrays satirPinleri und sutunPinleri.
  • Langer Piepton beim Scharfschalten: Kein Defekt, sondern die Nicht-bereit-Warnung: Die Tür ist offen oder der Laserstrahl erreicht den LDR nicht. Mit der Taste D siehst du den Zustand der Zonen.
  • Relais klickt beim Einschalten kurz: Der Aus-Pegel wurde nicht vor pinMode geschrieben, oder ROLE_AKTIF_LOW steht bei einem High-aktiven Relais noch auf true.
  • Arduino startet neu, wenn die Sirene losgeht: Sirene und Arduino teilen sich ein schwaches Netzteil, und der Spannungseinbruch setzt das Board zurück. Nimm ein stärkeres Netzteil, einen separaten Wandler und dickere Kabel; Details unter Arduino startet ständig neu.
  • Erschütterungssensor löst nie oder ständig aus: Dreh das Poti langsam, um die Schwelle einzustellen, und klebe den Sensor fest auf Glas oder Rahmen; ein locker sitzender Sensor überträgt den Schlag nicht.
  • ESP32 sendet keine Nachrichten: Token oder Chat-ID sind falsch, das WLAN ist ein 5-GHz-Netz oder du hast den Bot in Telegram nie gestartet. Ein Bot kann dir erst schreiben, wenn du ihm einmal /start geschickt hast.
  • Laser tagsüber in Ordnung, nachts Fehlalarm (oder umgekehrt): Die Schwelle liegt zu nah am Umgebungslicht. Miss die Werte mit und ohne Strahl in beiden Situationen und wähle LAZER_ESIK neu in der Mitte.

Ideen zum Erweitern der Alarmanlage

  • Scharf und unscharf per Karte: Ergänze neben dem Keypad einen RFID-RC522-Leser und schalte mit einem Schlüsselanhänger unscharf.
  • 24-Stunden-Zonen: Mit einem MQ-2-Gassensor oder einem Flammensensor baust du eine Gas- und Feuerzone, die auch im unscharfen Zustand Alarm gibt.
  • Funk-Türkontakte: Baue für entfernte Fenster batteriebetriebene Kontakte mit 433-MHz-Funkmodulen.
  • Bedienteil mit Display: Zeige Countdown, Status und Alarmspeicher auf einem I2C-LCD an.
  • Smart-Home-Anbindung: Verbinde den ESP32 über MQTT mit Home Assistant und steuere die Alarmanlage zusammen mit Licht und Kameras.
  • Sabotagekontakt: Ein Mikroschalter im Gehäusedeckel lässt den Summer ertönen, sobald jemand das Gehäuse öffnet, auch im unscharfen Zustand.
  • Modellhaus-Projekt: Fasse alle Zonen in einem Modellhaus zusammen, als Sicherheitsebene eines Smart-Home-Projekts.

Weil sie Sensoren, Timing, Zustandsautomat und Stromversorgung in einem Projekt vereint, ist eine Arduino Alarmanlage auch zum Lernen ideal. Starte mit dem Sensortest, teste die Anlage dann ein paar Tage nur mit dem Summer und schließe die Sirene zuletzt an. In der BlueGrays-App findest du Projekte wie den Reedkontakt-Türsensor, das Laser-Sicherheitssystem, das 4x4-Keypad-LCD-Codeschloss und die ESP32-Bewegungsalarmanlage mit Telegram-Benachrichtigung, jeweils mit Schritt-für-Schritt-Schaltplänen und Code.

Häufige Fragen

Wie baut man eine Alarmanlage mit Arduino?

Du schließt an einen Arduino Uno einen PIR-Bewegungsmelder, einen Reedkontakt für die Tür, ein Keypad, einen aktiven Summer und ein Relaismodul für die Sirene an. Der Code ist ein Zustandsautomat, der zwischen unscharf, Ausgangsverzögerung, scharf, Eingangsverzögerung und Alarm wechselt. Scharf- und unscharf geschaltet wird per PIN am Keypad; löst im Scharfzustand ein Sensor aus, ertönt die Sirene.

Welche Sensoren braucht man für eine Arduino Alarmanlage?

Am häufigsten sind ein PIR-Bewegungsmelder (HC-SR501) für Innenräume, Reed- bzw. Magnetkontakte (MC-38) für Türen und Fenster, ein SW-420-Erschütterungssensor für Schläge auf Glas und eine Laser-Lichtschranke für Flure. Dazu kommen bei Bedarf ein MQ-2 für Gas und ein Flammensensor für Feuer. Jeder Sensor wird als eigene Zone mit eigenem Verhalten definiert.

Funktioniert eine Arduino Alarmanlage bei Stromausfall?

Nicht, wenn sie nur am Steckernetzteil hängt. Mit einem 13,8-V-Alarmnetzteil mit Akkuanschluss und einem 12-V-Akku mit 7 Ah läuft ein Uno-System mit etwa 100 mA rund 4–5 Tage. In der ESP32-Version wird der Scharfzustand im Flash gespeichert, sodass die Anlage nach der Rückkehr des Stroms scharf startet; damit Meldungen rausgehen, braucht auch der Router eine USV.

Kann eine Arduino Alarmanlage Benachrichtigungen aufs Handy schicken?

Ein Arduino Uno allein kann das nicht, ein ESP32 schickt aber per WLAN sofort Telegram-Nachrichten und nimmt Befehle wie /kur und /coz vom Handy entgegen. Wo kein Internet verfügbar ist, kann ein SIM800L-GSM-Modul SMS senden oder anrufen. Beide Wege zu kombinieren ist am zuverlässigsten.

Warum löst mein PIR-Bewegungsmelder Fehlalarme aus?

Typische Ursachen sind die Aufwärmzeit von 30–60 Sekunden nach dem Einschalten, Wärmequellen wie Heizkörper und Klimaanlagen, in der Sonne erwärmte, wehende Vorhänge und Störungen auf der Versorgung beim Schalten eines Relais. Die Aufwärmzeit im Code zu ignorieren, den Sensor von Wärmequellen fernzuhalten und einen Kondensator zwischen VCC und GND zu setzen, löst die meisten Probleme.

Kann man eine Arduino Alarmanlage im echten Zuhause nutzen?

Zum Lernen und zur Überwachung eines Zimmers, einer Garage oder eines Lagers funktioniert sie gut, eine zertifizierte Alarmanlage ersetzt sie aber nicht. Kommerzielle Systeme werden nach Normen wie EN 50131 auf Sabotageschutz, Notstromversorgung, verschlüsselte Funkverbindungen und Anbindung an eine Notrufleitstelle geprüft. Wenn du eine Arduino-Anlage zu Hause einsetzt, ergänze unbedingt Notstromakku, Sabotagekontakt und einen zweiten Meldeweg.

Alle Projekte ansehen
ESP32 Bewegungsalarmsystem (PIR + Buzzer + LED + Telegram)
#145Fortgeschritten
Premium

ESP32 Bewegungsalarmsystem (PIR + Buzzer + LED + Telegram)

Ein intelligentes Sicherheitsalarmsystem, das Buzzer und LED aktiviert, sobald der PIR-Sensor Bewegung erkennt, und gleichzeitig eine sofortige Benachrichtigung per Telegram an den Besitzer sendet. Ein 30-sekündiger Cooldown begrenzt die Häufigkeit wiederholter Alarme; dieser Mechanismus überprüft weder die Gültigkeit der PIR-Erkennungen noch verhindert er Fehlauslösungen. Geeignet für Heimsicherheit, Garagenüberwachung, Lagerräume oder Arbeitszimmer. Mit ESP32-CAM sind Foto-Benachrichtigungen m

0.0(0)
9
Laser-Sicherheitssystem
#23Fortgeschritten
Premium

Laser-Sicherheitssystem

Wie die Laser-Einbruchsszenen aus Filmen: Ein Laserstrahl trifft auf einen Lichtsensor, und sobald jemand dazwischentritt und den Strahl unterbricht, leuchtet die rote LED und der Summer legt los. Der

4.8(60)
77
Laser-Warnsystem
#22Mittel
Premium

Laser-Warnsystem

Du baust einen unsichtbaren Lichtschrankenzaun wie im Film. Ein Laser leuchtet auf einen Lichtsensor auf der anderen Seite; sobald jemand hindurchgeht und den Strahl unterbricht, leuchtet die LED und

4.8(64)
83
Türsensor mit Reedschalter
#38Anfänger
Premium

Türsensor mit Reedschalter

Erkenne an einem Licht, ob deine Tür offen oder zu ist. An der Tür befestigst du einen kleinen Magneten, am Rahmen einen Schalter, der Magnete spürt; ist die Tür zu, liegen beide nebeneinander und die

4.7(34)
44
Passwortschloss mit 4x4-Tastenfeld und LCD
#64Fortgeschritten
Premium

Passwortschloss mit 4x4-Tastenfeld und LCD

Ein kleines elektronisches Türschloss, das niemanden ohne den richtigen Code hereinlässt. Du drückst die Rautetaste und tippst den Code auf dem Tastenfeld ein; stimmt er, meldet das Display Zutritt er

4.8(59)
75
Bewegungsmelder mit Bewegungssensor
#24Anfänger
Premium

Bewegungsmelder mit Bewegungssensor

Ein einfacher Wächter, der Bescheid gibt, wenn jemand den Raum betritt. Der Bewegungsmelder spürt die Wärme, die der menschliche Körper abgibt; der Arduino lässt die LED leuchten, solange sich etwas b

4.6(47)
67
Intelligenter elektronischer Safe
#92Fortgeschritten
Premium

Intelligenter elektronischer Safe

Ein kleiner Tresor für deine Wertsachen, der sich mit einem Code öffnet. Du tippst deinen vierstelligen Code auf dem Tastenfeld ein; stimmt er, öffnet der Servomotor das Schloss, das grüne Licht geht

4.7(42)
63