En buena parte de Chile el agua no llega sola: llega a un estanque. Casas rurales con noria, parcelas con puntera, sistemas de APR, edificios con estanque de acumulación en el subterráneo, invernaderos con acumulador para riego. Y en casi todos esos casos la rutina es la misma: alguien sube, destapa, mira adentro con una linterna y decide si prende la bomba.
Este proyecto reemplaza esa rutina por un número. Un sensor ultrasónico impermeable mide la distancia entre la tapa del estanque y la superficie del agua, un ESP32 convierte esa distancia en porcentaje, y un relé enciende y apaga la bomba sola cuando el nivel cruza los límites que tú definiste. Los límites se ajustan desde el celular, desde cualquier lugar con internet, y quedan guardados en la memoria del microcontrolador: si se corta la luz, no se pierden.
Al terminar vas a tener el sistema funcionando, y además vas a entender tres cosas que casi ningún tutorial de nivel de agua explica: por qué la lectura del sensor es al revés que el nivel, dónde está el techo real de este diseño cuando el estanque es alto, y qué necesitas de verdad para que ande con un panel solar y no se apague la primera noche.
Cómo se mide el nivel sin tocar el agua
La idea de fondo es simple: el sensor va arriba, mirando hacia abajo, y mide la distancia hasta lo primero que encuentre. Si el estanque está lleno, esa distancia es corta. Si está vacío, es larga. La lectura del sensor y el nivel del agua se mueven en direcciones opuestas, y esa inversión es la fuente de casi todos los errores de calibración.
Un sensor ultrasónico emite un pulso de sonido y cronometra cuánto tarda el eco en volver. Como el sonido viaja a unos 343 m/s en aire a 20 °C, el tiempo de ida y vuelta se traduce directo a distancia. Eso trae dos consecuencias prácticas que conviene saber antes de instalarlo:
- La velocidad del sonido depende de la temperatura, alrededor de 0,6 m/s por cada grado. Un estanque de plástico negro al sol en Copiapó puede tener 45 °C adentro al mediodía y 10 °C en la madrugada: entre esos dos extremos hay un error de aproximadamente 3% en la medición. Para prender una bomba es irrelevante; si quieres litros exactos, no lo es.
- Hay una zona muerta. Ningún ultrasónico mide bien pegado a la cara del sensor, porque el transductor todavía está vibrando cuando llega el eco. En el A02YYUW ese piso son 3 cm. Si montas el sensor demasiado cerca del nivel máximo, el estanque lleno te va a dar lecturas basura.

El hardware, y por qué este sensor y no el barato
Acá está la decisión que define el proyecto. El HC SR04. el ultrasónico de $2.000 que aparece en todos los tutoriales de Arduino. no sirve para un estanque de agua, y no es por precisión: es porque no está sellado. Adentro de un estanque cerrado hay 100% de humedad relativa y condensación permanente en la tapa. Un HC SR04 con los transductores al aire libre se llena de agua y muere en semanas.
Las tres opciones reales, de menor a mayor:
| Sensor | Sellado | Rango | Interfaz | Uso recomendado |
|---|---|---|---|---|
| HC SR04 | No | 2 a 400 cm | Trigger/Echo | Prototipo en el escritorio, nada más |
| JSN SR04T | Transductor sí, placa no | 25 a 450 cm | Trigger/Echo o UART | Estanques, con la placa afuera y protegida |
| A02YYUW | Sí, completo | 3 a 450 cm | UART o PWM | La opción sin dolores de cabeza |
El A02YYUW es el que usa este montaje. Viene en dos versiones: UART y PWM; la de este proyecto es la UART, que entrega la distancia ya calculada en un paquete de 4 bytes y te ahorra medir pulsos a mano. Funciona con 3,3 V y con 5 V, así que se conecta directo a un ESP32 sin adaptador de nivel.
Una nota sobre el JSN SR04T, que es bastante más barato y se consigue fácil: el transductor sí es impermeable, pero la placa de control no lo es. Si lo usas, la placa va afuera del estanque, en una caja sellada, y solo el transductor entra por la tapa. Su zona muerta además es de 25 cm, mucho peor que los 3 cm del A02YYUW, así que pierdes los primeros 25 cm de estanque lleno.
Para el control, cualquier ESP32 sirve. El montaje original usa un Xiao ESP32C3, que es un módulo muy compacto y trae circuito de carga de batería LiTio integrado. dato importante si vas a alimentar con solar, porque te ahorra una placa TP4056 aparte. Un ESP32 DevKit clásico funciona igual de bien para toda la lógica, pero no carga batería solo: ahí sí necesitas el módulo de carga por separado.

Conexiones
El cableado es corto. El sensor habla por puerto serie, el display por I2C y el relé es una salida digital simple:
- A02YYUW: cable rojo a 3V3, cable negro a GND, TX del sensor al pin D2 y RX del sensor al pin D3.
- Display OLED SSD1306: VCC a 3V3, GND a GND, SDA a D4 y SCL a D5.
- Módulo relé 5 V SPDT: señal al pin D0, más su alimentación y tierra.

El OLED es opcional en términos de funcionamiento. el sistema anda igual sin él. pero en la práctica vale mucho la pena dejarlo: es la única forma de ver qué está midiendo el sensor cuando estás parado al lado del estanque y el WiFi no llega bien.

La aplicación en Blynk
Blynk IoT es la capa que te deja ver el estanque desde el celular sin montar servidor propio. La lógica del proyecto usa cinco pines virtuales:
| Pin virtual | Qué hace |
|---|---|
V0 |
Muestra el nivel en porcentaje |
V1 |
Muestra la distancia cruda en cm |
V2 |
Botón de control manual de la bomba |
V3 |
Ajuste del límite inferior |
V4 |
Ajuste del límite superior |
En el dashboard necesitas entonces dos widgets de visualización (V0 y V1), un botón (V2) y dos sliders (V3 y V4). No hace falta que el celular esté en la misma red WiFi que el ESP32: el tráfico pasa por la nube de Blynk, así que puedes revisar el estanque desde el trabajo o desde otra ciudad.

El código
El sketch completo está abajo. Antes de cargarlo tienes que reemplazar cuatro valores: el BLYNK_TEMPLATE_ID y el BLYNK_AUTH_TOKEN que te entrega tu propio proyecto de Blynk, y el nombre y la clave de tu red WiFi.
Importante: los valores que aparecen acá son marcadores de posición, no credenciales reales. Y ya que estamos: dejar la clave del WiFi escrita en el código es una mala costumbre que se paga cuando subes el proyecto a GitHub sin pensarlo. Si vas a publicarlo, mueve las credenciales a un archivo aparte que agregues al .gitignore, o usa WiFiManager para configurarlas desde el celular la primera vez.
Necesitas estas librerías instaladas desde el Library Manager del IDE de Arduino: Blynk, Adafruit GFX y Adafruit SSD1306.
#define BLYNK_TEMPLATE_ID "TU_TEMPLATE_ID"
#define BLYNK_TEMPLATE_NAME "Solar Power based Water Level Monitoring"
#define BLYNK_AUTH_TOKEN "TU_AUTH_TOKEN_DE_BLYNK"
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <EEPROM.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <HardwareSerial.h>
HardwareSerial Ultrasonic_Sensor(1); //
// Define connections to sensor
int pinRX = D2; // white wire
int pinTX = D3; // yellow wire
char auth[] = BLYNK_AUTH_TOKEN;
// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "NOMBRE_DE_TU_WIFI";
char pass[] = "CLAVE_DE_TU_WIFI";
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
unsigned char data_buffer[4] = {0};
int distance;
int waterLevelPer;
// Variable to hold checksum
unsigned char CS;
int Relay = D0;
int Relay_Status = 0;
int Percentage = 0;
int previousDistance;
unsigned long distanceChangeTime;
BlynkTimer timer;
int lowerlevel;
int upperlevel;
void setup()
{
Serial.begin(115200); // Initialize the serial monitor
Ultrasonic_Sensor.begin(9600, SERIAL_8N1, pinRX, pinTX); // Initialize the hardware serial
pinMode(Relay,OUTPUT);
Blynk.begin(auth, ssid, pass);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
delay(2000);
display.clearDisplay();
display.setTextColor(WHITE);
EEPROM.begin(512);
upperlevel = EEPROM.read(0);
lowerlevel = EEPROM.read(1);
timer.setInterval(1000L, displayvalues);
}
void loop()
{
Blynk.run();
timer.run(); // Initiates SimpleTimer
A02YYUW_Sensor();
}
void A02YYUW_Sensor()
{
if (Ultrasonic_Sensor.available() > 0) {
delay(4);
// Check for packet header character 0xff
if (Ultrasonic_Sensor.read() == 0xff) {
// Insert header into array
data_buffer[0] = 0xff;
// Read remaining 3 characters of data and insert into array
for (int i = 1; i < 4; i++) {
data_buffer[i] = Ultrasonic_Sensor.read();
}
//Compute checksum
CS = data_buffer[0] + data_buffer[1] + data_buffer[2];
// If checksum is valid compose distance from data
if (data_buffer[3] == CS) {
distance = (data_buffer[1] << 8) + data_buffer[2];
// Print to serial monitor
distance= distance / 10; // cm
}
}
}
Serial.print("distance: ");
Serial.print(distance);
Serial.println(" cm");
}
void displayvalues()
{
waterLevelPer = map(distance, upperlevel, lowerlevel, 100, 0);
// display on Oled display
if(waterLevelPer<0)
{
waterLevelPer=0;
}
if(waterLevelPer>100)
{
waterLevelPer=100;
}
if (waterLevelPer <= 20)
{
digitalWrite(Relay, HIGH);
delay(10);
}
if (waterLevelPer >= 85)
{
digitalWrite(Relay, LOW);
delay(10);
}
// Oled display
display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(2);
display.setTextColor(WHITE);
display.print(String("D:")+distance);
display.setCursor(0,20);
display.setTextSize(2);
display.print(String(waterLevelPer)+"%");
display.setTextSize(2);
display.setCursor(0,20);
display.setCursor(0,45);
display.setTextSize(2);
display.print("L:"+String(lowerlevel)+" U:"+String(upperlevel));
display.display();
Blynk.virtualWrite(V0, waterLevelPer);
Blynk.virtualWrite(V1, distance);
Serial.print("waterLevelPer : ");
Serial.println( waterLevelPer);
Serial.print("lower level:");
Serial.println(lowerlevel);
Serial.print("upper level:");
Serial.println(upperlevel);
}
BLYNK_WRITE(V2)
{
int pinValue=param.asInt();
digitalWrite(Relay,pinValue);
}
BLYNK_WRITE(V3)
{
lowerlevel=param.asInt();
EEPROM.write(1, lowerlevel);
EEPROM.commit();
}
BLYNK_WRITE(V4)
{
upperlevel=param.asInt();
EEPROM.write(0, upperlevel);
EEPROM.commit();
}
Calibración: los dos números que hacen todo el trabajo
Esta es la parte que el tutorial original resuelve en una frase y que en terreno te va a tomar la mitad del tiempo. Los límites lowerlevel y upperlevel no son porcentajes ni niveles de agua: son distancias en centímetros medidas desde el sensor.
La conversión ocurre en una sola línea:
waterLevelPer = map(distance, upperlevel, lowerlevel, 100, 0);
Léela con calma, porque el orden de los argumentos es contraintuitivo a propósito: upperlevel (la distancia más corta, estanque lleno) mapea a 100%, y lowerlevel (la distancia más larga, estanque vacío) mapea a 0%. Están invertidos respecto a lo que sugieren sus nombres, y así es como corresponde.
Para calibrar tu estanque:
- Con el estanque lleno hasta donde quieres que llegue, anota la distancia que muestra el display. Ese es tu
upperlevel. Deja al menos 5 cm de aire entre el sensor y el agua para no caer en la zona muerta. - Con el estanque en el mínimo aceptable. normalmente unos centímetros sobre la salida, para que la bomba nunca chupe aire ni sedimento. anota la distancia. Ese es tu
lowerlevel. - Carga ambos valores desde los sliders de Blynk. Se guardan solos en la EEPROM.

El código además protege los extremos: si el cálculo da menos de 0 o más de 100, lo recorta. Eso evita que un porcentaje negativo por una lectura rara haga algo impredecible con la bomba.
El techo del diseño: 255 cm
Acá hay un detalle que conviene mirar antes de instalar, porque no aparece en ningún lado y solo se manifiesta cuando el estanque es alto.
Los límites se guardan así:
upperlevel=param.asInt();
EEPROM.write(0, upperlevel);
EEPROM.commit();
EEPROM.write() guarda un byte, y un byte llega hasta 255. El A02YYUW mide hasta 450 cm, pero cualquier límite que definas sobre 255 cm no se va a guardar bien: al reiniciar vuelve truncado y la calibración queda mal sin ningún mensaje de error.
Para estanques domésticos típicos. un tambor de 1.000 litros tiene poco más de un metro de alto. esto nunca aparece. Pero si tu estanque es vertical y alto, o si el sensor queda montado bien arriba de la tapa, es un límite real. La solución es guardar cada valor en dos bytes:
EEPROM.write(0, upperlevel & 0xFF);
EEPROM.write(1, upperlevel >> 8);
y al leer, recomponerlo con EEPROM.read(0) | (EEPROM.read(1) << 8), recordando reasignar las direcciones del límite inferior para que no se pisen.
Que no se pierda la configuración
El motivo de usar EEPROM en vez de variables normales es simple: un corte de luz no puede dejar el sistema descalibrado. Si los límites se perdieran en cada reinicio, alguien tendría que volver a configurar el estanque después de cada apagón. y un sistema que necesita mantención manual después de cada falla es un sistema que nadie usa.

Prueba antes de conectar la bomba
Nunca pruebes este montaje con la bomba conectada la primera vez. Conecta al relé una ampolleta o cualquier carga inofensiva y verifica el ciclo completo: mueve una superficie reflectante frente al sensor simulando el nivel, y confirma que la carga se enciende bajo 20% y se apaga sobre 85%.


Dos advertencias serias sobre el lado de potencia:
- 220 V mata. El relé conmuta la red eléctrica domiciliaria. Todo el cableado de ese lado va dentro de una caja cerrada, con bornera, y no se toca con el sistema energizado.
- Un módulo relé chico no maneja una bomba de verdad. Los relés de 10 A que vienen en los módulos de Arduino son para cargas resistivas. Un motor de bomba tira una corriente de partida (inrush) de tres a siete veces su corriente nominal, y esos contactos se pegan soldados con el uso. Para cualquier bomba sobre ½ HP, el relé debe accionar un contactor, y el contactor mueve la bomba. Es la diferencia entre un montaje que dura años y uno que se quema en un mes.
Errores comunes
- La distancia queda pegada en 0 o en un valor fijo: casi siempre es TX y RX cruzados. El TX del sensor va al pin de recepción del ESP32 (
D2, definido comopinRX). Prueba invirtiéndolos. - El porcentaje salta como loco: el sensor está viendo la pared del estanque o la turbulencia del llenado. Móntalo lo más al centro posible de la tapa y lejos de la entrada de agua. Un promedio móvil de cinco lecturas en el código también ayuda mucho.
- El OLED no muestra nada: la dirección I2C. El código usa
0x3C, que es la más común, pero hay módulos en0x3D. Un sketch de escaneo I2C te lo resuelve en un minuto. - Se conecta al WiFi pero Blynk no aparece: revisa que el
BLYNK_TEMPLATE_IDdel código sea el del mismo template donde creaste los widgets. Es el error número uno con Blynk. - Lecturas erráticas de noche o con el estanque recién llenado: la condensación en la cara del sensor y la espuma sobre el agua absorben el ultrasonido. Es normal que la medición sea peor justo después de llenar.
Lo que realmente pide el panel solar
El proyecto original conecta un panel solar chico y funciona, pero vale la pena hacer el cálculo porque acá es donde muchos montajes fallan a la semana.
Un ESP32 con WiFi conectado de forma permanente consume del orden de 80 a 120 mA promedio. En 24 horas son cerca de 2.400 mAh. Una batería LiTio 18650 de 2.600 mAh, sola, no alcanza a cubrir un día completo, y un panel de 6 V y 1 W entrega unos 160 mA en pleno sol: con las 4 a 5 horas de sol útil que tienes en invierno en la zona centro sur, recuperas menos de la mitad de lo que gastaste.
Hay dos caminos honestos:
- Dimensionar el panel de verdad: panel de 6 V y 5 W o más, con batería 18650 de 3.400 mAh. Con eso el sistema aguanta varios días nublados seguidos.
- Bajar el consumo con deep sleep, que es la solución elegante. El nivel de un estanque cambia lento: medir cada 15 minutos es más que suficiente. Despertando, midiendo y volviendo a dormir, el ESP32 promedia menos de 1 mA y un panel chico basta y sobra. El costo es que pierdes el control manual instantáneo desde el celular, porque la placa está dormida casi todo el tiempo.

Variantes y mejoras
Tres extensiones concretas que este montaje no trae y que valen la pena:
- Protección contra marcha en seco. Si la bomba saca agua de un pozo o noria, el peor escenario no es el estanque vacío sino el pozo seco: la bomba trabaja sin agua y se quema en minutos. Agrega un segundo sensor. un flotador simple en el pozo, o un sensor de flujo YF-S201 en la impulsión. y no dejes que el relé cierre si no hay agua en el origen. Con el sensor de flujo además detectas que la bomba está prendida pero no mueve agua, que es exactamente la señal de que se cebó mal.
- Registro histórico y detección de fugas. Guardando el porcentaje en una microSD por SPI, o enviándolo a un canal de ThingSpeak, tienes la curva de consumo. Ahí aparece algo que el número instantáneo no muestra nunca: si el nivel baja de madrugada, cuando nadie está usando agua, tienes una fuga. Es probablemente la función más útil que le puedes agregar a este proyecto.
- Alertas y respaldo sin internet. Un mensaje automático cuando el estanque baja del 15% te avisa antes de quedarte sin agua. Y si la instalación es rural con internet intermitente, un módulo LoRa en lugar de WiFi te da kilómetros de alcance hasta la casa sin depender de la señal celular.
Personalización para Chile
Todo lo necesario para este proyecto se arma con componentes del catálogo de MechatronicStore:
- Sensor ultrasónico impermeable A02YYUW (UART). el corazón del proyecto. Si no lo encuentras disponible, el JSN SR04T es el reemplazo válido, con las dos salvedades de más arriba: deja la placa de control fuera del estanque y considera que pierdes los primeros 25 cm por su zona muerta. Un HC SR04 no es un reemplazo: sirve solo para probar el código en el escritorio.
- Placa ESP32. el montaje original usa un Xiao ESP32C3 por su tamaño y su cargador de batería integrado. Un ESP32 DevKit V1 del catálogo corre exactamente el mismo código; solo recuerda que los nombres de pines
D0–D5del Xiao hay que cambiarlos por los GPIO correspondientes de tu placa. - Display OLED SSD1306 0.96" I2C. opcional pero muy recomendable para diagnosticar en terreno.
- Módulo relé 5 V de 1 canal. para la señal de control. Repito lo de arriba porque importa: para una bomba real, este relé acciona un contactor, no la bomba directo.
- Panel solar 6 V y batería LiTio 18650 con su portapilas. dimensiona según la sección de arriba, no según lo que se ve más barato.
- Cables Dupont hembra hembra y una caja estanca IP65 para montar todo sobre la tapa del estanque.
Equivalencias: donde el tutorial original habla de una PCB propia diseñada por el autor y distribuida por su Patreon, no la necesitas para nada. Esa placa solo integra en un circuito impreso lo mismo que tú vas a armar con el ESP32, el regulador y el portabatería. El proyecto funciona idéntico sobre protoboard o sobre una placa perforada.
Dos ítems que probablemente tengas que conseguir fuera de una tienda de electrónica: el contactor para la bomba (va en una casa de materiales eléctricos) y el prensacable para pasar el cable del sensor por la tapa del estanque manteniendo el sellado.
Recursos
- Tutorial original: I Built a Solar Powered Water Tank Level Sensor, de Engr. Shahzada Fahad en Electronic Clinic
- Librería Adafruit SSD1306: github.com/adafruit/Adafruit_SSD1306
- Librería Adafruit GFX: github.com/adafruit/Adafruit GFX Library
- Librería Blynk para Arduino: github.com/blynkkk/blynk library
- Documentación de Blynk IoT: docs.blynk.io
- Datasheet del A02YYUW: wiki.dfrobot.com
Artículo inspirado en el proyecto de Electronic Clinic, reorganizado por bloques temáticos y ampliado con la física de la medición ultrasónica en estanques, el procedimiento de calibración paso a paso, el límite de 255 cm de la EEPROM, el dimensionamiento real del sistema solar y las precauciones de potencia para accionar una bomba en 220 V. Versión chilena con componentes en stock local en MechatronicStore.




