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

Test mit HMS-2000-4T, OpenDTU-C526A4, v24.1.4 erfolgreich #29

Open grasmax opened 3 months ago

grasmax commented 3 months ago

python-Script zum Auslesen und Einstellen des Limits erfolgreich getestet: OpenDTU mit externer Magnetfuß Antenne für OpenDTU HMS/HMT Serie 865 MHz, 5dBm, 40m Luftlinie Antenne-Wechselrichter

Großer Dank an die Macher!

tha0o0 commented 2 weeks ago

Moin Moin, ich versuche mich jetzt hier seit Stunden duch die verschiedensten Versionen, aber ich verliere den Überblick bei den ganzen FORKS weil teilweise die Dokumentation für einen Laien schwer verfolgbar ist, und irgendwie läuft das script nicht. Mag mir jemand verhelfen meine OpenDTU mit meinem Shelly3EM Pro und meinem RASPI5 zum laufen zu bekommen, am HMS-2000-4t ?

grasmax commented 2 weeks ago

Steht die WLAN-Verbindung zwischen DTU und dem Router? Steht die Funkverbindung zum Wechselrichter?

Hier die Codefragemente, mit denen ich getestet habe. Aber Achtung! Das Setzen von Konfigurationswerten zerstört irgendwas! Hinweise unbedingt beachten!!! Nur das Auslesen der Werte und das Setzen des Limits funktionieren.

# Hier gefunden: https://github.com/Selbstbau-PV/Selbstbau-PV-Hoymiles-nulleinspeisung-mit-OpenDTU-und-Shelly3EM
# Testergebnis gepostet: https://github.com/Selbstbau-PV/Selbstbau-PV-Hoymiles-nulleinspeisung-mit-OpenDTU-und-Shelly3EM/issues/29

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

serial = "nnnnnnnnn" # Seriennummer des Hoymiles Wechselrichters
maximum_wr = 1990 # Maximale Leistung des Wechselrichters in Watt
minimum_wr = 100 # Minimale Leistung des Wechselrichters in Watt

dtu_ip = 'nnn.nnn.nnn.nnn' # IP Adresse von OpenDTU
dtu_nutzer = 'xxxxx' # OpenDTU Nutzername
dtu_passwort = 'yyyyyyy' # OpenDTU Passwort

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

# API siehe https://www.opendtu.solar/firmware/web_api/

while True:

    try:
        #url = f'http://{dtu_ip}/api/dtu/config'  err
        # url = f'http://{dtu_ip}/api/limit/status' ok
        #url = f'http://{dtu_ip}/api/ntp/status' ok
        # url = f'http://{dtu_ip}/api/ntp/config' err
        # url = f'http://{dtu_ip}/api/system/status' ok

        # in https://www.opendtu.solar/firmware/web_api/ findet man nur /api/dtu/config
        # dann muss man sich selbst zusammenreimen über 
        #   https://www.opendtu.solar/firmware/configuration/dtu_settings/
        # wie man die Werte setzen kann 
        # Leider muss man nicht nur beim Post, sondern auch beim GET Nutzer und Passwort mitgeben!

        ret = requests.get(
                    url = f'http://{dtu_ip}/api/dtu/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'}
                )
        jData = ret.json()

#Achtung! dieser Aufruf unterbricht die Funkverbindung zum Wechselrichter!
# Vorher unbedingt die config.json sichern, denn nur durch Einlesen der geretteten Konfig und Setzen der Sendeleistung über den 
# GUI kann man die Verbindung wiederherstellen!!!
        ret = requests.post(
                    url = f'http://{dtu_ip}/api/dtu/config',
                    data = f'data={{"serial":"{serial}", "pollinterval":8, "nrf_enabled": "True", "nrf_palevel":0, "cmt_enabled": "True", "cmt_palevel":17,"cmt_frequency": 865000}}',
                    auth = HTTPBasicAuth(dtu_nutzer, dtu_passwort),
                    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
                )

        # 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 Exception as e:
        print(f'Fehler beim Abrufen der Daten von openDTU: {e}')

    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
        setpoint = 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')
            print(f'Setpoint {setpoint} neu')

        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