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)

cpp
/*
 * 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)

cpp
/*
 * 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

cpp
/*
 * 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):

text
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):

text
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:

text
GPIO21 (SDA) → OLED SDA
GPIO22 (SCL) → OLED SCL
3.3V         → OLED VCC
GND          → OLED GND

 Configuración Inicial

  1. Obtener direcciones MAC:

    • Carga este código en cada ESP32 para obtener su MAC:

    cpp
    #include <WiFi.h>
    void setup() {
      Serial.begin(115200);
      WiFi.mode(WIFI_MODE_STA);
      Serial.print("MAC Address: ");
      Serial.println(WiFi.macAddress());
    }
    void loop() {}
  2. Actualizar direcciones MAC:

    • En el código del Maestro, actualizar macBomba y macMonitor

    • En el código de la Bomba, actualizar macMaestro

    • En el código del Monitor, actualizar macMaestro

  3. Ajustar niveles según tu instalación:

    • Modificar nivelMinimoCisterna y nivelMaximoCisterna segú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

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *