Introducción
En esta clase exploraremos la arquitectura típica de un sistema IoT y cómo implementarla usando un stack tecnológico completo: ESP32 como dispositivo sensor, DHT11 para medición de temperatura y humedad, Mosquitto como broker MQTT, y Flask como servidor web para visualización de datos.
La arquitectura IoT sigue un patrón fundamental: Dispositivo → Broker → Servidor → Cliente, donde cada componente tiene un rol específico en el flujo de datos desde la captura hasta la presentación al usuario final.
Objetivos de Aprendizaje
- Comprender la arquitectura completa de un sistema IoT
- Implementar cada componente del stack tecnológico
- Establecer comunicación MQTT entre dispositivos
- Crear una interfaz web para monitoreo de datos
Conceptos Fundamentales
Arquitectura de 4 Capas IoT
Un sistema IoT típico se compone de cuatro elementos principales que forman una cadena de comunicación bidireccional:
Dispositivo
ESP32 + DHT11
Broker
Mosquitto MQTT
Servidor
Flask
Cliente
Navegador Web
ESP32 → WiFi → MQTT Broker → Flask Server → HTTP → Web Browser
- Dispositivo (ESP32 + DHT11): Capa física que captura datos del entorno
- Broker (Mosquitto MQTT): Intermediario que gestiona la comunicación entre dispositivos
- Servidor (Flask): Procesa, almacena y sirve los datos
- Cliente (Navegador Web): Interfaz de usuario para visualización y control
Flujo de Datos:
Los datos fluyen desde los sensores físicos hasta la interfaz de usuario, pasando por el broker MQTT que actúa como intermediario de mensajería, y el servidor Flask que procesa y almacena la información antes de presentarla al cliente web.
Protocolo MQTT
MQTT (Message Queuing Telemetry Transport) es un protocolo de mensajería ligero ideal para IoT:
- Publish/Subscribe: Patrón de comunicación desacoplada
- Topics: Canales de comunicación organizados jerárquicamente
- QoS Levels: Garantías de entrega (0, 1, 2)
- Retain Messages: Último mensaje disponible para nuevos suscriptores
Roles y Responsabilidades
Componente | Función Principal | Tecnología |
---|---|---|
Dispositivo Sensor | Captura datos ambientales, conectividad WiFi, publicación MQTT | ESP32 + DHT11 |
Broker MQTT | Enrutamiento de mensajes, gestión de suscripciones | Mosquitto |
Servidor de Aplicación | Lógica de negocio, almacenamiento, API REST | Flask + Python |
Cliente Web | Interfaz de usuario, visualización, control | HTML/CSS/JavaScript |
Implementación Práctica
1. Configuración del Dispositivo ESP32
Comenzamos implementando el código para el ESP32 que conectará el sensor DHT11 y publicará datos vía MQTT:
Conexiones físicas:
- DHT11 VCC → ESP32 3.3V
- DHT11 GND → ESP32 GND
- DHT11 DATA → ESP32 GPIO4
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h>
// Configuración WiFi
const char* ssid = "TU_RED_WIFI";
const char* password = "TU_PASSWORD";
// Configuración MQTT
const char* mqtt_server = "192.168.1.100"; // IP del broker Mosquitto
const int mqtt_port = 1883;
const char* mqtt_user = ""; // Usuario MQTT (opcional)
const char* mqtt_password = ""; // Password MQTT (opcional)
// Configuración DHT11
#define DHT_PIN 4
#define DHT_TYPE DHT11
DHT dht(DHT_PIN, DHT_TYPE);
// Topics MQTT
const char* temperature_topic = "home/sensors/temperature";
const char* humidity_topic = "home/sensors/humidity";
const char* status_topic = "home/sensors/status";
WiFiClient espClient;
PubSubClient client(espClient);
// Variables de control
unsigned long lastMsg = 0;
const long interval = 30000; // Envío cada 30 segundos
void setup() {
Serial.begin(115200);
dht.begin();
// Conectar WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi conectado!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
// Configurar MQTT
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
unsigned long now = millis();
if (now - lastMsg > interval) {
lastMsg = now;
publishSensorData();
}
}
void publishSensorData() {
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
// Verificar lecturas válidas
if (isnan(temperature) || isnan(humidity)) {
Serial.println("Error leyendo DHT11!");
client.publish(status_topic, "ERROR: DHT11 read failed");
return;
}
// Crear JSON con los datos
StaticJsonDocument<200> doc;
doc["temperature"] = temperature;
doc["humidity"] = humidity;
doc["timestamp"] = millis();
doc["device_id"] = "ESP32_001";
String jsonString;
serializeJson(doc, jsonString);
// Publicar datos
client.publish(temperature_topic, String(temperature).c_str(), true);
client.publish(humidity_topic, String(humidity).c_str(), true);
client.publish("home/sensors/data", jsonString.c_str(), true);
Serial.printf("Datos publicados - Temp: %.2f°C, Humedad: %.2f%%\n",
temperature, humidity);
}
void callback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.printf("Mensaje recibido [%s]: %s\n", topic, message.c_str());
}
void reconnect() {
while (!client.connected()) {
Serial.print("Conectando MQTT...");
String clientId = "ESP32Client-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
Serial.println(" conectado!");
client.publish(status_topic, "ESP32 Online");
client.subscribe("home/sensors/commands");
} else {
Serial.printf(" falló, rc=%d. Reintentando en 5s\n", client.state());
delay(5000);
}
}
}
2. Configuración del Broker Mosquitto
Configuramos Mosquitto MQTT broker en el servidor. Crear archivo de configuración:
# /etc/mosquitto/mosquitto.conf
port 1883
listener 1883 0.0.0.0
# Permitir conexiones anónimas (para desarrollo)
allow_anonymous true
# Configuración de logs
log_dest file /var/log/mosquitto/mosquitto.log
log_type error
log_type warning
log_type notice
log_type information
log_timestamp true
# Persistencia de datos
persistence true
persistence_location /var/lib/mosquitto/
# Configuración de QoS
max_queued_messages 1000
message_size_limit 0
# Instalar Mosquitto (Ubuntu/Debian)
sudo apt update
sudo apt install mosquitto mosquitto-clients
# Iniciar el servicio
sudo systemctl start mosquitto
sudo systemctl enable mosquitto
# Verificar estado
sudo systemctl status mosquitto
# Probar suscripción
mosquitto_sub -h localhost -t "home/sensors/#" -v
3. Servidor Flask
Implementamos el servidor Flask que se suscribirá a los topics MQTT y servirá los datos vía HTTP:
from flask import Flask, render_template, jsonify
from flask_socketio import SocketIO, emit
import paho.mqtt.client as mqtt
import json
import threading
import time
from datetime import datetime
import sqlite3
app = Flask(__name__)
app.config['SECRET_KEY'] = 'tu_clave_secreta_aqui'
socketio = SocketIO(app, cors_allowed_origins="*")
# Configuración MQTT
MQTT_BROKER = "localhost"
MQTT_PORT = 1883
MQTT_TOPICS = [
("home/sensors/temperature", 0),
("home/sensors/humidity", 0),
("home/sensors/data", 0),
("home/sensors/status", 0)
]
# Variables globales para almacenar datos
sensor_data = {
"temperature": None,
"humidity": None,
"timestamp": None,
"status": "Desconectado"
}
# Base de datos SQLite
def init_db():
conn = sqlite3.connect('sensor_data.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS readings
(id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME,
temperature REAL,
humidity REAL,
device_id TEXT)''')
conn.commit()
conn.close()
def save_to_db(temperature, humidity, device_id="ESP32_001"):
conn = sqlite3.connect('sensor_data.db')
c = conn.cursor()
c.execute("INSERT INTO readings (timestamp, temperature, humidity, device_id) VALUES (?, ?, ?, ?)",
(datetime.now(), temperature, humidity, device_id))
conn.commit()
conn.close()
# Callbacks MQTT
def on_connect(client, userdata, flags, rc):
print(f"Conectado al broker MQTT con código: {rc}")
for topic, qos in MQTT_TOPICS:
client.subscribe(topic, qos)
print(f"Suscrito a: {topic}")
def on_message(client, userdata, msg):
global sensor_data
topic = msg.topic
payload = msg.payload.decode()
print(f"Mensaje recibido [{topic}]: {payload}")
try:
if topic == "home/sensors/temperature":
sensor_data["temperature"] = float(payload)
elif topic == "home/sensors/humidity":
sensor_data["humidity"] = float(payload)
elif topic == "home/sensors/data":
data = json.loads(payload)
sensor_data.update({
"temperature": data.get("temperature"),
"humidity": data.get("humidity"),
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"device_id": data.get("device_id", "Unknown")
})
# Guardar en base de datos
save_to_db(data.get("temperature"), data.get("humidity"),
data.get("device_id", "ESP32_001"))
elif topic == "home/sensors/status":
sensor_data["status"] = payload
# Actualizar timestamp
sensor_data["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Emitir datos a clientes conectados vía WebSocket
socketio.emit('sensor_update', sensor_data)
except Exception as e:
print(f"Error procesando mensaje: {e}")
# Configurar cliente MQTT y SocketIO
def setup_mqtt():
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect(MQTT_BROKER, MQTT_PORT, 60)
return client
# Rutas Flask
@app.route("/")
def index():
return jsonify({"message": "Servidor Flask IoT operativo", "data": sensor_data})
@app.route("/api/current")
def api_current():
return jsonify(sensor_data)
@app.route("/api/history")
def api_history():
rows = []
try:
conn = sqlite3.connect('sensor_data.db')
c = conn.cursor()
c.execute("SELECT timestamp, temperature, humidity, device_id FROM readings ORDER BY id DESC LIMIT 100")
rows = [
{
"timestamp": r[0],
"temperature": r[1],
"humidity": r[2],
"device_id": r[3],
}
for r in c.fetchall()
]
conn.close()
except Exception as e:
print(f"Error obteniendo historial: {e}")
return jsonify(rows)
def start_mqtt_loop(client):
client.loop_forever()
if __name__ == "__main__":
init_db()
mqtt_client = setup_mqtt()
t = threading.Thread(target=start_mqtt_loop, args=(mqtt_client,), daemon=True)
t.start()
# Ejecutar SocketIO (sustituye a app.run)
socketio.run(app, host="0.0.0.0", port=5000, debug=True)
Ejecución del Sistema Completo
- 1. Iniciar broker Mosquitto: sudo systemctl start mosquitto
- 2. Ejecutar servidor Flask: python app.py
- 3. Cargar y ejecutar código en ESP32
- 4. Verificar comunicación: curl http://localhost:5000/api/current