case-device-block-diagram, Qué construirás: Un prototipo de termostato independiente y accesible vía web usando un ESP32 que monitoriza la temperatura ambiente y escala dinámicamente la velocidad de un ventilador de refrigeración mediante Modulación por Ancho de Pulso (PWM). Audiencia: Desarrolladores de IoT, Entusiastas del Hardware, Creadores de Domótica; Nivel: Intermedio Arquitectura/flujo: Sensor de Temperatura Analógico (ADC) → Procesamiento ESP32 → Salida PWM al Controlador del Ventilador ↔ AP Wi-Fi del ESP32 & UI del Servidor Web Asíncrono 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 termostato web es un prototipo educativo de bajo voltaje, no un controlador HVAC certificado ni un sistema de protección térmica. No conmutes calefactores, compresores ni cargas de red directamente desde el ESP32; usa solo ventiladores de baja tensión o módulos de interfaz aislados, con fuente externa adecuada y GND común solo cuando el esquema lo requiera. No lo dejes controlando equipos críticos sin supervisión. Para construir este prototipo, necesitas exactamente los siguientes componentes: La configuración del hardware se divide en tres bloques funcionales: el sensor de temperatura, el controlador del ventilador y el indicador de estado. Nota importante sobre la conexión a tierra: El GND del ESP32 debe estar conectado al GND de la fuente de alimentación de 12V para establecer un voltaje de referencia común para la puerta del MOSFET. Nota sobre el Divisor de Tensión: La resistencia de 10k y el termistor NTC forman un divisor de tensión. Debido a que el NTC está conectado a GND y la resistencia fija a 3.3V, el voltaje en GPIO34 disminuirá a medida que la temperatura aumente (ya que la resistencia del NTC cae a medida que se calienta). El software tiene en cuenta esta topología específica. El proyecto requiere dos archivos en tu espacio de trabajo de PlatformIO. El archivo Crea o sobrescribe el archivo Coloca el siguiente código en Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print. Usa los 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
Materiales
* ESP32 DevKitC (Versión estándar de 38 pines o 30 pines)
* Termistor NTC de 10 kOhm (Se asume un valor B estándar de 3950)
* Resistencia de precisión de 10 kOhm (Se prefiere una tolerancia del 1%, para el divisor de tensión)
* Controlador de ventilador MOSFET de nivel lógico (p. ej., un módulo IRLZ44N o un circuito personalizado con un MOSFET de nivel lógico de canal N, una resistencia pull-down de 10k en la puerta y un diodo flyback a través de los terminales del ventilador)
* LED de estado (Cualquier color, estándar de 5mm)
* Resistencia de 220 Ohm (Para el LED de estado)
* Ventilador de PC de 12V DC (Estándar de 2 o 3 hilos, accionado mediante conmutación de lado bajo)
* Fuente de alimentación de 12V DC (Con la capacidad nominal adecuada para el consumo de corriente del ventilador)
* Placa de pruebas y cables puenteConfiguración/Conexión
Componente
Pin / Conexión ESP32
Destino / Descripción
Termistor NTC
GNDUna pata del termistor NTC.
Termistor NTC
GPIO34 (ADC1_CH6)La otra pata del NTC. Conecta también la resistencia fija de 10k aquí.
Resistencia fija de 10k
3V3Conecta el otro extremo de la resistencia fija de 10k al pin 3.3V del ESP32.
Puerta del MOSFET (Controlador)
GPIO18Controla la conmutación PWM. (Asegúrate de que el controlador tenga una resistencia pull-down).
Fuente del MOSFET
GNDConecta a la tierra común (GND del ESP32 y GND de 12V).
Drenador del MOSFET
Negativo del ventilador (-)
Lleva el ventilador a tierra cuando el MOSFET está activo.
Positivo del ventilador (+)
Fuente de alimentación de 12VConecta directamente al terminal positivo de 12V.
Ánodo del LED de estado
GPIO21Conecta a través de la resistencia limitadora de corriente de 220 Ohm.
Cátodo del LED de estado
GNDConecta a la tierra común.
Código validado
platformio.ini configura el entorno de compilación, y src/main.cpp contiene la lógica de la aplicación.platformio.ini
platformio.ini en la raíz de tu proyecto con la siguiente configuración. Esto asegura que se utilicen el framework y la velocidad de baudios en serie correctos.[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
src/main.cpp
src/main.cpp. Este código maneja el Punto de Acceso Wi-Fi, el servidor web de estilo asíncrono (utilizando la biblioteca WebServer integrada), los cálculos del termistor de Steinhart-Hart y la lógica PWM proporcional.#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// --- Pin Definitions ---
const int NTC_PIN = 34; // ADC pin for thermistor voltage divider
const int FAN_PWM_PIN = 18; // PWM output to MOSFET gate
const int LED_PIN = 21; // Status LED
// --- PWM Configuration ---
const int PWM_FREQ = 5000; // 5 kHz frequency for standard PC fans
const int PWM_CHANNEL = 0; // LEDC channel 0
const int PWM_RES = 8; // 8-bit resolution (0-255)
// --- Thermistor Constants (Steinhart-Hart) ---
const float SERIES_RESISTOR = 10000.0; // 10k fixed resistor
const float NOMINAL_RESISTOR = 10000.0; // 10k NTC at 25 degrees C
const float NOMINAL_TEMP = 25.0; // 25 degrees C
const float B_COEFFICIENT = 3950.0; // Beta value of the thermistor
// --- Global Variables ---
float currentTempC = 0.0;
float targetTempC = 25.0; // Default threshold
int currentFanSpeed = 0; // 0 to 255
// --- Web Server on port 80 ---
WebServer server(80);
// --- HTML Dashboard (Stored in Flash) ---
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP32 Thermostat</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; margin: 0; padding: 20px; background-color: #f4f4f9; }
h1 { color: #333; }
.card { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); max-width: 400px; margin: auto; }
.metric { font-size: 2rem; font-weight: bold; color: #007BFF; margin: 10px 0; }
.slider-container { margin-top: 20px; }
input[type=range] { width: 100%; }
</style>
</head>
<body>
<div class="card">
<h1>Fan Thermostat</h1>
<p>Current Temperature:</p>
<div class="metric"><span id="tempValue">--</span> °C</div>
<p>Fan Speed (PWM):</p>
<div class="metric"><span id="fanValue">--</span> / 255</div>
<div class="slider-container">
<p>Target Threshold: <span id="targetValue">25</span> °C</p>
<input type="range" min="15" max="40" value="25" id="targetSlider" onchange="updateTarget(this.value)">
</div>
</div>
<script>
// Fetch data every 2 seconds
setInterval(function() {
fetch('/data')
.then(response => response.json())
.then(data => {
document.getElementById('tempValue').innerText = data.temperature.toFixed(1);
document.getElementById('fanValue').innerText = data.fan_speed;
document.getElementById('targetValue').innerText = data.target;
document.getElementById('targetSlider').value = data.target;
});
}, 2000);
// Send new threshold to ESP32
function updateTarget(val) {
document.getElementById('targetValue').innerText = val;
fetch('/set?threshold=' + val);
}
</script>
</body>
</html>
)rawliteral";
// --- Function to Calculate Temperature ---
float readTemperature() {
int adcValue = analogRead(NTC_PIN);
// Avoid division by zero if ADC is maxed out or grounded
if (adcValue == 0 || adcValue == 4095) return currentTempC;
// Convert ADC value to voltage (ESP32 ADC is 12-bit: 0-4095)
float voltage = adcValue * (3.3 / 4095.0);
// Calculate NTC resistance (Divider: 3.3V -> 10k -> ADC -> NTC -> GND)
float ntcResistance = SERIES_RESISTOR * voltage / (3.3 - voltage);
// Steinhart-Hart Equation
float steinhart;
steinhart = ntcResistance / NOMINAL_RESISTOR; // (R/Ro)
steinhart = log(steinhart); // ln(R/Ro)
steinhart /= B_COEFFICIENT; // 1/B * ln(R/Ro)
steinhart += 1.0 / (NOMINAL_TEMP + 273.15); // + (1/To)
steinhart = 1.0 / steinhart; // Invert
steinhart -= 273.15; // Convert to Celsius
return steinhart;
}
// ...#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// --- Pin Definitions ---
const int NTC_PIN = 34; // ADC pin for thermistor voltage divider
const int FAN_PWM_PIN = 18; // PWM output to MOSFET gate
const int LED_PIN = 21; // Status LED
// --- PWM Configuration ---
const int PWM_FREQ = 5000; // 5 kHz frequency for standard PC fans
const int PWM_CHANNEL = 0; // LEDC channel 0
const int PWM_RES = 8; // 8-bit resolution (0-255)
// --- Thermistor Constants (Steinhart-Hart) ---
const float SERIES_RESISTOR = 10000.0; // 10k fixed resistor
const float NOMINAL_RESISTOR = 10000.0; // 10k NTC at 25 degrees C
const float NOMINAL_TEMP = 25.0; // 25 degrees C
const float B_COEFFICIENT = 3950.0; // Beta value of the thermistor
// --- Global Variables ---
float currentTempC = 0.0;
float targetTempC = 25.0; // Default threshold
int currentFanSpeed = 0; // 0 to 255
// --- Web Server on port 80 ---
WebServer server(80);
// --- HTML Dashboard (Stored in Flash) ---
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP32 Thermostat</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; margin: 0; padding: 20px; background-color: #f4f4f9; }
h1 { color: #333; }
.card { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); max-width: 400px; margin: auto; }
.metric { font-size: 2rem; font-weight: bold; color: #007BFF; margin: 10px 0; }
.slider-container { margin-top: 20px; }
input[type=range] { width: 100%; }
</style>
</head>
<body>
<div class="card">
<h1>Fan Thermostat</h1>
<p>Current Temperature:</p>
<div class="metric"><span id="tempValue">--</span> °C</div>
<p>Fan Speed (PWM):</p>
<div class="metric"><span id="fanValue">--</span> / 255</div>
<div class="slider-container">
<p>Target Threshold: <span id="targetValue">25</span> °C</p>
<input type="range" min="15" max="40" value="25" id="targetSlider" onchange="updateTarget(this.value)">
</div>
</div>
<script>
// Fetch data every 2 seconds
setInterval(function() {
fetch('/data')
.then(response => response.json())
.then(data => {
document.getElementById('tempValue').innerText = data.temperature.toFixed(1);
document.getElementById('fanValue').innerText = data.fan_speed;
document.getElementById('targetValue').innerText = data.target;
document.getElementById('targetSlider').value = data.target;
});
}, 2000);
// Send new threshold to ESP32
function updateTarget(val) {
document.getElementById('targetValue').innerText = val;
fetch('/set?threshold=' + val);
}
</script>
</body>
</html>
)rawliteral";
// --- Function to Calculate Temperature ---
float readTemperature() {
int adcValue = analogRead(NTC_PIN);
// Avoid division by zero if ADC is maxed out or grounded
if (adcValue == 0 || adcValue == 4095) return currentTempC;
// Convert ADC value to voltage (ESP32 ADC is 12-bit: 0-4095)
float voltage = adcValue * (3.3 / 4095.0);
// Calculate NTC resistance (Divider: 3.3V -> 10k -> ADC -> NTC -> GND)
float ntcResistance = SERIES_RESISTOR * voltage / (3.3 - voltage);
// Steinhart-Hart Equation
float steinhart;
steinhart = ntcResistance / NOMINAL_RESISTOR; // (R/Ro)
steinhart = log(steinhart); // ln(R/Ro)
steinhart /= B_COEFFICIENT; // 1/B * ln(R/Ro)
steinhart += 1.0 / (NOMINAL_TEMP + 273.15); // + (1/To)
steinhart = 1.0 / steinhart; // Invert
steinhart -= 273.15; // Convert to Celsius
return steinhart;
}
// --- Web Server Route Handlers ---
void handleRoot() {
server.send(200, "text/html", index_html);
}
void handleData() {
String json = "{";
json += "\"temperature\":" + String(currentTempC) + ",";
json += "\"fan_speed\":" + String(currentFanSpeed) + ",";
json += "\"target\":" + String(targetTempC);
json += "}";
server.send(200, "application/json", json);
}
void handleSet() {
if (server.hasArg("threshold")) {
targetTempC = server.arg("threshold").toFloat();
Serial.print("New target threshold set to: ");
Serial.println(targetTempC);
}
server.send(200, "text/plain", "OK");
}
void setup() {
Serial.begin(115200);
Serial.println("\nInitializing ESP32 Thermostat...");
// Setup Pins
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Setup PWM for Fan (Using ESP32 LEDC API)
ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RES);
ledcAttachPin(FAN_PWM_PIN, PWM_CHANNEL);
ledcWrite(PWM_CHANNEL, 0);
// Setup Wi-Fi Access Point
WiFi.softAP("ESP32-Thermostat", "admin1234");
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
// Setup Web Server Routes
server.on("/", handleRoot);
server.on("/data", handleData);
server.on("/set", handleSet);
server.begin();
Serial.println("HTTP server started.");
}
void loop() {
server.handleClient();
// Read temperature periodically
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate > 1000) {
lastUpdate = millis();
currentTempC = readTemperature();
// Proportional Fan Control Logic
// If temp is below threshold, fan is off (0).
// If temp is at threshold, fan starts at minimum effective PWM (e.g., 100).
// If temp is 5 degrees above threshold, fan runs at max (255).
if (currentTempC < targetTempC) {
currentFanSpeed = 0;
} else {
// Map temperature range [target, target+5] to PWM range [100, 255]
currentFanSpeed = map(currentTempC * 10, targetTempC * 10, (targetTempC + 5) * 10, 100, 255);
// Constrain to ensure we don't exceed 8-bit limits
currentFanSpeed = constrain(currentFanSpeed, 100, 255);
}
// Apply PWM to fan
ledcWrite(PWM_CHANNEL, currentFanSpeed);
// Update Status LED
if (currentFanSpeed > 0) {
digitalWrite(LED_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
}
// Log to Serial Monitor
Serial.printf("Temp: %.1f C | Target: %.1f C | Fan PWM: %d\n", currentTempC, targetTempC, currentFanSpeed);
}
}
Comandos de compilación/flasheo/ejecución
Caso práctico: Termostato web con ESP32

Caso práctico: Baliza localizadora BLE con ESP32
case-device-block-diagram, Qué construirás: Una baliza localizadora de herramientas por Bluetooth de Baja Energía (BLE) que se acopla al equipo, permitiendo a los usuarios activar una alarma audible y visual localizada a través del teléfono inteligente para encontrar rápidamente objetos perdidos. Audiencia: Desarrolladores de sistemas embebidos e ingenieros de IoT; Nivel: Intermedio Arquitectura/flujo: Teléfono inteligente (Cliente BLE) → Conexión de RF → ESP32 (Servidor GATT BLE) → Devolución de llamada asíncrona → Control GPIO no bloqueante (LED/Zumbador) 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 una baliza BLE educativa de bajo voltaje, no un sistema certificado de localización, seguridad personal o inventario crítico. Verifica el pinout de tu ESP32 DevKitC, mantén los GPIO a 3,3 V, usa alimentación USB o batería adecuada y respeta la privacidad y la normativa local al emitir identificadores BLE en espacios compartidos. Para completar con éxito este tutorial, asegúrese de tener el siguiente software y conocimientos previos: Necesitará los componentes de hardware exactos que se enumeran a continuación para construir este prototipo: La configuración de hardware requiere conectar el pulsador, el zumbador piezoeléctrico y el LED de estado a pines específicos de Entrada/Salida de Propósito General (GPIO) en el ESP32 DevKitC. Utilizaremos la resistencia pull-up interna del ESP32 para el pulsador a fin de minimizar la cantidad de componentes externos. Instrucciones de cableado: Tabla resumen de conexiones: El proyecto requiere dos archivos dentro de la estructura de su proyecto de PlatformIO: el archivo de configuración ( Cree un nuevo proyecto de PlatformIO para el ESP32 DevKitC y reemplace el contenido de Reemplace el contenido de Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print. Para compilar, cargar y monitorear el código en su ESP32 DevKitC, use la CLI de PlatformIO Core. Asegúrese de que su terminal esté navegando en el directorio raíz de su proyecto (donde se encuentra Flujo de trabajo de ejecución: Siga estos puntos de control para verificar la funcionalidad de su baliza localizadora de herramientas BLE. Si encuentra problemas durante las fases de construcción o validación, consulte la siguiente tabla para ver problemas comunes y sus soluciones. Una vez que el prototipo básico esté funcionando, considere las siguientes mejoras para avanzar hacia un dispositivo listo para producció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.
.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
0x01) en la característica BLE personalizada activa la alarma de hardware con una latencia <50ms.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
* PlatformIO IDE: Instalado como una extensión dentro de Visual Studio Code, o la CLI de PlatformIO Core disponible en el path de su sistema.
* Aplicación de escáner BLE: Un teléfono inteligente con Bluetooth habilitado y una aplicación de depuración BLE genérica instalada. Las aplicaciones recomendadas incluyen «LightBlue» (Punch Through) o «BLE Scanner» (Bluepixel Technologies).
* Conocimientos básicos de C++: Familiaridad con variables, funciones y estructuras de control estándar.
* Controladores USB: Controladores adecuados para el puente USB a UART del ESP32 DevKitC (comúnmente Silicon Labs CP210x o WCH CH34x) instalados en su sistema operativo.Materiales
* ESP32 DevKitC: La placa de desarrollo del microcontrolador principal con capacidades de Wi-Fi y Bluetooth.
* Pulsador: Un interruptor táctil momentáneo estándar de 4 o 2 pines.
* Zumbador piezoeléctrico: Un zumbador piezoeléctrico pasivo capaz de generar diferentes tonos mediante modulación por ancho de pulsos (PWM).
* LED de estado: Un diodo emisor de luz estándar de 5mm (por ejemplo, rojo o azul) para proporcionar retroalimentación visual.
* Resistencia (330Ω): Una resistencia limitadora de corriente para el LED de estado.
* Protoboard: Una placa de pruebas (breadboard) de prototipado estándar de tamaño medio o completo.
* Cables puente (Jumper): Cables puente macho a macho para establecer conexiones entre los componentes.
* Cable Micro-USB: Un cable USB de alta calidad con capacidad de transmisión de datos para conectar el ESP32 DevKitC a su computadora.Configuración/Conexión
1. Distribución de energía: Conecte el pin GND del ESP32 DevKitC al riel negativo (azul/negro) de su protoboard.
2. LED de estado: Coloque el LED en la protoboard. Conecte el ánodo (pata más larga) al GPIO 25 del ESP32. Conecte el cátodo (pata más corta) a un extremo de la resistencia de 330Ω. Conecte el otro extremo de la resistencia al riel GND de la protoboard.
3. Zumbador piezoeléctrico: Coloque el zumbador piezoeléctrico pasivo en la protoboard. Conecte su terminal positivo al GPIO 26 del ESP32. Conecte su terminal negativo al riel GND.
4. Pulsador: Inserte el pulsador táctil a través de la zanja central de la protoboard. Conecte un terminal del botón al GPIO 27 del ESP32. Conecte el terminal diagonalmente opuesto (o el terminal adyacente en un interruptor de 2 pines) directamente al riel GND.
Componente
Pin del ESP32 DevKitC
Componente intermedio
Destino
LED de estado (Ánodo)
GPIO 25
Ninguno
Ánodo del LED
LED de estado (Cátodo)
N/A
Resistencia de 330Ω
Riel GND
Zumbador piezoeléctrico (+)
GPIO 26
Ninguno
Zumbador (+)
Zumbador piezoeléctrico (-)
N/A
Ninguno
Riel GND
Pulsador (Lado A)
GPIO 27
Ninguno
Terminal del botón
Pulsador (Lado B)
N/A
Ninguno
Riel GND
Código validado
platformio.ini) y el código fuente principal en C++ (src/main.cpp). Configuración de PlatformIO
platformio.ini con la siguiente configuración. Esto establece el framework, la placa y la velocidad en baudios del monitor serie.; platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
Código fuente de la aplicación principal
src/main.cpp con el código a continuación. Este código inicializa el servidor BLE, crea un servicio y una característica personalizados, e implementa un bucle no bloqueante para manejar el estado de la alarma y las pulsaciones del botón físico.// src/main.cpp
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// Hardware Pin Definitions
const int LED_PIN = 25;
const int BUZZER_PIN = 26;
const int BUTTON_PIN = 27;
// LEDC PWM Settings for the passive buzzer
const int PWM_CHANNEL = 0;
const int PWM_FREQ = 2000;
const int PWM_RESOLUTION = 8;
// BLE UUIDs - Generated unique identifiers for our custom service
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Global State Variables
bool deviceConnected = false;
bool alarmActive = false;
// Timing variables for non-blocking alarm pattern
unsigned long previousMillis = 0;
const long interval = 250; // Toggle every 250 milliseconds
bool toggleState = false;
// BLE Server Callbacks: Handle connection and disconnection events
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("BLE Device Connected.");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("BLE Device Disconnected. Restarting advertising...");
// Restart advertising so the device can be found again
pServer->startAdvertising();
}
};
// BLE Characteristic Callbacks: Handle incoming write requests from the smartphone
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.print("Received Value: ");
for (int i = 0; i < value.length(); i++) {
Serial.print(value[i], HEX);
Serial.print(" ");
}
Serial.println();
// Check if the received byte is 0x01 (Start Alarm) or 0x00 (Stop Alarm)
if (value[0] == 0x01) {
alarmActive = true;
Serial.println("Alarm TRIGGERED via BLE!");
} else if (value[0] == 0x00) {
alarmActive = false;
Serial.println("Alarm STOPPED via BLE.");
// Ensure hardware is turned off immediately
digitalWrite(LED_PIN, LOW);
ledcWriteTone(PWM_CHANNEL, 0);
}
}
}
};
// ...// src/main.cpp
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// Hardware Pin Definitions
const int LED_PIN = 25;
const int BUZZER_PIN = 26;
const int BUTTON_PIN = 27;
// LEDC PWM Settings for the passive buzzer
const int PWM_CHANNEL = 0;
const int PWM_FREQ = 2000;
const int PWM_RESOLUTION = 8;
// BLE UUIDs - Generated unique identifiers for our custom service
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Global State Variables
bool deviceConnected = false;
bool alarmActive = false;
// Timing variables for non-blocking alarm pattern
unsigned long previousMillis = 0;
const long interval = 250; // Toggle every 250 milliseconds
bool toggleState = false;
// BLE Server Callbacks: Handle connection and disconnection events
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("BLE Device Connected.");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("BLE Device Disconnected. Restarting advertising...");
// Restart advertising so the device can be found again
pServer->startAdvertising();
}
};
// BLE Characteristic Callbacks: Handle incoming write requests from the smartphone
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.print("Received Value: ");
for (int i = 0; i < value.length(); i++) {
Serial.print(value[i], HEX);
Serial.print(" ");
}
Serial.println();
// Check if the received byte is 0x01 (Start Alarm) or 0x00 (Stop Alarm)
if (value[0] == 0x01) {
alarmActive = true;
Serial.println("Alarm TRIGGERED via BLE!");
} else if (value[0] == 0x00) {
alarmActive = false;
Serial.println("Alarm STOPPED via BLE.");
// Ensure hardware is turned off immediately
digitalWrite(LED_PIN, LOW);
ledcWriteTone(PWM_CHANNEL, 0);
}
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting Tool Finder Beacon...");
// Initialize Hardware Pins
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Configure Pushbutton with internal pull-up resistor
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Configure PWM for the passive buzzer
ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);
ledcAttachPin(BUZZER_PIN, PWM_CHANNEL);
ledcWriteTone(PWM_CHANNEL, 0); // Ensure buzzer is quiet on boot
// Initialize BLE Device
BLEDevice::init("ToolFinder-Beacon");
// Create BLE Server
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create BLE Characteristic (Write capability)
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // Functions that help with iPhone connections
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("BLE Advertising Started. Waiting for connections...");
}
void loop() {
// 1. Check physical pushbutton to clear the alarm locally
// Since we use INPUT_PULLUP, a pressed button reads as LOW
if (digitalRead(BUTTON_PIN) == LOW) {
if (alarmActive) {
alarmActive = false;
Serial.println("Alarm STOPPED via physical button press.");
// Immediately turn off the hardware
digitalWrite(LED_PIN, LOW);
ledcWriteTone(PWM_CHANNEL, 0);
// Simple debounce delay
delay(300);
}
}
// 2. Handle the Alarm Pattern (Non-blocking)
if (alarmActive) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
toggleState = !toggleState;
if (toggleState) {
digitalWrite(LED_PIN, HIGH);
ledcWriteTone(PWM_CHANNEL, 2500); // 2.5 kHz tone
} else {
digitalWrite(LED_PIN, LOW);
ledcWriteTone(PWM_CHANNEL, 0); // Silence
}
}
} else {
// Ensure hardware remains off when alarm is inactive
digitalWrite(LED_PIN, LOW);
ledcWriteTone(PWM_CHANNEL, 0);
}
// Small delay to yield to FreeRTOS background tasks (BLE stack)
delay(10);
}
Comandos de compilación/carga/ejecución
platformio.ini).
Comando
Propósito
pio runCompila el proyecto para asegurar que no haya errores de sintaxis o dependencias.
pio run --target uploadCompila y carga el firmware construido en el ESP32 conectado a través de USB.
pio device monitorAbre el monitor serie para ver las declaraciones de impresión y los registros de depuración.
1. Conecte el ESP32 DevKitC a su computadora mediante el cable micro-USB.
2. Abra su terminal o la ventana de la CLI de PlatformIO.
3. Ejecute pio run para verificar que el código se compile limpiamente.
4. Ejecute pio run --target upload para cargar el dispositivo. Si el proceso de carga se detiene en «Connecting…», es posible que deba presionar y mantener presionado el botón «BOOT» en el ESP32 hasta que comience la carga.
5. Ejecute pio device monitor para observar la salida serie. Debería ver «Starting Tool Finder Beacon…» seguido de «BLE Advertising Started.»Validación paso a paso
pio device monitor) y presione el botón «EN» (Reinicio) en el ESP32.
4fafc201...) y el monitor serie muestra «BLE Device Connected.»
beb5483e...), toque el icono «Write» (Escribir, generalmente una flecha hacia arriba), seleccione «Byte Array» o «UINT8», ingrese 01 y envíe.
Solución de problemas
Síntoma
Causa probable
Solución
Falla la carga / Tiempo de espera en «Connecting…»
El ESP32 no entra automáticamente en el modo gestor de arranque (bootloader).
Presione y mantenga presionado el botón «BOOT» en el ESP32 DevKitC cuando el terminal muestre «Connecting…».
No hay salida serie visible
Velocidad en baudios incorrecta o faltan los controladores CP210x/CH34x.
Asegúrese de tener
monitor_speed = 115200 en platformio.ini. Verifique que los controladores USB estén instalados en el Administrador de dispositivos de su SO.
El dispositivo no aparece en el escaneo BLE
Problema de caché BLE del teléfono inteligente o inestabilidad de energía del ESP32.
Apague y vuelva a encender el Bluetooth en su teléfono inteligente. Asegúrese de que el ESP32 esté alimentado por un puerto USB con capacidad suficiente.
La aplicación se desconecta inmediatamente
Falla o tiempo de espera del servidor GATT.
Reinicie el ESP32. Asegúrese de que está escribiendo en la Característica, no en el descriptor del Servicio.
El zumbador emite un ruido continuo, sin pulsaciones
Código bloqueante (por ejemplo,
delay()) utilizado dentro de la devolución de llamada BLE.Verifique que el
loop() use millis() para la temporización como se proporciona en el Código validado, evitando bucles con delay().Mejoras
0x180F) para informar la energía restante del dispositivo al teléfono inteligente.
Quiz rápido
Caso práctico: Detector de fugas de agua con ESP32

case-device-block-diagram,
.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
Qué vas a construir: Un dispositivo inteligente de detección de fugas de agua basado en ESP32 que activa una alarma audiovisual local continua de forma inmediata, mientras aloja un Punto de Acceso (SoftAP) autónomo con un panel web en vivo y una API JSON para informar su estado a dispositivos locales.
Por qué es importante / Casos de uso
- Prevención de inundaciones industriales y residenciales: Detecta filtraciones de agua subterránea o fallos en válvulas de forma temprana, permitiendo la intervención antes de que ocurran daños graves.
- Monitorización de salas de servidores: Colocado debajo de unidades de aire acondicionado de precisión (CRAC) para detectar desbordamientos de condensación que podrían amenazar la infraestructura crítica.
- Dispositivo IoT de borde autónomo: Al operar en modo SoftAP, el dispositivo no depende de la infraestructura WiFi del edificio, garantizando que la red de alerta local y la API JSON permanezcan operativas incluso durante cortes de red del router principal.
Resultado esperado
- El ESP32 inicializa un Punto de Acceso WiFi autónomo y muestra su dirección IP en el monitor serie.
- En estado seco, los indicadores de hardware permanecen apagados y el panel web informa un estado «SECO» (DRY).
- Cuando el agua puentea los contactos del sensor, el dispositivo activa una alarma local continua (tiempo de respuesta del hardware de <100ms) y actualiza instantáneamente el panel web/JSON a un estado de «FUGA» (WET) con una latencia de red de <500ms.
Audiencia: Desarrolladores de IoT y automatización industrial; Nivel: Intermedio
Arquitectura/flujo: Sensor de agua analógico/digital → GPIO del ESP32 → Alarma de hardware de estado sólido (zumbador/LED) + Servidor web asíncrono sobre SoftAP → API JSON / Panel en vivo
Diagrama de bloques conceptual
Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.
Arquitectura funcional
Flujo conceptual: detección de humedad, decisión local y aviso al usuario.
Ruta de validación
Resumen conceptual de las herramientas usadas para comprobar el proyecto ESP32 publicado.
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, 4 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
Peligro eléctrico e hídrico: este proyecto interactúa con agua y debe mantenerse siempre como prototipo educativo de baja tensión. No lo coloques cerca de enchufes expuestos ni de cableado de 110/220 V, alimenta el ESP32 DevKitC desde USB o una batería aislada, mantén sus GPIO a 3,3 V y desconecta siempre la alimentación antes de manipular sensores, cambiar cables o reubicar el dispositivo.
Requisitos previos
Para completar con éxito este tutorial, necesitarás:
* Software: Visual Studio Code con la extensión PlatformIO IDE instalada.
* Conocimientos: Comprensión de la sintaxis de C++, lógica de hardware (estados HIGH/LOW) y manejo básico de redes locales.
* Sistema: Un ordenador con un puerto USB disponible y los controladores USB a UART adecuados instalados (CP210x o CH34x, dependiendo de tu variante específica de ESP32 DevKitC).
Materiales
Necesitarás los siguientes componentes de hardware. Asegúrate de estar utilizando el modelo de dispositivo exacto especificado.
| Componente | Descripción / Especificación | Cantidad |
|---|---|---|
| ESP32 DevKitC | Placa de desarrollo estándar ESP32 de 38 o 30 pines. | 1 |
| Sensor de fuga de agua | Módulo sensor de agua (módulo comparador con sondas de trazas expuestas). Usaremos el pin de salida digital (DO). | 1 |
| Zumbador activo | Módulo de zumbador activo de 3.3V (suena continuamente cuando se le proporciona una señal HIGH). | 1 |
| LED de estado | LED estándar de 5 mm (rojo para alertas críticas). | 1 |
| Resistencia | Resistencia de 220 ohmios para limitar la corriente del LED. | 1 |
| Protoboard | Placa de pruebas estándar para la creación de prototipos. | 1 |
| Cables puente | Cables Dupont variados. | 1 juego |
| Cable Micro-USB | Cable USB con capacidad de datos. | 1 |
Configuración/Conexión
Cablea cuidadosamente los componentes en tu protoboard de acuerdo con la siguiente tabla. Asegúrate de que el ESP32 esté desconectado de tu ordenador mientras realizas las conexiones.
Mapeo de pines
| Pin ESP32 DevKitC | Componente | Pin del componente | Notas |
|---|---|---|---|
| 3V3 | Sensor de fuga de agua | VCC | Alimenta la lógica del sensor. |
| GND | Sensor de fuga de agua | GND | Tierra común. |
| GPIO 32 | Sensor de fuga de agua | DO (Salida digital) | Cambia a HIGH cuando se detecta agua. |
| 3V3 | Zumbador activo | VCC | Alimenta el zumbador. |
| GND | Zumbador activo | GND | Tierra común. |
| GPIO 26 | Zumbador activo | I/O (Señal) | Activa el zumbador continuamente cuando es HIGH. |
| GPIO 27 | LED de estado | Ánodo (Pata larga) | Conectar a través de la resistencia de 220 ohmios. |
| GND | LED de estado | Cátodo (Pata corta) | Tierra común. |
Nota de hardware: Los zumbadores activos generan su propio tono continuo cuando se aplica una señal lógica HIGH. Esto es ideal para alarmas industriales simples y evita la necesidad de generar señales PWM por software.
Código validado
Crea un nuevo proyecto en PlatformIO, seleccionando el Espressif ESP32 Dev Module como tu placa y Arduino como el framework. Reemplaza el contenido de los archivos generados automáticamente con el código a continuación. El código utiliza el modo SoftAP para evitar depender de redes WiFi externas, garantizando que el código sea 100% funcional y reproducible sin modificaciones.
platformio.ini
Este archivo configura el entorno de compilación de PlatformIO, especificando la placa, el framework y la velocidad del monitor serie.
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
src/main.cpp
Este es el código principal de la aplicación. Implementa la lógica de control de hardware en tiempo real y el servidor web asíncrono sobre un Punto de Acceso dedicado.
Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// ---------------------------------------------------------
// SoftAP Network Credentials
// ---------------------------------------------------------
const char* ssid = "ESP32_Water_Alert";
const char* password = "wateradmin";
// ---------------------------------------------------------
// Pin Definitions
// ---------------------------------------------------------
const int SENSOR_PIN = 32; // Digital input from water sensor
const int BUZZER_PIN = 26; // Output to active buzzer
const int LED_PIN = 27; // Output to status LED
// ---------------------------------------------------------
// Global State Variables
// ---------------------------------------------------------
bool isLeakDetected = false;
// ---------------------------------------------------------
// Web Server Initialization (Port 80)
// ---------------------------------------------------------
WebServer server(80);
// Function to generate the HTML dashboard
void handleRoot() {
String html = "<!DOCTYPE html><html><head>";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
html += "<meta http-equiv=\"refresh\" content=\"2\">";
html += "<title>Water Leak Monitor</title>";
html += "<style>";
html += "body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; background-color: #f4f4f9; }";
html += "h1 { color: #333; }";
html += ".status-box { display: inline-block; padding: 40px; border-radius: 10px; font-size: 24px; font-weight: bold; color: white; }";
html += ".dry { background-color: #4CAF50; }"; // Green for safe
html += ".leak { background-color: #F44336; }"; // Solid Red for danger
html += "</style></head><body>";
html += "<h1>Industrial Water Leak Monitor</h1>";
if (isLeakDetected) {
html += "<div class=\"status-box leak\">CRITICAL ALARM: LEAK DETECTED</div>";
html += "<p>Immediate intervention required.</p>";
} else {
html += "<div class=\"status-box dry\">SYSTEM STATUS: DRY</div>";
html += "<p>No water detected. Monitoring active.</p>";
}
html += "</body></html>";
server.send(200, "text/html", html);
}
// Function to provide a JSON API endpoint for system integration
void handleStatusJSON() {
String json = "{";
json += "\"leak_detected\": " + String(isLeakDetected ? "true" : "false") + ",";
json += "\"sensor_pin\": " + String(SENSOR_PIN) + ",";
json += "\"device_mode\": \"SoftAP\"";
json += "}";
server.send(200, "application/json", json);
}
// ...
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// ---------------------------------------------------------
// SoftAP Network Credentials
// ---------------------------------------------------------
const char* ssid = "ESP32_Water_Alert";
const char* password = "wateradmin";
// ---------------------------------------------------------
// Pin Definitions
// ---------------------------------------------------------
const int SENSOR_PIN = 32; // Digital input from water sensor
const int BUZZER_PIN = 26; // Output to active buzzer
const int LED_PIN = 27; // Output to status LED
// ---------------------------------------------------------
// Global State Variables
// ---------------------------------------------------------
bool isLeakDetected = false;
// ---------------------------------------------------------
// Web Server Initialization (Port 80)
// ---------------------------------------------------------
WebServer server(80);
// Function to generate the HTML dashboard
void handleRoot() {
String html = "<!DOCTYPE html><html><head>";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
html += "<meta http-equiv=\"refresh\" content=\"2\">";
html += "<title>Water Leak Monitor</title>";
html += "<style>";
html += "body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; background-color: #f4f4f9; }";
html += "h1 { color: #333; }";
html += ".status-box { display: inline-block; padding: 40px; border-radius: 10px; font-size: 24px; font-weight: bold; color: white; }";
html += ".dry { background-color: #4CAF50; }"; // Green for safe
html += ".leak { background-color: #F44336; }"; // Solid Red for danger
html += "</style></head><body>";
html += "<h1>Industrial Water Leak Monitor</h1>";
if (isLeakDetected) {
html += "<div class=\"status-box leak\">CRITICAL ALARM: LEAK DETECTED</div>";
html += "<p>Immediate intervention required.</p>";
} else {
html += "<div class=\"status-box dry\">SYSTEM STATUS: DRY</div>";
html += "<p>No water detected. Monitoring active.</p>";
}
html += "</body></html>";
server.send(200, "text/html", html);
}
// Function to provide a JSON API endpoint for system integration
void handleStatusJSON() {
String json = "{";
json += "\"leak_detected\": " + String(isLeakDetected ? "true" : "false") + ",";
json += "\"sensor_pin\": " + String(SENSOR_PIN) + ",";
json += "\"device_mode\": \"SoftAP\"";
json += "}";
server.send(200, "application/json", json);
}
// ---------------------------------------------------------
// Setup Function
// ---------------------------------------------------------
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
delay(1000);
Serial.println("\nInitializing Water Leak Monitor...");
// Configure GPIO Pins
pinMode(SENSOR_PIN, INPUT_PULLDOWN); // Ensure pin doesn't float
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
// Ensure alarm is off at boot
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
// Initialize WiFi SoftAP
Serial.print("Configuring Access Point: ");
Serial.println(ssid);
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP Address: ");
Serial.println(IP);
// Define Web Server Routes
server.on("/", handleRoot);
server.on("/status", handleStatusJSON);
// Start the Web Server
server.begin();
Serial.println("HTTP server started.");
}
// ---------------------------------------------------------
// Main Loop
// ---------------------------------------------------------
void loop() {
// 1. Handle incoming HTTP client requests
server.handleClient();
// 2. Read the water sensor
int sensorValue = digitalRead(SENSOR_PIN);
// 3. Evaluate state and trigger hardware alarm
if (sensorValue == HIGH) {
if (!isLeakDetected) {
Serial.println("CRITICAL: Water Leak Detected!");
isLeakDetected = true;
// Solid alarm activation
digitalWrite(LED_PIN, HIGH);
digitalWrite(BUZZER_PIN, HIGH);
}
} else {
if (isLeakDetected) {
Serial.println("INFO: Sensor is dry. System normalized.");
isLeakDetected = false;
// Deactivate alarm
digitalWrite(LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
}
}
}
Comandos de compilación/flasheo/ejecución
Usa la CLI principal de PlatformIO (o la terminal integrada en VS Code) para ejecutar los siguientes comandos.
| Comando | Propósito |
|---|---|
pio run |
Compila el proyecto para asegurar que no haya errores de sintaxis en el entorno. |
pio run --target upload |
Compila y flashea el firmware al ESP32 a través del puerto USB. |
pio device monitor |
Abre el monitor serie para ver las salidas de registro y confirmar la inicialización del SoftAP. |
Flujo de trabajo:
1. Abre la terminal de PlatformIO.
2. Ejecuta pio run para verificar que tu código se compile correctamente.
3. Conecta tu ESP32 DevKitC a través de USB.
4. Ejecuta pio run --target upload. (Si la carga no logra conectarse, mantén presionado el botón «BOOT» en el ESP32 mientras la terminal dice «Connecting…»).
5. Ejecuta pio device monitor para observar la secuencia de arranque y confirmar que la dirección IP del AP es 192.168.4.1 (predeterminada de ESP32).
Validación paso a paso
Sigue estos puntos de control para verificar que el sistema industrial funciona exactamente como está previsto.
- Comprobación de compilación
- Acción: Ejecuta
pio run. - Observación esperada: La terminal muestra
SUCCESSsin errores de compilación. - Condición de aprobación: El binario del firmware se genera con éxito.
- Comprobación del Punto de Acceso (SoftAP)
- Acción: Ejecuta
pio device monitor. Busca redes WiFi desde tu ordenador o teléfono. - Observación esperada: El monitor serie imprime «AP IP Address: 192.168.4.1». La red
ESP32_Water_Alertaparece en tu lista de WiFi. - Condición de aprobación: El ESP32 está transmitiendo su propia red de forma autónoma.
- Verificación en estado seco
- Acción: Conéctate a la red
ESP32_Water_Alert(contraseña:wateradmin). Abrehttp://192.168.4.1en tu navegador. - Observación esperada: El LED está apagado, el zumbador está en silencio. La página web muestra un cuadro verde con el texto «SYSTEM STATUS: DRY».
- Condición de aprobación: El sistema permanece estable y el servidor web responde correctamente.
- Verificación en estado de alarma (Fuga)
- Acción: Sumerge las sondas del sensor en agua o puentea los contactos metálicos con un cable.
- Observación esperada: El monitor serie imprime «CRITICAL: Water Leak Detected!». El LED rojo y el zumbador se encienden de forma continua y sólida al instante.
- Condición de aprobación: La alarma de hardware se activa de forma inmediata e ininterrumpida.
- Verificación de latencia de red y API
- Acción: Mientras el sensor está mojado, recarga la página web o visita
http://192.168.4.1/status. Abre las herramientas de desarrollo del navegador (F12) en la pestaña de Red. - Observación esperada: La página principal muestra un bloque rojo «CRITICAL ALARM: LEAK DETECTED». El endpoint
/statusdevuelve{"leak_detected": true, "sensor_pin": 32, "device_mode": "SoftAP"}. El TTFB en la pestaña de red es inferior a 500ms. - Condición de aprobación: La API JSON refleja el estado físico en tiempo real cumpliendo con el objetivo de latencia.
Solución de problemas
| Síntoma | Causa probable | Solución |
|---|---|---|
| El código falla al cargarse (Tiempo de espera) | El ESP32 no entra en modo bootloader automáticamente. | Mantén presionado el botón «BOOT» en el DevKitC cuando la consola muestre «Connecting…». Suéltalo una vez que comience la carga. |
| La red WiFi no aparece | Interferencia de energía o inicialización fallida. | Presiona el botón EN (Reset) en el ESP32. Asegúrate de que el cable USB proporcione suficiente corriente (mínimo 500mA). |
| El sensor se activa cuando está seco (Falsos positivos) | Pin de entrada flotante o humedad ambiental extrema. | Verifica que la resistencia pull-down interna esté actuando, o añade una resistencia externa de 10k ohmios entre el GPIO 32 y GND. |
| El zumbador hace un sonido de clic, no un tono continuo | Estás utilizando un zumbador pasivo en lugar de un zumbador activo. | Reemplázalo con un zumbador activo de 3.3V, o reescribe la lógica para usar la biblioteca ledc del ESP32 para generar una señal PWM. |
| La página web tarda en cargar o agota el tiempo de espera | Desconexión de la red SoftAP. | Algunos sistemas operativos se desconectan de redes sin internet. Asegúrate de que tu dispositivo esté configurado para «mantener la conexión» a ESP32_Water_Alert. |
Mejoras
Una vez implementado el dispositivo base, considera estas mejoras para un entorno de producción:
- Integración de interrupciones de hardware (ISR):
- Reemplaza el sondeo continuo (
digitalReaden elloop) con una interrupción de hardware (attachInterrupt) para garantizar que la detección ocurra estrictamente a nivel de hardware independientemente de la carga del servidor web.
- Reemplaza el sondeo continuo (
- Gestión de energía industrial:
- Alimenta el sistema a través del pin VIN utilizando un convertidor buck industrial de 24V a 5V, permitiendo su integración directa en armarios de control de automatización (PLCs).
- Protección ambiental:
- Aplica un revestimiento conformado (conformal coating) de silicona a la placa ESP32 y aloja el sistema en una caja con clasificación IP65, dejando únicamente las sondas del sensor expuestas al entorno.
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.
Quiz rápido
Caso práctico: Control web de garaje con ESP32

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 esp32dev
Compilar firmware
pio run
Subir al ESP32
pio run --target upload
Abrir 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+).
Quiz rápido
Caso práctico: Monitor de congelador Wi-Fi con ESP32

case-device-block-diagram, Qué construirás: Un sistema de monitorización de temperatura de congeladores con Wi-Fi que lee un termistor NTC analógico, activa una alarma local sonora y visual cuando las temperaturas superan un umbral seguro (por ejemplo, > -10°C) y sirve datos en tiempo real a un panel web local con una latencia de <1s. Público: Aficionados al IoT, estudiantes de electrónica e ingenieros de prototipos; Nivel: Intermedio Arquitectura/flujo: Termistor NTC → ADC del microcontrolador → Lógica de umbral (Zumbador/LED GPIO) → Punto de acceso/Estación Wi-Fi → Interfaz de usuario web local. Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema. Flujo conceptual: detección de humedad, decisión local y aviso al usuario. 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 bajo voltaje, no un sistema certificado de alarma alimentaria. Verifica el pinout de tu ESP32 DevKitC y del sensor de temperatura, mantén todos los GPIO dentro de sus límites de 3,3 V y no conectes el circuito a la red eléctrica ni a la alimentación interna del congelador. Usa una fuente USB fiable, protege el cableado de la humedad y desconecta la alimentación antes de manipular el montaje. Antes de comenzar este tutorial, asegúrese de tener listo lo siguiente: Para garantizar que el código y el cableado se comporten exactamente como se describe, utilice los componentes precisos enumerados a continuación: La configuración del hardware requiere cablear cuidadosamente el termistor NTC en una configuración de divisor de voltaje, junto con los periféricos de salida. Nota de ingeniería importante sobre el ADC: Usamos GPIO34 para el termistor. GPIO34 pertenece al bloque El proyecto se basa en dos archivos dentro del entorno PlatformIO. Cree un nuevo proyecto en PlatformIO seleccionando la placa Este archivo de configuración define el objetivo de hardware, el framework y la velocidad de baudios del monitor serie. Este es el código fuente principal. Asegúrese de actualizar las variables Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print. 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
* Software: Visual Studio Code con la extensión PlatformIO IDE instalada.
* Conocimientos: Comprensión básica de programación en C++, divisores de voltaje y conceptos fundamentales de redes Wi-Fi.
* Red: Acceso a una red Wi-Fi estándar de 2.4 GHz (los microcontroladores ESP32 no soportan redes de 5 GHz).
Materiales
Componente
Especificación / Modelo exacto
Cantidad
Microcontrolador
ESP32 DevKitC (variante de 38 pines o 30 pines)
1
Sensor de temperatura
Termistor NTC de 10 kOhm (valor B ~3950)
1
Resistencia fija
10 kOhm (1/4 Watt, para el divisor de voltaje)
1
Resistencia limitadora de corriente
220 Ohm (1/4 Watt, para el LED)
1
Indicador visual
LED de estado (Estándar de 5mm, preferiblemente rojo)
1
Indicador sonoro
Zumbador activo (compatible con 3.3V o 5V)
1
Prototipado
Protoboard y cables puente surtidos
1 juego
Energía/Datos
Cable de datos Micro-USB
1
Configuración/Conexión
ADC1 del ESP32. No utilice pines del ADC2 (como GPIO4 o GPIO2) porque el ADC2 es utilizado por el controlador Wi-Fi y no podrá leer voltajes analógicos mientras la radio Wi-Fi esté activa.1. Divisor de voltaje del termistor NTC
2. Zumbador activo
+ o la pata más larga) del zumbador activo al GPIO26.3. LED de estado
4. Configuración del controlador USB
Código validado
esp32dev y el framework Arduino. Reemplace el contenido de los archivos generados con el código a continuación.platformio.ini[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
src/main.cppWIFI_SSID y WIFI_PASSWORD para que coincidan con las credenciales de su red local antes de compilar.#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// ------------------------------------------------------------------
// Network Credentials
// ------------------------------------------------------------------
const char* WIFI_SSID = "LabNetwork";
const char* WIFI_PASSWORD = "LabPassword123";
// ------------------------------------------------------------------
// Pin Definitions
// ------------------------------------------------------------------
const int NTC_PIN = 34; // ADC1_CH6 - Safe to use with Wi-Fi
const int BUZZER_PIN = 26; // Output for active buzzer
const int LED_PIN = 27; // Output for status LED
// ------------------------------------------------------------------
// Thermistor & Steinhart-Hart Parameters
// ------------------------------------------------------------------
const float SERIES_RESISTOR = 10000.0; // 10k Ohm fixed resistor
const float NOMINAL_RESISTANCE = 10000.0; // 10k Ohm NTC at 25 degrees C
const float NOMINAL_TEMPERATURE = 25.0; // Nominal temperature in Celsius
const float B_COEFFICIENT = 3950.0; // Beta value of the thermistor
const float ALARM_THRESHOLD = -10.0; // Alarm triggers if temp rises above -10.0 C
// ------------------------------------------------------------------
// Global Variables
// ------------------------------------------------------------------
WebServer server(80);
float currentTemperature = 0.0;
bool isAlarmActive = false;
// Timing variables for non-blocking alarm
unsigned long previousMillis = 0;
const long blinkInterval = 500; // 500ms toggle rate for buzzer/LED
bool toggleState = false;
// ------------------------------------------------------------------
// Function Prototypes
// ------------------------------------------------------------------
void connectWiFi();
void handleRoot();
float readTemperature();
void handleAlarmLogic();
// ------------------------------------------------------------------
// Setup
// ------------------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(1000); // Allow serial monitor to stabilize
// Initialize output pins
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
// Initialize ADC
analogReadResolution(12); // 12-bit ADC (0 - 4095)
Serial.println("\n--- Freezer Temperature Alarm System ---");
connectWiFi();
// Setup Web Server Routes
server.on("/", handleRoot);
server.begin();
Serial.println("Web server started.");
}
// ------------------------------------------------------------------
// Main Loop
// ------------------------------------------------------------------
void loop() {
server.handleClient(); // Listen for incoming HTTP requests
// Read temperature every cycle
currentTemperature = readTemperature();
// Evaluate and execute alarm logic
handleAlarmLogic();
// Small delay to stabilize ADC reads and prevent watchdog resets
delay(50);
}
// ...#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// ------------------------------------------------------------------
// Network Credentials
// ------------------------------------------------------------------
const char* WIFI_SSID = "LabNetwork";
const char* WIFI_PASSWORD = "LabPassword123";
// ------------------------------------------------------------------
// Pin Definitions
// ------------------------------------------------------------------
const int NTC_PIN = 34; // ADC1_CH6 - Safe to use with Wi-Fi
const int BUZZER_PIN = 26; // Output for active buzzer
const int LED_PIN = 27; // Output for status LED
// ------------------------------------------------------------------
// Thermistor & Steinhart-Hart Parameters
// ------------------------------------------------------------------
const float SERIES_RESISTOR = 10000.0; // 10k Ohm fixed resistor
const float NOMINAL_RESISTANCE = 10000.0; // 10k Ohm NTC at 25 degrees C
const float NOMINAL_TEMPERATURE = 25.0; // Nominal temperature in Celsius
const float B_COEFFICIENT = 3950.0; // Beta value of the thermistor
const float ALARM_THRESHOLD = -10.0; // Alarm triggers if temp rises above -10.0 C
// ------------------------------------------------------------------
// Global Variables
// ------------------------------------------------------------------
WebServer server(80);
float currentTemperature = 0.0;
bool isAlarmActive = false;
// Timing variables for non-blocking alarm
unsigned long previousMillis = 0;
const long blinkInterval = 500; // 500ms toggle rate for buzzer/LED
bool toggleState = false;
// ------------------------------------------------------------------
// Function Prototypes
// ------------------------------------------------------------------
void connectWiFi();
void handleRoot();
float readTemperature();
void handleAlarmLogic();
// ------------------------------------------------------------------
// Setup
// ------------------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(1000); // Allow serial monitor to stabilize
// Initialize output pins
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
// Initialize ADC
analogReadResolution(12); // 12-bit ADC (0 - 4095)
Serial.println("\n--- Freezer Temperature Alarm System ---");
connectWiFi();
// Setup Web Server Routes
server.on("/", handleRoot);
server.begin();
Serial.println("Web server started.");
}
// ------------------------------------------------------------------
// Main Loop
// ------------------------------------------------------------------
void loop() {
server.handleClient(); // Listen for incoming HTTP requests
// Read temperature every cycle
currentTemperature = readTemperature();
// Evaluate and execute alarm logic
handleAlarmLogic();
// Small delay to stabilize ADC reads and prevent watchdog resets
delay(50);
}
// ------------------------------------------------------------------
// Functions
// ------------------------------------------------------------------
void connectWiFi() {
Serial.print("Connecting to Wi-Fi: ");
Serial.println(WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi Connected!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
}
float readTemperature() {
// Read the analog value (0-4095)
int adcValue = analogRead(NTC_PIN);
// Prevent division by zero if pin is shorted to Ground or 3.3V
if (adcValue == 0) return -99.0;
if (adcValue >= 4095) return 99.0;
// Calculate NTC Resistance
// Based on Voltage Divider: Vout = Vcc * (R_NTC / (R_NTC + R_SERIES))
// Derives to: R_NTC = R_SERIES * (ADC / (4095 - ADC))
float ntcResistance = SERIES_RESISTOR * ((float)adcValue / (4095.0 - (float)adcValue));
// Apply Steinhart-Hart equation
float steinhart;
steinhart = ntcResistance / NOMINAL_RESISTANCE; // (R/Ro)
steinhart = log(steinhart); // ln(R/Ro)
steinhart /= B_COEFFICIENT; // 1/B * ln(R/Ro)
steinhart += 1.0 / (NOMINAL_TEMPERATURE + 273.15); // + (1/To)
steinhart = 1.0 / steinhart; // Invert
steinhart -= 273.15; // Convert Kelvin to Celsius
return steinhart;
}
void handleAlarmLogic() {
if (currentTemperature > ALARM_THRESHOLD) {
isAlarmActive = true;
// Non-blocking toggle for LED and Buzzer
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= blinkInterval) {
previousMillis = currentMillis;
toggleState = !toggleState;
digitalWrite(LED_PIN, toggleState ? HIGH : LOW);
digitalWrite(BUZZER_PIN, toggleState ? HIGH : LOW);
}
} else {
isAlarmActive = false;
// Ensure outputs are turned off when temperature is safe
digitalWrite(LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
toggleState = false;
}
}
void handleRoot() {
// Construct a simple, auto-refreshing HTML dashboard
String html = "<!DOCTYPE html><html><head>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
html += "<meta http-equiv='refresh' content='5'>"; // Auto-refresh every 5 seconds
html += "<style>";
html += "body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }";
html += ".temp { font-size: 3em; font-weight: bold; }";
html += ".status-ok { color: green; font-size: 2em; }";
html += ".status-alarm { color: red; font-size: 2em; font-weight: bold; animation: blinker 1s linear infinite; }";
html += "@keyframes blinker { 50% { opacity: 0; } }";
html += "</style></head><body>";
html += "<h1>Freezer Monitor Dashboard</h1>";
html += "<div class='temp'>" + String(currentTemperature, 1) + " °C</div>";
if (isAlarmActive) {
html += "<div class='status-alarm'>WARNING: TEMPERATURE HIGH</div>";
} else {
html += "<div class='status-ok'>STATUS: NORMAL</div>";
}
html += "</body></html>";
// Send the response to the client
server.send(200, "text/html", html);
}
Quiz rápido
Caso práctico: Panel de acceso seguro con ESP32

Objetivo y caso de uso
Qué construirás: Un prototipo funcional de un panel de acceso seguro utilizando la detección táctil capacitiva integrada del ESP32, indicadores visuales LED y retroalimentación acústica por zumbador.
Por qué es importante / Casos de uso
- Interfaces sin desgaste: Elimina la degradación mecánica, haciéndolo ideal para paneles de acceso de alto tráfico, salas blancas o teclados de exteriores expuestos a los elementos.
- Automatización segura de edificios: Demuestra la lógica fundamental de validación de secuencias y gestión de estados requerida en los sistemas de seguridad comercial de primera línea.
- Retroalimentación de usuario integrada: Combina señales visuales (LED) y acústicas (zumbador) para una HMI robusta, garantizando que los usuarios sepan que la entrada fue registrada con una latencia de respuesta inferior a 50 ms.
- Máquinas de estado no bloqueantes: Gestiona la entrada humana asíncrona sin detener el microcontrolador, manteniendo una capacidad de respuesta constante del sistema.
Resultado esperado
- Detección táctil confiable y eliminación de rebotes (debouncing) por software utilizando el hardware capacitivo interno del ESP32.
- Una máquina de estados no bloqueante capaz de procesar entradas secuenciales y rechazar códigos inválidos al instante.
- Actuación GPIO sincronizada y de baja latencia para controlar la retroalimentación del LED y el zumbador en función del estado de acceso.
Audiencia: Ingenieros de sistemas embebidos, desarrolladores de IoT; Nivel: Intermedio
Arquitectura/flujo: Pines táctiles capacitivos del ESP32 → Filtro de eliminación de rebotes por software → Validador de secuencias no bloqueante → Actuación GPIO (LED/Zumbador)
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: 3 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, verifique la asignación de pines de su revisión exacta de la placa ULX3S, mantenga las señales de E/S de la FPGA a 3.3 V, nunca conecte 5 V directamente a los pines de E/S, desconecte la alimentación antes de cambiar el cableado y use fuentes externas adecuadas para cargas, motores o servos mientras comparte la tierra 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 este tutorial con éxito, necesitará:
* Comprensión básica de la programación en C++ (variables, arreglos, lógica condicional y funciones).
* Visual Studio Code instalado con la extensión PlatformIO IDE.
* Familiaridad con la creación de prototipos en protoboard y componentes electrónicos básicos.
* Un cable micro-USB o USB-C (dependiendo de su variante específica de ESP32 DevKitC) capaz de transferir tanto energía como datos.
Materiales
Debe utilizar los componentes exactos enumerados a continuación para garantizar que el código y las instrucciones de cableado proporcionados funcionen sin modificaciones:
* Microcontrolador: ESP32 DevKitC (versión estándar de 38 o 30 pines).
* Entrada: Almohadillas táctiles capacitivas. (Puede usar módulos de almohadillas táctiles comerciales dedicados, o crear fácilmente los suyos propios usando cinta de cobre, papel de aluminio o monedas metálicas soldadas a cables puente).
* Salida (Visual): 1x LED de estado estándar de 5 mm (por ejemplo, rojo o verde) y 1x resistencia limitadora de corriente de 220Ω a 330Ω.
* Salida (Audio): 1x Zumbador piezoeléctrico (se prefiere el tipo pasivo para tonos variables, aunque un zumbador activo funcionará para pitidos simples).
* Prototipado: 1x Protoboard sin soldadura y un surtido de cables puente macho-macho.
Nota de configuración de hardware: Asegúrese de que su computadora tenga instalados los controladores de puente USB a UART adecuados (generalmente CP210x o CH34x, dependiendo del fabricante de su ESP32 DevKitC) para que PlatformIO pueda comunicarse con la placa.
Configuración/Conexión
El ESP32 cuenta con hardware de detección táctil interno dedicado en varios pines GPIO. Estos pines miden la capacitancia del circuito conectado. Cuando un dedo humano toca la almohadilla, la capacitancia cambia, lo que el ESP32 detecta como una caída en el valor analógico sin procesar.
Debido a que el ESP32 maneja la medición de capacitancia internamente, no necesita resistencias pull-up o pull-down externas para las almohadillas táctiles. Conecte los componentes de acuerdo con la siguiente tabla.
Tabla de asignación de pines
| Componente | Pin ESP32 DevKitC | Detalles y Conexiones |
|---|---|---|
| Almohadilla táctil 1 (Tecla 1) | GPIO 4 (Touch 0) | Conectar directamente a la almohadilla metálica/moneda. |
| Almohadilla táctil 2 (Tecla 2) | GPIO 2 (Touch 2) | Conectar directamente a la almohadilla metálica/moneda. |
| Almohadilla táctil 3 (Tecla 3) | GPIO 15 (Touch 3) | Conectar directamente a la almohadilla metálica/moneda. |
| Ánodo del LED de estado (+) | GPIO 21 | Conectar a través de una resistencia de 220Ω al GPIO 21. |
| Cátodo del LED de estado (-) | GND | Conectar directamente al pin de Tierra (GND) del ESP32. |
| Zumbador piezoeléctrico (+) | GPIO 22 | Conectar al GPIO 22. |
| Zumbador piezoeléctrico (-) | GND | Conectar al pin de Tierra (GND) del ESP32. |
Construcción de las almohadillas táctiles: Si no tiene almohadillas táctiles comerciales, corte tres cuadrados idénticos de cinta de cobre o use tres monedas idénticas. Suelde o pegue firmemente con cinta un cable puente a cada una. Sepárelas al menos 2 centímetros en su escritorio o protoboard para evitar la capacitancia cruzada (donde tocar una almohadilla activa accidentalmente una adyacente).
Código validado
Los siguientes archivos constituyen el proyecto completo y compilable. El proyecto se gestiona a través de PlatformIO.
platformio.ini
Cree o sobrescriba el archivo platformio.ini en la raíz de su directorio de proyecto con la siguiente configuración. Esto asegura que se apunte a la placa y el framework correctos.
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
src/main.cpp
Cree o sobrescriba el archivo main.cpp en su directorio src con el siguiente código. La lógica implementa una máquina de estados no bloqueante, maneja la eliminación de rebotes táctiles y gestiona la secuencia de validación de acceso.
Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.
#include <Arduino.h>
// --------------------------------------------------------
// Pin Definitions
// --------------------------------------------------------
const int TOUCH_PAD_1 = 4; // GPIO 4 (Touch 0)
const int TOUCH_PAD_2 = 2; // GPIO 2 (Touch 2)
const int TOUCH_PAD_3 = 15; // GPIO 15 (Touch 3)
const int LED_PIN = 21; // Status LED
const int BUZZER_PIN = 22; // Piezo Buzzer
// --------------------------------------------------------
// System Configuration & Thresholds
// --------------------------------------------------------
// A typical untouched ESP32 pin reads ~50-80.
// A touched pin drops below 20. Adjust this if your pads differ.
const int TOUCH_THRESHOLD = 30;
// Access Control Sequence Configuration
const int SEQUENCE_LENGTH = 4;
const int SECRET_PIN[SEQUENCE_LENGTH] = {1, 2, 3, 2}; // The correct access code
int inputSequence[SEQUENCE_LENGTH];
int inputIndex = 0;
// State Machine Variables
enum SystemState { LOCKED, INPUTTING, UNLOCKED };
SystemState currentState = LOCKED;
unsigned long unlockTimestamp = 0;
const unsigned long UNLOCK_DURATION = 5000; // Keep unlocked for 5 seconds
// Debouncing Variables
bool pad1_wasTouched = false;
bool pad2_wasTouched = false;
bool pad3_wasTouched = false;
// --------------------------------------------------------
// Function Prototypes
// --------------------------------------------------------
void processTouch();
void handleKeyPress(int keyNumber);
void evaluateSequence();
void triggerSuccess();
void triggerFailure();
void lockSystem();
void playTone(int frequency, int duration);
// --------------------------------------------------------
// Setup
// --------------------------------------------------------
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); } // Wait for serial connection
Serial.println("\n--- Capacitive Touch Access Panel Initialized ---");
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
lockSystem(); // Ensure system starts in locked state
}
// --------------------------------------------------------
// Main Loop
// --------------------------------------------------------
void loop() {
// Handle state timeouts (Auto-lock)
if (currentState == UNLOCKED) {
if (millis() - unlockTimestamp >= UNLOCK_DURATION) {
Serial.println("Auto-locking system due to timeout.");
lockSystem();
}
} else {
// Only process touch inputs if the system is not currently unlocked
processTouch();
}
// Small delay to yield to the underlying RTOS
delay(10);
}
// --------------------------------------------------------
// Touch Processing & Debouncing
// --------------------------------------------------------
void processTouch() {
// Read raw capacitance values
int val1 = touchRead(TOUCH_PAD_1);
int val2 = touchRead(TOUCH_PAD_2);
int val3 = touchRead(TOUCH_PAD_3);
// Evaluate Pad 1
bool pad1_isTouched = (val1 < TOUCH_THRESHOLD);
if (pad1_isTouched && !pad1_wasTouched) {
handleKeyPress(1);
}
pad1_wasTouched = pad1_isTouched;
// Evaluate Pad 2
bool pad2_isTouched = (val2 < TOUCH_THRESHOLD);
if (pad2_isTouched && !pad2_wasTouched) {
handleKeyPress(2);
}
// ...
#include <Arduino.h>
// --------------------------------------------------------
// Pin Definitions
// --------------------------------------------------------
const int TOUCH_PAD_1 = 4; // GPIO 4 (Touch 0)
const int TOUCH_PAD_2 = 2; // GPIO 2 (Touch 2)
const int TOUCH_PAD_3 = 15; // GPIO 15 (Touch 3)
const int LED_PIN = 21; // Status LED
const int BUZZER_PIN = 22; // Piezo Buzzer
// --------------------------------------------------------
// System Configuration & Thresholds
// --------------------------------------------------------
// A typical untouched ESP32 pin reads ~50-80.
// A touched pin drops below 20. Adjust this if your pads differ.
const int TOUCH_THRESHOLD = 30;
// Access Control Sequence Configuration
const int SEQUENCE_LENGTH = 4;
const int SECRET_PIN[SEQUENCE_LENGTH] = {1, 2, 3, 2}; // The correct access code
int inputSequence[SEQUENCE_LENGTH];
int inputIndex = 0;
// State Machine Variables
enum SystemState { LOCKED, INPUTTING, UNLOCKED };
SystemState currentState = LOCKED;
unsigned long unlockTimestamp = 0;
const unsigned long UNLOCK_DURATION = 5000; // Keep unlocked for 5 seconds
// Debouncing Variables
bool pad1_wasTouched = false;
bool pad2_wasTouched = false;
bool pad3_wasTouched = false;
// --------------------------------------------------------
// Function Prototypes
// --------------------------------------------------------
void processTouch();
void handleKeyPress(int keyNumber);
void evaluateSequence();
void triggerSuccess();
void triggerFailure();
void lockSystem();
void playTone(int frequency, int duration);
// --------------------------------------------------------
// Setup
// --------------------------------------------------------
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); } // Wait for serial connection
Serial.println("\n--- Capacitive Touch Access Panel Initialized ---");
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
lockSystem(); // Ensure system starts in locked state
}
// --------------------------------------------------------
// Main Loop
// --------------------------------------------------------
void loop() {
// Handle state timeouts (Auto-lock)
if (currentState == UNLOCKED) {
if (millis() - unlockTimestamp >= UNLOCK_DURATION) {
Serial.println("Auto-locking system due to timeout.");
lockSystem();
}
} else {
// Only process touch inputs if the system is not currently unlocked
processTouch();
}
// Small delay to yield to the underlying RTOS
delay(10);
}
// --------------------------------------------------------
// Touch Processing & Debouncing
// --------------------------------------------------------
void processTouch() {
// Read raw capacitance values
int val1 = touchRead(TOUCH_PAD_1);
int val2 = touchRead(TOUCH_PAD_2);
int val3 = touchRead(TOUCH_PAD_3);
// Evaluate Pad 1
bool pad1_isTouched = (val1 < TOUCH_THRESHOLD);
if (pad1_isTouched && !pad1_wasTouched) {
handleKeyPress(1);
}
pad1_wasTouched = pad1_isTouched;
// Evaluate Pad 2
bool pad2_isTouched = (val2 < TOUCH_THRESHOLD);
if (pad2_isTouched && !pad2_wasTouched) {
handleKeyPress(2);
}
pad2_wasTouched = pad2_isTouched;
// Evaluate Pad 3
bool pad3_isTouched = (val3 < TOUCH_THRESHOLD);
if (pad3_isTouched && !pad3_wasTouched) {
handleKeyPress(3);
}
pad3_wasTouched = pad3_isTouched;
}
// --------------------------------------------------------
// Logic Handling
// --------------------------------------------------------
void handleKeyPress(int keyNumber) {
// Provide immediate acoustic feedback
playTone(1000, 100);
Serial.print("Key Pressed: ");
Serial.println(keyNumber);
// Update state
currentState = INPUTTING;
// Store the input
inputSequence[inputIndex] = keyNumber;
inputIndex++;
// Check if we have collected enough inputs
if (inputIndex >= SEQUENCE_LENGTH) {
evaluateSequence();
}
}
void evaluateSequence() {
Serial.println("Evaluating entered sequence...");
bool isMatch = true;
for (int i = 0; i < SEQUENCE_LENGTH; i++) {
if (inputSequence[i] != SECRET_PIN[i]) {
isMatch = false;
break;
}
}
if (isMatch) {
triggerSuccess();
} else {
triggerFailure();
}
// Reset input index for the next attempt
inputIndex = 0;
}
// --------------------------------------------------------
// Output & Feedback Generators
// --------------------------------------------------------
void triggerSuccess() {
Serial.println("ACCESS GRANTED.");
currentState = UNLOCKED;
unlockTimestamp = millis();
// Visual indicator: LED ON
digitalWrite(LED_PIN, HIGH);
// Acoustic indicator: Success Melody
playTone(1200, 150);
delay(50);
playTone(1500, 150);
delay(50);
playTone(2000, 300);
}
void triggerFailure() {
Serial.println("ACCESS DENIED. Incorrect PIN.");
// Acoustic indicator: Error Tone
playTone(300, 400);
delay(100);
playTone(300, 400);
// Return to locked state immediately
lockSystem();
}
void lockSystem() {
currentState = LOCKED;
inputIndex = 0; // Clear any partial inputs
digitalWrite(LED_PIN, LOW); // LED OFF indicates locked
Serial.println("System LOCKED. Ready for input.");
}
// Helper function for the buzzer
void playTone(int frequency, int duration) {
tone(BUZZER_PIN, frequency, duration);
// The tone function in Arduino is non-blocking, but for this HMI
// we want the beep to complete before proceeding in feedback sequences.
delay(duration);
}
Comandos de compilación/flasheo/ejecución
Para compilar, cargar y monitorear el proyecto, abra la terminal en Visual Studio Code (Terminal -> Nueva terminal) y asegúrese de estar en el directorio raíz de su proyecto (donde se encuentra platformio.ini).
Use los siguientes comandos:
| Comando | Acción |
|---|---|
pio run |
Compila el código fuente en C++ y verifica si hay errores de sintaxis/enlace. |
pio run --target upload |
Compila y flashea el firmware compilado en el ESP32 DevKitC. |
pio device monitor |
Abre el monitor serie para ver los registros en tiempo real del ESP32. |
Flujo de trabajo numerado:
1. Conecte el ESP32 DevKitC a su computadora a través de USB.
2. Ejecute pio run para verificar que el código se compila limpiamente.
3. Ejecute pio run --target upload para flashear la placa. (Nota: En algunos modelos de ESP32 DevKitC, es posible que deba mantener presionado el botón «BOOT» en la placa cuando la terminal muestre «Connecting…» para permitir que comience el proceso de flasheo).
4. Ejecute pio device monitor para interactuar con el dispositivo y ver la salida serie.
Validación paso a paso
Realice las siguientes comprobaciones físicas mientras observa el monitor serie para validar la funcionalidad del prototipo.
- Punto de control 1: Inicialización base
- Acción: Reinicie el ESP32 (presione el botón EN) mientras observa el monitor serie.
- Observación esperada: El monitor serie imprime «— Capacitive Touch Access Panel Initialized —» seguido de «System LOCKED. Ready for input.». El LED de estado debe permanecer apagado.
- Condición de aprobación: Secuencia de arranque limpia sin bucles de reinicio ni bloqueos.
- Punto de control 2: Detección de un solo toque y eliminación de rebotes
- Acción: Toque firmemente la Almohadilla táctil 1 una vez y suéltela inmediatamente.
- Observación esperada: El zumbador emite un pitido corto de 100 ms. El monitor serie registra «Key Pressed: 1».
- Condición de aprobación: Solo se registra una pulsación por toque físico. Si se registran múltiples pulsaciones, es posible que el
TOUCH_THRESHOLDnecesite ajuste.
- Punto de control 3: Rechazo de secuencia incorrecta
- Acción: Toque las almohadillas en una secuencia incorrecta (por ejemplo, Almohadilla 1, Almohadilla 1, Almohadilla 1, Almohadilla 1).
- Observación esperada: En el cuarto toque, el monitor serie registra «Evaluating entered sequence…» seguido de «ACCESS DENIED. Incorrect PIN.». El zumbador reproduce dos tonos de error bajos y largos. El LED permanece apagado.
- Condición de aprobación: El sistema identifica correctamente una discrepancia y vuelve al estado «System LOCKED».
- Punto de control 4: Autorización de secuencia correcta
- Acción: Toque las almohadillas en la secuencia correcta definida en el código (Almohadilla 1, Almohadilla 2, Almohadilla 3, Almohadilla 2).
- Observación esperada: El monitor serie registra «ACCESS GR
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.
