diff --git a/fhem/CHANGED b/fhem/CHANGED index 032b8476d..9d8bae59f 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: add attr ctrlWeatherDev2, ctrlWeatherDev3 - bugfix: 76_SMAInverter: Voltage L1-L2-L3 bug - change: 76_SolarForecast: first step of multi weather device merger - change: 76_SolarForecast: language support consumer info diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index a55678185..7fd9fef0a 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -157,7 +157,7 @@ BEGIN { # Versions History intern my %vNotesIntern = ( - "1.15.0" => "03.02.2024 reduce cpu utilization ", + "1.15.0" => "03.02.2024 reduce cpu utilization, add attributes ctrlWeatherDev2, ctrlWeatherDev3 ", "1.14.3" => "02.02.2024 _transferWeatherValues: first step of multi weather device merger ", "1.14.2" => "02.02.2024 fix warning, _transferAPIRadiationValues: Consider upper and lower deviation limit AI to API forecast ", "1.14.1" => "01.02.2024 language support for ___setConsumerPlanningState -> supplement, fix setting 'swoncond not met' ", @@ -300,6 +300,7 @@ my $kJtokWh = 0.00027778; my $defmaxvar = 0.5; # max. Varianz pro Tagesberechnung Autokorrekturfaktor my $definterval = 70; # Standard Abfrageintervall my $defslidenum = 3; # max. Anzahl der Arrayelemente in Schieberegistern +my $weatherDevMax = 3; # max. Anzahl Wetter Devices (Attr ctrlWeatherDevX) my $maxSoCdef = 95; # default Wert (%) auf den die Batterie maximal aufgeladen werden soll bzw. als aufgeladen gilt my $carecycledef = 20; # max. Anzahl Tage die zwischen der Batterieladung auf maxSoC liegen dürfen my $batSocChgDay = 5; # prozentuale SoC Änderung pro Tag @@ -476,6 +477,8 @@ my %hattr = ( # H ctrlConsRecommendReadings => { fn => \&_attrcreateConsRecRdgs }, ctrlStatisticReadings => { fn => \&_attrcreateStatisticRdgs }, ctrlWeatherDev1 => { fn => \&_attrWeatherDev }, + ctrlWeatherDev2 => { fn => \&_attrWeatherDev }, + ctrlWeatherDev3 => { fn => \&_attrWeatherDev }, ); my %htr = ( # Hash even/odd für
";
@@ -11548,240 +12014,6 @@ sub checkdwdattr {
return $err;
}
-################################################################
-# Korrekturen und Qualität berechnen / speichern
-# sowie AI Quellen Daten hinzufügen
-################################################################
-sub calcValueImproves {
- my $paref = shift;
- my $hash = $paref->{hash};
- my $name = $paref->{name};
- my $chour = $paref->{chour};
- my $t = $paref->{t}; # aktuelle Unix-Zeit
-
- my $idts = ReadingsTimestamp ($name, 'currentInverterDev', ''); # Definitionstimestamp des Inverterdevice
- return if(!$idts);
-
- my ($acu, $aln) = isAutoCorrUsed ($name);
-
- if ($acu) {
- $idts = timestringToTimestamp ($idts);
-
- readingsSingleUpdate ($hash, '.pvCorrectionFactor_Auto_Soll', ($aln ? $acu : $acu.' noLearning'), 0) if($acu =~ /on/xs);
-
- if ($t - $idts < 7200) {
- my $rmh = sprintf "%.1f", ((7200 - ($t - $idts)) / 3600);
- readingsSingleUpdate ($hash, 'pvCorrectionFactor_Auto', "standby (remains in standby for $rmh hours)", 0);
-
- Log3 ($name, 4, "$name - Correction usage is in standby. It starts in $rmh hours.");
-
- return;
- }
- else {
- my $acuset = ReadingsVal ($name, '.pvCorrectionFactor_Auto_Soll', 'on_simple');
- readingsSingleUpdate ($hash, 'pvCorrectionFactor_Auto', $acuset, 0);
- }
- }
- else {
- readingsSingleUpdate ($hash, '.pvCorrectionFactor_Auto_Soll', 'off', 0);
- }
-
- Log3 ($name, 4, "$name - INFO - The correction factors are now calculated and stored proactively independent of the autocorrection usage");
-
- $paref->{acu} = $acu;
- $paref->{aln} = $aln;
-
- for my $h (1..23) {
- next if(!$chour || $h > $chour);
- $paref->{h} = $h;
-
- _calcCaQcomplex ($paref); # Korrekturberechnung mit Bewölkung duchführen/speichern
- _calcCaQsimple ($paref); # einfache Korrekturberechnung duchführen/speichern
- _addHourAiRawdata ($paref); # AI Raw Data hinzufügen
-
- delete $paref->{h};
- }
-
- delete $paref->{aln};
- delete $paref->{acu};
-
-return;
-}
-
-################################################################
-# PV Ist/Forecast ermitteln und Korrekturfaktoren, Qualität
-# in Abhängigkeit Bewölkung errechnen und speichern (komplex)
-################################################################
-sub _calcCaQcomplex {
- my $paref = shift;
- my $hash = $paref->{hash};
- my $name = $paref->{name};
- my $debug = $paref->{debug};
- my $acu = $paref->{acu};
- my $aln = $paref->{aln}; # Autolearning
- my $h = $paref->{h};
-
- my $maxvar = AttrVal ($name, 'affectMaxDayVariance', $defmaxvar); # max. Korrekturvarianz
- my $sr = ReadingsVal ($name, '.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', '');
-
- if ($sr eq 'done') {
- # Log3 ($name, 1, "$name DEBUG> Complex Corrf -> factor Hour: ".sprintf("%02d",$h)." already calculated");
- return;
- }
-
- if (!$aln) {
- storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done');
- debugLog ($paref, 'pvCorrection', "Autolearning is switched off for hour: $h -> skip the recalculation of the complex correction factor");
- return;
- }
-
- debugLog ($paref, 'pvCorrection', "start calculation complex correction factor for hour: $h");
-
- my $pvre = CircularVal ($hash, sprintf("%02d",$h), 'pvrl', 0);
- my $pvfc = CircularVal ($hash, sprintf("%02d",$h), 'pvapifc', 0);
-
- if (!$pvre || !$pvfc) {
- storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done');
- return;
- }
-
- $paref->{hour} = $h;
- my ($pvhis,$fchis,$dnum,$range) = __Pv_Fc_Complex_Dnum_Hist ($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, $dnum) = __calcNewFactor ({ name => $name,
- oldfac => $oldfac,
- dnum => $dnum,
- pvre => $pvre,
- pvfc => $pvfc,
- pvhis => $pvhis,
- fchis => $fchis
- }
- );
-
- if (abs($factor - $oldfac) > $maxvar) {
- $factor = sprintf "%.2f", ($factor > $oldfac ? $oldfac + $maxvar : $oldfac - $maxvar);
- Log3 ($name, 3, "$name - new complex correction factor calculated (limited by affectMaxDayVariance): $factor (old: $oldfac) for hour: $h");
- }
- else {
- Log3 ($name, 3, "$name - new complex correction factor for hour $h calculated: $factor (old: $oldfac)");
- }
-
- $pvre = sprintf "%.0f", $pvre;
- $pvfc = sprintf "%.0f", $pvfc;
-
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> determined values - hour: $h, cloudiness range: $range, average forecast: $pvfc, average real: $pvre, old corrf: $oldfac, new corrf: $factor, days: $dnum");
-
- if (defined $range) {
- my $type = $paref->{type};
-
- my $qual = __calcFcQuality ($pvfc, $pvre); # Qualität der Vorhersage für die vergangene Stunde
-
- debugLog ($paref, 'pvCorrection|saveData2Cache', "Complex Corrf -> write range correction values into Circular: hour: $h, cloudiness range: $range, factor: $factor, quality: $qual");
-
- $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$range} = $factor; # Korrekturfaktor für Bewölkung der jeweiligen Stunde als Datenquelle eintragen
- $data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{$range} = $qual;
-
- storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done');
- }
- else {
- $range = "";
- }
-
- if ($acu =~ /on_complex/xs) {
- storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac, cloudiness range: $range, days in range: $dnum)");
- storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h).'_autocalc', 'done');
- }
-
-return;
-}
-
-################################################################
-# PV Ist/Forecast ermitteln und Korrekturfaktoren, Qualität
-# ohne Nebenfaktoren errechnen und speichern (simple)
-################################################################
-sub _calcCaQsimple {
- my $paref = shift;
- my $hash = $paref->{hash};
- my $name = $paref->{name};
- my $date = $paref->{date};
- my $acu = $paref->{acu};
- my $aln = $paref->{aln}; # Autolearning
- my $h = $paref->{h};
-
- my $maxvar = AttrVal($name, 'affectMaxDayVariance', $defmaxvar); # max. Korrekturvarianz
- my $sr = ReadingsVal ($name, '.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', '');
-
- if($sr eq "done") {
- # debugLog ($paref, 'pvCorrection', "Simple Corrf factor Hour: ".sprintf("%02d",$h)." already calculated");
- return;
- }
-
- if (!$aln) {
- storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done');
- debugLog ($paref, 'pvCorrection', "Autolearning is switched off for hour: $h -> skip the recalculation of the simple correction factor");
- return;
- }
-
- debugLog ($paref, 'pvCorrection', "start calculation simple correction factor for hour: $h");
-
- my $pvre = CircularVal ($hash, sprintf("%02d",$h), 'pvrl', 0);
- my $pvfc = CircularVal ($hash, sprintf("%02d",$h), 'pvapifc', 0);
-
- if (!$pvre || !$pvfc) {
- storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done');
- return;
- }
-
- $paref->{hour} = $h;
- my ($pvhis,$fchis,$dnum) = __Pv_Fc_Simple_Dnum_Hist ($paref); # historischen Percentilfaktor / Qualität ermitteln
-
- my ($oldfac, $oldq) = CircularAutokorrVal ($hash, sprintf("%02d",$h), 'percentile', 0);
- $oldfac = 1 if(1 * $oldfac == 0);
-
- (my $factor, $dnum) = __calcNewFactor ({ name => $name,
- oldfac => $oldfac,
- dnum => $dnum,
- pvre => $pvre,
- pvfc => $pvfc,
- pvhis => $pvhis,
- fchis => $fchis
- }
- );
-
- if (abs($factor - $oldfac) > $maxvar) {
- $factor = sprintf "%.2f", ($factor > $oldfac ? $oldfac + $maxvar : $oldfac - $maxvar);
- Log3 ($name, 3, "$name - new simple correction factor calculated (limited by affectMaxDayVariance): $factor (old: $oldfac) for hour: $h");
- }
- else {
- Log3 ($name, 3, "$name - new simple correction factor for hour $h calculated: $factor (old: $oldfac)");
- }
-
- $pvre = sprintf "%.0f", $pvre;
- $pvfc = sprintf "%.0f", $pvfc;
-
- my $qual = __calcFcQuality ($pvfc, $pvre); # Qualität der Vorhersage für die vergangene Stunde
-
- debugLog ($paref, 'pvCorrection', "Simple Corrf -> determined values - average forecast: $pvfc, average real: $pvre, old corrf: $oldfac, new corrf: $factor, days: $dnum");
- debugLog ($paref, 'pvCorrection|saveData2Cache', "Simple Corrf -> write percentile correction values into Circular - hour: $h, factor: $factor, quality: $qual");
-
- my $type = $paref->{type};
-
- $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{percentile} = $factor; # Korrekturfaktor der jeweiligen Stunde als Datenquelle eintragen
- $data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{percentile} = $qual;
-
- storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done');
-
- if ($acu =~ /on_simple/xs) {
- storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac, average days: $dnum)");
- storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h).'_autocalc', 'done');
- }
-
-return;
-}
-
################################################################
# AI Daten für die abgeschlossene Stunde hinzufügen
################################################################
@@ -11818,233 +12050,20 @@ sub _addHourAiRawdata {
return;
}
-################################################################
-# ermittle PV Vorhersage / PV Ertrag aus PV History
-# unter Berücksichtigung der maximal zu nutzenden Tage
-# und der relevanten Bewölkung
-################################################################
-sub __Pv_Fc_Complex_Dnum_Hist {
- my $paref = shift;
- my $hash = $paref->{hash};
- my $name = $paref->{name};
- my $type = $paref->{type};
- 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 $pvhh = $data{$type}{$name}{pvhist};
-
- my ($dnum , $pvrl, $pvfc) = (0,0,0);
- my ($usenhd, $calcd) = __useNumHistDays ($name); # ist Attr affectNumHistDays 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 if(!defined $idx);
-
- my $ei = $idx-1;
- $ei = $ei < 0 ? $ile : $ei;
- my @efa;
-
- for my $e (0..$calcd) {
- last if($e == $calcd || $k[$ei] == $day);
- unshift @efa, $k[$ei];
- $ei--;
- }
-
- my $chwcc = HistoryVal ($hash, $day, $hour, "wcc", undef); # Wolkenbedeckung Heute & abgefragte Stunde
-
- if(!defined $chwcc) {
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> Day $day has no cloudiness value set for hour $hour, no past averages can be calculated");
- return;
- }
-
- my $range = cloud2bin ($chwcc);
-
- if (scalar(@efa)) {
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> Raw Days ($calcd) for average check: ".join " ",@efa);
- }
- else {
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> Day $day has index $idx. Use only current day for average calc");
- return (undef,undef,undef,$range);
- }
-
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> cloudiness range of day/hour $day/$hour is: $range");
-
- for my $dayfa (@efa) {
- my $histwcc = HistoryVal ($hash, $dayfa, $hour, "wcc", undef); # historische Wolkenbedeckung
-
- if(!defined $histwcc) {
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> Day $dayfa has no cloudiness value set for hour $hour, this history dataset is ignored.");
- next;
- }
-
- $histwcc = cloud2bin ($histwcc); # V 0.50.1
-
- if($range == $histwcc) {
- $pvrl += HistoryVal ($hash, $dayfa, $hour, 'pvrl', 0);
- $pvfc += HistoryVal ($hash, $dayfa, $hour, 'pvfc', 0);
- $dnum++;
-
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> historical Day/hour $dayfa/$hour included - cloudiness range: $range");
-
- last if( $dnum == $calcd);
- }
- else {
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> cloudiness range different: $range/$histwcc (current/historical) -> ignore stored Day:$dayfa, hour:$hour");
- }
- }
-
- if(!$dnum) {
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> all cloudiness ranges were different/not set -> no historical averages calculated");
- return (undef,undef,undef,$range);
- }
-
- my $pvhis = sprintf "%.2f", $pvrl;
- my $fchis = sprintf "%.2f", $pvfc;
-
- debugLog ($paref, 'pvCorrection', "Complex Corrf -> Summary - cloudiness range: $range, days: $dnum, pvHist:$pvhis, fcHist:$fchis");
-
-return ($pvhis,$fchis,$dnum,$range);
-}
-
-################################################################
-# ermittle PV Vorhersage / PV Ertrag aus PV History
-# unter Berücksichtigung der maximal zu nutzenden Tage
-# OHNE Bewölkung
-################################################################
-sub __Pv_Fc_Simple_Dnum_Hist {
- my $paref = shift;
- my $hash = $paref->{hash};
- my $name = $paref->{name};
- my $type = $paref->{type};
- 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 $pvhh = $data{$type}{$name}{pvhist};
-
- my ($dnum , $pvrl, $pvfc) = (0,0,0);
- my ($usenhd, $calcd) = __useNumHistDays ($name); # ist Attr affectNumHistDays 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 ($pvrl, $pvfc, $dnum) if(!defined $idx);
-
- my $ei = $idx-1;
- $ei = $ei < 0 ? $ile : $ei;
-
- my @efa;
-
- for my $e (0..$calcd) { # old: $calcmaxd
- last if($e == $calcd || $k[$ei] == $day); # old: $calcmaxd
- unshift @efa, $k[$ei];
- $ei--;
- }
-
- if(scalar(@efa)) {
- debugLog ($paref, "pvCorrection", "Simple Corrf -> Raw Days ($calcd) for average check: ".join " ",@efa);
- }
- else {
- debugLog ($paref, "pvCorrection", "Simple Corrf -> Day $day has index $idx. Use only current day for average calc");
- return ($pvrl, $pvfc, $dnum);
- }
-
- for my $dayfa (@efa) {
- $pvrl += HistoryVal ($hash, $dayfa, $hour, 'pvrl', 0);
- $pvfc += HistoryVal ($hash, $dayfa, $hour, 'pvfc', 0);
- $dnum++;
-
- debugLog ($paref, "pvCorrection", "Simple Corrf -> historical Day/hour $dayfa/$hour included -> PVreal: $pvrl, PVforecast: $pvfc");
- last if($dnum == $calcd);
- }
-
- $dnum = 0 if(!$pvrl && !$pvfc); # es gab keine gespeicherten Werte in pvHistory
-
- if(!$dnum) {
- Log3 ($name, 5, "$name - PV History -> no historical PV data forecast and real found");
- return ($pvrl, $pvfc, $dnum);
- }
-
- my $pvhis = sprintf "%.2f", $pvrl;
- my $fchis = sprintf "%.2f", $pvfc;
-
-return ($pvhis, $fchis, $dnum);
-}
-
-################################################################
-# den neuen Korrekturfaktur berechnen
-################################################################
-sub __calcNewFactor {
- my $paref = shift;
-
- my $name = $paref->{name};
- my $oldfac = $paref->{oldfac};
- my $dnum = $paref->{dnum};
- my $pvre = $paref->{pvre};
- my $pvfc = $paref->{pvfc};
- my $pvhis = $paref->{pvhis};
- my $fchis = $paref->{fchis};
-
- my $factor;
- my ($usenhd) = __useNumHistDays ($name); # ist Attr affectNumHistDays gesetzt ?
-
- if ($dnum) { # Werte in History vorhanden -> haben Prio !
- $dnum++;
- $pvre = ($pvre + $pvhis) / $dnum; # Ertrag aktuelle Stunde berücksichtigen
- $pvfc = ($pvfc + $fchis) / $dnum; # Vorhersage aktuelle Stunde berücksichtigen
- $factor = sprintf "%.2f", ($pvre / $pvfc); # Faktorberechnung: reale PV / Prognose
- }
- elsif ($oldfac && !$usenhd) { # keine Werte in History vorhanden, aber in CircularVal && keine Beschränkung durch Attr affectNumHistDays
- $dnum = 1;
- $factor = sprintf "%.2f", ($pvre / $pvfc);
- $factor = sprintf "%.2f", ($factor + $oldfac) / 2;
- }
- else { # ganz neuer Wert
- $dnum = 1;
- $factor = sprintf "%.2f", ($pvre / $pvfc);
- }
-
- $factor = 1.00 if(1 * $factor == 0); # 0.00-Werte ignorieren (Schleifengefahr)
-
-return ($factor, $dnum);
-}
-
-################################################################
-# Ist Attribut 'affectNumHistDays' gesetzt ?
-# $usenhd: 1 - ja, 0 - nein
-# $nhd : Anzahl der zu verwendenden HistDays
-################################################################
-sub __useNumHistDays {
- my $name = shift;
-
- my $usenhd = 0;
- my $nhd = AttrVal($name, 'affectNumHistDays', $calcmaxd+1);
-
- if($nhd == $calcmaxd+1) {
- $nhd = $calcmaxd;
- }
- else {
- $usenhd = 1;
- }
-
-return ($usenhd, $nhd);
-}
-
################################################################
# Qualität der Vorhersage berechnen
################################################################
sub __calcFcQuality {
my $pvfc = shift; # PV Vorhersagewert
- my $pvre = shift; # PV reale Erzeugung
+ my $pvrl = shift; # PV reale Erzeugung
- return if(!$pvfc || !$pvre);
+ return if(!$pvfc || !$pvrl);
+
+ $pvrl = sprintf "%.0f", $pvrl;
+ $pvfc = sprintf "%.0f", $pvfc;
- my $diff = $pvfc - $pvre;
- my $hdv = 1 - abs ($diff / $pvre); # Abweichung der Stunde, 1 = bestmöglicher Wert
+ my $diff = $pvfc - $pvrl;
+ my $hdv = 1 - abs ($diff / $pvrl); # Abweichung der Stunde, 1 = bestmöglicher Wert
$hdv = $hdv < 0 ? 0 : $hdv;
$hdv = sprintf "%.2f", $hdv;
@@ -12216,7 +12235,7 @@ return;
################################################################
# KI trainieren
################################################################
-sub aiTrain { ## no critic "not used"
+sub aiTrain { ## no critic "not used"
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
@@ -12259,7 +12278,7 @@ sub aiTrain { ## no critic "not used"
$err = writeCacheToFile ($hash, 'aitrained', $aitrained.$name);
if (!$err) {
- debugLog ($paref, 'aiData', qq{AI trained: }.Dumper $data{$type}{$name}{aidectree}{aitrained});
+ debugLog ($paref, 'aiData', qq{AI trained number of entities: }. scalar keys %{$data{$type}{$name}{aidectree}{aitrained}});
debugLog ($paref, 'aiProcess', qq{AI trained and saved data into file: }.$aitrained.$name);
debugLog ($paref, 'aiProcess', qq{Training instances and their associated information where purged from the AI object});
$data{$type}{$name}{current}{aitrainstate} = 'ok';
@@ -13040,15 +13059,19 @@ sub listDataPool {
if (!keys %{$h}) {
return qq{Circular cache is empty.};
}
+
for my $idx (sort keys %{$h}) {
- my $pvfc = CircularVal ($hash, $idx, "pvfc", '-');
- my $pvaifc = CircularVal ($hash, $idx, "pvaifc", '-');
- my $pvapifc = CircularVal ($hash, $idx, "pvapifc", '-');
- my $aihit = CircularVal ($hash, $idx, "aihit", '-');
my $pvrl = CircularVal ($hash, $idx, 'pvrl', '-');
- my $confc = CircularVal ($hash, $idx, "confc", '-');
- my $gcons = CircularVal ($hash, $idx, "gcons", '-');
- my $gfeedin = CircularVal ($hash, $idx, "gfeedin", '-');
+ 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", '-');
@@ -13072,60 +13095,24 @@ sub listDataPool {
my $idbotot = CircularVal ($hash, $idx, "initdaybatouttot", '-');
my $botot = CircularVal ($hash, $idx, "batouttot", '-');
my $rtaitr = CircularVal ($hash, $idx, "runTimeTrainAI", '-');
-
- no warnings 'numeric';
-
- my $pvcf = qq{};
- if(ref $pvcorrf eq "HASH") {
- for my $f (sort {$a<=>$b} keys %{$h->{$idx}{pvcorrf}}) {
- next if($f eq 'percentile');
- $pvcf .= " " if($pvcf);
- $pvcf .= "$f=".$h->{$idx}{pvcorrf}{$f};
- my $ct = ($pvcf =~ tr/=// // 0) / 10;
- $pvcf .= "\n " if($ct =~ /^([1-9])?$/);
- }
-
- if (defined $h->{$idx}{pvcorrf}{'percentile'}) {
- $pvcf .= "\n " if($pvcf && $pvcf !~ /\n\s+$/xs);
- $pvcf .= " " if($pvcf);
- $pvcf .= "percentile=".$h->{$idx}{pvcorrf}{'percentile'};
- }
- }
- else {
- $pvcf = $pvcorrf;
- }
-
- my $cfq = qq{};
- if(ref $quality eq "HASH") {
- for my $q (sort {$a<=>$b} keys %{$h->{$idx}{quality}}) {
- next if($q eq 'percentile');
- $cfq .= " " if($cfq);
- $cfq .= "$q=".$h->{$idx}{quality}{$q};
- $cfq .= "\n " if($q eq 'percentile');
- my $ct1 = ($cfq =~ tr/=// // 0) / 10;
- $cfq .= "\n " if($ct1 =~ /^([1-9])?$/);
- }
-
- if (defined $h->{$idx}{quality}{'percentile'}) {
- $cfq .= "\n " if($cfq && $cfq !~ /\n\s+$/xs);
- $cfq .= " " if($cfq);
- $cfq .= "percentile=".$h->{$idx}{quality}{'percentile'};
- }
- }
- else {
- $cfq = $quality;
- }
-
- use warnings;
+
+ my $pvcf = _ldchash2val ( {pool => $h, idx => $idx, key => 'pvcorrf', cval => $pvcorrf} );
+ my $cfq = _ldchash2val ( {pool => $h, idx => $idx, key => 'quality', cval => $quality} );
+ my $pvrs = _ldchash2val ( {pool => $h, idx => $idx, key => 'pvrlsum', cval => $pvrlsum} );
+ my $pvfs = _ldchash2val ( {pool => $h, idx => $idx, key => 'pvfcsum', cval => $pvfcsum} );
+ my $dnus = _ldchash2val ( {pool => $h, idx => $idx, key => 'dnumsum', cval => $dnumsum} );
$sq .= "\n" if($sq);
- if($idx != 99) {
+ if ($idx != 99) {
$sq .= $idx." => pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, pvrl: $pvrl\n";
$sq .= " batin: $batin, batout: $batout, confc: $confc, gcon: $gcons, gfeedin: $gfeedin, wcc: $wccv, wrp: $wrprb\n";
$sq .= " temp: $temp, wid: $wid, wtxt: $wtxt\n";
- $sq .= " corr: $pvcf\n";
- $sq .= " quality: $cfq";
+ $sq .= " pvcorrf: $pvcf\n";
+ $sq .= " quality: $cfq\n";
+ $sq .= " pvrlsum: $pvrs\n";
+ $sq .= " pvfcsum: $pvfs\n";
+ $sq .= " dnumsum: $dnus";
}
else {
$sq .= $idx." => tdayDvtn: $tdayDvtn, ydayDvtn: $ydayDvtn\n";
@@ -13144,6 +13131,7 @@ sub listDataPool {
if (!keys %{$h}) {
return qq{NextHours cache is empty.};
}
+
for my $idx (sort keys %{$h}) {
my $nhts = NexthoursVal ($hash, $idx, 'starttime', '-');
my $hod = NexthoursVal ($hash, $idx, 'hourofday', '-');
@@ -13278,6 +13266,44 @@ sub listDataPool {
return $sq;
}
+################################################################
+# Hashwert aus CircularVal in formatierten String umwandeln
+################################################################
+sub _ldchash2val {
+ my $paref = shift;
+ my $pool = $paref->{pool};
+ my $idx = $paref->{idx};
+ my $key = $paref->{key};
+ my $cval = $paref->{cval};
+
+ my $ret = qq{};
+
+ if (ref $cval eq 'HASH') {
+ no warnings 'numeric';
+
+ for my $f (sort {$a<=>$b} keys %{$pool->{$idx}{$key}}) {
+ next if($f eq 'percentile');
+ $ret .= " " if($ret);
+ $ret .= "$f=".$pool->{$idx}{$key}{$f};
+ my $ct = ($ret =~ tr/=// // 0) / 10;
+ $ret .= "\n " if($ct =~ /^([1-9])?$/);
+ }
+
+ use warnings;
+
+ if (defined $pool->{$idx}{$key}{percentile}) {
+ $ret .= "\n " if($ret && $ret !~ /\n\s+$/xs);
+ $ret .= " " if($ret);
+ $ret .= "percentile=".$pool->{$idx}{$key}{percentile};
+ }
+ }
+ else {
+ $ret = $cval;
+ }
+
+return $ret;
+}
+
################################################################
# Berechnung führende Spaces für Hashanzeige
# $str - String dessen Länge für die Anzahl Spaces
@@ -13387,24 +13413,26 @@ sub checkPlantConfig {
## Check Attribute DWD Wetterdevice
#####################################
- my $fcname = ReadingsVal ($name, 'currentWeatherDev', '');
+ for my $step (1..$weatherDevMax) {
+ my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$step, '');
- if (!$fcname || !$defs{$fcname}) {
- $result->{'DWD Weather Attributes'}{state} = $nok;
- $result->{'DWD Weather Attributes'}{result} .= qq{The DWD device "$fcname" doesn't exist. }; - $result->{'DWD Weather Attributes'}{fault} = 1; - } - else { - $result->{'DWD Weather Attributes'}{note} = qq{checked attributes of device "$fcname": }. join ' ', @dweattrmust; - $err = checkdwdattr ($name, $fcname, \@dweattrmust); - - if ($err) { - $result->{'DWD Weather Attributes'}{state} = $nok; - $result->{'DWD Weather Attributes'}{result} = $err; - $result->{'DWD Weather Attributes'}{fault} = 1; + if (!$fcname || !$defs{$fcname}) { + $result->{'DWD Weather Attributes'}{state} = $nok; + $result->{'DWD Weather Attributes'}{result} .= qq{The DWD device "$fcname" doesn't exist. }; + $result->{'DWD Weather Attributes'}{fault} = 1; } else { - $result->{'DWD Weather Attributes'}{result} = $hqtxt{fulfd}{$lang}; + $result->{'DWD Weather Attributes'}{note} .= qq{checked attributes of device "$fcname": }. join (' ', @dweattrmust). ' '; + $err = checkdwdattr ($name, $fcname, \@dweattrmust); + + if ($err) { + $result->{'DWD Weather Attributes'}{state} = $nok; + $result->{'DWD Weather Attributes'}{result} .= $err. ' '; + $result->{'DWD Weather Attributes'}{fault} = 1; + } + else { + $result->{'DWD Weather Attributes'}{result} = $hqtxt{fulfd}{$lang}; + } } } @@ -14030,9 +14058,17 @@ sub createAssociatedWith { my (@cd,@nd); my ($afc,$ara,$ain,$ame,$aba,$h); - my $fcdev = ReadingsVal($name, 'currentWeatherDev', ''); # Weather forecast Device - ($afc,$h) = parseParams ($fcdev); - $fcdev = $afc->[0] // ""; + my $fcdev1 = AttrVal ($name, 'ctrlWeatherDev1', ''); # Weather forecast Device 1 + ($afc,$h) = parseParams ($fcdev1); + $fcdev1 = $afc->[0] // ""; + + my $fcdev2 = AttrVal ($name, 'ctrlWeatherDev2', ''); # Weather forecast Device 2 + ($afc,$h) = parseParams ($fcdev2); + $fcdev2 = $afc->[0] // ""; + + my $fcdev3 = AttrVal ($name, 'ctrlWeatherDev3', ''); # Weather forecast Device 3 + ($afc,$h) = parseParams ($fcdev3); + $fcdev3 = $afc->[0] // ""; my $radev = ReadingsVal($name, 'currentRadiationAPI', ''); # Radiation forecast Device ($ara,$h) = parseParams ($radev); @@ -14064,16 +14100,23 @@ sub createAssociatedWith { @nd = @cd; - push @nd, $fcdev; - push @nd, $radev if($radev ne $fcdev && $radev !~ /SolCast-API/xs); + push @nd, $fcdev1 if($fcdev1); + push @nd, $fcdev2 if($fcdev2); + push @nd, $fcdev3 if($fcdev3); + push @nd, $radev if($radev !~ /^($fcdev1|$fcdev2|$fcdev3)/xs && $radev !~ /SolCast-API/xs); push @nd, $indev; push @nd, $medev; push @nd, $badev; - - if(@nd) { - $hash->{NOTIFYDEV} = join ",", @cd if(@cd); - readingsSingleUpdate ($hash, ".associatedWith", join(" ",@nd), 0); + + my @ndn = (); + + for my $e (@nd) { + next if($e ~~ @ndn); + push @ndn, $e; } + + $hash->{NOTIFYDEV} = join ",", @cd if(@cd); + readingsSingleUpdate ($hash, ".associatedWith", join(" ",@ndn), 0) if(@ndn); } else { InternalTimer(gettimeofday()+3, "FHEM::SolarForecast::createAssociatedWith", $hash, 0); @@ -15231,6 +15274,9 @@ return $def; # $hod: Stunde des Tages (01,02,...,24) bzw. 99 (besondere Verwendung) # $key: pvrl - realer PV Ertrag # pvfc - PV Vorhersage +# pvrlsum - Summe PV Ertrag über die gesamte Laufzeit +# pvfcsum - Summe PV Prognose über die gesamte Laufzeit +# dnumsum - Anzahl der Tage der Durchschnittsberechnung über die gesamte Laufzeit # confc - Vorhersage Hausverbrauch (Wh) # gcons - realer Netzbezug # gfeedin - reale Netzeinspeisung @@ -15304,14 +15350,14 @@ sub CircularAutokorrVal { my $pvcorrf = $def; my $quality = $def; - if(defined($data{$type}{$name}{circular}) && + if (defined($data{$type}{$name}{circular}) && defined($data{$type}{$name}{circular}{$hod}) && defined($data{$type}{$name}{circular}{$hod}{pvcorrf}) && defined($data{$type}{$name}{circular}{$hod}{pvcorrf}{$range})) { $pvcorrf = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; } - if(defined($data{$type}{$name}{circular}) && + if (defined($data{$type}{$name}{circular}) && defined($data{$type}{$name}{circular}{$hod}) && defined($data{$type}{$name}{circular}{$hod}{quality}) && defined($data{$type}{$name}{circular}{$hod}{quality}{$range})) { @@ -15321,6 +15367,58 @@ sub CircularAutokorrVal { return ($pvcorrf, $quality); } +######################################################################################################## +# Die durchschnittliche reale PV Erzeugung, PV Prognose und Tage +# einer bestimmten Bewölkungs-Range aus dem circular-Hash zurückliefern +# Usage: +# ($pvrlsum, $pvfcsum, $dnumsum) = CircularSumVal ($hash, $hod, $range, $def) +# +# $pvrlsum: Summe reale PV Erzeugung pro Bewölkungsbereich über die gesamte Laufzeit +# $pvfcsum: Summe PV Prognose pro Bewölkungsbereich über die gesamte Laufzeit +# $dnumsum: Anzahl Tage pro Bewölkungsbereich über die gesamte Laufzeit +# +# $hod: Stunde des Tages (01,02,...,24) +# $range: Range Bewölkung (1...100) oder "percentile" +# $def: Defaultwert +# +####################################################################################################### +sub CircularSumVal { + my $hash = shift; + my $hod = shift; + my $range = shift; + my $def = shift; + + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + my $pvrlsum = $def; + my $pvfcsum = $def; + my $dnumsum = $def; + + if (defined($data{$type}{$name}{circular}) && + defined($data{$type}{$name}{circular}{$hod}) && + defined($data{$type}{$name}{circular}{$hod}{pvrlsum}) && + defined($data{$type}{$name}{circular}{$hod}{pvrlsum}{$range})) { + $pvrlsum = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + } + + if (defined($data{$type}{$name}{circular}) && + defined($data{$type}{$name}{circular}{$hod}) && + defined($data{$type}{$name}{circular}{$hod}{pvfcsum}) && + defined($data{$type}{$name}{circular}{$hod}{pvfcsum}{$range})) { + $pvfcsum = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + } + + if (defined($data{$type}{$name}{circular}) && + defined($data{$type}{$name}{circular}{$hod}) && + defined($data{$type}{$name}{circular}{$hod}{dnumsum}) && + defined($data{$type}{$name}{circular}{$hod}{dnumsum}{$range})) { + $dnumsum = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + } + +return ($pvrlsum, $pvfcsum, $dnumsum); +} + ######################################################################################### # Wert des nexthours-Hash zurückliefern # Usage: @@ -15886,29 +15984,6 @@ to ensure that the system configuration is correct. -
-
- - - + + + + + Defines the device (type DWD_OpenData), which provides the required weather data (cloudiness, precipitation, + sunrise/sunset, etc.). + If no device of this type exists, the selection list is empty and a device must first be defined + (see DWD_OpenData Commandref). + If more than one ctrlWeatherDevX is specified, the average of all weather stations is determined and used + if the respective value was supplied and is numerical. + Otherwise, the data from 'ctrlWeatherDev1' is always used as the leading weather device. + At least these attributes must be set in the selected DWD_OpenData Device: + +
@@ -17960,29 +18054,6 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. -
-
- - - + + + + + Legt das Device (Typ DWD_OpenData) fest, welches die benötigten Wetterdaten (Bewölkung, Niederschlag, + Sonnenauf bzw. -untergang, usw.) liefert. + Ist noch kein Device dieses Typs vorhanden, ist die Auswahlliste leer und es muß zunächst mindestens ein Device + definiert werden (siehe DWD_OpenData Commandref). + Sind mehr als ein ctrlWeatherDevX angegeben, wird der Durchschnitt aller Wetterstationen ermittelt und verwendet + sofern der jeweilige Wert geliefert wurde und numerisch ist. + Anderenfalls werden immer die Daten von 'ctrlWeatherDev1' als führendes Wetterdevice genutzt. + Im ausgewählten DWD_OpenData Device müssen mindestens diese Attribute gesetzt sein: + +
|