Objetivo y caso de uso
Qué construirás: Un Monitor de Red Local para Técnicos de Campo independiente utilizando un ESP32 configurado como Punto de Acceso por Software (SoftAP). Este transmite una red Wi-Fi segura y localizada para servir un panel de diagnóstico en tiempo real y aislado de internet directamente a un teléfono inteligente o tableta.
Por qué es importante / Casos de uso
- Diagnóstico independiente de la infraestructura: Permite a los técnicos conectarse directamente a los equipos en entornos sin conexión, como campos agrícolas remotos, sótanos industriales profundos o sitios de nueva construcción.
- Registro seguro de fallas de la máquina: Se interconecta con relés de falla industriales para que el personal pueda monitorear de manera segura los estados de la máquina a través del navegador, evitando la exposición física a paneles de control de alto voltaje.
- Interfaz hombre-máquina (HMI) aislada: Ofrece un portal de configuración altamente seguro y aislado de internet (air-gapped) al que no se puede acceder desde la internet pública, reduciendo drásticamente las superficies de ataque de ciberseguridad.
- Monitoreo de control de acceso: Actúa como un monitor localizado y temporal para puertas de racks de servidores o puertas de seguridad, registrando instantáneamente los estados de apertura/cierre.
Resultado esperado
- El ESP32 transmite de manera confiable una red Wi-Fi protegida por WPA2 (SSID:
ESP32-FieldMonitor) con un tiempo de conexión de cliente de <2 segundos. - Un panel web totalmente adaptable a dispositivos móviles se carga sin internet externo, renderizando elementos de la interfaz de usuario de diagnóstico.
- El estado del hardware en tiempo real y los registros de fallas se actualizan en el navegador del cliente con una latencia de red de <50ms.
Audiencia: Desarrolladores de IoT Industrial, Técnicos de Campo, Ingenieros de Mantenimiento; Nivel: Intermedio
Arquitectura/flujo: ESP32 (Modo SoftAP) → Transmite SSID WPA2 → El teléfono inteligente del técnico se conecta → Servidor Web ESP32 → Sirve Panel HTML/JS & Transmite datos GPIO en tiempo real.
Nota educativa de validación
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 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
- Resultado automático: PASS.
- Estructura parseada: 4 apartados, 2 tablas y 2 bloques de código detectados antes de publicar.
- Código comprobado: 1 PlatformIO config + 1 ESP32 source/pio run.
- Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
- Hallazgos del informe: sin hallazgos bloqueantes.
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.
Nota educativa de seguridad
Este proyecto es un prototipo educativo, no un producto certificado. Antes de encender la configuración, verifica el esquema de pines de la revisión exacta de tu placa ULX3S, mantén las señales de E/S de la FPGA a 3.3 V, nunca conectes 5 V directamente a los pines de E/S, desconecta la alimentación antes de cambiar el cableado y utiliza fuentes externas adecuadas para cargas, motores o servos mientras compartes la tierra (ground) solo cuando el cableado lo requiera.
Diagrama de bloques conceptual
Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.
Arquitectura funcional
Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.
Ruta de validación
Resumen conceptual de las herramientas usadas para comprobar el material publicado.
Requisitos previos
Para completar con éxito este tutorial, necesitarás:
* Conocimiento básico de programación en C++ y conceptos de GPIO de microcontroladores.
* PlatformIO IDE instalado (ya sea como una extensión de Visual Studio Code o a través de la Interfaz de Línea de Comandos).
* Conocimiento básico de conceptos de redes, específicamente Puntos de Acceso Wi-Fi (SSID, WPA2) y direccionamiento IP.
* Un navegador web en un dispositivo con Wi-Fi (teléfono inteligente, tableta o computadora portátil) para ver el panel.
Materiales
- Microcontrolador: ESP32 DevKitC (Modelo exacto: ESP32 DevKitC V4, típicamente equipado con el módulo ESP32-WROOM-32).
- Dispositivo de entrada: Pulsador o interruptor de contacto seco (esto simulará un relé de falla de máquina externo o contacto de puerta).
- Indicador visual: LED de estado estándar de 5mm (ej., azul o verde).
- Componentes pasivos: 1x resistencia de 330Ω (limitadora de corriente para el LED).
- Prototipado: Protoboard estándar y cables puente macho a macho.
- Conexión: Cable micro-USB (debe soportar tanto energía como transferencia de datos; los cables de solo carga fallarán al flashear el dispositivo).
(Nota: Dependiendo de tu fabricante específico de ESP32 DevKitC, es posible que necesites instalar controladores USB a UART CP210x o CH34x en tu sistema operativo para permitir que PlatformIO reconozca el dispositivo).
Configuración/Conexión
La configuración de hardware utiliza las resistencias pull-up internas del ESP32 para la entrada del contacto, minimizando la necesidad de componentes pasivos externos. Cuando el interruptor de contacto está abierto, la resistencia interna lleva el pin a HIGH (ALTO). Cuando el interruptor está cerrado, conecta el pin a Tierra (LOW o BAJO).
Tabla de cableado:
| Componente | Pin ESP32 DevKitC | Conexión / Destino | Descripción |
|---|---|---|---|
| Interruptor de contacto | GPIO 18 | Terminal 1 del interruptor | Configurado como INPUT_PULLUP. |
| Interruptor de contacto | GND | Terminal 2 del interruptor | Lleva el GPIO 18 a LOW cuando está cerrado. |
| LED de estado | GPIO 19 | Ánodo del LED (Pata larga) | Configurado como OUTPUT. |
| LED de estado | GND | Cátodo del LED (Pata corta) a través de resistencia de 330Ω | Completa el circuito del LED de forma segura. |
Notas importantes de hardware:
1. Asegúrate de usar una resistencia de 330Ω en serie con el LED de estado para evitar extraer corriente excesiva del pin GPIO del ESP32, lo cual podría dañar el microcontrolador.
2. No conectes el interruptor de contacto a ninguna fuente de voltaje externa. Debe actuar como un «contacto seco» (un simple cierre mecánico a Tierra).
Código validado
El proyecto requiere dos archivos dentro de tu espacio de trabajo de PlatformIO. El archivo platformio.ini configura el entorno de compilación, mientras que src/main.cpp contiene la lógica de la aplicación.
Configuración de PlatformIO
Crea o sobrescribe el archivo platformio.ini en la raíz del directorio de tu proyecto con la siguiente configuración. Esto asegura que se utilicen la definición correcta de la placa y la tasa de baudios del monitor serie.
; platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
Código fuente de la aplicación
Crea o sobrescribe el archivo src/main.cpp con el siguiente código completo y compilable.
Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.
// src/main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// Hardware Pin Definitions
#define CONTACT_PIN 18
#define STATUS_LED_PIN 19
// SoftAP Network Credentials
const char* ssid = "ESP32-FieldMonitor";
const char* password = "admin1234"; // WPA2 requires a minimum of 8 characters
// Initialize the WebServer on port 80
WebServer server(80);
// Global state variables
int currentContactState = HIGH;
int lastContactState = HIGH;
// HTML Dashboard stored in Program Memory (PROGMEM) to save RAM
const char dashboard_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Field Monitor Dashboard</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
text-align: center;
background-color: #e9ecef;
margin: 0;
padding: 20px;
}
.card {
background: white;
max-width: 400px;
margin: 40px auto;
padding: 30px;
border-radius: 12px;
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
h2 {
color: #343a40;
margin-top: 0;
}
.status-box {
font-size: 1.8em;
font-weight: bold;
margin: 20px 0;
padding: 20px;
border-radius: 8px;
transition: background-color 0.3s, color 0.3s;
}
.loading { background-color: #f8f9fa; color: #6c757d; border: 2px dashed #6c757d; }
.closed { background-color: #d4edda; color: #155724; border: 2px solid #28a745; }
.open { background-color: #f8d7da; color: #721c24; border: 2px solid #dc3545; }
.footer {
margin-top: 20px;
font-size: 0.85em;
color: #6c757d;
}
</style>
</head>
<body>
<div class="card">
<h2>Machine Contact Status</h2>
<div id="contact-state" class="status-box loading">Awaiting Data...</div>
<div class="footer">Auto-refreshing every 500ms via JSON API</div>
</div>
<script>
// Asynchronous function to fetch status from the ESP32
function fetchStatus() {
fetch('/api/status')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
const statusDiv = document.getElementById('contact-state');
// data.contact is 0 when closed (LOW) due to INPUT_PULLUP
if(data.contact === 0) {
statusDiv.innerHTML = "CONTACT CLOSED";
statusDiv.className = "status-box closed";
} else {
statusDiv.innerHTML = "CONTACT OPEN";
statusDiv.className = "status-box open";
}
// ... continúa para miembros en el código completo validado ...// src/main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// Hardware Pin Definitions
#define CONTACT_PIN 18
#define STATUS_LED_PIN 19
// SoftAP Network Credentials
const char* ssid = "ESP32-FieldMonitor";
const char* password = "admin1234"; // WPA2 requires a minimum of 8 characters
// Initialize the WebServer on port 80
WebServer server(80);
// Global state variables
int currentContactState = HIGH;
int lastContactState = HIGH;
// HTML Dashboard stored in Program Memory (PROGMEM) to save RAM
const char dashboard_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Field Monitor Dashboard</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
text-align: center;
background-color: #e9ecef;
margin: 0;
padding: 20px;
}
.card {
background: white;
max-width: 400px;
margin: 40px auto;
padding: 30px;
border-radius: 12px;
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
h2 {
color: #343a40;
margin-top: 0;
}
.status-box {
font-size: 1.8em;
font-weight: bold;
margin: 20px 0;
padding: 20px;
border-radius: 8px;
transition: background-color 0.3s, color 0.3s;
}
.loading { background-color: #f8f9fa; color: #6c757d; border: 2px dashed #6c757d; }
.closed { background-color: #d4edda; color: #155724; border: 2px solid #28a745; }
.open { background-color: #f8d7da; color: #721c24; border: 2px solid #dc3545; }
.footer {
margin-top: 20px;
font-size: 0.85em;
color: #6c757d;
}
</style>
</head>
<body>
<div class="card">
<h2>Machine Contact Status</h2>
<div id="contact-state" class="status-box loading">Awaiting Data...</div>
<div class="footer">Auto-refreshing every 500ms via JSON API</div>
</div>
<script>
// Asynchronous function to fetch status from the ESP32
function fetchStatus() {
fetch('/api/status')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
const statusDiv = document.getElementById('contact-state');
// data.contact is 0 when closed (LOW) due to INPUT_PULLUP
if(data.contact === 0) {
statusDiv.innerHTML = "CONTACT CLOSED";
statusDiv.className = "status-box closed";
} else {
statusDiv.innerHTML = "CONTACT OPEN";
statusDiv.className = "status-box open";
}
})
.catch(error => {
console.error('Error fetching status:', error);
const statusDiv = document.getElementById('contact-state');
statusDiv.innerHTML = "CONNECTION LOST";
statusDiv.className = "status-box loading";
});
}
// Poll the API every 500 milliseconds
setInterval(fetchStatus, 500);
// Initial fetch immediately on load
window.onload = fetchStatus;
</script>
</body>
</html>
)rawliteral";
// Route Handler: Serve the main HTML dashboard
void handleRoot() {
server.send(200, "text/html", dashboard_html);
Serial.println("Dashboard accessed by a client.");
}
// Route Handler: Serve the JSON API for the dashboard to consume
void handleApiStatus() {
// Construct a simple JSON string manually
String jsonPayload = "{\"contact\": " + String(currentContactState) + "}";
server.send(200, "application/json", jsonPayload);
}
// Route Handler: Handle 404 Not Found
void handleNotFound() {
server.send(404, "text/plain", "404: Not Found");
}
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
delay(1000); // Allow serial to stabilize
Serial.println("\n--- ESP32 Field Monitor Initialization ---");
// Configure Hardware Pins
pinMode(CONTACT_PIN, INPUT_PULLUP);
pinMode(STATUS_LED_PIN, OUTPUT);
// Read initial state
currentContactState = digitalRead(CONTACT_PIN);
lastContactState = currentContactState;
// Set initial LED state (ON when contact is closed/LOW)
digitalWrite(STATUS_LED_PIN, (currentContactState == LOW) ? HIGH : LOW);
// Configure Wi-Fi in Access Point (SoftAP) mode
Serial.print("Configuring Access Point...");
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.println(" Ready!");
Serial.print("SoftAP SSID: ");
Serial.println(ssid);
Serial.print("SoftAP IP Address: ");
Serial.println(IP);
// Define Web Server Routing
server.on("/", HTTP_GET, handleRoot);
server.on("/api/status", HTTP_GET, handleApiStatus);
server.onNotFound(handleNotFound);
// Start the Web Server
server.begin();
Serial.println("HTTP Web Server started.");
}
void loop() {
// Handle incoming HTTP client requests
server.handleClient();
// Read the physical contact state
currentContactState = digitalRead(CONTACT_PIN);
// Detect state changes to update the LED and log to Serial
if (currentContactState != lastContactState) {
// Debounce delay (basic implementation)
delay(50);
currentContactState = digitalRead(CONTACT_PIN);
if (currentContactState != lastContactState) {
if (currentContactState == LOW) {
Serial.println("EVENT: Contact CLOSED (Active).");
digitalWrite(STATUS_LED_PIN, HIGH); // Turn LED ON
} else {
Serial.println("EVENT: Contact OPEN (Inactive).");
digitalWrite(STATUS_LED_PIN, LOW); // Turn LED OFF
}
lastContactState = currentContactState;
}
}
}
Comandos de compilación/flasheo/ejecución
Usa la CLI de PlatformIO Core para compilar, cargar y monitorear tu proyecto. Abre tu terminal en el directorio raíz del proyecto (donde se encuentra platformio.ini) y ejecuta los siguientes comandos.
| Comando | Propósito |
|---|---|
pio run | Compila el proyecto y verifica todas las dependencias y la sintaxis. |
pio run --target upload | Compila y flashea el firmware compilado al ESP32 DevKitC. |
pio device monitor | Abre el monitor serie para ver los registros de ejecución a 115200 baudios. |
Flujo de trabajo de ejecución:
1. Conecta el ESP32 DevKitC a tu computadora a través del cable micro-USB.
2. Ejecuta pio run para asegurarte de que el código se compila sin errores de sintaxis.
3. Ejecuta pio run --target upload. (Si la carga falla al conectar, mantén presionado el botón BOOT en el ESP32 DevKitC cuando veas «Connecting…» en la terminal).
4. Ejecuta pio device monitor para observar la secuencia de inicialización y verificar la dirección IP del SoftAP.
Validación paso a paso
Sigue estos puntos de control para asegurarte de que el prototipo funcione exactamente como está previsto.
- Verificar la inicialización del puerto serie
- Acción: Observa la salida de la terminal inmediatamente después de ejecutar
pio device monitoro presionar el botónEN(Reinicio) en el ESP32. - Observación esperada: La terminal imprime «— ESP32 Field Monitor Initialization —«, seguido del SSID
ESP32-FieldMonitory la IP192.168.4.1. - Condición de aprobación: El ESP32 no se bloquea ni entra en un bucle de reinicios.
- Acción: Observa la salida de la terminal inmediatamente después de ejecutar
- Verificar la transmisión SoftAP
- Acción: Abre la configuración de Wi-Fi en un teléfono inteligente o computadora portátil.
- Observación esperada: Una red llamada
ESP32-FieldMonitoraparece en la lista de redes disponibles. - Condición de aprobación: Puedes conectarte a la red con éxito usando la contraseña
admin1234.
- Verificar la carga del panel web
- Acción: Abre un navegador web en el dispositivo conectado y navega a
http://192.168.4.1. - Observación esperada: Se carga el «Field Monitor Dashboard» (Panel del Monitor de Campo), mostrando una tarjeta estilizada. El monitor serie registra «Dashboard accessed by a client.» (Panel accedido por un cliente).
- Condición de aprobación: La interfaz de usuario se renderiza correctamente sin CSS roto.
- Acción: Abre un navegador web en el dispositivo conectado y navega a
- Verificar entrada física y salida LED
- *Acción
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.




