Solarwatt REST-API verlangt Authorisierung nach Software Update

Hallo Community,

hatte ja schon an anderer Stelle beschrieben wie “begeistert” wir davon sind seit einem Jahr eine Solaranlage von Solarwatt zu haben :face_with_spiral_eyes:
Gestern war es mal wieder soweit: Software Update von Solarwatt auf unserem Solarwatt Manager Flex und … NICHTS(!) ging mehr :enraged_face:!

Zum Setup:

wir machen unser gesamtes Energie Management über Home Assistant bzw. die Ladesteuerung unseres E-Autos, sowie das thermische Management unsere Wärmepumpe über evcc, das in Home Assistant integriert ist. Um dies bewerkstelligen zu können werden die entsprechenden Werte (Einspeisung ins Netz, Produktion der PV-Anlage, …) über das REST-API von Solarwatt ausgelesen … nach besagtem Software Update kam KEINER(!) der Werte mehr in Home Assistant an :sad_but_relieved_face:.

Analyse:

Eine etwas “emotionale” Analyse hat ergeben, dass das REST-API, das vorher public war nun eine Autorisierung (Password) benötigt. Nach einem Login wird ein Cookie mit einer “kiwisessionid” geschickt, die für 1h gültig ist.

Lösung (temporär?):

Haben nach einer kurzen, ziemlich schlaflosen Nacht … tja, so ist es eben :wink: … folgenden Proxy erstellt:

from flask import Flask, jsonify
import requests
import time

app = Flask(__name__)
session = requests.Session()

LOGIN_URL = "http://<your ip Solarwatt Manager flex>/auth/login"
DATA_URL = "http://<your ip Solarwatt Manager flex>/rest/items"
PASSWORD = "<your password>"
USERNAME = "installer"

last_login = 0
SESSION_TTL = 900

def login():
    global last_login
    payload = {
        "username": USERNAME,
        "password": PASSWORD,
        "url": "/"
    }
    response = session.post(LOGIN_URL, data=payload)
    if response.status_code == 200:
        last_login = time.time()
        print("Login erfolgreich")
    else:
        print("Login fehlgeschlagen:", response.status_code)

def ensure_session():
    if time.time() - last_login > SESSION_TTL:
        print("Erneuter Login")
        login()

@app.route("/item/<item_name>")
def get_item(item_name):
    ensure_session()
    response = session.get(DATA_URL)
    if response.status_code != 200 or "application/json" not in response.headers.get("Content-Type", ""):
        return "Fehler beim Abrufen der Daten", 500

    try:
        data = response.json()
    except Exception as e:
        return f"Fehler beim Parsen: {e}", 501

    for item in data:
        if item.get("name") == item_name:
            return jsonify(item)

    return f"Item '{item_name}' nicht gefunden", 404

if __name__ == "__main__":
    login()
    app.run(host="0.0.0.0", port=5000)

Er läuft auf einem weiteren Raspberry PI und klemmt sich zwischen unseren Solarwatt Manager Flex und Home Assistant. Was er macht ist schnell erklärt:

  1. Login und damit öffnen einer Session
  2. Request von Home Assistant annehmen und an den Solarwatt Manager routen
    (alle REST Items sind nun über die URL http://:5000/item verfügbar)
  3. überpüfen ob 15min vorbei sind … erneuerter Login so dass die Session erneuert wird

Das Ganze läuft nun schon seit über einem halben Tag stabil. Dachte ich poste die Lösung … vielleicht hat jemand dasselbe Problem … oder eine bessere Lösung :wink:

Wie geht es weiter?

Support von Solarwatt angerufen, die erklärt haben, dass sie da auf ihren “Software Hersteller” angewiesen wären und selber nichts machen können. Habe sie darum gebeten den vorherigen Stand der Software wieder zu installieren … auch hier war die Antwort, dass sie auf ihren “Software Hersteller” angewiesen sind. Verkneife mir hier jeden weiteren Kommentar …

Sollte von Seiten Solarwatt nichts kommen, wird aus der temporären Lösung eine permanente und der Proxy zu einem Demon :wink:
Was etwas “stört”, neben der Art und Weise wie die Situation entstanden ist, ist die Tatsache, dass ich einen weiteren Raspberry Pi benötige … leider kenne ich mich aber mit Home Assistant nicht so gut aus, so dass ich nicht weiß wie man den Proxy dort integrieren könnte … aber vielleicht weiß das jemand von euch :wink:

1 „Gefällt mir“

Ja, wir tüfteln auch dran rum … Du hast immerhin da jemand erreicht …. habe ich noch nie geschafft :wink:

@Bernd28 deine Bemerkung “… habe ich noch nie geschafft :wink:” wundert mich nicht: Solarwatt hat eine gute Marketing Maschine hinter der leider sehr, sehr wenig Substanz ist … haben es vielfach bereut bei ihnen gekauft zu haben. Tja … shit happens …

Zurück zum Thema: habe den Proxy “entschärft”, so dass er nun deutlich weniger “chatty” ist:

from flask import Flask, jsonify
import requests
import time
import logging

app = Flask(__name__)
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
session = requests.Session()

LOGIN_URL = "http://solar-energiemanager.fritz.box/auth/login"
DATA_URL = "http://solar-energiemanager.fritz.box/rest/items"
PASSWORD = "eqyknupc"
USERNAME = "installer"

last_login = 0
SESSION_TTL = 900

logging.basicConfig(level=logging.ERROR)

def login():
    global last_login
    payload = {
        "username": USERNAME,
        "password": PASSWORD,
        "url": "/"
    }
    response = session.post(LOGIN_URL, data=payload)
    if response.status_code == 200:
        last_login = time.time()
        # print("Login erfolgreich")
    else:
        # print("Login fehlgeschlagen:", response.status_code)
        logging.error(f"Login fehlgeschlagen: {response.status_code}")

def ensure_session():
    if time.time() - last_login > SESSION_TTL:
        # print("Erneuter Login")
        login()

@app.route("/item/<item_name>")
def get_item(item_name):
    ensure_session()
    response = session.get(DATA_URL)
    if response.status_code != 200 or "application/json" not in response.headers.get("Content-Type", ""):
        logging.error(f"Fehler beim Abrufen der Daten: Status {response.status_code}")
        return "Fehler beim Abrufen der Daten", 500

    try:
        data = response.json()
    except Exception as e:
        logging.error(f"Fehler beim Parsen: {e}")
        return f"Fehler beim Parsen: {e}", 501

    for item in data:
        if item.get("name") == item_name:
            return jsonify(item)

    logging.error(f"Item '{item_name}' nicht gefunden")
    return f"Item '{item_name}' nicht gefunden", 404

if __name__ == "__main__":
    login()
    app.run(host="0.0.0.0", port=5000)

Die original print Statements sind noch im Code … aber nun auskommentiert. Der eigentlich Durchbruch war das Ändern des Log-Levels von “Flask”.
Außerdem läuft das Ganze nun als Service …

[Unit]
Description=Solarwatt Proxy Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /<your path>/solarwatt_proxy.py
WorkingDirectory=/<your path>/
Restart=on-failure
RestartSec=5
StandardOutput=file:/var/log/solarwatt_proxy.log
StandardError=file:/var/log/solarwatt_proxy.err
User=proxyuser
Group=proxyuser

[Install]
WantedBy=multi-user.target

… und seit gestern Abend stabil. evcc, das als Plugin für Home Assistant installiert ist, ist ebenfalls wieder glücklich und lädt im Moment unser E-Auto :grinning_face:

Bin gespannt ob nächste Woche irgendeine Reaktion von Solarwatt kommt …

3 „Gefällt mir“

Hallo @asch

Wäre es möglich, dass du noch etwas konkreter wirst, was du wie installiert hast und auch wie du was konfiguriert hast, damit ich das eventuell auf einem Raspberry Pi mit Debian selbst installieren und nachvollziehen kann?

Das wäre ganz lieb von dir.

Danke im voraus.

MfG UB67

1 „Gefällt mir“

Vielen Dank für den Codeschnipsel hat mir mittels ChatGPT geholfen meine config zum Laufen zu bekommen. :+1:

Kurz gesagt habe ich den Code von asch in ChatGPT kopiert und mich davon leiten lassen. Fürs erste Mal hat es erstaunlich gut funktioniert.

Habe allerdings HA in einer VM unter Proxmox laufen und eine neue Debian-VM für den Proxy aufgesetzt. Kommt halt auf deine Konfiguration an.

Hallo @UB67 ,

hoffe folgende Beschreibung hilft dir weiter: so ist die Konfiguration bei uns …

  • obiges Python Code Stück unter ~/Programs/Python/solarwatt_proxy.py abgelegt

  • in solarwatt_proxy.py die Zeilen …

    LOGIN_URL = "http://<deine ip adresse>/auth/login"
    DATA_URL = "http://<deine ip adresse>/rest/items"
    PASSWORD = "<dein password>"
    

    … an dieser Stelle musst du deine Konfiguration eintragen. <deine ip adresse> ist die Internetadresse deines Solarwatt Manager Flex. <dein password> ist das Passwort mit dem du dich auf dem Solarwatt Manager Flex anmeldest.

Wenn das soweit läuft, einfach mal das Python Programm auf dem Raspberry starten (in unserem Fall geht das über python3 solarwatt_proxy.py). Nun müsste der Zugriff über deinen Browser auf einer URL dieser Art klappen:

http://<dein Raspberry>:5000/item/kiwigrid_location_standard_6ce51aeb70e1_harmonized_power_consumed

ACHTUNG: <dein Raspberry> ist zu ersetzen mit der IP Adresse deines Raspberry PIs UND(!) die URL heißt mit Sicherheit bei dir anders … nimm einfach eine der URLs die du ursprünglich hattest und ersetzte http://<dein Solarwatt Manager Flex>/rest/items durch http://<dein Raspberry PI>:5000/item und du müsstest nun die ursprüngliche Werte wieder sehen. Der Proxy macht nichts anderes als deine Requests weiterzugeben … allerdings nachdem er sich vorher angemeldet hat :wink:
Wenn das geklappt hat kommt das Aufsetzen eines Service, damit das Programm permanent läuft. Programm wieder stoppen und die oben beschrieben Konfigurationsdatei unter /etc/systemd/system mit z.B. dem Namen solarwatt_proxy.service ablegen und danach …

sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable solarwatt-proxy.service
sudo systemctl start solarwatt-proxy.service

… eingeben und es müsste dann loslaufen.

Danach einfach alle URLs wie oben beschrieben (ersetzte http://<dein Solarwatt Manager Flex>/rest/items durch http://<dein Raspberry PI>:5000/item) anpassen und Home Assistent die geänderte Konfiguration einlesen lassen.

Hoffe das hilft dir.

1 „Gefällt mir“

Hallo,

vielen Dank für dieses Thema.

Um http:///rest/items für evcc komplett bereitzustellen habe ich mich (und CHatGPT) von @asch Python Skript inspirieren lassen:

from flask import Flask, Response, request
import requests
import time
import logging

app = Flask(__name__)
session = requests.Session()

# === KONFIGURATION ===
USERNAME = "installer"
PASSWORD = "password"
SOLARWATT_IP = "192.168.178.100"  # Ersetze durch IP deines Solarwatt Manager Flex
LOGIN_URL = f"http://{SOLARWATT_IP}/auth/login"
DATA_URL = f"http://{SOLARWATT_IP}/rest/items"

SESSION_TTL = 900  # 15 Minuten
last_login = 0

logging.basicConfig(level=logging.ERROR)

def login():
    global last_login
    logging.error("Login-Versuch...")
    payload = {
        "username": USERNAME,
        "password": PASSWORD,
        "url": "/"
    }
    try:
        response = session.post(LOGIN_URL, data=payload, timeout=5)
    except Exception as e:
        logging.error(f"Login fehlgeschlagen: {e}")
        return False

    if response.status_code == 200:
        last_login = time.time()
        logging.error("Login erfolgreich")
        return True
    else:
        logging.error(f"Login fehlgeschlagen mit Status: {response.status_code}")
        return False

def ensure_session():
    if time.time() - last_login > SESSION_TTL:
        logging.error("Session abgelaufen, erneuter Login...")
        return login()
    return True

@app.route("/rest/items")
def rest_items_proxy():
    # Stelle sicher, dass wir eingeloggt sind
    if not ensure_session():
        return Response("Login fehlgeschlagen", status=500)

    try:
        response = session.get(DATA_URL, timeout=5)
    except Exception as e:
        return Response(f"Fehler beim Abrufen der Daten: {e}", status=502)

    # Falls 401 Session abgelaufen oder ungueltig: Neu einloggen & wiederholen
    if response.status_code == 401:
        logging.error("401 erhalten, Session ungueltig. Versuche neuen Login...")
        if login():
            try:
                response = session.get(DATA_URL, timeout=5)
            except Exception as e:
                return Response(f"Fehler beim zweiten Abruf: {e}", status=502)
        else:
            return Response("Login nach 401 fehlgeschlagen", status=500)

    return Response(
        response.content,
        status=response.status_code,
        content_type=response.headers.get("Content-Type", "application/json")
    )

if __name__ == "__main__":
    if not login():
        logging.error("Initialer Login fehlgeschlagen: beende.")
        exit(1)

    app.run(host="0.0.0.0", port=5000)
1 „Gefällt mir“

@thokaro: danke für die Inspiration :grinning_face: … eine Frage an dich: kennst du dich mit Home Assistant Integration aus? Der Vorteil des Proxy Ansatz ist: das Login Verhalten liegt nun gekapselt hinter dem Proxy. Sollte Solarwatt “morgen” an weiteren Schrauben drehen müsste lediglich der Proxy angepasst werden. Home Assistant und z.B. evcc laufen weiter bekommen davon nichts mit. Der Nachteil des Proxy Ansatz ist: im Moment benötigt er ein weiteres System (in unserem Fall einen Raspberry PI). Würde das Ganze als Home Assistant Integration verpackt würde sich Home Assistent “nebenher” darum kümmern. Weißt du wie aufwendig es ist eine solche Integration zu erstellen?

Hallo Community,

der Vollständigkeit halber: hier die Antwort des Solarwatt Supports auf meine Frage hin wieso da ein Update eingespielt wurde, der eine Public Schnittstelle in eine die Autorisierung benötigt transformiert:

"Mit dem aktuellen Update des SOLARWATT Managers wurden weitere Maßnahmen ergriffen, den Manager für künftige Anwendungsfälle, insbesondere für netzdienliche Funktionen fit zu machen und etwaige Zertifizierungen vorzubereiten. Die Einbindung von Funktionalitäten z.B. im Sinne des Energiewirtschaftsgesetzes und andere UseCases bedingen restriktivere Maßnahmen, als es bisher schon gab.

Die … bisher genutzten APIs wurden nicht beworben oder versprochen - im Gegenteil: auf Nachfrage haben wir stets von einer Nutzung abgeraten, da wir nur die Funktionen des EnergyManagers supporten, aber nicht die Software, die dahintersteht."

Hilft auch nicht wirklich weiter …

Hallo @asch, hallo @thokaro,

danke für eure schnelle und sehr umfassende Hilfe.

Im Moment läuft das soweit, ich habe es nur noch nicht hinbekommen, dass evcc und der Flask Service auf einem Rechner, in dem Fall ein Raspberry Pi laufen.

Also nochmals Danke, sehr angenehm :+1:

Hallo!

Vielen Dank für das Skript, damit klappt es wieder.

Ein kleiner Verbesserungsvorschlag: Aktuell wird bei jeder Anfrage nach einem Item der komplette Item-Satz geholt. Das hat bei mir in der Vergangenheit zu Problemen geführt, wenn ich das zu schnell hintereinander gemacht habe. Mit

     response = session.get(DATA_URL+"/"+item_name)

wird nur das angefragte Item beim ManagerFlex abgerufen. Dann nur noch

    try:
        item = response.json()
    except Exception as e:
        logging.error(f"Fehler beim Parsen: {e}")
        return f"Fehler beim Parsen: {e}", 501

    return jsonify(item)

Super Work-Around, danke für’s Teilen! Mit Proxmox sicher relativ schnell zu realisieren. Würde mich mal interessieren, ob die älteren ‘Solarwatt Energy Manager pro’ ein ähnliches Update bekommen (werden). Derzeit geht da die REST-API noch ohne zusätzlichen Login… (derzeitige Version bei mir: 7.46.16.0). Kann man Updates des Energy-Managers eigentlich nebenwirkungsfrei verhindern (Internet-Zugang kappen ginge sicherlich mit Verlust des Zugriffs auf die Daten im Webportal einher)?

Hallo Community,

danke für euer Feedback.

@chrizzzp: unter anderem habe ich genau diese Frage “Kann man Updates des Energy-Managers eigentlich nebenwirkungsfrei verhindern” bzw. wäre es nicht angebracht Kunden vor dem “spontanen” Einspielen eines Updates

  • zu informieren
  • eine Liste der Änderungen zukommen zu lassen so dass man die Möglichkeit hat einen Update zu rejecten bzw. zu delayen

Die Antwort des Supports von Solarwatt war, dass …

  • " … es (leider) auch nicht möglich (ist), die Updatefunktion des Managers abzuwählen …"
  • " … wir nur die Funktionen rund um den EnergyManager supporten, aber nicht die Software, die dahintersteht …"

Letzteres ist eine “interessante” Sichtweise :wink:

Haben dem Support erklärt, da wir weder die App noch das Portal von Solarwatt nutzen (läuft alles über Home Assistant bzw. evcc deutlich reibungsloser und besser) wir mit Sicherheit die “neuen” Funktionen, die der Upgrade zur Verfügung stellt nicht benötigen … wir erwarten

  • einen Downgrade oder …
  • ein Zurückdrehen der Authentifizierung oder …

  • … aber auf jeden Fall einen zielführenden Vorschlag wie in Zukunft mit solchen Situation umgegangen wird. “Spontanes” Einspielen von Updates ist keine Option!

Bisher kam keine weitere Reaktion …

Das Ganze läuft, wie erwartet :wink:, darauf hinaus, dass die vorgeschlagene “temporäre” Lösung wohl länger am Laufen gehalten werden muss … schaue mir nun an, was es bedeuten würde das Ganze in eine Home Assistant Integration zu verpacken …

Bin zwar mit OpenHAB unterwegs, aber das Problem mit Solarwatt Flex und der notwendigen Authentifizierung betrifft ja alle, egal mit welchem Metering-Instrument. Beim Versuch, mit GPT das Ding zu adaptieren und Cookie-Veränderungen durch eine automatisiertes Login alle 25min zu antizipieren, stößt die Engine beim REST-Login auf das Problem, das zwei user-password Paare notwendig sind. Zum einen das Paar, mit dem man sich am Energery Manager anmeldet (das habe ich), dann redet GPT noch von einem ominösen Installer User, den man erfragen soll. Ist Euch das Problem auch untergekommen beim Versuch, für Persistenz zu sorgen und Cookie-Veränderungen zu antizipieren ?
Über CURL und das Session cookie komme ich an die benötigten Werte, aber nicht über Rule Scripting, weil:

GPT sagt:
Das Formular verlangt explizit installer (Hidden-username), siehe dein Log („Hidden: url,username“ + Action /auth/login).

    1. Das Formular verlangt explizit installer (Hidden-username), siehe dein Log („Hidden: url,username“ + Action /auth/login).
    2. Mit falschem Passwort für installer gibt’s 303 → / und kein Cookie; mit falschem fitter gibt’s logon-error.html.
    3. Erst mit korrektem installer-Passwort wird im Follow-Up-GET ein Set-Cookie: kiwisessionid=… auftauchen.

Der Text vom Solarwatt Support war identisch zu meinem, wir haben damit nix zu tun, Software können wir nicht…

Hier eine Version die sowohl sämtliche Items als auch ein bestimmtes Item abholen kann:

from flask import Flask, Response, request
import requests
import time
import logging

app = Flask(__name__)
session = requests.Session()

# === KONFIGURATION ===
USERNAME = "installer"
PASSWORD = "password"
SOLARWATT_IP = "192.168.178.100"  # Ersetze durch IP deines Solarwatt Manager Flex
LOGIN_URL = f"http://{SOLARWATT_IP}/auth/login"
DATA_URL = f"http://{SOLARWATT_IP}/rest/items"

SESSION_TTL = 900  # 15 Minuten
last_login = 0

logging.basicConfig(level=logging.ERROR)

def login():
    """Meldet sich beim Solarwatt-System an und aktualisiert die Session."""
    global last_login
    logging.error("Login-Versuch...")
    payload = {
        "username": USERNAME,
        "password": PASSWORD,
        "url": "/"
    }
    try:
        response = session.post(LOGIN_URL, data=payload, timeout=5)
    except Exception as e:
        logging.error(f"Login fehlgeschlagen: {e}")
        return False

    if response.status_code == 200:
        last_login = time.time()
        logging.error("Login erfolgreich")
        return True
    else:
        logging.error(f"Login fehlgeschlagen mit Status: {response.status_code}")
        return False

def ensure_session():
    """Stellt sicher, dass die Session noch gültig ist."""
    if time.time() - last_login > SESSION_TTL:
        logging.error("Session abgelaufen, erneuter Login...")
        return login()
    return True


# === ZUSAMMENGEFASSTE ROUTE: /rest/items und /rest/items/<item_name> ===
@app.route("/rest/items", defaults={"item_name": None})
@app.route("/rest/items/<item_name>")
def rest_items_combined_proxy(item_name):
    """Proxy für alle Items oder ein bestimmtes Item."""
    if not ensure_session():
        return Response("Login fehlgeschlagen", status=500)

    # Ziel-URL bestimmen
    target_url = DATA_URL if item_name is None else f"{DATA_URL}/{item_name}"

    try:
        response = session.get(target_url, timeout=5)
    except Exception as e:
        msg = f"Fehler beim Abrufen {'der Items' if item_name is None else f'des Items {item_name}'}: {e}"
        return Response(msg, status=502)

    # Session ggf. erneuern bei 401
    if response.status_code == 401:
        logging.error("401 erhalten, Session ungültig. Versuche neuen Login...")
        if login():
            response = session.get(target_url, timeout=5)
        else:
            return Response("Login nach 401 fehlgeschlagen", status=500)

    return Response(
        response.content,
        status=response.status_code,
        content_type=response.headers.get("Content-Type", "application/json")
    )


if __name__ == "__main__":
    if not login():
        logging.error("Initialer Login fehlgeschlagen: beende.")
        exit(1)

    app.run(host="0.0.0.0", port=5000)

@djtn Du musst 2 verschiedene Zugänge unterscheiden:

  1. https://energymanager.com Das ist das Onlineportal, wo du Verbräuche und vieles mehr einsehen kannst. Diesen Zugang benötigst du nicht.
  2. http://energymanager.local/ Das ist der Zugang zum Energymanager Pro oder Manager flex 1.0 / flex 1.5 / rail Dort musst du dich mit einem Passwort anmelden, und genau dieses Passwort benötigst du für den Proxy. Der passende Benutzer dazu lautet “installer”.

@asch Nein, eigene HomeAssistant Integrationen habe ich bisher nicht umgesetzt, was ich auch nicht geplant habe.

2 „Gefällt mir“

Bei alten Energymanager Pro hatte ich immer das Problem, dass zu viele Anfragen ihn an die Grenze gebracht haben, weshalb ich das Intervall zur Abfrage recht hoch gesetzt habe (in HA und evcc). Deshalb ist das Skript nun noch ein wenig weiter angepasst und mit einem Cache versehen:

from flask import Flask, Response
import requests
import time
import logging

app = Flask(__name__)
session = requests.Session()

# === KONFIGURATION ===
USERNAME = "installer"
PASSWORD = "password"
SOLARWATT_IP = "192.168.178.100"
LOGIN_URL = f"http://{SOLARWATT_IP}/auth/login"
DATA_URL = f"http://{SOLARWATT_IP}/rest/items"

SESSION_TTL = 900  # 15 Minuten
CACHE_TTL = 15     # Cache-Dauer in Sekunden (z. B. 15 Sekunden)
last_login = 0

logging.basicConfig(level=logging.ERROR)

# === Einfacher In-Memory-Cache ===
cache = {}  # key -> (timestamp, response_data, status_code, content_type)

def login():
    """Meldet sich beim Solarwatt-System an und aktualisiert die Session."""
    global last_login
    logging.error("Login-Versuch...")
    payload = {"username": USERNAME, "password": PASSWORD, "url": "/"}
    try:
        response = session.post(LOGIN_URL, data=payload, timeout=5)
    except Exception as e:
        logging.error(f"Login fehlgeschlagen: {e}")
        return False

    if response.status_code == 200:
        last_login = time.time()
        logging.error("Login erfolgreich")
        return True
    else:
        logging.error(f"Login fehlgeschlagen mit Status: {response.status_code}")
        return False


def ensure_session():
    """Stellt sicher, dass die Session noch gültig ist."""
    if time.time() - last_login > SESSION_TTL:
        logging.error("Session abgelaufen, erneuter Login...")
        return login()
    return True


def get_cached_response(key):
    """Liefert eine gültige Cache-Antwort, falls vorhanden."""
    entry = cache.get(key)
    if not entry:
        return None
    timestamp, data, status, content_type = entry
    if time.time() - timestamp > CACHE_TTL:
        del cache[key]
        return None
    return Response(data, status=status, content_type=content_type)


def set_cache(key, response):
    """Speichert die Antwort im Cache."""
    cache[key] = (
        time.time(),
        response.content,
        response.status_code,
        response.headers.get("Content-Type", "application/json")
    )


# === ZUSAMMENGEFASSTE ROUTE mit Cache ===
@app.route("/rest/items", defaults={"item_name": None})
@app.route("/rest/items/<item_name>")
def rest_items_combined_proxy(item_name):
    """Proxy für alle Items oder ein bestimmtes Item, mit einfachem Cache."""
    if not ensure_session():
        return Response("Login fehlgeschlagen", status=500)

    target_url = DATA_URL if item_name is None else f"{DATA_URL}/{item_name}"
    cache_key = target_url

    # Cache prüfen
    cached = get_cached_response(cache_key)
    if cached:
        logging.debug(f"Cache-Treffer für {cache_key}")
        return cached

    # Anfrage durchführen
    try:
        response = session.get(target_url, timeout=5)
    except Exception as e:
        msg = f"Fehler beim Abrufen {'der Items' if item_name is None else f'des Items {item_name}'}: {e}"
        return Response(msg, status=502)

    if response.status_code == 401:
        logging.error("401 erhalten, Session ungültig. Versuche neuen Login...")
        if login():
            response = session.get(target_url, timeout=5)
        else:
            return Response("Login nach 401 fehlgeschlagen", status=500)

    # Ergebnis in Cache speichern
    set_cache(cache_key, response)

    return Response(
        response.content,
        status=response.status_code,
        content_type=response.headers.get("Content-Type", "application/json")
    )


if __name__ == "__main__":
    if not login():
        logging.error("Initialer Login fehlgeschlagen: beende.")
        exit(1)

    app.run(host="0.0.0.0", port=5000)

1 „Gefällt mir“

@thokaro … das Script hat sich ja richtig entwickelt :grinning_face:. Vielen Dank für deinen Einsatz. Wie schon geschrieben werde ich mir dann mal anschauen wie aufwendig es ist daraus eine Home Assistant Integration zu machen, da es ein wenig stört dass ein zweiter Raspberry zum Einsatz kommt.

@djtn … denke thokaro hat deine Frage im Wesentlichen schon beantwortet. Der interessante Teil ist der lokale Energy Manager Flex, der über ein REST API Zugriff auf die Messdaten von Solarwatt ermöglicht (user: installer, password: ). By the way … wir sind in der Zwischenzeit mit unserem Setup soweit, dass wir seit knapp einem Jahr das Onlineportal sowie die App von Solarwatt nicht mehr nutzen. Macht alles Home Assistant bzw. evcc. Letzteres managed den Ladeprozess unseres E-Autos und erledigt das Thermische Energie Management der Wärmepumpe (siehe Energie Management mit evcc).

1 „Gefällt mir“

Hallo zusammen,

ich bin sowohl hier als auch in der ganzen Thematik neu und habe anlässlich unserer frisch installieren Solarwatt-Anlage angefangen, mich mit dem Thema zu beschäftigen, nachdem ich vergeblich versucht habe, über openHAB (auch neu für mich) eine Verbindung zu dem SMF herzustellen.

@asch und @thokaro : Viiielen herzlichen Dank!!! Dieses Skript und die tolle Beschreibung dazu haben es möglich gemacht, dass ich nun tatsächlich Daten vom SMF empfangen kann. Mega!!!

@asch Schau einmal unter Solarwatt Vision steuern via Modbus … auch ich hatte eine lange Nacht :sweat_smile: