Sistema de alarma con Arduino: PIR, sensor de puerta y teclado
Cómo hacer un sistema de alarma con Arduino: PIR, sensor de puerta, vibración y láser, teclado con clave, retardo de entrada, sirena y avisos por Telegram.
Un sistema de alarma con Arduino es un dispositivo de seguridad que puedes construir tú mismo: hace sonar una sirena cuando alguien entra sin permiso en tu casa o habitación, te da unos segundos para teclear tu clave al abrir la puerta y, si quieres, te envía un aviso instantáneo al móvil. En esta guía montarás una alarma de cuatro zonas con un sensor de movimiento PIR, un contacto reed en la puerta, un sensor de vibración que detecta golpes en el cristal de la ventana y una barrera láser que cruza el pasillo. La armarás y desarmarás con una clave en un teclado 4x4, activarás una sirena de 12 V mediante un relé y lo controlarás todo con un solo Arduino Uno.
Paso a paso construiremos una pequeña copia de lo que hacen las centrales de alarma comerciales: retardo de entrada y de salida, modo en casa (modo noche), bloqueo tras claves erróneas, límite de tiempo de la sirena y memoria de alarma. No escribiremos el código con if anidados, sino como una máquina de estados, así siempre estará claro qué hace la alarma en cada momento. Después llevarás la misma lógica a un ESP32 para armar, desarmar y recibir avisos por Telegram, escribirás una versión en MicroPython para la Raspberry Pi Pico y verás cómo reducir el problema más molesto de todos: las falsas alarmas.

¿Cómo funciona un sistema de alarma con Arduino?
Todo sistema de alarma tiene tres partes: entradas (sensores), un cerebro que decide (el Arduino) y salidas (sirena, zumbador, aviso). Cada sensor vigila una zona. Las centrales profesionales clasifican las zonas según cómo reaccionan al dispararse, y esa distinción es lo que separa una buena alarma de un aparato molesto que suena cada vez que se abre una puerta. Nuestro sistema tendrá cuatro zonas:
| Zona | Sensor | Tipo de zona | Si se dispara estando armada |
|---|---|---|---|
| Puerta de entrada | Contacto reed (MC-38) | Retardada | Empieza un retardo de entrada de 20 s; alarma si no se teclea la clave |
| Salón, interior | PIR (HC-SR501) | Interior (seguidora) | Alarma inmediata; espera si corre el retardo de entrada; desactivada en modo en casa |
| Ventana | Vibración (SW-420) | Instantánea | Alarma inmediata con 3 golpes en 3 s |
| Pasillo, escalera | Láser + LDR (KY-008) | Interior (seguidora) | Alarma inmediata; espera si corre el retardo de entrada |
| Gas, fuego, sabotaje | MQ-2, sensor de llama, interruptor de caja | 24 horas | Alarma incluso desarmada (idea de mejora) |
Gracias a la zona retardada, la sirena no suena nada más llegar a casa: se abre la puerta, el zumbador pita para avisarte y tienes 20 segundos para teclear tu clave. Una zona interior seguidora tiene paciencia mientras corre el retardo de entrada, así que pasar por delante del PIR camino del teclado no dispara la alarma. Pero si alguien entra sin abrir la puerta, por ejemplo por una ventana, y aparece delante del PIR, no ha empezado ningún retardo y la alarma suena al instante. Una zona instantánea dispara siempre de inmediato; no tiene sentido regalar 20 segundos a quien fuerza una ventana. Al salir pasa algo parecido: al armar el sistema empieza un retardo de salida de 30 segundos y salir por la puerta en ese tiempo no dispara nada.
Lista de materiales para la alarma con Arduino
| Material | Cant. | Función |
|---|---|---|
| Arduino Uno (o Nano) | 1 | El cerebro del sistema; los pines de esta guía son iguales en Uno y Nano |
| Sensor de movimiento PIR HC-SR501 | 1 | Detección de movimiento interior, 3–7 m, unos 110° |
| Contacto magnético MC-38 (o reed + imán) | 1 | Abre el circuito al abrirse una puerta o ventana |
| Módulo sensor de vibración SW-420 | 1 | Detecta golpes en el cristal o el marco |
| Módulo láser KY-008 + LDR + resistencia de 10 kΩ | 1 juego | Barrera de luz que cruza el pasillo |
| Teclado de membrana 4x4 | 1 | Armar y desarmar con clave |
| Zumbador activo (5 V) | 1 | Pitido de teclas, aviso de entrada y salida, sirena interior |
| Módulo de relé de 1 canal, 5 V | 1 | Activa la sirena de 12 V aislada del Arduino |
| Sirena de 12 V (mejor con flash) | 1 | El sonido principal de la alarma, normalmente 100–120 dB |
| LED rojo y verde + 2 resistencias de 220 Ω | 1 juego | Indicadores de armada y lista |
| Fuente de 12 V, módulo LM2596, batería de 12 V (opcional) | 1 juego | Alimentación y respaldo en cortes de luz |
| Protoboard, cables dupont, cable de alarma de 4 hilos | - | Conexiones y sensores lejanos |







Sensores: ¿qué detecta cada zona?
- PIR (HC-SR501): Detecta los cambios en la radiación infrarroja que emite el cuerpo humano y ve el movimiento a 3–7 metros con un ángulo de unos 110 grados. Su salida es de 3,3 V, pero necesita alimentación de 5 V. Durante los 30–60 segundos posteriores al encendido aprende el entorno y puede dispararse solo; por eso el código ignora el primer minuto. Más detalles en el artículo sensor de movimiento PIR.
- Contacto magnético de puerta (MC-38): Dos cuerpos de plástico, uno atornillado al marco y otro a la hoja de la puerta. Con la puerta cerrada el imán está cerca y el contacto cerrado; cuando la puerta se abre más de 15–25 mm, el contacto se abre. Se lee con un solo pin y el pull-up interno; explicamos qué tiene dentro en el artículo interruptor reed.
- Sensor de vibración (SW-420): Un interruptor normalmente cerrado con una varilla metálica rodeada por un muelle. Cuando el cristal o el marco recibe un golpe, el contacto se abre un instante y el comparador LM393 de la placa lo convierte en un pulso digital. En la mayoría de módulos la salida está en LOW en reposo y pasa brevemente a HIGH con un golpe; compruébalo en tu módulo con la prueba de sensores. La sensibilidad se ajusta con el potenciómetro; detalles en el artículo sensor de vibración SW-420.
- Barrera láser (KY-008 + LDR): Un láser de 650 nm y unos 5 mW apunta de forma continua a una LDR al otro lado del pasillo. Al cortarse el haz, la resistencia de la LDR pasa de unos cientos de ohmios a decenas de kiloohmios y la lectura analógica cae claramente. Consejos de montaje y alineación en el artículo módulo láser.
- Teclado 4x4: Una matriz de 16 teclas formada por 4 líneas de fila y 4 de columna; usa 8 pines y se lee en una línea con la librería Keypad. Cómo funciona el escaneo de filas y columnas lo encontrarás en la guía del teclado matricial.
- Sirena y zumbador: Un zumbador activo tiene su propio oscilador y pita en cuanto pones el pin en HIGH; es ideal para el pitido de teclas y los avisos de entrada y salida. El sonido realmente disuasorio viene de la sirena de 12 V, que activas mediante un módulo de relé. La diferencia entre zumbador activo y pasivo está en qué es un buzzer.
Esquema de conexión y tabla de pines
La tabla reúne el cableado de las tres placas. La versión para Arduino Uno usa las cuatro zonas y el teclado. Como la versión ESP32 se arma y desarma con comandos de Telegram en lugar de teclado y la versión Pico es corta, la barrera láser solo está en el Uno. Para ver los pines de tu placa usa la herramienta Pinout, y para calcular las resistencias de los LED, la calculadora de resistencia para LED.
| Conexión | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Teclado R1–R4 (filas) | D9, D8, D7, D6 | – (Telegram) | GP2, GP3, GP4, GP5 |
| Teclado C1–C4 (columnas) | D5, D4, D3, D2 | – (Telegram) | GP6, GP7, GP8, GP9 |
| PIR VCC / OUT / GND | 5V / D10 / GND | VIN (5V) / GPIO 27 / GND | VBUS / GP16 / GND |
| Contacto reed (2 cables) | D11 y GND | GPIO 33 y GND | GP15 y GND |
| SW-420 VCC / DO / GND | 5V / D12 / GND | 3V3 / GPIO 32 / GND | 3V3(OUT) / GP17 / GND |
| KY-008 S / − (láser siempre encendido) | 5V / GND | – | – |
| LDR entre 5V y A0, 10 kΩ entre A0 y GND | A0 | – | – |
| Zumbador activo + (− a GND) | A1 | GPIO 25 | GP14 |
| IN del módulo de relé | A2 | GPIO 26 | GP13 |
| LED verde (con 220 Ω): lista | A3 | – | – |
| LED rojo (con 220 Ω): armada | D13 | GPIO 2 (LED integrado) | LED integrado |
Una alarma importa sobre todo cuando se va la luz, así que piensa en la alimentación desde el principio. Un montaje práctico: como en las centrales profesionales, una fuente de alarma de 13,8 V con conexión de batería alimenta la sirena y, reducida a 5 V con un convertidor reductor LM2596, el pin 5V del Arduino. Si falla la red, entra en juego una batería de plomo de 12 V y 7 Ah. La cuenta es sencilla: el Uno, el PIR, el láser, los LED y el SW-420 consumen unos 100 mA a 5 V en reposo; son 0,5 W, que con un convertidor del 85 % de rendimiento equivalen a unos 50 mA del lado de la batería. Aunque solo uses el 80 % de su capacidad, una batería de 7 Ah mantiene el sistema 4–5 días; los 0,2–0,5 A de la sirena mientras suena lo acortan un poco. Prueba tus propios valores con la calculadora de duración de batería y consulta cómo alimentar un Arduino para ver las opciones.
Montaje paso a paso
- En el Arduino IDE abre el Gestor de Librerías, escribe Keypad e instala la librería de Mark Stanley y Alexander Brevig. Si no está instalada, al compilar aparecerá el error Keypad.h: No such file or directory.
- Sujeta el teclado con las teclas hacia ti; conecta los cuatro primeros cables de la izquierda (R1–R4) a D9, D8, D7, D6 y los cuatro siguientes (C1–C4) a D5, D4, D3, D2.
- Conecta el PIR a 5V, D10 y GND; gira los dos potenciómetros del todo en sentido antihorario y pon el jumper de disparo en H.
- Conecta los dos cables del contacto reed a D11 y GND, y el SW-420 a 5V, D12 y GND.
- Pon la LDR entre 5V y A0 y la resistencia de 10 kΩ entre A0 y GND; coloca la LDR al fondo de un tubo negro y apunta el láser justo al centro del tubo.
- Conecta el zumbador a A1, el IN del módulo de relé a A2 y su VCC y GND al 5V y GND del Arduino; conecta los LED a A3 y D13 con resistencias de 220 Ω.
- Primero sube la prueba de sensores de abajo, abre el Monitor Serie a 9600 baudios y activa cada sensor uno por uno.
- Después sube el código completo de la alarma, cambia la clave y prueba el sistema unos días solo con el zumbador antes de conectar la sirena.
Primera prueba: ver todos los sensores en el Monitor Serie
Antes de pasar al código de la alarma, asegúrate de que cada sensor se comporta como esperas. Este pequeño programa muestra dos veces por segundo el estado del PIR, si la puerta está abierta o cerrada, cuántos pulsos de vibración hubo en el último medio segundo y el valor analógico de la LDR. El pin de vibración se lee continuamente, porque los pulsos del SW-420 pueden durar solo unos milisegundos y un código con delay() entre lecturas se los perdería.
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}Máquina de estados: el cerebro de la alarma
Si intentas escribir una alarma con bloques if y delay(), enseguida se convierte en un lío: mientras se espera el retardo de entrada nadie lee el teclado y mientras suena la sirena no se puede teclear la clave. La solución es una máquina de estados, en la que el sistema está en un único estado en cada momento y solo cambia ante eventos concretos. En el código los estados se definen con un enum, loop() aplica en cada vuelta solo las reglas del estado actual y las esperas se miden con millis() en lugar de delay(). Así el bucle da miles de vueltas por segundo y no se pierde ninguna pulsación ni ningún evento de sensor.
| Estado | ¿Cuándo se entra? | ¿Qué hace? | ¿Cuándo sale? |
|---|---|---|---|
| DESARMADA | Al encender y tras clave + # | Vigila los sensores pero nunca da alarma; el LED verde se enciende si todo está listo | Clave + A o clave + B |
| RETARDO DE SALIDA | Clave + A (total) o B (en casa) | Cuenta 30 s; el zumbador pita cada segundo y acelera en los últimos 10 s | ARMADA al acabar; DESARMADA con clave + # |
| ARMADA | Al terminar el retardo de salida | Vigila todas las zonas; el LED rojo queda encendido | ENTRADA si se abre la puerta; ALARMA en zona instantánea o interior |
| RETARDO DE ENTRADA | Se abre la puerta estando armada | El zumbador pita 20 s; el PIR y el láser esperan | DESARMADA con clave + #; ALARMA al acabar el tiempo |
| ALARMA | Se dispara una zona | Suenan sirena y zumbador; la sirena calla a los 3 min y el LED rojo parpadea rápido | Solo DESARMADA con clave + # |
Armar, desarmar y la clave en el teclado
| Teclas | Función |
|---|---|
| 2580 + A | Modo total: las cuatro zonas activas, 30 s de retardo de salida |
| 2580 + B | Modo en casa (noche): PIR desactivado; puerta, ventana y láser activos |
| 2580 + # | Desarma; si hubo alarma, muestra la zona disparada en el Monitor Serie |
| * o C | Borra una clave mal tecleada |
| D | Prueba de zonas: muestra los valores de puerta, PIR y láser en el Monitor Serie |
| 3 claves erróneas | El teclado se bloquea 60 s; en ese tiempo se rechaza incluso la clave correcta |
Pedir la clave también para armar evita que niños o invitados activen el sistema por error. El código hace además una comprobación de no lista: si intentas armar con la puerta abierta o el haz láser cortado, oirás un pitido largo y el sistema no se arma, igual que en las centrales comerciales. No dejes la clave en 2580 y evita códigos fáciles como 1234, 0000 o tu año de nacimiento. Si quieres cambiar la clave sin volver a subir el código, guárdala en la EEPROM y añade un menú para introducir una clave nueva desde el teclado. Con una pantalla LCD I2C junto al teclado verás también la cuenta atrás y los mensajes de estado; por eso los pines A4 y A5 se han dejado libres.
Código del sistema de alarma con Arduino
El código siguiente es el corazón de esta guía. En la sección de ajustes de arriba puedes cambiar la clave, los retardos, el tiempo de sirena, el umbral del láser y el tipo de relé; no necesitas tocar el resto. En un Arduino Uno ocupa unos 6 KB de memoria de programa y 620 bytes de RAM, así que queda mucho sitio para añadir una LCD o un lector RFID.
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 columna vertebral del código es la función suz(). Cada vez que cambia el valor bruto de una entrada se guarda una marca de tiempo, y el estado estable no cambia hasta que el valor se mantiene igual durante un tiempo fijo (50 ms para la puerta, 200 ms para el PIR, 30 ms para el láser). Este filtro sencillo evita que los picos cortos en cables largos y los rebotes de los contactos disparen la alarma. Leer el pin de la puerta con INPUT_PULLUP también es una decisión deliberada: con la puerta cerrada el reed une el pin a GND y se lee LOW; cuando la puerta se abre o alguien corta el cable, el pin pasa a HIGH. Esta lógica de lazo cerrado de las alarmas reales trata un cable cortado igual que una puerta abierta.
Para la vibración se usa otro método. Incluso un solo golpe hace rebotar el SW-420 varias veces en pocos milisegundos; titresimVar() cuenta primero como un único evento todo lo que ocurre en 150 ms y luego guarda los tiempos de los tres últimos eventos. Si tres eventos caben en 3 segundos, se considera un intento real de forzar la ventana; el viento que sacude la puerta o un balón contra el cristal no dan alarma. La sirena calla sola a los 3 minutos, pero el sistema sigue en estado ALARMA y el LED rojo parpadea rápido para avisar a quien llegue de que hubo una alarma. Al introducir la clave correcta, el nombre de la zona guardado en la variable tetikleyen aparece en el Monitor Serie; es una pequeña versión de la memoria de alarma de las centrales comerciales.
Cómo ajustar los retardos de entrada y salida
Si los retardos son demasiado cortos dispararás tu propia alarma; si son demasiado largos, regalarás tiempo al ladrón. Elige un retardo de salida del doble de lo que tardas en ir del teclado a la puerta y cerrarla, y un retardo de entrada de aproximadamente el doble de lo que tardas en abrir la puerta, llegar al teclado y teclear la clave. Las centrales comerciales suelen usar 30–60 segundos para salir y 15–30 segundos para entrar. Colocar el teclado junto a la puerta, pero fuera de la vista desde el exterior, te permite acortar el retardo de entrada. El comportamiento del zumbador también importa: al salir, un pitido corto por segundo se acelera en los últimos 10 segundos para que notes que se acaba el tiempo; al entrar, un pitido intermitente de 200 ms te recuerda que debes teclear la clave.
Conexión de la sirena y el relé
En el circuito de la sirena el Arduino solo acciona la bobina del relé; la corriente de 12 V de la sirena pasa por los contactos del relé. La mayoría de módulos de relé son activos a nivel bajo, es decir, se activan cuando el pin IN está en LOW; por eso el código tiene la constante ROLE_AKTIF_LOW. El código escribe el nivel de apagado antes de configurar el pin como salida, así el relé no se activa un instante al arrancar el Arduino ni hace chirriar la sirena. Al elegir la sirena fíjate en su consumo: las pequeñas sirenas de 12 V y 110 dB suelen consumir 0,1–0,3 A, una carga mínima para un contacto de relé de 10 A. Una sirena exterior con flash disuade con sonido y luz y muestra a los vecinos de dónde viene la alarma. Las centrales profesionales suelen apagar la sirena a los 3 minutos y en algunos países existe incluso un límite legal, así que mantén SIREN_SURE en un valor razonable. Si prefieres un interruptor de estado sólido en vez de un relé, puedes usar un MOSFET de nivel lógico y, en sirenas con bobina (electromagnéticas), añadir un diodo en inversa entre sus terminales.
Avisos por Telegram con el ESP32
Una sirena que nadie oye no sirve de nada; la forma más fácil de saber que ha saltado la alarma cuando estás fuera es un ESP32 y Telegram. El ESP32 se conecta al wifi y envía mensajes al servidor de Telegram mediante una conexión cifrada, y además puedes mandarle comandos. Explicamos paso a paso cómo crear un bot con BotFather, obtener el token y tu chat ID e instalar las librerías UniversalTelegramBot y ArduinoJson en el artículo bot de Telegram con ESP32. Esta versión no tiene teclado; el sistema se controla desde el móvil con los comandos /kur (armado total), /evde (modo en casa), /coz (desarmar) y /durum (estado). El bot solo responde a tu chat ID e ignora en silencio los comandos de cualquier otra persona.
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}El código del ESP32 tiene tres diferencias importantes. La primera: enviar un mensaje a Telegram o consultar comandos nuevos puede tardar 1–2 segundos, y durante ese tiempo loop() espera. Una puerta que se queda abierta y un PIR que se mantiene en HIGH al menos unos segundos no se ven afectados, pero los pulsos de milisegundos del SW-420 podrían perderse; por eso la vibración se captura con una interrupción (attachInterrupt). La segunda: alarmVer() enciende primero la sirena y después envía el mensaje, así que por lenta que vaya la conexión la sirena nunca se retrasa. Los avisos que no se pueden enviar quedan en una cola y se reintentan cada 15 segundos. La tercera: la librería Preferences guarda en la memoria flash si el sistema está armado; si se va la luz estando armado y vuelve, el ESP32 arranca armado y te avisa de que ha vuelto la corriente. Si el relé y la sirena comparten una fuente débil con el ESP32, la placa puede reiniciarse con el error Brownout detector was triggered; la solución está en el artículo el ESP32 se reinicia constantemente.
Alarma con Raspberry Pi Pico y MicroPython
Puedes montar la misma máquina de estados en una Raspberry Pi Pico con un programa corto en MicroPython. El teclado se lee con la función de escaneo de filas y columnas de la guía del teclado matricial; las filas están en GP2–GP5 y las columnas, con pull-up interno, en GP6–GP9. Alimenta el PIR desde VBUS (5 V); su salida ya es de 3,3 V, así que va directa a GP16. El SW-420, en cambio, aliméntalo siempre desde el pin 3V3(OUT), porque la salida de un módulo alimentado a 5 V puede dañar la entrada de la Pico. Los pulsos de vibración se capturan con Pin.irq y sus tiempos se guardan en una lista, mientras que los estados se siguen con cadenas de texto sencillas. Los tiempos se calculan con time.ticks_diff(), porque esta función da la diferencia correcta incluso cuando el contador se desborda.
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)Cómo reducir las falsas alarmas
El peor enemigo de un sistema de alarma no es el ladrón, sino la falsa alarma: una sirena que ha sonado tres veces por nada ya no se la toman en serio ni tú ni tus vecinos. Parte de las medidas siguientes ya están en el código; el resto tiene que ver con el montaje y el cableado:
- Espera el calentamiento: El PIR puede dispararse solo durante los primeros 30–60 segundos tras el encendido; el código ignora el primer minuto.
- Usa un filtro: Esperar a que cada entrada se mantenga estable cierto tiempo (antirrebote) elimina los picos cortos de los cables.
- Cuenta golpes: Buscar 3 golpes en 3 segundos en lugar de uno solo descarta el viento, los camiones que pasan y los portazos. Si hace falta, sube la cuenta a 4–5 o baja la sensibilidad con el potenciómetro.
- Coloca bien el PIR: No lo apuntes a radiadores, aire acondicionado, ventanas soleadas ni cortinas; detecta mejor cuando el movimiento cruza su campo de visión de lado a lado. Si tienes mascotas, móntalo alto, inclínalo un poco hacia abajo y baja la sensibilidad.
- Exige dos sensores: En zonas críticas, pedir que el PIR y el láser, o el PIR y la puerta, se disparen con pocos segundos de diferencia (zonas cruzadas) impide que un solo sensor defectuoso provoque una alarma.
- Limpia la alimentación: La caída de tensión al activarse el relé puede disparar el PIR. Pon condensadores de 100 µF y 100 nF entre VCC y GND de cada sensor y lleva los cables del relé y la sirena separados de los de los sensores.
- Usa pull-up externas en cables largos: La pull-up interna del Arduino es de 20–50 kΩ y capta ruido en un cable de 10 metros; una resistencia externa de 4,7 kΩ y un condensador de 100 nF en el pin hacen la línea mucho más robusta.
- Protege el láser de la luz ambiente: Coloca la LDR dentro de un tubo negro y prueba el umbral por separado de día y de noche.
Errores frecuentes y soluciones
- La alarma salta nada más armarla: El PIR aún se está calentando o el contacto de la puerta está mal conectado. En la prueba de sensores la puerta cerrada debe leer LOW; si lee HIGH, el imán está lejos o un cable está roto.
- El teclado no responde o lee teclas equivocadas: Los cables de filas y columnas están cambiados. En lugar de recablear, cambia el orden de los arrays satirPinleri y sutunPinleri en el código.
- Se oye un pitido largo al intentar armar: No es una avería, es el aviso de no lista: la puerta está abierta o el haz láser no llega a la LDR. Pulsa D para ver el estado de las zonas.
- El relé hace clic un instante al encender: No se escribió el nivel de apagado antes de configurar el pin como salida, o ROLE_AKTIF_LOW sigue en true con un relé activo a nivel alto.
- El Arduino se reinicia cuando suena la sirena: La sirena y el Arduino comparten una fuente débil y la caída de tensión reinicia la placa. Usa una fuente más potente, un convertidor aparte y cable más grueso; detalles en el Arduino se reinicia solo.
- El sensor de vibración nunca se dispara o se dispara siempre: Gira despacio el potenciómetro para ajustar el umbral y pega el sensor firmemente al cristal o al marco; un sensor suelto no transmite el golpe.
- El ESP32 no envía mensajes: El token o el chat ID son incorrectos, la red es de 5 GHz o nunca iniciaste el bot en Telegram. Un bot no puede escribirte hasta que le hayas enviado /start una vez.
- El láser va bien de día pero da falsas alarmas de noche (o al revés): El umbral está demasiado cerca del nivel de luz ambiente. Mide los valores con y sin haz en las dos situaciones y elige un nuevo LAZER_ESIK en el medio.
Ideas para mejorar tu sistema de alarma
- Armar y desarmar con tarjeta: Añade un lector RFID RC522 junto al teclado y desarma con un llavero.
- Zonas de 24 horas: Con un sensor de gas MQ-2 o un sensor de llama crea una zona de gas e incendio que da alarma aunque el sistema esté desarmado.
- Sensores de puerta inalámbricos: Haz contactos de puerta a pilas para ventanas lejanas con módulos RF de 433 MHz.
- Panel con pantalla: Muestra la cuenta atrás, el estado y la memoria de alarma en una LCD I2C.
- Integración domótica: Conecta el ESP32 con MQTT y Home Assistant y gestiona la alarma junto con las luces y las cámaras.
- Contacto antisabotaje (tamper): Pon un microinterruptor en la tapa de la caja para que el zumbador suene si alguien la abre, aunque el sistema esté desarmado.
- Proyecto de maqueta: Reúne todas estas zonas en una casa de maqueta como la capa de seguridad de un proyecto de casa inteligente.
Como combina lectura de sensores, temporización, máquina de estados y diseño de la alimentación en un solo proyecto, un sistema de alarma con Arduino es también una forma estupenda de aprender. Empieza por la prueba de sensores, prueba luego la alarma unos días solo con el zumbador y conecta la sirena al final. En la app BlueGrays encontrarás proyectos como el sensor de puerta con reed, el sistema de seguridad láser, la cerradura con clave en teclado 4x4 y LCD y el sistema de alarma de movimiento con ESP32 y avisos por Telegram, cada uno con esquemas y código paso a paso.
Preguntas frecuentes
¿Cómo se hace un sistema de alarma con Arduino?
Conectas a un Arduino Uno un sensor de movimiento PIR, un contacto reed para la puerta, un teclado, un zumbador activo y un módulo de relé que acciona una sirena. El código es una máquina de estados que pasa el sistema por los estados desarmada, retardo de salida, armada, retardo de entrada y alarma. Se arma y desarma con una clave en el teclado y, si un sensor se dispara estando armada, suena la sirena.
¿Qué sensores se usan en una alarma con Arduino?
Los más habituales son un sensor de movimiento PIR (HC-SR501) para interiores, contactos magnéticos reed (MC-38) para puertas y ventanas, un sensor de vibración SW-420 para golpes en el cristal y una barrera láser para pasillos. Se pueden añadir un MQ-2 para gas y un sensor de llama para incendios. Cada sensor se define como una zona aparte con su propio comportamiento.
¿Funciona una alarma con Arduino si se va la luz?
No, si solo se alimenta con un adaptador. Con una fuente de alarma de 13,8 V con conexión de batería y una batería de 12 V y 7 Ah, un sistema con Uno que consume unos 100 mA funciona 4–5 días. En la versión ESP32 el estado armado se guarda en la flash, así que al volver la luz el sistema arranca armado; para que salgan los avisos, el router también necesita un SAI.
¿Puede una alarma con Arduino enviar avisos al móvil?
Un Arduino Uno no puede hacerlo solo, pero un ESP32 envía mensajes instantáneos de Telegram por wifi y recibe desde el móvil comandos como /kur y /coz. Donde no hay internet, un módulo GSM SIM800L puede enviar SMS o hacer una llamada. Combinar los dos métodos es la opción más fiable.
¿Por qué el sensor PIR da falsas alarmas?
Las causas más comunes son el calentamiento de 30–60 segundos tras el encendido, fuentes de calor como radiadores y aire acondicionado, cortinas que se mueven calentadas por el sol y el ruido en la alimentación cuando conmuta un relé. Ignorar el calentamiento en el código, alejar el sensor de fuentes de calor y poner un condensador entre VCC y GND resuelve la mayoría de los casos.
¿Sirve una alarma con Arduino para una casa real?
Funciona bien para aprender y para vigilar una habitación, un garaje o un trastero, pero no sustituye a un sistema de alarma certificado. Los sistemas comerciales se prueban según normas como la EN 50131 en protección antisabotaje, alimentación de respaldo, comunicación inalámbrica cifrada y conexión con una central receptora. Si usas un sistema con Arduino en casa, te recomendamos añadir batería de respaldo, contacto antisabotaje y un segundo canal de aviso.
Proyectos para hacer después de leer esto
Ver todos los proyectos
Sistema de Alarma de Movimiento ESP32 (PIR + Buzzer + LED + Telegram)
Un sistema inteligente de alarma de seguridad que activa el buzzer y el LED cuando el sensor PIR detecta movimiento, y envía simultáneamente una notificación instantánea al propietario vía Telegram. Cuenta con un tiempo de espera de 30 segundos para limitar la frecuencia de las alarmas repetidas; este mecanismo no comprueba la validez de las detecciones del sensor PIR ni elimina las falsas detecciones. Ideal para seguridad del hogar, vigilancia de garajes, almacenes o salas de trabajo. Con ESP32

Sistema de seguridad láser
Como las escenas de robos con láser de las películas: un rayo láser apunta a un sensor de luz, y en cuanto alguien se cruza y corta el rayo, se enciende el LED rojo y el zumbador empieza a sonar. La a

Sistema de alerta láser
Montas una valla de rayos invisible, como en las películas. Un láser apunta a un sensor de luz al otro lado; en cuanto alguien pasa y corta el rayo, se enciende el LED y suena el zumbador, y cuando el

Sensor de puerta con interruptor reed
Sabe si tu puerta está abierta o cerrada con solo mirar una luz. Pones un pequeño imán en la puerta y en el marco un interruptor que nota los imanes; con la puerta cerrada quedan juntos y se enciende

Cerradura con contraseña, teclado 4x4 y LCD
Una pequeña cerradura electrónica que no deja pasar a quien no sepa la clave. Pulsas la tecla de almohadilla y escribes la clave en el teclado; si es correcta, la pantalla indica acceso permitido, se

Detector de movimiento con sensor de movimiento
Un vigilante sencillo que te avisa cuando alguien entra en la habitación. El sensor de movimiento capta el calor que desprende el cuerpo humano, y el Arduino mantiene el LED encendido mientras hay mov

Caja fuerte electrónica inteligente
Una pequeña caja fuerte para tus cosas de valor que se abre con un código. Escribes tu código de cuatro cifras en el teclado; si es correcto, el servomotor abre el cierre, se enciende la luz verde y l
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 200 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

