Caso práctico: Termostato web con ESP32

Caso práctico: Termostato web con ESP32 — hero

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é 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).

Por qué es importante / Casos de uso

  • Gestión térmica de armarios AV: Enfría automáticamente receptores de audio/vídeo cerrados solo cuando se genera un exceso de calor, minimizando el ruido acústico durante escenas de películas silenciosas.
  • Cerramientos de impresoras 3D: Mantiene una temperatura ambiente estable para la impresión de ABS/PETG ventilando el exceso de calor sin causar corrientes de aire rápidas que deformen la impresión.
  • Refrigeración de racks de servidores: Proporciona refrigeración localizada y proporcional a estantes específicos de racks de laboratorios domésticos basándose en las temperaturas de escape en tiempo real.
  • Circulación de aire en invernaderos: Activa un movimiento suave de aire sobre las plántulas cuando el sol calienta una zona localizada, previniendo la humedad y el sobrecalentamiento.

Resultado esperado

  • El ESP32 transmite su propio Punto de Acceso (AP) Wi-Fi y aloja un servidor web responsivo con una latencia sub-100ms.
  • La interfaz web muestra la temperatura en tiempo real (convertida de lecturas del ADC a Celsius) y la velocidad actual del ventilador (ciclo de trabajo PWM de 0-100%).
  • El usuario puede ajustar el umbral de temperatura objetivo en tiempo real mediante un control deslizante web.
  • El ventilador escala automáticamente sus RPM de forma proporcional basándose en el diferencial entre el umbral objetivo y la temperatura ambiente actual.

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

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

Botón local

ESP32 BLE

Paquete advertising

LED de estado

Móvil escáner

Flujo conceptual: configuración local, emisión BLE y lectura desde el móvil.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

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, 1 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 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.

Requisitos previos

  • Conocimiento básico de divisores de tensión y convertidores analógico-digitales (ADC).
  • Familiaridad con el concepto de Modulación por Ancho de Pulso (PWM) para el control de potencia.
  • Un ordenador con Visual Studio Code y la extensión PlatformIO IDE instalada.
  • Conocimiento básico de cómo conectarse a una red Wi-Fi y navegar a una dirección IP local en un navegador web.

Materiales

Para construir este prototipo, necesitas exactamente los siguientes componentes:
* 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 puente

Configuración/Conexión

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.

Componente Pin / Conexión ESP32 Destino / Descripción
Termistor NTC GND Una 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 3V3 Conecta el otro extremo de la resistencia fija de 10k al pin 3.3V del ESP32.
Puerta del MOSFET (Controlador) GPIO18 Controla la conmutación PWM. (Asegúrate de que el controlador tenga una resistencia pull-down).
Fuente del MOSFET GND Conecta 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 12V Conecta directamente al terminal positivo de 12V.
Ánodo del LED de estado GPIO21 Conecta a través de la resistencia limitadora de corriente de 220 Ohm.
Cátodo del LED de estado GND Conecta a la tierra común.

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.

Código validado

El proyecto requiere dos archivos en tu espacio de trabajo de PlatformIO. El archivo platformio.ini configura el entorno de compilación, y src/main.cpp contiene la lógica de la aplicación.

platformio.ini

Crea o sobrescribe el archivo 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

Coloca el siguiente código en 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.

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>

// --- 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> &deg;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> &deg;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> &deg;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> &deg;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

Usa los

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a 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

Pregunta 1: ¿Qué microcontrolador se utiliza para construir el prototipo de termostato?




Pregunta 2: ¿Qué técnica se utiliza para escalar dinámicamente la velocidad del ventilador de refrigeración?




Pregunta 3: ¿Cómo se puede acceder a este prototipo de termostato?




Pregunta 4: ¿Qué variable monitoriza principalmente el ESP32 en este proyecto?




Pregunta 5: ¿Cuál es una de las ventajas de usar este termostato en armarios AV (audio/vídeo)?




Pregunta 6: ¿Por qué es útil este sistema en cerramientos de impresoras 3D?




Pregunta 7: ¿Qué materiales de impresión 3D se mencionan específicamente como beneficiados por este sistema?




Pregunta 8: En el contexto de racks de servidores, ¿cómo proporciona refrigeración este sistema?




Pregunta 9: ¿Qué problema previene este termostato en la circulación de aire en invernaderos?




Pregunta 10: ¿Qué condición activa el movimiento suave de aire sobre las plántulas en el caso de uso del invernadero?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: Baliza localizadora BLE con ESP32

Caso práctico: Baliza localizadora BLE con ESP32 — hero

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

Por qué es importante / Casos de uso

  • Gestión de activos: Previene la pérdida de herramientas costosas en talleres concurridos, reduciendo los costos de reemplazo y el tiempo de búsqueda.
  • Auditoría de inventario: Permite al personal del almacén identificar instantáneamente contenedores de almacenamiento específicos entre recipientes visualmente idénticos.
  • Fundamentos de BLE: Demuestra la implementación práctica de conceptos de servidor GATT, incluyendo Servicios, Características y devoluciones de llamada (callbacks) asíncronas.
  • Control de hardware asíncrono: Gestiona alertas de hardware no bloqueantes (por ejemplo, parpadeo de LED a 2Hz y zumbadores pulsantes) mientras mantiene una conexión de RF estable y de baja latencia.

Resultado esperado

  • El ESP32 emite un anuncio BLE llamado «ToolFinder-Beacon» con un intervalo de anuncio de ~100ms.
  • Una aplicación de escáner BLE de teléfono inteligente descubre y se conecta con éxito al dispositivo.
  • Escribir un byte específico (0x01) en la característica BLE personalizada activa la alarma de hardware con una latencia <50ms.

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)

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

Botón local

ESP32 BLE

Paquete advertising

LED de estado

Móvil escáner

Flujo conceptual: configuración local, emisión BLE y lectura desde el móvil.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

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: 3 apartados, 3 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 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.

Requisitos previos

Para completar con éxito este tutorial, asegúrese de tener el siguiente software y conocimientos 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

Necesitará los componentes de hardware exactos que se enumeran a continuación para construir este prototipo:
* 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

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

Tabla resumen de conexiones:

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

El proyecto requiere dos archivos dentro de la estructura de su proyecto de PlatformIO: el archivo de configuración (platformio.ini) y el código fuente principal en C++ (src/main.cpp).

Configuración de PlatformIO

Cree un nuevo proyecto de PlatformIO para el ESP32 DevKitC y reemplace el contenido de 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

Reemplace el contenido de 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.

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

// src/main.cpp
#include <Arduino.h>
#include <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

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 platformio.ini).

Comando Propósito
pio run Compila el proyecto para asegurar que no haya errores de sintaxis o dependencias.
pio run --target upload Compila y carga el firmware construido en el ESP32 conectado a través de USB.
pio device monitor Abre el monitor serie para ver las declaraciones de impresión y los registros de depuración.

Flujo de trabajo de ejecució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

Siga estos puntos de control para verificar la funcionalidad de su baliza localizadora de herramientas BLE.

  1. Punto de control 1: Arranque e inicialización
    • Acción: Abra el monitor serie (pio device monitor) y presione el botón «EN» (Reinicio) en el ESP32.
    • Observación esperada: El terminal muestra «Starting Tool Finder Beacon…» y «BLE Advertising Started. Waiting for connections…».
    • Condición de aprobación: El ESP32 arranca sin fallar e inicia con éxito la pila de anuncios BLE.
  2. Punto de control 2: Descubrimiento BLE
    • Acción: Abra la aplicación BLE de su teléfono inteligente (por ejemplo, LightBlue) y comience a escanear dispositivos.
    • Observación esperada: Un dispositivo llamado «ToolFinder-Beacon» aparece en la lista de dispositivos disponibles.
    • Condición de aprobación: El teléfono inteligente detecta con éxito el anuncio BLE del ESP32.
  3. Punto de control 3: Establecimiento de conexión
    • Acción: Toque «Connect» (Conectar) junto a «ToolFinder-Beacon» en la aplicación.
    • Observación esperada: La aplicación se conecta, muestra el UUID del Servicio Personalizado (4fafc201...) y el monitor serie muestra «BLE Device Connected.»
    • Condición de aprobación: La conexión GATT se negocia con éxito.
  4. Punto de control 4: Activación remota de la alarma
    • Acción: En la aplicación, expanda el Servicio Personalizado, busque la Característica (beb5483e...), toque el icono «Write» (Escribir, generalmente una flecha hacia arriba), seleccione «Byte Array» o «UINT8», ingrese 01 y envíe.
    • Observación esperada: El monitor serie muestra «Alarm TRIGGERED via BLE!». El LED de estado comienza a parpadear y el zumbador piezoeléctrico pulsa un tono de 2.5 kHz.
    • Condición de aprobación: El ESP32 recibe con éxito la carga útil BLE y activa el patrón de alerta de hardware no bloqueante.
  5. Punto de control 5: Reinicio físico
    • Acción: Mientras la alarma suena activamente, presione el pulsador físico en la protoboard.
    • Observación esperada: El monitor serie muestra «Alarm STOPPED via physical button press.» El LED se apaga y el zumbador se silencia inmediatamente.
    • Condición de aprobación: La lógica local de interrupción/sondeo (polling) de GPIO anula con éxito el estado de la alarma sin requerir una desconexión de BLE.

Solución de problemas

Si encuentra problemas durante las fases de construcción o validación, consulte la siguiente tabla para ver problemas comunes y sus soluciones.

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

Una vez que el prototipo básico esté funcionando, considere las siguientes mejoras para avanzar hacia un dispositivo listo para producción:

  • Optimización de energía:
    • Implemente el modo de suspensión profunda (Deep Sleep) del ESP32. Configure el dispositivo para que se despierte periódicamente, anuncie su presencia y vuelva a dormir para ahorrar batería.
    • Transicione el hardware de un DevKitC a un módulo ESP32 independiente (bare) alimentado por una pila de botón CR2032 o una pequeña batería LiPo.
  • Características avanzadas de BLE:
    • Implemente la seguridad BLE (Emparejamiento/Vinculación) para asegurar que solo un teléfono inteligente autorizado pueda activar la alarma del localizador de herramientas.
    • Agregue un servicio BLE estándar de «Nivel de batería» (UUID 0x180F) para informar la energía restante del dispositivo al teléfono inteligente.
  • Factor de forma y carcasa:
    • Diseñe e imprima en 3D una carcasa resistente y compacta que se pueda sujetar con bridas o adherir a un taladro, llave inglesa o caja de herramientas.
    • Reemplace los componentes de la protoboard con una placa de circuito impreso (PCB) personalizada o una placa perforada (perfboard) para mayor durabilidad física.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a 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

Pregunta 1: ¿Qué tipo de dispositivo se construirá según el artículo?




Pregunta 2: ¿Cuál es uno de los beneficios principales en la 'Gestión de activos' mencionado en el texto?




Pregunta 3: ¿Qué conceptos fundamentales de BLE demuestra este proyecto?




Pregunta 4: ¿Cómo se gestionan las alertas de hardware (LED y zumbador) para no afectar la conexión de RF?




Pregunta 5: ¿Cuál es el nombre del anuncio BLE que emite el ESP32?




Pregunta 6: ¿A qué frecuencia parpadea el LED según el control de hardware asíncrono mencionado?




Pregunta 7: ¿Cómo se activa la alarma de la baliza para encontrar objetos perdidos?




Pregunta 8: ¿Qué tipo de latencia mantiene la conexión de RF durante las alertas de hardware?




Pregunta 9: ¿Qué permite hacer esta baliza al personal del almacén en la auditoría de inventario?




Pregunta 10: ¿Qué tipo de alarmas emite la baliza localizadora?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: Detector de fugas de agua con ESP32

Caso práctico: Detector de fugas de agua con ESP32 — hero

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

Sonda de agua

GPIO/ADC ESP32

Lógica de umbral

LED/buzzer

Alerta Wi-Fi

Flujo conceptual: detección de humedad, decisión local y aviso al usuario.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

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.

  1. Comprobación de compilación
  2. Acción: Ejecuta pio run.
  3. Observación esperada: La terminal muestra SUCCESS sin errores de compilación.
  4. Condición de aprobación: El binario del firmware se genera con éxito.
  5. Comprobación del Punto de Acceso (SoftAP)
  6. Acción: Ejecuta pio device monitor. Busca redes WiFi desde tu ordenador o teléfono.
  7. Observación esperada: El monitor serie imprime «AP IP Address: 192.168.4.1». La red ESP32_Water_Alert aparece en tu lista de WiFi.
  8. Condición de aprobación: El ESP32 está transmitiendo su propia red de forma autónoma.
  9. Verificación en estado seco
  10. Acción: Conéctate a la red ESP32_Water_Alert (contraseña: wateradmin). Abre http://192.168.4.1 en tu navegador.
  11. 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».
  12. Condición de aprobación: El sistema permanece estable y el servidor web responde correctamente.
  13. Verificación en estado de alarma (Fuga)
  14. Acción: Sumerge las sondas del sensor en agua o puentea los contactos metálicos con un cable.
  15. 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.
  16. Condición de aprobación: La alarma de hardware se activa de forma inmediata e ininterrumpida.
  17. Verificación de latencia de red y API
  18. 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.
  19. Observación esperada: La página principal muestra un bloque rojo «CRITICAL ALARM: LEAK DETECTED». El endpoint /status devuelve {"leak_detected": true, "sensor_pin": 32, "device_mode": "SoftAP"}. El TTFB en la pestaña de red es inferior a 500ms.
  20. 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 (digitalRead en el loop) 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.
  • 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

Ir a 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

Pregunta 1: ¿Cuál es el microcontrolador principal utilizado para construir el dispositivo de detección de fugas?




Pregunta 2: ¿Qué tipo de red inalámbrica aloja el dispositivo de forma autónoma?




Pregunta 3: ¿Qué tipo de alarma activa el dispositivo al detectar una fuga de agua?




Pregunta 4: ¿Qué formato utiliza la API del dispositivo para informar su estado a otros dispositivos locales?




Pregunta 5: ¿Cuál es uno de los casos de uso mencionados para este dispositivo en salas de servidores?




Pregunta 6: ¿Por qué es una ventaja que el dispositivo opere en modo SoftAP?




Pregunta 7: ¿Qué permite prevenir este dispositivo en entornos industriales y residenciales?




Pregunta 8: ¿Qué interfaz proporciona el dispositivo para visualizar su estado en vivo?




Pregunta 9: ¿Qué sucede con la red de alerta local si hay un corte de red en el router principal del edificio?




Pregunta 10: Según el resultado esperado, ¿dónde muestra el ESP32 su dirección IP al inicializarse?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: Control web de garaje con ESP32

Caso práctico: Control web de garaje con ESP32 — hero

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

Por qué es importante / Casos de uso

  • Verificación remota de estado: Confirmación en tiempo real del estado físico de la puerta a través del navegador de un teléfono inteligente con una latencia inferior a 100 ms, omitiendo nubes de terceros.
  • Integración de control de acceso: Reemplaza las llaves físicas por solicitudes de red autenticadas para sistemas de entrada seguros y localizados.
  • Puente para el hogar inteligente (Smart Home): Expone hardware de garaje heredado a través de HTTP estándar para una integración perfecta en plataformas como Home Assistant.

Resultado esperado

  • Una interfaz de usuario web local y adaptable (responsive) que muestra el estado abierto/cerrado en tiempo real a través de un sondeo (polling) continuo de los GPIO.
  • Accionamiento fiable del relé utilizando un pulso de activación de ~500ms para imitar de forma segura la pulsación de un botón físico de pared.
  • Un dispositivo IoT autónomo que funciona completamente con Wi-Fi local sin dependencias de nubes externas.

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

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

Botón local

ESP32 BLE

Paquete advertising

LED de estado

Móvil escáner

Flujo conceptual: configuración local, emisión BLE y lectura desde el móvil.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

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, 3 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 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.

Requisitos previos

Antes de comenzar este tutorial, asegúrese de tener lo siguiente:
* 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

Para este caso práctico, debe utilizar exactamente el siguiente modelo de dispositivo y componentes:
* 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

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

  1. Montar el ESP32: Coloque el ESP32 DevKitC en la placa de pruebas, asegurándose de que los pines queden a ambos lados del divisor central.
  2. Conectar el módulo de relé: Utilice cables puente hembra-macho para conectar el módulo de relé al ESP32. Conecte VCC al pin 5V del ESP32, GND al riel de tierra y el pin IN al GPIO 5.
  3. Conectar el interruptor magnético: Conecte un cable del interruptor magnético (reed switch) al GPIO 18. Conecte el otro cable al riel de tierra. Configuraremos el GPIO 18 por software para utilizar su resistencia pull-up interna. Cuando el imán está cerca del interruptor, el circuito se cierra, llevando el GPIO 18 a LOW (0V). Cuando se retira el imán, el pin pasa a HIGH (3.3V).
  4. Establecer tierra común: Asegúrese de que todos los componentes que comparten la lógica del ESP32 tengan sus conexiones de tierra unidas al pin GND del ESP32.

Código validado

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

Este archivo configura el entorno de compilación, 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 archivo fuente principal que contiene la lógica Wi-Fi, el enrutamiento del servidor web y el control de hardware GPIO. Actualice las macros WIFI_SSID y WIFI_PASSWORD para que coincidan con su router de pruebas.

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>

// --- 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

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.

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 monitor

Flujo de trabajo

  1. Cree un nuevo directorio para su proyecto y ábralo en VS Code.
  2. Inicialice el proyecto utilizando la CLI de PlatformIO.
  3. Reemplace el contenido de platformio.ini y src/main.cpp con el Código validado proporcionado anteriormente.
  4. Conecte su ESP32 DevKitC a su ordenador mediante USB.
  5. Ejecute pio run --target upload en la terminal para compilar el código y flashearlo en el microcontrolador.
  6. Ejecute 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

Para asegurar que el prototipo funcione correcta y de forma segura, realice los siguientes puntos de control agrupados.

  1. Comprobación de inicialización de red
    • Acción: Observe la salida del Monitor Serie inmediatamente después de reiniciar la placa.
    • Observación esperada: La terminal imprime «Connecting to WiFi…», seguido de una serie de puntos, y finalmente «WiFi connected successfully» junto con una dirección IP (por ejemplo, 192.168.1.50).
    • Condición de aprobación: El ESP32 adquiere con éxito una dirección IP local.
  2. Carga de la interfaz web
    • Acción: Abra un navegador web en un ordenador o teléfono inteligente conectado a la misma red Wi-Fi e introduzca la dirección IP del ESP32 en la barra de URL.
    • Observación esperada: Se carga la página «Garage Door Monitor». La interfaz de usuario muestra un gran bloque de estado y un botón «Toggle Door».
    • Condición de aprobación: La página se renderiza correctamente sin estilos CSS faltantes ni errores de JavaScript en la consola del navegador.
  3. Monitorización del estado del sensor (Cerrado)
    • Acción: Acerque la parte magnética del interruptor a la carcasa cableada del sensor.
    • Observación esperada: En menos de 2 segundos, el bloque de estado de la interfaz web se vuelve verde y muestra «CLOSED».
    • Condición de aprobación: El ESP32 lee correctamente el estado LOW del GPIO 18 y envía el payload JSON actualizado al navegador que realiza la consulta.
  4. Monitorización del estado del sensor (Abierto)
    • Acción: Aleje el imán de la carcasa cableada del sensor.
    • Observación esperada: En menos de 2 segundos, el bloque de estado de la interfaz web se vuelve rojo y muestra «OPEN».
    • Condición de aprobación: La resistencia pull-up interna lleva con éxito el GPIO 18 a HIGH, y la interfaz web refleja el cambio automáticamente.
  5. Validación del disparo del actuador
    • Acción: Haga clic en el botón «Toggle Door» en la interfaz web.
    • Observación esperada: Debería escuchar un «clic» audible del módulo de relé, seguido de un segundo «clic» exactamente medio segundo después.
    • Condición de aprobación: El ESP32 recibe correctamente la petición HTTP POST, lleva el GPIO 5 a HIGH durante 500ms y lo devuelve a LOW, simulando la pulsación momentánea de un botón.

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+).

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a 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

Pregunta 1: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 2: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 3: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 4: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 5: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 6: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 7: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 8: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 9: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 10: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: Monitor de congelador Wi-Fi con ESP32

Caso práctico: Monitor de congelador Wi-Fi con ESP32 — hero

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

Por qué es importante / Casos de uso

  • Seguridad y almacenamiento de alimentos: Evita el deterioro de los alimentos alertando a los usuarios de inmediato si la puerta del congelador se deja entreabierta o si falla el compresor.
  • Monitorización de activos de laboratorio: Proporciona una base educativa para monitorizar muestras biológicas sensibles o productos químicos que requieren un almacenamiento estricto en cadena de frío.
  • Diagnóstico de HVAC y refrigeración: Actúa como un prototipo de herramienta de diagnóstico para que los técnicos registren los ciclos de enfriamiento e identifiquen unidades de refrigeración ineficientes.
  • Monitorización remota de instalaciones: Ofrece visibilidad remota continua sobre el estado del almacenamiento en frío fuera de las instalaciones sin requerir inspección física.

Resultado esperado

  • Un circuito de microcontrolador desplegado leyendo valores del termistor NTC con una precisión estimada de ±0.5°C.
  • Activación instantánea de alarma local mediante zumbador y LED (latencia inferior a 2 segundos) ante la superación de un umbral de temperatura.
  • Un servidor web local ligero que aloja un panel de control que renderiza gráficos de temperatura en vivo actualizándose a 1Hz.

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.

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

Sonda de agua

GPIO/ADC ESP32

Lógica de umbral

LED/buzzer

Alerta Wi-Fi

Flujo conceptual: detección de humedad, decisión local y aviso al usuario.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

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, 1 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 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.

Requisitos previos

Antes de comenzar este tutorial, asegúrese de tener listo lo siguiente:
* 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

Para garantizar que el código y el cableado se comporten exactamente como se describe, utilice los componentes precisos enumerados a continuación:

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

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

  • Conecte una pata de la resistencia fija de 10 kOhm al pin de 3.3V en el ESP32 DevKitC.
  • Conecte la otra pata de la resistencia fija de 10 kOhm a una fila vacía en la protoboard.
  • Conecte una pata del termistor NTC de 10 kOhm a esa misma fila de la protoboard.
  • Conecte la otra pata del termistor NTC a GND.
  • Conecte un cable puente desde la unión entre la resistencia fija y el termistor al GPIO34 del ESP32.

2. Zumbador activo

  • Conecte el pin positivo (a menudo marcado con un + o la pata más larga) del zumbador activo al GPIO26.
  • Conecte el pin negativo a GND.

3. LED de estado

  • Conecte el ánodo (pata más larga) del LED de estado a una pata de la resistencia de 220 Ohm.
  • Conecte la otra pata de la resistencia de 220 Ohm al GPIO27.
  • Conecte el cátodo (pata más corta) del LED a GND.

4. Configuración del controlador USB

  • Dependiendo del fabricante exacto de su ESP32 DevKitC, contará con un puente USB a UART CP210x o CH34x.
  • Si su computadora no reconoce automáticamente la placa al conectarla, descargue e instale los controladores CP210x o CH34x adecuados para su sistema operativo.

Código validado

El proyecto se basa en dos archivos dentro del entorno PlatformIO. Cree un nuevo proyecto en PlatformIO seleccionando la placa esp32dev y el framework Arduino. Reemplace el contenido de los archivos generados con el código a continuación.

platformio.ini

Este archivo de configuración define el objetivo de hardware, el framework y la velocidad de baudios del monitor serie.

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

src/main.cpp

Este es el código fuente principal. Asegúrese de actualizar las variables WIFI_SSID y WIFI_PASSWORD para que coincidan con las credenciales de su red local antes de compilar.

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>

// ------------------------------------------------------------------
// 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) + " &deg;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);
}

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a 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

Pregunta 1: ¿Qué tipo de sensor de temperatura utiliza el sistema de monitorización descrito?




Pregunta 2: ¿Qué tipo de alarma activa el sistema cuando la temperatura supera el umbral seguro?




Pregunta 3: ¿Cuál es el umbral de temperatura de ejemplo mencionado para activar la alarma?




Pregunta 4: ¿Cuál es la latencia esperada al servir datos en tiempo real al panel web local?




Pregunta 5: En el contexto de seguridad alimentaria, ¿qué problema busca evitar este sistema?




Pregunta 6: ¿Para qué tipo de muestras de laboratorio es útil este sistema de monitorización?




Pregunta 7: ¿Qué función cumple el sistema para los técnicos de HVAC y refrigeración?




Pregunta 8: ¿Qué ventaja ofrece el sistema para la monitorización de instalaciones remotas?




Pregunta 9: ¿Qué tecnología de conectividad inalámbrica utiliza el sistema de monitorización?




Pregunta 10: ¿Qué tipo de panel se utiliza para visualizar los datos en tiempo real?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: Panel de acceso seguro con ESP32

Caso práctico: Panel de acceso seguro con ESP32 — hero

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

Pines táctiles capacitivos del ESP32

Filtro de eliminación de rebotes por soft…

Validador de secuencias no bloqueante

Actuación GPIO (LED/Zumbador)

Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

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_THRESHOLD necesite 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

Ir a 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

Pregunta 1: ¿Qué tipo de prototipo se construirá según el texto?




Pregunta 2: ¿Qué tipo de prototipo se construirá según el texto?




Pregunta 3: ¿Qué tipo de prototipo se construirá según el texto?




Pregunta 4: ¿Qué tipo de prototipo se construirá según el texto?




Pregunta 5: ¿Qué tipo de prototipo se construirá según el texto?




Pregunta 6: ¿Qué tipo de prototipo se construirá según el texto?




Pregunta 7: ¿Qué tipo de prototipo se construirá según el texto?




Pregunta 8: ¿Qué tipo de prototipo se construirá según el texto?




Pregunta 9: ¿Qué tipo de prototipo se construirá según el texto?




Pregunta 10: ¿Qué tipo de prototipo se construirá según el texto?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme: