« Photovoltaik / PV  |

Solaredge Optimierer via Nodered auslesen

Teilen: facebook    whatsapp    email
Zusammenfassung anzeigen (Beta)
  •  Wolferl
13.12. - 26.12.2022
18 Antworten | 5 Autoren 18
18
Hallo liebe Forengemeinde

Ich bin seit einiger Zeit am Recherchieren, wie und ob man SE Optimierer auslesen und die Daten dann lokal speichern und auswerten kann. 
Es gibt einige Lösungen die ausschliesslich lokal funktionieren und dann die etwas einfacheren, die die Daten von der Cloud abgreifen und sie dann lokal speichern. 

Mein Ziel wäre die zweite Variante: Daten über Nodered von der Cloud abgreifen und in InfluxDB zu speichern um auch die Modul lokal überwachen zu können. 

Hier mal was ich gefunden habe: 
 • Lösung für Homeassistant: https://github.com/ProudElm/solaredgeoptimizers/releases
 • Bash script (funktioniert wohl nicht mehr):https://github.com/dragoshenron/solaredge-webscrape
 • Python snippet: https://gist.github.com/dragoshenron/0920411a2f3e53c214be0a26f51c53e2

Für mich als Laien in der Programmierung alles leider nicht verwendbar.
Hat sich jemand von euch schon mal mit dem Thema beschäftigt und darüber nachgedacht, das ganze in eine Nodered Node zu integrieren?
Bin gespannt auf eure Antworten.
Wolferl

  •  wiwi
  •   Bronze-Award
13.12.2022  (#1)
Es gibt genau eine lokale Lösung die für pre-SetApp Wechselrichter funktioniert wenn man den Traffic mitschneidet und den Key per RS485 aus dem WR WR [Wechselrichter] ausliest:

https://github.com/jbuehl/solaredge

Alles andere geht nur noch via Webscraping (deine Links).

Es spricht aber auch nichts dagegen die Daten via python snippet abzuholen (per cron oder telegraf), in eine InfluxDB zu schreiben und von dort holt Nodered die Daten wieder ab.

Laienbedienbar ist das nicht, stimmt, aber ein RPI findet sich gleich zum basteln.

1
  •  Pretorianer
  •   Bronze-Award
13.12.2022  (#2)
Für wirklich wichtige Daten kann man den Solaredge Wechselrichter via Modbus TCP auslesen. Solaredge hat "Sunspec" implementiert https://www.solaredge.com/sites/default/files/sunspec-implementation-technical-note.pdf - hier der link zu der Sunspec alliance: https://sunspec.org/
Leider unterstütz Sunspec keine Optimiererdaten, diese müssen wie @wiwi schon sagte über das Portal abgefragt werden (sollten aber auch nicht sooo kritisch sein)

Ich verfolge bei meinen Auswertungen auch den Ansatz alles offline abzufragen, aber solche zusätzlichen aber nicht essenziellen infos finde ich ok wenn man diese aus dem Internet zieht.


1
  •  Wolferl
15.12.2022  (#3)
Hallo zusammen

Danke für eure Antworten. Die lokale Lösung habe ich mir angeschaut und da ist mir zuviel Aufwand dabei, auch weil Keys ausgelesen werden müssen und weil keiner weiss, ob das nach dem nächsten Update noch läuft.

Mir würde die Lösung über Webscraping reichen. Leider reichen meine Programmierkenntnisse nicht aus, um das Python Snippet zum Laufen zu bringen.

Die Daten sollen final sowieso in eine influxDB, NoderRed habe ich nur vorgeschlagen, weil ich nicht wusste, ob sich schon jemand eine Node gebastelt hat...

Hat von euch schon mal jemand eine Lösung zum Laufen gebracht?


 
SG
Wolferl


1


  •  Fani
15.12.2022  (#4)
nodes für solaredge gibt es ja, hast hier mal gesucht?

https://flows.nodered.org/node/node-red-contrib-solaredge-power

https://flows.nodered.org/search?term=solaredge

1
  •  Wolferl
15.12.2022  (#5)
Hallo Fani

Danke für die Links.
Ja, die habe ich schon gefunden. Leider geben diese Nodes keine Optimizer Daten aus.

Wie gesagt, NodeRed wäre für mich erst mal nicht so wichtig. Ich hätte die Daten einfach gerne in InfluxDB, NodeRed kann ein Vehikel dazu sein. Wenn es eine Lösung gibt (die ich mit meinen beschränkten Programmierkenntnissen implementieren kann) die direkt in Influx schreibt, passt das auch...

SG
Wolferl


1
  •  gschnasl
15.12.2022  (#6)
Mit Google Suche "solaredge api optimizer data" findet man einige Seiten.
z.B. https://github.com/ProudElm/solaredgeoptimizers
Das Problem ist, das die Optimizer-Daten (außer Inventory) nicht mit SolarEdge API ausgelesen werden, sondern von der WebSite geholt werden.

Ich verwende derzeit ein Image von solaranzeige.de, wo aber die Daten nur mit Modbus TCP ausgelesen werden.
Wenn ich mehr Zeit habe, möchte mir aber Optimizer-Daten zusätzlich holen. Aber ich habe leider noch keine perfekte Lösung gesehen.

1
  •  wiwi
  •   Bronze-Award
15.12.2022  (#7)
Das Problem ist, per Modbus TCP werden die Optimierer Daten von SE nicht zur Verfügung gestellt, obwohl es möglich wäre.

Aktuelle SetApp-WR kommunizieren SSL-verschlüsselt mit SE, da bleibt de-facto nur Webscraping.

Bei den alten WR WR [Wechselrichter] mit Display (pre-SetApp) funktioniert die Entschlüsselung über den per Modbus RTU extrahierten Key und das mitschneiden (oder umleiten) des Datenverkehrs.

1
  •  Wolferl
16.12.2022  (#8)
Für mich wäre Webscraping mehr als ok, perfekte Lösung gibt es wahrscheinlich keine da SE das nicht unterstützt...
wenn sich jemand mit besseren Programmierkenntnissen als ich mit dem Snippet von https://gist.github.com/mihailescu2m/27d5dd22656a4d64a0c755da99b7b162 beschäftigt wird es vielleicht was. Schön wäre, wenn man das in eine function node in Nodered integrieren könnte...
Ich habs probiert, auch in Python, bin aber nicht weiter gekommen emoji


1
  •  wiwi
  •   Bronze-Award
16.12.2022  (#9)
Mit https://gist.github.com/cooldil/0b2c5ee22befbbfcdefd06c9cf2b7a98 kommen da schon Daten (hab den Fork genommen weil der vor drei Monaten zumindest aktiv war).

Die Variablen im Python Code sind halt mit deinen Login Daten und der Site ID zu ergänzen ansonsten kommt der cookie Error.

Eine laufende InfluxDB wäre auch von Vorteil, dann füllt man auch die IP und DB etc. aus.
Ansonsten kann man auch die letzten zwei Zeilen ("conn ...") auskommentieren und vorher ein print(df.to_markdown()) einfügen dann landen die Daten erstmal im Terminal.

1
  •  Wolferl
16.12.2022  (#10)
Hab mir jetzt mal Visual Studio mit Python installiert und der Fork, den du vorgeschlagen hast, bringt wirklich Daten. Hatte das vorher schon mal mit dem Original Snippet gemacht, da war ein Return() statt einem Exit() in Zeile 31 und dauernd eine Fehlermeldung.
Muss mich da jetzt ein bisschen einlesen, wäre aber froh, wenn ich dir danach noch ein oder zwei Fragen stellen dürfte. Du scheinst in Python ja gut daheim zu sein. 
Im Moment bekomme ich noch eine Fehlermeldung 401 wenn ich in den Bucket schreiben möchte, Benutzername und Passwort ist eingetragen der Zeile mit dem DataFrameClient.
Hast du da auch noch eine Idee?
 
Danke schon mal für deine Hilfe bis jetzt...
Wolferl


1
  •  wiwi
  •   Bronze-Award
17.12.2022  (#11)
Der Fehler 401 deutet auf Probleme mit der Authentifizierung bei der InfluxDB hin. Schwer zu sagen aus der Ferne wo es da zwickt. 🙂

1
  •  Wolferl
18.12.2022  (#12)
Danke, das hab ich mir gedacht. Hab dann mal nachgelesen. Ich glaub, mein Problem ist, dass die Funktion DataFrameClient zwar User und PW akzeptiert, aber keinen Token...
habs noch nicht rausgefunden...

1
  •  Wolferl
19.12.2022  (#13)
Hallo wiwi

Ich hab es jetzt hinbekommen, dass die Daten in die Datenbank geschrieben werden. mit DataFrameClient() gings nicht, ich hab das jetzt mit InfluxDBClient gemacht.

Bleiben noch zwei Probleme:
 • Ich habe 30 Optimierer, die Abfrage bringt aber nur 22 Stk
 • Im Ergebnis der Abfrage wird nicht die Seriennummer der Optimierer ausgegeben, sondern scheinbar eine ID. Hast du eine Ahnung, ob es da irgendwo eine Lookup Table gibt, die Seriennummer des Optimierers und ID korreliert?

Das Script kommt im nächsten Post, habs nicht geschafft das als Datei hochzuladen...

Danke
Wolferl


1
  •  Wolferl
19.12.2022  (#14)

import requests, pickle
from datetime import datetime
import json, pytz
import pandas as pd
from influxdb import DataFrameClient
from influxdb_client import InfluxDBClient, Point, Dialect
from influxdb_client.client.write_api import SYNCHRONOUS

login_url = "https://monitoring.solaredge.com/solaredge-apigw/api/login"
panels_url = "https://monitoring.solaredge.com/solaredge-web/p/playbackData"

SOLAREDGE_USER = "x@gmail.com" # web username
SOLAREDGE_PASS = "xxx" # web password
SOLAREDGE_SITE_ID = "123" # site id
DAILY_DATA = "4"
WEEKLY_DATA = "5"
MONTHLY_DATA = "6"
YEARLY_DATA = "7"
ALL_DATA = "0"
INFLUXDB_IP = "192.168.1.10:8086"
INFLUXDB_TOKEN = "lkjhlkjhlkjhlkjhlj"
INFLUXDB_ORG = 'hjkjhjk'
INFLUXDB_BUCKET = 'test'

session = requests.session()
try:  # Make sure the cookie file exists
    with open('solaredge.cookies', 'rb') as f:
        f.close()
except IOError:  # Create the cookie file 
    session.post(login_url, headers = {"Content-Type": "application/x-www-form-urlencoded"}, data={"j_username": SOLAREDGE_USER, "j_password": SOLAREDGE_PASS})
    panels = session.post(panels_url, headers = {"Content-Type": "application/x-www-form-urlencoded", "X-CSRF-TOKEN": session.cookies["CSRF-TOKEN"]}, data={"fieldId": SOLAREDGE_SITE_ID, "timeUnit": DAILY_DATA})
    with open('solaredge.cookies', 'wb') as f:
        pickle.dump(session.cookies, f)
        f.close()
with open('solaredge.cookies', 'rb') as f:
    session.cookies.update(pickle.load(f))
    panels = session.post(panels_url, headers = {"Content-Type": "application/x-www-form-urlencoded", "X-CSRF-TOKEN": session.cookies["CSRF-TOKEN"]}, data={"fieldId": SOLAREDGE_SITE_ID, "timeUnit":DAILY_DATA})
    if panels.status_code != 200:
        session.post(login_url, headers = {"Content-Type": "application/x-www-form-urlencoded"}, data={"j_username": SOLAREDGE_USER, "j_password": SOLAREDGE_PASS})
        panels = session.post(panels_url, headers = {"Content-Type": "application/x-www-form-urlencoded", "X-CSRF-TOKEN": session.cookies["CSRF-TOKEN"]}, data={"fieldId": SOLAREDGE_SITE_ID, "timeUnit": DAILY_DATA})
        if panels.status_code != 200:
            exit()
        with open('solaredge.cookies', 'wb') as f:
            pickle.dump(s.cookies, f)
    response = panels.content.decode("utf-8").replace('\'', '"').replace('Array', '').replace('key', '"key"').replace('value', '"value"')
    response = response.replace('timeUnit', '"timeUnit"').replace('fieldData', '"fieldData"').replace('reportersData', '"reportersData"')
    response = json.loads(response)
    
    data = {}
    for date_str in response["reportersData"].keys():
        date = datetime.strptime(date_str, '%a %b %d %H:%M:%S GMT %Y')
        date = pytz.timezone('Europe/Berlin').localize(date).astimezone(pytz.utc)
        for sid in response["reportersData"][date_str].keys():
            for entry in response["reportersData"][date_str][sid]:
                if entry["key"] not in data.keys():
                     data[entry["key"]] = {}
                data[entry["key"]][date] = float(entry["value"].replace(",", ""))

    df = pd.DataFrame(data)
    
    
    client=InfluxDBClient(url=INFLUXDB_IP, token = INFLUXDB_TOKEN, org=INFLUXDB_ORG)
    write_api = client.write_api(write_options=SYNCHRONOUS)

    #print(df.to_string())

    write_api.write(bucket=INFLUXDB_BUCKET, org=INFLUXDB_ORG, record=df, data_frame_measurement_name = "optimizer")
    

1
  •  wiwi
  •   Bronze-Award
19.12.2022  (#15)

zitat..
Wolferl schrieb:

 • Im Ergebnis der Abfrage wird nicht die Seriennummer der Optimierer ausgegeben, sondern scheinbar eine ID. Hast du eine Ahnung, ob es da irgendwo eine Lookup Table gibt, die Seriennummer des Optimierers und ID korreliert?

Leider nein, das musst du vermutlich anhand der Daten im Monitoringportal zuordnen.


1
  •  Wolferl
20.12.2022  (#16)
Hallo wiwi

Ich habe inzwischen mit dem Author des scripts Kontakt aufgenommen. Das mit den Daten im Monitoring Portal ist leider richtig. So wie es aussieht, muss man das aber nur einmal machen. 

Ich bin relativ nah dran, sobald ich das habe werde ich das hier posten, vielleicht kann es ja ein anderer SE Besitzer brauchen.


SG
Wolferl


1
  •  Wolferl
26.12.2022  (#17)
Guten Morgen

Nochmal ein update zu meinem Setup. Es funktioniert jetzt alles wie gewünscht. Das überarbeitete Python Script kommt dann im nächsten Post:
 • Das Python script läuft bei mir auf einer Synology DS216+II und wird per taskplaner alle 15min gestartet
 • Damit werden die Daten in einen temporären InfluxDB bucket geschrieben
 • Aus diesem Bucket holt ein InfluxDB Task alle 15min die letzten Daten und schreibt sie in den endgültigen bucket. Bei diesem Task werden dann noch drei Tags dazugefügt
- Seriennummer (aus dem Python script kommt nur eine ID)
- logische Nummer des Panels aus dem Portal
- Typ der Daten: Inverter, String oder Panel

Damit habe ich jetzt die Daten in einem InfluxDB bucket und kann sie so auswerten, wie ich möchte. Ausserdem sind sie da dauerhaft gespeichert. 

Frohe Weihnachten 
Wolferl


1
  •  Wolferl
26.12.2022  (#18)
# script zum Importieren von Optimiererdaten aus dem SE Portal
# wird alle 15min mittels Taskplaner auf der DS216 ausgeführt, SE aktualisiert die Daten im selben Rythmus
# Original Script von https://gist.github.com/cooldil/0b2c5ee22befbbfcdefd06c9cf2b7a98
# Nach dem Auslesen werden die Daten in eine InfluxDB geschrieben, Dort werden die Daten mittels Task weiterverarbeitet

import requests, pickle
from datetime import datetime
import json, pytz
import pandas as pd
from influxdb_client import InfluxDBClient, Point, Dialect
from influxdb_client.client.write_api import SYNCHRONOUS

login_url = "https://monitoring.solaredge.com/solaredge-apigw/api/login"
panels_url = "https://monitoring.solaredge.com/solaredge-web/p/playbackData"

SOLAREDGE_USER = "XXX" # web username
SOLAREDGE_PASS = "XXX" # web password
SOLAREDGE_SITE_ID = "XXX" # site id
DATA_RANGE = "4"  # 4 is daily, 5 is weekly
ALL_DATA = "0"
INFLUXDB_IP = "192.168.1.10:8086"
INFLUXDB_TOKEN = "XX"
INFLUXDB_ORG = 'XX'
INFLUXDB_BUCKET = 'SE_Temp'
INFLUXDB_MEASUREMENT = "SE-web"

session = requests.session()
try:  # Make sure the cookie file exists
    with open('solaredge.cookies', 'rb') as f:
        f.close()
except IOError:  # Create the cookie file 
    session.post(login_url, headers = {"Content-Type": "application/x-www-form-urlencoded"}, data={"j_username": SOLAREDGE_USER, "j_password": SOLAREDGE_PASS})
    panels = session.post(panels_url, headers = {"Content-Type": "application/x-www-form-urlencoded", "X-CSRF-TOKEN": session.cookies["CSRF-TOKEN"]}, data={"fieldId": SOLAREDGE_SITE_ID, "timeUnit": DATA_RANGE})
    with open('solaredge.cookies', 'wb') as f:
        pickle.dump(session.cookies, f)
        f.close()
with open('solaredge.cookies', 'rb') as f:
    session.cookies.update(pickle.load(f))
    panels = session.post(panels_url, headers = {"Content-Type": "application/x-www-form-urlencoded", "X-CSRF-TOKEN": session.cookies["CSRF-TOKEN"]}, data={"fieldId": SOLAREDGE_SITE_ID, "timeUnit":DATA_RANGE})
    if panels.status_code != 200:
        session.post(login_url, headers = {"Content-Type": "application/x-www-form-urlencoded"}, data={"j_username": SOLAREDGE_USER, "j_password": SOLAREDGE_PASS})
        panels = session.post(panels_url, headers = {"Content-Type": "application/x-www-form-urlencoded", "X-CSRF-TOKEN": session.cookies["CSRF-TOKEN"]}, data={"fieldId": SOLAREDGE_SITE_ID, "timeUnit": DATA_RANGE})
        if panels.status_code != 200:
            exit()
        with open('solaredge.cookies', 'wb') as f:
            pickle.dump(session.cookies, f)
    response = panels.content.decode("utf-8").replace('\'', '"').replace('Array', '').replace('key', '"key"').replace('value', '"value"')
    response = response.replace('timeUnit', '"timeUnit"').replace('fieldData', '"fieldData"').replace('reportersData', '"reportersData"')
    response = json.loads(response)
    
    data = {}
    for date_str in response["reportersData"].keys():
        date = datetime.strptime(date_str, '%a %b %d %H:%M:%S GMT %Y')
        date = pytz.timezone('Europe/Berlin').localize(date).astimezone(pytz.utc)
        for sid in response["reportersData"][date_str].keys():
            for entry in response["reportersData"][date_str][sid]:
                if entry["key"] not in data.keys():
                     data[entry["key"]] = {}
                data[entry["key"]][date] = float(entry["value"].replace(",", ""))

    df = pd.DataFrame(data)
    
    
    client=InfluxDBClient(url=INFLUXDB_IP, token = INFLUXDB_TOKEN, org=INFLUXDB_ORG)
    write_api = client.write_api(write_options=SYNCHRONOUS)

    #print(df.to_string())

    write_api.write(bucket=INFLUXDB_BUCKET, org=INFLUXDB_ORG, record=df, data_frame_measurement_name =INFLUXDB_MEASUREMENT )

1


Beitrag schreiben oder Werbung ausblenden?
Einloggen

 Kostenlos registrieren [Mehr Infos]

Nächstes Thema: (Verbund-)Strom Fixpreis mit Jahresende und dann?