Sistema de alarma antirrobos

Descripción del proyecto
Para este proyecto final, hemos decidido crear un sistema de alarma antirrobos. El objetivo es que una vez activado, el sistema monitorice continuamente el entorno mediante distintos sensores. Ante la detección de una intrusión o de una condición peligrosa, el sistema responderá activando señales acústicas y visuales, mostrando información en una pantalla LCD y enviando una notificación al móvil del usuario para alertar de forma remota.
Funcionamiento
El sistema funciona de la siguiente manera:
- Inicio y conexión a Internet
Cuando se enciende lo primero que hace es conectarse a internet.
– Si la conexión tiene éxito el sistema se inicia y se establece en modo reposo.
– Si la conexión falla, te avisa por la pantalla LCD y te da la opción de usar el sistema de manera offline, si accedes para a modo reposo. - Modo reposo
El sistema permanece en estado de reposo hasta que se escanea una tarjeta RFID o se pulsa la A en el teclado. - Activación del sistema
Si la tarjeta es válida, el sistema se activa y comienza la monitorización del entorno. - Sistema activo
Con el sistema activo los sensores detectan movimiento, humo, gas o un aumento excesivo de la temperatura.
Si se detecta alguna condición peligrosa:
– Se activa el buzzer.
– Se enciende el LED rojo.
– Se muestra un mensaje en la pantalla LCD indicando el tipo de alerta.
– Se envía una notificación al móvil del usuario indicando el tipo de alerta. - Desactivación del sistema
El sistema puede desactivarse mediante una tarjeta RFID autorizada o introduciendo un código en el teclado. - Información en tiempo real
La pantalla LCD informa en todo momento del estado actual del sistema y de los eventos ocurridos.
También existe un modo administrador en el sistema al que se accede pulsando en el teclado 0000, en este modo se pueden borrar las tarjetas autorizadas, autorizar nuevas tarjetas o cambiar la clave de desactivación.
Hardware
Componentes utilizados
| Componente | Uso en el proyecto | Precio |
| Arduino MEGA 2560 | Microcontrolador principal | Kit Propio |
| Módulo RFID RC522 | Lectura de tarjetas | Kit Propio |
| Teclado matricial 4×4 | Desactivación mediante código | Kit Propio |
| Sensor ultrasónico | Detección de movimiento | Kit Universidad |
| Sensor DHT11 | Monitorización de temperatura | Kit Universidad |
| Sensor de humo y gas MQ-2 | Detección de humo y gases | Comprado: 2€ |
| Pasive buzzer | Alarma sonora | Kit Propio |
| LED rojo | Alarma visual | Kit Universidad |
| Módulo de comunicación (NodeMCU ESP) | Envío de notificaciones al móvil | Comprado: 3€ |
| Pantalla LCD 1602 | Visualización del estado | Kit Universidad |
| Botones, protoboard, cables y resistencias | Conexión de componentes | Kit Propio |
Esquema de conexiones

Sistema montado

Software
El software del proyecto se divide en dos códigos, uno principal para la placa Arduino MEGA 2560, y otro para el NodeMCU encargado de la conexión a Internet y el envío a Telegram.
Código principal
/*
* ======================================================================================
* PROYECTO GRUPO 11 - SISTEMA DE SEGURIDAD CON NOTIFICACIÓN AL TELÉFONO.
* ======================================================================================
* DESCRIPCIÓN:
* - Sistema de alarma que utiliza múltiples sensores (Ultrasonido, Temperatura, Gas).
* - Se activa/desactiva mediante teclado o tarjetas RFID.
* - Las tarjetas RFID se guardan en la memoria EEPROM (no se borran al apagar).
* - Se comunica con un módulo WiFi (NodeMCU) vía Serial1 para alertas de Telegram.
* ======================================================================================
* FUNCIONAMIENTO:
* - Tecla 'A': ARMA el sistema.
* - Código "0000": Entra en MODO ADMIN para grabar nueva tarjeta.
* - Hasta 5 tarjetas RFID guardadas en EEPROM (persistentes).
* ======================================================================================
*/
// --------------------------------------------------------------------------------------
// 1. LIBRERÍAS Y CONSTANTES
// --------------------------------------------------------------------------------------
#include <SPI.h> // Protocolo de comunicación para el lector RFID.
#include <MFRC522.h> // Biblioteca para manejar el lector RFID RC522.
#include <LiquidCrystal.h> // Biblioteca para manejar la pantalla LCD.
#include <Keypad.h> // Biblioteca para manejar el teclado matricial.
#include <DHT.h> // Biblioteca para el sensor de temperatura/humedad.
#include <EEPROM.h> // Biblioteca para leer/escribir en la memoria interna del Arduino.
// --- PINES ---
// LCD
const int rs = 31, en = 33, d4 = 35, d5 = 37, d6 = 39, d7 = 41; // Definimos qué pines del Arduino van a la pantalla.
// ACTUADORES
#define PIN_BUZZER 9 // Zumbador para sonido de alarma.
#define PIN_LED 4 // LED indicador de estado/alarma.
// SENSORES
#define PIN_TRIG 6 // Pin de disparo del sensor Ultrasónico.
#define PIN_ECHO 7 // Pin de eco del sensor Ultrasónico.
#define PIN_DHT 8 // Pin de datos del sensor de temperatura.
#define PIN_GAS A0 // Pin analógico para el sensor de gas (MQ).
// COMUNICACIÓN RFID
#define SS_PIN 53 // Pin Slave Select para SPI.
#define RST_PIN 5 // Pin de Reset del módulo RFID.
// NodeMCU
#define RST_PIN_NODE 2 // Pin para resetear el NodeMCU
// --- CONFIGURACIÓN DE SENSORES ---
#define DHTTYPE DHT11 // Modelo del sensor de temperatura (DHT11).
// --- CONFIGURACIÓN DE MEMORIA (EEPROM) ---
// TARJETAS RFID
#define MAX_TARJETAS 5 // Máximo del número de tarjetas que recordaremos.
#define UID_SIZE 4 // Tamaño del identificador de la tarjeta (4 bytes estándar).
byte tarjetasValidas[MAX_TARJETAS][UID_SIZE]; // Matriz en RAM para guardar las tarjetas leídas de EEPROM.
// CLAVE CORRECTA
#define EEPROM_ADDR_CLAVE 100 // Dirección inicial para la clave.
#define CLAVE_SIZE 4 // Tamaño de la clave (4 dígitos).
// --------------------------------------------------------------------------------------
// 2. CREACIÓN DE OBJETOS
// --------------------------------------------------------------------------------------
LiquidCrystal lcd(rs, en, d4, d5, d6, d7); // Creamos el objeto pantalla LCD.
MFRC522 rfid(SS_PIN, RST_PIN); // Creamos el objeto RFID.
DHT dht(PIN_DHT, DHTTYPE); // Creamos el objeto sensor DHT.
// --- CONFIGURACIÓN TECLADO MATRICIAL (4x4) ---
const byte FILAS = 4;
const byte COLS = 4;
char keys[FILAS][COLS] = { // Mapa de caracteres del teclado.
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[FILAS] = {36, 34, 32, 30}; // Pines conectados a las filas.
byte colPins[COLS] = {28, 26, 24, 22}; // Pines conectados a las columnas.
Keypad teclado = Keypad(makeKeymap(keys), rowPins, colPins, FILAS, COLS); // Creamos el objeto teclado.
// --------------------------------------------------------------------------------------
// 3. VARIABLES GLOBALES
// --------------------------------------------------------------------------------------
// MÁQUINA DE ESTADOS
enum Estado { DESARMADO, ARMADO, ALARMA, ADMIN, ERROR }; // Enumeración para la "Máquina de Estados": define en qué situación está la alarma.
Estado estadoActual; // El estado actual.
// VARIABLES DEL SISTEMA
String claveCorrecta = "1234"; // Clave maestra para desarmar por teclado.
String inputClave = ""; // Variable para ir guardando lo que teclea el usuario.
String motivoAlarma = ""; // Guarda la razón de la alarma (Gas, Intruso, Fuego).
// TIMERS y FLAGs
unsigned long previousMillis = 0; // Para controlar tiempos sin usar delay() (multitarea).
bool ledState = LOW; // Estado del LED parpadeante en alarma.
// --------------------------------------------------------------------------------------
// 5. PROTOTIPOS DE FUNCIONES (SOLUCIÓN AL ERROR DE COMPILACIÓN)
// --------------------------------------------------------------------------------------
// Declaramos las funciones aquí para que el compilador sepa que existen antes de usarlas.
void loopDesarmado();
void loopArmado();
void loopAlarma();
void loopAdmin();
void loopError();
void cargarTarjetas();
void guardarTarjeta(byte nuevoUID[UID_SIZE]);
void borrarTarjetas();
bool esTarjetaValida(byte uid[UID_SIZE]);
void guardarClave(String clave);
void cargarClave();
void cambiarClaveCorrecta();
void cambiarEstado(Estado nuevo);
void triggerAlarma(String motivo);
void enviarMensajeTelegram(String mensaje);
void textoArmando();
bool gestionarPreAlarma();
void mensajeLCD(String linea1, String linea2);
void gestionarTecladoLCD();
bool leerRFID();
bool checkUltrasonico();
bool checkTemp();
bool checkGas();
// --------------------------------------------------------------------------------------
// 5. SETUP (CONFIGURACIÓN INICIAL)
// --------------------------------------------------------------------------------------
void setup() {
Serial.begin(9600); // Serial para el monitor del PC.
Serial1.begin(9600); // Serial para hablar con el NodeMCU.
Serial.println("Iniciando MEGA");
Serial.println("\n");
SPI.begin(); // Inicia bus SPI.
rfid.PCD_Init(); // Inicia lector RFID.
lcd.begin(16, 2); // Inicia LCD 16 columnas, 2 filas.
dht.begin(); // Inicia sensor temperatura.
// Configuración de modos de pines.
pinMode(PIN_BUZZER, OUTPUT);
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
cargarTarjetas(); // Leer tarjetas guardadas.
cargarClave(); // Cargar la claveCorrecta.
mensajeLCD("SISTEMA LISTO", "GRUPO 11");
delay(2000);
if(conectarWifi()) { // Si no ha habido un error.
mensajeLCD("CONEXION WIFI", "EXITO");
delay(2500);
cambiarEstado(DESARMADO); // Estado inicial DESARMADO.
} else { // Si ha habido un error.
mensajeLCD("CONEXION WIFI", "ERROR");
delay(2500);
cambiarEstado(ERROR); // Cambiar estado a ERROR.
}
}
// --------------------------------------------------------------------------------------
// 6. LOOP PRINCIPAL (MÁQUINA DE ESTADOS)
// --------------------------------------------------------------------------------------
void loop() {
switch (estadoActual) { // Máquina de estados: Ejecuta una función diferente según el estado actual.
case DESARMADO: loopDesarmado(); break;
case ARMADO: loopArmado(); break;
case ALARMA: loopAlarma(); break;
case ADMIN: loopAdmin(); break;
case ERROR: loopError(); break;
}
}
// ======================================================================================
// 7: LÓGICA DE ESTADOS (EL CEREBRO)
// ======================================================================================
// --- MODO: DESARMADO ---
void loopDesarmado() { // Alarma apagada.
char tecla = teclado.getKey(); // Lee el teclado.
if (tecla) {
if (tecla == '#') { // Si '#' borra la clave introducida.
inputClave = ""; // Limpia inputClave.
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(" "); // Limpiar línea de la pantalla.
} else if (tecla == 'A') { // Si 'A' se arma el sistema.
textoArmando(); // Muestra en pantalla que se esta armando.
cambiarEstado(ARMADO); // Cambia el estado a ARMADO.
} else { // Si cualquier otra cosa se añade a la clave.
inputClave += tecla;
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(" "); // Limpiar línea de la pantalla.
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(inputClave); // Muestra lo que escribes.
}
}
if (inputClave == "0000") { // Código secreto para entrar en modo admin.
inputClave = ""; // Limpia inputClave.
cambiarEstado(ADMIN); // Cambia el estado a ADMIN.
}
if (leerRFID()) { // Si pasas una tarjeta válida.
mensajeLCD("TARJETA OK", "");
delay(1000);
textoArmando(); // Muestra en pantalla que se esta armando.
cambiarEstado(ARMADO); // Cambia el estado a ARMADO.
}
}
// --- MODO: ARMADO ---
void loopArmado() { // Alarma activa.
// Verifica los sensores constantemente.
bool intruso = checkUltrasonico(); // Comprueba sensor ultrasónico.
bool calor = checkTemp(); // Comprueba sensor de temperatura.
bool gas = checkGas(); // Comprueba sensor de gas.
String motivo;
if (calor || gas) { // Si algún sensor salta.
// Guardamos el motivo (Que sensor a saltado).
if (calor) {
motivo = "FUEGO/CALOR";
} else {
motivo = "FUGA DE GAS";
}
triggerAlarma(motivo); // Dispara la alarma.
return;
}
if(intruso) {
if(!gestionarPreAlarma()) { // Si no se ha identificado a tiempo.
triggerAlarma("INTRUSO");
}
return;
}
// Permite desarmar con teclado o tarjeta.
gestionarTecladoLCD();
if (leerRFID()) {
cambiarEstado(DESARMADO); // Cambia el estado a DESARMADO.
}
}
// --- MODO: ALARMA DISPARADA ---
void loopAlarma() { // Alarma sonando.
// Parpadeo de LED y sonido sin usar delay() para no bloquear el teclado.
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= 200) { // Cada 200ms cambia de estado.
previousMillis = currentMillis;
ledState = !ledState; // Invierte (Encendido -> Apagado -> Encendido...).
// Activamos el LED y el Buzzer.
digitalWrite(PIN_LED, ledState);
digitalWrite(PIN_BUZZER, ledState);
}
// Permite apagar la alarma con teclado o tarjeta.
gestionarTecladoLCD();
if (leerRFID()) {
cambiarEstado(DESARMADO); // Cambia el estado a DESARMADO.
}
}
// -- MODO: ADMIN (CONFIGURACIÓN) ---
void loopAdmin() { // Modo Admin.
mensajeLCD("MODO ADMIN","* = C, # = B");
char tecla = teclado.getKey(); // Lee el teclado.
if(tecla) {
if (tecla == '#') { // Si pulsa '#' borra toda la memoria EEPROM.
borrarTarjetas(); // Borra todas las tarjetas
cambiarEstado(ADMIN); // Vuelve a admin.
return;
} else if(tecla == '*') { // Si pulsa '*' puede cambiar la clave correcta.
cambiarClaveCorrecta(); // Cambia la claveCorrecta.
delay(2000);
cambiarEstado(ADMIN); // Vuelve a admin.
return;
} else { // Cualquier otra tecla
inputClave += tecla;
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(" "); // Limpiar línea de la pantalla.
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(inputClave); // Muestra lo que escribes.
}
}
if (inputClave == "0000") { // Código para salir del modo admin.
mensajeLCD("SALIENDO...", "ADMIN CERRADO");
delay(1000);
inputClave = ""; // Limpia inputClave.
cambiarEstado(DESARMADO); // Cambia el estado a DESARMADO.
return;
}
// Si detecta una tarjeta nueva.
if (!rfid.PICC_IsNewCardPresent()) { // Comprueba si hay una nueva tarjeta RFID cerca del lector.
return; // Si no hay tarjeta.
}
if (!rfid.PICC_ReadCardSerial()) { // Intenta leer el número de serie (UID) de la tarjeta detectada.
return; // Si no se puede leer el UID.
}
// Copia el UID de la tarjeta detectada.
byte nuevoUID[UID_SIZE]; // Array de bytes para almacenar el UID de la tarjeta.
for (byte i = 0; i < UID_SIZE; i++) {
nuevoUID[i] = rfid.uid.uidByte[i]; // Guarda cada byte del UID en el array.
Serial.print(nuevoUID[i], HEX);
Serial.print(" ");
}
Serial.println();
// Guarda la tarjeta en EEPROM.
guardarTarjeta(nuevoUID);
rfid.PICC_HaltA(); // Libera la tarjeta para que no quede bloqueada.
mensajeLCD("NUEVA TARJETA", "GUARDADA OK!");
delay(2000);
cambiarEstado(ADMIN); // Vuelve a admin.
return;
}
// --- MODO: ERROR ---
void loopError() { // Error al conectar.
char tecla = teclado.getKey(); // Lee el teclado.
if(tecla == '*') { // Si pulsa *.
cambiarEstado(DESARMADO); // Cambiar el estado a DESARMADO.
return; // Salir.
}
}
// ======================================================================================
// 8: LÓGICA DE HARDWARE Y SENSORES
// ======================================================================================
bool conectarWifi() { // Reinicia el NodeMCU y trata de sincronizar. Devuelve true si no ha habido un error.
// Reiniciar el NodeMCU
pinMode(RST_PIN_NODE, OUTPUT);
digitalWrite(RST_PIN_NODE,LOW); // Llevar a LOW para resetear.
delay(100);
pinMode(RST_PIN_NODE, INPUT); // Cambiamos a INPUT para volver a HIGH (estado normal), pero sin mandar 5V, de esta forma enviara solo 3.3V.
// Sincronización: Espera a que el NodeMCU esté listo antes de arrancar la lógica.
mensajeLCD("CONECTANDO WIFI","");
int contador = 0; // Empieza el contador a 0.
String respuesta = ""; // Empieza la respuesta vacía.
bool error = true; // Iniciamos como si hubiera habido un error.
// unsigned long tiempoInicio = millis(); // Guardamos el tiempo actual.
while(respuesta.indexOf("Fin Setup NodeMC") == -1) { // Bucle mientras no haya terminado el NodeMCU.
// while(millis() - tiempoInicio < 30000) { // Esperamos respuesta MÁXIMO 30 s.
if (Serial1.available()) { // Si NodeMCU envía algo.
respuesta = Serial1.readStringUntil('\n'); // Lee la nueva línea que envía NodeMCU.
respuesta.trim(); // Elimina espacios en blanco sobrantes.
Serial.println("Respuesta del NodeMCU: " + respuesta);
}
if(respuesta.indexOf("Fin Setup NodeMC") >= 0) { // Salida de seguridad si ya conectó.
error = false; // No ha habido error.
}
if(respuesta.indexOf("Error de conexion") >= 0) { // Si ha habido un error en la conexión.
error = true; // Ha habido error.
break; // Salir.
}
// Animación de puntos en LCD.
String puntos = "";
for(int i=0; i<contador; i++) {
puntos += ".";
}
mensajeLCD("CONECTANDO WIFI", puntos);
contador++; // Incrementamos el contador.
if(contador > 5) { // Cuando llega a 5 puntos reinicia.
contador = 0; // Reiniciamos el contador.
}
}
if(error) { // Ha habido error.
return false;
} else { // No ha habido error.
return true;
}
}
void gestionarTecladoLCD() { // Gestiona la entrada de la clave por teclado.
char tecla = teclado.getKey(); // Leer teclado.
if (tecla) {
// Limpiar SIEMPRE la segunda línea antes de mostrar lo que escribes.
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(" "); // Limpia la línea.
if (tecla == '#') { // Si pulsa '#'.
inputClave = ""; // Limpia inputClave.
} else { // Si pulsa otra cosa.
inputClave += tecla; // Añadir tecla pulsada.
}
// Mostrar la clave actual en la segunda línea.
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(inputClave);
}
// Verificar si la clave es correcta.
if (inputClave == claveCorrecta) { // Si es correcta.
inputClave = ""; // Limpiar inputClave.
cambiarEstado(DESARMADO); // Cambiar al estado DESARMADO.
}
}
bool leerRFID() { // Función genérica para leer RFID y validar si la tarjeta es conocida.
// Si detecta una tarjeta nueva.
if (!rfid.PICC_IsNewCardPresent()) { // Comprueba si hay una nueva tarjeta RFID cerca del lector.
return false; // Si no hay tarjeta.
}
if (!rfid.PICC_ReadCardSerial()) { // Intenta leer el número de serie (UID) de la tarjeta detectada.
return false; // Si no se puede leer el UID.
}
// Copia el UID de la tarjeta detectada.
byte uid[UID_SIZE]; // Array de bytes para almacenar el UID de la tarjeta.
for (byte i = 0; i < UID_SIZE; i++) {
uid[i] = rfid.uid.uidByte[i]; // Guarda cada byte del UID en el array.
}
rfid.PICC_HaltA(); // Libera la tarjeta para que no quede bloqueada.
if(esTarjetaValida(uid)) { // Si la tarjeta es válida.
return true;
} else { // Si la tarjeta NO es válida.
// Guardar el estado actual para restaurar después.
String linea1, linea2;
if (estadoActual == DESARMADO) {
linea1 = "DESARMADO";
linea2 = "PASE TARJETA";
} else if (estadoActual == ARMADO) {
linea1 = "SISTEMA ARMADO";
linea2 = "VIGILANDO...";
} else if (estadoActual == ADMIN) {
linea1 = "MODO ADMIN";
linea2 = "";
} else if (estadoActual == ALARMA) {
linea1 = "!!ALARMA!!";
linea2 = motivoAlarma;
}
// Mostrar error temporal.
mensajeLCD("TARJETA", "ERRONEA");
delay(2000);
// Restaurar lo que había antes.
mensajeLCD(linea1, linea2);
return false;
}
}
bool checkUltrasonico() { // Lógica del sensor ultrasónico.
long duration, distance;
// Secuencia de pulso para activar el sensor.
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
duration = pulseIn(PIN_ECHO, HIGH, 30000); // Lee el PIN_ECHO con un timeout 30ms.
if (duration == 0) { // Si no detecta nada.
return false; // No activa la alarma.
}
distance = (duration / 2) / 29.1; // Convertir a cm.
return (distance <= 20); // Alarma si hay algo a 20 cm o menos.
}
bool checkTemp() { // Lógica del sensor de temperatura.
float t = dht.readTemperature(); // Lee los datos del sensor.
// Serial.print("Temperatura: ");
// Serial.println(t);
if (isnan(t)) { // Comprueba si es un número valido.
return false; // Si hay un error de lectura lo ignora.
}
return (t > 35.0); // Salta la alarma si supera 35°C.
}
bool checkGas() { // Lógica del sensor de gas.
long suma = 0;
// Hacemos 20 lecturas rápidas para eliminar el ruido.
for(int i = 0; i < 20; i++) {
suma += analogRead(PIN_GAS); // Lee los datos del sensor.
delay(5); // Pequeña pausa entre lecturas.
}
int promedio = suma / 20; // Calcula el promedio.
// Para comprobar y elegir el valor de calibración.
Serial.print("Gas: ");
Serial.println(promedio);
return (promedio > 400); // Valor de calibración.
}
// ======================================================================================
// 9: GESTIÓN DE MEMORIA (EEPROM)
// ======================================================================================
// --- GESTIÓN DE LAS TARJETAS RFID ---
void cargarTarjetas() { // Carga las tarjetas desde la memoria permanente a la variable RAM al iniciar.
for (int t = 0; t < MAX_TARJETAS; t++) {
for (int i = 0; i < UID_SIZE; i++) {
tarjetasValidas[t][i] = EEPROM.read(t * UID_SIZE + i); // Lee cada byte de la EEPROM.
}
}
}
void guardarTarjeta(byte nuevoUID[UID_SIZE]) { // Guarda una nueva tarjeta en la primera posición libre de la EEPROM.
for (int t = 0; t < MAX_TARJETAS; t++) {
bool libre = true;
for (int i = 0; i < UID_SIZE; i++) {
if (tarjetasValidas[t][i] != 0x00 && tarjetasValidas[t][i] != 0xFF) { // Verificamos si la posición 't' está vacía (0x00) o limpia (0xFF).
libre = false; // Esta posición ya tiene una tarjeta guardada.
break;
}
}
if (libre) { // Si encontramos un hueco libre, guardamos la tarjeta.
for (int i = 0; i < UID_SIZE; i++) {
tarjetasValidas[t][i] = nuevoUID[i]; // Actualiza RAM.
EEPROM.write(t * UID_SIZE + i, nuevoUID[i]); // Actualiza EEPROM (persistente).
}
Serial.println("Tarjeta añadida a EEPROM");
return; // Salimos tras guardar.
}
}
Serial.println("No hay espacio para más tarjetas");
}
void borrarTarjetas() { // Borra todas las tarjetas de la memoria (Escribe 0x00 en todo).
for (int t = 0; t < MAX_TARJETAS; t++) {
for (int i = 0; i < UID_SIZE; i++) {
tarjetasValidas[t][i] = 0x00; // Limpiamos el array de tarjetasValidas.
EEPROM.write(t * UID_SIZE + i, 0x00); // Limpiamos la memoria EEPROM.
}
}
Serial.println("Todas las tarjetas borradas de EEPROM");
mensajeLCD("EEPROM LIMPIA", "SIN TARJETAS");
delay(2000);
}
bool esTarjetaValida(byte uid[UID_SIZE]) { // Comprueba si la tarjeta leída existe en nuestra lista de permitidas.
for (int t = 0; t < MAX_TARJETAS; t++) {
bool coincide = true;
for (int i = 0; i < UID_SIZE; i++) {
if (tarjetasValidas[t][i] != uid[i]) { // Si un byte no coincide, no es esta tarjeta.
coincide = false; // La tarjeta no vale.
break;
}
}
if (coincide) { // Si la tarjeta vale.
return true; // Tarjeta encontrada en la lista.
}
}
return false; // Tarjeta NO encontrada en la lista.
}
// --- GESTIÓN DE LA CLAVE ---
void guardarClave(String clave) { // Función para guardar la clave en la memoria EEPROM.
for (int i = 0; i < CLAVE_SIZE; i++) {
EEPROM.write(EEPROM_ADDR_CLAVE + i, clave[i]); // Actualiza el EEPROM (persistente).
}
claveCorrecta = clave; // Actualizar en la RAM.
}
void cargarClave() { // Función para cargar la clave de memoria a la RAM.
String clave = "";
for (int i = 0; i < CLAVE_SIZE; i++) {
char c = EEPROM.read(EEPROM_ADDR_CLAVE + i); // Lee la memoria EEPROM.
if (c >= '0' && c <= '9') {
clave += c; // Validar que sea dígito.
}
}
if (clave.length() == CLAVE_SIZE) { // Si la clave tiene el tamaño adecuado.
claveCorrecta = clave; // Cargamos en la RAM.
} else { // Si no hay clave válida.
claveCorrecta = "1234"; // Cargamos en la RAM el valor por defecto.
}
}
void cambiarClaveCorrecta() { // Función para cambiar la claveCorrecta.
mensajeLCD("NUEVA CLAVE:", "____"); // Indicamos al usuario qué hacer.
inputClave = ""; // Limpia inputClave.
while(inputClave.length() < CLAVE_SIZE) { // BUCLE DE BLOQUEO: No salimos de aquí hasta tener 4 dígitos o cancelar.
char tecla = teclado.getKey(); // Lee el teclado.
if (tecla) {
if (tecla == '#') { // Si '#' borra la clave introducida.
inputClave = ""; // Limpia inputClave.
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(" "); // Limpiar línea de la pantalla.
} else if(tecla == '*') { // Si '*' cancelar.
mensajeLCD("CANCELADO", "");
delay(1000);
inputClave = ""; // Limpia inputClave.
mensajeLCD("MODO ADMIN", ""); // Restaurar pantalla anterior.
return;
} else { // Si cualquier otra cosa.
inputClave += tecla; // Se añade a la clave.
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(" "); // Limpiar línea de la pantalla.
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(inputClave); // Muestra lo que escribes.
}
}
}
if (inputClave == "0000") { // Si la clave nueva es 0000, poner que no es válida.
mensajeLCD("CLAVE NO VALIDA", "");
} else if(inputClave == claveCorrecta) { // Si la clave es la misma que estaba.
mensajeLCD("MISMA CLAVE", "");
} else { // Si es una nueva clave válida.
guardarClave(inputClave); // Guardar la nueva clave.
mensajeLCD("EXITO:", "CLAVE CAMBIADA");
}
delay(2000);
inputClave = ""; // Limpia inputClave.
mensajeLCD("MODO ADMIN", ""); // Volver a mostrar menú admin.
}
// ======================================================================================
// 10: INTERFAZ DE USUARIO Y UTILIDADES
// ======================================================================================
void cambiarEstado(Estado nuevo) { // Función para cambiar de estado.
estadoActual = nuevo;
lcd.clear(); // Limpia la pantalla.
digitalWrite(PIN_BUZZER, LOW); // Asegura silencio al cambiar.
digitalWrite(PIN_LED, LOW); // Apagamos el LED.
inputClave = ""; // Limpia inputClave.
// Pone el mensaje correspondiente al nuevo estado.
if (nuevo == DESARMADO) {
mensajeLCD("DESARMADO", "PASE TARJETA");
} else if (nuevo == ARMADO) {
mensajeLCD("SISTEMA ARMADO", "VIGILANDO...");
} else if (nuevo == ADMIN) {
mensajeLCD("MODO ADMIN", "");
} else if (nuevo == ERROR) {
mensajeLCD("ERROR CONEXION", "OFFLINE? = *");
}
}
void triggerAlarma(String motivo) { // Activa el estado de alarma y manda el aviso.
estadoActual = ALARMA;
motivoAlarma = motivo;
enviarMensajeTelegram("ALARMA:" + motivo); // Manda el mensaje al NodeMCU (Wifi).
mensajeLCD("!!ALARMA!!", motivo); // Muestra el mensaje en la pantalla LCD.
}
void enviarMensajeTelegram(String mensaje) { // Envía un comando al NodeMCU para mandar mensaje por Telegram.
Serial1.println(mensaje); // Enviar aviso al NodeMCU.
Serial.println("Enviar: " + mensaje); // Mostrar en Serial.
// Leer respuesta del NodeMCU.
if (Serial1.available()) {
String respuesta = Serial1.readStringUntil('\n');
Serial.println("\n");
Serial.println("Respuesta del NodeMCU: " + respuesta);
}
}
void textoArmando() { // Función auxiliar que muestra en pantalla que se esta armando, con un contador de 5s.
mensajeLCD("ARMANDO...", "SALGA EN 5s");
delay(1000);
for(int i=5; i>0; i--){ // Cuenta atras de 5s.
lcd.setCursor(14,1); // Coloca el cursor en la posición 14 de la segunda línea.
lcd.print(i); // Escribe en esa posición.
delay(1000);
}
}
bool gestionarPreAlarma() { // Función que gestiona un tiempo previo, para identificarse, antes de que suene la alarma.
mensajeLCD("INTRUSO DETECTADO", "IDENTIFICATE: 10");
unsigned long tiempoInicio = millis();
int segundosAnteriores = 10;
// Bucle que dura 10 segundos (10000 ms).
while (millis() - tiempoInicio < 10000) {
// Cuenta atrás de 5s en la pantalla LCD.
int segundosRestantes = 10 - (millis() - tiempoInicio) / 1000;
if (segundosRestantes != segundosAnteriores) {
lcd.setCursor(14, 1);
lcd.print(" "); // sobrescribe con espacios.
lcd.setCursor(14, 1);
lcd.print(segundosRestantes);
segundosAnteriores = segundosRestantes;
}
// --- LEER TECLADO ---
gestionarTecladoLCD();
if (estadoActual == DESARMADO) {
return true; // ¡Te has identificado a tiempo!
}
// --- LEER RFID ---
if (leerRFID()) {
cambiarEstado(DESARMADO);
return true; // ¡Te has identificado a tiempo!
}
}
return false; // Si salimos del while es que pasaron los 5s y nadie se identificó.
}
void mensajeLCD(String linea1, String linea2) { // Ayuda rápida para escribir en las dos líneas del LCD.
lcd.clear(); // Limpia la pantalla LCD.
lcd.setCursor(0, 0); // Coloca el cursor al inicio de la primera línea.
lcd.print(linea1); // Escribe el texto en la primera línea.
lcd.setCursor(0, 1); // Coloca el cursor al inicio de la segunda línea.
lcd.print(linea2); // Escribe el texto en la segunda línea.
}
Código NodeMCU
/*
* ================================================================================================
* PROYECTO GRUPO 11 - NODEMCU ESP8266 (MÓDULO DE COMUNICACIÓN).
* ================================================================================================
* DESCRIPCIÓN:
* 1. Se conecta a la red WiFi.
* 2. Escucha mensajes provenientes del Arduino Mega a través de SoftwareSerial (Pines D1 y D2).
* 3. Si recibe un mensaje, lo envía a un Bot de Telegram.
* ================================================================================================
*/
// --------------------------------------------------------------------------------------
// 1. LIBRERÍAS
// --------------------------------------------------------------------------------------
#include <ESP8266WiFi.h> // Biblioteca base para conexión WiFi.
#include <ESP8266HTTPClient.h> // Biblioteca para hacer peticiones web (GET/POST).
#include <WiFiClientSecure.h> // Biblioteca para conexiones seguras (HTTPS para Telegram).
#include <Ticker.h> // Biblioteca para temporizadores (usada para parpadear el LED).
// --------------------------------------------------------------------------------------
// 2. CONFIGURACIÓN DE USUARIO
// --------------------------------------------------------------------------------------
// --- DATOS WIFI ---
const char* ssid = "wifi"; // Nombre de tu WiFi (Usando la WiFi del móvil).
const char* password = "12345678"; // Contraseña de tu WiFi (Si no tiene pones "").
// --- DATOS TELEGRAM ---
const String token = "8349071268:AAEs5dE3DoI_XULVnBGZMyaBkCS4yPRdFXQ"; // Token de tu bot (Obtenido de @BotFather).
const String chat_id = "1477925874"; // Tu chat_id (ID de usuario).
// --- CONFIGURACIÓN DE HARDWARE ---
#define ledWifi D4 // El led integrado en el NodeMCU.
#define PIN_RX_MEGA D1 // Recibe datos DEL Mega (D1 del NodeMCU -> TX del Mega).
#define PIN_TX_MEGA D2 // Envía datos AL Mega (D2 del NodeMCU -> RX del Mega).
// --------------------------------------------------------------------------------------
// 3. VARIABLES Y OBJETOS GLOBALES
// --------------------------------------------------------------------------------------
// Objetos de Red.
WiFiClientSecure client; // Cliente seguro para HTTPS.
Ticker tic_WifiLed; // Objeto para controlar el parpadeo del LED.
// Variables de control.
byte cont = 0; // Contador de intentos.
byte max_intentos = 50; // Intentos de conexión a la Wifi.
// --------------------------------------------------------------------------------------
// 4. SETUP (INICIALIZACIÓN)
// --------------------------------------------------------------------------------------
void setup() {
Serial.begin(9600); // Canal USB (Para ver mensajes en el PC).
pinMode(ledWifi,OUTPUT); // Pin del Led.
digitalWrite(ledWifi, HIGH); // Apagado inicial.
client.setInsecure(); // Para HTTPS sin certificados (Telegram usa HTTPS. Esto evita tener que cargar certificados SSL complejos.).
tic_WifiLed.attach(0.2,parpadeoLedWifi); // Inicia parpadeo del led (Indica que esta intentando conectarse).
Serial.println("\n");
Serial.println("Iniciando NodeMCU");
// --- INTENTO DE CONEXIÓN WIFI ---
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED and cont < max_intentos) { // Cuenta hasta 50 si no se puede conectar lo cancela.
cont++;
delay(500);
Serial.print("."); // Imprime puntos en el PC mientras conecta.
}
Serial.println("");
// --- RESULTADO DE LA CONEXIÓN ---
if (cont < max_intentos) { // Si se conectó.
Serial.println("********************************************");
Serial.print("Conectado a la red WiFi: ");
Serial.println(WiFi.SSID());
Serial.print("IP: ");
Serial.println(WiFi.localIP());
Serial.print("macAdress: ");
Serial.println(WiFi.macAddress());
Serial.println("*********************************************");
}
else { // No se conectó.
Serial.println("------------------------------------");
Serial.println("Error de conexion");
Serial.println("------------------------------------");
}
tic_WifiLed.detach(); // Detiene el parpadeo.
digitalWrite(ledWifi,HIGH); // Se queda encendido el led.
delay(1000); // Pausa de seguridad.
Serial.println("Fin Setup NodeMCU."); // Enviamos este mensaje al MEGA, para indicar que está listo.
}
// --------------------------------------------------------------------------------------
// 5. LOOP PRINCIPAL
// --------------------------------------------------------------------------------------
void loop() {
if (Serial.available()) { // Escuchamos si hay datos llegando de MEGA.
String mensaje = Serial.readStringUntil('\n'); // Recibe de MEGA.
mensaje.trim(); // Limpiar espacios sobrantes al final.
if(mensaje.length() > 0) { // Si el mensaje tiene contenido.
Serial.println("Recibido: " + mensaje); // Confirmar que lo hemos recibido.
// Intentamos enviar a Telegram.
if (mensaje.startsWith("ALARMA")) { // Si el mensaje empieza por "ALARMA", enviamos a Telegram.
enviarTelegram(mensaje);
}
}
}
}
// --------------------------------------------------------------------------------------
// 6. FUNCIONES
// --------------------------------------------------------------------------------------
// --- FUNCIÓN PARA ENVIAR A TELEGRAM ---
void enviarTelegram(String mensaje) {
if (WiFi.status() == WL_CONNECTED) { // Verificar que seguimos conectados a internet.
// Necesario para evitar excepciones de memoria
std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
client->setInsecure(); // Ignorar certificados
HTTPClient http;
// Codificar mensaje para URL (Espacios y caracteres raros básicos).
mensaje.replace(" ", "%20");
mensaje.replace(":", "%3A");
// Construcción de la URL de la API de Telegram.
String url = "https://api.telegram.org/bot" + token + "/sendMessage?chat_id=" + chat_id + "&text=" + mensaje;
// Iniciar conexión y enviar petición GET.
if(http.begin(*client, url)) {
int httpCode = http.GET();
http.end(); // Cerramos conexión.
// Código 200 significa "OK" en protocolo HTTP.
if (httpCode == 200) {
Serial.println("Telegram OK");
} else { // Devuelve el código de error.
Serial.print("Error Telegram: ");
Serial.println(httpCode);
}
} else {
Serial.println("Error al conectar con API Telegram");
}
} else { // Si no hay WiFi.
Serial.println("Error: Sin WiFi");
}
}
// --- AUXILIARES ---
void parpadeoLedWifi(){ // Función simple para cambiar el estado del LED (Encender/Apagar)
byte estado = digitalRead(ledWifi);
digitalWrite(ledWifi,!estado);
}