Tutorial: SunSpec-Register eines PV-Wechselrichters über Modbus TCP auslesen

Management Summary
Ein Solarwechselrichter macht den Strom der Module nutzbar und liefert Messwerte zur Energieüberwachung. SunSpec beschreibt diese Werte nach gemeinsamen Regeln; Modbus ist das Verfahren zum Auslesen. Damit lässt sich Solartechnik an eine unabhängige Überwachung anbinden. Der Beitrag erklärt die Bedeutung gerätespezifischer Datenstellen, Einheiten und Umrechnungen: Eine funktionierende Verbindung kann trotzdem plausible, aber falsche Zahlen liefern. Fehlende Messwerte dürfen deshalb nicht zu künstlichen Nullen werden.
Inhalt
Ein Modbus-Register ist ein nummerierter Speicherplatz für 16 Bit. SunSpec legt fest, wie Gruppen solcher Register Messwerte und Geräteangaben beschreiben. Die Modellkette erlaubt das Auffinden unterstützter Datenblöcke, statt eine Adresse von einem anderen Wechselrichter zu kopieren. Erforderlich bleiben die richtige Gerätekonfiguration und passende Modelldefinitionen. Nicht jeder Messwert ist auf jedem Wechselrichter verfügbar.
Dieses Tutorial liest den Strom der Phase A aus dem ganzzahligen Wechselrichtermodell 103. Das Python-Beispiel verwendet die am 24. Oktober 2026 geprüfte Schnittstelle von PyModbus 3.16.1 und führt ausschließlich Holding-Register-Lesezugriffe aus. Host, Port und Gerätekennung sind gerätespezifisch. Freischaltung und Registerauswahl richten sich nach der Herstellerdokumentation.
1. Die Karte mit eindeutiger Adresszählung finden
Die standardisierten SunSpec-Protokolladressen sind 0, 40000 und 50000, jeweils ab null gezählt. Die ersten beiden Register enthalten 0x5375 und 0x6E53, zusammen die Zeichen SunS. Diese Angaben sind bereits Protokolloffsets.
Handbücher können Offsets ab null, Registernummern ab eins oder traditionelle 4xxxx-Referenzen für Holding-Register verwenden. Eine gedruckte „40001“ lässt sich deshalb nicht pauschal in 40000 umrechnen. In traditioneller Referenzschreibweise kann 40001 das erste Holding-Register mit Protokolloffset 0 meinen. Dokumentierte Zählweise und gefundene Marke müssen zusammenpassen.
Zähler und Wechselrichter können getrennte Gerätekennungen, getrennte Verbindungen oder unterschiedliche Modelle innerhalb einer gemeinsamen Karte verwenden. Weder eine zweite Kennung noch ein bestimmter Auslieferungszustand ist universell. Eine gültige Modbus-Antwort beweist allein nicht, dass das beabsichtigte Gerät geantwortet hat.

2. Den Modellköpfen folgen
Nach den beiden Markierungsregistern enthält jeder Kopf eine Modell-ID und eine Länge. Die Länge zählt die Datenregister nach dem zwei Register langen Kopf. Der nächste Kopf liegt deshalb bei aktueller_Kopf + 2 + Länge. Das Endmodell 0xFFFF hat Länge 0.
Modell-IDs kennzeichnen Datenlayouts. Namen, Einheiten und Punktpositionen stammen jedoch aus den veröffentlichten Modelldefinitionen, nicht aus einem vollständig vom Gerät übertragenen Wörterbuch. Unbekannte Modelle lassen sich anhand der Länge überspringen; bekannte Modelle benötigen weiterhin einen passenden Decoder.
- 1: gemeinsame Geräteidentifikation.
- 101/102/103: ganzzahlige Wechselrichtermodelle für Einphase, Split Phase und Dreiphasenbetrieb.
- 111/112/113: entsprechende Gleitkommafamilie mit anderem Datenlayout.
- 160: Erweiterung für mehrere MPP-Tracker; Eingangsgruppen sind nicht pauschal einzelne physische Strings.
- 201–204: ganzzahlige Zählermodelle für unterschiedliche Anschlussarten.
Unterstützte Modelle und eine mögliche Ganzzahl-/Gleitkommaauswahl hängen vom Produkt ab. Modell 102 bezeichnet Split Phase, nicht eine beliebige zweiphasige Anlage. Das Beispiel meldet bewusst einen Fehler, wenn nicht genau eine Instanz von Modell 103 vorhanden ist.
3. Den Phasenstrom konsistent dekodieren
Alle folgenden Offsets zählen ab null vom ersten Datenregister, ohne ID und Länge: A liegt bei 0, AphA bei 1, AphB bei 2, AphC bei 3 und A_SF bei 4. Beginnen die Daten bei 40071, liegt Phase A bei 40072 und der Skalierungsfaktor bei 40075. Der Adressabstand beträgt drei Register.
Der Strom ist vorzeichenlos; der Skalierungsfaktor ist ein vorzeichenbehafteter Zehnerexponent. Rohstrom 1234 und Exponent −2 ergeben 1234 × 10^-2 = 12,34 A. Ein anderer Exponent ergibt einen anderen Multiplikator. Fehlender Strom 0xFFFF oder fehlender Skalierungsfaktor 0x8000 dürfen nicht zu einem Messwert umgerechnet werden.
4. Mit Grenzen und Fehlerprüfung lesen
Das folgende Beispiel prüft Verbindung, Antwortlänge, Modellgrenzen, Endmarke und beide Ungültigkeitswerte. Verwendet wird device_id entsprechend der dokumentierten PyModbus-Schnittstelle. Ältere Beispiele mit slave benötigen eine Anpassung an die eingesetzte Version.
from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ModbusException
def read_words(client, address, count, unit):
if not 1 <= count <= 125 or not 0 <= address <= 65536 - count:
raise ValueError("Invalid register range")
response = client.read_holding_registers(
address, count=count, device_id=unit
)
if response.isError():
raise RuntimeError(f"Modbus error at {address}: {response}")
words = response.registers
if len(words) != count or any(not 0 <= x <= 65535 for x in words):
raise RuntimeError("Incomplete or invalid response")
return words
def find_base(client, unit):
failures = []
for base in (40000, 0, 50000):
try:
if read_words(client, base, 2, unit) == [0x5375, 0x6E53]:
return base
except (ModbusException, OSError, RuntimeError) as exc:
failures.append(f"{base}: {exc}")
raise RuntimeError("No SunS marker found; " + "; ".join(failures))
def discover(client, base, unit):
position = base + 2
models = []
for _ in range(256):
model, length = read_words(client, position, 2, unit)
if model == 0xFFFF:
if length != 0:
raise RuntimeError("Invalid end model length")
return models
next_position = position + 2 + length
if model == 0 or length == 0 or next_position > 65534:
raise RuntimeError("Invalid model header or address range")
models.append((model, position + 2, length))
position = next_position
raise RuntimeError("Model scan limit reached")
def phase_a_current(client, models, unit):
matches = [m for m in models if m[0] == 103]
if len(matches) != 1:
raise RuntimeError("Exactly one model 103 required")
_, start, length = matches[0]
if length < 5:
raise RuntimeError("Model 103 is too short")
block = read_words(client, start, 5, unit)
raw, sf_raw = block[1], block[4]
if raw == 0xFFFF or sf_raw == 0x8000:
return None
exponent = sf_raw - 65536 if sf_raw >= 32768 else sf_raw
if not -10 <= exponent <= 10:
raise RuntimeError("Invalid SunSpec scale factor")
return raw * (10.0 ** exponent)
def main():
unit = 1 # Device-specific configuration
client = ModbusTcpClient("192.168.1.50", port=502, timeout=3)
try:
if not client.connect():
raise RuntimeError("Connection failed")
base = find_base(client, unit)
models = discover(client, base, unit)
print("Base:", base, "Models:", models)
current = phase_a_current(client, models, unit)
print("No valid phase A current" if current is None
else f"{current:.2f} A")
finally:
client.close()
if __name__ == "__main__":
main()Die Grenze von 256 Modellköpfen ist eine Schutzgrenze dieses Beispiels, keine SunSpec-Grenze für die Modellanzahl. Fehlende Marke, fehlendes Modell oder ungültige Antwort beenden die Berechnung, statt eine plausible Zahl auszugeben. Das Beispiel behandelt genau eine Modell-103-Instanz; mehrere Instanzen benötigen eine ausdrückliche Auswahl. Die Tests nutzten einen simulierten Client mit alternativen Basisadressen, fehlenden Werten und fehlerhaften Köpfen. Kein Wechselrichter wurde angesprochen.
Weitere Typen und Abfragehäufigkeit
SunSpec legt für Mehrregisterwerte das höherwertige Wort zuerst fest. Modbus selbst definiert die Registerübertragung, nicht sämtliche herstellerspezifischen Mehrwortlayouts. Vorzeichenwerte benötigen Zweierkomplement-Dekodierung; Gleitkommawerte das passende IEEE-Format und eine Ungültigkeitsprüfung. Text belegt einen festen Registerbereich und kann durch Nullbytes beendet oder aufgefüllt sein.
Ungültige Bitmuster hängen vom SunSpec-Typ ab: etwa 0x8000 bei int16, 0xFFFF bei uint16 und null als „nicht akkumuliert“ bei acc32. Ein acc32 ist trotz gleicher Registerbreite nicht mit uint32 austauschbar.
Eine Blockabfrage spart Übertragungswege, garantiert aber keine gleichzeitige physische Abtastung. Funktion 03 erlaubt höchstens 125 Register pro Anfrage; ein Gerät kann eine kleinere Grenze vorgeben. Abfragehäufigkeit, Verbindungszahl und Aktualisierungstakt richten sich nach der Gerätedokumentation. Ein universell problemloses Fünfsekundenintervall gibt es nicht für jedes Produkt und jeden gemeinsam genutzten Gateway.
Home Assistant mit klar begrenztem Beispiel
Diese Konfiguration gilt nur für die gezeigte Karte: Modell-103-Daten beginnen bei 40071 und A_SF = -2. Der feste Wert scale: 0.01 ist ein Multiplikator, nicht der Exponent. Home Assistant entdeckt aus diesem YAML weder das SunSpec-Modell noch dessen Skalierungsfaktor automatisch.
modbus:
- name: inverter
type: tcp
host: 192.168.1.50
port: 502
sensors:
- name: "Strom Phase A"
slave: 1
address: 40072
input_type: holding
data_type: uint16
nan_value: 65535
scale: 0.01
precision: 2
unit_of_measurement: "A"
device_class: current
state_class: measurement
scan_interval: 10nan_value schließt den ungültigen Stromrohwert aus und erzeugt laut aktueller Integrationsdokumentation den Zustand unknown. Das separate Skalierungsregister wird dadurch nicht geprüft. Die feste Konfiguration passt nur bei weiterhin gültiger Karte und Skalierung; nach Firmware- oder Modellkonfigurationsänderungen sind erneute Erkennung und Vergleich sinnvoll.
Leistung in Watt verwendet device_class: power und state_class: measurement. Energiezähler verwenden device_class: energy, eine unterstützte Energieeinheit und je nach Rücksetzverhalten total oder total_increasing. Aktuelle Home-Assistant-Energieansichten können zusätzlich Leistungsmessungen verwenden. Energie lässt sich auch aus der Leistung über die Zeit berechnen: Konstant 1 kW über zwei Stunden ergeben 2 kWh. Bei wechselnder Leistung hängt die Genauigkeit von der Messhäufigkeit und dem Rechenverfahren ab.
Fragen und Antworten
Warum gehört der Skalierungsfaktor zum Modell?
Ein vorzeichenbehafteter Exponent verbindet Zahlenbereich und Auflösung. Bei gültiger int16-Leistung erlaubt Exponent 0 bis zu 32767 W; positive Exponenten erweitern den Bereich in größeren Schritten. Die referenzierte SunSpec-Modelldefinition beschreibt Skalierungspunkte als statisch. Geänderte Gerätekonfiguration oder Firmware rechtfertigen trotzdem eine Prüfung der resultierenden Karte.
Was kann Energiestatistiken verfälschen?
Eine ungültige null mit anschließendem echten Lebensdauerzählerstand kann unter total_increasing als Rücksetzung und großer neuer Zuwachs erscheinen. Deshalb zählen Punkttyp und Rücksetzverhalten. Bei einem modellgemäßen Akkumulator ist eine ungültige rohe null auszuschließen. Diese Regel darf nicht blind auf andere Sensoren mit gültigem Nullwert übertragen werden. Unbekannte Daten sollten unbekannt bleiben statt künstlich null zu werden.
Wie lässt sich der Zugriff begrenzen?
Klassisches Modbus TCP auf Port 502 besitzt keine eingebaute Authentifizierung oder Verschlüsselung. Modbus Security ist ein getrenntes TLS-basiertes Protokoll und benötigt Geräteunterstützung. Für dieses Tutorial genügt Lesezugriff. Ein begrenzter Netzwerkpfad ohne öffentliche Portweiterleitung reduziert die Angriffsfläche. Eine geroutete Firewall kontrolliert normalerweise keine direkten Verbindungen im selben Subnetz; Host-Firewalls, Switch-ACLs und Client-Isolation können solche Verbindungen dennoch einschränken.
Quellen
- SunSpec: Geräteinformationsmodelle, Entwurf 2021
- SunSpec: Informationsmodelle 1.9
- SunSpec: Definition von Modell 103
- SunSpec: Definition von Modell 160
- PyModbus 3.16.1: Client-Schnittstelle
- Home Assistant: Modbus-Integration
- Home Assistant: Energie-Fragen
- Home Assistant: Zustandsklassen für Sensoren
- Home Assistant: Integral
- Modbus-Anwendungsprotokoll V1.1b3
- Modbus- und Modbus-Security-Spezifikationen
- iSMA CONTROLLI: Beispiel zur Modbus-Adressschreibweise


