Módulo 11

Sistema de riego automático con sensores de humedad

Aplicaciones en Mecatrónica

ESP32 Mecatrónica IoT UNAM

Introducción a la Agricultura Inteligente

Los sistemas de riego automático representan una revolución en la agricultura de precisión, combinando tecnologías IoT, sensores inteligentes y control automatizado para optimizar el uso del agua. En un mundo donde el agua es cada vez más escasa, estos sistemas mecatrónicos no solo mejoran la eficiencia agrícola, sino que contribuyen significativamente a la conservación de recursos hídricos.

El ESP32 se ha convertido en el corazón de muchos sistemas AgTech (Tecnología Agrícola) debido a su capacidad de conectividad dual (Wi-Fi/Bluetooth), múltiples canales ADC para sensores, y su bajo consumo energético. Esta combinación lo hace ideal para aplicaciones agrícolas que requieren monitoreo continuo y control remoto.

Agricultura de Precisión
  • Monitoreo continuo de parámetros del suelo
  • Aplicación optimizada de recursos
  • Reducción del desperdicio de agua hasta 40%
  • Mejora en rendimiento de cultivos
Tecnología de Sensores
  • Sensores capacitivos vs resistivos
  • Medición de humedad volumétrica
  • Compensación de temperatura
  • Calibración específica por tipo de suelo
Beneficios de Automatización
  • Reducción de mano de obra hasta 60%
  • Riego nocturno para minimizar evaporación
  • Respuesta inmediata a condiciones cambiantes
  • Registro histórico de datos para optimización
Integración IoT
  • Monitoreo remoto vía smartphone/web
  • Alertas automáticas por SMS/email
  • Integración con estaciones meteorológicas
  • Análisis predictivo y machine learning

Fundamentos Técnicos del Sistema

Tipos de Sensores de Humedad del Suelo

Los sensores de humedad son el componente crítico en cualquier sistema de riego inteligente. Existen principalmente dos tecnologías:

Sensores Resistivos

Miden la resistencia eléctrica entre dos electrodos. Son económicos pero susceptibles a corrosión y menos precisos en diferentes tipos de suelo.

Sensores Capacitivos

Miden cambios en la capacitancia del suelo. Son más precisos, duraderos y no sufren corrosión, ideales para sistemas permanentes.

Principios de Medición y Calibración

La humedad del suelo se puede expresar de diferentes formas:

  • Contenido Volumétrico de Agua (θv): Volumen de agua / Volumen total de suelo
  • Contenido Gravimétrico (θg): Masa de agua / Masa de suelo seco
  • Potencial Hídrico: Energía necesaria para extraer agua del suelo
Arduino C++ - Calibración de Sensor de Humedad
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

// Pines del sistema
#define MOISTURE_SENSOR_PIN 34
#define RELAY_PIN 26
#define PUMP_ENABLE_PIN 27
#define FLOW_SENSOR_PIN 35

// Calibración del sensor (valores específicos por sensor)
const int AIR_VALUE = 3000;    // Valor en aire seco
const int WATER_VALUE = 1300;  // Valor en agua
const int MOISTURE_THRESHOLD = 30; // Porcentaje mínimo de humedad

// Variables del sistema
float moisturePercentage = 0;
bool pumpActive = false;
unsigned long lastReading = 0;
const unsigned long READING_INTERVAL = 30000; // 30 segundos

// Configuración WiFi y MQTT
const char* ssid = "TU_WIFI";
const char* password = "TU_PASSWORD";
const char* mqtt_server = "tu.broker.mqtt.com";

WiFiClient espClient;
PubSubClient client(espClient);

void setup() {
    Serial.begin(115200);
    
    // Configurar pines
    pinMode(MOISTURE_SENSOR_PIN, INPUT);
    pinMode(RELAY_PIN, OUTPUT);
    pinMode(PUMP_ENABLE_PIN, OUTPUT);
    pinMode(FLOW_SENSOR_PIN, INPUT_PULLUP);
    
    // Estado inicial: bomba apagada
    digitalWrite(RELAY_PIN, LOW);
    digitalWrite(PUMP_ENABLE_PIN, LOW);
    
    // Conectar WiFi
    setupWiFi();
    
    // Configurar MQTT
    client.setServer(mqtt_server, 1883);
    client.setCallback(mqttCallback);
    
    Serial.println("Sistema de riego iniciado");
}

void loop() {
    // Mantener conexión MQTT
    if (!client.connected()) {
        reconnectMQTT();
    }
    client.loop();
    
    // Lectura periódica del sensor
    if (millis() - lastReading >= READING_INTERVAL) {
        readMoistureSensor();
        controlIrrigation();
        sendData();
        lastReading = millis();
    }
    
    delay(100);
}

float readMoistureSensor() {
    // Tomar múltiples lecturas para estabilidad
    int totalReading = 0;
    const int numReadings = 10;
    
    for (int i = 0; i < numReadings; i++) {
        totalReading += analogRead(MOISTURE_SENSOR_PIN);
        delay(50);
    }
    
    int averageReading = totalReading / numReadings;
    
    // Convertir a porcentaje con calibración
    moisturePercentage = map(averageReading, AIR_VALUE, WATER_VALUE, 0, 100);
    moisturePercentage = constrain(moisturePercentage, 0, 100);
    
    Serial.printf("Lectura ADC: %d, Humedad: %.1f%%\n", 
                  averageReading, moisturePercentage);
    
    return moisturePercentage;
}

void controlIrrigation() {
    if (moisturePercentage < MOISTURE_THRESHOLD && !pumpActive) {
        startIrrigation();
    } else if (moisturePercentage > (MOISTURE_THRESHOLD + 10) && pumpActive) {
        stopIrrigation();
    }
}

void startIrrigation() {
    pumpActive = true;
    digitalWrite(RELAY_PIN, HIGH);
    digitalWrite(PUMP_ENABLE_PIN, HIGH);
    
    Serial.println("🌱 Iniciando riego automático");
    
    // Enviar notificación MQTT
    client.publish("irrigation/status", "STARTED");
}

void stopIrrigation() {
    pumpActive = false;
    digitalWrite(RELAY_PIN, LOW);
    digitalWrite(PUMP_ENABLE_PIN, LOW);
    
    Serial.println("🛑 Deteniendo riego - Humedad adecuada");
    
    // Enviar notificación MQTT
    client.publish("irrigation/status", "STOPPED");
}

Control de Bomba y Sistema de Válvulas

El control preciso de actuadores es fundamental para un sistema eficiente:

  • Relés de Estado Sólido: Para control suave y sin chispa
  • Válvulas Solenoides: Respuesta rápida y bajo consumo
  • Sensores de Flujo: Monitoreo de consumo de agua
  • Protección contra Funcionamiento en Seco: Sensor de nivel de tanque
Arduino C++ - Sistema Multi-Zona
// Sistema de riego multi-zona avanzado
#define NUM_ZONES 4
#define FLOW_SENSOR_INTERRUPT 0

struct IrrigationZone {
    int moistureSensorPin;
    int valveRelayPin;
    float moistureLevel;
    bool isActive;
    unsigned long startTime;
    unsigned long duration;
    String name;
};

IrrigationZone zones[NUM_ZONES] = {
    {34, 26, 0, false, 0, 600000, "Tomates"},      // 10 min
    {35, 27, 0, false, 0, 900000, "Lechugas"},     // 15 min
    {32, 28, 0, false, 0, 1200000, "Maíz"},        // 20 min
    {33, 29, 0, false, 0, 450000, "Hierbas"}       // 7.5 min
};

volatile unsigned int flowPulseCount = 0;
float flowRate = 0.0;
unsigned int flowMilliLitres = 0;
unsigned long totalMilliLitres = 0;

void setup() {
    // Configurar zonas
    for (int i = 0; i < NUM_ZONES; i++) {
        pinMode(zones[i].moistureSensorPin, INPUT);
        pinMode(zones[i].valveRelayPin, OUTPUT);
        digitalWrite(zones[i].valveRelayPin, LOW);
    }
    
    // Configurar sensor de flujo
    pinMode(FLOW_SENSOR_PIN, INPUT_PULLUP);
    attachInterrupt(FLOW_SENSOR_INTERRUPT, pulseCounter, FALLING);
}

void pulseCounter() {
    flowPulseCount++;
}

void updateFlowMeasurement() {
    // Cálculo cada segundo
    static unsigned long lastTime = 0;
    
    if (millis() - lastTime >= 1000) {
        detachInterrupt(FLOW_SENSOR_INTERRUPT);
        
        // Calcular flujo (L/min) - Calibración específica del sensor
        flowRate = ((1000.0 / (millis() - lastTime)) * flowPulseCount) / 7.5;
        flowMilliLitres = (flowRate / 60) * 1000;
        totalMilliLitres += flowMilliLitres;
        
        flowPulseCount = 0;
        lastTime = millis();
        
        attachInterrupt(FLOW_SENSOR_INTERRUPT, pulseCounter, FALLING);
    }
}

void manageMultiZoneIrrigation() {
    for (int i = 0; i < NUM_ZONES; i++) {
        // Leer humedad de cada zona
        zones[i].moistureLevel = readZoneMoisture(i);
        
        // Control de riego por zona
        if (zones[i].moistureLevel < MOISTURE_THRESHOLD && !zones[i].isActive) {
            startZoneIrrigation(i);
        }
        
        // Verificar duración máxima
        if (zones[i].isActive && 
            (millis() - zones[i].startTime) > zones[i].duration) {
            stopZoneIrrigation(i);
        }
    }
}

void startZoneIrrigation(int zone) {
    zones[zone].isActive = true;
    zones[zone].startTime = millis();
    digitalWrite(zones[zone].valveRelayPin, HIGH);
    
    Serial.printf("🌱 Iniciando riego en zona %s\n", zones[zone].name.c_str());
    
    // Crear mensaje JSON para MQTT
    DynamicJsonDocument doc(200);
    doc["zone"] = zones[zone].name;
    doc["action"] = "start";
    doc["moisture"] = zones[zone].moistureLevel;
    doc["timestamp"] = millis();
    
    String message;
    serializeJson(doc, message);
    client.publish("irrigation/zone_status", message.c_str());
}

Ejercicios Prácticos Progresivos

1

Medición Básica de Humedad

Principiante 20 min Sensores

Objetivo: Familiarizarse con la lectura y calibración de sensores de humedad capacitivos.

Materiales Necesarios:
  • ESP32 DevKit V1
  • Sensor de humedad capacitivo v1.2
  • Protoboard y cables jumper
  • Multímetro (opcional)
Procedimiento:
  1. Conectar sensor al pin ADC (GPIO 34)
  2. Implementar rutina de calibración
  3. Probar en diferentes tipos de suelo
  4. Registrar valores de referencia
2

Sistema de Riego Temporizador

Intermedio 45 min Automatización

Objetivo: Crear un sistema de riego programable con horarios específicos y duración controlada.

Materiales Adicionales:
  • Módulo RTC DS3231
  • Relé de 5V y válvula solenoide
  • Bomba de agua 12V
  • Fuente de alimentación
Características del Sistema:
  • Programación de múltiples horarios diarios
  • Duración configurable por sesión
  • Interfaz web para configuración
  • Log de eventos en memoria SPIFFS
3

Riego Inteligente Multi-Zona

Avanzado 90 min IoT

Objetivo: Implementar sistema inteligente que gestiona múltiples zonas de cultivo con diferentes requerimientos hídricos.

Funcionalidades Avanzadas:
  • 4 zonas independientes con sensores dedicados
  • Algoritmo de riego basado en tipo de cultivo
  • Integración con datos meteorológicos
  • Predicción de riego con machine learning básico
  • Notificaciones push y alertas por email
4

Plataforma AgTech Completa

Experto 120 min Profesional

Objetivo: Desarrollar una plataforma completa de agricultura de precisión con análisis de datos y optimización automática.

Características Profesionales:
  • Dashboard web responsivo con gráficas en tiempo real
  • Aplicación móvil con React Native
  • Base de datos histórica con análisis trends
  • API REST completa para integración
  • Sistema de alertas inteligente
  • Reportes automáticos PDF

Proyecto Final: Sistema de Agricultura de Precisión

Estación de Monitoreo Agrícola Inteligente

Desarrollaremos un sistema completo de agricultura de precisión que integra múltiples sensores, control automatizado de riego, monitoreo IoT y análisis predictivo para optimizar la producción agrícola.

Especificaciones Técnicas
Hardware Requerido:
  • ESP32 DevKit V1 (controlador principal)
  • 4x Sensores de humedad capacitivos
  • Sensor DHT22 (temperatura/humedad ambiente)
  • Sensor BMP280 (presión atmosférica)
  • Sensor de luz BH1750
  • 4x Relés de estado sólido
  • Válvulas solenoides 12V
  • Bomba de agua centrífuga
  • Sensor de flujo YF-S201
  • Panel solar 20W + batería 12V
Software y Conectividad:
  • Firmware ESP32 con FreeRTOS
  • Comunicación Wi-Fi y MQTT
  • Base de datos InfluxDB
  • Dashboard Grafana
  • API REST con Node.js
  • App móvil React Native
  • Servidor Raspberry Pi 4
  • Integración con OpenWeatherMap
Sistema Completo AgTech
Código Principal del Sistema
Arduino C++ - Sistema AgTech Completo
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <time.h>
#include <DHT.h>
#include <BMP280.h>
#include <BH1750.h>
#include <SPIFFS.h>

// Configuración del sistema
#define DHT_PIN 22
#define DHT_TYPE DHT22
#define NUM_ZONES 4
#define FLOW_SENSOR_PIN 21
#define WATER_LEVEL_PIN 36

// Estructura para cada zona de cultivo
struct CropZone {
    int moistureSensorPin;
    int relayPin;
    float currentMoisture;
    float targetMoisture;
    bool isIrrigating;
    unsigned long irrigationStartTime;
    unsigned long totalIrrigationTime;
    String cropType;
    float waterConsumption;
};

// Configuración de zonas
CropZone zones[NUM_ZONES] = {
    {34, 26, 0, 65, false, 0, 0, "Tomate", 0},
    {35, 27, 0, 55, false, 0, 0, "Lechuga", 0},
    {32, 28, 0, 70, false, 0, 0, "Maíz", 0},
    {33, 29, 0, 60, false, 0, 0, "Hierbas", 0}
};

// Sensores ambientales
DHT dht(DHT_PIN, DHT_TYPE);
BMP280 bmp;
BH1750 lightSensor;

// Variables del sistema
float ambientTemperature = 0;
float ambientHumidity = 0;
float atmosphericPressure = 0;
float lightIntensity = 0;
float waterLevel = 0;
volatile unsigned int flowPulseCount = 0;
float totalWaterUsed = 0;

// Configuración de red y servicios
const char* ssid = "AgTech_Network";
const char* password = "SecurePassword123";
const char* mqtt_server = "192.168.1.100";
const char* weather_api_key = "tu_api_key_openweather";
const char* ntp_server = "pool.ntp.org";

WiFiClient espClient;
PubSubClient client(espClient);
HTTPClient http;

// Variables de tiempo
struct tm timeinfo;
unsigned long lastSensorReading = 0;
unsigned long lastDataTransmission = 0;
unsigned long lastWeatherUpdate = 0;

// Configuración de agricultura de precisión
struct WeatherData {
    float temperature;
    float humidity;
    float pressure;
    float windSpeed;
    float precipitation;
    String description;
};

WeatherData currentWeather;
bool weatherDataAvailable = false;

void setup() {
    Serial.begin(115200);
    
    // Inicializar SPIFFS para almacenamiento
    if (!SPIFFS.begin(true)) {
        Serial.println("Error al montar SPIFFS");
        return;
    }
    
    // Configurar pines
    initializeHardware();
    
    // Inicializar sensores
    initializeSensors();
    
    // Conectar a WiFi y servicios
    connectToWiFi();
    configureTime();
    
    // Configurar MQTT
    client.setServer(mqtt_server, 1883);
    client.setCallback(mqttCallback);
    
    // Configurar interrupciones
    attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), flowPulseCounter, FALLING);
    
    Serial.println("🌱 Sistema AgTech iniciado correctamente");
    logEvent("Sistema iniciado");
}

void loop() {
    // Mantener conexiones activas
    maintainConnections();
    
    // Lecturas de sensores (cada 30 segundos)
    if (millis() - lastSensorReading >= 30000) {
        readAllSensors();
        processIrrigationLogic();
        lastSensorReading = millis();
    }
    
    // Transmisión de datos (cada 5 minutos)
    if (millis() - lastDataTransmission >= 300000) {
        transmitSensorData();
        updatePredictiveModel();
        lastDataTransmission = millis();
    }
    
    // Actualización meteorológica (cada hora)
    if (millis() - lastWeatherUpdate >= 3600000) {
        updateWeatherData();
        lastWeatherUpdate = millis();
    }
    
    // Procesar comandos MQTT
    client.loop();
    
    delay(100);
}

void initializeHardware() {
    // Configurar pines de sensores de humedad
    for (int i = 0; i < NUM_ZONES; i++) {
        pinMode(zones[i].moistureSensorPin, INPUT);
        pinMode(zones[i].relayPin, OUTPUT);
        digitalWrite(zones[i].relayPin, LOW);
    }
    
    // Configurar otros pines
    pinMode(FLOW_SENSOR_PIN, INPUT_PULLUP);
    pinMode(WATER_LEVEL_PIN, INPUT);
    
    Serial.println("Hardware inicializado");
}

void initializeSensors() {
    // Inicializar DHT22
    dht.begin();
    
    // Inicializar BMP280
    if (!bmp.begin()) {
        Serial.println("Error al inicializar BMP280");
    }
    
    // Inicializar sensor de luz
    if (!lightSensor.begin()) {
        Serial.println("Error al inicializar BH1750");
    }
    
    Serial.println("Sensores inicializados");
}

void readAllSensors() {
    // Leer sensores ambientales
    ambientTemperature = dht.readTemperature();
    ambientHumidity = dht.readHumidity();
    atmosphericPressure = bmp.readPressure() / 100.0; // hPa
    lightIntensity = lightSensor.readLightLevel();
    waterLevel = analogRead(WATER_LEVEL_PIN) * (100.0 / 4095.0);
    
    // Leer sensores de humedad de suelo
    for (int i = 0; i < NUM_ZONES; i++) {
        zones[i].currentMoisture = readSoilMoisture(zones[i].moistureSensorPin);
    }
    
    // Calcular flujo de agua
    calculateWaterFlow();
    
    Serial.printf("📊 Lecturas: T=%.1f°C, H=%.1f%%, P=%.1fhPa, L=%.0flux, Agua=%.1f%%\n",
                  ambientTemperature, ambientHumidity, atmosphericPressure, 
                  lightIntensity, waterLevel);
}

float readSoilMoisture(int pin) {
    const int AIR_VALUE = 3000;
    const int WATER_VALUE = 1300;
    
    // Promedio de 10 lecturas para estabilidad
    long totalReading = 0;
    for (int i = 0; i < 10; i++) {
        totalReading += analogRead(pin);
        delay(50);
    }
    
    int averageReading = totalReading / 10;
    float moisturePercent = map(averageReading, AIR_VALUE, WATER_VALUE, 0, 100);
    return constrain(moisturePercent, 0, 100);
}

void processIrrigationLogic() {
    for (int i = 0; i < NUM_ZONES; i++) {
        // Lógica inteligente considerando múltiples factores
        float adjustedTarget = calculateAdjustedMoistureTarget(i);
        
        if (shouldStartIrrigation(i, adjustedTarget)) {
            startZoneIrrigation(i);
        } else if (shouldStopIrrigation(i, adjustedTarget)) {
            stopZoneIrrigation(i);
        }
    }
}

float calculateAdjustedMoistureTarget(int zoneIndex) {
    float baseTarget = zones[zoneIndex].targetMoisture;
    float adjustment = 0;
    
    // Ajustar según temperatura (más calor = más agua)
    if (ambientTemperature > 30) adjustment += 5;
    else if (ambientTemperature > 25) adjustment += 2;
    
    // Ajustar según humedad relativa (menos humedad = más agua)
    if (ambientHumidity < 40) adjustment += 3;
    else if (ambientHumidity < 60) adjustment += 1;
    
    // Ajustar según intensidad lumínica (más luz = más agua)
    if (lightIntensity > 50000) adjustment += 2;
    else if (lightIntensity > 20000) adjustment += 1;
    
    // Considerar pronóstico meteorológico
    if (weatherDataAvailable && currentWeather.precipitation < 0.1) {
        adjustment += 2; // Sin lluvia esperada
    }
    
    return constrain(baseTarget + adjustment, baseTarget - 5, baseTarget + 10);
}

bool shouldStartIrrigation(int zoneIndex, float targetMoisture) {
    CropZone &zone = zones[zoneIndex];
    
    // Verificar condiciones mínimas
    if (zone.isIrrigating) return false;
    if (waterLevel < 20) return false; // Nivel de agua muy bajo
    if (zone.currentMoisture >= (targetMoisture - 2)) return false;
    
    // Verificar horario óptimo (evitar horas de mayor calor)
    if (!getLocalTime(&timeinfo)) return false;
    int hour = timeinfo.tm_hour;
    if (hour >= 10 && hour <= 16) return false; // Evitar 10AM - 4PM
    
    return true;
}

void startZoneIrrigation(int zoneIndex) {
    CropZone &zone = zones[zoneIndex];
    
    zone.isIrrigating = true;
    zone.irrigationStartTime = millis();
    digitalWrite(zone.relayPin, HIGH);
    
    Serial.printf("🌱 Iniciando riego en zona %d (%s) - Humedad: %.1f%%\n", 
                  zoneIndex + 1, zone.cropType.c_str(), zone.currentMoisture);
    
    // Enviar notificación MQTT
    DynamicJsonDocument doc(300);
    doc["zone"] = zoneIndex + 1;
    doc["crop"] = zone.cropType;
    doc["action"] = "start_irrigation";
    doc["moisture_current"] = zone.currentMoisture;
    doc["moisture_target"] = calculateAdjustedMoistureTarget(zoneIndex);
    doc["timestamp"] = millis();
    doc["ambient_temp"] = ambientTemperature;
    doc["ambient_humidity"] = ambientHumidity;
    
    String message;
    serializeJson(doc, message);
    client.publish("agtech/irrigation/status", message.c_str());
    
    logEvent(String("Riego iniciado - Zona ") + String(zoneIndex + 1));
}

Troubleshooting y Diagnóstico

Problemas Comunes de Sensores
Lecturas Inconsistentes

Los sensores de humedad pueden dar lecturas erráticas debido a:

  • Corrosión en sensores resistivos
  • Variaciones de temperatura no compensadas
  • Interferencia electromagnética
  • Calibración incorrecta por tipo de suelo
Solución: Usar sensores capacitivos, implementar filtrado digital y calibración específica.
Deriva de Calibración

La calibración se pierde con el tiempo debido a:

  • Deposición de sales minerales
  • Cambios en la composición del suelo
  • Envejecimiento del sensor
Solución: Rutina de recalibración mensual automática.
Fallos del Sistema de Bombeo
Bomba No Arranca

Posibles causas:

  • Falla en el relé de control
  • Bomba bloqueada por sedimentos
  • Bajo voltaje de alimentación
  • Protección térmica activada
Diagnóstico: Verificar voltaje, continuidad y limpieza del sistema.
Funcionamiento en Seco

Protección crítica del sistema:

  • Sensor de nivel de agua defectuoso
  • Fuga en tuberías de succión
  • Válvula de pie obstruida
Prevención: Sensor de flujo + presión + nivel redundante.
Arduino C++ - Sistema de Diagnóstico
// Sistema de diagnóstico y autodiagnóstico
class SystemDiagnostics {
private:
    struct DiagnosticTest {
        String testName;
        bool (*testFunction)();
        String errorMessage;
        String solution;
    };
    
    static DiagnosticTest tests[];
    static const int numTests = 8;
    
public:
    static void runFullDiagnostic() {
        Serial.println("\n🔍 Iniciando diagnóstico del sistema...");
        bool systemHealthy = true;
        
        for (int i = 0; i < numTests; i++) {
            Serial.printf("Ejecutando: %s... ", tests[i].testName.c_str());
            
            if (tests[i].testFunction()) {
                Serial.println("✅ PASS");
            } else {
                Serial.println("❌ FAIL");
                Serial.printf("   Error: %s\n", tests[i].errorMessage.c_str());
                Serial.printf("   Solución: %s\n", tests[i].solution.c_str());
                systemHealthy = false;
            }
        }
        
        if (systemHealthy) {
            Serial.println("\n✅ Sistema saludable - Todos los tests pasaron");
        } else {
            Serial.println("\n⚠️ Sistema requiere atención - Revisar errores");
        }
    }
    
    // Tests específicos
    static bool testWiFiConnection() {
        return WiFi.status() == WL_CONNECTED;
    }
    
    static bool testMQTTConnection() {
        return client.connected();
    }
    
    static bool testSensorsReading() {
        bool allSensorsOK = true;
        
        // Verificar sensores de humedad
        for (int i = 0; i < NUM_ZONES; i++) {
            int reading = analogRead(zones[i].moistureSensorPin);
            if (reading < 100 || reading > 4000) {
                allSensorsOK = false;
                break;
            }
        }
        
        // Verificar sensor DHT
        float temp = dht.readTemperature();
        float hum = dht.readHumidity();
        if (isnan(temp) || isnan(hum)) {
            allSensorsOK = false;
        }
        
        return allSensorsOK;
    }
    
    static bool testWaterLevel() {
        return waterLevel > 15; // Mínimo 15% de nivel
    }
    
    static bool testRelayFunction() {
        // Test rápido de todos los relés
        bool allRelaysOK = true;
        
        for (int i = 0; i < NUM_ZONES; i++) {
            digitalWrite(zones[i].relayPin, HIGH);
            delay(100);
            // Aquí podrías agregar verificación de feedback del relé
            digitalWrite(zones[i].relayPin, LOW);
        }
        
        return allRelaysOK;
    }
    
    static bool testPowerVoltage() {
        // Leer voltaje de alimentación a través del ADC
        int adcReading = analogRead(39); // Pin específico para voltaje
        float voltage = (adcReading / 4095.0) * 3.3 * 4; // Factor de división
        
        return (voltage > 11.0 && voltage < 14.5); // Rango aceptable para 12V
    }
    
    static bool testFlowSensor() {
        unsigned int initialCount = flowPulseCount;
        delay(5000); // Esperar 5 segundos
        
        return (flowPulseCount > initialCount) || (totalWaterUsed == 0);
    }
    
    static bool testMemoryUsage() {
        return ESP.getFreeHeap() > 50000; // Mínimo 50KB libre
    }
};

// Inicialización de tests
SystemDiagnostics::DiagnosticTest SystemDiagnostics::tests[] = {
    {"Conexión WiFi", testWiFiConnection, "WiFi desconectado", "Verificar SSID y contraseña"},
    {"Conexión MQTT", testMQTTConnection, "Broker MQTT inaccesible", "Verificar servidor y credenciales"},
    {"Lectura de Sensores", testSensorsReading, "Sensores con lecturas anómalas", "Verificar conexiones y calibración"},
    {"Nivel de Agua", testWaterLevel, "Nivel de agua crítico", "Rellenar tanque de agua"},
    {"Función de Relés", testRelayFunction, "Relé no responde", "Verificar conexiones y alimentación"},
    {"Voltaje de Alimentación", testPowerVoltage, "Voltaje fuera de rango", "Verificar fuente de poder"},
    {"Sensor de Flujo", testFlowSensor, "Sensor de flujo sin respuesta", "Verificar instalación del sensor"},
    {"Memoria Disponible", testMemoryUsage, "Memoria insuficiente", "Reiniciar sistema o revisar código"}
};

// Función de mantenimiento preventivo
void performPreventiveMaintenance() {
    static unsigned long lastMaintenance = 0;
    const unsigned long maintenanceInterval = 86400000; // 24 horas
    
    if (millis() - lastMaintenance >= maintenanceInterval) {
        Serial.println("🔧 Iniciando mantenimiento preventivo...");
        
        // Limpiar archivos temporales
        cleanupTempFiles();
        
        // Verificar y limpiar conexiones
        WiFi.disconnect();
        delay(1000);
        connectToWiFi();
        
        // Ejecutar diagnóstico completo
        SystemDiagnostics::runFullDiagnostic();
        
        // Reiniciar contadores
        resetSystemCounters();
        
        lastMaintenance = millis();
        Serial.println("✅ Mantenimiento preventivo completado");
    }
}

void cleanupTempFiles() {
    // Limpiar archivos de log antiguos
    File root = SPIFFS.open("/");
    File file = root.openNextFile();
    
    while (file) {
        String fileName = file.name();
        if (fileName.endsWith(".tmp") || fileName.startsWith("old_")) {
            SPIFFS.remove(fileName);
            Serial.printf("Eliminado: %s\n", fileName.c_str());
        }
        file = root.openNextFile();
    }
}

void resetSystemCounters() {
    totalWaterUsed = 0;
    flowPulseCount = 0;
    
    for (int i = 0; i < NUM_ZONES; i++) {
        zones[i].totalIrrigationTime = 0;
        zones[i].waterConsumption = 0;
    }
}

Criterios de Evaluación

Precisión del Sistema
Criterios de Medición:
  • Exactitud de sensores: ±2% de error
  • Tiempo de respuesta: <30 segundos
  • Repetibilidad: 95% de consistencia
  • Calibración: Deriva <1% por mes
Puntuación:
90%
Excelente precisión alcanzada
Eficiencia Hídrica
Métricas de Eficiencia:
  • Reducción de consumo: Mínimo 30%
  • Uniformidad de riego: >85%
  • Pérdidas por evaporación: <5%
  • Tiempo de saturación: Optimizado
Puntuación:
85%
Muy buena eficiencia
Confiabilidad IoT
Criterios de Conectividad:
  • Uptime del sistema: >99%
  • Latencia de datos: <2 segundos
  • Recuperación automática: Funcional
  • Integridad de datos: 100%
Puntuación:
95%
Excelente confiabilidad
Evaluación Integral del Sistema

El sistema será evaluado considerando no solo la funcionalidad técnica, sino también el impacto en eficiencia agrícola, sostenibilidad ambiental y viabilidad económica. Se considerará la documentación técnica, la calidad del código, la escalabilidad del diseño y la innovación en la solución propuesta.

Referencias y Recursos Adicionales

Publicaciones Científicas Relevantes

1. López, M. et al. (2023). "Smart Irrigation Systems using IoT: A Comprehensive Review". Journal of Agricultural Engineering, 45(2), 123-145.

2. González, R. & Martínez, A. (2022). "Precision Agriculture with ESP32 Microcontrollers: Performance Analysis". Computers and Electronics in Agriculture, 189, 106-118.

3. Silva, P. et al. (2023). "Water Conservation through Smart Irrigation: A Mexican Case Study". Water Resources Management, 37(8), 2891-2905.