Selbstbau-PV / Selbstbau-PV-Hoymiles-nulleinspeisung-mit-OpenDTU-und-Shelly3EM

Ein Python-Skript, das den aktuellen Hausverbrauch aus einem Shelly 3EM ausliest, die Nulleinspeisung berechnet und die Ausgangsleistung eines Hoymiles-Wechselrichters mit Hilfe der OpenDTU entsprechend anpasst. Somit wird kein unnötiger Strom ins Betreibernetz abgegeben.
61 stars 24 forks source link

Fehlermeldung: NameError: name 'power' is not defined #27

Closed MaTiX1980 closed 4 months ago

MaTiX1980 commented 4 months ago

Hallo zusammen, ich hab jetzt schon einiges gelesen rund um diesen Code aber eine Lösung hat nicht geklappt. hat einer eine Idee wie ich das zum laufen bekomme (der Raspberry und die DTU sind schon neu aufgesetzt)? Durch ChatGPT hab ich das auch schon gejagt, da kam aber auch nicht wirklich was funktionsfähiges bei rum. Danke schonmal, Stefan

die Fehelermeldung sieht wie folgt aus: In [1] runfile('/home/stefan/Nulleinspeisung/nulleinspeisung.py', wdir='/home/stefan/Nulleinspeisung') Fehler beim Abrufen der Daten von openDTU Traceback (most recent call last):

File /usr/lib/python3/dist-packages/spyder_kernels/py3compat.py:356 in compat_exec exec(code, globals, locals)

File ~/Nulleinspeisung/nulleinspeisung.py:41 print(f'\nBezug: {round(grid_sum, 1)} W, Produktion: {round(power, 1)} W, Verbrauch: {round(grid_sum + power, 1)} W')

NameError: name 'power' is not defined


und der Code ist dieser:

`#!/usr/bin/env python3 import requests, time, sys from requests.auth import HTTPBasicAuth

Diese Daten müssen angepasst werden:

serial = "114190639469" # Seriennummer des Hoymiles Wechselrichters maximum_wr = 100 # Maximale Ausgabe des Wechselrichters minimum_wr = 10 # Minimale Ausgabe des Wechselrichters

dtu_ip = '192.168.101.83' # IP Adresse von OpenDTU dtu_nutzer = 'admin' # OpenDTU Nutzername dtu_passwort = 'openDTU42' # OpenDTU Passwort

shelly_ip = '192.168.101.85' # IP Adresse von Shelly 3EM

while True: try:

Nimmt Daten von der openDTU Rest-API und übersetzt sie in ein json-Format

    r = requests.get(url = f'http://{dtu_ip}/api/livedata/status/inverters' ).json()

    # Selektiert spezifische Daten aus der json response
    reachable   = r['inverters'][0]['reachable'] # Ist DTU erreichbar?
    producing   = int(r['inverters'][0]['producing']) # Produziert der Wechselrichter etwas?
    altes_limit = int(r['inverters'][0]['limit_absolute']) # Altes Limit
    power_dc    = r['inverters'][0]['AC']['0']['Power DC']['v']  # Lieferung DC vom Panel
    power       = r['inverters'][0]['AC']['0']['Power']['v'] # Abgabe BKW AC in Watt
except:
    print('Fehler beim Abrufen der Daten von openDTU')
try:
    # Nimmt Daten von der Shelly 3EM Rest-API und übersetzt sie in ein json-Format
    phase_a     = requests.get(f'http://{shelly_ip}/emeter/0', headers={'Content-Type': 'application/json'}).json()['power']
    phase_b     = requests.get(f'http://{shelly_ip}/emeter/1', headers={'Content-Type': 'application/json'}).json()['power']
    phase_c     = requests.get(f'http://{shelly_ip}/emeter/2', headers={'Content-Type': 'application/json'}).json()['power']
    grid_sum    = phase_a + phase_b + phase_c # Aktueller Bezug - rechnet alle Phasen zusammen
except:
    print('Fehler beim Abrufen der Daten von Shelly 3EM')

# Werte setzen
print(f'\nBezug: {round(grid_sum, 1)} W, Produktion: {round(power, 1)} W, Verbrauch: {round(grid_sum + power, 1)} W')
if reachable:
    setpoint = grid_sum + altes_limit - 5 # Neues Limit in Watt

    # Fange oberes Limit ab
    if setpoint > maximum_wr:
        setpoint = maximum_wr
        print(f'Setpoint auf Maximum: {maximum_wr} W')
    # Fange unteres Limit ab
    elif setpoint < minimum_wr:
        setpoint = minimum_wr
        print(f'Setpoint auf Minimum: {minimum_wr} W')
    else:
        print(f'Setpoint berechnet: {round(grid_sum, 1)} W + {round(altes_limit, 1)} W - 5 W = {round(setpoint, 1)} W')

    if setpoint != altes_limit:
        print(f'Setze Inverterlimit von {round(altes_limit, 1)} W auf {round(setpoint, 1)} W... ', end='')
        # Neues Limit setzen
        try:
            r = requests.post(
                url = f'http://{dtu_ip}/api/limit/config',
                data = f'data={{"serial":"{serial}", "limit_type":0, "limit_value":{setpoint}}}',
                auth = HTTPBasicAuth(dtu_nutzer, dtu_passwort),
                headers = {'Content-Type': 'application/x-www-form-urlencoded'}
            )
            print(f'Konfiguration gesendet ({r.json()["type"]})')
        except:
            print('Fehler beim Senden der Konfiguration')

sys.stdout.flush() # write out cached messages to stdout
time.sleep(5) # wait`
Nutmeg0082 commented 4 months ago

Moin! Hast du Dir mal den Code aus der #26 geholt? Alle notwendigen Variablen penibel angepasst? Bei mir hat es dann wirklich top funktioniert. Mit ChatGPT 3.5 (free) muss man wirklich höllisch aufpassen. Wenn Du das Script im Sinn nicht verstehst, wird es dir Fehler einbauen.

Nutmeg0082 commented 4 months ago

Im groben hat das Script die JSON-Struktur von der DTU falsch ausgelesen. Der Fehler tauchte dann vor der Zeile 41 auf, wo eben genau die Variable "Power" definiert wird. Deine JSON-Struktur der DTU kannst du dir easy anzeigen lassen:

http://{dtu_ip}/api/livedata/status/inverters').json() die dtu_ip musst du natürlich ergänzen.

Dann werden dir quasi die Livedaten der DTU verwertbarer "" in Text umgewandelt, welches das Script dann weiter verarbeitet. Du wirst sehen, dass diese Text-datei nach einer ganz bestimmten Struktur vorgeht, welche natürlich auch korrekt im Script sein muss. Dort lag bei mir der Fehler.

Ich weiß nicht, ob sich mit dem Update der DTU auch mal diese JSON-Struktur ändern kann. Das könnte dann dafür sprechen, dass das Script nach Update nicht funktioniert....

Ich weiß auch wir frustrierend das ist, aber zusammen bekommen wir bestimmt eine Lösung hin!

Nutmeg0082 commented 4 months ago

das ist dein Abschnitt in dem geteilten Script:

Selektiert spezifische Daten aus der json response

reachable   = r['inverters'][0]['reachable'] # Ist DTU erreichbar?
producing   = int(r['inverters'][0]['producing']) # Produziert der Wechselrichter etwas?
altes_limit = int(r['inverters'][0]['limit_absolute']) # Altes Limit
power_dc    = r['inverters'][0]['AC']['0']['Power DC']['v']  # Lieferung DC vom Panel
power       = r['inverters'][0]['AC']['0']['Power']['v'] # Abgabe BKW AC in Watt

Das ist meiner aus #26:

Selektiert spezifische Daten aus der json response

    inverters_data = r.get('inverters', [])
    if inverters_data:
        inverters_data = inverters_data[0]
        reachable = inverters_data.get('reachable', False)  # Ist DTU erreichbar?
        producing = int(inverters_data.get('producing', 0))  # Produziert der Wechselrichter etwas?
        altes_limit = int(inverters_data.get('limit_absolute', 0))  # Altes Limit
        power = r['total']['Power']['v'] if 'total' in r and 'Power' in r['total'] else 0  # Abgabe BKW AC in Watt

Man sieht dort deutliche Unterschiede! Denk aber dran, dass du diesen Teil des Scriptes nicht einfach kopieren und ersetzen kannst, da mein Script noch etwas optimiert wurde!

MaTiX1980 commented 4 months ago

Hallo und guten Abend sowie vielen Dank für den Support.

Ich hab das Script soweit zum laufen bekommen, lediglich an der Berechnung etwas gepfeilt. Es gibt jetzt keine Fehlermeldung mehr, jedoch wenn die Daten gesendet werden, werden diese von dem Wechselrichter nicht angenommen ;-( . (Auch schon das Passwort geändert...)

Als Info zu meinem Setup: ich habe 2 Solarmodule parallel geschaltet die über einen Wechselrichter einen Akku laden, davon geht es, zur Zeit noch, über einen digiatelen DC/DC-Wandler den ich in der Leistung fest beschreiben kann (Spannung und Stromstärke) in den Wechselrichter. Aber auch nur an einen Eingang. Das sollte jedoch nicht die Informationen beeinträchtigen die an den Wechselrichter gesendet werden. Denn die Leistung kann ich manuell über die openDTU ja anpassen. Daher schließe ich einen Fehler auf Seite der openDTU nebst Antenne NRF24L01+ aus. Plan ist es, den digitalen DC/DC Wandler zu ersetzen und die Steuern der Last eben über die openDTU zu realisieren. Achso: Mein Wechselrichter ist nach Anzeige ein HM-800-2T, also kein WLAN-Modell.

grafik

Jetzt zu dem Script:

`!/usr/bin/env python3 import requests import time import sys from requests.auth import HTTPBasicAuth

`# Diese Daten müssen angepasst werden: serial = "11419069469" # Seriennummer des Hoymiles Wechselrichters maximum_wr = 100 # Maximale Ausgabe des Wechselrichters minimum_wr = 10# Minimale Ausgabe des Wechselrichters

dtu_ip = '192.168.101.83' # IP Adresse von OpenDTU dtu_nutzer = 'admin' # OpenDTU Nutzername dtu_passwort = 'Sk181280!' # OpenDTU Passwort

shelly_ip = '192.168.101.85' # IP Adresse von Shelly 3EM

while True: try:

Nimmt Daten von der openDTU Rest-API und übersetzt sie in ein json-Format

    r = requests.get(url=f'http://{dtu_ip}/api/livedata/status/inverters').json()

    # Selektiert spezifische Daten aus der json response
    inverters_data = r.get('inverters', [])
    if inverters_data:
        inverters_data = inverters_data[0]
        reachable = inverters_data.get('reachable', False)  # Ist DTU erreichbar?
        producing = int(inverters_data.get('producing', 0))  # Produziert der Wechselrichter etwas?
        altes_limit = int(inverters_data.get('limit_absolute', 0))  # Altes Limit
        power = r['total']['Power']['v'] if 'total' in r and 'Power' in r['total'] else 0  # Abgabe BKW AC in Watt
    else:
        raise KeyError("Inverters data not found")

except Exception as e:
    print('Fehler beim Abrufen der Daten von openDTU:', str(e))
    power = 0  # Setze power auf 0 im Fehlerfall

try:
    # Nimmt Daten von der Shelly 3EM Rest-API und übersetzt sie in ein json-Format
    phase_a = requests.get(f'http://{shelly_ip}/emeter/0', headers={'Content-Type': 'application/json'}).json()['power']
    phase_b = requests.get(f'http://{shelly_ip}/emeter/1', headers={'Content-Type': 'application/json'}).json()['power']
    phase_c = requests.get(f'http://{shelly_ip}/emeter/2', headers={'Content-Type': 'application/json'}).json()['power']
    grid_sum = phase_a + phase_b + phase_c  # Aktueller Bezug - rechnet alle Phasen zusammen
except Exception as e:
    print('Fehler beim Abrufen der Daten von Shelly 3EM:', str(e))

# Werte setzen
print(f'\nBezug: {round(grid_sum, 1)} W, Produktion: {round(power, 1)} W, Verbrauch: {round(grid_sum, 1)} W')
if reachable:
    setpoint = grid_sum #+ altes_limit - 5  # Neues Limit in Watt

    # Fange oberes Limit ab
    if setpoint > maximum_wr:
        setpoint = maximum_wr
        print(f'Setpoint auf Maximum: {maximum_wr} W')
    # Fange unteres Limit ab
    elif setpoint < minimum_wr:
        setpoint = minimum_wr
        print(f'Setpoint auf Minimum: {minimum_wr} W')
    else:
        print(f'Setpoint berechnet: {round(grid_sum, 1)} W = {round(setpoint, 1)} W') #+ {round(altes_limit, 1)} W - 5 W = {round(setpoint, 1)} W')

    if setpoint != altes_limit:
        print(f'Setze Inverterlimit von {round(altes_limit, 1)} W auf {round(setpoint, 1)} W... ', end='')
        # Neues Limit setzen
        try:
            r = requests.post(
                url=f'http://{dtu_ip}/api/limit/config',
                data=f'data={{"serial":"{serial}", "limit_type":0, "limit_value":{setpoint}}}',
                auth=HTTPBasicAuth(dtu_nutzer, dtu_passwort),
                headers={'Content-Type': 'application/x-www-form-urlencoded'}
            )
            print(f'Konfiguration gesendet ({r.json()["type"]})')
        except Exception as e:
            print('Fehler beim Senden der Konfiguration:', str(e))

sys.stdout.flush()  # write out cached messages to stdout
time.sleep(5)  # wait`

Das fürt zu folgendem Ergebnis: grafik

Man sieht, dass die Konfigurationsdaten gesendet werden, beim nächsten mal steht da auch wieder das ursprüngliche Limit von 800W da. Ich kann das Intervall auch auf 60 Sekunden oder 5 Minuten einstellen, auf dem Wechselrichter ändert sich nichts.

Die JSON sieht bei mir so aus: grafik

Selbstbau-PV commented 4 months ago

The script works only with opnedtu lower than V24.1.26

MaTiX1980 commented 4 months ago

Hallo Selbstbau-PV, Ichhabe auf 24.1.21 geflashed nach dem es mit V24.1.26 auch nicht ging.

grafik

und die JSON grafik

MaTiX1980 commented 4 months ago

Hallo zusammen, wie so oft sitzt der Teufel im Details. Zum einen habe ich beim flashen den haken bei "DoNotChgBin" noch drin gehabt. Zum anderen hab ich bei der Serial eine Ziffer vergessen. Nun Läuft und regelt er.

Den originalen Code dazu müsste man aber mal erklären? Ich benutze diesen aber hab da mehr Fragezeichen als eine Lösung.

Ich hab ein max-Limit von 100W. ein min-Limit von 10W. Die Shelly liefert einen Verbrauchswert vom 85W. Das Limit im Wechselrichter wird auch auf 85W gesetz, geregelt wird ehr dann aber am Ausgang nur auf 40W. Also wesentlich weniger als der Bedarf ist. Kann mir das jemand erklären?

Grüße Stefan

MaTiX1980 commented 4 months ago

Hallo zusammen, die Begrenzung ist im Fall für einen WR mit 2 Eingangsstrings die Summe die dann aufgeteilt wird. Stefan