Proyecto Final: Sistema IoT Multitarea
Sistema de Monitoreo Industrial en Tiempo Real
Este proyecto integra todos los conceptos del módulo 7 en un sistema industrial completo que utiliza multitasking con FreeRTOS para manejar sensores, comunicación WiFi, y visualización en tiempo real.
El sistema implementa 4 tareas concurrentes con diferentes prioridades, comunicación mediante colas y semáforos, y un servidor web embebido para monitoreo remoto.
🎯 Objetivos de Aprendizaje:
- ✅ Integrar multitasking avanzado
- ✅ Implementar comunicación IoT
- ✅ Gestionar prioridades en tiempo real
- ✅ Desarrollar interfaz web embebida
🏭 Aplicaciones Industriales:
- 🏭 Automatización de procesos
- 📊 Monitoreo de calidad
- 🔧 Mantenimiento predictivo
- 📡 Telemetría en tiempo real
Arquitectura del Sistema Multitarea
Flujo de Datos en Tiempo Real
Sensores
Temp, Humedad, PresiónESP32
Procesamiento Dual-CoreWiFi
Comunicación IoTDashboard
Visualización WebTarea Sensores
Prioridad: 4 (Alta)
Función: Lectura continua de sensores DHT22 y BMP280
Frecuencia: 1Hz con filtrado digital
Núcleo: Core 0
Tarea WiFi
Prioridad: 3 (Media-Alta)
Función: Servidor web y cliente HTTP
Protocolo: REST API + WebSocket
Núcleo: Core 1
Tarea Display
Prioridad: 2 (Normal)
Función: Actualización OLED local
Interfaz: I2C con mutex protection
Núcleo: Core 0
Implementación Completa del Sistema
El siguiente código implementa un sistema completo que integra todos los conceptos del módulo:
#include
#include
#include
#include
#include
#include
#include
// Configuración de red WiFi
const char* ssid = "TU_RED_WIFI";
const char* password = "TU_PASSWORD";
// Configuración de sensores
#define DHT_PIN 4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);
// Display OLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Sensores I2C
Adafruit_BMP280 bmp;
// Servidor web
WebServer server(80);
// Variables globales del sistema
struct SensorData {
float temperature;
float humidity;
float pressure;
unsigned long timestamp;
bool valid;
};
// Colas para comunicación entre tareas
QueueHandle_t sensorQueue;
QueueHandle_t displayQueue;
QueueHandle_t wifiQueue;
// Semáforos para sincronización
SemaphoreHandle_t i2cMutex;
SemaphoreHandle_t dataMutex;
SemaphoreHandle_t wifiSemaphore;
// Handles de tareas
TaskHandle_t sensorTaskHandle = NULL;
TaskHandle_t wifiTaskHandle = NULL;
TaskHandle_t displayTaskHandle = NULL;
TaskHandle_t monitorTaskHandle = NULL;
// Variables de estado del sistema
volatile bool systemRunning = true;
volatile bool wifiConnected = false;
SensorData latestData = {0, 0, 0, 0, false};
// TAREA SENSORES (Prioridad 4) - Core 0
void sensorTask(void *pvParameters) {
Serial.println("[SENSOR] Iniciando tarea de sensores...");
// Inicializar sensores
dht.begin();
if (!bmp.begin(0x76)) {
Serial.println("[SENSOR] Error: No se pudo inicializar BMP280");
vTaskDelete(NULL);
}
// Configurar BMP280
bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500);
SensorData data;
TickType_t lastWakeTime = xTaskGetTickCount();
for (;;) {
// Leer sensores con timeout
data.timestamp = millis();
data.valid = true;
// Leer DHT22 (temperatura y humedad)
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
if (isnan(data.temperature) || isnan(data.humidity)) {
Serial.println("[SENSOR] Warning: Error leyendo DHT22");
data.valid = false;
}
// Leer BMP280 (presión) con mutex I2C
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(100)) == pdPASS) {
data.pressure = bmp.readPressure() / 100.0F; // hPa
xSemaphoreGive(i2cMutex);
} else {
Serial.println("[SENSOR] Warning: Timeout I2C mutex");
data.valid = false;
}
if (data.valid) {
Serial.printf("[SENSOR] T:%.1f°C H:%.1f%% P:%.1fhPa\n",
data.temperature, data.humidity, data.pressure);
// Actualizar datos globales con mutex
if (xSemaphoreTake(dataMutex, pdMS_TO_TICKS(50)) == pdPASS) {
latestData = data;
xSemaphoreGive(dataMutex);
}
// Enviar a colas (no bloqueante)
xQueueSend(displayQueue, &data, 0);
xQueueSend(wifiQueue, &data, 0);
}
// Dormir hasta próxima lectura (1Hz)
vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(1000));
}
}
// TAREA WiFi (Prioridad 3) - Core 1
void wifiTask(void *pvParameters) {
Serial.println("[WIFI] Iniciando tarea WiFi...");
// Conectar a WiFi
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
vTaskDelay(pdMS_TO_TICKS(500));
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
Serial.printf("\n[WIFI] Conectado a %s\n", ssid);
Serial.printf("[WIFI] IP: %s\n", WiFi.localIP().toString().c_str());
// Configurar rutas del servidor web
setupWebServer();
server.begin();
// Señalizar que WiFi está listo
xSemaphoreGive(wifiSemaphore);
} else {
Serial.println("\n[WIFI] Error: No se pudo conectar");
wifiConnected = false;
}
SensorData data;
for (;;) {
if (wifiConnected) {
// Manejar clientes web
server.handleClient();
// Procesar datos de sensores
if (xQueueReceive(wifiQueue, &data, pdMS_TO_TICKS(100)) == pdPASS) {
// Aquí se podrían enviar datos a servicios cloud
// Por ejemplo: ThingSpeak, AWS IoT, etc.
Serial.printf("[WIFI] Datos listos para transmisión web\n");
}
// Verificar conexión WiFi
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WIFI] Conexión perdida, reintentando...");
wifiConnected = false;
WiFi.reconnect();
}
} else {
// Reintentar conexión
vTaskDelay(pdMS_TO_TICKS(5000));
WiFi.begin(ssid, password);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// TAREA DISPLAY (Prioridad 2) - Core 0
void displayTask(void *pvParameters) {
Serial.println("[DISPLAY] Iniciando tarea de display...");
// Inicializar display con mutex I2C
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("[DISPLAY] Error: No se pudo inicializar OLED");
xSemaphoreGive(i2cMutex);
vTaskDelete(NULL);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("Sistema IoT");
display.println("Iniciando...");
display.display();
xSemaphoreGive(i2cMutex);
}
SensorData data;
unsigned long lastUpdate = 0;
for (;;) {
if (xQueueReceive(displayQueue, &data, pdMS_TO_TICKS(500)) == pdPASS) {
// Actualizar display con mutex I2C
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(100)) == pdPASS) {
display.clearDisplay();
display.setCursor(0, 0);
// Título
display.setTextSize(1);
display.println("=== IoT Monitor ===");
// Datos de sensores
display.printf("Temp: %.1f C\n", data.temperature);
display.printf("Hum: %.1f %%\n", data.humidity);
display.printf("Pres: %.0f hPa\n", data.pressure);
// Estado del sistema
display.println("---Status---");
display.printf("WiFi: %s\n", wifiConnected ? "OK" : "X");
display.printf("Up: %lu s\n", millis() / 1000);
display.display();
xSemaphoreGive(i2cMutex);
lastUpdate = millis();
}
} else {
// Timeout - mostrar mensaje de estado
if (millis() - lastUpdate > 10000) {
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(100)) == pdPASS) {
display.clearDisplay();
display.setCursor(0, 0);
display.println("Sistema activo");
display.printf("Sin datos: %lu s\n", (millis() - lastUpdate) / 1000);
display.display();
xSemaphoreGive(i2cMutex);
}
}
}
vTaskDelay(pdMS_TO_TICKS(200));
}
}
// TAREA MONITOR (Prioridad 1) - Core 0
void monitorTask(void *pvParameters) {
Serial.println("[MONITOR] Iniciando tarea de monitoreo...");
for (;;) {
Serial.println("\n=== ESTADO DEL SISTEMA ===");
Serial.printf("Heap libre: %d bytes\n", ESP.getFreeHeap());
Serial.printf("Tiempo activo: %lu segundos\n", millis() / 1000);
// Estado de las colas
Serial.printf("Cola sensores: %d/%d\n",
uxQueueMessagesWaiting(sensorQueue),
uxQueueSpacesAvailable(sensorQueue) + uxQueueMessagesWaiting(sensorQueue));
Serial.printf("Cola display: %d/%d\n",
uxQueueMessagesWaiting(displayQueue),
uxQueueSpacesAvailable(displayQueue) + uxQueueMessagesWaiting(displayQueue));
Serial.printf("Cola WiFi: %d/%d\n",
uxQueueMessagesWaiting(wifiQueue),
uxQueueSpacesAvailable(wifiQueue) + uxQueueMessagesWaiting(wifiQueue));
// Stack de tareas
if (sensorTaskHandle) {
Serial.printf("Sensor stack: %d bytes libres\n",
uxTaskGetStackHighWaterMark(sensorTaskHandle) * sizeof(StackType_t));
}
if (wifiTaskHandle) {
Serial.printf("WiFi stack: %d bytes libres\n",
uxTaskGetStackHighWaterMark(wifiTaskHandle) * sizeof(StackType_t));
}
// Datos actuales
if (xSemaphoreTake(dataMutex, pdMS_TO_TICKS(100)) == pdPASS) {
if (latestData.valid) {
Serial.printf("Última lectura: %.1f°C, %.1f%%, %.1fhPa\n",
latestData.temperature, latestData.humidity, latestData.pressure);
}
xSemaphoreGive(dataMutex);
}
Serial.println("==========================\n");
vTaskDelay(pdMS_TO_TICKS(15000)); // Reporte cada 15 segundos
}
}
// Configuración del servidor web
void setupWebServer() {
// Página principal
server.on("/", HTTP_GET, []() {
String html = R"(
Sistema IoT ESP32
🏭 Sistema IoT ESP32
Sistema en línea
🌡️ Temperatura
-- °C
💧 Humedad
-- %
🌬️ Presión
-- hPa
Última actualización: --
)";
server.send(200, "text/html", html);
});
// API para datos JSON
server.on("/api/data", HTTP_GET, []() {
DynamicJsonDocument doc(1024);
if (xSemaphoreTake(dataMutex, pdMS_TO_TICKS(100)) == pdPASS) {
doc["temperature"] = latestData.temperature;
doc["humidity"] = latestData.humidity;
doc["pressure"] = latestData.pressure;
doc["timestamp"] = latestData.timestamp;
doc["valid"] = latestData.valid;
xSemaphoreGive(dataMutex);
} else {
doc["error"] = "Timeout accediendo a datos";
}
doc["uptime"] = millis();
doc["heap"] = ESP.getFreeHeap();
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
});
server.on("/api/status", HTTP_GET, []() {
DynamicJsonDocument doc(512);
doc["wifi"] = wifiConnected;
doc["ip"] = WiFi.localIP().toString();
doc["ssid"] = WiFi.SSID();
doc["rssi"] = WiFi.RSSI();
doc["uptime"] = millis();
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
});
}
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("\n=== SISTEMA IoT MULTITAREA ESP32 ===");
Serial.println("Módulo 7 - Proyecto Final");
Serial.println("======================================\n");
// Inicializar I2C
Wire.begin();
// Crear semáforos
i2cMutex = xSemaphoreCreateMutex();
dataMutex = xSemaphoreCreateMutex();
wifiSemaphore = xSemaphoreCreateBinary();
if (i2cMutex == NULL || dataMutex == NULL || wifiSemaphore == NULL) {
Serial.println("ERROR: No se pudieron crear los semáforos");
return;
}
// Crear colas
sensorQueue = xQueueCreate(5, sizeof(SensorData));
displayQueue = xQueueCreate(3, sizeof(SensorData));
wifiQueue = xQueueCreate(10, sizeof(SensorData));
if (sensorQueue == NULL || displayQueue == NULL || wifiQueue == NULL) {
Serial.println("ERROR: No se pudieron crear las colas");
return;
}
Serial.println("✅ Semáforos y colas creados exitosamente");
// Crear tareas con prioridades específicas
xTaskCreatePinnedToCore(sensorTask, "SensorTask", 4096, NULL, 4, &sensorTaskHandle, 0);
xTaskCreatePinnedToCore(wifiTask, "WiFiTask", 8192, NULL, 3, &wifiTaskHandle, 1);
xTaskCreatePinnedToCore(displayTask, "DisplayTask", 3072, NULL, 2, &displayTaskHandle, 0);
xTaskCreatePinnedToCore(monitorTask, "MonitorTask", 3072, NULL, 1, &monitorTaskHandle, 0);
Serial.println("🚀 Todas las tareas creadas exitosamente");
Serial.println("📊 Monitoreo disponible en: http://" + WiFi.localIP().toString());
Serial.println("🔧 Sistema iniciado - verificar monitor serial para detalles\n");
}
void loop() {
// Loop principal vacío - todo controlado por tareas FreeRTOS
vTaskDelete(NULL);
}
Guía de Implementación Paso a Paso
Proceso de Desarrollo
Preparación del Hardware
Conexiones:
- DHT22: Pin 4 (con resistencia pull-up 4.7kΩ)
- BMP280: I2C (SDA=21, SCL=22)
- OLED: I2C (SDA=21, SCL=22, dirección 0x3C)
- LED indicador: Pin 2
Verificar: Continuidad de conexiones y alimentación 3.3V
Instalación de Librerías
Librerías requeridas (Arduino IDE):
- DHT sensor library by Adafruit
- Adafruit BMP280 Library
- Adafruit SSD1306
- ArduinoJson (v6.x)
- ESP32 WebServer
Configuración WiFi
Editar credenciales:
- Cambiar
ssid
por el nombre de tu red WiFi - Cambiar
password
por la contraseña de tu red - Verificar que tu router permite conexiones ESP32
Compilación y Upload
Configuración de compilación:
- Board: ESP32 Dev Module
- CPU Frequency: 240MHz
- Flash Size: 4MB
- Partition Scheme: Default 4MB
Verificación y Testing
Comprobar funcionamiento:
- Monitor Serial: Datos de sensores cada segundo
- Display OLED: Información local actualizada
- Página Web: Acceder vía IP mostrada en Serial
- API REST: Probar endpoint
/api/data
Testing y Métricas de Rendimiento
Parámetros de Rendimiento Esperados
🧪 Pruebas de Validación:
- Stress Test: Funcionamiento continuo 24h
- Network Test: Pérdida/reconexión WiFi
- Sensor Test: Desconexión/reconexión sensores
- Memory Test: Monitoring de memory leaks
- Priority Test: Comportamiento bajo carga
- Recovery Test: Reinicio automático en errores
Extensiones y Mejoras Propuestas
🎯 Mejoras de Funcionalidad
- Almacenamiento SD: Logging histórico de datos
- OTA Updates: Actualización remota del firmware
- MQTT Client: Integración con brokers IoT
- Alertas SMS/Email: Notificaciones críticas
- Dashboard avanzado: Gráficos en tiempo real
- Control remoto: Actuadores via web
⚡ Optimizaciones Avanzadas
- Deep Sleep: Ahorro energético inteligente
- Watchdog: Reset automático en cuelgues
- Calibración: Auto-calibración de sensores
- Compresión: Reducir tráfico de red
- Caching: Optimización de respuesta web
- Security: HTTPS y autenticación
Desafío para Estudiantes
Implementa 2 mejoras adicionales de tu elección y documenta el impacto en el rendimiento del sistema. Considera aspectos como consumo de memoria, latencia, y estabilidad.