Débitmètre eau
Classification automatique par appareil - un débitmètre à effet Hall sur l'arrivée générale, un ESP32 sous ESPHome, et une couche logicielle qui devine si ce sont les WC, la douche ou la machine à laver qui viennent d'être utilisés. Le tout intégré dans Home Assistant avec une carte Lovelace sur mesure.
Code : github.com/Shad107/ha-water-classifier
Glossaire
Un vocabulaire compact utile avant de dérouler l’article. Aucun de ces termes n’est technique au point d’être obscur, mais ils reviennent souvent et méritent d’être posés.
- YF-B9 : débitmètre à effet Hall pour eau potable, corps laiton/inox, filetage G3/4. La série YF-B* couvre les diamètres 1/2 à 1 pouce, on trouve les variantes B1 à B10 chez le même fournisseur.
- Rotor à ailettes : le petit moulin en plastique noir à l’intérieur du corps du débitmètre. Il tourne proportionnellement au débit et déclenche une impulsion Hall à chaque tour.
- Pulse counter (=PCNT) : périphérique matériel de l’ESP32 qui compte les impulsions sans passer par un lambda logiciel. Précis, insensible aux ratés d’interruption sous forte charge Wi-Fi.
- ESPHome : framework de firmware pour ESP32/ESP8266 orienté domotique, config en YAML, intégration Home Assistant native via l’API chiffrée.
- Home Assistant (=HA) : serveur domotique open source, écosystème d’intégrations, tourne en Docker/LXC/VM/appliance.
- HACS : Home Assistant Community Store, gestionnaire d’intégrations tierces installables depuis GitHub en quelques clics.
- Water-Monitor : custom_component HACS
markaggar/Water-Monitorqui découpe un flux de débit en sessions discrètes (=un événement de consommation continue). Il ne classifie pas. - Cascade classifier : jeu de règles ordonnées de la plus spécifique à la plus générique, la première qui match gagne. Approche déterministe, rapide, sans ML.
- WEUSEDTO : Water End USE Dataset and TOols, projet académique italien qui fournit un dataset labellisé de sessions d’eau réelles + un framework Python de classification par RandomForest.
Contexte
Beaucoup de foyers en France sont désormais équipés d’un compteur d’eau communicant (=Birdz Téléo, m2ocity, Diehl, selon le délégataire local). Ces compteurs remontent bien la consommation au fournisseur, mais côté client on n’a droit qu’à un portail web avec un volume J-1 et un graphe horaire pas très fin. Impossible de voir en direct qu’un robinet fuit ou que la chasse d’eau part en boucle.
Je voulais donc :
- Une mesure live du débit et du volume, avec cumul jour/mois/année
- Une détection de fuite automatique
- Idéalement une classification par usage : combien de chasses d’eau par jour, combien de douches, combien d’arrosage jardin
Alternatives regardées et écartées :
- Reed switch sur le compteur mécanique - imprécis, dépend d’un aimant qui doit être bien placé
- API équivalent GRDF-ADICT côté eau - aucun délégataire français n’expose ce genre d’API grand public, on reste sur des données J-1
- Débitmètres multi-points par appareil - surcoût et surtout travaux plomberie non envisageables
- Solution commerciale type Aguardio - 200 €, propriétaire, cloud obligatoire
La solution retenue est un débitmètre à effet Hall unique posé sur l’arrivée générale, plus une classification logicielle des sessions. Le tell-tale c’est que chaque appareil (=WC 6 L, douche 60 L, machine 60 L répartis en plusieurs cycles) a une signature suffisamment distincte pour qu’un simple jeu de règles fasse ~80 % du travail.
Identification hardware
Le YF-B9

J’ai comparé les YF-B1 à B10 (=même famille, même connectique, plage de débits et précision différentes) et le B9 s’est imposé pour trois raisons :
- Construction copper/stainless steel : eau potable, anti-corrosion long terme, pas de patine
- Filetage G3/4 femelle des deux côtés : le clapet anti-retour G3/4 M en sortie compteur visse directement, plus besoin de manchon intermédiaire
- Précision ±5 % à calibrer sur une chasse Geberit à la pose
Résolution constructeur : 1 pulse tous les 1/476 de litre. Le rotor à ailettes ferme et ouvre le contact d’un capteur Hall à chaque tour. On mesure la fréquence de ces pulses pour calculer le débit instantané, puis on intègre pour avoir un volume cumulé.
Le microcontrôleur
Un M5Stack ATOM Lite basé sur ESP32-PICO-D4. Format 24×24 mm, alimenté en 5 V USB-C, pinout bottom accessible. Sous ESPHome il est utilisable en pulse counter matériel (=PCNT interne de l’ESP32) sans risquer de rater des pulses même à 30 L/min.
Cinq pins utilisés seulement :
| Pin ATOM | Fonction | Fil YF-B9 |
|---|---|---|
| 5V (bottom droit, 3ème) | Alim capteur Hall | 🔴 rouge (VCC) |
| GND (bottom droit, 4ème) | Commun | ⚫ noir (GND) |
| G22 (bottom gauche, 2ème) | Pulse Hall | 🟡 jaune (signal) |
Le pullup interne de l’ESP32 (=~45 kΩ) suffit pour la sortie open-collector du YF-B9, pas besoin de résistance externe.

Programmation ESP32
Sous ESPHome la config est courte mais dense. Objectif : exposer depuis l’ESP tout ce dont Home Assistant a besoin pour l’affichage, la classification et l’alerte fuite, sans dépendre d’un calcul côté HA.
Squelette base ESPHome
esphome:
name: debitmetre-eau
friendly_name: Débitmètre eau
esp32:
board: m5stack-atom
framework:
type: esp-idf
logger:
level: INFO
api:
encryption:
key: !secret debitmetre_api_key
ota:
password: !secret debitmetre_ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
Rien d’exotique côté connectivité : Wi-Fi VLAN IoT, API HA chiffrée, OTA activé pour flasher sans jamais avoir à débrancher physiquement l’ATOM une fois posé.
Le pulse counter avec calibration modifiable en direct
number:
- platform: template
name: "Calibration factor"
id: calibration_factor
min_value: 0.001
max_value: 0.01
step: 0.00001
initial_value: 0.002962
optimistic: true
restore_value: true
mode: box
sensor:
- platform: pulse_counter
pin:
number: GPIO22
mode:
input: true
pullup: true
name: "Débit eau"
id: debit_eau
unit_of_measurement: L/min
accuracy_decimals: 2
update_interval: 10s
filters:
- lambda: return x * id(calibration_factor).state;
- platform: integration
name: "Volume eau total"
id: volume_total
sensor: debit_eau
time_unit: min
unit_of_measurement: L
accuracy_decimals: 3
filters:
- throttle: 10s
Le calibration_factor est un template number modifiable via l’UI Home Assistant. La théorie donne 0.00210 (=1/476) mais dans mon cas la calibration réelle a donné une valeur plus proche de 0.002962. L’écart avec la valeur constructeur vient de la pression réseau et des tolérances de fabrication du corps du débitmètre - chaque exemplaire a son propre facteur à mesurer une fois posé.
La méthode de calibration : plutôt que de remplir un seau bien jaugé (=peu pratique sur une arrivée générale déjà raccordée), j’utilise le mécanisme Geberit des toilettes - la double chasse 3 L / 6 L d’un réservoir Geberit type Sigma / G500 est un volume connu et reproductible, tiré directement en aval du débitmètre. Je note le delta de volume total avant et après chaque tirage :
- Petite chasse (=3 L) : je note le delta, je calcule le facteur correcteur
- Grande chasse (=6 L) : idem, ça permet de recouper avec un volume différent
- Moyenne sur plusieurs tirages pour absorber la variabilité intra-appareil (=le remplissage du réservoir n’est jamais exactement identique, mes trois séries à 6 L ont donné 5.6, 7.3 et 5.6 L de mesure brute avant recalage du facteur)
Les accumulateurs jour / mois / année
globals:
- id: volume_jour_accumulateur
type: float
restore_value: true
initial_value: '0.0'
- id: volume_mois_accumulateur
type: float
restore_value: true
initial_value: '0.0'
- id: volume_annee_accumulateur
type: float
restore_value: true
initial_value: '0.0'
- id: last_volume_total
type: float
restore_value: true
initial_value: '0.0'
interval:
- interval: 10s
then:
- lambda: |-
float current = id(volume_total).state;
if (!isnan(current)) {
float diff;
if (current >= id(last_volume_total)) {
diff = current - id(last_volume_total);
} else {
// Reset détecté (=reboot ESP) → accumuler brut
diff = current;
}
id(volume_jour_accumulateur) += diff;
id(volume_mois_accumulateur) += diff;
id(volume_annee_accumulateur) += diff;
id(last_volume_total) = current;
}
time:
- platform: sntp
on_time:
- hours: 0
minutes: 0
seconds: 0
then:
- lambda: |-
id(volume_jour_accumulateur) = 0;
// Reset mensuel : premier du mois
auto now = id(sntp_time).now();
if (now.day_of_month == 1) {
id(volume_mois_accumulateur) = 0;
// Reset annuel : premier janvier
if (now.month == 1) {
id(volume_annee_accumulateur) = 0;
}
}
id: sntp_time
Piège subtil découvert en prod : le sensor platform: integration part de zéro à chaque reboot de l’ESP, sans restore_value par défaut. Résultat : après un reboot le lambda accumulateur ne comptait plus rien tant que le volume n’avait pas dépassé la valeur pré-reboot (=condition current >= last_volume_total fausse). Fix dans la branche else ci-dessus : on considère qu’un current < last signifie reset, et on accumule la valeur brute au lieu de la différence négative.
Le debug component (=capital)
sensor:
- platform: uptime
name: "Uptime"
update_interval: 60s
- platform: wifi_signal
name: "WiFi RSSI"
update_interval: 60s
text_sensor:
- platform: template
name: "Reset reason"
lambda: |-
auto reason = esp_reset_reason();
switch(reason) {
case ESP_RST_POWERON: return {"Power on"};
case ESP_RST_EXT: return {"External"};
case ESP_RST_SW: return {"Software"};
case ESP_RST_PANIC: return {"Panic"};
case ESP_RST_INT_WDT: return {"WDT interrupt"};
case ESP_RST_TASK_WDT: return {"WDT task"};
case ESP_RST_WDT: return {"WDT other"};
case ESP_RST_BROWNOUT: return {"Brownout"};
default: return {"Unknown"};
}
update_interval: never
Le Reset reason remonté au boot est capital pour comprendre pourquoi l’ESP a rebooté. En prod j’ai eu plusieurs Brownout la première semaine à cause d’un module Synclum sous-dimensionné ; sans cette info je serais parti chercher un bug soft. C’est le premier composant à ajouter sur tout ESP32 en install fixe.
Le YAML complet à copier
Voilà le fichier ESPHome tel qu’il tourne en prod chez moi, dépersonnalisé. Il suffit de le déposer dans ~/config/esphome/debitmetre-eau.yaml, de définir les !secret correspondants dans secrets.yaml (=wifi_ssid, wifi_password, debitmetre_api_key) et de flasher.
# Board: M5Stack ATOM Lite
esphome:
name: debitmetre-eau
friendly_name: debitmetre-eau
esp32:
variant: esp32
flash_size: 4MB
framework:
type: esp-idf
logger:
api:
encryption:
key: !secret debitmetre_api_key
ota:
- platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: debitmetre-eau Fallback Hotspot
password: "CHANGE_ME"
captive_portal:
# ==========================================
# DEBUG / TÉLÉMÉTRIE (=identification root cause crashes)
# ==========================================
debug:
update_interval: 30s
text_sensor:
- platform: debug
device:
name: "Device Info"
reset_reason:
name: "Reset Reason"
# ==========================================
# TIME (=source pour resets périodiques)
# ==========================================
time:
- platform: homeassistant
id: ha_time
on_time:
# Reset compteur jour : tous les jours à minuit
# + check année si on est le jour/mois configuré dans HA
- seconds: 0
minutes: 0
hours: 0
then:
- lambda: |-
id(volume_jour_accumulateur) = 0.0;
auto now = id(ha_time).now();
int cur_day = now.day_of_month;
int cur_month = now.month;
int reset_d = (int)id(reset_annuel_jour).state;
int reset_m = (int)id(reset_annuel_mois).state;
if (cur_day == reset_d && cur_month == reset_m) {
id(volume_annee_accumulateur) = 0.0;
}
# Reset compteur mois : le 1er de chaque mois à minuit
- seconds: 0
minutes: 0
hours: 0
days_of_month: 1
then:
- lambda: |-
id(volume_mois_accumulateur) = 0.0;
# ==========================================
# GLOBALS (=compteurs persistants jour/mois/année)
# ==========================================
globals:
- id: volume_jour_accumulateur
type: float
restore_value: true
initial_value: '0.0'
- id: volume_mois_accumulateur
type: float
restore_value: true
initial_value: '0.0'
- id: volume_annee_accumulateur
type: float
restore_value: true
initial_value: '0.0'
- id: last_volume_total
type: float
restore_value: true
initial_value: '0.0'
# ==========================================
# NUMBER (=tarifs modifiables depuis HA)
# ==========================================
number:
- platform: template
name: "Prix eau potable"
id: prix_eau_potable
initial_value: 1.83
min_value: 0
max_value: 20
step: 0.01
unit_of_measurement: "€/m³"
icon: mdi:water
optimistic: true
restore_value: true
mode: box
- platform: template
name: "Prix assainissement"
id: prix_assainissement
initial_value: 2.10
min_value: 0
max_value: 20
step: 0.01
unit_of_measurement: "€/m³"
icon: mdi:pipe
optimistic: true
restore_value: true
mode: box
- platform: template
name: "Abonnement eau annuel"
id: abonnement_annuel
initial_value: 24.81
min_value: 0
max_value: 500
step: 0.01
unit_of_measurement: "€"
icon: mdi:file-document-outline
optimistic: true
restore_value: true
mode: box
- platform: template
name: "Reset annuel - jour"
id: reset_annuel_jour
initial_value: 1
min_value: 1
max_value: 31
step: 1
icon: mdi:calendar-refresh
optimistic: true
restore_value: true
mode: box
- platform: template
name: "Reset annuel - mois"
id: reset_annuel_mois
initial_value: 5
min_value: 1
max_value: 12
step: 1
icon: mdi:calendar-refresh
optimistic: true
restore_value: true
mode: box
# Facteur calibration débitmètre (=ajustable sans recompiler)
# Théorique YF-B9 = 0.00210 (=1/476 pulses/L), à ajuster sur une chasse Geberit 3L/6L
- platform: template
name: "Calibration facteur"
id: calibration_factor
initial_value: 0.003129
min_value: 0.0001
max_value: 0.01
step: 0.000001
unit_of_measurement: "L/pulse"
icon: mdi:sine-wave
optimistic: true
restore_value: true
mode: box
# ==========================================
# SENSORS
# ==========================================
sensor:
# Télémétrie debug
- platform: uptime
name: "Uptime"
id: uptime_sec
unit_of_measurement: s
update_interval: 30s
- platform: debug
free:
name: "Heap Free"
block:
name: "Heap Max Block"
loop_time:
name: "Loop Time"
# Débit instantané (=Hall pulse → L/min)
- platform: pulse_counter
pin:
number: GPIO22
mode:
input: true
pullup: true
name: "Débit eau"
id: debit_eau
unit_of_measurement: L/min
accuracy_decimals: 2
update_interval: 10s
filters:
- lambda: return x * id(calibration_factor).state;
# Volume total cumulé (=depuis toujours)
- platform: integration
name: "Volume eau total"
id: volume_total
sensor: debit_eau
time_unit: min
unit_of_measurement: L
device_class: water
state_class: total_increasing
accuracy_decimals: 1
# Volume jour / mois / année (=via globals reset)
- platform: template
name: "Volume eau jour"
id: volume_jour_l
unit_of_measurement: L
device_class: water
state_class: total_increasing
accuracy_decimals: 1
update_interval: 60s
lambda: return id(volume_jour_accumulateur);
- platform: template
name: "Volume eau mois"
id: volume_mois_l
unit_of_measurement: L
device_class: water
state_class: total_increasing
accuracy_decimals: 1
update_interval: 60s
lambda: return id(volume_mois_accumulateur);
- platform: template
name: "Volume eau année"
id: volume_annee_l
unit_of_measurement: L
device_class: water
state_class: total_increasing
accuracy_decimals: 1
update_interval: 60s
lambda: return id(volume_annee_accumulateur);
# Prix total au m³
- platform: template
name: "Prix eau total m³"
unit_of_measurement: "€/m³"
icon: mdi:cash
update_interval: 30s
lambda: |-
return id(prix_eau_potable).state + id(prix_assainissement).state;
# Coûts jour / mois / année
- platform: template
name: "Coût eau jour"
unit_of_measurement: "€"
icon: mdi:cash
update_interval: 30s
accuracy_decimals: 2
lambda: |-
float vol_m3 = id(volume_jour_l).state / 1000.0;
float prix_var = id(prix_eau_potable).state + id(prix_assainissement).state;
return vol_m3 * prix_var;
- platform: template
name: "Coût eau mois"
unit_of_measurement: "€"
icon: mdi:cash
update_interval: 30s
accuracy_decimals: 2
lambda: |-
float vol_m3 = id(volume_mois_l).state / 1000.0;
float prix_var = id(prix_eau_potable).state + id(prix_assainissement).state;
float abo_mois = id(abonnement_annuel).state / 12.0;
return vol_m3 * prix_var + abo_mois;
- platform: template
name: "Coût eau année"
unit_of_measurement: "€"
icon: mdi:cash
update_interval: 30s
accuracy_decimals: 2
lambda: |-
float vol_m3 = id(volume_annee_l).state / 1000.0;
float prix_var = id(prix_eau_potable).state + id(prix_assainissement).state;
float abo = id(abonnement_annuel).state;
auto now = id(ha_time).now();
int cur_day = now.day_of_month;
int cur_month = now.month;
int cur_year = now.year;
int reset_d = (int)id(reset_annuel_jour).state;
int reset_m = (int)id(reset_annuel_mois).state;
int ref_year = cur_year;
if (cur_month < reset_m || (cur_month == reset_m && cur_day < reset_d)) {
ref_year = cur_year - 1;
}
int days_elapsed = (cur_year - ref_year) * 365 + (cur_month - reset_m) * 30 + (cur_day - reset_d);
if (days_elapsed < 0) days_elapsed = 0;
if (days_elapsed > 365) days_elapsed = 365;
float abo_prorata = abo * days_elapsed / 365.0;
return vol_m3 * prix_var + abo_prorata;
# Débit L/h (=plus lisible pour la maison)
- platform: template
name: "Débit eau L/h"
unit_of_measurement: "L/h"
icon: mdi:speedometer
update_interval: 10s
accuracy_decimals: 1
lambda: return id(debit_eau).state * 60.0;
# ==========================================
# INTERVAL (=maj accumulateurs jour/mois/année)
# ==========================================
interval:
- interval: 10s
then:
- lambda: |-
float current = id(volume_total).state;
if (!isnan(current)) {
float diff;
if (current >= id(last_volume_total)) {
diff = current - id(last_volume_total);
} else {
// Reset détecté (=reboot ESP) : integration repart de 0
diff = current;
}
id(volume_jour_accumulateur) += diff;
id(volume_mois_accumulateur) += diff;
id(volume_annee_accumulateur) += diff;
id(last_volume_total) = current;
}
Flasher l’ATOM Lite
Trois étapes, aucune préparation particulière :
- Câble USB-C entre l’ATOM Lite et le PC (=le port USB-C se trouve au dos de l’ATOM, sous le boîtier)
- Aller sur web.esphome.io (=Chrome ou Edge, Firefox n’a pas encore Web Serial), cliquer Connect puis choisir le port
USB Serialqui apparaît - Prepare for first use puis charger le YAML ci-dessus. Le premier flash prend 90 secondes ; les suivants passent par OTA Wi-Fi (=quelques secondes)
Une fois le module en Wi-Fi, il s’auto-annonce à Home Assistant qui propose « Nouveau device ESPHome trouvé, configurer ? » dans les intégrations. Les futures modifications YAML se font depuis l’add-on ESPHome dans HA sans jamais rebrancher le câble.
Vérification via logs série
Une fois le flash validé, on ouvre la page Diagnostics de l’ESP dans l’add-on ESPHome et on vérifie que le pulse counter matériel remonte des impulsions cohérentes.
En tournant manuellement le rotor du YF-B9 avec un stylet (=avant de brancher l’eau), on doit voir des lignes du type :
[D][pulse_counter:200]: 'Débit eau': Retrieved counter: 3.00 pulses/min
[S][sensor]: 'Débit eau' >> 0.00 L/min
[S][sensor]: 'Volume eau total' >> 0.0 L
Une fois l’eau ouverte et un robinet en aval, un débit franc doit apparaître dans les 10 secondes. Le tell-tale est que Retrieved counter monte à plusieurs milliers de pulses par minute (=à 15 L/min, environ 7000 pulses/min).
L’install
Ordre de montage sur l’arrivée générale, sortie du clapet anti-retour côté domicile :
[compteur d'eau communicant]
│
▼
[clapet anti-retour G3/4 M existant]
│ visse direct sans manchon
▼
[YF-B9 G3/4 F-F côté amont]
│
▼
[mamelon laiton G3/4 M-M neuf]
│ écrou G3/4 F
▼
[flex inox existant]
→ [cuivre G1/2 M existant → circuit maison]
Deux joints à serrer, téflon sur les filets, eau coupée à la vanne rouge au-dessus. Une demi-heure pas plus.
Piège classique : orienter la flèche du YF-B9 dans le sens du flux (=vers le domicile). Elle est gravée sur le corps mais peu lisible. Le mettre à l’envers ne casse rien mais fausse totalement les mesures - le rotor tourne dans l’autre sens et le comptage est incohérent.

Côté électronique, le boîtier Plexo IP55 est déporté à 1 m du débitmètre. On y trouve, une fois refermé :
- Alim USB 220 V→5 V clipsée sur bornier vis (=phase + neutre, pas de terre requise pour le petit module)
- ATOM Lite fixé au 3M VHB double-face
- 3 câbles Dupont YF-B9 → ATOM (=couleur respectée)
- Presse-étoupe pour la sortie du câble Hall

Vue d’ensemble une fois le boîtier accroché au mur du garage, juste sous l’arrivée générale :

Intégration Home Assistant
Trois couches logicielles s’empilent, chacune indépendamment installable :
1. Le sensor ESPHome brut
L’ATOM Lite s’annonce en Wi-Fi sur le VLAN IoT et s’auto-adopte dans Home Assistant. Les sensors exposés (=débit, volume total, volume jour/mois/année, coûts jour/mois/année, prix €/m³, uptime, reset reason) apparaissent tous nommés.
2. Water-Monitor : détection de sessions
Water-Monitor est une intégration HACS (=markaggar/Water-Monitor) qui prend en entrée le sensor de débit et détecte automatiquement le début et la fin de chaque « session » d’usage : un événement continu de consommation, avec une tolérance pour les micro-pauses (=un utilisateur qui savonne pendant 15 s en pleine douche par exemple).
Elle expose pour chaque session :
- Volume total (L)
- Durée (s)
- Débit moyen (L/min)
- Débit de pic
Ce sont les features d’entrée de la classification. Sans Water-Monitor, on aurait juste un débit brut et un volume cumulé, pas de découpage en événements discrets.
3. Water Pattern Classifier (=le custom_component présenté ici)
Installation : le repo GitHub Shad107/ha-water-classifier est HACS-ready. Dans Home Assistant :
- HACS → menu ⋮ → Dépôts personnalisés → coller l’URL du repo, catégorie « Integration » → Ajouter
- HACS → chercher « Water Pattern Classifier » → Télécharger
- Redémarrer Home Assistant
- Paramètres → Appareils et services → Ajouter une intégration → « Water Pattern Classifier »
Une fois configuré, une carte Lovelace water-classifier-card est auto-registrée : elle affiche en un coup d’œil la dernière session détectée + les compteurs journaliers par appareil, avec un code couleur.
C’est le projet que j’ai développé la semaine du 1er août 2026, disponible en open source sur github.com/Shad107/ha-water-classifier sous licence MIT.
Le principe est un classifier en cascade rule-based :
1. Long durée + gros volume → Machine à laver
2. Long durée + petit volume → Lave-vaisselle
3. Volume > 100 L + durée moyenne → Bain
4. Débit soutenu haut + heure matin/soir → Arrosage jardin
5. Durée moyenne + volume moyen + débit modéré → Douche
6. Volume 4-9 L + durée <2 min + pic haut → WC
7. Volume <3 L + durée courte → Robinet/Lavabo
8. Fallback → Autre
Les seuils sont dérivés d’un corpus académique : WEUSEDTO (=Naples 2019-2020, 7 appareils monitorés en 1 seconde de résolution) et REUWS (=DeOreo 2016, Residential End Uses of Water Study aux États-Unis). Un fork PyNIWM publié à ScienceDirect en octobre 2024 documente des classifiers ML atteignant F1 > 0.85 sur 800 000 événements labélisés - mon rule-based ne prétend pas rivaliser, mais couvre ~80 % des cas discriminables à la résolution de mon capteur (=10 L/pulse, 10 s d’échantillonnage).
Le custom_component expose :
- Un sensor
sensor.last_session_typequi donne le type de la dernière session détectée - Un config flow UI pour choisir les 4 sensors sources (=débit live + trois sensors Water-Monitor)
- Une custom Lovelace card en TypeScript / lit-element compilée avec Rollup
La carte affiche en un coup d’œil le type de la dernière session (=badge coloré par appareil), les métriques (=volume/durée/débit), et une grille de 8 compteurs journaliers avec le compteur du jour le plus utilisé mis en avant.
Résultat sur le dashboard Eau
Photo/screenshot à ajouter : l’onglet Eau du dashboard HA avec la card water-classifier active.
L’onglet Eau du dashboard Aperçu HA compile :
- Un gauge débit live + volume session en cours
- Les consommations jour/mois/année et les coûts associés
- Les sensors Water-Monitor (=sessions volume/durée/débit moyen)
- La custom card water-classifier (=type + compteurs colorés)
- Un graphique historique du débit sur 24 h
- Un histogramme volume quotidien 30 jours
- Le logbook des sessions
La suite
Trois évolutions envisagées, dans l’ordre :
v0.3 - Machine Learning propre. Migrer d’un rule-based statique à un modèle RandomForest ou XGBoost entraîné sur mes propres sessions labélisées manuellement. Après 2-4 semaines d’usage j’aurai suffisamment de données pour bootstrap. Le fork WEUSEDTO fournit un dataset public labellisé qui peut servir de pré-entraînement.
LXC dédié pour le classifier. Sortir le module Python de Home Assistant et le déployer dans un LXC Debian 12 séparé (=cohérent avec le pattern LXC 105 CI runner, LXC 112 FreeRADIUS, LXC 113 AdGuard). API MQTT, containerisation, plus facile à faire évoluer sans perturber HA.
Détection de patterns temporels. Détecter les habitudes (=temps de douche moyen par membre du foyer via horaires), et pouvoir alerter sur des dérives (=douche > 10 min, chasse WC qui fuit avec plusieurs sessions courtes rapprochées).
Sources et références
- WEUSEDTO - Water End USE Dataset and TOols (=Naples, GPL v3)
- PyNIWM paper ScienceDirect (oct 2024)
- markaggar/Water-Monitor sur GitHub
- Shad107/ha-water-classifier sur GitHub - le custom_component présenté ici
- REUWS 2016 - Residential End Uses of Water Study (DeOreo)
Matériel
- ◨
Capteur à effet Hall corps inox + laiton, filetage 3/4 pouce femelle-femelle. Range 1-30 L/min. 476 pulses/L constructeur. Voltage 3.5-24V DC. Sélectionner la variante "YF-B9" sur les listings AliExpress qui regroupent B1 à B10.
- ◨
Microcontrôleur ESP32 compact 24x24 mm avec bouton, LED RGB WS2812 et port Grove HY2.0. Pinout accessible côté bas. Wi-Fi 2.4 GHz et BLE natifs.
- ◨Mamelon laiton G3/4 M-M ~3 € pièce
Pour raccorder le YF-B9 (F) au flex inox (F) existant sortie de clapet anti-retour. Compatible eau potable, laiton hex 25.7 mm.
Amazon (lot de 3 OMZSXK) - ◨Boîtier Plexo Legrand 92042 IP55 ~17 €
Boîtier PVC 155×110×74 mm à visser, entrées presse-étoupe. Bloqué contre l’humidité et la poussière du garage.
Leroy Merlin / Amazon - ◨Module 220V→USB 5V 2.1A encastrable ~4 € pièce
Alimentation compacte pour brancher le boîtier sur une prise 220V permanente. Sortie USB-A femelle standard.
AliExpress (lot de 3) - ◨Câble Dupont femelle-femelle + gaine thermo ~2 €
Pour connecter les 3 fils du YF-B9 (VCC/GND/pulse) aux pins bottom de l'ATOM Lite sans soudure. Gaine thermo pour propreté.
stock
Prix indicatifs relevés au moment du projet. Aucun lien affilié - je ne touche rien.