izar-Wasserzähler per Funk auslesen


Technische Funktionsbeschreibung (ESP32-S3 + SX1262, wM-Bus T1, ESPHome)

Hinweis: Dieser Artikel und auch der Code wurde mit Hilfe von KI geschrieben.

1. Überblick

Diehl-/Sappel-Izar-Wasserzähler mit Funkmodul senden ihre Zählerdaten selbstständig und unaufgefordert als wireless M-Bus-Telegramme (Modus T1, 868,95 MHz), ungefähr alle 8–10 Sekunden. Ein Empfänger muss daher nichts anfragen, sondern nur zuhören. Die hier beschriebene Lösung empfängt diese Telegramme mit einem Mikrocontroller-Board mit eingebautem LoRa-Funkchip, dekodiert sie und stellt die Werte als Sensoren in Home Assistant bereit.

Datenfluss:

  • Zähler sendet Telegramm (wM-Bus T1, 868,95 MHz, 100 kbit/s, 3-aus-6-Kodierung)
  • SX1262-Funkchip empfängt das Frame im FSK-Modus
  • ESPHome-Komponente wmbus_radio liest das Frame per SPI aus und prüft es
  • wmbus_common entschlüsselt und dekodiert es mit dem Izar-Treiber (aus dem Projekt wmbusmeters)
  • wmbus_meter ordnet das Telegramm anhand der Zähler-ID einem konfigurierten Zähler zu und liefert die Felder an Sensoren
  • ESPHome überträgt die Sensorwerte per Native API an Home Assistant

2. Hardware

Verwendet wird ein Heltec WiFi LoRa 32 V3 (ESP32-S3 mit eingebautem Semtech SX1262 und 0,96″-OLED). Ein externes Funkmodul ist nicht nötig. Wichtig ist eine auf 868 MHz abgestimmte Antenne.

Der Heltec V3 kommt fix & fertig verdrahtet, es ist also kein Löten etc. nötig, um diese Anwendung abzubilden.

FunktionGPIOHinweis
SPI SCK / MOSI / MISO9 / 10 / 11SPI-Bus zum SX1262
SX1262 CS (NSS)8
SX1262 RESET12
SX1262 BUSY13
SX1262 DIO1 (IRQ)14auf dem Board teils fälschlich als „DIO0“ beschriftet
Vext36active-low; versorgt Antennen-Verstärker und OLED, muss beim Booten eingeschaltet (LOW) werden
OLED I2C SDA / SCL / RST17 / 18 / 21optional, SSD1306 128×64, Adresse 0x3C
PRG-Taste0optional (z. B. Display-Seiten umschalten)

Board-Besonderheiten für die Funkkonfiguration:

  • rf_switch: true – DIO2 des SX1262 steuert den Antennen-Umschalter.
  • has_tcxo: true – der Quarzoszillator (TCXO) wird über DIO3 versorgt. Die Komponente setzt dabei fest 3,0 V, obwohl der TCXO für 1,8 V spezifiziert ist; in der Praxis funktioniert der Empfang, sollte aber geprüft werden.

Grundsätzlich funktioniert das Prinzip auch mit anderen Empfängern, die die Komponente unterstützt (z. B. ESP32 + CC1101 oder SX1276); dann ändern sich nur Pins und radio_type.

3. Software-Grundlage

Firmware ist ESPHome (Framework ESP-IDF). Die wM-Bus-Unterstützung kommt als External Component aus dem GitHub-Projekt SzczepanLeon/esphome-components, bestehend aus drei Teilen:

  • wmbus_radio – Treiber für den Funkchip (Frequenz 868,95 MHz, FSK, 100 kbit/s, Präambel-/Sync-Erkennung, Dekodierung 3-aus-6, CRC-Prüfung). Löst für jedes empfangene Frame den Trigger on_frame aus.
  • wmbus_common – Telegramm-Parser und Zählertreiber (portiert aus wmbusmeters), darunter der Treiber „izar“.
  • wmbus_meter – eine Instanz pro Zähler: filtert nach Zähler-ID und Treiber und stellt die dekodierten Felder als Sensor- bzw. Text-Sensor-Plattform bereit.

Verschlüsselung: Izar-Telegramme (Diehl „PRIOS“) sind mit einem LFSR-Verfahren verschleiert. Der Treiber bringt die öffentlich bekannten Diehl-Standardschlüssel mit – für diese Zähler muss kein individueller Schlüssel angegeben werden.

4. Zähler-ID

Jeder Zähler wird über seine achtstellige Zähler-ID (Adressfeld im Telegramm) identifiziert. Sie steht meist auf dem Funkmodul bzw. Typenschild. In der Konfiguration wird sie als Hex-Literal mit denselben Ziffern angegeben, also ID 12345678 → meter_id: 0x12345678 (die Adresse ist im Telegramm BCD-kodiert).

Unbekannte IDs lassen sich ermitteln, indem man im on_frame-Trigger den Telegramm-Header parst (Telegram::parseHeader) und die Adresse aller gehörten Frames loggt. Alle IDs, die nicht in der eigenen Liste stehen, werden in einem kleinen Ringpuffer gesammelt und gelegentlich als Text-Sensor gemeldet – so findet man Zähler in Reichweite, ohne die Home-Assistant-Datenbank zu fluten.

5. Gelieferte Werte pro Zähler

Feld (wmbus_meter)BedeutungEinheit
total_m3aktueller Zählerstandm³
last_month_total_m3Zählerstand am letzten Stichtag (Monatsende)m³
last_month_measure_dateDatum dieses StichtagsText (JJJJ-MM-TT)
remaining_battery_life_ygeschätzte Batterie-RestlaufzeitJahre
current_alarms / previous_alarmsAlarme aktuell / früher (Leck, Rückfluss, Manipulation, blockiert …)Text
rssi_dbmEmpfangsstärke des letzten TelegrammsdBm

Das Feld target_m3 liefern diese Zähler nicht (immer leer); es wird daher nicht verwendet. Zusätzlich lässt sich per Template-Sensor der Verbrauch seit dem Stichtag berechnen: (total_m3 − last_month_total_m3) × 1000 in Litern. Das Ergebnis sollte gerundet werden (roundf), da die Subtraktion zweier nahe beieinander liegender float-Werte sonst Nachkommastellen-Artefakte erzeugt.

6. Datenaufbereitung

Darstellung in Grafana des Monatsverbrauchs

Da jeder Zähler etwa alle 9 Sekunden sendet, würden ungefilterte Werte Home Assistant mit identischen Einträgen überschwemmen. Deshalb:

  • Numerische Sensoren: Filter delta (nur bei tatsächlicher Änderung senden, z. B. 0,5 Liter beim Zählerstand) plus heartbeat (z. B. alle 6 h den aktuellen Wert erneut senden, damit sichtbar bleibt, dass der Zähler noch empfangen wird).
  • Text-Sensoren (Alarme, Stichtag): kleiner Lambda-Filter, der nur bei geändertem Text weitergibt.
  • Für den Zählerstand: device_class water, state_class total_increasing – damit ist er direkt im Energie-Dashboard von Home Assistant als Wasserverbrauch nutzbar.
  • Optional: im on_telegram-Trigger des Zählers einen Zeitstempel „zuletzt empfangen“ setzen.

7. Robustheit: Funkstille-Watchdog

Beim SX1262 mit dieser Komponente ist ein Fehler bekannt, bei dem der Empfänger nach einiger Zeit ohne Fehlermeldung verstummt (Issue #318 im Komponenten-Projekt). Abhilfe: Im on_frame-Trigger wird bei jedem empfangenen Frame (egal von welchem Absender) millis() in einer globalen Variable gespeichert. Ein interval-Block prüft regelmäßig, ob seit mehr als 15 Minuten kein Frame mehr kam, und startet das Gerät dann neu. Da in der Regel mehrere Zähler in Reichweite senden, tritt eine echte 15-minütige Funkstille praktisch nie auf.

8. Minimal-Konfiguration (Kern)

Der folgende Ausschnitt zeigt nur die für das Auslesen nötigen Teile; WLAN, API, OTA usw. wie bei jedem ESPHome-Gerät ergänzen.


esp32:
  board: esp32-s3-devkitc-1
  variant: ESP32S3
  flash_size: 8MB
  framework:
    type: esp-idf

external_components:
  - source: github://SzczepanLeon/esphome-components@main
    components: [wmbus_radio, wmbus_common, wmbus_meter]

spi:
  clk_pin: GPIO9
  mosi_pin: GPIO10
  miso_pin: GPIO11

switch:
  - platform: gpio # Vext einschalten (active-low)
    id: vext_enable
    internal: true
    restore_mode: ALWAYS_ON
    pin: { number: GPIO36, inverted: true }

globals:
  - id: last_frame_millis
    type: uint32_t
    initial_value: '0'

wmbus_radio:
  radio_type: SX1262
  cs_pin: GPIO8
  reset_pin: GPIO12
  busy_pin: GPIO13
  irq_pin: GPIO14
  rf_switch: true
  has_tcxo: true
  on_frame:
    - then:
        - lambda: id(last_frame_millis) = millis();

wmbus_meter:
  - id: wasserzaehler
    meter_id: 0x12345678 # Zaehler-ID 12345678
    type: izar
    mode: [T1]

sensor:
  - platform: wmbus_meter
    parent_id: wasserzaehler
    field: total_m3
    name: "Wasser gesamt"
    unit_of_measurement: "m³"
    device_class: water
    state_class: total_increasing
    accuracy_decimals: 3
    filters:
      - delta: 0.0005
      - heartbeat: { period: 6h, optimistic: true }
  - platform: wmbus_meter
    parent_id: wasserzaehler
    field: rssi_dbm
    name: "Wasser RSSI"
    unit_of_measurement: "dBm"

interval:
  - interval: 1min # Funkstille-Watchdog
    then:
      - if:
          condition:
            lambda: return millis() - id(last_frame_millis) > 15UL*60*1000;
          then:
            - lambda: App.safe_reboot();

Weitere Zähler werden durch zusätzliche wmbus_meter-Einträge (je mit eigener meter_id) und zugehörige Sensoren ergänzt; ein Empfänger kann beliebig viele Zähler in Reichweite gleichzeitig auslesen. Bei mehreren Zählern bietet sich an, den Zähler-Block als ESPHome-Package mit vars (meter_id) einmal pro Zähler einzubinden.

9. Inbetriebnahme und Fehlersuche

  • Logger-Level zunächst auf DEBUG stellen: Jedes empfangene Frame erscheint mit RSSI; so sieht man, ob überhaupt Funkverkehr ankommt und welche IDs in Reichweite sind.
  • Empfangsstärke: ab etwa −100 dBm wird es unzuverlässig. Zähler in Schächten oder hinter Metall dämpfen stark – Antennenposition optimieren.
  • Kommen Frames an, aber keine Werte beim Zähler: meter_id prüfen (Hex-Schreibweise mit den Dezimalziffern der ID) und type: izar.
  • Kommt gar nichts an: Vext eingeschaltet? rf_switch/has_tcxo gesetzt? Antenne für 868 MHz angeschlossen?