PV-Vorhersage algorithmisch - abgeleitet aus Globalstrahlung

Begonnen von Prof. Dr. Peter Henning, 04 April 2026, 18:47:12

Vorheriges Thema - Nächstes Thema

Prof. Dr. Peter Henning

Der Deutsche Wetterdienst liefert eine stündliche Vorhersage über die zu erwartende Globalstrahlung.
Daraus kann man die vorhergesagte Einstrahlung auf die eigene PV-Anlage nach gut etablierten strahlungsphysikalischen Modellen berechnen.

Ich habe das mal provisorisch hier niedergelegt, inklusive eines entsprechenden Perl-Programms.

https://wiki.fhem.de/wiki/Von_der_Globalstrahlung_zur_Ertragsvorhersage

Das ist natürlich noch nicht die ganze Miete.

Es gibt drei weitere Perl-Programme:
1. Jeweils um kurz nach Mitternacht werden für den gegenwärtigen und den nächsten Tag die Sonnenstände vorausberechnet und in den Readings eines Dummy abgelegt.
2. Sobald vom DWD neue Vorhersagedaten einlaufen (also alle 60 Minuten), werden für alle meiner PV-Strings die neuen stündlichen Vorhersagewerte für den heutigen und den nächsten Tag berechnet und in den Readings dieses Dummys abgelegt.
3. Anschließend werden alle Daten aus den Readings des Dummys mit der Funktion logFromArray($$$;$$$$$) alle Daten der beiden Tage in eine Logdatei geschrieben. Diese Datei gehört zu einem FileLog-Device, das eigentlich _gar nichts_ loggt - sondern nur das täglich neue File, Backup- und Archivfunktionen etc. bereitstellt. Die Datei wird also alle 60 Minuten komplett überschrieben.

Im Endeffekt habe ich für jede meiner Anlagen eine stündlich aktualisierte Vorhersage.

Der Vergleich mit den tatsächlichen Erträgen ist noch etwas trickreich, weil die natürlich auch über eine Stunde gemittelt werden müssen - da feile ich noch an den genau passenden Zeiträumen. Sieht aber vorläufig sehr gut aus: Nach Ablauf des Sonnentages weniger als 10% Abweichung zwischen Prognose (die ja keine Prognose mehr ist, weil der aktuelle Tag bei DWD dann durch die gemessenen Daten ersetzt wird) und Messung.

Bei Gelegenheit werde ich die Doku noch komplettieren und den ganzen Perl-Code öffentlich machen.

LG

pah

Prof. Dr. Peter Henning

Ich habe das jetzt noch um das Thema Incident Angle Modifier ergänzt.
Damit kann man bereits auf einfache Weise Module unterschiedlicher Typen einbinden, und im Prinzip die IAM-Kurven der Hersteller einbauen.

LG

pah

DS_Starter

#2
Hallo pah,

vielen Dank für deine Arbeit und Veröffentlichung die natürlich für SolarForecast sehr interessant ist.
Zu gegebener Zeit werde ich deine Herleitung gern in SolarForecast übernehmen und deine seit 2024 in SF verwendete Methode (https://wiki.fhem.de/wiki/Ertragsprognose_PV) ersetzen.
Die Sonnenstandsparameter sind ja via Astro-Subs bereits im Modul vorhanden.

(geändert)

LG,
Heiko
Proxmox+Debian+MariaDB, PV: SMA, Victron MPII+Pylontech+CerboGX
Maintainer: SSCam, SSChatBot, SSCal, SSFile, DbLog/DbRep, Log2Syslog, SolarForecast,Watches, Dashboard, PylonLowVoltage
Kaffeekasse: https://www.paypal.me/HMaaz
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter

Prof. Dr. Peter Henning

@DS_Starter:

Man muss noch aufpassen, weil die DWD-Daten in kJ/m²h angegeben werden und sich am Ende jeder Stunde auf die vorausgehenden 60 Minuten beziehen. Idealerweise bestimmt man also die Sonnenhöhe und den Sonnenazimut 30 Minuten vor diesem Zeitpunkt.

Ich habe jetzt den Wiki-Artikel noch einmal überarbeitet und die gebräuchlichen Symbole eingesetzt, das war noch zu sehr auf eigenem Mist gewachsen. Anbei ein Bild, wie es derzeit bei mir aussieht. Für die erste Anlage, also PV1, muss ich die Skalierung noch optimieren. Für PV2, pass das wie die Faust aufs Auge.

Bei beiden Anlagen stimmen die Werte morgens nicht, da liegen sie im Schatten des Nachbarhauses. Müsste ich abreißen lassen, um das zu verbessern.

Und die gemessene Globalstrahlung ist auch nicht korrekt, denn mein geeichter Sensor ist nicht horizontal.

LG

pah




DS_Starter

Aus Interesse ..

Zitat...denn mein geeichter Sensor...

was ist das für ein Gerät und ist es in FHEM einbindbar bzw. von dir evtl. bereits eingebunden?
Proxmox+Debian+MariaDB, PV: SMA, Victron MPII+Pylontech+CerboGX
Maintainer: SSCam, SSChatBot, SSCal, SSFile, DbLog/DbRep, Log2Syslog, SolarForecast,Watches, Dashboard, PylonLowVoltage
Kaffeekasse: https://www.paypal.me/HMaaz
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter

Prof. Dr. Peter Henning

#5
Das Teil habe ich vor 19 Jahren zusammen mit meiner ersten PV-Anlage bezogen (400 €), es ist analog an meinen NT5000 Wechselrichter angebunden und wird zusammen mit dessen Leistungsdaten von FHEM abgefragt. Sprich: Wenn ich Ende 2027 den alten WR herauswerfe, muss ich das an einen A/D-Wandler anschließen.

Allerdings ist nach 19 Jahren die Eichung auch nicht mehr vertrauenswürdig. Mal sehen, vielleicht bestelle ich mir das hier:
https://de.aliexpress.com/item/1005010313016760.html

LG

pah

Parallix

Zitat von: Prof. Dr. Peter Henning am 05 April 2026, 16:39:12...
Mal sehen, vielleicht bestelle ich mir das hier:
https://de.aliexpress.com/item/1005010313016760.html
...
Einen Versuch ist es vielleicht wert. Schöner wäre es, wenn es eine leitungslose Komplettlösung, z.B. auf EnOcean-Basis, gäbe. Der SR64-LI ist für den Anwendungsfall leider nicht wirklich geeignet.
FHEM: Debian/Testing BananaPro - AVM: 7490 (7.62) und 7591 (8.25) - Goodwe: GW25K-ET (DSP V10 / ARM V12) - Trina TSM 405: (#East, #South, #West) = (12,16,12) - BYD: 2 x HVS 7.7 (BMS V3.31-B, BMU V3.26-B) - EnOcean - Z-Wave - FS20/HMS

ch.eick

#7
Moin zusammen,
aktuell passt meine Prognose auch super, das Problem sind ja eher die Winter Tage und deren Wetterlage, die dann beim DWD auch nur Glaskugel sind.

Die Erträge müssen bei mir wegen des DC Speichers leider immer korrigiert werden, da Kostal den Ertrag erst am AC Ausgang angibt und somit hätte man in der Nacht auch Ertrag :-)
Meine Prognose kommt vollständig ohne Angaben zur PV-Anlage, abgesehen von den Erträgen, aus was für nicht Techniker einfach ist.
Du darfst diesen Dateianhang nicht ansehen.

VG   Christian
RPI4; Docker; CUNX; Eltako FSB61NP; SamsungTV H-Serie; Sonos; Vallox; Luxtronik; 3x FB7490; Stromzähler mit DvLIR; wunderground; Plenticore 10 mit BYD; EM410; SMAEM; Modbus TCP
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/ch.eick

DS_Starter

#8
Hallo pah,

ich habe deine neue Logik erfolgreich in das SF-Modul integriert.
Sieht schonmal richtig gut aus. Da die SF-Schleife deutlich häufiger zyklisch läuft, habe ich den implementierten Code mit einem Caching Mechanismus angereichert. model und b0 habe ich erstmal auf default belassen. Damit muß ich mich noch befassen.

Hier nur als Info/Anregung ein Codeauszug aus der Implementierung der natürlich auf SF zugeschnitten ist.

globale Definition:
my %AREA_CACHE;                                                                     # Caching Storage, key → { G_tilt => ..., wcc => ... }
my %AREA_CACHE_STATS = (                                                            # Cache Statistic
    hits   => 0,
    misses => 0,
    evicts => 0,
    size   => 0,
);

Dann:

sub ___computeTiltedIrradianceCached {  
  my $paref    = shift;
  my $name     = $paref->{name};
  my $fd       = $paref->{fd};                                                  # lfd. Tag-Index 0, 1, 2 ...
  my $dday     = $paref->{dday};                                                # abzufragender Tag: 01 .. 31
  my $chour    = $paref->{chour};                                               # aktuelle Stunde (00 .. 23)
  my $hod      = $paref->{hod};                                                 # abzufragende Stunde des Tages 01, 02 ... 24
  my $str_tilt = $paref->{tilt};                                                # String Anstellwinkel / Neigung
  my $str_azi  = $paref->{azimut};                                              # String Ausrichtung / Azimut
  my $model    = $paref->{model} // 0;                                          # 0 - Berechnung mit isotropem Himmelsmodell, 1 - Berechnung nach Hay-Davies
  my $b0       = $paref->{b0}    // 0.05;                                       # Materialfaktor für IAM, 0.05 .. 0.2
  my $rad      = $paref->{rad};                                                 # Rad1h = Absolute Globalstrahlung letzte 1 Stunde, kJ/m2
  my $dofyear  = $paref->{dofyear};                                             # Tag des Jahres (001 - 366)

  .........
  .........
 
  my $cache_key = join ':',                                                     # Cache‑Key erzeugen
    $offset_hours,
    $sunalt // 'U',
    $sunaz  // 'U',
    $rad    // 'U',
    $str_tilt,
    $str_azi,
    $model,
    $b0,
    $dofyear;
   
  # --- Cache-Treffer? ---
  if (exists $AREA_CACHE{$cache_key}) {
      $AREA_CACHE_STATS{hits}++;
      return $AREA_CACHE{$cache_key};
  }

  my $sg  = $rad * KJ2WH;                                                       # rad=kJ/m²h -> 1kJ = 0,27778 Wh
  my $rho = 0.2;                                                                # Bodenalbedo
  my $pi  = 4 * atan2 (1,1);                                                    # klassischer Perl-Trick, um π (Pi) mathematisch exakt zu berechnen – ohne es als feste Zahl einzutragen
  my $deg = $pi / 180.0;
  my $Isc = 1367.0;                                                             # Solarkonstante W/m²

  $AREA_CACHE_STATS{misses}++;

  return 0 if(!defined($sg) || $sg <= 0);

  my $sunaz_r  = $sunaz    * $deg;
  my $sunalt_r = $sunalt   * $deg;
  my $azimut_r = $str_azi  * $deg;
  my $tilt_r   = $str_tilt * $deg;
  my $sin_ele  = sin ($sunalt_r);
  my $cos_ele  = cos ($sunalt_r);

  return 0 if($sin_ele <= 0.0);

  my $I0n = $Isc * (1.0 + 0.033 * cos (2.0 * $pi * $dofyear / 365.0));
  my $I0h = $I0n * $sin_ele;
 
  return 0 if($I0h <= 0.0);

  my $Kt = $sg / $I0h;
  $Kt    = max (0.0, min (1.0, $Kt));                                           # Kt (Clear-Sky-Index), Clamping wichtig

  my $kd;                                                                       # Diffusanteil kd nach Erbs-Polynom
 
  if ($Kt <= 0.22) {
      $kd = 1.0 - 0.09 * $Kt;
  }
  elsif ($Kt <= 0.80) {
      $kd = 0.9511
          - 0.1604  * $Kt
          + 4.388   * $Kt**2
          - 16.638  * $Kt**3
          + 12.336  * $Kt**4;
  }
  else {
      $kd = 0.165;
  }

  $kd = max (0.0, min (1.0, $kd));

  my $sf = $kd * $sg;                                                           # diffuse horizontale Strahlung = DHI
  my $si = $sg - $sf;                                                           # direkte horizontale Strahlung = BHI
  $si    = max (0.0, $si);

  my $cos_thetai = $sin_ele * cos($tilt_r)                                      # Einfallswinkel cos(θi)
                 + $cos_ele * sin($tilt_r) * cos($sunaz_r - $azimut_r);

  my $cos_thetai_pos = $cos_thetai;
  $cos_thetai_pos    = 0.0 if $cos_thetai_pos < 0.0;

  my $dni    = ($sin_ele > 0.01) ? ($si / $sin_ele) : 0.0;                      # DNI (Direct Normal Irradiance)
  my $B_tilt = $dni * $cos_thetai_pos;                                          # Direktstrahlung auf geneigte Fläche
  $B_tilt    = max (0.0, $B_tilt);

  # --- IAM nur für Direktstrahlung (ASHRAE)
  my $IAMb = 1.0;
 
  if ($B_tilt > 0.0 && $cos_thetai_pos > 0.0) {
      $IAMb = 1.0 - $b0 * (1.0 / $cos_thetai_pos - 1.0);                        # IAM (Incidence Angle Modifier)
      $IAMb = max (0.0, min (1.0, $IAMb));
  }

  my $B_eff = $B_tilt * $IAMb;

  my $D_tilt;

  if ($model == 1) {                                                            # Diffusanteil auf geneigte Fläche, Modell 1 (Hay-Davies)
      my $Ai  = $sg > 0.0 ? (($si / $sg) * $Kt) : 0.0;
      $Ai     = max (0.0, $Ai);
      $Ai     = min (1.0, $Ai);
      my $Rb  = $sin_ele > 0.01 ? ($cos_thetai_pos / $sin_ele) : 0.0;
      $Rb     = max (0.0, $Rb);
      $D_tilt = $sf * ($Ai * $Rb + (1.0 - $Ai) * (1.0 + cos($tilt_r)) / 2.0);
  }
  else {
      $D_tilt = $sf * (1.0 + cos($tilt_r)) / 2.0;                               # Diffusanteil auf geneigte Fläche, Modell 0 (isotrop)
  }

  $D_tilt    = max (0.0, $D_tilt);
 
  my $R_tilt = $rho * $sg * (1.0 - cos($tilt_r)) / 2.0;                         # Bodenreflexion
  $R_tilt    = max (0.0, $R_tilt);
 
  my $G_tilt = $B_eff + $D_tilt + $R_tilt;                                      # Gesamteinstrahlung
  $G_tilt    = max (0.0, $G_tilt);

  $G_tilt = round2 ($G_tilt);
 
  if ($G_tilt > 0) {                                                            # Berechnungsergebnis cachen
      $AREA_CACHE{$cache_key} = $G_tilt;
      $AREA_CACHE_STATS{size} = scalar keys %AREA_CACHE;
  }   

return $G_tilt;                                                                 # effektive Einstrahlung $G_tilt auf die PV-Anlage in W/m²
}

Eine LRU Cacheverwaltung muß ich noch einbauen damit das Ding nicht unkontrolliert wächst.
Der Ergebniswert wird dann wie bekannt mit Bewölkung bzw. Umweltfaktoren in Beziehung gebracht.
Bin gespannt auf die Ergebnisse aus der Community.

LG,
Heiko
Proxmox+Debian+MariaDB, PV: SMA, Victron MPII+Pylontech+CerboGX
Maintainer: SSCam, SSChatBot, SSCal, SSFile, DbLog/DbRep, Log2Syslog, SolarForecast,Watches, Dashboard, PylonLowVoltage
Kaffeekasse: https://www.paypal.me/HMaaz
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter