Introducción
En esta clase aprenderemos a implementar un sistema completo de monitoreo de temperatura y humedad utilizando el ESP32 como dispositivo IoT. Desarrollaremos la capacidad de leer datos del sensor DHT11, procesar la información, implementar manejo de errores robusto y transmitir los datos a través de MQTT hacia una aplicación web Flask.
Este sistema constituye la base fundamental para aplicaciones IoT industriales y domóticas, donde la precisión en la lectura de datos ambientales y la confiabilidad del sistema son críticas para el éxito del proyecto.
- Configurar y programar el sensor DHT11 con ESP32
- Implementar manejo de errores y validación de datos
- Establecer comunicación MQTT con broker Mosquitto
- Crear interfaz web con Flask para visualización
Conceptos Fundamentales
Para construir un sistema robusto de lectura de sensores, debemos comprender varios conceptos técnicos fundamentales:
Sensor DHT11 - Características Técnicas
- Protocolo de comunicación: One-Wire digital
- Rango de temperatura: 0-50°C (±2°C precisión)
- Rango de humedad: 20-90% RH (±5% RH precisión)
- Frecuencia de muestreo: 1Hz (1 lectura por segundo)
- Voltaje de alimentación: 3.5V a 5.5V
Protocolo MQTT para IoT
- Publisher/Subscriber: Patrón de mensajería asíncrona
- QoS Levels: 0 (At most once), 1 (At least once), 2 (Exactly once)
- Topics: Estructura jerárquica para organización de mensajes
- Retain Flag: Mantiene último mensaje para nuevos suscriptores
Estrategias de Manejo de Errores
- Timeout Handling: Manejo de timeouts en lecturas del sensor
- Data Validation: Verificación de rangos válidos de datos
- Retry Mechanisms: Reintentos automáticos con backoff exponencial
- Graceful Degradation: Operación con funcionalidad reducida ante fallos
Conexiones Físicas del DHT11
Diagrama de conexión:
ESP32 DHT11 ----- ----- 3.3V → VCC (Pin 1) GPIO4 → DATA (Pin 2) N/C → N/C (Pin 3) GND → GND (Pin 4) Resistor pull-up 10kΩ entre DATA y VCC
Implementación Práctica
Implementaremos el sistema completo paso a paso, comenzando con la configuración del ESP32 y culminando con la aplicación web Flask.
Configuración Inicial del ESP32
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h>
// Configuración de pines y constantes
#define DHT_PIN 4
#define DHT_TYPE DHT11
#define LED_PIN 2
// Configuración WiFi
const char* ssid = "TU_WIFI_SSID";
const char* password = "TU_WIFI_PASSWORD";
// Configuración MQTT
const char* mqtt_server = "localhost";
const int mqtt_port = 1883;
const char* mqtt_user = "esp32_user";
const char* mqtt_password = "esp32_pass";
const char* topic_temperature = "sensors/temperature";
const char* topic_humidity = "sensors/humidity";
const char* topic_status = "sensors/status";
// Instancias de objetos
DHT dht(DHT_PIN, DHT_TYPE);
WiFiClient espClient;
PubSubClient client(espClient);
// Variables para control de tiempo y errores
unsigned long lastMeasurement = 0;
const unsigned long measurementInterval = 5000; // 5 segundos
int consecutiveErrors = 0;
const int maxConsecutiveErrors = 5;
Función de Configuración Inicial
void setup() {
Serial.begin(115200);
Serial.println("Iniciando sistema de monitoreo DHT11...");
// Configuración de pines
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Inicialización del sensor DHT11
dht.begin();
Serial.println("Sensor DHT11 inicializado");
// Conexión WiFi
setupWiFi();
// Configuración MQTT
client.setServer(mqtt_server, mqtt_port);
client.setCallback(onMqttMessage);
// Conexión inicial MQTT
connectToMQTT();
Serial.println("Sistema listo para operación");
digitalWrite(LED_PIN, HIGH); // Indica sistema listo
}
void setupWiFi() {
delay(10);
Serial.println();
Serial.print("Conectando a WiFi: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println();
Serial.println("WiFi conectado exitosamente!");
Serial.print("Dirección IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println();
Serial.println("Error: No se pudo conectar a WiFi");
// Reiniciar ESP32 después de fallo crítico
ESP.restart();
}
}
Lectura Robusta del Sensor DHT11
struct SensorData {
float temperature;
float humidity;
bool isValid;
String errorMessage;
};
SensorData readDHT11Sensor() {
SensorData data = {0.0, 0.0, false, ""};
Serial.println("Leyendo sensor DHT11...");
// Lectura de los valores del sensor
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
// Verificación básica de NaN
if (isnan(humidity) || isnan(temperature)) {
data.errorMessage = "Error: Lectura NaN del sensor";
Serial.println(data.errorMessage);
return data;
}
// Validación de rangos técnicos del DHT11
if (temperature < -10.0 || temperature > 60.0) {
data.errorMessage = "Error: Temperatura fuera del rango válido (" +
String(temperature) + "°C)";
Serial.println(data.errorMessage);
return data;
}
if (humidity < 0.0 || humidity > 100.0) {
data.errorMessage = "Error: Humedad fuera del rango válido (" +
String(humidity) + "%)";
Serial.println(data.errorMessage);
return data;
}
// Datos válidos
data.temperature = temperature;
data.humidity = humidity;
data.isValid = true;
Serial.println("Lectura exitosa - Temp: " + String(temperature) +
"°C, Humedad: " + String(humidity) + "%");
return data;
}
void handleSensorReading() {
SensorData sensorData = readDHT11Sensor();
if (sensorData.isValid) {
// Resetear contador de errores consecutivos
consecutiveErrors = 0;
// Publicar datos válidos via MQTT
publishSensorData(sensorData.temperature, sensorData.humidity);
// Indicador visual de operación exitosa
blinkLED(1, 100); // 1 parpadeo rápido
} else {
// Incrementar contador de errores
consecutiveErrors++;
Serial.println("Error en lectura del sensor (#" +
String(consecutiveErrors) + "): " +
sensorData.errorMessage);
// Publicar estado de error
publishErrorStatus(sensorData.errorMessage);
// Indicador visual de error
blinkLED(3, 200); // 3 parpadeos lentos
// Verificar si hay demasiados errores consecutivos
if (consecutiveErrors >= maxConsecutiveErrors) {
Serial.println("ALERTA: Demasiados errores consecutivos. Reiniciando...");
publishErrorStatus("CRITICO: Reiniciando por errores consecutivos");
delay(1000);
ESP.restart();
}
}
}
Gestión de Comunicación MQTT
void connectToMQTT() {
while (!client.connected()) {
Serial.print("Intentando conexión MQTT...");
String clientId = "ESP32Client-";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
Serial.println(" conectado!");
// Publicar mensaje de conexión
publishErrorStatus("ESP32 conectado y operativo");
// Suscribirse a topics de control (opcional)
client.subscribe("sensors/control");
} else {
Serial.print(" falló, rc=");
Serial.print(client.state());
Serial.println(" reintentando en 5 segundos");
delay(5000);
}
}
}
void publishSensorData(float temperature, float humidity) {
// Crear JSON con los datos del sensor
StaticJsonDocument<200> doc;
doc["timestamp"] = millis();
doc["device_id"] = "ESP32-DHT11-01";
doc["temperature"] = round(temperature * 100.0) / 100.0; // 2 decimales
doc["humidity"] = round(humidity * 100.0) / 100.0; // 2 decimales
String jsonString;
serializeJson(doc, jsonString);
// Publicar temperatura
String tempPayload = String(temperature, 2);
bool tempResult = client.publish(topic_temperature, tempPayload.c_str(), true);
// Publicar humedad
String humPayload = String(humidity, 2);
bool humResult = client.publish(topic_humidity, humPayload.c_str(), true);
// Publicar datos completos en JSON
bool jsonResult = client.publish("sensors/data", jsonString.c_str(), true);
if (tempResult && humResult && jsonResult) {
Serial.println("Datos publicados exitosamente via MQTT");
} else {
Serial.println("Error al publicar algunos datos MQTT");
}
}
void publishErrorStatus(String errorMessage) {
StaticJsonDocument<200> doc;
doc["timestamp"] = millis();
doc["device_id"] = "ESP32-DHT11-01";
doc["status"] = "error";
doc["message"] = errorMessage;
doc["consecutive_errors"] = consecutiveErrors;
String jsonString;
serializeJson(doc, jsonString);
client.publish(topic_status, jsonString.c_str(), true);
}
void onMqttMessage(char* topic, byte* payload, unsigned int length) {
Serial.print("Mensaje recibido en topic: ");
Serial.println(topic);
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println("Payload: " + message);
}
void blinkLED(int times, int delayMs) {
for (int i = 0; i < times; i++) {
digitalWrite(LED_PIN, LOW);
delay(delayMs);
digitalWrite(LED_PIN, HIGH);
delay(delayMs);
}
}
Loop Principal del ESP32
void loop() {
// Verificar conexión MQTT
if (!client.connected()) {
connectToMQTT();
}
client.loop();
// Verificar conexión WiFi
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi desconectado. Reintentando...");
setupWiFi();
}
// Realizar lectura periódica del sensor
unsigned long now = millis();
if (now - lastMeasurement > measurementInterval) {
lastMeasurement = now;
handleSensorReading();
}
// Pequeña pausa para evitar saturar el procesador
delay(100);
}