Encuentra tu pasión a solo 4,17 US$/mes | Aprende Robótica, IA e IOT.

Este Servidor Web con ESP32 y DHT11 Te Dirá Si Hace Frío o Calor (¡Con Emojis!)

¿Quieres aprender a crear un termómetro digital con ESP32? En este tutorial crearás un servidor web ESP32 DHT11 con emojis dinámicos y umbrales editables para monitorear la temperatura en tiempo real desde tu navegador.

Lista de Materiales

Para construir este servidor web ESP32 DHT11 que simula un termómetro digital con emojis y control de umbrales, necesitas algunos componentes esenciales. Esta lista te ayudará a reunir todo lo necesario para completar el proyecto sin contratiempos.

Cantidad Material Función
1 ESP32 DevKit Microcontrolador principal que aloja el servidor web
1 Sensor DHT11 Mide la temperatura y humedad del entorno
1 Protoboard Permite realizar las conexiones sin necesidad de soldar
3 Jumpers macho-macho Conectan el sensor al ESP32 a través de la protoboard
1 Cable USB Permite alimentar y programar el ESP32 desde el PC

Diagrama de Conexiones

A continuación, te mostramos cómo conectar cada componente paso a paso.o.

Paso 1: Identifica los pines del sensor DHT11

Explicación: El DHT11 tiene tres pines principales: VCC, DATA y GND. Verifica su disposición, que suele estar impresa en el módulo o en su hoja técnica.

Paso 2: Conecta el pin VCC del DHT11 al pin 3.3V del ESP32

Explicación: El pin VCC del sensor debe ir al pin de 3.3V del ESP32 para que reciba la alimentación necesaria para funcionar correctamente.

Paso 3: Conecta el pin GND del DHT11 al pin GND del ESP32

Explicación: Une el pin de tierra del sensor al GND del ESP32 para establecer la referencia común entre ambos dispositivos.

Paso 4: Conecta el pin DATA del DHT11 al GPIO 27 del ESP32

Explicación: El pin de datos del DHT11 debe conectarse al GPIO 27 del ESP32. Este pin se usará para leer la temperatura desde el código cargado en el microcontrolador.

Paso 5: Revisa todas las conexiones antes de encender el sistema

Explicación: Asegúrate de que todos los cables estén bien insertados en la protoboard y que no haya cables sueltos o mal ubicados, ya que esto puede afectar el funcionamiento del proyecto.

Nota: Si el GPIO 27 te queda incómodo en la protoboard, puedes usar el GPIO 4. Solo cambia el número de pin en el código.

Código

A continuación te presento el código completo que permite convertir tu ESP32 en un termómetro digital con servidor web, usando el sensor DHT11 y controlando los umbrales de temperatura desde el navegador.

				
					// Incluye las librerías necesarias para ESP32
#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h> // Librería para el sensor DHT11/DHT22

// --- Configuración del Wi-Fi ---
// ¡IMPORTANTE! Descomenta y modifica estas líneas con los datos de tu red Wi-Fi
const char* ssid = "xx";       // Reemplaza con el nombre de tu red Wi-Fi
const char* password = "xx"; // Reemplaza con la contraseña de tu red Wi-Fi


// --- Configuración del Sensor DHT11 ---
#define DHTPIN 27    // Pin digital donde está conectado el sensor DHT11 (GPIO27 en la ESP32)
#define DHTTYPE DHT11 // Tipo de sensor: DHT11 o DHT22

DHT dht(DHTPIN, DHTTYPE); // Inicializa el sensor DHT

WebServer server(80); // Crea una instancia del servidor web en el puerto 80

// --- Variables globales para los umbrales de temperatura ---
float coldThreshold = 20.0; // Umbral de temperatura para 'frío' (<= coldThreshold)
float hotThreshold = 27.0;  // Umbral de temperatura para 'caliente' (>= hotThreshold)

// --- Contenido de la Página Web (HTML, CSS, JavaScript) ---
// El contenido HTML se sirve como una cadena de caracteres.
const char* index_html = R"rawliteral(
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Termómetro Digital ESP32</title>
    <!-- Incluye Tailwind CSS para un diseño moderno y responsivo -->
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        /* Define una fuente personalizada para una mejor estética */
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');

        body {
            font-family: 'Inter', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: white; /* Fondo blanco */
            margin: 0;
            color: #333;
            text-align: center;
        }

        /* Estilos específicos para el termómetro */
        .thermometer {
            width: 40px; /* Ancho del cuerpo del termómetro */
            height: 200px; /* Alto del cuerpo del termómetro */
            background-color: rgba(255, 255, 255, 0.8);
            border: 2px solid #ccc;
            border-radius: 20px 20px 5px 5px; /* Bordes redondeados en la parte superior */
            position: relative;
            overflow: hidden; /* Asegura que el líquido no se desborde */
            box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
        }

        .bulb {
            width: 60px; /* Tamaño de la bombilla */
            height: 60px;
            background-color: #dc3545; /* Color del mercurio en la bombilla */
            border-radius: 50%;
            margin-top: -10px; /* Para que se superponga ligeramente con el cuerpo */
            border: 2px solid #a00;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
            z-index: 1; /* Para que la bombilla esté por encima del líquido */
        }

        .liquid {
            position: absolute;
            bottom: 0;
            left: 0;
            width: 100%;
            background-color: #dc3545; /* Color del líquido que sube */
            transition: height 0.7s ease-in-out; /* Animación suave para el cambio de altura */
            border-radius: 0 0 5px 5px; /* Bordes inferiores redondeados para el líquido */
        }
    </style>
</head>
<body class="bg-white flex justify-center items-center min-h-screen text-gray-800">
    <!-- Nuevo div para agrupar el contenido visualmente -->
    <div class="p-8 md:p-12 rounded-xl shadow-2xl max-w-sm w-full bg-white">
        <!-- Título: "Temperatura" -->
        <h1 class="text-3xl font-extrabold text-blue-600 mb-6">Temperatura</h1>
        <div class="thermometer-container flex flex-col items-center mb-6">
            <div class="thermometer">
                <div class="liquid"></div>
            </div>
            <div class="bulb"></div>
        </div>
        <!-- Etiqueta de temperatura con espacio para el emoji -->
        <span id="temperature-label" class="text-5xl font-bold text-blue-700 mb-8 block drop-shadow-lg">--.-°C <span id="temperature-emoji"></span></span>

        <h2 class="text-2xl font-semibold text-gray-700 mt-8 mb-4">Configurar Umbrales</h2>
        <div class="flex flex-col gap-4">
            <div class="flex items-center justify-between">
                <label for="coldThreshold" class="text-lg">Frío (🥶 &lt;=):</label>
                <input type="number" id="coldThreshold" value="20" step="0.1" class="w-24 p-2 border border-gray-300 rounded-md text-center">
            </div>
            <div class="flex items-center justify-between">
                <label for="hotThreshold" class="text-lg">Caliente (🥵 >=):</label>
                <input type="number" id="hotThreshold" value="27" step="0.1" class="w-24 p-2 border border-gray-300 rounded-md text-center">
            </div>
            <button id="saveThresholds" class="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white font-semibold py-3 px-8 rounded-lg text-lg cursor-pointer transition-all duration-300 ease-in-out shadow-md hover:shadow-xl transform hover:-translate-y-1">
                Guardar Umbrales
            </button>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const temperatureLabel = document.getElementById('temperature-label');
            const liquidElement = document.querySelector('.liquid');
            const temperatureEmoji = document.getElementById('temperature-emoji');
            const coldThresholdInput = document.getElementById('coldThreshold');
            const hotThresholdInput = document.getElementById('hotThreshold');
            const saveThresholdsButton = document.getElementById('saveThresholds');

            let currentColdThreshold = 20; // Default inicial, se actualizará desde la ESP32
            let currentHotThreshold = 27;  // Default inicial, se actualizará desde la ESP32

            /**
             * Obtiene los umbrales de temperatura del servidor ESP32 y actualiza los campos de entrada.
             */
            async function fetchThresholds() {
                try {
                    const response = await fetch('/get_thresholds');
                    if (!response.ok) {
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }
                    const data = await response.json();
                    currentColdThreshold = parseFloat(data.cold).toFixed(1);
                    currentHotThreshold = parseFloat(data.hot).toFixed(1);
                    coldThresholdInput.value = currentColdThreshold;
                    hotThresholdInput.value = currentHotThreshold;
                    console.log(`Umbrales fetched: Frio=${currentColdThreshold}, Caliente=${currentHotThreshold}`);
                } catch (error) {
                    console.error('Error al obtener los umbrales:', error);
                }
            }

            /**
             * Guarda los umbrales de temperatura en el servidor ESP32.
             */
            async function saveThresholds() {
                const newCold = parseFloat(coldThresholdInput.value);
                const newHot = parseFloat(hotThresholdInput.value);

                if (isNaN(newCold) || isNaN(newHot)) {
                    alert('Por favor, introduce valores numéricos válidos para los umbrales.');
                    return;
                }

                try {
                    const response = await fetch(`/set_thresholds?cold=${newCold}&hot=${newHot}`);
                    if (!response.ok) {
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }
                    console.log('Umbrales guardados con éxito.');
                    // Actualizar los umbrales locales para la lógica de emojis
                    currentColdThreshold = newCold;
                    currentHotThreshold = newHot;
                    // Forzar una actualización de temperatura para reflejar los nuevos umbrales inmediatamente
                    fetchTemperature();
                } catch (error) {
                    console.error('Error al guardar los umbrales:', error);
                    alert('Error al guardar los umbrales. Consulta la consola para más detalles.');
                }
            }

            /**
             * Obtiene la temperatura del servidor ESP32 y actualiza la interfaz.
             */
            async function fetchTemperature() {
                try {
                    // Realiza una solicitud al endpoint /temperature del servidor ESP32
                    const response = await fetch('/temperature');
                    if (!response.ok) {
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }
                    const data = await response.json(); // Parsea la respuesta JSON

                    const currentTemperature = parseFloat(data.temperature).toFixed(1);

                    // Actualiza la etiqueta de texto con la nueva temperatura.
                    temperatureLabel.firstChild.textContent = `${currentTemperature}°C `;

                    // Define la temperatura máxima para el cálculo de la altura del líquido.
                    // La escala del termómetro es de 0 a 100 grados Celsius.
                    const maxTemperature = 100;
                    // Calcula la altura del líquido como un porcentaje basado en la temperatura actual.
                    const liquidHeightPercentage = (currentTemperature / maxTemperature) * 100;

                    // Limita la altura del líquido entre 0% y 100% para evitar desbordamientos visuales.
                    liquidElement.style.height = `${Math.max(0, Math.min(100, liquidHeightPercentage))}%`;

                    // Actualiza el emoji según el rango de temperatura utilizando los umbrales actuales
                    if (parseFloat(currentTemperature) >= currentHotThreshold) { // Cambiado a >=
                        temperatureEmoji.textContent = '🥵'; // Mayor o igual que el umbral de caliente
                    } else if (parseFloat(currentTemperature) <= currentColdThreshold) { // Cambiado a <=
                        temperatureEmoji.textContent = '🥶'; // Menor o igual que el umbral de frío
                    } else {
                        temperatureEmoji.textContent = '🤠'; // Entre los umbrales
                    }

                } catch (error) {
                    console.error('Error al obtener la temperatura:', error);
                    temperatureLabel.firstChild.textContent = 'Error°C '; // Muestra error si falla la lectura
                    temperatureEmoji.textContent = '❓';
                    liquidElement.style.height = '0%'; // Resetea el líquido
                }
            }

            // Llama a fetchThresholds y fetchTemperature una vez al cargar la página
            fetchThresholds();
            fetchTemperature();

            // Actualiza la temperatura cada 2 segundos (2000 milisegundos)
            setInterval(fetchTemperature, 2000);
            // Actualiza los umbrales cada 60 segundos por si se han cambiado en otro lugar o se quieren sincronizar
            // setInterval(fetchThresholds, 60000); // Esto es opcional, solo si esperas cambios externos

            // Asigna el evento click al botón de guardar umbrales
            saveThresholdsButton.addEventListener('click', saveThresholds);
        });
    </script>
</body>
</html>
)rawliteral";

// --- Manejadores de Solicitudes del Servidor Web ---

// Maneja la solicitud de la página raíz ("/")
void handleRoot() {
  server.send(200, "text/html", index_html);
}

// Maneja la solicitud para obtener los umbrales actuales
void handleGetThresholds() {
  String jsonResponse = "{";
  jsonResponse += "\"cold\": " + String(coldThreshold) + ", ";
  jsonResponse += "\"hot\": " + String(hotThreshold);
  jsonResponse += "}";
  server.send(200, "application/json", jsonResponse);
}

// Maneja la solicitud para establecer nuevos umbrales
void handleSetThresholds() {
  if (server.hasArg("cold") && server.hasArg("hot")) {
    coldThreshold = server.arg("cold").toFloat();
    hotThreshold = server.arg("hot").toFloat();
    Serial.print("Umbral Frio actualizado a: ");
    Serial.println(coldThreshold);
    Serial.print("Umbral Caliente actualizado a: ");
    Serial.println(hotThreshold);
    server.send(200, "text/plain", "Umbrales actualizados con éxito!");
  } else {
    server.send(400, "text/plain", "Parámetros de umbral faltantes (cold o hot).");
  }
}


// Maneja la solicitud de la temperatura (endpoint para AJAX)
void handleTemperature() {
  float h = dht.readHumidity();
  float t = dht.readTemperature(); // Lee la temperatura en Celsius

  String jsonResponse = "{";
  if (isnan(h) || isnan(t)) {
    Serial.println("Error al leer del sensor DHT!");
    jsonResponse += "\"temperature\": \"Error\", \"humidity\": \"Error\"";
  } else {
    Serial.print("Temperatura: ");
    Serial.print(t);
    Serial.print("°C ");
    Serial.print("Humedad: ");
    Serial.print(h);
    Serial.println("%");

    jsonResponse += "\"temperature\": " + String(t) + ", ";
    jsonResponse += "\"humidity\": " + String(h);
  }
  jsonResponse += "}";

  server.send(200, "application/json", jsonResponse);
}

// --- Función de Configuración (Setup) ---
void setup() {
  Serial.begin(115200); // Inicia la comunicación serial para depuración
  dht.begin(); // Inicializa el sensor DHT

  // --- Conexión a la red Wi-Fi existente ---
  Serial.print("Conectando a WiFi ");
  Serial.println(ssid);
  WiFi.begin(ssid, password); // Intenta conectar a la red Wi-Fi

  // Espera hasta que la conexión Wi-Fi sea exitosa
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi conectado!");
  Serial.print("Dirección IP: ");
  Serial.println(WiFi.localIP()); // Muestra la dirección IP asignada


  // --- Configuración de las Rutas del Servidor ---
  server.on("/", handleRoot); // Cuando se acceda a la raíz, sirve el HTML
  server.on("/temperature", handleTemperature); // Endpoint para obtener datos de temperatura
  server.on("/get_thresholds", handleGetThresholds); // Nuevo endpoint para obtener los umbrales
  server.on("/set_thresholds", handleSetThresholds); // Nuevo endpoint para establecer los umbrales

  server.begin(); // Inicia el servidor web
  Serial.println("Servidor HTTP iniciado");
}

// --- Función Principal (Loop) ---
void loop() {
  server.handleClient(); // Permanece escuchando las solicitudes de los clientes
  // Puedes añadir otras tareas aquí si es necesario
}

				
			

A continuación, te voy a explicar las acciones más importantes del código para que puedas entender cómo funciona y personalizarlo si lo necesitas.

Instalación de librerías necesarias

 Al inicio del código encontrarás:

				
					#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>

				
			

Estas librerías son fundamentales.

  • WiFi.h permite conectar el ESP32 a tu red WiFi.

  • WebServer.h habilita la creación del servidor web local.

  • DHT.h gestiona la lectura del sensor DHT11.

Asegúrate de tener instalada la librería «DHT sensor library» de Adafruit desde el Administrador de Librerías.

Definición de pines y configuración inicial

				
					#define DHTPIN 27
#define DHTTYPE DHT11

				
			

DHTPIN es el pin digital donde está conectado el sensor. Por defecto es el GPIO 27, pero puedes cambiarlo por otro (por ejemplo, el GPIO 4) si te resulta más cómodo.

Función principal del proyecto

Dentro del loop() y las funciones manejadas por el servidor (server.on), se ejecutan las siguientes acciones clave:

  1. Lectura de temperatura actual desde el sensor.

  2. Comparación con los umbrales definidos por el usuario.

  3. Generación de una página HTML dinámica que muestra la temperatura y un emoji representativo del clima.

  4. Lectura y actualización de los nuevos umbrales cuando el usuario los edita desde el formulario web.

				
					String generateHTML() {
  // Se arma el HTML con temperatura actual, emoji y formulario de umbrales
}


				
			

Esta es la función central del proyecto, ya que define cómo se visualiza la información y cómo interactúa el usuario con el servidor.

Edición de umbrales desde el navegador

El formulario web permite enviar valores nuevos para los umbrales de «frío» y «calor». Estos valores se capturan así:

				
					server.on("/set", HTTP_POST, []() {
  umbralFrio = server.arg("frio").toFloat();
  umbralCalor = server.arg("calor").toFloat();
  ...
});


				
			

Aquí es donde el usuario personaliza los umbrales de temperatura directamente desde el navegador.

Pruebas Experimentales

Una vez que hayas realizado todas las conexiones y tengas listo el código, es momento de poner a prueba el proyecto. En esta sección te explico cómo cargar el programa al ESP32, acceder al servidor web y verificar el funcionamiento del termómetro digital interactivo.

Paso 1: Conecta el ESP32 al computador

Explicación: Usa un cable USB para conectar tu ESP32 al computador. Asegúrate de que el cable sea de datos, no solo de carga. Abre el Arduino IDE y selecciona el puerto correcto desde el menú Herramientas > Puerto.

Paso 2: Verifica y carga el código

Explicación: Abre el archivo del proyecto en Arduino IDE y haz clic en el botón de verificar (✔️) para compilar el código. Si no hay errores, haz clic en cargar (→) para subir el programa al ESP32.

Paso 3: Abre el monitor serie

Explicación: Una vez cargado el código, abre el monitor serie desde el menú Herramientas. Configura la velocidad en 115200 baudios. Aquí podrás ver si la conexión WiFi fue exitosa y cuál es la dirección IP local asignada al ESP32.

Paso 4: Ingresa a la IP desde tu navegador

Explicación: Copia la dirección IP mostrada en el monitor serie y pégala en el navegador de tu celular o PC (conectado a la misma red WiFi). Esto te llevará al servidor web del termómetro digital.

Paso 5: Verifica la lectura de temperatura

Explicación: En la página verás la temperatura actual que mide el sensor DHT11. Junto a ella se mostrará un emoji climático dependiendo de si la temperatura está en un rango frío, normal o caliente.

Paso 6: Modifica los umbrales desde el formulario

Explicación: En la misma página encontrarás un formulario para editar los umbrales de temperatura. Ingresa nuevos valores y haz clic en enviar. La página se recargará con los nuevos umbrales aplicados automáticamente.

Paso 7: Realiza pruebas con diferentes temperaturas

Explicación: Acerca una fuente de calor (como tu mano) o frío (como un ventilador o un paño húmedo) al sensor para comprobar cómo cambia el emoji según la lectura. Observa cómo la interfaz reacciona en tiempo real.

Ingrese a su cuenta