Zum Inhalt

Modbus TCP

Der WattWächter Plus stellt einen integrierten Modbus TCP Server bereit. Die Messdaten deines Stromzählers sind damit direkt als Modbus-Register abrufbar — geeignet für Home Assistant, evcc, openHAB, ioBroker, SMA, Loxone, SCADA-Systeme und jeden anderen Modbus-TCP-Client.

SunSpec-kompatibel

Der Server implementiert die SunSpec-Modelle 1 (Common Block) und 203 (Three Phase Meter, Wye). Systeme mit SunSpec-Discovery (z.B. Home Assistant, evcc, openHAB, SMA Sunny Home Manager) erkennen den WattWächter dadurch automatisch als Dreiphasen-Zähler. Loxone hat keine SunSpec-Discovery — siehe Integrationen → Loxone.

Voraussetzungen

  • WattWächter Plus eingerichtet und mit dem WLAN verbunden — falls noch nicht geschehen, folge zunächst der Anleitung unter Erste Schritte
  • Firmware 1.2.0 oder neuer
  • Modbus-TCP-Client bzw. SunSpec-Client im selben Netzwerk
  • Port 502 (Standard) erreichbar zwischen Client und WattWächter

Modbus TCP aktivieren

Die Modbus-Schnittstelle ist ab Werk deaktiviert und muss einmalig eingeschaltet werden. Du hast drei Möglichkeiten:

Über die Weboberfläche (empfohlen) — ruf den WattWächter im Browser auf, entweder über seinen mDNS-Namen http://wattwaechter-XXXXXXXXXXXX.local oder über seine IP-Adresse, und klicke auf Einstellungen. Klapp dort den Abschnitt Modbus TCP auf, schalte Modbus-TCP-Server aktivieren ein und bestätige unten mit Speichern.

Das Feld Port steht auf 502 und muss nur angefasst werden, wenn dieser Port bei dir schon belegt ist.

Weboberfläche: Modbus TCP in den Einstellungen aktivieren

Über den API-Explorer — öffne die Web-UI des WattWächters, klicke auf „API-Explorer", wähle den Endpunkt POST /api/v1/settings und sende folgenden Body:

{ "modbus": { "enable": true, "port": 502 } }

Per curl — falls du es lieber von der Kommandozeile erledigst:

curl -X POST http://wattwaechter-XXXXXXXXXXXX.local/api/v1/settings \
  -H "Authorization: Bearer WRITE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "modbus": {
      "enable": true,
      "port": 502
    }
  }'

Die Änderung wird sofort übernommen — ein Neustart ist nicht erforderlich. Du kannst den Port anpassen, falls 502 bereits belegt ist.

API-Authentifizierung deaktiviert?

Ist die API-Authentifizierung ausgeschaltet (Werkseinstellung), kannst du den Authorization-Header weglassen. Siehe REST-API.


Verbindungsdaten

Parameter Wert
Protokoll Modbus TCP
Port 502 (konfigurierbar)
Unterstützte Function Codes 0x03 (Read Holding Registers), 0x04 (Read Input Registers)
Max. gleichzeitige Clients 2
Idle-Timeout 5 Minuten
Byte-Order Big-Endian (Modbus-Standard)
Register-Basis 40000 (SunSpec-Standard)

Als Host wird der mDNS-Name (wattwaechter-XXXXXXXXXXXX.local) oder die IP-Adresse des Geräts verwendet. Wie du die IP-Adresse findest, ist in den FAQs beschrieben.

Loxone: nur die IP-Adresse

Nicht jeder Client löst mDNS auf. Der Loxone Miniserver kann mit .local-Namen nichts anfangen — dort muss die IP-Adresse stehen, abgesichert über eine DHCP-Reservierung oder eine feste IP. Details unter Integrationen → Loxone.


SunSpec Register-Map

Der Adressraum startet bei 40000 und enthält drei Blöcke:

Adresse Inhalt Größe
40000–40001 SunSpec-ID "SunS" (0x53756E53) 2 Register
40002–40069 Model 1 — Common Block (Hersteller, Modell, Seriennummer, Firmware) 68 Register
40070–40176 Model 203 — Three Phase Meter (Wye) 107 Register
40177–40178 End-Marker (0xFFFF, 0x0000) 2 Register

Nicht befüllte Felder sind mit dem SunSpec-Sentinel 0x8000 (int16) bzw. 0xFFFF (uint16) als not implemented markiert.

Model 1 — Common Block

Feld Adresse Typ Inhalt
ID 40002 uint16 1
L 40003 uint16 66
Mn (Manufacturer) 40004–40019 string32 SmartCircuits GmbH
Md (Model) 40020–40035 string32 WattWaechter
Vr (Version) 40044–40051 string16 Firmware-Version
SN (Serial Number) 40052–40067 string32 MAC-Adresse (hex, 12 Zeichen)
DA (Device Address) 40068 uint16 1

Model 203 — Three Phase Meter (Wye)

Alle Messwerte werden per Skalierungsfaktor (_SF) in ganzzahlige Register umgerechnet. Der tatsächliche Wert ergibt sich aus:

Wert = Rohwert × 10^SF
Adresse Beschreibung Feld Typ Einheit SF OBIS
40070 203 ID uint16
40071 105 L uint16
40072 Summenstrom (berechnet) A int16 A A_SF
40073 Strom L1 AphA int16 A A_SF 1-0:31.7.0
40074 Strom L2 AphB int16 A A_SF 1-0:51.7.0
40075 Strom L3 AphC int16 A A_SF 1-0:71.7.0
40076 Skalierung Strom (-2 → 0,01 A) A_SF int16
40077 Spannung LN (Mittelwert) PhV int16 V V_SF
40078 Spannung L1 PhVphA int16 V V_SF 1-0:32.7.0
40079 Spannung L2 PhVphB int16 V V_SF 1-0:52.7.0
40080 Spannung L3 PhVphC int16 V V_SF 1-0:72.7.0
40085 Skalierung Spannung (-1 → 0,1 V) V_SF int16
40086 Netzfrequenz Hz int16 Hz Hz_SF 1-0:14.7.0
40087 Skalierung Frequenz (-2 → 0,01 Hz) Hz_SF int16
40088 Gesamtwirkleistung W int16 W W_SF 1-0:16.7.0
40089 Leistung L1 WphA int16 W W_SF 1-0:21.7.0
40090 Leistung L2 WphB int16 W W_SF 1-0:41.7.0
40091 Leistung L3 WphC int16 W W_SF 1-0:61.7.0
40092 Skalierung Leistung (1 → 10 W, ab Firmware 1.2.0) W_SF int16
40108–40109 Gesamte Einspeisung TotWhExp acc32 Wh TotWh_SF 1-0:2.8.0
40116–40117 Gesamter Bezug TotWhImp acc32 Wh TotWh_SF 1-0:1.8.0
40124 Skalierung Energie (0 → 1 Wh) TotWh_SF int16

Datentypen

  • int16 — 16-Bit signed, 1 Register. Negative Werte bei Einspeisung (Leistung).
  • acc32 — 32-Bit unsigned Akkumulator, 2 Register (Highword zuerst).

Der Wertebereich ergibt sich aus Datentyp und Skalierungsfaktor. Die Leistungsregister decken mit W_SF = 1 rund ±327 kW ab, die Energiezähler als acc32 rund 4,29 GWh. Liegt ein Messwert darüber, meldet das Gerät not implemented (0x8000) statt eines abgeschnittenen Werts — eine geklemmte Zahl wäre von einem echten Messwert nicht zu unterscheiden.

Welche Register sind verfügbar?

Welche Felder tatsächlich befüllt werden, hängt von deinem Stromzähler ab. Liefert er z.B. nur den Summenverbrauch 1-0:1.8.0 und die Momentanleistung 1-0:16.7.0, bleiben die Phasen-Register auf not implemented (0x8000).

Welche Register aktuell gültige Werte enthalten, kannst du direkt über den Status-Endpunkt prüfen — ein GET auf /api/v1/modbus/status liefert für jedes Register ein valid-Flag:

# Nur ungültige (nicht vom Zähler gelieferte) Register anzeigen
curl -s http://wattwaechter-XXXXXXXXXXXX.local/api/v1/modbus/status \
  | jq '.registers[] | select(.valid == false)'

Details siehe Status-Endpunkt weiter unten.


Beispielzugriff

Gesamtleistung lesen (modpoll)

# Register 40088 (W) lesen — 1 Register
modpoll -m tcp -a 1 -r 40088 -c 1 -t 3 wattwaechter-XXXXXXXXXXXX.local

Zählerstand Bezug lesen (Python / pymodbus)

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient("wattwaechter-XXXXXXXXXXXX.local", port=502)
client.connect()

# TotWhImp: 2 Register ab 40116 (acc32)
rr = client.read_holding_registers(address=40116, count=2, slave=1)
tot_wh_imp = (rr.registers[0] << 16) | rr.registers[1]

# Skalierungsfaktor (TotWh_SF) aus Register 40124
sf = client.read_holding_registers(address=40124, count=1, slave=1).registers[0]
sf = sf if sf < 0x8000 else sf - 0x10000   # int16

kwh = tot_wh_imp * (10 ** sf) / 1000
print(f"Bezug: {kwh:.3f} kWh")

client.close()

Status-Endpunkt

Zum Testen und Debuggen liefert die REST-API eine Live-Übersicht des Modbus-Zustands und aller Register, die das Gerät bedient — samt Rohwert und Skalierungsfaktor. Ein Modbus-Client (z. B. Loxone) lässt sich damit allein aus dieser Antwort konfigurieren, ohne die SunSpec-Spezifikation danebenzulegen:

curl http://wattwaechter-XXXXXXXXXXXX.local/api/v1/modbus/status \
  -H "Authorization: Bearer READ_TOKEN"

Antwort (gekürzt):

{
  "enabled": true,
  "running": true,
  "port": 502,
  "active_connections": 1,
  "registers": [
    { "register": 40073, "name": "AphA", "obis": "31.7.0",
      "value": 1.2300, "unit": "A", "valid": true,
      "raw": 123, "scale_factor": -2, "scale_register": 40076, "derived": false },
    { "register": 40086, "name": "Hz", "obis": "14.7.0",
      "value": null, "unit": "", "valid": false,
      "raw": -32768, "scale_factor": -2, "scale_register": 40087, "derived": false },
    { "register": 40088, "name": "W", "obis": "16.7.0",
      "value": -450.0000, "unit": "W", "valid": true,
      "raw": -45, "scale_factor": 1, "scale_register": 40092, "derived": false },
    { "register": 40116, "name": "TotWhImp", "obis": "1.8.0",
      "value": 12345600.0000, "unit": "Wh", "valid": true,
      "raw": 12345600, "scale_factor": 0, "scale_register": 40124, "derived": false },
    { "register": 40072, "name": "A", "obis": "",
      "value": 3.6900, "unit": "A", "valid": true,
      "raw": 369, "scale_factor": -2, "scale_register": 40076, "derived": true }
  ]
}
Feld Beschreibung
enabled Modbus-Server in den Einstellungen aktiviert
running Server-Task läuft aktuell und lauscht auf dem Port
active_connections Anzahl der aktuell verbundenen Modbus-Clients (max. 2)
registers[].register Modbus-Adresse; acc32-Werte belegen zwei aufeinanderfolgende Register
registers[].obis OBIS-Code des Zählers, aus dem das Register befüllt wird — leer bei berechneten Werten
registers[].value Skalierter Wert in unit; null, wenn nicht verfügbar
registers[].valid true, wenn der Zähler diesen Wert liefert. Sonst steht im Register der Sentinel 0x8000 (−32768)
registers[].raw Registerinhalt, so wie ihn ein Modbus-Client liest (int16 bzw. acc32 als unsigned)
registers[].scale_factor Dezimalexponent: value = raw × 10^scale_factor
registers[].scale_register Adresse des SF-Registers, das diesen Faktor über Modbus veröffentlicht
registers[].derived true bei A (40072) und PhV (40077) — die berechnet das Gerät aus den Phasenwerten, sie haben keinen OBIS-Code

Die Felder raw, scale_factor, scale_register und derived sowie die Einträge A und PhV gibt es ab Firmware 1.2.0. Ältere Firmware liefert nur die OBIS-gemappten Register mit value, unit und valid.

Skalierungsfaktor direkt ablesen

Wer einen Client von Hand konfiguriert, nimmt den Faktor aus scale_factor, statt ihn zu raten: -45 × 10^1 = -450 W. Die fünf SF-Register selbst (A_SF, V_SF, Hz_SF, W_SF, TotWh_SF) tauchen nicht als eigene Zeilen auf — sonst würde jeder Client, der die Liste rendert, sie als Sensoren anzeigen. Wo sie liegen, sagt scale_register; die vollständige Belegung steht in der Register-Map.

Siehe auch REST-API.


Fehlerbehebung

Verbindung wird abgelehnt / Timeout
  • Ist Modbus in den Einstellungen aktiviert? Prüfe /api/v1/modbus/statusenabled: true, running: true.
  • Verwendest du den richtigen Port? Standard ist 502.
  • Ist der Client im selben Netzwerk/Subnetz? In Gast-WLANs ist Client-zu-Client-Kommunikation meist gesperrt.
  • Es sind maximal 2 gleichzeitige Verbindungen möglich. Weitere Clients werden abgewiesen, bis eine Verbindung geschlossen wird oder nach 5 min Leerlauf.
Register liefern nur 0x8000 / -32768

Der Wert 0x8000 ist der SunSpec-Sentinel für not implemented. Das bedeutet: dein Zähler liefert diesen OBIS-Code nicht. Prüfe im Dashboard, welche OBIS-Codes tatsächlich ankommen — nur diese werden in die Modbus-Register geschrieben.

Werte erscheinen um Faktor 10 / 100 verschoben

Die SunSpec-Skalierungsfaktoren (A_SF, V_SF, W_SF, Hz_SF, TotWh_SF) müssen bei jeder Abfrage angewendet werden. Beispiel: Strom-Register AphA = 123 mit A_SF = -2 ergibt 1,23 A. Echte SunSpec-Clients (Home Assistant, evcc, openHAB modbus.sunspec, pymodbus mit SunSpec-Parser) rechnen das automatisch um — bei generischen Modbus-Clients (Loxone, modpoll) musst du es selbst tun.

Port 502 bereits belegt

Hast du bereits ein Gerät auf Port 502? Stelle in den Einstellungen einen alternativen Port ein, z.B. 1502:

curl -X POST http://wattwaechter-XXXXXXXXXXXX.local/api/v1/settings \
  -H "Authorization: Bearer WRITE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"modbus": {"port": 1502}}'