From fcb7f80ed296cd9386ff2ffe55b2f06dd6ea3c40 Mon Sep 17 00:00:00 2001 From: DS_Starter Date: Fri, 4 Jul 2025 20:35:37 +0000 Subject: [PATCH] 76_SolarForecast: showDiff can be set separately for each level git-svn-id: https://svn.fhem.de/fhem/trunk@30095 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/76_SolarForecast.pm | 459 ++++++++++++++++++++-------------- 2 files changed, 269 insertions(+), 191 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 6a890e976..aec60aceb 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it + - feature: 76_SolarForecast: showDiff can be set separately for each level - bugfix: 76_SolarForecast: fix Perl warning in release 1.53.0 - feature: 76_SolarForecast: minor release 1.53.0 - feature: 76_SolarForecast: new special Reading conForecastComingNight diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index ae8756147..55445d77e 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -160,6 +160,9 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.53.3" => "04.07.2025 Change of the correction factor calculation to the ratio of real production and the API raw forecast ", + "1.53.2" => "03.07.2025 graphicControl->showDiff can be set separately for each level ". + "setupInverterDevXX: Check that there are no commas with spaces before and after (strings) ", "1.53.1" => "30.06.2025 add utf8 smileys, fix Perl warning uninitialized value \$color ", "1.53.0" => "28.06.2025 new battery style (batcontainer), new key setupBatteryDevXX->label, new reading Battery_ChargeUnrestricted_XX ". "attribute graphicShowDiff replaced by graphicControl->showDiff ". @@ -6487,17 +6490,18 @@ sub _attrgraphicControl { ## no critic "not used" my $cmd = $paref->{cmd}; my $valid = { - beamPaddingBottom => { comp => '\d+', act => 0 }, - beamPaddingTop => { comp => '\d+', act => 0 }, - beamWidth => { comp => '([2-9][0-9]|100)', act => 0 }, - energyUnit => { comp => '(Wh|kWh)', act => 0 }, - headerDetail => { comp => '.*', act => 1 }, - hourCount => { comp => '([4-9]|1[0-9]|2[0-4])', act => 0 }, - hourStyle => { comp => ':(0{1,2})', act => 0 }, - layoutType => { comp => '(single|double|diff)', act => 0 }, - scaleMode => { comp => '(?:[1-3]:(?:log|lin))(?:,(?:[1-3]:(?:log|lin)))*', act => 0 }, - showDiff => { comp => '(no|top|bottom)', act => 0 }, - spaceSize => { comp => '\d+', act => 0 }, + beamPaddingBottom => { comp => '\d+', act => 0 }, + beamPaddingTop => { comp => '\d+', act => 0 }, + beamWidth => { comp => '([2-9][0-9]|100)', act => 0 }, + energyUnit => { comp => '(Wh|kWh)', act => 0 }, + headerDetail => { comp => '.*', act => 1 }, + hourCount => { comp => '([4-9]|1[0-9]|2[0-4])', act => 0 }, + hourStyle => { comp => ':(0{1,2})', act => 0 }, + layoutType => { comp => '(single|double|diff)', act => 0 }, + scaleMode => { comp => '(?:[1-3]:(?:log|lin))(?:,(?:[1-3]:(?:log|lin)))*', act => 0 }, + #showDiff => { comp => '(no|top|bottom)', act => 0 }, + showDiff => { comp => '(?:[1-3]:(?:top|bottom))(?:,(?:[1-3]:(?:top|bottom)))*', act => 0 }, + spaceSize => { comp => '\d+', act => 0 }, }; my ($a, $h) = parseParams ($aVal); @@ -6952,7 +6956,11 @@ sub _attrInverterDev { ## no critic "not used" asynchron => { comp => '(0|1)', act => 0 }, }; - if ($paref->{cmd} eq 'set') { + if ($paref->{cmd} eq 'set') { + if ($aVal =~ /strings=/xs && $aVal !~ /strings=(?!.*(\s,|,\s)).*$/xs) { + return "The key 'string' is not specified correctly. Please refer to the command reference."; + } + my ($err, $indev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); @@ -10305,9 +10313,9 @@ sub _transferAPIRadiationValues { $sunaz = NexthoursVal ($name, $nhtstr, 'sunaz', 0); } - $paref->{sabin} = sunalt2bin ($sunalt); - my $pvapifc = __calcPVestimates ($paref); # API Wert ermitteln - my ($msg, $pvaifc) = aiGetResult ($paref); # KI Entscheidungen abfragen + $paref->{sabin} = sunalt2bin ($sunalt); + my ($pvapifc, $pvapifcraw) = __calcPVestimates ($paref); # API Wert mit Korrekturfaktor und ohne KF ermitteln + my ($msg, $pvaifc) = aiGetResult ($paref); # KI Entscheidungen abfragen delete $paref->{fd}; delete $paref->{fh1}; @@ -10365,18 +10373,22 @@ sub _transferAPIRadiationValues { debugLog ($paref, 'aiData', "use PV from API (no AI or AI result tolerance overflow) -> hod: $hod, Rad1h: ".(defined $rad1h ? $rad1h : '-').", pvfc: $pvfc Wh"); } - $data{$name}{nexthours}{$nhtstr}{pvapifc} = $pvapifc; # durch API gelieferte PV Forecast - $data{$name}{nexthours}{$nhtstr}{pvfc} = $pvfc; # resultierende PV Forecast zuweisen + $data{$name}{nexthours}{$nhtstr}{pvapifc} = $pvapifc; # durch API gelieferte PV Forecast mit Korrekturfaktor + $data{$name}{nexthours}{$nhtstr}{pvapifcraw} = $pvapifcraw; # durch API gelieferte PV Forecast Raw + $data{$name}{nexthours}{$nhtstr}{pvfc} = $pvfc; # resultierende PV Forecast zuweisen + my $hh1 = sprintf "%02d", $fh1; + if ($num < 23 && $fh < 24) { # Ringspeicher PV forecast Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 - $data{$name}{circular}{sprintf "%02d",$fh1}{pvapifc} = NexthoursVal ($name, $nhtstr, 'pvapifc', undef); - $data{$name}{circular}{sprintf "%02d",$fh1}{pvfc} = $pvfc; - $data{$name}{circular}{sprintf "%02d",$fh1}{pvaifc} = NexthoursVal ($name, $nhtstr, 'pvaifc', undef); - $data{$name}{circular}{sprintf "%02d",$fh1}{aihit} = NexthoursVal ($name, $nhtstr, 'aihit', 0); + $data{$name}{circular}{$hh1}{pvapifc} = NexthoursVal ($name, $nhtstr, 'pvapifc', undef); + $data{$name}{circular}{$hh1}{pvapifcraw} = NexthoursVal ($name, $nhtstr, 'pvapifcraw', undef); + $data{$name}{circular}{$hh1}{pvaifc} = NexthoursVal ($name, $nhtstr, 'pvaifc', undef); + $data{$name}{circular}{$hh1}{aihit} = NexthoursVal ($name, $nhtstr, 'aihit', 0); + $data{$name}{circular}{$hh1}{pvfc} = $pvfc; } if ($fd == 0 && int $pvfc > 0) { # Vorhersagedaten des aktuellen Tages zum manuellen Vergleich in Reading speichern - storeReading ('Today_Hour'.sprintf ("%02d",$fh1).'_PVforecast', "$pvfc Wh"); + storeReading ('Today_Hour'.$hh1.'_PVforecast', "$pvfc Wh"); } if ($fd == 0 && $fh1) { @@ -10385,7 +10397,7 @@ sub _transferAPIRadiationValues { } } - storeReading ('.lastupdateForecastValues', $t); # Statusreading letzter update + storeReading ('.lastupdateForecastValues', $t); # Statusreading letzter update return; } @@ -10463,8 +10475,9 @@ sub __calcPVestimates { delete $paref->{wcc}; my ($lh,$sq,$peakloss, $modtemp); - my $pvsum = 0; - my $peaksum = 0; + my $pvsum = 0; + my $peaksum = 0; + my $pvsumraw = 0; my %sum; for my $string (sort keys %{$data{$name}{strings}}) { @@ -10492,6 +10505,7 @@ sub __calcPVestimates { if ($istrings eq 'all' || grep /^$string$/, (split ',', $istrings)) { $sum{$in}{pvinvsum} += $pv; + $sum{$in}{pvrawsum} += $pvest; # PV Prognose ohne Faktorenanwendung $sum{$in}{string} = defined $sum{$in}{string} ? $sum{$in}{string}.','.$string : $string; } } @@ -10524,6 +10538,11 @@ sub __calcPVestimates { for my $ins (keys %sum) { my $cap = InverterVal ($name, $ins, 'invertercap', 0); # Max. Leistung des Inverters my $pvinvsum = $sum{$ins}{pvinvsum}; + my $pvrawsum = $sum{$ins}{pvrawsum}; + + if ($pvrawsum > $cap) { + $pvrawsum = $cap; + } if ($pvinvsum > $cap) { $pvinvsum = $cap; # betreffende Strings auf WR Kapazität begrenzen @@ -10531,12 +10550,15 @@ sub __calcPVestimates { debugLog ($paref, "radiationProcess", "String(s) ".$sum{$ins}{string}." in total limited to $cap Wh due to inverter $ins capacity"); } - $pvsum += $pvinvsum; + $pvsum += $pvinvsum; + $pvsumraw += $pvrawsum; # PV Prognose ohne Faktorenanwendung } $data{$name}{current}{allstringspeak} = $peaksum; # temperaturbedingte Korrektur der installierten Peakleistung in W $pvsum = $peaksum if($peaksum && $pvsum > $peaksum); # Vorhersage nicht größer als die Summe aller PV-Strings Peak $pvsum = sprintf "%.0f", $pvsum; + $pvsumraw = $peaksum if($peaksum && $pvsumraw > $peaksum); + $pvsumraw = sprintf "%.0f", $pvsumraw; if ($debug =~ /radiationProcess/xs) { $lh = { # Log-Hash zur Ausgabe @@ -10557,7 +10579,7 @@ sub __calcPVestimates { Log3 ($name, 1, "$name DEBUG> PV API estimate for $reld Hour ".sprintf ("%02d", $hod)." summary: \n$sq"); } -return $pvsum; +return ($pvsum, $pvsumraw); } ###################################################################### @@ -14092,77 +14114,6 @@ sub _calcDataEveryFullHour { return; } -################################################################ -# PV Ist/Forecast ermitteln und Korrekturfaktoren, Qualität -# in Abhängigkeit Bewölkung errechnen und speichern (komplex) -################################################################ -sub _calcCaQcomplex { - my $paref = shift; - my $name = $paref->{name}; - my $debug = $paref->{debug}; - my $acu = $paref->{acu}; - my $pvrlvd = $paref->{pvrlvd}; # PV-Wert valide 1/0 - my $h = $paref->{h}; - my $day = $paref->{day}; # aktueller Tag - my $yday = $paref->{yday}; # vorheriger Tag (falls gesetzt) - my $aihit = $paref->{aihit}; - - if (!$pvrlvd) { - debugLog ($paref, 'pvCorrectionWrite', "real PV generation is marked as invalid for hour: $h -> skip the recalculation of the complex correction factor"); - return; - } - - my $hh = sprintf "%02d", $h; - my $pvrl = CircularVal ($name, $hh, 'pvrl', 0); # real erzeugte PV Energie am Ende der vorherigen Stunde - my $pvfc = CircularVal ($name, $hh, 'pvapifc', 0); # vorhergesagte PV Energie am Ende der vorherigen Stunde - - if (!$pvrl || !$pvfc) { - return; - } - - my $chwcc = HistoryVal ($name, $day, $hh, 'wcc', 0); # Wolkenbedeckung heute & abgefragte Stunde - my $sunalt = HistoryVal ($name, $day, $hh, 'sunalt', 0); # Sonne Altitude - my $crang = cloud2bin ($chwcc); - my $sabin = sunalt2bin ($sunalt); - - ## Speicherarrays schreiben - ############################# - push @{$data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}}, $pvrl; - push @{$data{$name}{circular}{$hh}{'pvfc_'.$sabin}{"$crang"}}, $pvfc; - - removeMinMaxArray ($data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}, SPLSLIDEMAX); - removeMinMaxArray ($data{$name}{circular}{$hh}{'pvfc_'.$sabin}{"$crang"}, SPLSLIDEMAX); - - ## neuen Korrekturfaktor berechnen - #################################### - $paref->{pvrl} = $pvrl; - $paref->{pvfc} = $pvfc; - $paref->{crang} = $crang; - $paref->{sabin} = $sabin; - $paref->{calc} = 'Complex'; - - my ($oldfac, $factor, $dnum) = __calcNewFactor_migrated ($paref); # migrierte Daten verwenden - - delete $paref->{pvrl}; - delete $paref->{pvfc}; - delete $paref->{crang}; - delete $paref->{sabin}; - delete $paref->{calc}; - - $aihit = $aihit ? ' AI result used,' : ''; - - if ($acu =~ /on_complex/xs) { - if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt - storeReading ('pvCorrectionFactor_'.$hh, $factor." (automatic - old factor: $oldfac,$aihit Sun Alt range: $sabin, Cloud range: $crang, Days in range: $dnum)"); - } - else { - storeReading ('pvCorrectionFactor_'.$hh, $paref->{cpcf}." / flexmatic result $factor for Sun Alt range: $sabin,$aihit Cloud range: $crang, Days in range: $dnum"); - } - } - -return; -} - ################################################################ # PV Ist/Forecast ermitteln und Korrekturfaktoren, Qualität # ohne Nebenfaktoren errechnen und speichern (simple) @@ -14183,27 +14134,30 @@ sub _calcCaQsimple { return; } - my $hh = sprintf "%02d", $h; - my $pvrl = CircularVal ($name, $hh, 'pvrl', 0); - my $pvfc = CircularVal ($name, $hh, 'pvapifc', 0); + my $hh = sprintf "%02d", $h; + my $pvrl = CircularVal ($name, $hh, 'pvrl', 0); + my $pvapifc = CircularVal ($name, $hh, 'pvapifc', 0); # vorhergesagte PV Energie incl. Korrekturfaktoren am Ende der vorherigen Stunde + my $pvapifcraw = CircularVal ($name, $hh, 'pvapifcraw', 0); # vorhergesagte PV Energie (raw) am Ende der vorherigen Stunde - if (!$pvrl || !$pvfc) { + if (!$pvrl || !$pvapifcraw) { return; } my $sunalt = HistoryVal ($name, $day, $hh, 'sunalt', 0); # Sonne Altitude my $sabin = sunalt2bin ($sunalt); - $paref->{pvrl} = $pvrl; - $paref->{pvfc} = $pvfc; - $paref->{sabin} = $sabin; - $paref->{crang} = 'simple'; - $paref->{calc} = 'Simple'; + $paref->{pvrl} = $pvrl; + $paref->{pvapifc} = $pvapifc; + $paref->{pvapifcraw} = $pvapifcraw; + $paref->{sabin} = $sabin; + $paref->{crang} = 'simple'; + $paref->{calc} = 'Simple'; my ($oldfac, $factor, $dnum) = __calcNewFactor_migrated ($paref); # migrierte Daten verwenden delete $paref->{pvrl}; - delete $paref->{pvfc}; + delete $paref->{pvapifc}; + delete $paref->{pvapifcraw}; delete $paref->{sabin}; delete $paref->{crang}; delete $paref->{calc}; @@ -14222,6 +14176,80 @@ sub _calcCaQsimple { return; } +################################################################ +# PV Ist/Forecast ermitteln und Korrekturfaktoren, Qualität +# in Abhängigkeit Bewölkung errechnen und speichern (komplex) +################################################################ +sub _calcCaQcomplex { + my $paref = shift; + my $name = $paref->{name}; + my $debug = $paref->{debug}; + my $acu = $paref->{acu}; + my $pvrlvd = $paref->{pvrlvd}; # PV-Wert valide 1/0 + my $h = $paref->{h}; + my $day = $paref->{day}; # aktueller Tag + my $yday = $paref->{yday}; # vorheriger Tag (falls gesetzt) + my $aihit = $paref->{aihit}; + + if (!$pvrlvd) { + debugLog ($paref, 'pvCorrectionWrite', "real PV generation is marked as invalid for hour: $h -> skip the recalculation of the complex correction factor"); + return; + } + + my $hh = sprintf "%02d", $h; + my $pvrl = CircularVal ($name, $hh, 'pvrl', 0); # real erzeugte PV Energie am Ende der vorherigen Stunde + my $pvapifc = CircularVal ($name, $hh, 'pvapifc', 0); # vorhergesagte PV Energie incl. Korrekturfaktoren am Ende der vorherigen Stunde + my $pvapifcraw = CircularVal ($name, $hh, 'pvapifcraw', 0); # vorhergesagte PV Energie (raw) am Ende der vorherigen Stunde + + if (!$pvrl || !$pvapifcraw) { + return; + } + + my $chwcc = HistoryVal ($name, $day, $hh, 'wcc', 0); # Wolkenbedeckung heute & abgefragte Stunde + my $sunalt = HistoryVal ($name, $day, $hh, 'sunalt', 0); # Sonne Altitude + my $crang = cloud2bin ($chwcc); + my $sabin = sunalt2bin ($sunalt); + + ## Speicherarrays schreiben + ############################# + push @{$data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}}, $pvrl; + push @{$data{$name}{circular}{$hh}{'pvfc_'.$sabin}{"$crang"}}, $pvapifcraw; + + removeMinMaxArray ($data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}, SPLSLIDEMAX); + removeMinMaxArray ($data{$name}{circular}{$hh}{'pvfc_'.$sabin}{"$crang"}, SPLSLIDEMAX); + + ## neuen Korrekturfaktor berechnen + #################################### + $paref->{pvrl} = $pvrl; + $paref->{pvapifc} = $pvapifc; + $paref->{pvapifcraw} = $pvapifcraw; + $paref->{crang} = $crang; + $paref->{sabin} = $sabin; + $paref->{calc} = 'Complex'; + + my ($oldfac, $factor, $dnum) = __calcNewFactor_migrated ($paref); # migrierte Daten verwenden + + delete $paref->{pvrl}; + delete $paref->{pvapifc}; + delete $paref->{pvapifcraw}; + delete $paref->{crang}; + delete $paref->{sabin}; + delete $paref->{calc}; + + $aihit = $aihit ? ' AI result used,' : ''; + + if ($acu =~ /on_complex/xs) { + if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt + storeReading ('pvCorrectionFactor_'.$hh, $factor." (automatic - old factor: $oldfac,$aihit Sun Alt range: $sabin, Cloud range: $crang, Days in range: $dnum)"); + } + else { + storeReading ('pvCorrectionFactor_'.$hh, $paref->{cpcf}." / flexmatic result $factor for Sun Alt range: $sabin,$aihit Cloud range: $crang, Days in range: $dnum"); + } + } + +return; +} + ################################################################ # den Hausverbrauch der vergangenen Stunde zum con-Array # im Circular Speicher hinzufügen @@ -14257,14 +14285,15 @@ return; # den neuen Korrekturfaktur berechnen (neue Median Funktion) ################################################################ sub __calcNewFactor_migrated { - my $paref = shift; - my $name = $paref->{name}; - my $pvrl = $paref->{pvrl}; - my $pvfc = $paref->{pvfc}; - my $crang = $paref->{crang}; - my $sabin = $paref->{sabin}; - my $h = $paref->{h}; - my $calc = $paref->{calc}; + my $paref = shift; + my $name = $paref->{name}; + my $pvrl = $paref->{pvrl}; + my $pvapifc = $paref->{pvapifc}; + my $pvfcraw = $paref->{pvapifcraw}; + my $crang = $paref->{crang}; + my $sabin = $paref->{sabin}; + my $h = $paref->{h}; + my $calc = $paref->{calc}; my $hash = $defs{$name}; my ($factor, $pvcirc, $fccirc, $pvrlsum, $pvfcsum, $dnum); @@ -14283,30 +14312,30 @@ sub __calcNewFactor_migrated { if ($dnum) { # Werte in History vorhanden -> haben Prio ! $dnum++; $pvrlsum = $pvrl + $pvcirc; - $pvfcsum = $pvfc + $fccirc; + $pvfcsum = $pvfcraw + $fccirc; $pvrl = $pvrlsum / $dnum; - $pvfc = $pvfcsum / $dnum; - $factor = sprintf "%.2f", ($pvrl / $pvfc); # Faktorberechnung: reale PV / Prognose + $pvfcraw = $pvfcsum / $dnum; + $factor = sprintf "%.2f", ($pvrl / $pvfcraw); # Faktorberechnung: reale PV / Prognose } elsif ($oldfac && (!$pvcirc || !$fccirc)) { # Circular Hash liefert einen vorhandenen Korrekturfaktor aber keine gespeicherten PV-Werte $dnum = 1; - $factor = sprintf "%.2f", ($pvrl / $pvfc); + $factor = sprintf "%.2f", ($pvrl / $pvfcraw); $factor = sprintf "%.2f", ($factor + $oldfac) / 2; } else { # ganz neuer Wert $dnum = 1; - $factor = sprintf "%.2f", ($pvrl / $pvfc); + $factor = sprintf "%.2f", ($pvrl / $pvfcraw); } } else { - $pvrl = medianArray (\@{$data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}}); # neuen Median berechnen - $pvfc = medianArray (\@{$data{$name}{circular}{$hh}{'pvfc_'.$sabin}{"$crang"}}); # neuen Median berechnen + $pvrl = medianArray (\@{$data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}}); # neuen Median berechnen + $pvfcraw = medianArray (\@{$data{$name}{circular}{$hh}{'pvfc_'.$sabin}{"$crang"}}); # neuen Median berechnen $factor = 0; $dnum = scalar (@{$data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}}); - $factor = sprintf "%.2f", ($pvrl / $pvfc) if($pvrl && $pvfc); # devision by zero Forum: https://forum.fhem.de/index.php?msg=1341884 + $factor = sprintf "%.2f", ($pvrl / $pvfcraw) if($pvrl && $pvfcraw); # devision by zero Forum: https://forum.fhem.de/index.php?msg=1341884 - debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> read stored values: PVreal median: $pvrl, PVforecast median: $pvfc, days: $dnum"); + debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> read stored values: PVreal median: $pvrl, PVforecast median: $pvfcraw, days: $dnum"); } $factor = 1.00 if(1 * $factor == 0); # 0.00-Werte ignorieren (Schleifengefahr) @@ -14324,7 +14353,7 @@ sub __calcNewFactor_migrated { ## Qualität berechnen ####################### $oldfac = sprintf "%.2f", $oldfac; - my $qual = __calcFcQuality ($pvfc, $pvrl); # Qualität der Vorhersage für die vergangene Stunde + my $qual = __calcFcQuality ($pvapifc, $pvrl); # Qualität der Vorhersage für die vergangene Stunde debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> determined values - hour: $hh, Sun Altitude range: $sabin, Cloud range: $crang, old factor: $oldfac, new factor: $factor, days: $dnum"); debugLog ($paref, 'pvCorrectionWrite|saveData2Cache', "$calc Corrf -> write correction values into Circular - hour: $hh, Sun Altitude range: $sabin, Cloud range: $crang, factor: $factor, quality: $qual, days: $dnum"); @@ -14351,12 +14380,12 @@ return ($oldfac, $factor, $dnum); # Qualität der Vorhersage berechnen ################################################################ sub __calcFcQuality { - my $pvfc = shift; # PV Vorhersagewert - my $pvrl = shift; # PV reale Erzeugung + my $pvapifc = shift; # PV Vorhersagewert + my $pvrl = shift; # PV reale Erzeugung - return if(!$pvfc || !$pvrl); + return if(!$pvapifc || !$pvrl); - my $diff = $pvfc - $pvrl; + my $diff = $pvapifc - $pvrl; my $hdv = 1 - abs ($diff / $pvrl); # Abweichung der Stunde, 1 = bestmöglicher Wert $hdv = $hdv < 0 ? 0 : $hdv; @@ -14985,7 +15014,6 @@ sub entryGraphic { colorw => AttrVal ($name, 'graphicWeatherColor', WTHCOLDDEF), # Wetter Icon Farbe Tag colorwn => AttrVal ($name, 'graphicWeatherColorNight', WTHCOLNDEF), # Wetter Icon Farbe Nacht wlalias => AttrVal ($name, 'alias', $name), - show_diff => CurrentVal ($name, 'showDiff', 'no'), # zusätzliche Anzeige $di{} in allen Typen lotype => CurrentVal ($name, 'layoutType', 'double'), hourstyle => CurrentVal ($name, 'hourStyle', ''), hdrDetail => CurrentVal ($name, 'headerDetail', 'all'), # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten @@ -15079,7 +15107,8 @@ sub entryGraphic { ## Balkengrafiken ################################################################################### - my $scm = _parseScaleModes ($name); # Scale Modes auflösen + my $scm = _parseScaleModes ($name); # Scale Modes auflösen + my $sdf = _parseShowdiffModes ($name); ## Balkengrafik Ebene 1 ######################### @@ -15087,6 +15116,7 @@ sub entryGraphic { my %hfcg1; $paref->{chartlvl} = 1; # Balkengrafik Ebene 1 $paref->{scm} = $scm->{1}; # Scale Mode Level 1 + $paref->{showdiff} = $sdf->{1}; # show Diff Mode Level 1 $paref->{hfcg} = \%hfcg1; # hfcg = hash forecast graphic ## Werte aktuelle Stunde @@ -15125,6 +15155,7 @@ sub entryGraphic { $paref->{chartlvl} = 2; $paref->{scm} = $scm->{2}; # Scale Mode Level 2 + $paref->{showdiff} = $sdf->{2}; # show Diff Mode Level 2 $paref->{beam1cont} = $paref->{beam3cont}; $paref->{beam2cont} = $paref->{beam4cont}; $paref->{colorb1} = AttrVal ($name, 'graphicBeam3Color', B3COLDEF); @@ -15168,6 +15199,7 @@ sub entryGraphic { $paref->{chartlvl} = 3; $paref->{scm} = $scm->{3}; # Scale Mode Level 3 + $paref->{showdiff} = $sdf->{3}; # show Diff Mode Level 3 $paref->{beam1cont} = $paref->{beam5cont}; $paref->{beam2cont} = $paref->{beam6cont}; $paref->{colorb1} = AttrVal ($name, 'graphicBeam5Color', B5COLDEF); @@ -15366,7 +15398,7 @@ return; } ################################################################ -# Parsed den Scale Mode für jede Balkengrafik Ebene +# Parse den Scale Mode für jede Balkengrafik Ebene # z.B. scaleMode=1:log,2:lin,3:lin ################################################################ sub _parseScaleModes { @@ -15392,6 +15424,43 @@ sub _parseScaleModes { return $scm; } +################################################################ +# Parse den Diff Mode für jede Balkengrafik Ebene +# z.B. showDiff=1:top,2:bottom,3:bottom +################################################################ +sub _parseShowdiffModes { + my $name = shift; + my $sdf; + + for my $bl (1..MAXBEAMLEVEL) { # Hashref Diff Modes initial mit Standard füllen + $sdf->{"$bl"} = ''; + } + + my $mo = CurrentVal ($name, 'showDiff', ''); + + ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! 03.07. + ########################################################################################### + if ($mo) { + $mo = $mo eq 'no' ? qq{1:'',2:'',3:''} : + $mo eq 'top' ? qq{1:top,2:top,3:top} : + $mo eq 'bottom' ? qq{1:bottom,2:bottom,3:bottom} : + $mo; + } + ########################################################################################## + + if ($mo) { + my @moa = split ',', $mo; + + for my $elem (@moa) { + my ($lvl, $mode) = split ':', $elem; + $sdf->{"$lvl"} = $mode; + } + + } + +return $sdf; +} + ################################################################ # forecastGraphic Headerzeile generieren ################################################################ @@ -16747,7 +16816,7 @@ sub _beamGraphicFirstHour { $hfcg->{0}{beam1} //= 0; $hfcg->{0}{beam2} //= 0; $hfcg->{0}{diff} = sprintf "%.1f", ($hfcg->{0}{beam1} - $hfcg->{0}{beam2}); - $hfcg->{0}{diff} = sprintf "%.0f", $hfcg->{0}{diff} if(int ($hfcg->{0}{diff}) - $hfcg->{0}{diff} == 0); + $hfcg->{0}{diff} = sprintf "%.0f", $hfcg->{0}{diff} if(($hfcg->{0}{beam1} - $hfcg->{0}{beam2}) * 1 == 0); my $epc = CurrentVal ($hash, 'ePurchasePriceCcy', 0); my $efc = CurrentVal ($hash, 'eFeedInTariffCcy', 0); @@ -16957,7 +17026,7 @@ sub _beamGraphicRemainingHours { $hfcg->{$i}{beam1} //= 0; $hfcg->{$i}{beam2} //= 0; $hfcg->{$i}{diff} = sprintf "%.1f", ($hfcg->{$i}{beam1} - $hfcg->{$i}{beam2}); - $hfcg->{$i}{diff} = sprintf "%.0f", $hfcg->{$i}{diff} if(int ($hfcg->{$i}{diff}) - $hfcg->{$i}{diff} == 0); + $hfcg->{$i}{diff} = sprintf "%.0f", $hfcg->{$i}{diff} if(($hfcg->{$i}{beam1} - $hfcg->{$i}{beam2}) * 1 == 0); $maxVal = $hfcg->{$i}{beam1} if($hfcg->{$i}{beam1} > $maxVal); $maxVal = $hfcg->{$i}{beam2} if($hfcg->{$i}{beam2} > $maxVal); @@ -17049,7 +17118,7 @@ sub _beamGraphic { my $maxhours = $paref->{maxhours}; my $weather = $paref->{weather}; my $show_night = $paref->{show_night}; # alle Balken (Spalten) anzeigen ? - my $show_diff = $paref->{show_diff}; # zusätzliche Anzeige $di{} in allen Typen + my $showdiff = $paref->{showdiff}; # zusätzliche Anzeige $di{} in allen Typen my $scm = $paref->{scm}; # Scale Mode my $lotype = $paref->{lotype}; my $height = $paref->{height}; @@ -17103,7 +17172,7 @@ sub _beamGraphic { #################################### $ret .= __batteryOnBeam ($paref); - if ($show_diff eq 'top') { # Zusätzliche Zeile Ertrag - Verbrauch + if ($showdiff eq 'top') { # Zusätzliche Zeile Ertrag - Verbrauch $ret .= ""; my $ii = 0; @@ -17369,7 +17438,7 @@ sub _beamGraphic { } } - if ($show_diff eq 'bottom') { # zusätzliche diff Anzeige + if ($showdiff eq 'bottom') { # zusätzliche diff Anzeige $val = normBeamWidth ($paref, 'diff', $i, 'beam1'); if ($val ne ' ') { # negative Zahlen in Fettschrift, 0 aber ohne + @@ -20631,24 +20700,25 @@ sub _listDataPoolCircular { for my $idx (sort keys %{$h}) { next if($par && $idx ne $par); - my $pvrl = CircularVal ($hash, $idx, 'pvrl', '-'); - my $pvfc = CircularVal ($hash, $idx, 'pvfc', '-'); - my $pvrlsum = CircularVal ($hash, $idx, 'pvrlsum', '-'); - my $pvfcsum = CircularVal ($hash, $idx, 'pvfcsum', '-'); - my $dnumsum = CircularVal ($hash, $idx, 'dnumsum', '-'); - my $pvaifc = CircularVal ($hash, $idx, 'pvaifc', '-'); - my $pvapifc = CircularVal ($hash, $idx, 'pvapifc', '-'); - my $aihit = CircularVal ($hash, $idx, 'aihit', '-'); - my $confc = CircularVal ($hash, $idx, 'confc', '-'); - my $gcons = CircularVal ($hash, $idx, 'gcons', '-'); - my $gfeedin = CircularVal ($hash, $idx, 'gfeedin', '-'); - my $wid = CircularVal ($hash, $idx, 'weatherid', '-'); - my $wtxt = CircularVal ($hash, $idx, 'weathertxt', '-'); - my $wccv = CircularVal ($hash, $idx, 'wcc', '-'); - my $rr1c = CircularVal ($hash, $idx, 'rr1c', '-'); - my $temp = CircularVal ($hash, $idx, 'temp', '-'); - my $pvcorrf = CircularVal ($hash, $idx, 'pvcorrf', '-'); - my $quality = CircularVal ($hash, $idx, 'quality', '-'); + my $pvrl = CircularVal ($hash, $idx, 'pvrl', '-'); + my $pvfc = CircularVal ($hash, $idx, 'pvfc', '-'); + my $pvrlsum = CircularVal ($hash, $idx, 'pvrlsum', '-'); + my $pvfcsum = CircularVal ($hash, $idx, 'pvfcsum', '-'); + my $dnumsum = CircularVal ($hash, $idx, 'dnumsum', '-'); + my $pvaifc = CircularVal ($hash, $idx, 'pvaifc', '-'); + my $pvapifc = CircularVal ($hash, $idx, 'pvapifc', '-'); # PV Forecast der API incl. angewendeten Korrekturfaktor + my $pvapifcraw = CircularVal ($hash, $idx, 'pvapifcraw', '-'); # PV Forecast der API Raw + my $aihit = CircularVal ($hash, $idx, 'aihit', '-'); + my $confc = CircularVal ($hash, $idx, 'confc', '-'); + my $gcons = CircularVal ($hash, $idx, 'gcons', '-'); + my $gfeedin = CircularVal ($hash, $idx, 'gfeedin', '-'); + my $wid = CircularVal ($hash, $idx, 'weatherid', '-'); + my $wtxt = CircularVal ($hash, $idx, 'weathertxt', '-'); + my $wccv = CircularVal ($hash, $idx, 'wcc', '-'); + my $rr1c = CircularVal ($hash, $idx, 'rr1c', '-'); + my $temp = CircularVal ($hash, $idx, 'temp', '-'); + my $pvcorrf = CircularVal ($hash, $idx, 'pvcorrf', '-'); + my $quality = CircularVal ($hash, $idx, 'quality', '-'); my $pvcf = _ldchash2val ( {pool => $h, idx => $idx, key => 'pvcorrf', cval => $pvcorrf} ); my $cfq = _ldchash2val ( {pool => $h, idx => $idx, key => 'quality', cval => $quality} ); @@ -20720,7 +20790,7 @@ sub _listDataPoolCircular { $gconsall .= _ldchash2val ( { pool => $h, idx => $idx, key => $gcoa, cval => $gcaref } ); } - $sq .= $idx." => pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, pvrl: $pvrl"; + $sq .= $idx." => pvapifcraw: $pvapifcraw, pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, pvrl: $pvrl"; $sq .= "\n $bin"; $sq .= "\n $bout"; $sq .= "\n confc: $confc, gcons: $gcons, gfeedin: $gfeedin, wcc: $wccv, rr1c: $rr1c"; @@ -20809,29 +20879,30 @@ sub _listDataPoolNextHours { } for my $idx (sort keys %{$h}) { - my $nhts = NexthoursVal ($name, $idx, 'starttime', '-'); - my $day = NexthoursVal ($name, $idx, 'day', '-'); - my $hod = NexthoursVal ($name, $idx, 'hourofday', '-'); - my $today = NexthoursVal ($name, $idx, 'today', '-'); - my $pvfc = NexthoursVal ($name, $idx, 'pvfc', '-'); - my $pvapifc = NexthoursVal ($name, $idx, 'pvapifc', '-'); # PV Forecast der API - my $pvaifc = NexthoursVal ($name, $idx, 'pvaifc', '-'); # PV Forecast der KI - my $aihit = NexthoursVal ($name, $idx, 'aihit', '-'); # KI ForeCast Treffer Status - my $wid = NexthoursVal ($name, $idx, 'weatherid', '-'); - my $wcc = NexthoursVal ($name, $idx, 'wcc', '-'); - my $crang = NexthoursVal ($name, $idx, 'cloudrange', '-'); - my $rr1c = NexthoursVal ($name, $idx, 'rr1c', '-'); - my $rrange = NexthoursVal ($name, $idx, 'rainrange', '-'); - my $rad1h = NexthoursVal ($name, $idx, 'rad1h', '-'); - my $pvcorrf = NexthoursVal ($name, $idx, 'pvcorrf', '-'); - my $temp = NexthoursVal ($name, $idx, 'temp', '-'); - my $confc = NexthoursVal ($name, $idx, 'confc', '-'); - my $confcex = NexthoursVal ($name, $idx, 'confcEx', '-'); - my $don = NexthoursVal ($name, $idx, 'DoN', '-'); - my $sunaz = NexthoursVal ($name, $idx, 'sunaz', '-'); - my $sunalt = NexthoursVal ($name, $idx, 'sunalt', '-'); - my $socprgs = NexthoursVal ($name, $idx, 'socprogwhsum', '-'); - my $dinrang = NexthoursVal ($name, $idx, 'DaysInRange', '-'); + my $nhts = NexthoursVal ($name, $idx, 'starttime', '-'); + my $day = NexthoursVal ($name, $idx, 'day', '-'); + my $hod = NexthoursVal ($name, $idx, 'hourofday', '-'); + my $today = NexthoursVal ($name, $idx, 'today', '-'); + my $pvfc = NexthoursVal ($name, $idx, 'pvfc', '-'); + my $pvapifc = NexthoursVal ($name, $idx, 'pvapifc', '-'); # PV Forecast der API incl. angewendeten Korrekturfaktor + my $pvapifcraw = NexthoursVal ($name, $idx, 'pvapifcraw', '-'); # PV Forecast der API Raw + my $pvaifc = NexthoursVal ($name, $idx, 'pvaifc', '-'); # PV Forecast der KI + my $aihit = NexthoursVal ($name, $idx, 'aihit', '-'); # KI ForeCast Treffer Status + my $wid = NexthoursVal ($name, $idx, 'weatherid', '-'); + my $wcc = NexthoursVal ($name, $idx, 'wcc', '-'); + my $crang = NexthoursVal ($name, $idx, 'cloudrange', '-'); + my $rr1c = NexthoursVal ($name, $idx, 'rr1c', '-'); + my $rrange = NexthoursVal ($name, $idx, 'rainrange', '-'); + my $rad1h = NexthoursVal ($name, $idx, 'rad1h', '-'); + my $pvcorrf = NexthoursVal ($name, $idx, 'pvcorrf', '-'); + my $temp = NexthoursVal ($name, $idx, 'temp', '-'); + my $confc = NexthoursVal ($name, $idx, 'confc', '-'); + my $confcex = NexthoursVal ($name, $idx, 'confcEx', '-'); + my $don = NexthoursVal ($name, $idx, 'DoN', '-'); + my $sunaz = NexthoursVal ($name, $idx, 'sunaz', '-'); + my $sunalt = NexthoursVal ($name, $idx, 'sunalt', '-'); + my $socprgs = NexthoursVal ($name, $idx, 'socprogwhsum', '-'); + my $dinrang = NexthoursVal ($name, $idx, 'DaysInRange', '-'); my ($rcdbat, $socs, $lcintime); for my $bn (1..MAXBATTERIES) { # alle Batterien @@ -20851,7 +20922,7 @@ sub _listDataPoolNextHours { $sq .= $idx." => "; $sq .= "starttime: $nhts, day: $day, hourofday: $hod, today: $today"; $sq .= "\n "; - $sq .= "pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit"; + $sq .= "pvapifcraw: $pvapifcraw, pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit"; $sq .= "\n "; $sq .= "confc: $confc, confcEx: $confcex, weatherid: $wid, wcc: $wcc, rr1c: $rr1c, temp=$temp"; $sq .= "\n "; @@ -25633,6 +25704,7 @@ to ensure that the system configuration is correct. DaysInRange previously recorded days with comparable sun position and clouds at this time DoN sunrise and sunset status (0 - night, 1 - day) hourofday current hour of the day + pvapifcraw expected PV generation (Wh) of the used API (raw) pvapifc expected PV generation (Wh) of the used API incl. a possible correction pvaifc expected PV generation of the AI (Wh) pvfc PV generation forecast used (Wh) @@ -25752,7 +25824,8 @@ to ensure that the system configuration is correct. initdaybatouttotXX initial value of the total energy drawn from the battery XX at the beginning of the current day. (Wh) lastTsMaxSocRchdXX Timestamp of last achievement of battery XX SoC >= maxSoC (default 95%) nextTsMaxSocChgeXX Timestamp by which the battery XX should reach maxSoC at least once - pvapifc expected PV generation (Wh) of the API used + pvapifcraw expected PV generation (Wh) of the API used (raw) + pvapifc expected PV generation (Wh) of the API used incl. correction factor applied pvaifc PV forecast (Wh) of the AI for the next 24h from the current hour of the day pvfc PV forecast used for the next 24h from the current hour of the day pvfc_XX Array of predicted PV generation values depending on a certain degree of cloud cover (XX = altitude of the sun) @@ -26698,9 +26771,10 @@ to ensure that the system configuration is correct. <Level>:log - logarithmic scaling showDiff Additional numerical display of the difference ‘<primary bar content> - <secondary bar content>’. - no - no difference display (default) - top - display above the bars - bottom - display below the bars + The specification for each level consists of the level number (1..X), a ‘:’ followed by the position ‘top’ or ‘bottom’. + The strings for each level are separated by commas (see example). + <Level>:top - display above the bars + <Level>:bottom - display below the bars spaceSize Defines how much space in px is kept free above or below the bar (for display type layoutType=diff) to display the values. For styles with large fonts, the default value may be too small or a bar may slide over the baseline. @@ -26712,7 +26786,7 @@ to ensure that the system configuration is correct. @@ -28286,6 +28360,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. DaysInRange bisher aufgezeichnete Tage mit vergleichbaren Sonnenstand und Bewölkungen zu dieser Zeit DoN Sonnenauf- und untergangsstatus (0 - Nacht, 1 - Tag) hourofday laufende Stunde des Tages + pvapifcraw erwartete PV Erzeugung (Wh) der verwendeten API (raw) pvapifc erwartete PV Erzeugung (Wh) der verwendeten API inkl. einer eventuellen Korrektur pvaifc erwartete PV Erzeugung der KI (Wh) pvfc verwendete PV Erzeugungsprognose (Wh) @@ -28406,7 +28481,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. initdaybatouttotXX initialer Wert der total aus der Batterie XX entnommenen Energie zu Beginn des aktuellen Tages (Wh) lastTsMaxSocRchdXX Timestamp des letzten Erreichens von Batterie XX SoC >= maxSoC (default 95%) nextTsMaxSocChgeXX Timestamp bis zu dem die Batterie XX mindestens einmal maxSoC erreichen soll - pvapifc erwartete PV Erzeugung (Wh) der verwendeten API + pvapifcraw erwartete PV Erzeugung (Wh) der verwendeten API (raw) + pvapifc erwartete PV Erzeugung (Wh) der verwendeten API incl. angewendetem Korrekturfaktor pvaifc PV Vorhersage (Wh) der KI für die nächsten 24h ab aktueller Stunde des Tages pvfc verwendete PV Prognose für die nächsten 24h ab aktueller Stunde des Tages pvfc_XX Array der prognostizierten PV Erzeugungswerte abhängig von einem bestimmten Bewölkungsgrad (XX = Altitude der Sonne) @@ -29353,9 +29429,10 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. <Ebene>:log - logarithmische Skalierung showDiff Zusätzliche numerische Anzeige der Differenz '<primärer Balkeninhalt> - <sekundärer Balkeninhalt>'. - no - keine Differenzanzeige (default) - top - Anzeige über den Balken - bottom - Anzeige unter den Balken + Die Angabe für jede Ebene besteht aus der Ebenen-Nummer (1..X), einem ':' gefolgt von der Position 'top' oder 'bottom'. + Die Strings für jede Ebene werden durch Komma getrennt (siehe Beispiel). + <Ebene>:top - Anzeige über den Balken + <Ebene>:bottom - Anzeige unter den Balken spaceSize Legt fest, wieviel Platz in px über oder unter den Balken (bei Anzeigetyp layoutType=diff) zur Anzeige der Werte freigehalten wird. Bei Styles mit großen Fonts kann der default-Wert zu klein sein bzw. rutscht ein @@ -29367,7 +29444,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.