Introducción
En esta clase aprenderemos los fundamentos de Flask, un framework web minimalista y poderoso de Python que utilizaremos para crear la interfaz web de nuestro sistema IoT. Flask nos permitirá recibir datos del ESP32 a través de MQTT y mostrarlos en una aplicación web interactiva.
Al final de esta sesión, tendrás una aplicación Flask funcional que podrá conectarse al broker Mosquitto MQTT y mostrar los datos de temperatura y humedad del sensor DHT11 en tiempo real a través de una interfaz web profesional.
Conceptos Fundamentales
¿Qué es Flask?
Flask es un microframework web de Python basado en Werkzeug y Jinja2. Se caracteriza por su simplicidad y flexibilidad, permitiendo crear aplicaciones web desde prototipos simples hasta aplicaciones complejas.
Características principales de Flask:
- Minimalista: Núcleo simple con extensiones opcionales
- Flexible: No impone una estructura específica
- Werkzeug: Biblioteca WSGI para manejo de requests/responses
- Jinja2: Motor de templates potente y expresivo
- Debugger integrado: Facilita el desarrollo y debugging
Arquitectura MVC en Flask
Aunque Flask no impone MVC estrictamente, podemos organizarlo siguiendo este patrón:
- Modelo (Model): Clases de datos y lógica de negocio
- Vista (View): Templates HTML con Jinja2
- Controlador (Controller): Funciones de rutas que manejan requests
Estructura básica de una aplicación Flask
Estructura mínima:
mi_app/
├── app.py
└── templates/
└── index.html
Estructura escalable:
iot_dashboard/
├── app.py
├── config.py
├── requirements.txt
├── static/
│ ├── css/
│ ├── js/
│ └── images/
└── templates/
├── base.html
├── dashboard.html
└── sensor_data.html
Sistema de Rutas en Flask
Las rutas definen qué función se ejecuta cuando se accede a una URL específica:
- Decorador @app.route(): Define la URL y métodos HTTP
- Parámetros dinámicos: Capturan valores de la URL
- Métodos HTTP: GET, POST, PUT, DELETE
Templates con Jinja2
Jinja2 es el motor de templates que permite generar HTML dinámico:
- Variables: {{ variable }}
- Control de flujo: {% if %}, {% for %}
- Herencia: {% extends %}, {% block %}
- Filtros: {{ variable|filtro }}
Implementación Práctica
Paso 1: Configuración del entorno
Primero, instalemos las dependencias necesarias para nuestro proyecto IoT:
requirements.txt
Flask==2.3.3
paho-mqtt==1.6.1
python-dotenv==1.0.0
Werkzeug==2.3.7
Instalación:
# Crear entorno virtual
python -m venv venv
source venv/bin/activate # En Windows: venv\Scripts\activate
# Instalar dependencias
pip install -r requirements.txt
Paso 2: Aplicación Flask básica
app.py - Aplicación principal
from flask import Flask, render_template, jsonify, request
import paho.mqtt.client as mqtt
import json
import threading
from datetime import datetime
import os
# Inicializar Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'tu_clave_secreta_aqui'
# Variables globales para almacenar datos del sensor
sensor_data = {
'temperature': 0.0,
'humidity': 0.0,
'timestamp': '',
'status': 'Desconectado'
}
# Configuración MQTT
MQTT_BROKER = 'localhost'
MQTT_PORT = 1883
MQTT_TOPIC_TEMP = 'iot/dht11/temperature'
MQTT_TOPIC_HUM = 'iot/dht11/humidity'
MQTT_CLIENT_ID = 'flask_dashboard'
# Cliente MQTT
mqtt_client = mqtt.Client(client_id=MQTT_CLIENT_ID)
# Callbacks MQTT
def on_connect(client, userdata, flags, rc):
"""Callback cuando se conecta al broker MQTT"""
if rc == 0:
print("Conectado al broker MQTT")
sensor_data['status'] = 'Conectado'
# Suscribirse a los topics
client.subscribe(MQTT_TOPIC_TEMP)
client.subscribe(MQTT_TOPIC_HUM)
print(f"Suscrito a {MQTT_TOPIC_TEMP} y {MQTT_TOPIC_HUM}")
else:
print(f"Error de conexión MQTT: {rc}")
sensor_data['status'] = 'Error de conexión'
def on_message(client, userdata, msg):
"""Callback cuando se recibe un mensaje MQTT"""
try:
topic = msg.topic
payload = msg.payload.decode('utf-8')
print(f"Mensaje recibido - Topic: {topic}, Payload: {payload}")
# Actualizar datos según el topic
if topic == MQTT_TOPIC_TEMP:
sensor_data['temperature'] = float(payload)
elif topic == MQTT_TOPIC_HUM:
sensor_data['humidity'] = float(payload)
# Actualizar timestamp
sensor_data['timestamp'] = datetime.now().strftime('%H:%M:%S')
sensor_data['status'] = 'Datos recibidos'
except Exception as e:
print(f"Error procesando mensaje MQTT: {e}")
def on_disconnect(client, userdata, rc):
"""Callback cuando se desconecta del broker"""
print("Desconectado del broker MQTT")
sensor_data['status'] = 'Desconectado'
# Configurar callbacks
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message
mqtt_client.on_disconnect = on_disconnect
# Función para conectar MQTT en hilo separado
def connect_mqtt():
"""Conectar al broker MQTT"""
try:
mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
mqtt_client.loop_forever()
except Exception as e:
print(f"Error conectando a MQTT: {e}")
sensor_data['status'] = 'Error de conexión'
# RUTAS DE LA APLICACIÓN
@app.route('/')
def index():
"""Ruta principal - Dashboard"""
return render_template('dashboard.html',
title='Dashboard IoT',
sensor_data=sensor_data)
@app.route('/api/sensor_data')
def api_sensor_data():
"""API REST para obtener datos del sensor"""
return jsonify(sensor_data)
@app.route('/api/sensor_data', methods=['POST'])
def api_update_sensor():
"""API para actualizar datos manualmente (testing)"""
try:
data = request.get_json()
if 'temperature' in data:
sensor_data['temperature'] = float(data['temperature'])
if 'humidity' in data:
sensor_data['humidity'] = float(data['humidity'])
sensor_data['timestamp'] = datetime.now().strftime('%H:%M:%S')
return jsonify({'status': 'success', 'data': sensor_data})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@app.route('/about')
def about():
"""Página de información"""
return render_template('about.html', title='Acerca del Sistema')
if __name__ == '__main__':
# Iniciar cliente MQTT en hilo separado
mqtt_thread = threading.Thread(target=connect_mqtt)
mqtt_thread.daemon = True
mqtt_thread.start()
# Iniciar aplicación Flask
app.run(debug=True, host='0.0.0.0', port=5000, use_reloader=False)
Paso 3: Templates HTML
templates/base.html - Template base
<!DOCTYPE html>