Automatización sistema tinaco cisterna
Estructura del Proyecto
Sistema Tinaco-Cisterna
├── ESP32 Maestro (Tinaco)
│ ├── Sensores de nivel (mínimo y máximo)
│ ├── Control de válvula de llenado
│ └── Comunicación ESP-NOW
├── ESP32 Bomba (Cisterna)
│ ├── Sensor de nivel de cisterna
│ ├── Control de bomba
│ └── Comunicación ESP-NOW
└── ESP32 Monitor
└── Pantalla OLED SSD1306
Componentes Necesarios
Hardware:
-
3x ESP32 (cualquier modelo)
-
2x Sensores de nivel (interruptores magnéticos flotadores)
-
1x Sensor ultrasónico HC-SR04 (para cisterna)
-
1x Módulo relé 2 canales (para bomba y válvula)
-
1x Pantalla OLED SSD1306 (I2C)
-
Fuente de alimentación 5V/2A
-
Cables y protoboard
Código Completo
1. Código para ESP32 Maestro (Tinaco)
/* * ESP32 MAESTRO - Control de Tinaco * Monitorea niveles, controla válvula y envía estado */ #include <esp_now.h> #include <WiFi.h> // Pines de sensores y actuadores #define SENSOR_MIN 4 // GPIO4 - Sensor nivel mínimo #define SENSOR_MAX 5 // GPIO5 - Sensor nivel máximo #define VALVULA_PIN 18 // GPIO18 - Control de válvula (válvula solenoide) // Direcciones MAC de los otros ESP32 uint8_t macBomba[] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX}; // MAC del ESP32 Bomba uint8_t macMonitor[] = {0xYY, 0xYY, 0xYY, 0xYY, 0xYY, 0xYY}; // MAC del ESP32 Monitor // Estructura para enviar datos typedef struct struct_message { int nivelTinaco; // 0: vacío, 1: medio, 2: lleno bool bombaActiva; bool valvulaAbierta; unsigned long timestamp; } struct_message; struct_message estadoActual; // Variables de estado bool nivelMin = false; bool nivelMax = false; bool bombaEncendida = false; unsigned long ultimoEnvio = 0; const unsigned long intervaloEnvio = 2000; // Enviar cada 2 segundos // Callback cuando se envía datos void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("Estado del envío: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Éxito" : "Fallo"); } void setup() { Serial.begin(115200); // Configurar pines pinMode(SENSOR_MIN, INPUT_PULLUP); pinMode(SENSOR_MAX, INPUT_PULLUP); pinMode(VALVULA_PIN, OUTPUT); digitalWrite(VALVULA_PIN, LOW); // Iniciar válvula cerrada // Configurar WiFi para ESP-NOW WiFi.mode(WIFI_STA); // Inicializar ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error inicializando ESP-NOW"); return; } // Registrar callback esp_now_register_send_cb(OnDataSent); // Registrar pares esp_now_peer_info_t peerInfo; // Registrar ESP32 Bomba memcpy(peerInfo.peer_addr, macBomba, 6); peerInfo.channel = 0; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("Error añadiendo peer Bomba"); return; } // Registrar ESP32 Monitor memcpy(peerInfo.peer_addr, macMonitor, 6); if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("Error añadiendo peer Monitor"); return; } Serial.println("ESP32 Maestro iniciado correctamente"); } void loop() { // Leer sensores bool nivelMinActual = !digitalRead(SENSOR_MIN); // Invertir porque usamos INPUT_PULLUP bool nivelMaxActual = !digitalRead(SENSOR_MAX); // Control de válvula if (!nivelMinActual) { // Nivel mínimo detectado - abrir válvula digitalWrite(VALVULA_PIN, HIGH); Serial.println("Nivel mínimo - Abriendo válvula"); } else if (nivelMaxActual) { // Nivel máximo detectado - cerrar válvula digitalWrite(VALVULA_PIN, LOW); Serial.println("Nivel máximo - Cerrando válvula"); } // Determinar nivel del tinaco int nivelTinaco = 0; // Vacío if (nivelMinActual) { nivelTinaco = 1; // Medio } if (nivelMaxActual) { nivelTinaco = 2; // Lleno } // Actualizar estado estadoActual.nivelTinaco = nivelTinaco; estadoActual.valvulaAbierta = digitalRead(VALVULA_PIN); estadoActual.bombaActiva = bombaEncendida; estadoActual.timestamp = millis(); // Enviar estado periódicamente if (millis() - ultimoEnvio > intervaloEnvio) { // Enviar a ESP32 Bomba esp_err_t resultBomba = esp_now_send(macBomba, (uint8_t *) &estadoActual, sizeof(estadoActual)); if (resultBomba == ESP_OK) { Serial.println("Estado enviado a Bomba"); } // Enviar a ESP32 Monitor esp_err_t resultMonitor = esp_now_send(macMonitor, (uint8_t *) &estadoActual, sizeof(estadoActual)); if (resultMonitor == ESP_OK) { Serial.println("Estado enviado a Monitor"); } ultimoEnvio = millis(); } delay(100); // Pequeño delay para estabilidad } // Función para recibir confirmación de bomba void recibirEstadoBomba(bool estado) { bombaEncendida = estado; }
2. Código para ESP32 Bomba (Cisterna)
/* * ESP32 BOMBA - Control de bomba de agua * Monitorea nivel de cisterna y controla bomba */ #include <esp_now.h> #include <WiFi.h> // Pines #define TRIG_PIN 12 // GPIO12 - Trigger del sensor ultrasónico #define ECHO_PIN 14 // GPIO14 - Echo del sensor ultrasónico #define BOMBA_PIN 19 // GPIO19 - Control de bomba (relé) // Dirección MAC del ESP32 Maestro uint8_t macMaestro[] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; // MAC del ESP32 Maestro // Estructura para recibir datos del maestro typedef struct struct_message { int nivelTinaco; // 0: vacío, 1: medio, 2: lleno bool bombaActiva; bool valvulaAbierta; unsigned long timestamp; } struct_message; struct_message datosRecibidos; // Estructura para enviar confirmación typedef struct confirmacion { bool bombaEncendida; int nivelCisterna; bool error; } confirmacion; confirmacion estadoBomba; // Variables float nivelCisterna = 0; // en cm bool bombaActiva = false; unsigned long ultimaLectura = 0; const unsigned long intervaloLectura = 1000; // Leer sensor cada segundo const float nivelMinimoCisterna = 20; // Nivel mínimo para operar bomba (cm) const float nivelMaximoCisterna = 50; // Nivel máximo de seguridad (cm) // Callback cuando se reciben datos void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&datosRecibidos, incomingData, sizeof(datosRecibidos)); Serial.print("Bytes recibidos: "); Serial.println(len); // Procesar datos recibidos Serial.print("Nivel Tinaco: "); Serial.println(datosRecibidos.nivelTinaco); } // Callback cuando se envía datos void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("Confirmación enviada: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Éxito" : "Fallo"); } float medirNivelCisterna() { // Enviar pulso ultrasónico digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); // Medir tiempo de eco long duracion = pulseIn(ECHO_PIN, HIGH, 30000); // Timeout de 30ms // Calcular distancia en cm float distancia = duracion * 0.034 / 2; // Validar lectura if (distancia > 0 && distancia < 200) { return distancia; } else { return -1; // Error en lectura } } void controlarBomba() { bool deberiaEncender = false; // Condiciones para encender bomba if (nivelCisterna > nivelMinimoCisterna && nivelCisterna < nivelMaximoCisterna) { if (datosRecibidos.nivelTinaco < 2) { // Tinaco no está lleno deberiaEncender = true; } } // Controlar bomba if (deberiaEncender && !bombaActiva) { digitalWrite(BOMBA_PIN, HIGH); bombaActiva = true; Serial.println("Bomba ENCENDIDA"); } else if (!deberiaEncender && bombaActiva) { digitalWrite(BOMBA_PIN, LOW); bombaActiva = false; Serial.println("Bomba APAGADA"); } // Verificar nivel muy bajo de cisterna if (nivelCisterna < nivelMinimoCisterna && bombaActiva) { digitalWrite(BOMBA_PIN, LOW); bombaActiva = false; Serial.println("¡ALERTA! Nivel de cisterna muy bajo - Bomba apagada por seguridad"); } } void setup() { Serial.begin(115200); // Configurar pines pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); pinMode(BOMBA_PIN, OUTPUT); digitalWrite(BOMBA_PIN, LOW); // Iniciar bomba apagada // Configurar WiFi para ESP-NOW WiFi.mode(WIFI_STA); // Inicializar ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error inicializando ESP-NOW"); return; } // Registrar callbacks esp_now_register_recv_cb(OnDataRecv); esp_now_register_send_cb(OnDataSent); // Registrar peer (Maestro) esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, macMaestro, 6); peerInfo.channel = 0; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("Error añadiendo peer Maestro"); return; } Serial.println("ESP32 Bomba iniciado correctamente"); } void loop() { // Leer sensor de nivel periódicamente if (millis() - ultimaLectura > intervaloLectura) { float lectura = medirNivelCisterna(); if (lectura > 0) { nivelCisterna = lectura; Serial.print("Nivel cisterna: "); Serial.print(nivelCisterna); Serial.println(" cm"); } else { Serial.println("Error en lectura del sensor ultrasónico"); } ultimaLectura = millis(); } // Controlar bomba basado en niveles controlarBomba(); // Preparar estado para enviar estadoBomba.bombaEncendida = bombaActiva; estadoBomba.nivelCisterna = (int)nivelCisterna; estadoBomba.error = (nivelCisterna < 0); // Enviar confirmación al maestro esp_err_t result = esp_now_send(macMaestro, (uint8_t *) &estadoBomba, sizeof(estadoBomba)); delay(500); }
3. Código para ESP32 Monitor con OLED
/* * ESP32 MONITOR - Pantalla OLED * Muestra estado del sistema tinaco-cisterna */ #include <esp_now.h> #include <WiFi.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> // Configuración OLED #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Dirección MAC del ESP32 Maestro uint8_t macMaestro[] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; // Estructura para recibir datos del maestro typedef struct struct_message { int nivelTinaco; // 0: vacío, 1: medio, 2: lleno bool bombaActiva; bool valvulaAbierta; unsigned long timestamp; } struct_message; struct_message datosSistema; // Variables para tiempo sin conexión unsigned long ultimoRecibido = 0; const unsigned long timeoutConexion = 5000; // 5 segundos sin datos // Callback cuando se reciben datos void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&datosSistema, incomingData, sizeof(datosSistema)); ultimoRecibido = millis(); Serial.print("Datos recibidos - Nivel: "); Serial.println(datosSistema.nivelTinaco); } void setup() { Serial.begin(115200); // Inicializar OLED if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("Error inicializando OLED"); for(;;); } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); // Configurar WiFi para ESP-NOW WiFi.mode(WIFI_STA); // Inicializar ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error inicializando ESP-NOW"); mostrarError("ESP-NOW Error"); return; } // Registrar callback de recepción esp_now_register_recv_cb(OnDataRecv); // Registrar peer (Maestro) esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, macMaestro, 6); peerInfo.channel = 0; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("Error añadiendo peer Maestro"); mostrarError("Peer Error"); return; } Serial.println("ESP32 Monitor iniciado"); mostrarPantallaInicio(); } void mostrarPantallaInicio() { display.clearDisplay(); display.setCursor(0, 0); display.println("Sistema Tinaco"); display.println("Monitor v1.0"); display.println("Esperando datos..."); display.display(); delay(2000); } void mostrarError(const char* mensaje) { display.clearDisplay(); display.setCursor(0, 0); display.println("ERROR:"); display.println(mensaje); display.display(); } void dibujarBarraNivel(int x, int y, int ancho, int alto, int nivel, const char* titulo) { // Dibujar contorno display.drawRect(x, y, ancho, alto, SSD1306_WHITE); // Dibujar título display.setCursor(x, y - 8); display.print(titulo); // Dibujar nivel (0-100%) int nivelPixeles = map(nivel, 0, 100, 0, alto - 2); display.fillRect(x + 1, y + 1 + (alto - 2 - nivelPixeles), ancho - 2, nivelPixeles, SSD1306_WHITE); // Mostrar porcentaje display.setCursor(x + ancho + 5, y + alto/2 - 4); display.print(nivel); display.print("%"); } void loop() { display.clearDisplay(); // Verificar conexión bool conectado = (millis() - ultimoRecibido) < timeoutConexion; if (!conectado) { display.setCursor(0, 0); display.println("! SIN CONEXION !"); display.println("Verificar Maestro"); display.display(); delay(1000); return; } // Convertir nivel (0,1,2) a porcentaje int porcentajeTinaco = 0; switch(datosSistema.nivelTinaco) { case 0: porcentajeTinaco = 0; break; case 1: porcentajeTinaco = 50; break; case 2: porcentajeTinaco = 100; break; } // Título display.setCursor(0, 0); display.println("=== SISTEMA AGUA ==="); // Dibujar indicadores de nivel dibujarBarraNivel(0, 20, 40, 30, porcentajeTinaco, "Tinaco"); // Estados display.setCursor(70, 20); display.print("Bomba:"); display.setCursor(70, 30); display.print(datosSistema.bombaActiva ? "ON " : "OFF"); display.setCursor(70, 40); display.print("Valvula:"); display.setCursor(70, 50); display.print(datosSistema.valvulaAbierta ? "ABIERTA" : "CERRADA"); // Timestamp display.setCursor(0, 55); display.print("T:"); display.print((millis() - datosSistema.timestamp) / 1000); display.print("s"); display.display(); delay(500); // Actualizar cada 500ms }
Diagrama de Conexiones
ESP32 Maestro (Tinaco):
GPIO4 → Sensor nivel mínimo (flotador) GPIO5 → Sensor nivel máximo (flotador) GPIO18 → Relé válvula solenoide 3.3V → Alimentación sensores GND → Común
ESP32 Bomba (Cisterna):
GPIO12 → TRIG sensor HC-SR04 GPIO14 → ECHO sensor HC-SR04 GPIO19 → Relé bomba de agua 5V → Alimentación HC-SR04 GND → Común
ESP32 Monitor:
GPIO21 (SDA) → OLED SDA GPIO22 (SCL) → OLED SCL 3.3V → OLED VCC GND → OLED GND
Configuración Inicial
-
Obtener direcciones MAC:
-
Carga este código en cada ESP32 para obtener su MAC:
#include <WiFi.h> void setup() { Serial.begin(115200); WiFi.mode(WIFI_MODE_STA); Serial.print("MAC Address: "); Serial.println(WiFi.macAddress()); } void loop() {}
-
-
Actualizar direcciones MAC:
-
En el código del Maestro, actualizar
macBombaymacMonitor -
En el código de la Bomba, actualizar
macMaestro -
En el código del Monitor, actualizar
macMaestro
-
-
Ajustar niveles según tu instalación:
-
Modificar
nivelMinimoCisternaynivelMaximoCisternasegún las dimensiones reales -
Ajustar lógica de niveles según la altura de tus sensores
-
Características del Sistema
-
✅ Control automático: Llena el tinaco cuando baja el nivel
-
✅ Protección bomba: No funciona si no hay agua en cisterna
-
✅ Monitoreo remoto: Pantalla OLED con estado en tiempo real
-
✅ Comunicación inalámbrica: Usa ESP-NOW (rápido y sin WiFi)
-
✅ Detección de fallos: Timeout y manejo de errores
-
✅ Fácil expansión: Se pueden agregar más sensores
