ESP32 y Arduino

Sensor de nivel de depósito de agua con ESP32 y MQTT: porcentaje de llenado y alertas de nivel bajo

Mide el nivel de un depósito con un sensor ultrasónico impermeable, convierte la distancia en litros y porcentaje de llenado en un ESP32 y publica lecturas MQTT retenidas junto con alertas de nivel bajo sin rebotes.

Montaje de un sensor de nivel de depósito de agua con ESP32 y una sonda ultrasónica en la tapa que envía el porcentaje de llenado por MQTT
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

Un sensor de nivel de depósito de agua con ESP32 es uno de los primeros proyectos IoT más útiles: responde a "¿cuánta agua queda?" desde cualquier lugar y te avisa antes de que una bomba trabaje en seco. Sirve igual para un depósito en una casa de campo en España que para un tanque, una cisterna o un tinaco en América Latina. En este tutorial conectarás un sensor ultrasónico impermeable, convertirás su lectura de distancia en porcentaje de llenado y litros, y publicarás ambos valores en un broker MQTT junto con una alerta de nivel bajo con antirrebote.

Cómo funciona la medición ultrasónica del nivel de un depósito

Un sensor ultrasónico montado en la tapa del depósito emite un breve pulso de sonido hacia abajo y mide cuánto tarda el eco en volver desde la superficie del agua. La distancia es la mitad del tiempo de ida y vuelta multiplicada por la velocidad del sonido. El nivel de agua es entonces simplemente la profundidad del depósito menos esa distancia.

        sensor (lid)
   ---------[S]---------   <- distance d measured by the sensor
   |         |         |
   |         v         |
   |~~~~~~~~~~~~~~~~~~~|   <- water surface
   |                   |   level = tank_depth - (d - mount_offset)
   |                   |
   ---------------------   <- tank bottom

La medición ultrasónica es sin contacto, así que no hay nada sumergido en el agua, y funciona con cualquier líquido que no haga espuma. Sus puntos débiles son un alcance mínimo (una "zona ciega" cerca del sensor), la condensación sobre el transductor y los ecos de las paredes o tuberías en depósitos estrechos. Los tres se pueden controlar con una buena ubicación y un buen filtrado.

Lista de materiales

Componente Función
ESP32 DevKit (cualquier variante con Wi-Fi) Lee el sensor y publica por MQTT
Sensor ultrasónico impermeable JSN-SR04T o AJ-SR04M Mide la distancia hasta la superficie del agua
Resistencias de 1 kΩ y 2 kΩ Divisor de tensión para la señal de eco de 5 V
Fuente de alimentación de 5 V Alimenta el sensor y el ESP32 a través del pin 5V
Tramo corto de tubo de PVC (opcional) Tubo tranquilizador que reduce ecos y ondulaciones

El HC-SR04, más barato, sirve para una prueba en el banco, pero no es impermeable y se corroe rápidamente en el aire húmedo sobre un depósito. Una sonda sellada con cable es la opción correcta para una instalación real.

Conexión del sensor al ESP32

Pin del JSN-SR04T Pin del ESP32 Notas
5V 5V (VIN) El sensor necesita 5 V para un alcance fiable
GND GND Masa común
Trig GPIO 5 Acepta un pulso de disparo de 3,3 V
Echo GPIO 18 a través del divisor 1 kΩ en serie, 2 kΩ a GND: unos 3,3 V en el pin

No conectes el pin de eco directamente: los GPIO del ESP32 no toleran 5 V. El divisor se monta en unos segundos y te ahorra una placa.

Calibración del depósito

Antes de escribir código, mide tres valores con una cinta métrica:

  1. Desplazamiento de montaje: distancia desde la cara del sensor hasta la línea de agua "lleno". Mantenla por encima de la zona ciega del sensor, normalmente entre 20 y 25 cm en el JSN-SR04T.
  2. Profundidad útil: distancia desde la línea de lleno hasta la salida o el nivel más bajo que te interese.
  3. Capacidad en litros en la línea de lleno. En un cilindro vertical, los litros equivalen al área de la sección en decímetros cuadrados multiplicada por la profundidad en decímetros; en depósitos irregulares, usa el dato del fabricante.

El porcentaje de llenado es entonces (usable_depth - (distance - mount_offset)) / usable_depth * 100, limitado entre 0 y 100.

El sketch de nivel de depósito de agua para ESP32

El sketch toma siete lecturas, usa la mediana para descartar ecos espurios, calcula nivel, porcentaje y litros, y publica un mensaje JSON cada 30 segundos. Instala antes PubSubClient desde el Gestor de librerías.

Topic Retenido Payload de ejemplo
connectmq/demo/tank-91c2/level sí {"distance_cm":63.4,"level_cm":96.6,"percent":64.4,"litres":644}
connectmq/demo/tank-91c2/alert sí LOW u OK
connectmq/demo/tank-91c2/status sí online / offline (Last Will)

Sustituye tank-91c2 por tu propio identificador aleatorio, porque el sketch publica en broker.connectmq.com, el servidor MQTT público gratuito, que cualquiera puede leer y que solo sirve para pruebas.

#include <WiFi.h>
#include <PubSubClient.h>

const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST     = "broker.connectmq.com";
const uint16_t MQTT_PORT  = 1883;
const char* CLIENT_ID     = "tank-91c2-esp32";
const char* TOPIC_LEVEL   = "connectmq/demo/tank-91c2/level";
const char* TOPIC_ALERT   = "connectmq/demo/tank-91c2/alert";
const char* TOPIC_STATUS  = "connectmq/demo/tank-91c2/status";

const int TRIG_PIN = 5;
const int ECHO_PIN = 18;

// Calibración del depósito (centímetros / litros)
const float MOUNT_OFFSET_CM = 25.0;   // cara del sensor a línea de lleno
const float USABLE_DEPTH_CM = 150.0;  // línea de lleno a línea de vacío
const float CAPACITY_L      = 1000.0;

// Histéresis de la alerta: alarma por debajo del 20 %, se borra por encima del 25 %
const float LOW_ON_PCT  = 20.0;
const float LOW_OFF_PCT = 25.0;

const uint32_t PUBLISH_INTERVAL_MS = 30000;
const float SOUND_CM_PER_US = 0.0343;  // a unos 20 grados C

WiFiClient net;
PubSubClient mqtt(net);
uint32_t lastPublish = 0;
bool lowAlarm = false;
bool alarmPublished = false;

float readDistanceOnce() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(5);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(20);              // el JSN-SR04T prefiere un disparo más largo
  digitalWrite(TRIG_PIN, LOW);
  unsigned long us = pulseIn(ECHO_PIN, HIGH, 30000);  // timeout de 30 ms
  if (us == 0) return NAN;
  return us * SOUND_CM_PER_US / 2.0;
}

float readDistanceMedian() {
  const int N = 7;
  float v[N];
  int n = 0;
  for (int i = 0; i < N; i++) {
    float d = readDistanceOnce();
    if (!isnan(d)) v[n++] = d;
    delay(60);                        // deja que se extingan los ecos
  }
  if (n < 3) return NAN;
  for (int i = 1; i < n; i++) {       // ordenación por inserción
    float key = v[i];
    int j = i - 1;
    while (j >= 0 && v[j] > key) { v[j + 1] = v[j]; j--; }
    v[j + 1] = key;
  }
  return v[n / 2];
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) delay(500);
}

void connectMqtt() {
  while (!mqtt.connected()) {
    if (mqtt.connect(CLIENT_ID, TOPIC_STATUS, 1, true, "offline")) {
      mqtt.publish(TOPIC_STATUS, "online", true);
      alarmPublished = false;         // republica el estado de la alerta tras reconectar
    } else {
      delay(5000);
    }
  }
}

void publishLevel() {
  float d = readDistanceMedian();
  if (isnan(d)) {
    mqtt.publish(TOPIC_LEVEL, "{\"error\":\"no_echo\"}", true);
    return;
  }
  float level = USABLE_DEPTH_CM - (d - MOUNT_OFFSET_CM);
  level = constrain(level, 0.0f, USABLE_DEPTH_CM);
  float pct = level / USABLE_DEPTH_CM * 100.0;
  float litres = CAPACITY_L * pct / 100.0;

  char json[160];
  snprintf(json, sizeof(json),
           "{\"distance_cm\":%.1f,\"level_cm\":%.1f,\"percent\":%.1f,\"litres\":%.0f,\"rssi\":%d}",
           d, level, pct, litres, WiFi.RSSI());
  mqtt.publish(TOPIC_LEVEL, json, true);
  Serial.println(json);

  bool prev = lowAlarm;
  if (!lowAlarm && pct < LOW_ON_PCT) lowAlarm = true;
  if (lowAlarm && pct > LOW_OFF_PCT) lowAlarm = false;
  if (lowAlarm != prev || !alarmPublished) {
    mqtt.publish(TOPIC_ALERT, lowAlarm ? "LOW" : "OK", true);
    alarmPublished = true;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  connectWifi();
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  connectMqtt();
  publishLevel();
  lastPublish = millis();
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) connectWifi();
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();
  if (millis() - lastPublish >= PUBLISH_INTERVAL_MS) {
    lastPublish = millis();
    publishLevel();
  }
}

Observa las lecturas desde tu computadora:

mosquitto_sub -h broker.connectmq.com -p 1883 -i tank-91c2-viewer -t "connectmq/demo/tank-91c2/#" -v

Como los topics de nivel, alerta y estado son retenidos, un panel en el teléfono que se conecte en cualquier momento muestra el estado actual de inmediato en lugar de esperar hasta 30 segundos. Si el flag de retención y el Last Will son nuevos para ti, Last Will MQTT en ESP32 explica ambos.

Por qué la alerta usa histéresis

La superficie del agua se ondula, sobre todo mientras una bomba llena o extrae. Con un único umbral del 20 por ciento, una lectura que oscilara entre 19,8 y 20,3 activaría y desactivaría la alerta en cada ciclo e inundaría de mensajes a todos los suscriptores. Dos umbrales (activación por debajo de 20, desactivación por encima de 25) crean una zona muerta que absorbe ese ruido. Además, el sketch solo publica la alerta cuando cambia el estado, y una vez después de cada reconexión, lo que mantiene el tráfico bajo y el valor retenido correcto.

Puedes añadir una segunda capa sin tocar el firmware. En el portal de ConnectMQ, las reglas de alerta por umbral vigilan un topic y registran una entrada en el log de alertas cuando un valor cruza tu límite, algo útil como comprobación independiente de la lógica del dispositivo.

Consejos de precisión para un sensor de nivel de depósito de agua con ESP32

  1. Monta el sensor perfectamente vertical y lejos de la entrada, para que el agua que cae y las ondulaciones no interfieran con el haz.
  2. Usa un tubo tranquilizador en depósitos estrechos o turbulentos: un tubo de PVC de la tapa al fondo con un pequeño orificio cerca de la base ofrece una superficie en calma y una trayectoria de eco limpia.
  3. Respeta la zona ciega. Si el agua puede subir hasta los primeros 20 a 25 cm, eleva el sensor con un tramo corto de tubo.
  4. Compensa la temperatura si el depósito está en un desván caluroso o en un cobertizo helado. La velocidad del sonido aumenta con la temperatura del aire, aproximadamente 0,6 m/s por grado Celsius; un DS18B20 junto al sensor te permite corregirla.
  5. Vigila la condensación. Las gotas sobre el transductor provocan lecturas "sin eco". Un pequeño orificio de ventilación en la tapa ayuda.
  6. Valida con una medición manual con varilla en varios niveles y ajusta MOUNT_OFFSET_CM hasta que coincidan.

Depósitos alimentados por batería

Los depósitos remotos a menudo no tienen red eléctrica. El sketch anterior mantiene Wi-Fi y MQTT conectados de forma permanente, lo que agota las baterías rápidamente. Para un nodo solar o a batería, reestructúralo: despertar, tomar una lectura, conectar, publicar el nivel y la alerta retenidos y entrar en deep sleep durante 10 o 15 minutos. Los mensajes retenidos importan todavía más en ese diseño, porque el dispositivo está desconectado la mayor parte del tiempo. La guía de deep sleep con MQTT en ESP32 muestra el patrón completo, y puedes alimentar el sensor a través de un MOSFET para que no consuma nada mientras el ESP32 duerme.

Paso a producción

El área pública de broker.connectmq.com es perfecta para el desarrollo, pero no para datos de producción: cualquiera que adivine tu topic puede leer todo lo que hay en ella. Antes de instalar el sensor de forma permanente:

  • Crea un broker privado y copia desde el portal el nombre de usuario, la contraseña y el client ID generados.
  • Cambia a WiFiClientSecure, el puerto 8883 y el certificado de CA ISRG Root X1.
  • Conéctate con mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD", TOPIC_STATUS, 1, true, "offline").

Operar tu propio broker para unos pocos depósitos rara vez compensa el mantenimiento, por eso un broker MQTT gestionado es la opción habitual. Si antes quieres comparar opciones de alojamiento, lee sobre el enfoque del broker MQTT en la nube, y para payloads estructurados la guía de ArduinoJson con MQTT es una alternativa más limpia a snprintf. Encontrarás más proyectos con sensores en el tutorial de IoT con ESP32.

Empieza con ConnectMQ

Tu monitor de depósito merece un endpoint privado y cifrado. ConnectMQ ofrece TLS en el puerto 8883, credenciales generadas, un explorador de topics en vivo y reglas de alerta por umbral en un solo portal. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, y cambia tres líneas del sketch.

Preguntas frecuentes

¿Qué sensor ultrasónico es el mejor para un depósito de agua?

Una sonda sellada e impermeable como el JSN-SR04T o el AJ-SR04M. El HC-SR04 está bien para pruebas en el banco, pero no aguanta mucho tiempo la humedad sobre un depósito.

¿Por qué mi sensor indica que el depósito está lleno cuando no lo está?

El agua o algún obstáculo está dentro de la zona ciega, o el eco rebota en una tubería o una pared. Eleva el sensor, apúntalo a una zona despejada o añade un tubo tranquilizador.

¿Con qué frecuencia debe publicar el ESP32 el nivel del depósito?

Cada 30 a 60 segundos es más que suficiente para la mayoría de los depósitos domésticos; los niveles cambian despacio. Con batería, cada 10 a 15 minutos con deep sleep es un buen equilibrio.

¿Puedo usarlo para gasóleo u otros líquidos?

La medición ultrasónica funciona con la mayoría de los líquidos que no forman espuma, pero los depósitos de combustible tienen normas de seguridad sobre la electrónica en zonas peligrosas. Consulta la normativa y usa equipos certificados cuando sea obligatorio.

Artículos relacionados

Ejemplo de ESP-IDF MQTT: crea un cliente esp-mqtt desde cero

Construye un proyecto ESP-IDF desde una carpeta vacía con el componente esp-mqtt: configuración de Wi-Fi en modo estación, el manejador de eventos MQTT, publicación y suscripción, propiedades de MQTT 5 y TLS con el bundle de certificados.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.