La detección de fugas de agua con IoT convierte un goteo silencioso bajo el fregadero en una alarma en tu móvil y, mejor aún, en una válvula cerrada antes de que se estropee el suelo. En este tutorial construirás sensores de fugas ESP32 de bajo coste que publican alarmas por MQTT, y un controlador de válvula independiente que se suscribe a todos los sensores y corta automáticamente el suministro de agua, permaneciendo cerrado hasta que una persona lo rearma.
Visión general del sistema
El diseño separa deliberadamente la detección de la acción:
- Los nodos sensores de fugas se colocan allí donde puede aparecer agua: bajo los fregaderos, detrás de la lavadora, junto al calentador de agua. Cada uno publica un estado retenido
dryowet. - El broker distribuye las alarmas a todos los interesados.
- Un controlador de válvula junto a la acometida principal se suscribe a todos los topics de fugas con un único comodín y cierra una válvula de bola motorizada al primer
wet. - Tu móvil, Home Assistant o Node-RED también se suscriben, para las notificaciones y el historial.
Separar los papeles significa que puedes añadir sensores sin tocar el código de la válvula, y que cualquier número de aplicaciones puede vigilar la misma alarma. También significa que el controlador de válvula debe fallar de forma segura, algo que tratamos más abajo.
Dónde colocar los sensores de fugas
Las fugas empiezan en lugares previsibles. Coloca los sensores en el punto más bajo cerca de cada uno de ellos, porque el agua corre hacia abajo y se acumula antes de extenderse:
| Ubicación | Causa típica | Consejo de colocación |
|---|---|---|
| Bajo el fregadero de la cocina | Sifón suelto, latiguillo flexible averiado | Al fondo de la base del mueble, bajo las llaves de paso |
| Detrás de la lavadora | Manguera de entrada reventada, desagüe obstruido | En el suelo, justo debajo de las conexiones de las mangueras |
| Junto al calentador de agua | Depósito corroído, descarga de la válvula de seguridad | Dentro de la bandeja de goteo o en su borde más bajo |
| Lavavajillas | Junta de la puerta, válvula de entrada | Bajo el zócalo, si es accesible |
| Inodoros y muebles de lavabo | Válvula de llenado, tubo de alimentación agrietado | Detrás de la base del inodoro, dentro del mueble |
| Sótano o cuarto de instalaciones | Acometida principal, fallo de la bomba de achique | Cerca del sumidero, en la esquina más baja |
Nombra cada sensor por su ubicación en lugar de con un número. Cuando llega una alarma a las 3 de la madrugada, laundry te dice adónde correr; sensor-4, no.
Lista de materiales
| Pieza | Cantidad | Función |
|---|---|---|
| ESP32 DevKit | 1 por sensor + 1 para la válvula | Wi-Fi y MQTT |
| Dos tornillos de acero inoxidable o una sonda de PCB | 1 par por sensor | Electrodos que el agua puentea |
| Resistencia de 100 kΩ | 1 por sensor | Pull-down para la lectura analógica |
| Válvula de bola motorizada de 12 V con finales de carrera | 1 | Corta el suministro principal |
| Módulo de relés de 2 canales | 1 | Acciona los cables de apertura/cierre de la válvula |
| Fuente de alimentación de 12 V | 1 | Alimentación de la válvula |
| Zumbador opcional | 1 por sensor | Alarma local sin red |
Las sondas de fugas comerciales con dos contactos funcionan igual de bien que unos tornillos atravesando una cajita de plástico. Lo que importa es que los electrodos queden uno o dos milímetros por encima del suelo, para que una fina película de agua los puentee.
Cableado de un sensor de fugas
El agua conduce mal comparada con un cable, y una tensión continua constante en los electrodos provoca corrosión al cabo de unos meses. La solución es alimentar la sonda solo mientras se mide:
| Conexión | Pin del ESP32 |
|---|---|
| Electrodo A | GPIO 25 (excitación, en HIGH solo durante el muestreo) |
| Electrodo B | GPIO 34 (entrada ADC) |
| Resistencia de 100 kΩ | De GPIO 34 a GND |
| Zumbador opcional | GPIO 26 |
En seco, el ADC lee casi cero. Mojado, la corriente circula desde GPIO 25 a través del agua hasta GPIO 34 y la lectura sube. El agua del grifo suele dar un salto claro; el agua destilada apenas conduce, pero las fugas reales rara vez son de agua destilada.
Sketch del sensor para la detección de fugas de agua IoT
El nodo toma una muestra cada dos segundos, exige tres lecturas húmedas consecutivas antes de dar la alarma (para ignorar una salpicadura al fregar) y publica un estado retenido para que los nuevos suscriptores lo vean al instante.
#include <WiFi.h>
#include <PubSubClient.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* BROKER = "broker.connectmq.com";
const char* BASE = "connectmq/demo/w5n8c1/home1"; // tu propio prefijo aleatorio
const char* LOCATION = "kitchen-sink";
const char* CLIENT_ID = "leak-kitchen-sink-w5n8c1";
const int EXCITE_PIN = 25;
const int SENSE_PIN = 34;
const int BUZZER_PIN = 26;
const int WET_THRESHOLD = 600; // 0..4095, calibra con un paño mojado
const int CONFIRM_SAMPLES = 3;
WiFiClient net;
PubSubClient mqtt(net);
char tState[96], tAvail[96], tRaw[96];
bool wet = false;
int wetCount = 0, dryCount = 0;
int readProbe() {
digitalWrite(EXCITE_PIN, HIGH);
delay(5);
int v = analogRead(SENSE_PIN);
digitalWrite(EXCITE_PIN, LOW);
return v;
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(CLIENT_ID, nullptr, nullptr, tAvail, 1, true, "offline")) {
mqtt.publish(tAvail, "online", true);
mqtt.publish(tState, wet ? "wet" : "dry", true);
} else {
delay(3000);
}
}
}
void setup() {
pinMode(EXCITE_PIN, OUTPUT);
digitalWrite(EXCITE_PIN, LOW);
pinMode(BUZZER_PIN, OUTPUT);
snprintf(tState, sizeof tState, "%s/%s/leak/state", BASE, LOCATION);
snprintf(tAvail, sizeof tAvail, "%s/%s/leak/availability", BASE, LOCATION);
snprintf(tRaw, sizeof tRaw, "%s/%s/leak/raw", BASE, LOCATION);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
mqtt.setServer(BROKER, 1883);
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
static unsigned long last = 0;
if (millis() - last < 2000) return;
last = millis();
int v = readProbe();
if (v > WET_THRESHOLD) { wetCount++; dryCount = 0; }
else { dryCount++; wetCount = 0; }
if (!wet && wetCount >= CONFIRM_SAMPLES) {
wet = true;
mqtt.publish(tState, "wet", true);
} else if (wet && dryCount >= 15) { // 30 s en seco antes de borrar la alarma
wet = false;
mqtt.publish(tState, "dry", true);
}
digitalWrite(BUZZER_PIN, wet ? HIGH : LOW); // la alarma local funciona sin red
static unsigned long lastRaw = 0;
if (millis() - lastRaw > 60000) {
lastRaw = millis();
char buf[8];
snprintf(buf, sizeof buf, "%d", v);
mqtt.publish(tRaw, buf); // útil para calibración y deriva
}
}
El zumbador se acciona localmente, así que una fuga se oye aunque el Wi-Fi esté caído. Ese tipo de respaldo local es un buen hábito en cualquier proyecto relacionado con la seguridad.
El controlador de válvula
El nodo de la válvula se suscribe a <base>/+/leak/state, que coincide con todos los sensores de todas las habitaciones. Al primer wet cierra la válvula y se enclava: la válvula permanece cerrada hasta que alguien envía OPEN y ningún sensor sigue informando de humedad.
#include <WiFi.h>
#include <PubSubClient.h>
#include <set>
#include <string>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* BROKER = "broker.connectmq.com";
const char* BASE = "connectmq/demo/w5n8c1/home1";
const char* CLIENT_ID = "valve-main-w5n8c1";
const int RELAY_OPEN = 32;
const int RELAY_CLOSE = 33;
const unsigned long TRAVEL_MS = 8000; // consulta la hoja de datos de tu válvula
WiFiClient net;
PubSubClient mqtt(net);
char tLeakAll[96], tValve[96], tValveSet[96], tAvail[96];
std::set<std::string> wetSensors;
bool closedLatched = false;
void driveValve(bool close) {
digitalWrite(close ? RELAY_OPEN : RELAY_CLOSE, LOW);
digitalWrite(close ? RELAY_CLOSE : RELAY_OPEN, HIGH);
delay(TRAVEL_MS);
digitalWrite(RELAY_OPEN, LOW);
digitalWrite(RELAY_CLOSE, LOW);
mqtt.publish(tValve, close ? "closed" : "open", true);
}
void onMessage(char* topic, byte* payload, unsigned int len) {
std::string t(topic), p((char*)payload, len);
if (t == tValveSet) {
if (p == "CLOSE") { closedLatched = true; driveValve(true); }
else if (p == "OPEN") {
if (wetSensors.empty()) { closedLatched = false; driveValve(false); }
else mqtt.publish(tValve, "closed (sensor still wet)", true);
}
return;
}
if (p == "wet") {
wetSensors.insert(t);
if (!closedLatched) { closedLatched = true; driveValve(true); }
} else if (p == "dry") {
wetSensors.erase(t);
}
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(CLIENT_ID, nullptr, nullptr, tAvail, 1, true, "offline")) {
mqtt.publish(tAvail, "online", true);
mqtt.subscribe(tLeakAll, 1);
mqtt.subscribe(tValveSet, 1);
} else {
delay(3000);
}
}
}
void setup() {
pinMode(RELAY_OPEN, OUTPUT);
pinMode(RELAY_CLOSE, OUTPUT);
snprintf(tLeakAll, sizeof tLeakAll, "%s/+/leak/state", BASE);
snprintf(tValve, sizeof tValve, "%s/utility/main-valve/state", BASE);
snprintf(tValveSet, sizeof tValveSet, "%s/utility/main-valve/state/set", BASE);
snprintf(tAvail, sizeof tAvail, "%s/utility/main-valve/availability", BASE);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
mqtt.setServer(BROKER, 1883);
mqtt.setCallback(onMessage);
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
}
Como los estados de los sensores son retenidos, el controlador de válvula se entera de cualquier fuga ocurrida mientras se reiniciaba en el momento en que vuelve a suscribirse. Ese es el principal motivo para retener los estados de alarma en lugar de enviar eventos puntuales.
Probar el sistema
Observa todo y luego simula una fuga con un paño mojado o publicando manualmente:
B=connectmq/demo/w5n8c1/home1
mosquitto_sub -h broker.connectmq.com -p 1883 -i mon-w5n8c1 -t "$B/#" -v
mosquitto_pub -h broker.connectmq.com -p 1883 -i sim-w5n8c1 -r -q 1 -t $B/laundry/leak/state -m wet
mosquitto_pub -h broker.connectmq.com -p 1883 -i sim-w5n8c1 -r -q 1 -t $B/laundry/leak/state -m dry
mosquitto_pub -h broker.connectmq.com -p 1883 -i sim-w5n8c1 -q 1 -t $B/utility/main-valve/state/set -m OPEN
Después de la prueba, borra el topic retenido simulado con un mensaje retenido vacío (-r -n). El servidor MQTT público gratuito es ideal para esta fase de banco, pero sus topics son legibles por cualquiera, así que no dejes una válvula real escuchando en él.
Modos de fallo que hay que prever
- El sensor se queda sin alimentación. Su last will publica
offline. Genera también una alerta por ello; un sensor muerto no protege nada. - El controlador de válvula pierde el Wi-Fi. No puede oír las alarmas. Plantéate cablear el sensor más crítico (el del calentador de agua) directamente a un GPIO del controlador de válvula como respaldo cableado.
- La válvula se atasca. Las válvulas de bola que nunca se mueven pueden agarrotarse. Ejercita la válvula cada mes con un CLOSE/OPEN programado durante el día.
- Falsas alarmas. Calibra
WET_THRESHOLDusando el topic raw y mantén el recuento de confirmación. - Corte de la red eléctrica. Un pequeño SAI mantiene vivos el router y el controlador de válvula; los sensores con batería pueden usar deep sleep con un despertar ext0 en la sonda.
Escalado de alertas que la gente realmente percibe
Una alarma que nadie ve no es una alarma. Una escalera de escalado sencilla funciona bien:
- Zumbador local en el sensor, de inmediato y sin necesidad de red.
- Notificación push desde Home Assistant, Node-RED o un pequeño suscriptor en Python en cuanto llega un estado
wet. - La válvula se cierra automáticamente, y el estado cerrado se envía como segunda notificación para que sepas que el suministro está cortado.
- Recordatorios repetidos cada quince minutos mientras algún sensor siga mojado o la válvula esté enclavada.
- Alertas de desconexión cuando un sensor o el controlador de válvula publica
offline, porque el silencio puede ocultar un problema.
Mantén la lógica de notificaciones fuera de los sensores. Los dispositivos publican hechos; los suscriptores deciden a quién avisar y con qué frecuencia.
Paso a un broker privado
Para una instalación real, crea un broker en el portal y cambia ambos sketches a WiFiClientSecure, el puerto 8883, la CA ISRG Root X1 y tus YOUR_USERNAME, YOUR_PASSWORD y YOUR_CLIENT_ID generados. Cada dispositivo necesita su propio client ID. En el portal de ConnectMQ puedes observar todos los topics de fugas en el explorador de topics en vivo y crear reglas de alerta por umbral, por ejemplo sobre el valor bruto de la sonda.
Para las notificaciones en el móvil, Home Assistant puede suscribirse al mismo comodín; nuestra guía de automatizaciones MQTT en Home Assistant muestra la sintaxis de los disparadores. Si además quieres detectar fugas ocultas a partir del consumo nocturno, combina esto con un contador de agua por MQTT. La guía sobre la estructura de topics MQTT para el hogar inteligente explica las convenciones de /set y de estado retenido usadas aquí.
Protege tu hogar con ConnectMQ
Un sistema de fugas tiene que estar online cuando importa, lo que convierte un broker MQTT gestionado en una base sensata. ConnectMQ te ofrece TLS, credenciales generadas y reglas de alerta en un único portal. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, y traslada tus sensores en minutos. Encontrarás más proyectos para el hogar en la guía de automatización IoT.
Preguntas frecuentes
¿Por qué la válvula sigue cerrada cuando el suelo ya está seco?
Es deliberado. Una fuga suele significar que algo se ha roto, así que la válvula se enclava hasta que una persona lo revisa y envía OPEN, lo que solo funciona cuando todos los sensores informan de que están secos.
¿Puedo alimentar los sensores de fugas con baterías?
Sí. Usa deep sleep con un despertar periódico, o despierta con la sonda mediante ext0, y conéctate solo cuando cambie el estado. Cuenta con tener que ajustar el circuito, porque la sonda analógica necesita alimentación para detectar el agua.
¿Por qué usar retained messages para las alarmas de fugas?
Un estado wet retenido se entrega a cualquier cliente en el momento en que se suscribe, de modo que un controlador de válvula o una aplicación que se reinicie durante una fuga se entera de ella de inmediato.
¿Se corroerá la sonda?
Mantener tensión continua en los electrodos de forma constante acelera la corrosión. Alimentar la sonda solo unos milisegundos por muestra, como hace el sketch, y usar electrodos de acero inoxidable la mantiene funcionando durante mucho tiempo.



