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),