diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm
index a0fbde63b..29ac9a408 100644
--- a/fhem/contrib/DS_Starter/76_SolarForecast.pm
+++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm
@@ -126,6 +126,7 @@ BEGIN {
# Versions History intern
my %vNotesIntern = (
+ "0.69.0 "=> "12.10.2022 Autocorrection function for model SolCast-API, __solCast_ApiRequest: request only 48 hours ",
"0.68.7 "=> "07.10.2022 new function _calcCAQwithSolCastPercentil, check missed modules in _getRoofTopData ",
"0.68.6 "=> "06.10.2022 new attribute solCastPercentile, change _calcMaxEstimateToday ",
"0.68.5 "=> "03.10.2022 extent plant configuration check ",
@@ -912,7 +913,7 @@ sub Set {
"reset:$resets ".
"roofIdentPair ".
"writeHistory:noArg ".
- $cf
+ (!isSolCastUsed ($hash) ? $cf : q{});
;
my $params = {
@@ -1485,6 +1486,7 @@ sub _setpvCorrectionFactorAuto { ## no critic "not used"
$n = sprintf "%02d", $n;
my $rv = ReadingsVal ($name, "pvCorrectionFactor_${n}", "");
deleteReadingspec ($hash, "pvCorrectionFactor_${n}.*") if($rv !~ /manual/xs);
+ deleteReadingspec ($hash, "pvSolCastPercentile_${n}.*");
}
deleteReadingspec ($hash, "pvCorrectionFactor_.*_autocalc");
@@ -1853,11 +1855,11 @@ sub __solCast_ApiRequest {
my $url = "https://api.solcast.com.au/rooftop_sites/".
$roofid.
"/forecasts?format=json".
- "&hours=72".
+ "&hours=48".
"&api_key=".
$apikey;
- Log3 ($name, 4, qq{$name - Request SolCast API: $url});
+ Log3 ($name, 4, qq{$name - Request SolCast API for string "$string": $url});
my $caller = (caller(0))[3]; # Rücksprungmarke
@@ -1905,7 +1907,7 @@ sub __solCast_ApiResponse {
readingsSingleUpdate($hash, "state", $msg, 1);
return;
}
- elsif ($myjson ne "") { # Evaluiere ob Daten im JSON-Format empfangen wurden
+ elsif ($myjson ne "") { # Evaluiere ob Daten im JSON-Format empfangen wurden
my ($success) = evaljson($hash, $myjson);
if(!$success) {
@@ -1973,7 +1975,7 @@ sub __solCast_ApiResponse {
next;
}
- my $pvest = $jdata->{'forecasts'}[$k]{'pv_estimate'};
+ my $pvest50 = $jdata->{'forecasts'}[$k]{'pv_estimate'};
my $pvest10 = $jdata->{'forecasts'}[$k]{'pv_estimate10'};
my $pvest90 = $jdata->{'forecasts'}[$k]{'pv_estimate90'};
my $period = $jdata->{'forecasts'}[$k]{'period'};
@@ -1990,33 +1992,34 @@ sub __solCast_ApiResponse {
if($debug) { # nur für Debugging
if (exists $data{$type}{$name}{solcastapi}{$string}{$starttmstr}) {
- Log (1, qq{DEBUG> $name SolCast API Hash - Start Date/Time: }. $starttmstr);
- Log (1, qq{DEBUG> $name SolCast API Hash - pv_estimate already present: }. $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate});
- Log (1, qq{DEBUG> $name SolCast API Hash - pv_estimate to add: }. ($pvest * ($period/60) * 1000));
+ Log (1, qq{DEBUG> $name SolCast API Hash - Start Date/Time: }. $starttmstr);
+ Log (1, qq{DEBUG> $name SolCast API Hash - pv_estimate50 add: }.(sprintf "%.0f", ($pvest50 * ($period/60) * 1000)).qq{, contains already: }.SolCastAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0));
}
}
- $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate} += sprintf "%.0f", ($pvest * ($period/60) * 1000);
+ $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} += sprintf "%.0f", ($pvest50 * ($period/60) * 1000);
$data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate10} += sprintf "%.0f", ($pvest10 * ($period/60) * 1000);
$data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate90} += sprintf "%.0f", ($pvest90 * ($period/60) * 1000);
## erstellen Zusatzpercentile
###############################
- my $lowdm = ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate} - $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate10}) / 4;
- my $highdm = ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate90} - $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate}) / 4;
+ my $lowdm = ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} - $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate10}) / 4;
+ my $highdm = ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate90} - $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50}) / 4;
- $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate20} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate} - ($lowdm * 3));
- $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate30} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate} - ($lowdm * 2));
- $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate40} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate} - ($lowdm * 1));
+ $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate20} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} - ($lowdm * 3));
+ $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate30} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} - ($lowdm * 2));
+ $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate40} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} - ($lowdm * 1));
- $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate60} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate} + ($highdm * 1));
- $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate70} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate} + ($highdm * 2));
- $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate80} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate} + ($highdm * 3));
+ $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate60} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} + ($highdm * 1));
+ $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate70} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} + ($highdm * 2));
+ $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate80} = sprintf "%.0f", ($data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} + ($highdm * 3));
$k += 1;
}
}
+ Log3 ($name, 4, qq{$name - SolCast API answer received for string "$string"});
+
___setLastAPIcallKeyData ($hash, $t);
$data{$type}{$name}{solcastapi}{'?All'}{'?All'}{response_message} = 'success';
@@ -2792,7 +2795,8 @@ sub centralTask {
_evaluateThresholds ($centpars); # Schwellenwerte bewerten und signalisieren
_calcReadingsTomorrowPVFc ($centpars); # zusätzliche Readings Tomorrow_HourXX_PVforecast berechnen
_createSummaries ($centpars); # Zusammenfassungen erstellen
-
+ _calcTodayPVdeviation ($centpars); # Vorhersageabweichung erstellen (nach Sonnenuntergang)
+
createReadingsFromArray ($hash, \@da, 1); # Readings erzeugen
calcCorrAndQuality ($centpars); # neue Korrekturfaktor/Qualität berechnen und speichern
@@ -3003,6 +3007,7 @@ sub _specialActivities {
deleteReadingspec ($hash, "Today_Hour.*_Bat.*");
deleteReadingspec ($hash, "powerTrigger_.*");
deleteReadingspec ($hash, "Today_MaxPVforecast.*");
+ deleteReadingspec ($hash, "Today_PVdeviation");
if(ReadingsVal ($name, "pvCorrectionFactor_Auto", "off") eq "on") {
for my $n (1..24) {
@@ -3445,7 +3450,6 @@ sub __calcSolCastEstimates {
my $wantdt = $paref->{wantdt};
my $hod = $paref->{hod};
my $fd = $paref->{fd};
- my $fh1 = $paref->{fh1};
my $num = $paref->{num};
my $type = $hash->{TYPE};
@@ -3463,10 +3467,7 @@ sub __calcSolCastEstimates {
my $temp = NexthoursVal ($hash, "NextHour".sprintf("%02d",$num), "temp", $tempbasedef); # vorhergesagte Temperatur Stunde X
- #my $range = calcRange ($cloudcover); # Range errechnen
- #$paref->{range} = $range;
- #my ($hcfound, $hc, $hq) = ___readCorrfAndQuality ($paref); # liest den anzuwendenden Korrekturfaktor
- #delete $paref->{range};
+ my ($hcfound, $perc, $hq) = ___readPercAndQuality ($paref); # liest den anzuwendenden Korrekturfaktor
my ($lh,$sq);
my $pvsum = 0;
@@ -3488,12 +3489,10 @@ sub __calcSolCastEstimates {
$peak *= 1000;
- my $perc = AttrVal ($name, 'solCastPercentile', 50); # Percentile Auswahl
- $perc = q{} if($perc == 50);
-
- my $est = SolCastAPIVal ($hash, $string, $wantdt, 'pv_estimate', 0); # Estimate mittleres Percentile
- $est = SolCastAPIVal ($hash, $string, $wantdt, 'pv_estimate'.$perc, $est);
- my $pv = sprintf "%.1f", ($est * $ccf * $rcf);
+ my $edef = SolCastAPIVal ($hash, $string, $wantdt, 'pv_estimate', 0); # Back-Kompatibilität
+ my $ed50 = SolCastAPIVal ($hash, $string, $wantdt, 'pv_estimate50', $edef);
+ my $est = SolCastAPIVal ($hash, $string, $wantdt, 'pv_estimate'.$perc, $ed50);
+ my $pv = sprintf "%.1f", ($est * $ccf * $rcf);
$lh = { # Log-Hash zur Ausgabe
"Starttime" => $wantdt,
@@ -3502,7 +3501,6 @@ sub __calcSolCastEstimates {
"Module Temp (calculated)" => $modtemp." °C",
"Loss String Peak Power by Temp" => $peakloss." kWP",
"Cloudcover" => $cloudcover,
- #"CloudRange" => $range,
"CloudFactorDamping" => $clouddamp." %",
"Cloudfactor" => $ccf,
"Rainprob" => $rainprob,
@@ -3525,7 +3523,6 @@ sub __calcSolCastEstimates {
$data{$type}{$name}{current}{allstringspeak} = $peaksum; # temperaturbedingte Korrektur der installierten Peakleistung in W
- #$pvsum *= $hc; # Korrekturfaktor anwenden
$pvsum = $peaksum if($pvsum > $peaksum); # Vorhersage nicht größer als die Summe aller PV-Strings Peak
my $invcapacity = CurrentVal ($hash, "invertercapacity", 0); # Max. Leistung des Invertrs
@@ -3541,10 +3538,10 @@ sub __calcSolCastEstimates {
($pvsum, $logao) = ___70percentRule ($paref);
$lh = { # Log-Hash zur Ausgabe
- #"CloudCorrFoundInStore" => $hcfound,
- #"PV correction factor" => $hc,
- #"PV correction quality" => $hq,
- "PV generation forecast" => $pvsum." Wh ".$logao,
+ "CloudCorrFoundInStore" => $hcfound,
+ "SolCast selected percentile" => $perc,
+ "PV correction quality" => $hq,
+ "PV generation forecast" => $pvsum." Wh ".$logao,
};
$sq = q{};
@@ -3557,6 +3554,56 @@ sub __calcSolCastEstimates {
return $pvsum;
}
+######################################################################
+# Liest das anzuwendende Percentil (Qualität) und
+# speichert die Werte im Nexthours / PVhistory Hash
+######################################################################
+sub ___readPercAndQuality {
+ my $paref = shift;
+ my $hash = $paref->{hash};
+ my $name = $paref->{name};
+ my $num = $paref->{num}; # Nexthour
+ my $fh1 = $paref->{fh1};
+ my $fd = $paref->{fd};
+
+ my $type = $hash->{TYPE};
+
+ my $uac = ReadingsVal($name, "pvCorrectionFactor_Auto", "off"); # Auto- oder manuelle Korrektur
+ my $perc = ReadingsNum ($name, "pvSolCastPercentile_".sprintf("%02d",$fh1), 50); # Estimate Percentil
+ my $hcfound = "use manual percentile selection";
+ my $hq = "m";
+
+ if ($uac eq 'on') { # Autokorrektur soll genutzt werden
+ $hcfound = "yes"; # Status ob Autokorrekturfaktor im Wertevorrat gefunden wurde
+ ($perc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), 'percentile', undef); # Korrekturfaktor/KF-Qualität der Stunde des Tages der entsprechenden Bewölkungsrange
+ $hq //= 0;
+ if (!$perc) {
+ $hcfound = "no";
+ $perc = 50; # keine Korrektur
+ $hq = 0;
+ }
+
+ $perc = 50 if(!$perc);
+ }
+ else {
+ $perc = AttrVal ($name, 'solCastPercentile', $perc); # Percentile manuelle Auswahl
+ }
+
+ $perc = sprintf "%.0f", $perc;
+
+ $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{pvcorrf} = $perc."/".$hq;
+
+ if($fd == 0 && $fh1) {
+ $paref->{pvcorrf} = $perc."/".$hq;
+ $paref->{nhour} = sprintf("%02d",$fh1);
+ $paref->{histname} = "pvcorrfactor";
+ setPVhistory ($paref);
+ delete $paref->{histname};
+ }
+
+return ($hcfound, $perc, $hq);
+}
+
###################################################################
# Zellen Leistungskorrektur Einfluss durch Wärmekoeffizienten
# berechnen
@@ -5572,6 +5619,34 @@ sub _createSummaries {
return;
}
+################################################################
+# berechnet die prozentuale Abweichung von Today_PVforecast
+# und Today_PVreal
+################################################################
+sub _calcTodayPVdeviation {
+ my $paref = shift;
+ my $hash = $paref->{hash};
+ my $name = $paref->{name};
+ my $t = $paref->{t};
+ my $date = $paref->{date};
+ my $daref = $paref->{daref};
+
+ my $sstime = timestringToTimestamp ($date.' '.ReadingsVal($name, "Today_SunSet", '22:00').':00');
+
+ return if($t < $sstime);
+
+ my $pvfc = ReadingsNum ($name, 'Today_PVforecast', 0);
+ my $pvre = ReadingsNum ($name, 'Today_PVreal', 0);
+
+ my $diff = $pvfc - $pvre;
+
+ my $dp = sprintf("%.2f" , 100 * $diff / $pvre) if($pvre);
+
+ push @$daref, "Today_PVdeviation<>". $dp." %";
+
+return;
+}
+
################################################################
# Berechnen Forecast Tag / Stunden Verschieber
# aus aktueller Stunde + lfd. Nummer
@@ -6099,7 +6174,7 @@ sub _graphicHeader {
my $pvcorrf00 = NexthoursVal($hash, "NextHour00", "pvcorrf", "-/m");
my ($pcf,$pcq) = split "/", $pvcorrf00;
- my $pvcanz = "factor: $pcf / quality: $pcq";
+ my $pvcanz = (isSolCastUsed ($hash) ? 'Percentile' : 'factor').qq{: $pcf / quality: $pcq};
$pcq =~ s/m/-1/xs;
my $pvfc00 = NexthoursVal($hash, "NextHour00", "pvforecast", undef);
@@ -7586,28 +7661,8 @@ return;
}
################################################################
-# Ist Attribut 'numHistDays' gesetzt ?
-# $usenhd: 1 - ja, 0 - nein
-# $nhd : Anzahl der zu verwendenden HistDays
-################################################################
-sub useNumHistDays {
- my $name = shift;
-
- my $usenhd = 0;
- my $nhd = AttrVal($name, 'numHistDays', $calcmaxd+1);
-
- if($nhd == $calcmaxd+1) {
- $nhd = $calcmaxd;
- }
- else {
- $usenhd = 1;
- }
-
-return ($usenhd, $nhd);
-}
-
-################################################################
-# Korrekturfaktoren und Qualität berechnen / speichern
+# Korrekturen und Qualität berechnen / speichern
+# bei useAutoCorrection
################################################################
sub calcCorrAndQuality {
my $paref = shift;
@@ -7671,18 +7726,16 @@ sub _calcCAQfromDWDcloudcover {
if($cdone eq "done") {
Log3 ($name, 5, "$name - pvCorrectionFactor Hour: ".sprintf("%02d",$h)." already calculated");
next;
- }
-
- Log3 ($name, 5, "$name - Hour: ".sprintf("%02d",$h).", Today PVreal: $pvval, PVforecast: $fcval");
+ }
$paref->{hour} = $h;
- my ($pvhis,$fchis,$dnum,$range) = calcAvgFromHistory ($paref); # historische PV / Forecast Vergleichswerte ermitteln
+ my ($pvhis,$fchis,$dnum,$range) = __avgCloudcoverCorrFromHistory ($paref); # historische PV / Forecast Vergleichswerte ermitteln
my ($oldfac, $oldq) = CircularAutokorrVal ($hash, sprintf("%02d",$h), $range, 0); # bisher definierter Korrekturfaktor/KF-Qualität der Stunde des Tages der entsprechenden Bewölkungsrange
$oldfac = 1 if(1 * $oldfac == 0);
my $factor;
- my ($usenhd) = useNumHistDays ($name); # ist Attr numHistDays gesetzt ?
+ my ($usenhd) = __useNumHistDays ($name); # ist Attr numHistDays gesetzt ?
if($dnum) { # Werte in History vorhanden -> haben Prio !
$dnum = $dnum + 1;
@@ -7729,106 +7782,11 @@ sub _calcCAQfromDWDcloudcover {
return;
}
-################################################################
-# PVreal mit den SolCast Percentilen vergleichen und das
-# beste Percentil als Auswahl speichern
-################################################################
-sub _calcCAQwithSolCastPercentil {
- my $paref = shift;
- my $hash = $paref->{hash};
- my $name = $paref->{name};
- my $chour = $paref->{chour}; # aktuelle Stunde
- my $date = $paref->{date};
- my $daref = $paref->{daref};
-
- return if(!isSolCastUsed ($hash));
-
- my $debug = AttrVal ($name, "debug", 0);
-
- for my $h (1..23) {
- next if(!$chour || $h > $chour);
-
- my $pvval = ReadingsNum ($name, "Today_Hour".sprintf("%02d",$h)."_PVreal", 0);
- next if(!$pvval);
-
- my $cdone = ReadingsVal ($name, "pvSolCastPercentile_".sprintf("%02d",$h)."_autocalc", "");
-
- if($cdone eq "done") {
- Log3 ($name, 5, "$name - pvSolCastPercentile Hour: ".sprintf("%02d",$h)." already calculated");
- next;
- }
-
- my ($oldperc, $oldq) = CircularAutokorrVal ($hash, sprintf("%02d",$h), 'percentile', 0); # bisher definiertes Percentil/Qualität der Stunde des Tages der entsprechenden Bewölkungsrange
- $oldperc = q{default} if(1 * $oldperc == 0);
-
- my @sts = split ",", ReadingsVal($name, 'inverterStrings', '');
-
- my ($est,$est10,$est20,$est30,$est40,$est60,$est70,$est80,$est90);
-
- for my $s (@sts) {
- $est += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate', 0);
- $est10 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate10', 0);
- $est20 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate20', 0);
- $est30 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate30', 0);
- $est40 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate40', 0);
- $est60 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate60', 0);
- $est70 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate70', 0);
- $est80 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate80', 0);
- $est90 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate90', 0);
- }
-
- if ($debug) { # nur für Debugging
- Log (1, qq{DEBUG> $name summary PV estimates for hour of day "$h":\n}.
- qq{est: $est, est10: $est10, est20: $est20, est30: $est30, est40: $est40, est60: $est60, est70: $est70, est80: $est80, est90: $est90});
- }
-
- my %pc = (
- 10 => $est10,
- 20 => $est20,
- 30 => $est30,
- 40 => $est40,
- 60 => $est60,
- 70 => $est70,
- 80 => $est80,
- 90 => $est90,
- );
-
- my $dnum = 1;
- my $perc = q{}; # Standardpercentil
- my $diff0 = abs ($est - $pvval);
- ## no critic 'NoStrict'
- for my $p (sort keys %pc) {
- my $diff1 = abs ($pc{$p} - $pvval);
-
- if($diff1 < $diff0) {
- $diff0 = $diff1;
- $perc = $p;
- }
- }
-
- if ($debug) { # nur für Debugging
- Log (1, qq{DEBUG> $name percentile -> hour: $h, number checked days: $dnum, pvreal: $pvval, diffbest: $diff0, best percentile: $perc});
- }
-
- #Log3 ($name, 5, "$name - write percentile into circular Hash: $perc, Hour $h");
-
- #my $type = $hash->{TYPE};
-
- #$data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{percentile} = $perc; # bestes Percentil für die jeweilige Stunde speichern
- #$data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{percentile} = $dnum; # Percentil Qualität
-
- push @$daref, "pvSolCastPercentile_".sprintf("%02d",$h)."<>".$perc." (automatic - old percentile: $oldperc)";
- push @$daref, "pvSolCastPercentile_".sprintf("%02d",$h)."_autocalc<>done";
- }
-
-return;
-}
-
################################################################
# Berechne Durchschnitte PV Vorhersage / PV Ertrag
# aus Werten der PV History
################################################################
-sub calcAvgFromHistory {
+sub __avgCloudcoverCorrFromHistory {
my $paref = shift;
my $hash = $paref->{hash};
my $hour = $paref->{hour}; # Stunde des Tages für die der Durchschnitt bestimmt werden soll
@@ -7840,7 +7798,7 @@ sub calcAvgFromHistory {
my $type = $hash->{TYPE};
my $pvhh = $data{$type}{$name}{pvhist};
- my ($usenhd, $calcd) = useNumHistDays ($name); # ist Attr numHistDays gesetzt ? und welcher Wert
+ my ($usenhd, $calcd) = __useNumHistDays ($name); # ist Attr numHistDays gesetzt ? und welcher Wert
my @k = sort {$a<=>$b} keys %{$pvhh};
my $ile = $#k; # Index letztes Arrayelement
@@ -7917,6 +7875,225 @@ sub calcAvgFromHistory {
return;
}
+################################################################
+# Ist Attribut 'numHistDays' gesetzt ?
+# $usenhd: 1 - ja, 0 - nein
+# $nhd : Anzahl der zu verwendenden HistDays
+################################################################
+sub __useNumHistDays {
+ my $name = shift;
+
+ my $usenhd = 0;
+ my $nhd = AttrVal($name, 'numHistDays', $calcmaxd+1);
+
+ if($nhd == $calcmaxd+1) {
+ $nhd = $calcmaxd;
+ }
+ else {
+ $usenhd = 1;
+ }
+
+return ($usenhd, $nhd);
+}
+
+################################################################
+# PVreal mit den SolCast Percentilen vergleichen und das
+# beste Percentil als Auswahl speichern
+################################################################
+sub _calcCAQwithSolCastPercentil {
+ my $paref = shift;
+ my $hash = $paref->{hash};
+ my $name = $paref->{name};
+ my $chour = $paref->{chour}; # aktuelle Stunde
+ my $date = $paref->{date};
+ my $daref = $paref->{daref};
+
+ return if(!isSolCastUsed ($hash));
+
+ my $debug = AttrVal ($name, "debug", 0);
+
+ for my $h (1..23) {
+ next if(!$chour || $h > $chour);
+
+ my $pvval = ReadingsNum ($name, "Today_Hour".sprintf("%02d",$h)."_PVreal", 0);
+ next if(!$pvval);
+
+ my $cdone = ReadingsVal ($name, "pvSolCastPercentile_".sprintf("%02d",$h)."_autocalc", "");
+
+ if($cdone eq "done") {
+ Log3 ($name, 5, "$name - pvSolCastPercentile Hour: ".sprintf("%02d",$h)." already calculated");
+ next;
+ }
+
+ $paref->{hour} = $h;
+ my ($dnum,$avgperc) = __avgSolCastPercFromHistory ($paref); # historische Percentile / Qualität ermitteln
+
+ my ($oldperc, $oldq) = CircularAutokorrVal ($hash, sprintf("%02d",$h), 'percentile', 0); # bisher definiertes Percentil/Qualität der Stunde des Tages der entsprechenden Bewölkungsrange
+ $oldperc = 50 if(1 * $oldperc == 0);
+
+ my @sts = split ",", ReadingsVal($name, 'inverterStrings', '');
+
+ my ($est10,$est20,$est30,$est40,$est50,$est60,$est70,$est80,$est90);
+
+ for my $s (@sts) {
+ $est10 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate10', 0);
+ $est20 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate20', 0);
+ $est30 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate30', 0);
+ $est40 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate40', 0);
+ $est50 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate50', 0); # Standardpercentil
+ $est60 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate60', 0);
+ $est70 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate70', 0);
+ $est80 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate80', 0);
+ $est90 += SolCastAPIVal ($hash, $s, $date.' '.sprintf("%02d",$h-1).':00:00', 'pv_estimate90', 0);
+ }
+
+ if(!$est50) { # kein Standardpercentile vorhanden
+ Log (1, qq{DEBUG> $name percentile -> hour: $h, the best percentile can't be identified because of the default percentile has no value yet}) if($debug);
+ next;
+ }
+
+ my %pc = (
+ 10 => $est10,
+ 20 => $est20,
+ 30 => $est30,
+ 40 => $est40,
+ 60 => $est60,
+ 70 => $est70,
+ 80 => $est80,
+ 90 => $est90,
+ );
+
+ my $perc = 50; # Standardpercentil
+ my $diff0 = abs ($est50 - $pvval);
+ ## no critic 'NoStrict'
+ for my $p (sort keys %pc) {
+ my $diff1 = abs ($pc{$p} - $pvval);
+
+ if($diff1 < $diff0) {
+ $diff0 = $diff1;
+ $perc = $p;
+ }
+ }
+
+ if ($debug) { # nur für Debugging
+ Log (1, qq{DEBUG> $name summary PV estimates for hour of day "$h":\n}.
+ qq{est10: $est10, est20: $est20, est30: $est30, est40: $est40, est50 (default): $est50, est60: $est60, est70: $est70, est80: $est80, est90: $est90});
+ Log (1, qq{DEBUG> $name percentile -> hour: $h, number checked days: $dnum, pvreal: $pvval, diffbest: $diff0, best percentile: $perc});
+ }
+
+ my ($usenhd) = __useNumHistDays ($name); # ist Attr numHistDays gesetzt ?
+
+ if($dnum) { # Werte in History vorhanden -> haben Prio !
+ $avgperc = $avgperc * $dnum;
+ $dnum++;
+ $perc = sprintf "%.0f", ((($avgperc + $perc) / $dnum) / 10);
+ }
+ elsif($oldperc && !$usenhd) { # keine Werte in History vorhanden, aber in CircularVal && keine Beschränkung durch Attr numHistDays
+ $oldperc = $oldperc * $oldq;
+ $dnum = $oldq + 1;
+ $perc = sprintf "%.0f", ((($oldperc + $perc) / $dnum) / 10);
+ }
+ else { # ganz neuer Wert
+ $perc = sprintf "%.0f", ($perc / 10);
+ $dnum = 1;
+ }
+
+ $perc = $perc * 10;
+
+ if ($debug) { # nur für Debugging
+ Log (1, qq{DEBUG> $name percentile -> old avg percentile: }.($avgperc ? $avgperc : '-').qq{, new avg percentile: $perc});
+ }
+
+ Log3 ($name, 5, "$name - write percentile into circular Hash: $perc, Hour $h");
+
+ my $type = $hash->{TYPE};
+
+ $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{percentile} = $perc; # bestes Percentil für die jeweilige Stunde speichern
+ $data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{percentile} = $dnum; # Percentil Qualität
+
+ push @$daref, "pvSolCastPercentile_".sprintf("%02d",$h)."<>".$perc." (automatic - old percentile: $oldperc)";
+ push @$daref, "pvSolCastPercentile_".sprintf("%02d",$h)."_autocalc<>done";
+ }
+
+return;
+}
+
+################################################################
+# Berechne das durchschnittlich verwendete Percentil
+# aus Werten der PV History
+################################################################
+sub __avgSolCastPercFromHistory {
+ my $paref = shift;
+ my $hash = $paref->{hash};
+ my $hour = $paref->{hour}; # Stunde des Tages für die der Durchschnitt bestimmt werden soll
+ my $day = $paref->{day}; # aktueller Tag
+
+ $hour = sprintf("%02d",$hour);
+
+ my $name = $hash->{NAME};
+ my $type = $hash->{TYPE};
+ my $pvhh = $data{$type}{$name}{pvhist};
+
+ my ($usenhd, $calcd) = __useNumHistDays ($name); # ist Attr numHistDays gesetzt ? und welcher Wert
+
+ my @k = sort {$a<=>$b} keys %{$pvhh};
+ my $ile = $#k; # Index letztes Arrayelement
+ my ($idx) = grep {$k[$_] eq "$day"} (0..@k-1); # Index des aktuellen Tages
+
+ return 0 if(!defined $idx);
+
+ my $ei = $idx-1;
+ $ei = $ei < 0 ? $ile : $ei;
+ my @efa;
+
+ for my $e (0..$calcmaxd) {
+ last if($e == $calcmaxd || $k[$ei] == $day);
+ unshift @efa, $k[$ei];
+ $ei--;
+ }
+
+ if(scalar(@efa)) {
+ Log3 ($name, 4, "$name - PV History -> Raw Days ($calcmaxd) for average check: ".join " ",@efa);
+ }
+ else { # vermeide Fehler: Illegal division by zero
+ Log3 ($name, 4, "$name - PV History -> Day $day has index $idx. Use only current day for average calc");
+ return 0;
+ }
+
+ my ($dnum, $percsum) = (0, 0);
+ my ($perc, $qual) = (50,0);
+
+ for my $dayfa (@efa) {
+ my $histval = HistoryVal ($hash, $dayfa, $hour, 'pvcorrf', undef); # historisches Percentil/Qualität
+
+ next if(!defined $histval);
+
+ ($perc, $qual) = split "/", $histval; # Percentil und Qualität splitten
+
+ next if($qual eq 'm'); # manuell eingestellte Percentile überspringen
+
+ $perc = 50 if(!$perc || $perc < 10);
+
+ Log3 ($name, 5, qq{$name - PV History -> historical Day/hour $dayfa/$hour included - percentile: $perc});
+
+ $dnum++;
+ $percsum += $perc ;
+
+ last if($dnum == $calcd);
+ }
+
+ if(!$dnum) {
+ Log3 ($name, 5, "$name - PV History -> no historical percentile selected");
+ return 0;
+ }
+
+ $perc = sprintf "%.0f", ($percsum/$dnum);
+
+ Log3 ($name, 5, "$name - PV History -> Summary - days: $dnum, average percentile: $perc");
+
+return ($dnum,$perc);
+}
+
################################################################
# Bewölkungs- bzw. Regenrange berechnen
################################################################
@@ -10056,12 +10233,39 @@ Ein/Ausschaltzeiten sowie deren Ausführung vom SolarForecast Modul übernehmen
Die Wirkungsweise unterscheidet sich zwischen dem Model DWD und dem Model SolCastAPI.
Model SolCastAPI:
- (in Entwicklung)
+ Eine eingeschaltete Autokorrektur ermittelt am Ende jeder relevanten Stunde durch Vergleich von PV Prognose und
+ realer Erzeugung das beste Percentil (10-90).
+ Bevor die Autokorrektur eingeschaltet ist die Prognose zu optimieren mit folgenden Schritten:
+