Introducción a Flask y estructura básica de una aplicación

Framework Flask, estructura MVC, rutas básicas, templates Jinja2

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

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.

Objetivo del proyecto: Crear una aplicación web que reciba datos del sensor DHT11 conectado al ESP32, los procese a través de MQTT y los visualice en tiempo real.

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>