Ich bedanke mich für die tolle Zusammenarbeit !!
Hallo zusammen,
2023-02-13: BetaVersion SENEC Modul für die V2 Link
nachdem ich sehr viel in diesem Forum gelernt habe, möchte ich hier meine Lösung zur Diskussion stellen. Evtl. hilft es anderen auch ihre eigene Lösung zu bauen. Ich werde sicher aus Kommentaren lernen, diese Änderungen werde ich in diesem Post dann einarbeiten. Daher werden ggf. Kommentare etwas aus dem Kontext geraten.
Der Ausgangspunkt dieses Postes ist mein Setup zuhause:
Ich habe eine 9,9 kWp PV Anlage mit einem Fronius Wechselrichter.
Das System hat zudem noch eine 5 kWh SENEC Batterie vom Typ:Senec Home V2.1 1ph / Lithium
Der Speicher agiert in meinem System auch als Steuerung der Einspeisebegrenzung von 70%.
Um die Steuerung durchführen zu können, ist ein eigene Zähler über Modbus am Speicher angeschlossen, der direkt vor dem Zähler meines Netzbetreiber eingebaut ist.
Der Zähler des Netzbetreiber ist ein Echelon, der sich ohne Key meines Betreibers leider nicht direkt auslesen lässt.
In meinem Setup sind alle Daten im Speicher verfügbar: PV-Produktion, Speicher und alle EVU Daten. Zusätzlich sind auch Statistikdaten verfügbar.
Nun das Problem: SENEC unterstützt keine Opensource Lösung mit einer dokumentierten API. Alle Lösungen (OpenHab, FHEM, ...) sind durch motivierte Tüftler gebaut worden. Mit dieser Basis wird es zu SENEC kein offizielles Modul in OpenWB geben. (Meine Meinung) Mein ganzes Wissen für dieses Modul ist aus verschiedenen Posts zusammen getragen worden. In diesem Forum haben mir die Posts von Vize besonders geholfen. (Wie schonmal geschrieben: Danke !!)
Daher teile ich hier jetzt mein Setup und freue mich über jeden Verbesserungsvorschag der Experten. Ich bin kein Linux Experte, aber ich weiß mir zu helfen.
Da ich heute meine Hardware in Betrieb nehmen konnte dokumentiere ich hier jetzt den kompletten Ablauf:
Setup im Filesystem:
1. Als erstes habe ich mich in die UI eingeloggt und auf die aktuelle Nightly aktualisiert.
2. Dann habe ich per Terminal eine SSH Verbindung geöffnet
3. Wechsel ins Verzeichnis "cd /var/www/html/openWB/modules"
4. Anlegen des Verzeichnisses "mkdir _senec" - Das Verzeichnis wird beim Update von OpenWB nicht überschrieben.
5. Wechsel ins Verzeichnis "cd _senec"
6. Erstellen einer leeren Python datein "ls > senec.py" - Das geht sicher anders besser, reicht aber für mich.
7. starten des Editors "nano senec.py" und löschen des Inhaltes
8. Copy & paste des Scriptes weiter unten.
9. Speichern
10. Datei ausführbar machen "chmod -v 755 senec.py"
11. Datei ausführen als Test "./senec.py" als Ergebnis darf keine Fehlermeldung kommen, sonst hat es irgendein Problem gegeben.
12. Da dieses Script beim Aufruf jedesmal immer neue Werte in bestimmte Ramdisk Files schreibt, möchte ich, dass die Werte vor jedem Lesevorgang neu geschrieben werden. Das erreiche ich damit, dass ich in crontab alle OpenWB Zeilen durch mein Script erweitere: z.B.
"sleep 10 && /var/www/html/openWB/modules/_senec/senec.py && /var/www/html/openWB/regel.sh >> /var/log/openWB.log 2>&1 "
Dazu rufe ich in der Shell "crontab -e" auf und baue insgesamt 6 mal meine Scriptzeile mit ein.
Code: Alles auswählen
/var/www/html/openWB/modules/_senec/senec.py &&
Setup in OpenWB:
Das Script schreibt die notwendigen Werte in eigene RamDisk files, die dann über die Http Konfiguration für EVU und Speicher in OpenWB eingelesen werden.
In der OpenWB Oberfläche gehe ich auf: Einstellungen > Einstellungen > Modulkonfiguration und wähle im Bereich "Strombezugsmessmodul (EVU-Übergabepunkt)" das Strombezugsmodul "HTTP" aus.
Um meine Konfigurationen nun einzulesen gebe ich nacheinander die Pfade zu meinen RamDisk Dateien ein:
Code: Alles auswählen
http://127.0.0.1/openWB/ramdisk/wattbezug_senec
http://127.0.0.1/openWB/ramdisk/bezugkwh_senec
http://127.0.0.1/openWB/ramdisk/einspeisungkwh_senec
http://127.0.0.1/openWB/ramdisk/bezuga1_senec
http://127.0.0.1/openWB/ramdisk/bezuga2_senec
http://127.0.0.1/openWB/ramdisk/bezuga3_senec
Code: Alles auswählen
http://127.0.0.1/openWB/ramdisk/speicherleistung_senec
http://127.0.0.1/openWB/ramdisk/speichersoc_senec
http://127.0.0.1/openWB/ramdisk/speicherikwh_senec
http://127.0.0.1/openWB/ramdisk/speicherekwh_senec
Code: Alles auswählen
http://127.0.0.1/openWB/ramdisk/pvwatt_senec
http://127.0.0.1/openWB/ramdisk/pvewh_senec
Hier ist mein Script (der Inhalt der Datei senec.py) zur Nutzung bitte die IP Adresse anpassen.
Der grundsätzliche Aufbau des Scriptes ist:
Ich hole mit über einen HTTP Request einen JSON String, den ich dann auslese. Die werte rechne ich das aus dem Hex Code in Dezimal um, formatiere sie und schreibe sie dann in ein RamDisk File. Das mache ich zweimal: einmal für die EVU Daten und einmal für die Speicherdaten.
Da manche Werte nicht in der Modulkonfiguration eingetragen werden können, schreibe ich die EVU Spannung, Leistung sowie die Frequenz direkt.
Code: Alles auswählen
#!/usr/bin/python
import struct
import json
import urllib2
import ssl
import time
import sys
ipaddress = "192.168.XXX.YYY"
debug = False
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
#ipaddress = str(sys.argv[1])
def myDecode(stringValue):
# Parameter:
# stringValue: String Wert, im Format Typ_Wert
#
# Rueckgabe:
# result: Floatzahl
splitValue = stringValue.split('_')
if splitValue[0] == 'fl':
#Hex >> Float
result = struct.unpack('f',struct.pack('I',int('0x'+splitValue[1],0)))[0]
elif splitValue[0] == 'u3':
pass #TBD
elif splitValue[0] == 'u8':
pass #TBD
return result
def writeVal(filePath,stringValue,multiplier,decimalpoints):
#Parameter
#filePath: Pfad und Dateiname in der ein Wert geschrieben wird
#stringValue: Wert der nach dem knonvertieren in die Datei geschrieben wird
#multiplier: Wert mit dem die Zahl vor der Rundung multipliziert wird
#decimalpoints: Anzahl Kommastellen
#
#Rueckgabe: nichts
val= myDecode(stringValue)
# Format anpassen
if multiplier != 0:
val = val * multiplier
#auf 2 Ziffern runden
if decimalpoints == 0:
val = int(val)
elif decimalpoints != 0:
val = round(val,decimalpoints)
if debug:
print(filePath + ' ' + str(val))
else:
print(filePath + ' ' + str(val))
f = open(filePath, 'w')
f.write(str(val))
f.close()
#EVU Daten
try:
reqdata='{"PM1OBJ1":{"FREQ":"","U_AC":"","I_AC":"","P_AC":"","P_TOTAL":""}}'
response = urllib2.urlopen('https://'+ ipaddress +'/lala.cgi' ,data=reqdata, context=ctx, timeout=2)
jsondata = json.load(response)
except:
sys.exit(1)
#keine Werte gefunden
# echo $evupf1 > /var/www/html/openWB/ramdisk/evupf1
# echo $evupf2 > /var/www/html/openWB/ramdisk/evupf2
# echo $evupf3 > /var/www/html/openWB/ramdisk/evupf3
#SENEC: Gesamtleistung (W) Werte -3000 >> 3000
if not (jsondata['PM1OBJ1'] ['P_TOTAL'] is None):
writeVal('/var/www/html/openWB/ramdisk/wattbezug_senec', jsondata['PM1OBJ1'] ['P_TOTAL'],0,0)
#SENEC: Frequenz(Hz) Werte 49.00 >> 50.00
if not (jsondata['PM1OBJ1'] ['FREQ'] is None):
writeVal('/var/www/html/openWB/ramdisk/evuhz',jsondata['PM1OBJ1'] ['FREQ'],0,0)
#SENEC: Spannung (V) Werte 219.12 >> 223.43
if not (jsondata['PM1OBJ1'] ['U_AC'] [0] is None):
writeVal('/var/www/html/openWB/ramdisk/evuv1', jsondata['PM1OBJ1'] ['U_AC'] [0],0,0)
if not (jsondata['PM1OBJ1'] ['U_AC'] [1] is None):
writeVal('/var/www/html/openWB/ramdisk/evuv2', jsondata['PM1OBJ1'] ['U_AC'] [1],0,0)
if not (jsondata['PM1OBJ1'] ['U_AC'] [2] is None):
writeVal('/var/www/html/openWB/ramdisk/evuv3', jsondata['PM1OBJ1'] ['U_AC'] [2],0,0)
#SENEC: Leistung (W) Werte -2345 >> 3000
if not (jsondata['PM1OBJ1'] ['P_AC'] [0] is None):
writeVal('/var/www/html/openWB/ramdisk/bezugw1', jsondata['PM1OBJ1'] ['P_AC'] [0],0,0)
if not (jsondata['PM1OBJ1'] ['P_AC'] [1] is None):
writeVal('/var/www/html/openWB/ramdisk/bezugw2', jsondata['PM1OBJ1'] ['P_AC'] [1],0,0)
if not (jsondata['PM1OBJ1'] ['P_AC'] [2] is None):
writeVal('/var/www/html/openWB/ramdisk/bezugw3', jsondata['PM1OBJ1'] ['P_AC'] [2],0,0)
#SENEC: Strom (A) Werte 0 >> 1
if not (jsondata['PM1OBJ1'] ['I_AC'] [0] is None):
writeVal('/var/www/html/openWB/ramdisk/bezuga1_senec', jsondata['PM1OBJ1'] ['I_AC'] [0],0,0)
if not (jsondata['PM1OBJ1'] ['I_AC'] [1] is None):
writeVal('/var/www/html/openWB/ramdisk/bezuga2_senec', jsondata['PM1OBJ1'] ['I_AC'] [1],0,0)
if not (jsondata['PM1OBJ1'] ['I_AC'] [2] is None):
writeVal('/var/www/html/openWB/ramdisk/bezuga3_senec', jsondata['PM1OBJ1'] ['I_AC'] [2],0,0)
#Batteriedaten:
try:
reqdata='{"ENERGY":{"GUI_BAT_DATA_FUEL_CHARGE":"","GUI_BAT_DATA_POWER":"","GUI_BAT_DATA_VOLTAGE":"","GUI_INVERTER_POWER":""}}'
response = urllib2.urlopen('https://'+ ipaddress +'/lala.cgi' ,data=reqdata, context=ctx,timeout=2)
jsondata = json.load(response)
except:
sys.exit(1)
#SENEC: Batterieleistung (W) Werte -345 (Entladen) >> 1200 (laden)
if not (jsondata['ENERGY'] ['GUI_BAT_DATA_POWER'] is None):
writeVal('/var/www/html/openWB/ramdisk/speicherleistung_senec', jsondata['ENERGY'] ['GUI_BAT_DATA_POWER'],0,0)
#SENEC: Fuellmenge in Prozent Werte 10 >> 55 >> 100
if not (jsondata['ENERGY'] ['GUI_BAT_DATA_FUEL_CHARGE'] is None):
writeVal('/var/www/html/openWB/ramdisk/speichersoc_senec', jsondata['ENERGY'] ['GUI_BAT_DATA_FUEL_CHARGE'],0,0)
#SENEC: Leistung Wechselrichter in (W) Werte
if not (jsondata['ENERGY'] ['GUI_INVERTER_POWER'] is None):
writeVal('/var/www/html/openWB/ramdisk/pvwatt_senec', jsondata['ENERGY'] ['GUI_INVERTER_POWER'],0,0)
Ich habe um die HTTP Abfragen jetzt ein try ... except gesetzt, damit ein nicht antwortendes SENEC System nicht die OpenWB einfrieren lässt. (Danke an Newbe2020 für die Frage.)
Sollte nun innerhalb von 2 Sekunden keine Antwort kommen, wird die komplette Funktion einfach beendet, ohne den weiteren Code abzuarbeiten
Beispiel hier:
Code: Alles auswählen
try:
reqdata='{"PM1OBJ1":{"FREQ":"","U_AC":"","I_AC":"","P_AC":"","P_TOTAL":""}}'
response = urllib2.urlopen('https://'+ ipaddress +'/lala.cgi' ,data=reqdata, context=ctx, timeout=2)
jsondata = json.load(response)
except:
sys.exit(1)
In einer der letzten Nightlys scheint es nochmal eine Änderung der HTTP Module gegeben zu haben.
Das 'localhost' muss jetzt durch '127.0.0.1' ersetzt werden. Daher müssen jetzt alle Module mit http://127.0.0.1/.... starten.
Update 02.10.2021:
Durch eine Änderungen im Weg wie die HTTP Module die Werte aus meinem Modul abrufen, ist es nun notwendig, dass das korrekte Protokoll in der OpenWB Konfiguration angegeben werden muss. Daher muss vor alle localhost/... ein 'http://' gesetzt werden. Ich habe die Anleitung oben entsprechend geändert. (Genaueres zum Grund: https://openwb.de/forum/viewtopic.php?f=9&t=4092)
Update 16.05.2021:
Auf zweifachen Wunsch habe ich nun auch die Daten des Wechselrichters aus der SENEC Batterie geladen.
Dafür bitte den Code der senec.py mit dem oben austauschen und die Konfiguration des PV Moduls auf HTTP einstellen.
fall jemand - wie ich selbst - von einem direkten PV Modul zu den ausgelesenen Daten aus der SENEC wechselt: der zweite Wert 'pvewh' wir auf jeden Fall unterschiedlich sein, da sie SENEC die Gesamt-PV Erzeugung selbst errechnet und nicht aus dem Wechselrichter lädt.
Update 26.07.2020:
Nachdem SENEC eine neue Firmware in die Speicher geschrieben hat war der Aufruf der Statistischen Daten nicht mehr möglich und das Script führte zu einem kompletten Hänger in der OpenWB. Das ist mit diesem Update behoben. Das Script lief jetzt eine Woche ohne Probleme bei mir.
Vielen Dank an vize und Topper für die Fehlersuche und Fehlerlösung.
Änderungen in diesem Update:
- Vor der Verarbeitung jedes Einzelwertes wird geprüft ob der Wert vorhanden ist, das sollte Hänger von OpenWB verhindern, wenn SENEC wieder etwas ändert.
- Die Werte für den Gesamtnetz- import und export wurden von ['STATISTIC'] ['STAT_GRID_IMPORT'] und ['STATISTIC'] ['STAT_GRID_EXPORT'] auf ['STATISTIC'] ['LIVE_GRID_IMPORT'] und ['STATISTIC'] ['LIVE_GRID_EXPORT'] geändert.