// ----------------------------------------------------------------------
// Programa: Sistema de Control de Agua Inteligente para Ducha
// Autor: Gemini
// Fecha: 12 de Junio de 2025
// Descripción:
// Este programa para Arduino gestiona el nivel de agua de un tanque de ducha,
// controla el llenado automático, permite el control del flujo de la ducha
// con un pulsador y un límite de consumo diario, y muestra información
// relevante en una pantalla LCD 16x2 I2C. Utiliza un sensor ultrasónico
// HC-SR04 y un módulo RTC para funciones de tiempo.
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// 1. Librerías Necesarias
// ----------------------------------------------------------------------
// ESENCIAL: Instala estas librerías desde el Gestor de Librerías del IDE de Arduino:
// - LiquidCrystal I2C by Frank de Brabander
// - NewPing by Tim Eckel (¡MUY RECOMENDADO para HC-SR04 y compatible con este código!)
// - RTClib by Adafruit (para el módulo RTC DS3231)
#include <Wire.h> // Requerida para la comunicación I2C (LCD y RTC)
#include <LiquidCrystal_I2C.h> // Para la pantalla LCD 16x2 con interfaz I2C
#include <NewPing.h> // Para el sensor ultrasónico HC-SR04 (Librería NewPing)
#include <RTClib.h> // Para el módulo de Reloj de Tiempo Real (RTC)
// ----------------------------------------------------------------------
// 2. Definición de Pines de Hardware
// ----------------------------------------------------------------------
const int PIN_TRIG_US = 9; // Pin TRIG del sensor ultrasónico HC-SR04
const int PIN_ECHO_US = 10; // Pin ECHO del sensor ultrasónico HC-SR04
const int PIN_RELE_LLENADO = 2; // Pin de control para el relé de la electroválvula de llenado
const int PIN_RELE_DUCHA = 3; // Pin de control para el relé de la electroválvula de la ducha
const int PIN_PULSADOR_DUCHA = 4; // Pin de entrada para el pulsador de la ducha
// ----------------------------------------------------------------------
// 3. Configuración de Parámetros del Tanque y Ducha
// ----------------------------------------------------------------------
const float ALTURA_MAX_TANQUE_CM = 40.0; // Altura máxima del tanque en cm (desde el sensor hasta el fondo del tanque)
// ¡Importante! Este valor debe ser calibrado con la altura real de tu tanque.
const float NIVEL_MIN_CM = 10.0; // Nivel mínimo de agua en cm (desde el fondo) antes de iniciar el llenado
const float NIVEL_MAX_CM = 35.0; // Nivel máximo de agua en cm (desde el fondo) para detener el llenado
// Variables para el control de la duración de la ducha
const unsigned long TIEMPO_DUCHA_MS = 7 * 60 * 1000UL; // 7 minutos en milisegundos (duración estándar de la ducha)
const unsigned long TIEMPO_PULSADOR_ACTIVA_MS = 2000UL; // Tiempo de pulsación (2 segundos) para activar la ducha
const unsigned long TIEMPO_PULSADOR_CORTA_MS = 5000UL; // Tiempo de pulsación (5 segundos) para cortar la ducha
// Variables para el control de consumo diario de agua
float agua_dispensada_hoy_litros = 0.0; // Litros de agua dispensados hoy
float agua_maxima_diaria_litros = 50.0; // Cantidad máxima de agua diaria permitida en litros
// Este valor se puede ajustar en el código o a través de un menú si se implementa.
// Coeficiente de conversión de volumen a litros (¡CRÍTICO PARA LA PRECISIÓN!)
// Este valor debe ser calibrado: ¿Cuántos litros representa 1 cm de cambio de nivel en tu tanque?
// Ejemplo: Si tu tanque tiene una base de 20cm x 25cm (500 cm^2), entonces 1 cm de altura son 500 cm^3 = 0.5 litros.
const float CM_A_LITROS = 0.5; // Ejemplo: 1 cm de altura de agua equivale a 0.5 litros.
// => ¡DEBES CALIBRAR ESTE VALOR PARA TU TANQUE ESPECÍFICO!
// ----------------------------------------------------------------------
// 4. Instancias de Objetos / Inicialización de Librerías
// ----------------------------------------------------------------------
// Inicialización del objeto LCD: dirección I2C (0x27 es común, verificar), columnas, filas
// Si tu LCD no funciona, es posible que la dirección I2C sea diferente (ej. 0x3F).
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Inicialización del objeto ultrasónico con los pines TRIG, ECHO y la distancia máxima de medición.
// La distancia máxima de medición debe ser mayor que la altura del tanque para asegurar lecturas.
NewPing ultrasonic(PIN_TRIG_US, PIN_ECHO_US, ALTURA_MAX_TANQUE_CM + 10); // +10 cm de margen para la medición
// Inicialización del objeto RTC
RTC_DS3231 rtc;
// ----------------------------------------------------------------------
// 5. Variables de Estado del Sistema
// ----------------------------------------------------------------------
bool llenando_tanque = false; // Estado: true si la electroválvula de llenado está abierta
bool ducha_activa = false; // Estado: true si la electroválvula de la ducha está abierta
unsigned long tiempo_inicio_ducha = 0; // Marca de tiempo cuando la ducha fue activada
unsigned long tiempo_pulsador_presionado = 0; // Marca de tiempo cuando el pulsador fue presionado
float nivel_agua_cm = 0.0; // Nivel de agua actual en cm (desde el fondo del tanque)
float nivel_agua_anterior_cm = 0.0; // Nivel de agua antes de una sesión de ducha para calcular el consumo
int dia_actual = 0; // Guarda el día actual del RTC para reiniciar el contador de consumo
// ----------------------------------------------------------------------
// 6. Prototipos de Funciones
// (Declaran las funciones antes de que sean definidas, para que el compilador las conozca)
// ----------------------------------------------------------------------
void leerNivelAgua();
void controlarLlenado();
void controlarDucha();
void mostrarLCD();
void resetearConsumoDiario();
// ----------------------------------------------------------------------
// 7. Función setup() - Se ejecuta una vez al inicio del Arduino
// ----------------------------------------------------------------------
void setup() {
// Inicializar comunicación serial para depuración
Serial.begin(9600);
Serial.println("Iniciando Sistema de Ducha Inteligente...");
// Inicializar y configurar la pantalla LCD
lcd.init(); // Inicializa la interfaz I2C del LCD
lcd.backlight(); // Enciende la luz de fondo del LCD
lcd.setCursor(0, 0);
lcd.print("Iniciando...");
delay(2000); // Pequeña pausa para que el mensaje sea visible
lcd.clear(); // Limpiar la pantalla
// Inicializar el módulo RTC
if (!rtc.begin()) {
Serial.println("Error: No se encontró el módulo RTC. ¡Compruebe las conexiones!");
lcd.setCursor(0, 0);
lcd.print("Error RTC!");
// Si el RTC no inicia, el programa se detiene aquí ya que es crítico para el consumo diario.
while (true);
}
// Comentar la siguiente línea después de la primera carga para que el RTC mantenga la hora
// y no se reajuste cada vez que se reinicia el Arduino.
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Configura el RTC con la hora de compilación del código
// Configurar los pines de salida para los relés (electrovalvulas)
pinMode(PIN_RELE_LLENADO, OUTPUT);
pinMode(PIN_RELE_DUCHA, OUTPUT);
// Configurar el pin del pulsador como entrada con resistencia pull-up interna
// Esto significa que el pin estará HIGH por defecto y pasará a LOW cuando se presione el botón.
pinMode(PIN_PULSADOR_DUCHA, INPUT_PULLUP);
// Asegurarse de que las electroválvulas estén cerradas al inicio.
// Para relés NC (Normalmente Cerrados), un estado HIGH en el pin del Arduino los mantiene cerrados.
// Un estado LOW los activa (abre la válvula).
digitalWrite(PIN_RELE_LLENADO, HIGH);
digitalWrite(PIN_RELE_DUCHA, HIGH);
// Obtener el día actual del RTC al inicio para la lógica de reinicio de consumo
DateTime now = rtc.now();
dia_actual = now.day();
}
// ----------------------------------------------------------------------
// 8. Función loop() - Se ejecuta repetidamente después de setup()
// ----------------------------------------------------------------------
void loop() {
// 1. Leer el Nivel de Agua del Tanque
leerNivelAgua();
// 2. Controlar el Llenado Automático del Tanque
controlarLlenado();
// 3. Controlar la Activación/Desactivación de la Ducha
controlarDucha();
// 4. Reiniciar el Consumo Diario de Agua si el día ha cambiado
resetearConsumoDiario();
// 5. Actualizar la Información en la Pantalla LCD
mostrarLCD();
// Pequeña pausa para estabilizar lecturas y evitar un ciclo demasiado rápido.
// Ajustar según la necesidad de respuesta.
delay(200);
}
// ----------------------------------------------------------------------
// 9. Implementación de Funciones Auxiliares
// ----------------------------------------------------------------------
/**
* @brief Lee el sensor ultrasónico y calcula el nivel de agua en cm.
* Actualiza la variable global 'nivel_agua_cm'.
*/
void leerNivelAgua() {
// Usar el método 'ping_cm()' de la librería NewPing
// Este método devuelve directamente la distancia en centímetros.
// Un valor de 0 indica que no se obtuvo una lectura válida dentro del rango máximo.
float distancia_cm = ultrasonic.ping_cm();
// Si la distancia_cm es 0, significa que no se detectó un eco dentro del rango.
// Para evitar cálculos erráticos, se puede mantener el último valor válido o manejarlo.
if (distancia_cm == 0) {
Serial.println("Advertencia: No se detectó eco del sensor (distancia fuera de rango).");
// Opcional: Podrías aquí usar el nivel_agua_cm_anterior o algún valor por defecto
// para evitar un cambio brusco si la lectura es temporalmente inválida.
// Por ahora, si es 0, el cálculo dará un valor muy alto o negativo, que luego se acota.
}
// Calcular el nivel de agua desde el fondo del tanque.
// Si el sensor está en la parte superior, el nivel de agua es:
// Altura_Total_Tanque - Distancia_Medida_Al_Agua
// Se añade una pequeña tolerancia para asegurar que el nivel no exceda la altura máxima.
if (distancia_cm >= 0) { // NewPing devuelve 0 si no hay eco o la distancia es muy grande
nivel_agua_cm = ALTURA_MAX_TANQUE_CM - distancia_cm;
// Asegurarse de que el nivel no sea negativo ni exceda la altura máxima real del tanque.
if (nivel_agua_cm < 0) nivel_agua_cm = 0;
if (nivel_agua_cm > ALTURA_MAX_TANQUE_CM) nivel_agua_cm = ALTURA_MAX_TANQUE_CM;
} else {
// Si la lectura es un valor negativo (raro con NewPing, pero como precaución)
Serial.println("Lectura ultrasonica inválida.");
}
}
/**
* @brief Controla la electroválvula de llenado basándose en el nivel de agua.
* Inicia el llenado si el nivel cae por debajo de NIVEL_MIN_CM.
* Detiene el llenado si el nivel alcanza o supera NIVEL_MAX_CM.
*/
void controlarLlenado() {
// Lógica para iniciar el llenado
if (nivel_agua_cm < NIVEL_MIN_CM && !llenando_tanque) {
digitalWrite(PIN_RELE_LLENADO, LOW); // Abrir electroválvula de llenado (LOW activa el relé)
llenando_tanque = true;
Serial.println("Iniciando llenado del tanque...");
lcd.setCursor(0,1); // Mensaje temporal en LCD
lcd.print("Llenando! ");
}
// Lógica para detener el llenado
else if (nivel_agua_cm >= NIVEL_MAX_CM && llenando_tanque) {
digitalWrite(PIN_RELE_LLENADO, HIGH); // Cerrar electroválvula de llenado (HIGH desactiva el relé)
llenando_tanque = false;
Serial.println("Llenado del tanque completado.");
lcd.setCursor(0,1); // Mensaje temporal en LCD
lcd.print("Lleno! ");
delay(1000); // Mostrar por un segundo
}
}
/**
* @brief Controla la electroválvula de la ducha a través del pulsador.
* Permite activar la ducha por 7 minutos, cortarla antes de tiempo,
* y registra el consumo de agua.
*/
void controlarDucha() {
int estado_pulsador = digitalRead(PIN_PULSADOR_DUCHA); // Leer el estado del pulsador
// --- Lógica del Pulsador para Activar/Cortar la Ducha ---
if (estado_pulsador == LOW) { // El pulsador está presionado (debido a INPUT_PULLUP)
if (tiempo_pulsador_presionado == 0) {
tiempo_pulsador_presionado = millis(); // Registrar el momento en que se presionó por primera vez
}
// Si la ducha NO está activa y el pulsador ha estado presionado por TIEMPO_PULSADOR_ACTIVA_MS (2 seg)
if (!ducha_activa && (millis() - tiempo_pulsador_presionado >= TIEMPO_PULSADOR_ACTIVA_MS)) {
// Verificar si hay agua disponible dentro del límite diario
// Se hace una estimación inicial de agua disponible para evitar activar la ducha si ya no queda casi nada.
if ((agua_dispensada_hoy_litros + (nivel_agua_cm * CM_A_LITROS)) < agua_maxima_diaria_litros) {
digitalWrite(PIN_RELE_DUCHA, LOW); // Abrir electroválvula de la ducha
ducha_activa = true;
tiempo_inicio_ducha = millis(); // Registrar el inicio de la sesión de ducha
nivel_agua_anterior_cm = nivel_agua_cm; // Guardar el nivel de agua inicial para calcular el consumo
Serial.println("Ducha activada.");
lcd.setCursor(0,1);
lcd.print("Ducha ON! ");
} else {
Serial.println("Limite de agua diario alcanzado. Ducha no disponible.");
lcd.setCursor(0,1);
lcd.print("Limite Agua! ");
delay(2000); // Mostrar mensaje por 2 segundos
}
tiempo_pulsador_presionado = 0; // Resetear la marca de tiempo del pulsador para evitar repeticiones
}
// Si la ducha SÍ está activa y el pulsador ha estado presionado por TIEMPO_PULSADOR_CORTA_MS (5 seg)
else if (ducha_activa && (millis() - tiempo_pulsador_presionado >= TIEMPO_PULSADOR_CORTA_MS)) {
digitalWrite(PIN_RELE_DUCHA, HIGH); // Cerrar electroválvula de la ducha
ducha_activa = false;
Serial.println("Ducha cortada manualmente.");
// Calcular el agua consumida en esta sesión y añadirla al total diario
// El consumo se calcula como la diferencia de nivel antes y despues de la ducha.
float agua_consumida_sesion = (nivel_agua_anterior_cm - nivel_agua_cm) * CM_A_LITROS;
if (agua_consumida_sesion > 0) { // Asegurarse de que el consumo sea un valor positivo
agua_dispensada_hoy_litros += agua_consumida_sesion;
Serial.print("Consumo de esta sesion: "); Serial.print(agua_consumida_sesion, 2); Serial.println(" L");
}
tiempo_pulsador_presionado = 0; // Resetear la marca de tiempo del pulsador
lcd.setCursor(0,1);
lcd.print("Ducha Cortada! ");
delay(1000); // Mostrar por 1 segundo
}
} else { // El pulsador está liberado
// Resetear la marca de tiempo si el botón fue liberado antes del tiempo de activación/corte
if (tiempo_pulsador_presionado != 0) {
tiempo_pulsador_presionado = 0;
}
}
// --- Lógica para Desactivación Automática de la Ducha por Tiempo ---
if (ducha_activa && (millis() - tiempo_inicio_ducha >= TIEMPO_DUCHA_MS)) {
digitalWrite(PIN_RELE_DUCHA, HIGH); // Cerrar electroválvula de la ducha
ducha_activa = false;
Serial.println("Ducha finalizada por tiempo programado.");
// Calcular el agua consumida en esta sesión y añadirla al total diario
float agua_consumida_sesion = (nivel_agua_anterior_cm - nivel_agua_cm) * CM_A_LITROS;
if (agua_consumida_sesion > 0) {
agua_dispensada_hoy_litros += agua_consumida_sesion;
Serial.print("Consumo de esta sesion: "); Serial.print(agua_consumida_sesion, 2); Serial.println(" L");
}
lcd.setCursor(0,1);
lcd.print("Ducha Terminada!");
delay(1000); // Mostrar por 1 segundo
}
}
/**
* @brief Reinicia el contador de consumo diario de agua si el día ha cambiado.
* Utiliza el módulo RTC para verificar la fecha.
*/
void resetearConsumoDiario() {
DateTime now = rtc.now(); // Obtener la fecha y hora actuales del RTC
// Si el día actual del RTC es diferente al día guardado, reinicia el consumo
if (now.day() != dia_actual) {
agua_dispensada_hoy_litros = 0.0; // Reiniciar el contador de litros dispensados hoy
dia_actual = now.day(); // Actualizar el día actual
Serial.println("¡Día nuevo! Se reinició el consumo diario de agua.");
}
}
/**
* @brief Muestra información relevante en la pantalla LCD.
* Incluye el nivel de agua actual y el consumo diario.
*/
void mostrarLCD() {
lcd.clear(); // Limpiar la pantalla para evitar "fantasmas" de texto anterior
// Primera línea del LCD: Nivel de Agua
lcd.setCursor(0, 0); // Columna 0, Fila 0
lcd.print("Nivel: ");
lcd.print(nivel_agua_cm, 1); // Muestra el nivel con un decimal
lcd.print(" cm");
// Segunda línea del LCD: Consumo Diario de Agua
lcd.setCursor(0, 1); // Columna 0, Fila 1
lcd.print("Consumo: ");
lcd.print(agua_dispensada_hoy_litros, 1); // Muestra el consumo con un decimal
lcd.print("/");
lcd.print(agua_maxima_diaria_litros, 0); // Muestra el límite diario sin decimales
lcd.print(" L");
// Opcional: Puedes añadir un indicador de estado en una esquina, por ejemplo:
/*
lcd.setCursor(13,0);
if(llenando_tanque){
lcd.print("LLEN");
} else if (ducha_activa){
lcd.print("ACT ");
} else {
lcd.print("IDLE");
}
*/
}