//código sistema riego automatico ultimaversion //viernes 5 diciembre (código presentado ) #include #include #include #include #include #include #include // =============================== // 1. DATOS DE CONEXIÓN // =============================== const char* ssid = "POCO X3 NFC"; // Nombre del WiFi const char* password = "0102030405"; // Contraseña String botToken = "8493075062:AAG9uxldNYa0mhJ79umQZ5vIYuwLCV5V3UQ"; // Token de BotFather String chatId = "1584367913"; // Tu ID numérico // =============================== // 2. PINES DEL ESP32 // =============================== #define PIN_BOTON 4 #define PIN_RELE 26 #define PIN_SUELO 34 #define PIN_TRIG 5 #define PIN_ECHO 18 // =============================== // 3. CONFIGURACIÓN Y CALIBRACIÓN // =============================== const int HORA_NOCHE_INICIO = 19; // 7:00 pm const int HORA_NOCHE_FIN = 6; // 6:00 am const int TIEMPO_RIEGO_SEG = 5; // Tiempo máximo por ciclo de riego (segundos) // ---- CALIBRACIÓN SUELO ---- const int UMBRAL_SECO_RAW = 2000; // Valor crudo para disparar riego (suelo seco) const int LECTURA_SECO = 2500; // Valor al aire (0%) const int LECTURA_MOJADO = 1200; // Valor en agua (100%) // ---- CALIBRACIÓN TANQUE ---- // El tanque mide ~9 cm de alto const int PROFUNDIDAD_TANQUE_VACIO = 9; // cm (tanque vacío ≈ 0%) const int DISTANCIA_LLENO = 2; // cm (tanque lleno ≈ 100%) // Mínimo seguro: ~3 cm de 9 cm ≈ 33% // NO riega si está por debajo o igual a este porcentaje const int UMBRAL_TANQUE_SEGURO_PCT = 35; // 35% para dejar margen de seguridad // OBJETOS RTC_DS3231 rtc; LiquidCrystal_I2C lcd(0x27, 16, 2); WiFiClientSecure client; UniversalTelegramBot bot(botToken, client); // VARIABLES INTERNAS bool bombaEncendida = false; unsigned long tiempoInicioRiego = 0; unsigned long ultimoCheckAuto = 0; unsigned long ultimaActualizacionLCD = 0; unsigned long ultimoCheckTelegram = 0; int intervaloVerificacionAuto = 5000; // cada 5s revisa modo automático int retrasoTelegram = 1000; // cada 1s revisa Telegram // Para errores en automático (no spamear): // 8 horas = 8 * 60 * 60 * 1000 = 28,800,000 ms unsigned long ultimoErrorAuto = 0; const unsigned long INTERVALO_ERROR_AUTO_MS = 28800000UL; // 8 horas // =============================== // SETUP // =============================== void setup() { Serial.begin(115200); // Configurar Pines pinMode(PIN_BOTON, INPUT_PULLUP); pinMode(PIN_RELE, OUTPUT); pinMode(PIN_TRIG, OUTPUT); pinMode(PIN_ECHO, INPUT); digitalWrite(PIN_RELE, HIGH); // Apagado inicial (asumiendo rele activo en LOW) // Iniciar LCD Wire.begin(21, 22); lcd.begin(16, 2); lcd.backlight(); lcd.clear(); delay(100); lcd.init(); lcd.print("Conectando WiFi"); // Conectar WiFi WiFi.begin(ssid, password); client.setCACert(TELEGRAM_CERTIFICATE_ROOT); int i = 0; while (WiFi.status() != WL_CONNECTED && i < 20) { delay(500); Serial.print("."); i++; } // Iniciar Reloj if (!rtc.begin()) { Serial.println("Error RTC"); } rtc.adjust(DateTime(__DATE__, __TIME__)); // Sincroniza hora al compilar lcd.clear(); lcd.print("SISTEMA ONLINE"); Serial.println("\n--- SISTEMA ONLINE Y LISTO ---"); Serial.println("Prueba escribiendo /regar en el Monitor Serie o Telegram"); // Mensaje de bienvenida al Bot bot.sendMessage(chatId, "🤖 Sistema de Riego Iniciado y Conectado", ""); } // =============================== // LOOP PRINCIPAL // =============================== void loop() { // 1. SEGURIDAD TIEMPO (Apagado de emergencia) if (bombaEncendida) { if (millis() - tiempoInicioRiego >= (TIEMPO_RIEGO_SEG * 1000UL)) { apagarRiego("Tiempo cumplido"); } } // 2. BOTÓN FÍSICO if (digitalRead(PIN_BOTON) == LOW) { intentarRiegoManual("Boton Fisico"); while (digitalRead(PIN_BOTON) == LOW); // espera a soltar delay(500); lcd.clear(); ultimaActualizacionLCD = 0; } // 3. MODO AUTOMÁTICO if (millis() - ultimoCheckAuto > (unsigned long)intervaloVerificacionAuto) { verificarRiegoAutomatico(); ultimoCheckAuto = millis(); } // 4. LEER TELEGRAM (Comandos remotos) if (millis() - ultimoCheckTelegram > (unsigned long)retrasoTelegram) { int numNewMessages = bot.getUpdates(bot.last_message_received + 1); while (numNewMessages) { handleNewMessages(numNewMessages); numNewMessages = bot.getUpdates(bot.last_message_received + 1); } ultimoCheckTelegram = millis(); } // 5. LEER MONITOR SERIE (Comandos PC) if (Serial.available()) { String comando = Serial.readStringUntil('\n'); comando.trim(); if (comando == "/regar") intentarRiegoManual("Monitor PC"); else if (comando == "/parar") apagarRiego("Monitor PC"); else if (comando == "/estado") imprimirEstadoSerie(); } // 6. ACTUALIZAR PANTALLA (Solo si no está regando para evitar ruido) if (!bombaEncendida && millis() - ultimaActualizacionLCD > 2000UL) { actualizarPantallaStandby(); ultimaActualizacionLCD = millis(); } } // =============================== // GESTIÓN DE COMANDOS TELEGRAM // =============================== void handleNewMessages(int numNewMessages) { for (int i = 0; i < numNewMessages; i++) { String chat_id = String(bot.messages[i].chat_id); String text = bot.messages[i].text; // Verificar ID por seguridad if (chat_id != chatId) continue; Serial.println("Comando Telegram recibido: " + text); if (text == "/regar") { intentarRiegoManual("Telegram"); } else if (text == "/parar") { apagarRiego("Orden Telegram"); } else if (text == "/estado") { enviarEstadoTelegram(); } else { bot.sendMessage(chatId, "Comandos: /regar - /parar - /estado", ""); } } } void enviarEstadoTelegram() { int sRaw = analogRead(PIN_SUELO); int tCm = obtenerDistancia(); int pSuelo = calcularPorcentajeSuelo(sRaw); int pTanque = calcularPorcentajeTanque(tCm); bool noche = esDeNoche(); bool aguaSuficiente = (pTanque > UMBRAL_TANQUE_SEGURO_PCT); String msg = "📊 *ESTADO ACTUAL*\n"; msg += "🌱 Suelo: " + String(pSuelo) + "% (" + (esSueloSeco() ? "Seco" : "Humedo") + ")\n"; msg += "🪣 Tanque: " + String(pTanque) + "% (" + (aguaSuficiente ? "Suficiente" : "Bajo") + ")\n"; msg += "🔻 Minimo seguro tanque: " + String(UMBRAL_TANQUE_SEGURO_PCT) + "%\n"; msg += "☀️ Modo: " + String(noche ? "Noche" : "Dia"); bot.sendMessage(chatId, msg, "Markdown"); } // =============================== // LÓGICA PRINCIPAL DE RIEGO // =============================== void verificarRiegoAutomatico() { bool noche = esDeNoche(); bool seco = esSueloSeco(); int dist = obtenerDistancia(); int pTanque = calcularPorcentajeTanque(dist); bool aguaSuficiente = (pTanque > UMBRAL_TANQUE_SEGURO_PCT); // Si se cumplen todas → riega automático if (noche && aguaSuficiente && seco && !bombaEncendida) { String msg = "🌙 Riego AUTOMATICO iniciado.\n"; msg += "🪣 Tanque: " + String(pTanque) + "% (>= "; msg += String(UMBRAL_TANQUE_SEGURO_PCT) + "%)\n"; bot.sendMessage(chatId, msg, ""); encenderRiego("Automatico"); return; } // Si el suelo está seco pero NO se puede regar automáticamente → mandar mensaje de error if (seco && !bombaEncendida) { unsigned long ahora = millis(); if (ahora - ultimoErrorAuto > INTERVALO_ERROR_AUTO_MS) { String msg = "⚠️ No se puede regar en modo AUTOMATICO.\n"; if (!noche) { msg += "⏰ Fuera de horario de riego ("; msg += String(HORA_NOCHE_INICIO); msg += ":00 - "; msg += String(HORA_NOCHE_FIN); msg += ":00).\n"; } if (!aguaSuficiente) { msg += "🪣 Nivel tanque bajo: " + String(pTanque) + "% (< "; msg += String(UMBRAL_TANQUE_SEGURO_PCT); msg += "% minimo seguro).\n"; msg += "Se evita que la bomba trabaje en seco.\n"; } // Caso raro: seco, noche y agua suficiente, pero no regó if (noche && aguaSuficiente) { msg += "Condiciones casi OK, revisar sensores o logica.\n"; } bot.sendMessage(chatId, msg, ""); ultimoErrorAuto = ahora; } } } void intentarRiegoManual(String fuente) { int dist = obtenerDistancia(); int pTanque = calcularPorcentajeTanque(dist); bool aguaSuficiente = (pTanque > UMBRAL_TANQUE_SEGURO_PCT); bool seco = esSueloSeco(); if (aguaSuficiente && seco) { String msg = "✅ Riego iniciado via: " + fuente + "\n"; msg += "🪣 Tanque: " + String(pTanque) + "% (>= "; msg += String(UMBRAL_TANQUE_SEGURO_PCT) + "%)\n"; bot.sendMessage(chatId, msg, ""); encenderRiego(fuente); } else { String error; if (!aguaSuficiente) { error = "Nivel tanque bajo"; } else { error = "Suelo humedo"; } String mensajeError = "⚠️ No se pudo regar (" + fuente + "): " + error + "\n"; if (!aguaSuficiente) { mensajeError += "🪣 Tanque: " + String(pTanque) + "% (< "; mensajeError += String(UMBRAL_TANQUE_SEGURO_PCT) + "% minimo seguro)\n"; mensajeError += "Se evita que la bomba trabaje en seco."; } bot.sendMessage(chatId, mensajeError, ""); Serial.println("ERROR MANUAL: " + error); // LCD lcd.clear(); lcd.print("NO SE PUEDE:"); lcd.setCursor(0, 1); if (!aguaSuficiente) { lcd.print("Tanque bajo"); } else { lcd.print("Suelo humedo"); } delay(2000); lcd.clear(); lcd.init(); } } void encenderRiego(String fuente) { if (!bombaEncendida) { digitalWrite(PIN_RELE, LOW); // Enciende relé bombaEncendida = true; tiempoInicioRiego = millis(); lcd.clear(); lcd.print("REGANDO..."); lcd.setCursor(0, 1); lcd.print(fuente); Serial.println(">>> BOMBA ENCENDIDA (" + fuente + ") <<<"); } } void apagarRiego(String motivo) { if (bombaEncendida) { digitalWrite(PIN_RELE, HIGH); // Apaga relé bombaEncendida = false; bot.sendMessage(chatId, "🛑 Riego finalizado: " + motivo, ""); delay(500); lcd.init(); lcd.clear(); lcd.print("Riego OFF"); lcd.setCursor(0, 1); lcd.print(motivo); Serial.println(">>> BOMBA APAGADA: " + motivo); delay(2000); lcd.clear(); } } // =============================== // FUNCIONES AUXILIARES // =============================== int obtenerDistancia() { digitalWrite(PIN_TRIG, LOW); delayMicroseconds(2); digitalWrite(PIN_TRIG, HIGH); delayMicroseconds(10); digitalWrite(PIN_TRIG, LOW); long duration = pulseIn(PIN_ECHO, HIGH, 30000); if (duration == 0) return 999; // sin eco return duration * 0.034 / 2; // cm } int calcularPorcentajeSuelo(int raw) { int pct = map(raw, LECTURA_SECO, LECTURA_MOJADO, 0, 100); return constrain(pct, 0, 100); } int calcularPorcentajeTanque(int distCm) { // dist = PROFUNDIDAD_TANQUE_VACIO (9cm) -> 0% // dist = DISTANCIA_LLENO (2cm) -> 100% int pct = map(distCm, PROFUNDIDAD_TANQUE_VACIO, DISTANCIA_LLENO, 0, 100); return constrain(pct, 0, 100); } void actualizarPantallaStandby() { bool noche = esDeNoche(); int sRaw = analogRead(PIN_SUELO); int tCm = obtenerDistancia(); int pSuelo = calcularPorcentajeSuelo(sRaw); int pTanque = calcularPorcentajeTanque(tCm); lcd.setCursor(0, 0); lcd.print(noche ? "Auto:ON (Noche) " : "Auto:OFF (Dia) "); lcd.setCursor(0, 1); lcd.print("S:"); lcd.print(pSuelo); lcd.print("% "); lcd.print("T:"); lcd.print(pTanque); lcd.print("% "); } void imprimirEstadoSerie() { int sRaw = analogRead(PIN_SUELO); int tCm = obtenerDistancia(); int pSuelo = calcularPorcentajeSuelo(sRaw); int pTanque = calcularPorcentajeTanque(tCm); DateTime now = rtc.now(); Serial.println("\n--- ESTADO PC ---"); Serial.print("Hora: "); Serial.print(now.hour()); Serial.print(":"); Serial.println(now.minute()); Serial.print("Suelo: "); Serial.print(pSuelo); Serial.print("% (Raw:"); Serial.print(sRaw); Serial.println(")"); Serial.print("Tanque: "); Serial.print(pTanque); Serial.print("% (Cm:"); Serial.print(tCm); Serial.println(")"); } bool esDeNoche() { DateTime now = rtc.now(); return (now.hour() >= HORA_NOCHE_INICIO || now.hour() < HORA_NOCHE_FIN); } bool esSueloSeco() { return (analogRead(PIN_SUELO) > UMBRAL_SECO_RAW); }