Un lector de contador de agua MQTT convierte la esfera de tu sótano en datos de consumo en directo: litros al día, caudal actual y un aviso temprano cuando el agua sigue corriendo cuando no debería. En este tutorial elegirás el sensor adecuado para tu contador, contarás pulsos con un ESP32, mantendrás el total a salvo de los cortes de luz y lo publicarás en un broker MQTT. También añadirás una lógica de detección de fugas que detecta un grifo que gotea o una tubería reventada.
Cómo elegir un sensor para tu contador de agua
La mayoría de los contadores de agua domésticos son mecánicos y no tienen puerto de datos. Se leen detectando algo que se mueve una vez por litro (o por cada diez litros). Revisa tu contador antes de comprar piezas:
| Característica del contador | Sensor | Cómo funciona | Notas |
|---|---|---|---|
| Cable de salida de pulsos o marca "pulse-ready" | Módulo de contacto reed del fabricante del contador | Se cierra una vez por litro o cada 10 litros | El más fiable, señal limpia |
| Imán giratorio (algunos contadores) | Interruptor reed o sensor Hall pegado encima | El imán pasa por el sensor en cada vuelta | Compruébalo con una app de brújula en el móvil |
| Medio disco metálico en la esfera de litros | Sensor de proximidad inductivo (p. ej. LJ18A3-8-Z/BX) | Detecta el metal al pasar | Opción habitual; suele necesitar alimentación de 5-12 V y un adaptador de nivel o divisor |
| Marca reflectante en la esfera | Sensor reflectivo de infrarrojos (tipo TCRT5000) | Detecta el segmento claro/oscuro | Sensible a la alineación y a la luz ambiente |
| Sin ninguna característica aprovechable | Cámara con reconocimiento de dígitos (ESP32-CAM) | Lee los dígitos ópticamente | El proyecto "AI-on-the-edge-device" publica en MQTT por sí mismo |
Nunca abras ni modifiques el propio contador; normalmente pertenece a la compañía de suministro. Todos los sensores anteriores se sujetan o se pegan por fuera.
Cableado del ESP32
Este tutorial usa una señal de pulsos en el GPIO 25 que pasa a nivel bajo una vez por litro. Con un interruptor reed, conéctalo entre el GPIO 25 y GND y usa la resistencia pull-up interna. Con un sensor inductivo NPN alimentado a entre 5 y 12 V, conecta su salida al GPIO 25 con el pull-up interno activado; las salidas NPN de colector abierto llevan la línea a nivel bajo y nunca la elevan por encima de 3,3 V, pero revisa la hoja de datos de tu sensor, ya que algunos módulos incluyen un pull-up a su tensión de alimentación y entonces es necesario un divisor de tensión.
Decisiones de diseño que importan
Un lector de contador de agua parece sencillo, pero tres detalles deciden si se puede confiar en sus cifras:
- El total debe sobrevivir a los reinicios. Si el ESP32 se reinicia y cuenta desde cero, tu gráfico de consumo diario se rompe. El sketch guarda el total en la flash (NVS) y te permite ajustarlo para que coincida con la esfera física.
- Desgaste de la flash. Escribir en la flash con cada pulso la desgasta rápidamente. Guarda cuando el recuento haya cambiado y como mucho cada pocos minutos, aceptando que un corte de luz puede perder unos pocos litros.
- Antirrebote. Los contactos mecánicos rebotan, y un sensor inductivo puede vibrar en el borde del metal. Un breve antirrebote por software evita los recuentos dobles.
Topics para este montaje:
connectmq/demo/<random-id>/water/main/telemetry JSON every 60 s, retained
connectmq/demo/<random-id>/water/main/status online/offline (Last Will), retained
connectmq/demo/<random-id>/water/main/set/total_l command: set the total to match the dial
El sketch ESP32 del contador de agua MQTT
Instala PubSubClient de Nick O'Leary desde el gestor de bibliotecas. Preferences forma parte del núcleo Arduino para ESP32. El sketch se conecta al servidor MQTT público gratuito en broker.connectmq.com, útil para pruebas y prototipos pero no para datos de producción.
#include <WiFi.h>
#include <PubSubClient.h>
#include <Preferences.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* PREFIX = "connectmq/demo/w8m3d1/water/main";
const int PIN_PULSE = 25;
const float LITRES_PER_PULSE = 1.0; // 10.0 en muchos módulos de pulsos del fabricante
const uint32_t SAVE_INTERVAL_MS = 5UL * 60 * 1000;
const uint32_t PUBLISH_INTERVAL_MS = 60UL * 1000;
WiFiClient net;
PubSubClient mqtt(net);
Preferences prefs;
volatile uint32_t pulses = 0;
volatile uint32_t lastPulseMs = 0;
uint64_t baseLitresX10 = 0; // total guardado, en unidades de 0,1 L
uint32_t savedPulses = 0, lastSaveMs = 0, lastPublishMs = 0, pulsesAtPublish = 0;
char clientId[32], topicStatus[96], topicTelemetry[96], topicSet[96];
void IRAM_ATTR onPulse() {
uint32_t now = millis();
if (now - lastPulseMs > 50) { pulses++; lastPulseMs = now; } // antirrebote de 50 ms
}
double totalLitres() {
noInterrupts(); uint32_t p = pulses; interrupts();
return baseLitresX10 / 10.0 + p * LITRES_PER_PULSE;
}
void saveTotal() {
noInterrupts(); uint32_t p = pulses; interrupts();
if (p == savedPulses) return; // nada nuevo, sin escritura en flash
prefs.putULong64("total_x10", baseLitresX10 + (uint64_t)(p * LITRES_PER_PULSE * 10));
savedPulses = p;
}
void onMessage(char* topic, byte* payload, unsigned int len) {
if (strcmp(topic, topicSet) != 0) return;
char buf[24];
len = min(len, (unsigned int)sizeof(buf) - 1);
memcpy(buf, payload, len); buf[len] = 0;
double litres = atof(buf);
if (litres <= 0) return;
noInterrupts(); pulses = 0; interrupts();
baseLitresX10 = (uint64_t)(litres * 10);
savedPulses = 0; pulsesAtPublish = 0;
prefs.putULong64("total_x10", baseLitresX10);
Serial.printf("Total set to %.1f L\n", litres);
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(clientId, nullptr, nullptr, topicStatus, 1, true, "offline")) {
mqtt.publish(topicStatus, "online", true);
mqtt.subscribe(topicSet, 1);
} else {
Serial.printf("MQTT failed, state=%d\n", mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
prefs.begin("water", false);
baseLitresX10 = prefs.getULong64("total_x10", 0);
pinMode(PIN_PULSE, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_PULSE), onPulse, FALLING);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
snprintf(clientId, sizeof(clientId), "water-%llx", ESP.getEfuseMac());
snprintf(topicStatus, sizeof(topicStatus), "%s/status", PREFIX);
snprintf(topicTelemetry, sizeof(topicTelemetry), "%s/telemetry", PREFIX);
snprintf(topicSet, sizeof(topicSet), "%s/set/total_l", PREFIX);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
connectMqtt();
lastSaveMs = lastPublishMs = millis();
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
uint32_t now = millis();
if (now - lastSaveMs >= SAVE_INTERVAL_MS) { saveTotal(); lastSaveMs = now; }
if (now - lastPublishMs >= PUBLISH_INTERVAL_MS) {
noInterrupts(); uint32_t p = pulses; interrupts();
float litresInterval = (p - pulsesAtPublish) * LITRES_PER_PULSE;
float flowLpm = litresInterval / ((now - lastPublishMs) / 60000.0);
char payload[160];
snprintf(payload, sizeof(payload),
"{\"total_l\":%.1f,\"flow_lpm\":%.2f,\"interval_l\":%.1f,\"rssi\":%d}",
totalLitres(), flowLpm, litresInterval, WiFi.RSSI());
mqtt.publish(topicTelemetry, payload, true);
Serial.println(payload);
pulsesAtPublish = p;
lastPublishMs = now;
}
}
Flashea la placa, abre un grifo y observa el monitor serie. Después suscríbete desde tu ordenador:
mosquitto_sub -h broker.connectmq.com -p 1883 -i water-view-2207 -t "connectmq/demo/w8m3d1/water/#" -v
Lee la esfera física y ajusta el total para que coincida con ella, en litros:
mosquitto_pub -h broker.connectmq.com -p 1883 -i water-set-2208 -t "connectmq/demo/w8m3d1/water/main/set/total_l" -m "482137"
El comando de ajuste no se retiene a propósito, de modo que se aplica una sola vez y no restablece el total cada vez que el ESP32 se reconecta. Si algo no consigue conectarse, el tutorial de ESP32 con MQTT incluye una sección de resolución de problemas paso a paso.
Detección de fugas con los datos del contador de agua MQTT
Los datos de consumo se vuelven valiosos cuando te avisan. Dos patrones detectan la mayoría de las fugas:
- Caudal continuo. En la mayoría de los hogares hay periodos sin caudal cada hora. Si
interval_lha estado por encima de cero en todos los intervalos de un minuto durante, por ejemplo, dos horas, algo está corriendo: la válvula de llenado de una cisterna, un grifo que gotea o una zona de riego atascada en abierto. - Caudal alto durante demasiado tiempo. Una manguera o una tubería reventada se manifiesta como un
flow_lpmalto que se prolonga mucho más que una ducha o un baño. Elige un umbral basado en tus picos normales tras una semana de datos.
El primer patrón necesita estado a lo largo del tiempo y encaja bien en Node-RED o en un breve suscriptor en Python. El segundo es un umbral simple; en el portal de ConnectMQ puedes crear una regla de alerta por umbral sobre flow_lpm y ver las alertas activadas en el registro de alertas. Para la detección puntual bajo fregaderos y lavadoras, combínalo con los sensores específicos de la guía de detección de fugas de agua; el contador te dice que el agua está corriendo y el sensor de suelo te dice dónde.
Consumo diario y mensual
El ESP32 publica un total acumulado en lugar de cifras diarias, y es intencionado: los totales son fáciles de mantener correctos, y cualquier suscriptor puede obtener el consumo diario, semanal o mensual calculando la diferencia entre el primer y el último total de cada periodo. Telegraf con InfluxDB, el asistente utility meter de Home Assistant o un pequeño script que escriba en SQLite funcionan así. Mantén el dispositivo sencillo y haz los cálculos de calendario aguas abajo, donde las zonas horarias y los periodos de facturación son más fáciles de gestionar que en un microcontrolador.
Lista de comprobación de precisión y fiabilidad
- Compara con la esfera cada semana durante el primer mes y ajusta
LITRES_PER_PULSEo la posición del sensor si las lecturas se desvían. - Comprueba si hay recuentos dobles sacando 10 litros medidos con un cubo; el recuento debería aumentar exactamente en 10 pulsos a 1 L por pulso.
- Protege el sensor de la condensación. Las arquetas de contadores y las tuberías de agua fría sudan; usa un sensor sellado y mantén el ESP32 en una caja seca.
- Cuidado con el Wi-Fi en sótanos. Las señales débiles provocan reconexiones; la telemetría retenida y el estado Last Will hacen visibles las interrupciones.
- Recurre a tu fontanero o a la compañía de suministro para cualquier cosa que afecte al propio contador.
Paso a un broker privado
El consumo de agua revela las rutinas diarias y cuándo la casa está vacía, y cualquiera podría publicar en tu topic de ajuste en un broker compartido. Cuando el lector funcione, cambia a un broker privado con credenciales y TLS. Un broker MQTT gestionado como ConnectMQ te lo ofrece sin alojar nada en casa. Usa WiFiClientSecure con el certificado ISRG Root X1 de Let's Encrypt, el puerto 8883 y tus credenciales del portal:
#include <WiFiClientSecure.h>
WiFiClientSecure net; // en lugar de WiFiClient
// setup(): net.setCACert(ISRG_ROOT_X1_PEM);
// mqtt.setServer("broker.connectmq.com", 8883);
// mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD", topicStatus, 1, true, "offline");
El consumo de agua encaja de forma natural junto a los datos de electricidad de un monitor de energía doméstico. Para más ideas de automatización, consulta la guía de automatización IoT, y para las ventajas e inconvenientes del alojamiento, la visión general del broker MQTT en la nube.
Controla tu consumo de agua con ConnectMQ
ConnectMQ ofrece a tu lector de contador un broker MQTT privado protegido con TLS, con un explorador de topics en directo y reglas de alerta por umbral para avisos de fugas basados en el caudal. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, y recibe tus primeras lecturas en pocos minutos.
Preguntas frecuentes
¿Puedo leer cualquier contador de agua con MQTT?
La mayoría de los contadores mecánicos pueden leerse desde fuera con un sensor reed, inductivo u óptico, o con una cámara que lea los dígitos. Los contadores inteligentes con protocolos inalámbricos necesitan, en cambio, un receptor compatible.
¿Cuántos litros representa un pulso?
Depende del contador y del sensor. Un sensor sobre la esfera de litros suele dar un pulso por litro, mientras que los módulos de pulsos del fabricante a menudo dan un pulso cada 10 litros. Compruébalo con la esfera para confirmarlo.
¿Se perderá el total tras un corte de luz?
No con el sketch anterior. El total se guarda en la flash del ESP32 cada pocos minutos cuando cambia, así que como mucho se pueden perder unos minutos de pulsos, y puedes volver a sincronizarlo con el comando de ajuste.
¿Cómo detecto una fuga lenta?
Busca caudal en todos los intervalos durante un periodo largo, como dos horas por la noche. Los hogares normales tienen periodos regulares sin caudal, así que un caudal pequeño y continuo es un fuerte indicio de fuga.



