Arquitectura típica de un sistema IoT

Dispositivo → Broker → Servidor → Cliente: Componentes, flujo de datos y roles en el ecosistema

Módulo 1 ⏱️ 2 horas 🛠️ ESP32 + DHT11 🌐 Flask + MQTT 🏗️ Arquitectura

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 VCCESP32 3.3V
  • DHT11 GNDESP32 GND
  • DHT11 DATAESP32 GPIO4
Código ESP32 - Sensor DHT11 con MQTT Copiar

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

Configuración Mosquitto (mosquitto.conf) Copiar

# /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
            
Comandos para iniciar Mosquitto Copiar

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

Servidor Flask Principal (app.py) Copiar

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. 1. Iniciar broker Mosquitto: sudo systemctl start mosquitto
  2. 2. Ejecutar servidor Flask: python app.py
  3. 3. Cargar y ejecutar código en ESP32
  4. 4. Verificar comunicación: curl http://localhost:5000/api/current