Guías · 21 min de lectura

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.

Sensor de movimiento PIR HC-SR501
El sensor de movimiento PIR HC-SR501 es la zona interior de la alarma: su lente de Fresnel blanca divide el campo de visión en decenas de pequeñas zonas y los dos potenciómetros de al lado ajustan el alcance y el tiempo de salida.

¿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:

ZonaSensorTipo de zonaSi se dispara estando armada
Puerta de entradaContacto reed (MC-38)RetardadaEmpieza un retardo de entrada de 20 s; alarma si no se teclea la clave
Salón, interiorPIR (HC-SR501)Interior (seguidora)Alarma inmediata; espera si corre el retardo de entrada; desactivada en modo en casa
VentanaVibración (SW-420)InstantáneaAlarma inmediata con 3 golpes en 3 s
Pasillo, escaleraLáser + LDR (KY-008)Interior (seguidora)Alarma inmediata; espera si corre el retardo de entrada
Gas, fuego, sabotajeMQ-2, sensor de llama, interruptor de caja24 horasAlarma 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

MaterialCant.Función
Arduino Uno (o Nano)1El cerebro del sistema; los pines de esta guía son iguales en Uno y Nano
Sensor de movimiento PIR HC-SR5011Detección de movimiento interior, 3–7 m, unos 110°
Contacto magnético MC-38 (o reed + imán)1Abre el circuito al abrirse una puerta o ventana
Módulo sensor de vibración SW-4201Detecta golpes en el cristal o el marco
Módulo láser KY-008 + LDR + resistencia de 10 kΩ1 juegoBarrera de luz que cruza el pasillo
Teclado de membrana 4x41Armar y desarmar con clave
Zumbador activo (5 V)1Pitido de teclas, aviso de entrada y salida, sirena interior
Módulo de relé de 1 canal, 5 V1Activa la sirena de 12 V aislada del Arduino
Sirena de 12 V (mejor con flash)1El sonido principal de la alarma, normalmente 100–120 dB
LED rojo y verde + 2 resistencias de 220 Ω1 juegoIndicadores de armada y lista
Fuente de 12 V, módulo LM2596, batería de 12 V (opcional)1 juegoAlimentación y respaldo en cortes de luz
Protoboard, cables dupont, cable de alarma de 4 hilos-Conexiones y sensores lejanos
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

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.
El KY-008 es un láser de clase 3R; mirar el haz directamente o a través de un espejo puede dañarte la vista. Mantén siempre el haz por debajo de la altura de los ojos, por ejemplo a 20–40 cm del suelo. Esa altura es segura y además garantiza que quien entre corte el haz al dar un paso.

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ónArduino UnoESP32Raspberry 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 / GND5V / D10 / GNDVIN (5V) / GPIO 27 / GNDVBUS / GP16 / GND
Contacto reed (2 cables)D11 y GNDGPIO 33 y GNDGP15 y GND
SW-420 VCC / DO / GND5V / D12 / GND3V3 / GPIO 32 / GND3V3(OUT) / GP17 / GND
KY-008 S / − (láser siempre encendido)5V / GND––
LDR entre 5V y A0, 10 kΩ entre A0 y GNDA0––
Zumbador activo + (− a GND)A1GPIO 25GP14
IN del módulo de reléA2GPIO 26GP13
LED verde (con 220 Ω): listaA3––
LED rojo (con 220 Ω): armadaD13GPIO 2 (LED integrado)LED integrado
Nunca conectes la sirena de 12 V directamente a un pin del Arduino. El positivo de la fuente va al terminal COM del relé, el terminal NO del relé al positivo de la sirena y el negativo de la sirena vuelve al negativo de la fuente; así la corriente de la sirena nunca pasa por el Arduino. Los pines de 3,3 V del ESP32 y la Pico no siempre logran soltar algunos módulos de relé de 5 V y el relé puede quedarse activado; en estas placas usa un módulo compatible con 3,3 V o de disparo por nivel alto, o un driver con transistor NPN.

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

  1. 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.
  2. 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.
  3. 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.
  4. Conecta los dos cables del contacto reed a D11 y GND, y el SW-420 a 5V, D12 y GND.
  5. 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.
  6. 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 Ω.
  7. Primero sube la prueba de sensores de abajo, abre el Monitor Serie a 9600 baudios y activa cada sensor uno por uno.
  8. 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.

C++ (Arduino IDE)
1// Alarm sensör testi: tüm girişleri saniyede iki kez yazdırır / Alarm sensor test: prints every input twice a second
2const int PIR_PIN = 10;        // HC-SR501 OUT
3const int KAPI_PIN = 11;       // reed kapı kontağı, diğer ucu GND / reed door contact, other leg to GND
4const int TITRESIM_PIN = 12;   // SW-420 DO
5const int LAZER_PIN = A0;      // LDR bölücünün orta noktası / LDR divider midpoint
6
7int titresimSayisi = 0;        // yarım saniyedeki darbe sayısı / pulses in the last half second
8int oncekiTitresim = LOW;
9unsigned long sonYazma = 0;
10
11void setup() {
12  Serial.begin(9600);
13  pinMode(PIR_PIN, INPUT);
14  pinMode(KAPI_PIN, INPUT_PULLUP);   // kapı kapalı = LOW, açık = HIGH / door closed = LOW, open = HIGH
15  pinMode(TITRESIM_PIN, INPUT);
16}
17
18void loop() {
19  int t = digitalRead(TITRESIM_PIN);  // kısa darbeleri kaçırmamak için sürekli oku / read constantly so short pulses are not missed
20  if (t == HIGH && oncekiTitresim == LOW) titresimSayisi++;
21  oncekiTitresim = t;
22
23  if (millis() - sonYazma >= 500) {
24    sonYazma = millis();
25    Serial.print(F("PIR: "));
26    Serial.print(digitalRead(PIR_PIN));
27    Serial.print(F("  Kapi / door: "));
28    Serial.print(digitalRead(KAPI_PIN) == HIGH ? F("ACIK / open") : F("kapali / closed"));
29    Serial.print(F("  Titresim / vibration: "));
30    Serial.print(titresimSayisi);
31    Serial.print(F("  Lazer / laser: "));
32    Serial.println(analogRead(LAZER_PIN));
33    titresimSayisi = 0;
34  }
35}
Con esta prueba también encuentras el umbral del láser: anota la lectura con el haz sobre la LDR (p. ej. 950) y la lectura con la mano delante del haz (p. ej. 250), y pon en LAZER_ESIK del código de la alarma el punto medio (600). Si la diferencia entre ambas es menor de 300, alarga el tubo o vuelve a alinear el láser.

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?
DESARMADAAl encender y tras clave + #Vigila los sensores pero nunca da alarma; el LED verde se enciende si todo está listoClave + A o clave + B
RETARDO DE SALIDAClave + A (total) o B (en casa)Cuenta 30 s; el zumbador pita cada segundo y acelera en los últimos 10 sARMADA al acabar; DESARMADA con clave + #
ARMADAAl terminar el retardo de salidaVigila todas las zonas; el LED rojo queda encendidoENTRADA si se abre la puerta; ALARMA en zona instantánea o interior
RETARDO DE ENTRADASe abre la puerta estando armadaEl zumbador pita 20 s; el PIR y el láser esperanDESARMADA con clave + #; ALARMA al acabar el tiempo
ALARMASe dispara una zonaSuenan sirena y zumbador; la sirena calla a los 3 min y el LED rojo parpadea rápidoSolo DESARMADA con clave + #

Armar, desarmar y la clave en el teclado

TeclasFunción
2580 + AModo total: las cuatro zonas activas, 30 s de retardo de salida
2580 + BModo 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 CBorra una clave mal tecleada
DPrueba de zonas: muestra los valores de puerta, PIR y láser en el Monitor Serie
3 claves erróneasEl 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.

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

La 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.

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}

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.

El wifi no siempre es fiable: cuando se va la luz también se apaga el router, y un ladrón puede simplemente desenchufarlo. Conectar el router a un pequeño SAI o enviar SMS como canal de respaldo con un módulo GSM SIM800L asegura que el aviso llegue pase lo que pase. Si además quieres una foto cuando se detecta movimiento, mira enviar fotos a Telegram con la ESP32-CAM.

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.

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)

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.
Antes de confiar en el sistema, haz una prueba de recorrido: desconecta la sirena del terminal COM del relé, arma la alarma y recorre la casa por donde lo haría un ladrón. Observa en el Monitor Serie qué zona se dispara y cuándo, y después déjalo unos días funcionando solo con el zumbador para comprobar que no se dispara solo.

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.

Ver todos los proyectos
Sistema de Alarma de Movimiento ESP32 (PIR + Buzzer + LED + Telegram)
#145Avanzado
Premium

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

0.0(0)
9
Sistema de seguridad láser
#23Avanzado
Premium

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

4.8(60)
77
Sistema de alerta láser
#22Intermedio
Premium

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

4.8(64)
83
Sensor de puerta con interruptor reed
#38Principiante
Premium

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

4.7(34)
44
Cerradura con contraseña, teclado 4x4 y LCD
#64Avanzado
Premium

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

4.8(59)
75
Detector de movimiento con sensor de movimiento
#24Principiante
Premium

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

4.6(47)
67
Caja fuerte electrónica inteligente
#92Avanzado
Premium

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

4.7(42)
63