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 Loxone, ioBroker, OpenHAB, SMA, 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). Dadurch wird der WattWächter von SunSpec-fähigen Systemen (z.B. Loxone Miniserver, SMA Sunny Home Manager) automatisch als Dreiphasen-Zähler erkannt.
Voraussetzungen¶
- WattWächter Plus eingerichtet und mit dem WLAN verbunden — falls noch nicht geschehen, folge zunächst der Anleitung unter Erste Schritte
- Firmware 1.0.9 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 zwei Möglichkeiten:
Über den API-Explorer (empfohlen) — ö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 API-Referenz.
Verbindungsdaten¶
| Parameter | Wert |
|---|---|
| Protokoll | Modbus TCP |
| Port | 502 (konfigurierbar) |
| Unit ID / Slave ID | 1 |
| 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.
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 (0 → 1 W) |
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).
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 — Unit ID 1, 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()
Loxone Miniserver¶
Schritt 1 — Modbus-Server anlegen¶
In Loxone Config:
- Peripheriegerät hinzufügen → Modbus Server
- IP-Adresse des WattWächters und Port 502 eintragen
- Slave-ID auf
1setzen - Abfrageintervall nach Bedarf (z.B. 5–10 s)
Schritt 2 — Register manuell anlegen¶
Lege für jeden gewünschten Wert einen Modbus-Sensor unter dem Modbus-Server an. Die wichtigsten Register:
| Wert | Adresse | Anzahl | Datentyp | Skalierung (SF-Register) |
|---|---|---|---|---|
| Gesamtwirkleistung | 40088 | 1 | int16 (signed!) | 40092 (W_SF) |
| Wirkleistung L1 / L2 / L3 | 40089 / 40090 / 40091 | 1 | int16 | 40092 (W_SF) |
| Strom L1 / L2 / L3 | 40073 / 40074 / 40075 | 1 | int16 | 40076 (A_SF) |
| Spannung L1 / L2 / L3 | 40078 / 40079 / 40080 | 1 | int16 | 40085 (V_SF) |
| Frequenz | 40086 | 1 | int16 | 40087 (Hz_SF) |
| Bezug gesamt | 40116 | 2 | uint32 (acc32) | 40124 (TotWh_SF) |
| Einspeisung gesamt | 40108 | 2 | uint32 (acc32) | 40124 (TotWh_SF) |
Für jeden Wert in Loxone:
- Skalierungsfaktor als eigenen Sensor einlesen und per Formel-/Status-Baustein anwenden:
Wert = Rohwert × 10^SF. Die SF-Register sind in der Praxis stabil — du kannst sie auch einmalig auslesen und als Konstante hardcoden, um eine Berechnung zu sparen. - Sentinel-Filter: Werte ≤ −32000 sind das SunSpec-
0x8000(not implemented). Filtere sie per Status-Baustein heraus, sonst zeigt Loxone falsche Werte für Felder, die dein Zähler nicht liefert. Welche Felder valide sind, siehst du am Status-Endpunkt.
Schritt 3 — Bezug und Einspeisung trennen¶
Die Gesamtleistung W (40088) ist vorzeichenbehaftet: positiv bei Bezug, negativ bei Einspeisung. Für den Loxone-Energiemonitor zerlegst du den Wert per Status-Baustein in zwei Größen:
- Bezug =
MAX(W, 0) - Einspeisung =
MAX(-W, 0)
Die Energie-Zählerstände TotWhImp (Bezug) und TotWhExp (Einspeisung) liegen ohnehin in getrennten Registern — die kannst du direkt verdrahten.
Welche Register liefert mein Zähler?
Der WattWächter füllt W, TotWhImp und TotWhExp immer mit gültigen Werten (bei fehlenden Daten mit 0), damit Loxone das Gerät als gültigen Zähler akzeptiert. Phasenwerte und Frequenz sind nur befüllt, wenn dein Zähler die zugehörigen OBIS-Codes liefert. Eine Live-Übersicht liefert /api/v1/modbus/status.
Status-Endpunkt¶
Zum Testen und Debuggen liefert die REST-API eine Live-Übersicht des Modbus-Zustands und der Registerbelegung:
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.23, "unit": "A", "valid": true },
{ "register": 40088, "name": "W", "obis": "16.7.0",
"value": -452.0, "unit": "W", "valid": true },
{ "register": 40116, "name": "TotWhImp", "obis": "1.8.0",
"value": 12345600.0, "unit": "Wh", "valid": 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[].valid |
true, wenn der Zähler diesen OBIS-Code liefert |
Siehe auch API-Referenz.
Fehlerbehebung¶
Verbindung wird abgelehnt / Timeout
- Ist Modbus in den Einstellungen aktiviert? Prüfe
/api/v1/modbus/status→enabled: 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. SunSpec-Clients (Loxone, pymodbus mit SunSpec-Parser) rechnen das automatisch um.
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}}'