Presentación de resultados y discusión de mejoras

Demo del proyecto, análisis de resultados, propuestas de mejora y escalabilidad

Módulo 7 ⏱️ 2 horas 🛠️ ESP32 + DHT11 🌐 Flask + MQTT

Introducción

En esta clase final del curso, realizaremos una demostración completa del proyecto IoT desarrollado, analizaremos los resultados obtenidos y discutiremos propuestas de mejora y escalabilidad. Evaluaremos el rendimiento del sistema ESP32 + DHT11 + Mosquitto + Flask, identificando fortalezas y áreas de oportunidad para futuras implementaciones profesionales.

Los objetivos de esta sesión incluyen:

  • Demostrar el funcionamiento integral del sistema IoT
  • Analizar métricas de rendimiento y confiabilidad
  • Proponer mejoras técnicas y arquitectónicas
  • Evaluar opciones de escalabilidad empresarial

Conceptos Fundamentales

La presentación de resultados en proyectos IoT requiere un enfoque sistemático que abarque múltiples dimensiones:

Métricas de Evaluación IoT

  • Latencia: Tiempo desde captura del sensor hasta visualización en dashboard
  • Throughput: Cantidad de mensajes MQTT procesados por segundo
  • Confiabilidad: Porcentaje de uptime del sistema completo
  • Precisión: Exactitud de las lecturas del sensor DHT11
  • Consumo energético: Eficiencia del ESP32 en diferentes modos

Criterios de Mejora

  • Escalabilidad horizontal: Capacidad de agregar más dispositivos
  • Escalabilidad vertical: Optimización de recursos existentes
  • Tolerancia a fallos: Recuperación automática ante errores
  • Seguridad: Implementación de autenticación y cifrado
  • Mantenibilidad: Facilidad de actualización y monitoreo

Arquitecturas de Escalabilidad

  • Microservicios: Separación de responsabilidades en servicios independientes
  • Load Balancing: Distribución de carga entre múltiples instancias
  • Containerización: Docker para portabilidad y despliegue
  • Cloud Integration: Migración a servicios en la nube

Demo del Sistema Completo

Presentaremos el código optimizado y las métricas obtenidas durante las pruebas del sistema:

ESP32 - Código Final con Métricas


#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h>

#define DHT_PIN 4
#define DHT_TYPE DHT11
#define LED_PIN 2

const char* ssid = "TU_WIFI";
const char* password = "TU_PASSWORD";
const char* mqtt_server = "192.168.1.100";
const int mqtt_port = 1883;

DHT dht(DHT_PIN, DHT_TYPE);
WiFiClient espClient;
PubSubClient client(espClient);

// Variables para métricas
unsigned long lastReading = 0;
unsigned long totalReadings = 0;
unsigned long failedReadings = 0;
float avgTemperature = 0;
float avgHumidity = 0;

void setup() {
    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);
    dht.begin();
    
    setupWiFi();
    client.setServer(mqtt_server, mqtt_port);
    client.setCallback(callback);
    
    Serial.println("Sistema iniciado - Recopilando métricas");
}

void setupWiFi() {
    delay(10);
    Serial.println();
    Serial.print("Conectando a ");
    Serial.println(ssid);
    
    WiFi.begin(ssid, password);
    
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    
    Serial.println("");
    Serial.println("WiFi conectado");
    Serial.println("IP: ");
    Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
    Serial.print("Mensaje recibido [");
    Serial.print(topic);
    Serial.print("] ");
    
    String message;
    for (int i = 0; i < length; i++) {
        message += (char)payload[i];
    }
    Serial.println(message);
    
    // Control remoto del LED para testing
    if (String(topic) == "iot/control/led") {
        if (message == "ON") {
            digitalWrite(LED_PIN, HIGH);
        } else if (message == "OFF") {
            digitalWrite(LED_PIN, LOW);
        }
    }
}

void reconnect() {
    while (!client.connected()) {
        Serial.print("Intentando conexión MQTT...");
        
        if (client.connect("ESP32Client")) {
            Serial.println("conectado");
            client.subscribe("iot/control/led");
        } else {
            Serial.print("falló, rc=");
            Serial.print(client.state());
            Serial.println(" reintentando en 5 segundos");
            delay(5000);
        }
    }
}

void loop() {
    if (!client.connected()) {
        reconnect();
    }
    client.loop();
    
    unsigned long now = millis();
    if (now - lastReading > 10000) { // Cada 10 segundos
        lastReading = now;
        
        float humidity = dht.readHumidity();
        float temperature = dht.readTemperature();
        
        if (isnan(humidity) || isnan(temperature)) {
            Serial.println("Error leyendo DHT11!");
            failedReadings++;
            return;
        }
        
        // Actualizar métricas
        totalReadings++;
        avgTemperature = ((avgTemperature * (totalReadings - 1)) + temperature) / totalReadings;
        avgHumidity = ((avgHumidity * (totalReadings - 1)) + humidity) / totalReadings;
        
        // Crear JSON con datos y métricas
        StaticJsonDocument<200> doc;
        doc["temperature"] = temperature;
        doc["humidity"] = humidity;
        doc["timestamp"] = now;
        doc["total_readings"] = totalReadings;
        doc["failed_readings"] = failedReadings;
        doc["success_rate"] = ((float)(totalReadings - failedReadings) / totalReadings) * 100;
        doc["avg_temp"] = avgTemperature;
        doc["avg_humidity"] = avgHumidity;
        
        char buffer[256];
        serializeJson(doc, buffer);
        
        client.publish("iot/sensor/data", buffer);
        
        // Log local
        Serial.printf("Datos enviados - T:%.1f°C H:%.1f%% | Total:%lu Fallos:%lu Tasa éxito:%.1f%%\n",
                     temperature, humidity, totalReadings, failedReadings,
                     ((float)(totalReadings - failedReadings) / totalReadings) * 100);
        
        // LED indicador
        digitalWrite(LED_PIN, HIGH);
        delay(100);
        digitalWrite(LED_PIN, LOW);
    }
}
            

Flask - Dashboard con Análisis de Rendimiento


from flask import Flask, render_template, jsonify, request
import paho.mqtt.client as mqtt
import json
import sqlite3
import threading
from datetime import datetime, timedelta
import statistics

app = Flask(__name__)

# Variables globales para métricas
sensor_data = []
system_metrics = {
    'total_messages': 0,
    'messages_per_minute': 0,
    'avg_latency': 0,
    'uptime_start': datetime.now(),
    'last_message_time': None,
    'connection_status': 'Disconnected'
}

def init_database():
    """Inicializar base de datos SQLite para almacenar métricas"""
    conn = sqlite3.connect('iot_metrics.db')
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS sensor_readings (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
            temperature REAL,
            humidity REAL,
            total_readings INTEGER,
            failed_readings INTEGER,
            success_rate REAL
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS system_performance (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
            latency_ms INTEGER,
            memory_usage REAL,
            cpu_usage REAL,
            mqtt_messages_count INTEGER
        )
    ''')
    
    conn.commit()
    conn.close()

def on_connect(client, userdata, flags, rc):
    global system_metrics
    if rc == 0:
        print("Conectado al broker MQTT")
        system_metrics['connection_status'] = 'Connected'
        client.subscribe("iot/sensor/data")
        client.subscribe("iot/system/status")
    else:
        print(f"Conexión fallida con código {rc}")
        system_metrics['connection_status'] = 'Failed'

def on_message(client, userdata, msg):
    global sensor_data, system_metrics
    
    try:
        # Registrar tiempo de recepción para latency
        receive_time = datetime.now()
        system_metrics['last_message_time'] = receive_time
        system_metrics['total_messages'] += 1
        
        # Parsear datos del sensor
        data = json.loads(msg.payload.decode())
        
        # Calcular latencia (diferencia entre timestamp del ESP32 y recepción)
        esp_timestamp = data.get('timestamp', 0) / 1000  # Convertir de ms a segundos
        latency = (receive_time.timestamp() - esp_timestamp) * 1000  # Latencia en ms
        system_metrics['avg_latency'] = latency
        
        # Agregar timestamp legible
        data['datetime'] = receive_time.strftime('%Y-%m-%d %H:%M:%S')
        data['latency_ms'] = round(latency, 2)
        
        # Mantener solo los últimos 100 registros en memoria
        sensor_data.append(data)
        if len(sensor_data) > 100:
            sensor_data.pop(0)
        
        # Guardar en base de datos
        save_to_database(data)
        
        print(f"Datos recibidos: T={data['temperature']}°C, H={data['humidity']}%, Latencia={latency:.1f}ms")
        
    except json.JSONDecodeError as e:
        print(f"Error decodificando JSON: {e}")
    except Exception as e:
        print(f"Error procesando mensaje: {e}")

def save_to_database(data):
    """Guardar datos en SQLite"""
    try:
        conn = sqlite3.connect('iot_metrics.db')
        cursor = conn.cursor()
        
        cursor.execute('''
            INSERT INTO sensor_readings 
            (temperature, humidity, total_readings, failed_readings, success_rate)
            VALUES (?, ?, ?, ?, ?)
        ''', (
            data.get('temperature'),
            data.get('humidity'),
            data.get('total_readings', 0),
            data.get('failed_readings', 0),
            data.get('success_rate', 0)
        ))
        
        conn.commit()
        conn.close()
    except Exception as e:
        print(f"Error guardando en BD: {e}")

def calculate_system_metrics():
    """Calcular métricas del sistema cada minuto"""
    global system_metrics
    
    while True:
        import time
        time.sleep(60)  # Cada minuto
        
        # Calcular mensajes por minuto
        current_time = datetime.now()
        one_minute_ago = current_time - timedelta(minutes=1)
        
        recent_messages = [d for d in sensor_data 
                          if datetime.strptime(d['datetime'], '%Y-%m-%d %H:%M:%S') > one_minute_ago]
        
        system_metrics['messages_per_minute'] = len(recent_messages)
        
        # Calcular uptime
        uptime_delta = current_time - system_metrics['uptime_start']
        system_metrics['uptime_hours'] = round(uptime_delta.total_seconds() / 3600, 2)

@app.route('/')
def dashboard():
    return render_template('dashboard.html')

@app.route('/api/data')
def get_data():
    """API para obtener datos actuales del sensor"""
    if sensor_data:
        latest = sensor_data[-1]
        return jsonify({
            'temperature': latest.get('temperature', 0),
            'humidity': latest.get('humidity', 0),
            'timestamp': latest.get('datetime', ''),
            'latency': latest.get('latency_ms', 0),
            'success_rate': latest.get('success_rate', 0)
        })
    return jsonify({'error': 'No hay datos disponibles'})

@app.route('/api/metrics')
def get_metrics():
    """API para obtener métricas del sistema"""
    return jsonify(system_metrics)

@app.route('/api/analytics')
def get_analytics():
    """Análisis estadístico de los datos"""
    if len(sensor_data) < 2:
        return jsonify({'error': 'Datos insuficientes para análisis'})
    
    temperatures = [d['temperature'] for d in sensor_data if 'temperature' in d]
    humidities = [d['humidity'] for d in sensor_data if 'humidity' in d]
    latencies = [d.get('latency_ms', 0) for d in sensor_data]
    
    analytics = {
        'temperature_stats': {
            'min': min(temperatures) if temperatures else 0,
            'max': max(temperatures) if temperatures else 0,
            'avg': round(statistics.mean(temperatures),