case-device-block-diagram, Qué construirás: Construirás un servidor web autónomo con ESP32 que monitoriza el estado físico de una puerta de garaje utilizando un interruptor magnético de lengüeta (reed switch) y acciona de forma segura un relé para abrirla o cerrarla. Este prototipo educativo proporciona una interfaz web localizada y sin nube para el control de acceso remoto. Audiencia: Aficionados al IoT y desarrolladores de hogares inteligentes; Nivel: Intermedio Arquitectura/flujo: Navegador web → HTTP GET/POST (Wi-Fi local) → Servidor web ESP32 → Lectura GPIO (Interruptor magnético) / Escritura GPIO (Relé de 5V) → Abridor de puerta de garaje Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema. Flujo conceptual: configuración local, emisión BLE y lectura desde el móvil. Resumen conceptual de las herramientas usadas para comprobar el proyecto ESP32 publicado. Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. Para este perfil ESP32 DevKitC, el proyecto se comprobó como proyecto PlatformIO: el validador extrajo Esta validación confirma compatibilidad sintáctica y de herramientas para el código publicado, pero no sustituye la prueba física sobre tu placa ESP32 DevKitC exacta, tu cableado, alimentación y entorno WiFi local. Este proyecto es un prototipo educativo de baja tensión para aprender a leer el estado de una puerta y accionar una salida local, no un abridor de garaje certificado ni un sistema de control de acceso real. Prueba primero con un LED o una carga ficticia, conecta el relé solo a una entrada aislada de contacto seco y bajo voltaje del equipo de puerta, nunca a red eléctrica, motores o cableado de potencia. Mantén siempre los finales de carrera, fotocélulas y protecciones físicas originales de la puerta. Antes de comenzar este tutorial, asegúrese de tener lo siguiente: Para este caso práctico, debe utilizar exactamente el siguiente modelo de dispositivo y componentes: La configuración del hardware requiere mapear los pines GPIO del ESP32 a los componentes respectivos. El ESP32 funciona con una lógica de 3.3V, pero la mayoría de los módulos de relé estándar de un canal requieren 5V para la bobina del relé mientras aceptan una señal lógica de 3.3V en su pin de entrada. El ESP32 DevKitC proporciona un pin de El proyecto utiliza PlatformIO. Cree un nuevo proyecto y reemplace la configuración predeterminada y los archivos fuente con el código que se proporciona a continuación. El código incluye una cadena incrustada de HTML, CSS y JavaScript. El JavaScript utiliza la moderna API Este archivo configura el entorno de compilación, especificando la placa, el framework y la velocidad del monitor serie. Este es el archivo fuente principal que contiene la lógica Wi-Fi, el enrutamiento del servidor web y el control de hardware GPIO. Actualice las macros Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print. Utilice la CLI de PlatformIO Core para compilar y subir el firmware. Abra la terminal dentro de VS Code y asegúrese de estar en el directorio raíz de su proyecto. Para asegurar que el prototipo funcione correcta y de forma segura, realice los siguientes puntos de control agrupados. Encuentra este producto y/o libros sobre este tema en Amazon Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.
.prometeo-educational-note,
.prometeo-device-postcode-section,
.prometeo-device-section-card {
margin: 2.4rem 0;
padding: 1.45rem 1.55rem;
border: 1px solid rgba(148, 163, 184, 0.30);
border-radius: 14px;
background:
linear-gradient(135deg, rgba(30, 41, 59, 0.50), rgba(15, 23, 42, 0.18)),
rgba(17, 24, 39, 0.48);
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.20);
}
.case-objective > h2:first-of-type,
.case-device-block-diagram > h2:first-of-type,
.prometeo-device-postcode-section > h2:first-of-type,
.prometeo-device-section-card > h2:first-of-type {
margin-top: 0;
}
.prometeo-device-section-card > :last-child,
.case-objective > :last-child,
.case-device-block-diagram > :last-child,
.prometeo-device-postcode-section > :last-child {
margin-bottom: 0;
}
.prometeo-device-section-card.prometeo-device-section-card-code {
border-left: 4px solid rgba(56, 189, 248, 0.86);
background:
linear-gradient(135deg, rgba(8, 47, 73, 0.42), rgba(15, 23, 42, 0.18)),
rgba(15, 23, 42, 0.58);
}
.prometeo-device-section-card.prometeo-device-section-card-compact {
padding: 1.2rem 1.35rem;
}
.prometeo-device-section-card pre,
.case-objective pre,
.case-device-block-diagram pre,
.prometeo-device-postcode-section pre {
max-width: 100%;
}
.prometeo-device-postcode-section .prometeo-device-flow-item {
background:
linear-gradient(135deg, rgba(15, 23, 42, 0.50), rgba(30, 41, 59, 0.28)),
rgba(15, 23, 42, 0.28);
}
@media print {
.case-objective,
.case-device-block-diagram,
.prometeo-educational-note,
.prometeo-device-postcode-section,
.prometeo-device-section-card {
background: #fff;
color: #111827;
box-shadow: none;
break-inside: avoid;
page-break-inside: avoid;
}
}
Objetivo y caso de uso
Por qué es importante / Casos de uso
Resultado esperado
Diagrama de bloques conceptual
Arquitectura funcional
Ruta de validación
Nota educativa de validación
platformio.ini y src/main.cpp, creó un proyecto temporal y ejecutó pio run contra platform = espressif32, board = esp32dev y framework = arduino. También revisó la estructura del artículo, que los comandos sean copiables con guiones ASCII, y que no aparezcan stacks no soportados como ESP-IDF directo o placas ESP32 no acotadas.Evidencia de validación publicada
Nota educativa de seguridad
Requisitos previos
* Un ordenador con Windows, macOS o Linux.
* Visual Studio Code (VS Code) instalado.
* La extensión PlatformIO IDE instalada en VS Code.
* Un cable micro-USB o USB-C que soporte tanto alimentación como transferencia de datos.
* Una red Wi-Fi local de 2.4GHz (los microcontroladores ESP32 no soportan redes de 5GHz).Materiales
* Microcontrolador: ESP32 DevKitC (versión estándar de 38 o 30 pines, con el módulo ESP-WROOM-32).
* Sensor: Interruptor magnético de lengüeta (sensor estándar de puerta/ventana normalmente abierto con dos cables).
* Actuador: Módulo de relé de 5V (1 canal, con optoacoplador integrado y controlador de transistor).
* Prototipado: Placa de pruebas (breadboard) estándar sin soldadura y variedad de cables puente (jumper) macho-macho y hembra-macho.Configuración/Conexión
5V (o VIN) que transmite el voltaje del USB, el cual utilizaremos para alimentar la bobina del relé.Tabla de cableado
Componente Pin / Cable del componente Pin del ESP32 DevKitC Notas Módulo de relé VCC / DC+ 5V / VIN Alimenta la bobina del relé (requiere alimentación por USB). Módulo de relé GND / DC- GND Tierra común. Módulo de relé IN / Signal GPIO 5 Señal lógica de 3.3V para activar el relé. Módulo de relé Terminales NO y COM Dispositivo objetivo Se conecta a los terminales del botón de pared del abridor de garaje. Interruptor magnético Cable 1 GPIO 18 La polaridad no importa. Interruptor magnético Cable 2 GND Utiliza la resistencia pull-up interna del ESP32. Instrucciones de conexión
5V del ESP32, GND al riel de tierra y el pin IN al GPIO 5.GND del ESP32.Código validado
fetch para consultar (poll) al ESP32 sobre el estado de la puerta cada 2 segundos, asegurando que la página web refleje la realidad física de la puerta sin requerir actualizaciones manuales del navegador.platformio.ini[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
src/main.cppWIFI_SSID y WIFI_PASSWORD para que coincidan con su router de pruebas.#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// --- Configuration ---
#define WIFI_SSID "LabNetwork"
#define WIFI_PASSWORD "LabPassword123"
// --- GPIO Pin Definitions ---
const int RELAY_PIN = 5;
const int REED_SWITCH_PIN = 18;
// --- Web Server Initialization ---
// Initialize the web server on standard HTTP port 80
WebServer server(80);
// --- HTML/CSS/JS Payload ---
// Using a raw string literal to embed the frontend code cleanly
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Garage Door Web Monitor</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f4f4f9;
color: #333;
text-align: center;
padding: 50px 20px;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
max-width: 400px;
margin: auto;
}
h1 { font-size: 1.5em; margin-bottom: 20px; }
.status {
font-size: 2em;
font-weight: bold;
margin: 20px 0;
padding: 10px;
border-radius: 5px;
}
.closed { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.open { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
button {
background-color: #007bff;
color: white;
border: none;
padding: 15px 30px;
font-size: 1.2em;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover { background-color: #0056b3; }
button:active { background-color: #004085; }
</style>
</head>
<body>
<div class="container">
<h1>Garage Door Monitor</h1>
<div id="door-status" class="status closed">Loading...</div>
<button onclick="triggerRelay()">Toggle Door</button>
</div>
<script>
// Function to fetch the current door status
function fetchStatus() {
fetch('/status')
.then(response => response.json())
.then(data => {
const statusDiv = document.getElementById('door-status');
if (data.state === "Closed") {
statusDiv.innerText = "CLOSED";
statusDiv.className = "status closed";
} else {
statusDiv.innerText = "OPEN";
statusDiv.className = "status open";
}
})
.catch(error => console.error('Error fetching status:', error));
}
// Function to trigger the relay via POST request
function triggerRelay() {
fetch('/trigger', { method: 'POST' })
.then(response => {
if(response.ok) {
console.log("Relay triggered successfully.");
// Immediately fetch status to reflect potential changes
setTimeout(fetchStatus, 1000);
}
})
.catch(error => console.error('Error triggering relay:', error));
}
// ...#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// --- Configuration ---
#define WIFI_SSID "LabNetwork"
#define WIFI_PASSWORD "LabPassword123"
// --- GPIO Pin Definitions ---
const int RELAY_PIN = 5;
const int REED_SWITCH_PIN = 18;
// --- Web Server Initialization ---
// Initialize the web server on standard HTTP port 80
WebServer server(80);
// --- HTML/CSS/JS Payload ---
// Using a raw string literal to embed the frontend code cleanly
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Garage Door Web Monitor</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f4f4f9;
color: #333;
text-align: center;
padding: 50px 20px;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
max-width: 400px;
margin: auto;
}
h1 { font-size: 1.5em; margin-bottom: 20px; }
.status {
font-size: 2em;
font-weight: bold;
margin: 20px 0;
padding: 10px;
border-radius: 5px;
}
.closed { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.open { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
button {
background-color: #007bff;
color: white;
border: none;
padding: 15px 30px;
font-size: 1.2em;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover { background-color: #0056b3; }
button:active { background-color: #004085; }
</style>
</head>
<body>
<div class="container">
<h1>Garage Door Monitor</h1>
<div id="door-status" class="status closed">Loading...</div>
<button onclick="triggerRelay()">Toggle Door</button>
</div>
<script>
// Function to fetch the current door status
function fetchStatus() {
fetch('/status')
.then(response => response.json())
.then(data => {
const statusDiv = document.getElementById('door-status');
if (data.state === "Closed") {
statusDiv.innerText = "CLOSED";
statusDiv.className = "status closed";
} else {
statusDiv.innerText = "OPEN";
statusDiv.className = "status open";
}
})
.catch(error => console.error('Error fetching status:', error));
}
// Function to trigger the relay via POST request
function triggerRelay() {
fetch('/trigger', { method: 'POST' })
.then(response => {
if(response.ok) {
console.log("Relay triggered successfully.");
// Immediately fetch status to reflect potential changes
setTimeout(fetchStatus, 1000);
}
})
.catch(error => console.error('Error triggering relay:', error));
}
// Poll the ESP32 every 2 seconds for status updates
setInterval(fetchStatus, 2000);
// Fetch initial status on load
window.onload = fetchStatus;
</script>
</body>
</html>
)rawliteral";
// --- Route Handlers ---
// Serve the main HTML page
void handleRoot() {
server.send(200, "text/html", index_html);
}
// Return the current state of the reed switch as JSON
void handleStatus() {
// Read the reed switch.
// LOW means the magnet is present (door closed).
// HIGH means the magnet is away (door open).
int sensorValue = digitalRead(REED_SWITCH_PIN);
String state = (sensorValue == LOW) ? "Closed" : "Open";
String jsonResponse = "{\"state\": \"" + state + "\"}";
server.send(200, "application/json", jsonResponse);
}
// Pulse the relay to simulate a button press
void handleTrigger() {
if (server.method() != HTTP_POST) {
server.send(405, "text/plain", "Method Not Allowed");
return;
}
// Most relays are Active HIGH. If yours is Active LOW, invert these states.
digitalWrite(RELAY_PIN, HIGH);
delay(500); // Hold the relay closed for 500ms
digitalWrite(RELAY_PIN, LOW);
server.send(200, "text/plain", "Triggered");
}
// Handle 404 Not Found
void handleNotFound() {
server.send(404, "text/plain", "404: Not Found");
}
// --- Main Setup and Loop ---
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
delay(100);
Serial.println("\n--- Garage Door Web Monitor ---");
// Initialize GPIOs
pinMode(RELAY_PIN, OUTPUT);
// Ensure relay starts in the OFF state immediately
digitalWrite(RELAY_PIN, LOW);
// Use internal pull-up for the reed switch
pinMode(REED_SWITCH_PIN, INPUT_PULLUP);
// Connect to WiFi
Serial.print("Connecting to WiFi: ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected successfully.");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// Configure Web Server Routes
server.on("/", HTTP_GET, handleRoot);
server.on("/status", HTTP_GET, handleStatus);
server.on("/trigger", HTTP_POST, handleTrigger);
server.onNotFound(handleNotFound);
// Start the server
server.begin();
Serial.println("HTTP server started.");
}
void loop() {
// Listen for incoming client requests
server.handleClient();
// Small delay to yield to the ESP32's underlying WiFi/OS tasks
delay(2);
}
Comandos de Compilación/Flasheo/Ejecución
Tabla de comandos
Acción Comando Inicializar proyecto (si es necesario) pio project init --board esp32devCompilar firmware pio runSubir al ESP32 pio run --target uploadAbrir monitor serie pio device monitorFlujo de trabajo
platformio.ini y src/main.cpp con el Código validado proporcionado anteriormente.pio run --target upload en la terminal para compilar el código y flashearlo en el microcontrolador.pio device monitor para ver la salida serie. Anote la dirección IP que se imprime una vez que el Wi-Fi se conecte.Validación paso a paso
192.168.1.50).Solución de problemas
Síntoma Causa probable Solución El monitor serie imprime puntos continuos; nunca se conecta. Credenciales Wi-Fi incorrectas o fuera de rango. Verifique que WIFI_SSID y WIFI_PASSWORD coincidan exactamente con su red de 2.4GHz. Asegúrese de que el ESP32 esté dentro del alcance del router.La página web carga, pero el estado se mantiene en «Loading…». Error del fetch en JavaScript o el navegador está bloqueando las peticiones locales.Revise la consola de desarrolladores del navegador (F12) en busca de errores de red o de CORS. Asegúrese de que está accediendo mediante http://, no https://.El relé hace clic inmediatamente al arrancar y se queda activado. El módulo de relé es de tipo «Active LOW» en lugar de «Active HIGH». Cambie digitalWrite(RELAY_PIN, LOW) a HIGH en setup(), e intercambie HIGH/LOW en la función handleTrigger().El estado de la puerta alterna aleatoriamente entre Abierto y Cerrado. Pin flotante o cable suelto en el interruptor magnético. Asegúrese de que el interruptor esté firmemente conectado al GPIO 18 y a GND. Verifique que pinMode esté estrictamente configurado como INPUT_PULLUP.El LED del relé se enciende, pero no hay un «clic» audible. Alimentación insuficiente para la bobina del relé. Asegúrese de que el VCC del relé esté conectado al pin 5V (VIN) del ESP32, no al pin 3.3V. Asegúrese de que el puerto USB proporcione corriente suficiente (500mA+).
Botón local
ESP32 BLE
Paquete advertising
LED de estado
Móvil escáner
Código fuente
PlatformIO build
Flash
Monitor serie




