Automatisation IoT

Détection de fuite d'eau IoT : capteurs ESP32, alarmes MQTT et vanne d'arrêt automatique

Fabriquez des capteurs de fuite ESP32 qui publient des alarmes MQTT en retained message, et un contrôleur de vanne qui coupe automatiquement l'arrivée d'eau et reste verrouillé jusqu'à ce que quelqu'un le réarme.

Installation de détection de fuite d'eau IoT avec une sonde ESP32 sous un évier et une vanne d'arrêt motorisée sur la canalisation principale
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

La détection de fuite d'eau IoT transforme un goutte-à-goutte silencieux sous l'évier en alarme sur votre téléphone et, mieux encore, en vanne fermée avant que le sol ne soit abîmé. Dans ce tutoriel, vous allez fabriquer des capteurs de fuite ESP32 à bas coût qui publient des alarmes via MQTT, ainsi qu'un contrôleur de vanne distinct qui s'abonne à tous les capteurs et coupe automatiquement l'arrivée d'eau, en restant fermé jusqu'à ce qu'une personne le réarme.

Vue d'ensemble du système

La conception sépare volontairement la détection de l'action :

  1. Les nœuds capteurs de fuite sont placés partout où l'eau peut apparaître : sous les éviers, derrière le lave-linge, à côté du chauffe-eau. Chacun publie un état dry ou wet en retained message.
  2. Le broker distribue les alarmes à tous ceux qui sont intéressés.
  3. Un contrôleur de vanne situé près de l'arrivée principale s'abonne à tous les topics de fuite avec un seul wildcard et ferme une vanne à bille motorisée au premier wet.
  4. Votre téléphone, Home Assistant ou Node-RED s'abonnent également, pour les notifications et l'historique.

Séparer les rôles permet d'ajouter des capteurs sans toucher au code de la vanne, et un nombre quelconque d'applications peut surveiller la même alarme. Cela implique aussi que le contrôleur de vanne doit adopter un comportement sûr en cas de défaillance, ce que nous abordons plus bas.

Où placer les capteurs de fuite

Les fuites apparaissent à des endroits prévisibles. Placez les capteurs au point le plus bas près de chacun d'eux, car l'eau s'écoule vers le bas et forme une flaque avant de s'étendre :

Emplacement Cause typique Conseil de placement
Sous l'évier de la cuisine Siphon desserré, flexible défaillant Au fond du meuble, sous les robinets d'arrêt
Derrière le lave-linge Tuyau d'arrivée éclaté, évacuation bouchée Au sol, directement sous les raccords des tuyaux
À côté du chauffe-eau Cuve corrodée, écoulement du groupe de sécurité Dans le bac de récupération ou à son bord le plus bas
Lave-vaisselle Joint de porte, électrovanne d'arrivée Sous la plinthe si elle est accessible
Toilettes et meubles de salle de bains Robinet flotteur, flexible d'alimentation fissuré Derrière le pied des toilettes, à l'intérieur du meuble
Sous-sol ou buanderie Arrivée principale, panne de pompe de relevage Près du siphon de sol, dans l'angle le plus bas

Nommez chaque capteur selon son emplacement plutôt qu'avec un numéro. Quand une alarme arrive à 3 heures du matin, laundry vous dit où courir ; sensor-4, non.

Liste du matériel

Composant Quantité Rôle
ESP32 DevKit 1 par capteur + 1 pour la vanne Wi-Fi et MQTT
Deux vis en acier inoxydable ou une sonde sur circuit imprimé 1 paire par capteur Électrodes que l'eau relie
Résistance de 100 kΩ 1 par capteur Pull-down pour la lecture analogique
Vanne à bille motorisée 12 V avec fins de course 1 Coupe l'arrivée principale
Module relais 2 canaux 1 Commande les fils d'ouverture et de fermeture de la vanne
Alimentation 12 V 1 Alimentation de la vanne
Buzzer facultatif 1 par capteur Alarme locale sans réseau

Les sondes de fuite toutes faites à deux plots de contact fonctionnent aussi bien que des vis traversant un petit boîtier en plastique. L'important est que les électrodes se trouvent à un ou deux millimètres au-dessus du sol, pour qu'une fine pellicule d'eau les relie.

Câbler un capteur de fuite

L'eau conduit mal comparée à un fil, et une tension continue permanente sur les électrodes provoque de la corrosion au fil des mois. La solution consiste à n'alimenter la sonde que pendant la mesure :

Connexion Broche de l'ESP32
Électrode A GPIO 25 (excitation, mise à HIGH uniquement pendant l'échantillonnage)
Électrode B GPIO 34 (entrée ADC)
Résistance de 100 kΩ GPIO 34 vers GND
Buzzer facultatif GPIO 26

À sec, l'ADC lit une valeur proche de zéro. Mouillée, la sonde laisse passer le courant du GPIO 25 vers le GPIO 34 à travers l'eau, et la valeur grimpe. L'eau du robinet produit généralement un saut net ; l'eau distillée conduit à peine, mais les vraies fuites sont rarement distillées.

Sketch du capteur pour la détection de fuite d'eau IoT

Le nœud effectue une mesure toutes les deux secondes, exige trois lectures humides consécutives avant de déclencher l'alarme (pour ignorer les éclaboussures d'un coup de serpillière) et publie un état en retained message pour que les nouveaux abonnés le voient instantanément.

#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";   // your own random prefix
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, calibrate with a wet cloth
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 dry before clearing
    wet = false;
    mqtt.publish(tState, "dry", true);
  }
  digitalWrite(BUZZER_PIN, wet ? HIGH : LOW);   // local alarm works offline

  static unsigned long lastRaw = 0;
  if (millis() - lastRaw > 60000) {
    lastRaw = millis();
    char buf[8];
    snprintf(buf, sizeof buf, "%d", v);
    mqtt.publish(tRaw, buf);   // useful for calibration and drift
  }
}

Le buzzer est commandé localement : une fuite reste audible même lorsque le Wi-Fi est coupé. Ce type de repli local est une bonne habitude dans tout projet lié à la sécurité.

Le contrôleur de vanne

Le nœud de la vanne s'abonne à <base>/+/leak/state, ce qui couvre chaque capteur de chaque pièce. Au premier wet, il ferme la vanne et se verrouille : la vanne reste fermée jusqu'à ce que quelqu'un envoie OPEN et qu'aucun capteur ne signale plus d'humidité.

#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;   // check your valve's datasheet

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();
}

Comme les états des capteurs sont retenus, le contrôleur de vanne apprend l'existence de toute fuite survenue pendant son redémarrage dès qu'il se réabonne. C'est la principale raison de retenir les états d'alarme plutôt que d'envoyer des événements ponctuels.

Tester le système

Surveillez tout, puis simulez une fuite avec un chiffon mouillé ou en publiant manuellement :

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

Après le test, effacez le topic simulé avec un retained message vide (-r -n). Le serveur MQTT public gratuit est idéal pour cette phase sur l'établi, mais ses topics sont lisibles par tous : ne laissez pas une vraie vanne à l'écoute dessus.

Modes de défaillance à anticiper

  1. Un capteur perd son alimentation. Son last will publie offline. Déclenchez aussi une alerte dans ce cas ; un capteur hors service ne protège rien.
  2. Le contrôleur de vanne perd le Wi-Fi. Il ne peut plus entendre les alarmes. Envisagez de câbler directement le capteur le plus critique (chauffe-eau) sur une GPIO du contrôleur de vanne, comme secours filaire.
  3. La vanne se grippe. Les vannes à bille qui ne bougent jamais peuvent se bloquer. Manœuvrez la vanne une fois par mois avec un CLOSE/OPEN planifié en journée.
  4. Fausses alarmes. Calibrez WET_THRESHOLD à l'aide du topic raw, et conservez le nombre de confirmations.
  5. Coupure de courant secteur. Un petit onduleur maintient en vie la box et le contrôleur de vanne ; les capteurs sur batterie peuvent utiliser le deep sleep avec un réveil ext0 sur la sonde.

Une escalade des alertes que les gens remarquent vraiment

Une alarme que personne ne voit n'est pas une alarme. Une échelle d'escalade simple fonctionne bien :

  1. Buzzer local sur le capteur, immédiatement et sans aucun réseau.
  2. Notification push depuis Home Assistant, Node-RED ou un petit abonné Python dès qu'un état wet arrive.
  3. Fermeture automatique de la vanne, avec l'état fermé envoyé en seconde notification pour que vous sachiez que l'arrivée est coupée.
  4. Rappels répétés toutes les quinze minutes tant qu'un capteur est humide ou que la vanne est verrouillée.
  5. Alertes de déconnexion lorsqu'un capteur ou le contrôleur de vanne publie offline, car le silence peut masquer un problème.

Gardez la logique de notification hors des capteurs. Les appareils publient des faits ; les abonnés décident qui prévenir et à quelle fréquence.

Passer à un broker privé

Pour une vraie installation, créez un broker dans le portail, puis basculez les deux sketches vers WiFiClientSecure, le port 8883, l'autorité de certification ISRG Root X1 et vos YOUR_USERNAME, YOUR_PASSWORD et YOUR_CLIENT_ID générés. Chaque appareil a besoin de son propre client ID. Dans le portail ConnectMQ, vous pouvez suivre chaque topic de fuite dans l'explorateur de topics en direct et créer des règles d'alerte par seuil, par exemple sur la valeur brute de la sonde.

Pour les notifications sur téléphone, Home Assistant peut s'abonner au même wildcard ; notre guide sur les automatisations MQTT dans Home Assistant présente la syntaxe des déclencheurs. Jeedom et Domoticz savent aussi communiquer en MQTT et peuvent s'abonner aux mêmes topics. Si vous voulez aussi repérer les fuites cachées à partir de la consommation nocturne, associez ce montage à un compteur d'eau via MQTT. Le guide sur la structure des topics MQTT pour la maison connectée explique les conventions /set et d'état retenu utilisées ici.

Protégez votre maison avec ConnectMQ

Un système de détection de fuite doit être en ligne au moment où cela compte, ce qui fait d'un broker MQTT managé une base judicieuse. ConnectMQ vous fournit TLS, des identifiants générés et des règles d'alerte dans un seul portail. Créez un broker Serverless gratuit sur app.connectmq.com, sans carte bancaire, et migrez vos capteurs en quelques minutes. D'autres projets pour la maison sont rassemblés dans le guide de l'automatisation IoT.

Questions fréquentes

Pourquoi la vanne reste-t-elle fermée une fois le sol sec ?

C'est voulu. Une fuite signifie généralement que quelque chose est cassé : la vanne reste donc verrouillée jusqu'à ce qu'une personne vérifie et envoie OPEN, ce qui ne fonctionne qu'une fois que tous les capteurs signalent un état sec.

Les capteurs de fuite peuvent-ils fonctionner sur batterie ?

Oui. Utilisez le deep sleep avec un réveil périodique, ou un réveil par la sonde via ext0, et ne vous connectez que lorsque l'état change. Attendez-vous à devoir ajuster le circuit, car la sonde analogique doit être alimentée pour détecter l'eau.

Pourquoi utiliser des retained messages pour les alarmes de fuite ?

Un état wet en retained message est livré à tout client dès son abonnement : un contrôleur de vanne ou une application qui redémarre pendant une fuite en est donc informé immédiatement.

La sonde va-t-elle se corroder ?

Maintenir en permanence une tension continue sur les électrodes accélère la corrosion. N'alimenter la sonde que quelques millisecondes par mesure, comme le fait le sketch, et utiliser des électrodes en acier inoxydable lui assurent une longue durée de vie.

Articles liés

Guide d'installation de Zigbee2MQTT : du coordinateur au tableau de bord

Installez Zigbee2MQTT avec Docker, configurez le coordinateur et la connexion MQTT, appairez et renommez les appareils, pilotez-les via MQTT et affichez leurs états sur un tableau de bord, en testant d'abord sur le serveur MQTT public gratuit.

8 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.