isno.fr

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

Débitmètre eau
Le YF-B9 en place entre le flex inox et le cuivre existant, sortie du clapet anti-retour sur l’arrivée générale, dans le garage.
Sommaire
  1. Glossaire
  2. Contexte
  3. Identification hardware
  4. Programmation ESP32
  5. Vérification via logs série
  6. L’install
  7. Intégration Home Assistant
  8. Résultat sur le dashboard Eau
  9. La suite
  10. Sources et références

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.

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 :

  1. Une mesure live du débit et du volume, avec cumul jour/mois/année
  2. Une détection de fuite automatique
  3. 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 :

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

YF-B9 et M5Stack ATOM Lite côte à côte, prêts au câblage

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 :

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 ATOMFonctionFil 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.

Câbles Dupont femelle-femelle branchés directement sur les pins bottom de l'ATOM Lite

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 :

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 :

  1. 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)
  2. Aller sur web.esphome.io (=Chrome ou Edge, Firefox n’a pas encore Web Serial), cliquer Connect puis choisir le port USB Serial qui apparaît
  3. 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.

Le module 220 V→USB Synclum avant intégration au boîtier

Côté électronique, le boîtier Plexo IP55 est déporté à 1 m du débitmètre. On y trouve, une fois refermé :

Intérieur du boîtier Plexo : alim 220 V à gauche, ATOM Lite avec Dupont à droite

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

Vue install garage complète : YF-B9 en haut, boîtier électronique en dessous

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 :

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 :

  1. HACS → menu ⋮ → Dépôts personnalisés → coller l’URL du repo, catégorie « Integration » → Ajouter
  2. HACS → chercher « Water Pattern Classifier » → Télécharger
  3. Redémarrer Home Assistant
  4. 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 :

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 :

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

Matériel

Prix indicatifs relevés au moment du projet. Aucun lien affilié - je ne touche rien.