Künstliche Intelligenz
Fritz SmartEnergy 250: Mit Emulation zur PV-Akku-Steuerung
Da die Preise für Photovoltaik-Speicher stark gefallen sind, lohnt sich deren Anschaffung zunehmend. Damit lässt sich Strom speichern und etwa nach Zeitplan später ins Hausnetz einspeisen. Bedarfsgerechte Steuerung, auch als Nulleinspeisung bekannt, ist jedoch die Königsdisziplin. Allerdings braucht es dafür einen Smart-Meter – das meint ein Zangenmessgerät oder Strommesser –, das den aktuellen Strombedarf ausliest und einen Elektriker, der das einbaut – wenn noch Platz ist; das wird damit unter Umständen zum kostspieligen Unterfangen. Die Idee liegt nahe: Geht das nicht auch nichtinvasiv mit Lesekopf-Aufsatz wie dem Fritz SmartEnergy 250? Wir haben es ausprobiert.
Weiterlesen nach der Anzeige
Vorneweg gibt es dazu anzumerken, dass es keine einfache Lösung für alle Fälle gibt. Jeder PV-Speicher von unterschiedlichen Herstellern möchte die aktuellen Daten auf andere Art und Weise aufbereitet vorfinden. Selbst beim selben Hersteller gibt es da Unterschiede, abhängig von der Firmware-Version oder dem konkreten Speicher-Modell.
Die erste Idee war daher, das eigentlich von allen PV-Speichern unterstützte Smart-Meter Shelly Pro 3EM zu emulieren. Das ist gut erforscht und hat ein klar definiertes Verhalten. Es muss ein Programm her, das die Datenausgabe eines Shelly emuliert und dafür die Werte eines Lesekopfes, in diesem Fall dem populären Fritz SmartEnergy 250, ausliest und aufbereitet.
Als Plattform dient ein bereits im Heimnetz aktiver Raspberry Pi – genauer gesagt ein mit DietPi betriebener Radxa Zero 3 W – der ist recht genügsam und zieht selbst je nach Last zwischen 100 und 300 Milliampere). DietPi ist weitgehend mit Raspberry Pi OS kompatibel, was die Einrichtung anbelangt, das Projekt lässt sich daher auch damit umsetzen. Es gibt bereits einige Lösungen etwa für Home Assistant zum Einbinden von IR-Leseköpfen, aber das hat und will nicht jeder unbedingt haben, und der direkte Weg in die Akku-App ohne ein ganzes Heimautomatisierungssystem einzurichten, wirkt dafür eleganter.
Vibecoding
Da es sehr viel zu erforschen und auszuprobieren gibt, soll die Programmierarbeit mit starker Unterstützung von Künstlicher Intelligenz erfolgen. Dazu reichen bereits kostenlos nutzbare Modelle wie Googles Gemini aus – wichtig ist jedoch, darauf zu achten, nicht auf dem derzeit voreingestellten „Flash-Lite“-Modell zu bleiben, sondern auf „Flash“ umzustellen. Flash-Lite macht viele Fehler und ist äußerst vergesslich; das größere Flash-Modell jedoch nicht unbegrenzt nutzbar. Google sperrt die Nutzung dann gegebenenfalls mal für mehrere Stunden oder schaltet ohne Rückmeldung auf das Lite-Modell zurück, wodurch auf einmal fehlerhafte Antworten zurückkommen. Das Skript landet mittels git in einer Versionierung, damit gegebenenfalls fehlerhafte Änderungen leicht rückgängig zu machen sind.
Im konkreten Versuch geht es darum, zwei Marstek-B2500-D-Speicher mit Daten aus dem Fritz SmartEnergy 250 zu steuern. In der App kann man dafür Marstek-eigene Smart-Meter wie das CT002 oder CT003 auswählen, aber auch mehrere Shelly-Smart-Meter, die weitverbreitet für solche Steuerungsaufgaben sind. Die Marstek-Speicher sind typische Nachrüstlösungen, die durch die MC4-Anschlüsse zwischen Solarpanele und Wechselrichter eingeschleift werden. Bei den Komplettlösungen ist inzwischen die Integration eines Wechselrichters in den Akku häufig anzutreffen.
Weiterlesen nach der Anzeige
Viele Fehlschläge führten zu einem Ansatz, der dann erfolgreich war. Die KI darf nicht vollständig losgelöst das Skript bauen, sondern mit klaren Vorgaben und dem Verweis etwa auf das AstraMeter-Projekt von Tom Quist, der eine bekannte Emulation für Home Assistant gebaut hat; auch das Uni-Meter-Projekt bietet derartige Emulationen. Bereits darin sind die unterschiedlichen Emulationen verankert und die KI leitet daraus funktionierende Umsetzungen ab, gegebenenfalls mit mehreren Nachfragen und Iterationen.
Rückgriff auf bekannte Emulator-Projekte
Im Beispiel mussten Eigenheiten der Marstek-Speicher berücksichtigt werden. Wer PV-Speicher von anderen Herstellern nutzt, muss die KI-Prompts entsprechend darauf anpassen.
Ein beispielhafter Startpunkt ist etwa: „Erstelle unter Zuhilfenahme der AstraMeter-Quellen von Tom Quist ein Python-Skript, das mit der fritzconnection-Python-Bibliothek die aktuellen Daten aus einem Fritz SmartEnergy 250 (Fritz SE) ausliest. Nutze die Adressen <192.168.178.1 (Fritzbox-IP)> zum Auslesen des Fritz SE. Benutzername lautet , Passwort . Die AIN lautet hingegen . Die gesuchten Werte sind im AIN-Zweig -1 und -2 zu finden. Lies diese Werte im Speicher ein und bereite sie auf, dass sie als Shelly Pro 3EM ausgegeben werden. Stelle im Python-Skript die mDNS-Broadcasts bereit, es soll auf Anfragen auf den benötigten Ports antworten. Ein Webserver ist bereits vorhanden, daher sind die TCP-Ports 80, 443, 8080 und 8443 nicht nutzbar.“
Die Python-Bibliotheken lassen sich unter Raspbian und DietPi nicht einfach mittels pip nachinstallieren. Der Aufruf lautet da dann etwa sudo apt install python3-.
Das Konto auf der Fritzbox muss Zugriff auf Smarthome-Funktionen haben, das müssen Interessierte dort einstellen. Dabei müssen sie auch gleich die Geräte-Kennung (AIN) des Fritz SmartEnergy in der Smart-Home-Rubrik der Fritzbox-Oberfläche herausfinden und mit übergeben. Gegebenenfalls ist auch eine mehrstufige Entwicklung hilfreich – zunächst muss das Auslesen des Fritz SE funktionieren, idealerweise im 10-Sekunden-Takt. Die KI braucht gegebenenfalls den Hinweis, dass SID-Handling nötig ist, da die Fritzbox-Firmwares der Versionen 8.20 und neuer da Änderungen brachten und einfaches Auslesen über PnP nicht klappt.
Dann muss das Skript noch die Daten so ausgeben, wie der eingesetzte Speicher es gerne hätte. Bei Marstek war es zum Schluss relativ einfach: Die Speicher selbst fragen auf UDP-Port 1010 per Broadcast nach, und erwarten eine Antwort auf UDP-Port 22222. Kaum war der Mechanismus umgesetzt, ließ sich die Emulation als Steuerung in der Marstek einrichten. Hier kommt das nächste Problem. In der Fritzbox-Oberfläche wechselt der Fritz SE bei längerer Beobachtung von seinem 2-Minuten-Zyklus in ein 10-sekündliches Update-Intervall; eine Speisung über USB anstatt aus Batterien sorgt ebenfalls für den Übergang auf den 10-s-Takt. Damit sollte eine einigermaßen realistische Nachverfolgung des aktuellen Bedarfs möglich sein. Es gelang im Test jedoch nicht, diesen 10-Sekunden-Zyklus im Batteriebetrieb zu erzwingen. Die Werte bleiben also zwei Minuten stehen – zumindest in der Nacht passt das meist zum vorherrschenden Bedarf. Ein weiteres Problem besteht darin, dass die Emulation die Werte manchmal nicht korrekt übernimmt und damit falsche Werte für noch längere Zeit stehen bleiben.
Mit emuliertem Shelly lassen sich zudem mehrere Speicher nicht ordentlich gleichzeitig steuern, auch, wenn das Skript allen antwortet. Die Regelung schwingt sich dann auf und ab. Wer den konkreten Anwendungsfall mit mehreren Marstek-Speichern hat, kann der KI mitteilen, dass anstatt der Shelly-Emulation der Marstek-CT002 oder CT003 emuliert werden sollen. Das ist in den Quellen des AstraMeter-Projekts bereits hinterlegt und verspricht deutlich bessere Ergebnisse.
Keine dauerhafte Lösung
Das Skript kann die KI so bauen und auch eine Anleitung mitliefern, dass es sich auf dem Einplatinenrechner als Dienst einrichten lässt. Damit startet es automatisch und liefert dauerhaft die Emulation. Wer den Fritz SE 250 über USB speisen kann und lediglich einen Speicher verwendet, kann sich sicherlich mit einer solchen Lösung behelfen. Als Versuchsprojekt und zum Spaß funktioniert das auf jeden Fall – und ist insbesondere dann eine interessante Zwischenlösung, wenn der Elektriker zum Einbau eines „richtigen“ Smart-Meters erst in Wochen einen Termin hat.
Der Vergleich zur Variante mit dem echten Smart Meter in der Unterverteilung zeigt jedoch die Grenzen auf. Ein echtes Smart Meter braucht maximal Sekunden, bis Laständerungen bei den Speichern ankommen, die dann zügig darauf reagieren. Außerdem lassen sich damit auch mehrere Speicher ohne Klimmzüge und potenziell wackeliger Regelung steuern.
Das Beispielskript funktioniert mit Fritzbox-Firmwares ab Version 8.20 und kann darin die Werte des Fritz SE 250 auslesen. Die Ausgabe ist für Marstek B2500-D-Speicher angepasst. Das kann jedoch auch für andere Speicher als Ausgangspunkt für Anpassungen dienen – gegebenenfalls unter Zuhilfenahme einer KI zum Anpassen.
#!/usr/bin/python3
import threading
import time
import json
import socket
import logging
import requests
import hashlib
import binascii
import xml.etree.ElementTree as ET
# --- KONFIGURATION ---
FRITZ_IP = '192.168.178.1'
FRITZ_USER = ''
FRITZ_PW = ''
FRITZ_BASE_AIN = 'abcde fghijkl'
LISTEN_PORT = 2220
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
data_lock = threading.Lock()
shared_data = {"power": 0.0, "sid": "0000000000000000", "gateway_addr": None}
class FritzCollector:
def __init__(self):
self.ain_base = FRITZ_BASE_AIN.replace(' ', '%20')
def update(self):
try:
if shared_data["sid"] == "0000000000000000":
r = requests.get(f" timeout=5)
ch = ET.fromstring(r.text).findtext('Challenge')
res = f"{ch}-{hashlib.md5(f'{ch}-{FRITZ_PW}'.encode('utf-16le')).hexdigest()}"
resp = requests.get(f" timeout=5).text
shared_data["sid"] = ET.fromstring(resp).findtext('SID')
url = f"http://{FRITZ_IP}/webservices/homeautoswitch.lua?ain={self.ain_base}-1&sid={shared_data['sid']}&switchcmd=getdeviceinfos&refresh=1"
req = requests.get(url, timeout=5)
root = ET.fromstring(req.text)
power = round(float(root.find(".//power").text) / 1000.0, 2)
with data_lock:
shared_data["power"] = power
logging.info(f"FritzBox Update: {power} W")
except Exception as e:
logging.error(f"Fritz-Fehler: {e}")
shared_data["sid"] = "0000000000000000"
def collector_loop():
collector = FritzCollector()
while True:
collector.update()
time.sleep(10)
def udp_server():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', LISTEN_PORT))
logging.info(f"Server aktiv auf Port {LISTEN_PORT}")
while True:
try:
data, addr = sock.recvfrom(1024)
with data_lock:
shared_data["gateway_addr"] = addr
if b"EM.GetStatus" in data:
with data_lock:
p = shared_data["power"]
response = {
"id": 1,
"src": "shellypro3em-e682e89c1724",
"result": {
"id": 0,
"a_act_power": p, "b_act_power": 0.0, "c_act_power": 0.0,
"total_act_power": p,
"a_voltage": 230.0, "a_current": round(p/230.0, 2)
}
}
sock.sendto(json.dumps(response, separators=(',', ':')).encode('utf-8'), addr)
logging.info(f"Antwort an {addr} gesendet.")
except Exception as e:
logging.error(f"UDP Fehler: {e}")
if __name__ == "__main__":
# Korrekter Start der Threads
threading.Thread(target=collector_loop, daemon=True).start()
udp_server()
(dmk)