diff --git a/fhem/CHANGED b/fhem/CHANGED index 37de900f5..4b57a07ed 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,8 @@ # 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: take into account the position of the sun in + addition to cloud cover when determining the + correction factor - change: 76_SolarForecast: check age of Rad1h data, use RR1c instead of R101 - feature: 70_PylonLowVoltage: print out faulty response with verbose 5 - bugfix: 88_HMCCU: fixed device detection bug diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 9f659cd7d..083de08bb 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -55,7 +55,7 @@ use FHEM::SynoModules::SMUtils qw( moduleVersion trim ); # Hilfsroutinen Modul - + use Data::Dumper; use Blocking; use Storable qw(dclone freeze thaw nstore store retrieve); @@ -158,6 +158,11 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.16.3" => "24.02.2024 store pvcorrf, quality, pvrlsum, pvfcsum, dnumsum with value . in pvCircular ". + "get pvcorrf / quality from neff in combination with sun altitude (CircularSunCloudkorrVal) ". + "delete CircularCloudkorrVal, show sun position in beamgrafic weather mouse over ". + "split pvCorrection into pvCorrectionRead and pvCorrectionWrite ". + "_checkSetupNotComplete: improve setup Wizzard for ForecastSolar-API ", "1.16.2" => "22.02.2024 minor changes, R101 -> RR1c, totalrain instead of weatherrainprob, delete wrp r101 ". "delete wrp from circular & airaw, remove rain2bin, __getDWDSolarData: change \$runh, ". "fix Illegal division by zero Forum: https://forum.fhem.de/index.php?msg=1304009 ". @@ -181,7 +186,7 @@ my %vNotesIntern = ( "1.14.1" => "01.02.2024 language support for ___setConsumerPlanningState -> supplement, fix setting 'swoncond not met' ", "1.14.0" => "31.01.2024 data maintenance, new func _addDynAttr for adding attributes at runtime ". "replace setter currentWeatherDev by attr ctrlWeatherDev1, new data with func CircularSumVal ". - "rewrite correction factor calculation with _calcCaQcomplex, _calcCaQsimple, __calcNewFactor ", + "rewrite correction factor calculation with _calcCaQcomplex, _calcCaQsimple, __calcNewFactor ", "1.13.0" => "27.01.2024 minor change of deleteOldBckpFiles, Setter writeHistory replaced by operatingMemory ". "save, backup and recover in-memory operating data ", "1.12.0" => "26.01.2024 create backup files and delete old generations of them ", @@ -339,10 +344,10 @@ my $dwdcatgpx = $root."/FHEM/FhemUtils/DWDcat_SolarForecast.gpx"; my $aitrblto = 7200; # KI Training BlockingCall Timeout my $aibcthhld = 0.2; # Schwelle der KI Trainigszeit ab der BlockingCall benutzt wird my $aistdudef = 1095; # default Haltezeit KI Raw Daten (Tage) -my $aiSpreadUpLim = 150; # obere Abweichungsgrenze (%) AI 'Spread' von API Prognose -my $aiSpreadLowLim = 50; # untere Abweichungsgrenze (%) AI 'Spread' von API Prognose -my $aiAccUpLim = 170; # obere Abweichungsgrenze (%) AI 'Accurate' von API Prognose -my $aiAccLowLim = 30; # untere Abweichungsgrenze (%) AI 'Accurate' von API Prognose +my $aiSpreadUpLim = 130; # obere Abweichungsgrenze (%) AI 'Spread' von API Prognose +my $aiSpreadLowLim = 70; # untere Abweichungsgrenze (%) AI 'Spread' von API Prognose +my $aiAccUpLim = 150; # obere Abweichungsgrenze (%) AI 'Accurate' von API Prognose +my $aiAccLowLim = 50; # untere Abweichungsgrenze (%) AI 'Accurate' von API Prognose my $calcmaxd = 30; # Anzahl Tage die zur Berechnung Vorhersagekorrektur verwendet werden my @dweattrmust = qw(TTT Neff RR1c ww SunUp SunRise SunSet); # Werte die im Attr forecastProperties des Weather-DWD_Opendata Devices mindestens gesetzt sein müssen @@ -419,7 +424,8 @@ my @dd = qw( none epiecesCalc graphic notifyHandling - pvCorrection + pvCorrectionRead + pvCorrectionWrite radiationProcess saveData2Cache ); @@ -694,8 +700,8 @@ my %htitles = ( DE => qq{Ein -> kein off-Kommando definiert!} }, natc => { EN => qq{automatic cycle:}, DE => qq{automatischer Zyklus:} }, - predtime => { EN => qq{Prediction time Weather data:}, - DE => qq{Vorhersagezeitpunkt Wetterdaten:} }, + predtime => { EN => qq{Prediction time Radiation data:}, + DE => qq{Vorhersagezeitpunkt Strahlungsdaten:} }, upd => { EN => qq{Click for update}, DE => qq{Klick für Update} }, on => { EN => qq{switched on}, @@ -706,6 +712,12 @@ my %htitles = ( DE => qq{undefiniert} }, dela => { EN => qq{delayed}, DE => qq{verzoegert} }, + azimuth => { EN => qq{Azimuth}, + DE => qq{Azimut} }, + elevatio => { EN => qq{Elevation}, + DE => qq{Höhe} }, + sunpos => { EN => qq{Sun position (decimal degrees)}, + DE => qq{Sonnenstand (Dezimalgrad)} }, conrec => { EN => qq{Current time is within the consumption planning}, DE => qq{Aktuelle Zeit liegt innerhalb der Verbrauchsplanung} }, conrecba => { EN => qq{Current time is within the consumption planning, Priority charging Battery is active}, @@ -1150,7 +1162,7 @@ sub _readCacheFile { return; } - + if ($cachename eq 'dwdcatalog') { my ($err, $data) = fileRetrieve ($file); @@ -1438,6 +1450,13 @@ sub _setcurrentRadiationAPI { ## no critic "not used" my $rmf = reqModFail(); return "You have to install the required perl module: ".$rmf if($rmf); } + + readingsSingleUpdate ($hash, "currentRadiationAPI", $prop, 1); + createAssociatedWith ($hash); + writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration File schreiben + setModel ($hash); # Model setzen + + return if(_checkSetupNotComplete ($hash)); # keine Stringkonfiguration wenn Setup noch nicht komplett if ($prop eq 'ForecastSolar-API') { my ($set, $lat, $lon) = locCoordinates(); @@ -1450,15 +1469,6 @@ sub _setcurrentRadiationAPI { ## no critic "not used" return qq{Please complete command "set $name moduleAzimuth".} if(!$dir); } - if ($prop eq 'VictronVRM-API') { - - } - - readingsSingleUpdate ($hash, "currentRadiationAPI", $prop, 1); - createAssociatedWith ($hash); - writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration File schreiben - setModel ($hash); # Model setzen - my $type = $hash->{TYPE}; $data{$type}{$name}{current}{allStringsFullfilled} = 0; # Stringkonfiguration neu prüfen lassen @@ -2098,9 +2108,9 @@ sub _setreset { ## no critic "not used" $paref->{reorg} = 1; # den Tag Stunde "99" reorganisieren $paref->{reorgday} = $dday; - + setPVhistory ($paref); - + delete $paref->{reorg}; delete $paref->{reorgday}; } @@ -2138,8 +2148,8 @@ sub _setreset { ## no critic "not used" if ($circh) { delete $data{$type}{$name}{circular}{$circh}{pvcorrf}; delete $data{$type}{$name}{circular}{$circh}{quality}; - delete $data{$type}{$name}{circular}{$circh}{pvrlsum}; - delete $data{$type}{$name}{circular}{$circh}{pvfcsum}; + delete $data{$type}{$name}{circular}{$circh}{pvrlsum}; + delete $data{$type}{$name}{circular}{$circh}{pvfcsum}; delete $data{$type}{$name}{circular}{$circh}{dnumsum}; for my $hid (keys %{$data{$type}{$name}{pvhist}}) { @@ -2153,8 +2163,8 @@ sub _setreset { ## no critic "not used" for my $hod (keys %{$data{$type}{$name}{circular}}) { delete $data{$type}{$name}{circular}{$hod}{pvcorrf}; delete $data{$type}{$name}{circular}{$hod}{quality}; - delete $data{$type}{$name}{circular}{$hod}{pvrlsum}; - delete $data{$type}{$name}{circular}{$hod}{pvfcsum}; + delete $data{$type}{$name}{circular}{$hod}{pvrlsum}; + delete $data{$type}{$name}{circular}{$hod}{pvfcsum}; delete $data{$type}{$name}{circular}{$hod}{dnumsum}; } @@ -2537,7 +2547,7 @@ sub _getRoofTopData { my $hash = $paref->{hash}; my $name = $paref->{name}; my $type = $paref->{type}; - + delete $data{$type}{$name}{current}{dwdRad1hAge}; delete $data{$type}{$name}{current}{dwdRad1hAgeTS}; @@ -3379,7 +3389,7 @@ sub __getDWDSolarData { $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{lastretrieval_timestamp} = $t; $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{todayDoneAPIrequests} += 1; - + my $fctime = ReadingsVal ($raname, 'fc_time', '-'); $data{$type}{$name}{current}{dwdRad1hAge} = $fctime; $data{$type}{$name}{current}{dwdRad1hAgeTS} = timestringToTimestamp ($fctime); @@ -3988,24 +3998,24 @@ sub _getdwdCatalog { my $arg = $paref->{arg} // 'byID'; my $name = $paref->{name}; my $type = $paref->{type}; - + my ($aa,$ha) = parseParams ($arg); - + my $sort = 'byID' ~~ @$aa ? 'byID' : 'byName' ~~ @$aa ? 'byName' : 'byID'; my $export = 'exportgpx' ~~ @$aa ? 'exportgpx' : ''; my $force = 'force' ~~ @$aa ? 'force' : ''; - + $paref->{sort} = $sort; $paref->{export} = $export; $paref->{filtid} = $ha->{id} ? $ha->{id} : ''; $paref->{filtnam} = $ha->{name} ? $ha->{name} : ''; $paref->{filtlat} = $ha->{lat} ? $ha->{lat} : ''; $paref->{filtlon} = $ha->{lon} ? $ha->{lon} : ''; - + my $msg = "The DWD Station catalog is initially loaded into SolarForecast.\n". "Please execute the command 'get $name $paref->{opt} $arg' again."; - + if ($force) { __dwdStatCatalog_Request ($paref); return 'The DWD Station Catalog is forced to loaded into SolarForecast.'; @@ -4023,11 +4033,11 @@ sub _getdwdCatalog { ); if (!scalar keys %{$data{$type}{$name}{dwdcatalog}}) { # Ladung von File nicht erfolgreich - __dwdStatCatalog_Request ($paref); + __dwdStatCatalog_Request ($paref); return $msg; - } + } } - + return __generateCatOut ($paref); return; @@ -4049,7 +4059,7 @@ sub __generateCatOut { my $filtnam = $paref->{filtnam}; my $filtlat = $paref->{filtlat}; my $filtlon = $paref->{filtlon}; - + my $filter = $filtid ? 'id:'.$filtid : ''; $filter .= ',' if($filter && $filtnam); $filter .= $filtnam ? 'name:'.$filtnam : ''; @@ -4057,7 +4067,7 @@ sub __generateCatOut { $filter .= $filtlat ? 'lat:'.$filtlat : ''; $filter .= ',' if($filter && $filtlon); $filter .= $filtlon ? 'lon:'.$filtlon : ''; - + my $select = 'sort='.$sort; if ($filter) { $select .= ' filter='; @@ -4065,70 +4075,70 @@ sub __generateCatOut { } $select .= ' ' if($export); $select .= $export; - + # Katalog Organisation (default ist 'byID) ############################################ my ($err, $isfil); my %temp; - + if ($sort eq 'byName') { for my $id (keys %{$data{$type}{$name}{dwdcatalog}}) { $paref->{id} = $id; - + ($err, $isfil) = ___isCatFiltered ($paref); return (split " at", $err)[0] if($err); next if($isfil); - + my $nid = $data{$type}{$name}{dwdcatalog}{$id}{stnam}; $temp{$nid}{stnam} = $data{$type}{$name}{dwdcatalog}{$id}{stnam}; $temp{$nid}{id} = $data{$type}{$name}{dwdcatalog}{$id}{id}; $temp{$nid}{latdec} = $data{$type}{$name}{dwdcatalog}{$id}{latdec}; # Latitude Dezimalgrad $temp{$nid}{londec} = $data{$type}{$name}{dwdcatalog}{$id}{londec}; # Longitude Dezimalgrad - $temp{$nid}{elev} = $data{$type}{$name}{dwdcatalog}{$id}{elev}; + $temp{$nid}{elev} = $data{$type}{$name}{dwdcatalog}{$id}{elev}; } } - elsif ($sort eq 'byID') { + elsif ($sort eq 'byID') { for my $id (keys %{$data{$type}{$name}{dwdcatalog}}) { $paref->{id} = $id; ($err, $isfil) = ___isCatFiltered ($paref); return (split " at", $err)[0] if($err); next if($isfil); - + $temp{$id}{stnam} = $data{$type}{$name}{dwdcatalog}{$id}{stnam}; $temp{$id}{id} = $data{$type}{$name}{dwdcatalog}{$id}{id}; $temp{$id}{latdec} = $data{$type}{$name}{dwdcatalog}{$id}{latdec}; # Latitude Dezimalgrad $temp{$id}{londec} = $data{$type}{$name}{dwdcatalog}{$id}{londec}; # Longitude Dezimalgrad - $temp{$id}{elev} = $data{$type}{$name}{dwdcatalog}{$id}{elev}; + $temp{$id}{elev} = $data{$type}{$name}{dwdcatalog}{$id}{elev}; } } - + if ($export eq 'exportgpx') { # DWD Katalog als gpx speichern my @data = (); push @data, ''; push @data, ''; - + for my $idx (sort keys %temp) { my $londec = $temp{"$idx"}{londec}; my $latdec = $temp{"$idx"}{latdec}; my $elev = $temp{"$idx"}{elev}; my $id = $temp{"$idx"}{id}; my $stnam = $temp{"$idx"}{stnam}; - + push @data, qq{}; push @data, qq{ $elev}; push @data, qq{ $stnam (ID=$id, Latitude=$latdec, Longitude=$londec)}; push @data, qq{ City}; - push @data, qq{}; + push @data, qq{}; } - + push @data, ''; - - $err = FileWrite ( {FileName => $dwdcatgpx, + + $err = FileWrite ( {FileName => $dwdcatgpx, ForceType => 'file' }, @data - ); + ); if (!$err) { debugLog ($paref, 'dwdComm', qq{DWD catalog saved as gpx content: }.$dwdcatgpx); @@ -4136,11 +4146,11 @@ sub __generateCatOut { else { Log3 ($name, 1, "$name - ERROR - $err"); return $err; - } + } } - + my $noe = scalar keys %temp; - + ## Ausgabe ############ my $out = ''; @@ -4157,8 +4167,8 @@ sub __generateCatOut { $out .= qq{ ELEVATION }; $out .= qq{}; $out .= qq{}; - - for my $key (sort keys %temp) { + + for my $key (sort keys %temp) { $out .= qq{}; $out .= qq{ $temp{"$key"}{id} }; $out .= qq{ $temp{"$key"}{stnam} }; @@ -4171,7 +4181,7 @@ sub __generateCatOut { $out .= qq{}; $out .= qq{}; - undef %temp; + undef %temp; return $out; } @@ -4183,25 +4193,25 @@ sub ___isCatFiltered { my $paref = shift; my $id = $paref->{id}; my $name = $paref->{name}; - my $type = $paref->{type}; + my $type = $paref->{type}; my $filtid = $paref->{filtid}; my $filtnam = $paref->{filtnam}; my $filtlat = $paref->{filtlat}; my $filtlon = $paref->{filtlon}; - + my $isfil = 0; eval {$isfil = 1 if($filtid && $id !~ /^$filtid$/ixs); $isfil = 1 if($filtnam && $data{$type}{$name}{dwdcatalog}{$id}{stnam} !~ /^$filtnam$/ixs); $isfil = 1 if($filtlat && $data{$type}{$name}{dwdcatalog}{$id}{latdec} !~ /^$filtlat$/ixs); - $isfil = 1 if($filtlon && $data{$type}{$name}{dwdcatalog}{$id}{londec} !~ /^$filtlon$/ixs); + $isfil = 1 if($filtlon && $data{$type}{$name}{dwdcatalog}{$id}{londec} !~ /^$filtlon$/ixs); }; - + if ($@) { - return $@; + return $@; } - + return ('', $isfil); } @@ -4214,7 +4224,7 @@ sub __dwdStatCatalog_Request { my $hash = $paref->{hash}; my $name = $paref->{name}; my $debug = $paref->{debug}; - + my $url = "https://www.dwd.de/DE/leistungen/met_verfahren_mosmix/mosmix_stationskatalog.cfg?view=nasPublication&nn=16102"; debugLog ($paref, 'dwdComm', "Download DWD Station catalog from URL: $url"); @@ -4242,9 +4252,9 @@ return; ############################################################### # Download DWD Stationskatalog Response -# Für die Stationsliste im cfg-Format gilt: +# Für die Stationsliste im cfg-Format gilt: # Die Angabe der Längen- und Breitengrade erfolgt in der Form -# Grad und Minuten, also beispielsweise wird die Angabe 53◦ 23′ +# Grad und Minuten, also beispielsweise wird die Angabe 53◦ 23′ # in Grad und Minuten hier mit Punkt als 53.23 repräsentiert. ############################################################### sub __dwdStatCatalog_Response { @@ -4266,36 +4276,36 @@ sub __dwdStatCatalog_Response { return; } - elsif ($data ne "") { + elsif ($data ne "") { my @datarr = split "\n", $data; my $type = $hash->{TYPE}; for my $s (@datarr) { $s = encode ('utf8', $s); - + my ($id, $tail) = split " ", $s, 2; - + next if($id !~ /[A-Z0-9]+$/xs || $id eq 'ID'); - + my $ri = rindex ($tail, " "); my $elev = substr ($tail, $ri + 1); # Meereshöhe $tail = trim (substr ($tail, 0, $ri)); $ri = rindex ($tail, " "); my $lon = substr ($tail, $ri + 1); # Longitude - $tail = trim (substr ($tail, 0, $ri)); + $tail = trim (substr ($tail, 0, $ri)); $ri = rindex ($tail, " "); my $lat = substr ($tail, $ri + 1); # Latitude - $tail = trim (substr ($tail, 0, $ri)); + $tail = trim (substr ($tail, 0, $ri)); + + my ($icao, $stnam) = split " ", $tail, 2; # ICAO = International Civil Aviation Organization, Stationsname - my ($icao, $stnam) = split " ", $tail, 2; # ICAO = International Civil Aviation Organization, Stationsname - my ($latg, $latm) = split /\./, $lat; # in Grad und Minuten splitten my ($long, $lonm) = split /\./, $lon; my $latdec = sprintf "%.2f", ($latg + ($latm / 60)); my $londec = sprintf "%.2f", ($long + ($lonm / 60)); - + $data{$type}{$name}{dwdcatalog}{$id}{id} = $id; $data{$type}{$name}{dwdcatalog}{$id}{stnam} = $stnam; $data{$type}{$name}{dwdcatalog}{$id}{icao} = $icao; @@ -4303,8 +4313,8 @@ sub __dwdStatCatalog_Response { $data{$type}{$name}{dwdcatalog}{$id}{latdec} = $latdec; # Latitude Dezimalgrad $data{$type}{$name}{dwdcatalog}{$id}{lon} = $lon; $data{$type}{$name}{dwdcatalog}{$id}{londec} = $londec; # Longitude Dezimalgrad - $data{$type}{$name}{dwdcatalog}{$id}{elev} = $elev; - } + $data{$type}{$name}{dwdcatalog}{$id}{elev} = $elev; + } $err = writeCacheToFile ($hash, 'dwdcatalog', $dwdcatalog); # DWD Stationskatalog speichern @@ -4323,12 +4333,12 @@ sub __dwdStatCatalog_Response { cachename => 'dwdcatalog', title => 'DWD Station Catalog' } - ); + ); } - + my $prt = sprintf "%.4f", (tv_interval ($stc) - tv_interval ($sta)); # Laufzeit ermitteln - debugLog ($paref, 'dwdComm', "DWD Station Catalog retrieval and processing required >$prt< seconds"); - + debugLog ($paref, 'dwdComm', "DWD Station Catalog retrieval and processing required >$prt< seconds"); + return; } @@ -4602,7 +4612,7 @@ return; } ################################################################ -# Attr +# Attr # $cmd can be "del" or "set" # $name is device name # aName and aVal are Attribute name and value @@ -4617,7 +4627,7 @@ sub Attr { my $type = $hash->{TYPE}; my ($do,$val, $err); - + ### nicht mehr benötigte Daten löschen - Bereich kann später wieder raus !! ###################################################################################################################### #if ($cmd eq 'set' && $aName eq 'affectNumHistDays') { @@ -4691,7 +4701,7 @@ sub Attr { if ($cmd eq 'set') { if ($aName eq 'ctrlInterval' || $aName eq 'ctrlBackupFilesKeep' || $aName eq 'ctrlAIdataStorageDuration') { unless ($aVal =~ /^[0-9]+$/x) { - return qq{The value for $aName is not valid. Use only figures 0-9 !}; + return qq{Invalid value for $aName. Use only figures 0-9!}; } } @@ -4920,19 +4930,19 @@ return; ################################################################ # Attr ctrlWeatherDevX ################################################################ -sub _attrWeatherDev { ## no critic "not used" +sub _attrWeatherDev { ## no critic "not used" my $paref = shift; my $hash = $paref->{hash}; my $name = $paref->{name}; my $aVal = $paref->{aVal} // return qq{no weather forecast device specified} if($paref->{cmd} eq 'set'); - + return if(!$init_done); if ($paref->{cmd} eq 'set' ) { if (!$defs{$aVal} || $defs{$aVal}{TYPE} ne "DWD_OpenData") { return qq{The device "$aVal" doesn't exist or has no TYPE "DWD_OpenData"}; } - + my $err = checkdwdattr ($name, $aVal, \@dweattrmust); return $err if($err); } @@ -5127,7 +5137,7 @@ sub Delete { $airaw.$name, $aitrained.$name ); - + opendir (DIR, $cachedir); while (my $file = readdir (DIR)) { @@ -5137,7 +5147,7 @@ sub Delete { push @ftd, "$cachedir/$file"; } - closedir (DIR); + closedir (DIR); for my $f (@ftd) { my $err = FileDelete ($f); @@ -5281,7 +5291,7 @@ sub writeCacheToFile { return; } - + if ($cachename eq 'dwdcatalog') { if (scalar keys %{$data{$type}{$name}{dwdcatalog}}) { $error = fileStore ($data{$type}{$name}{dwdcatalog}, $file); @@ -5295,7 +5305,7 @@ sub writeCacheToFile { Log3 ($name, 1, "$name - $err"); return $err; } - + return; } @@ -5371,17 +5381,17 @@ sub runTask { my ($min, $sec) = split ':', $ms; # aktuelle Minute (00-59), aktuelle Sekunde (00-61) $min = int $min; $sec = int $sec; - + if ($sec > 10) { # Attribute zur Laufzeit hinzufügen if (!exists $hash->{HELPER}{S10DONE}) { - $hash->{HELPER}{S10DONE} = 1; + $hash->{HELPER}{S10DONE} = 1; _addDynAttr ($hash); # relevant für CPU Auslastung!! } } else { delete $hash->{HELPER}{S10DONE}; } - + my $name = $hash->{NAME}; my ($interval, $disabled, $inactive) = controller ($name); @@ -5412,7 +5422,7 @@ sub runTask { storeReading ('nextCycletime', FmtTime($new)); centralTask ($hash, 1); } - + my $debug = getDebug ($hash); if ($min == 59 && $sec > 48) { @@ -5437,14 +5447,14 @@ sub runTask { if ($debug =~ /collectData/x) { Log3 ($name, 1, "$name DEBUG> INFO - runTask starts data collection at the beginning of an hour"); } - + centralTask ($hash, 1); } } else { delete $hash->{HELPER}{S03DONE}; } - + if ($min =~ /^(2|22|42)$/xs && $sec > 20) { if (!exists $hash->{HELPER}{SUNCALCDONE}) { $hash->{HELPER}{SUNCALCDONE} = 1; @@ -5452,16 +5462,16 @@ sub runTask { if ($debug =~ /collectData/x) { Log3 ($name, 1, "$name DEBUG> INFO - runTask starts calculation of Sun positions"); } - + my $day = strftime "%d", localtime($t); # aktueller Tag (range 01 to 31) my $chour = strftime "%H", localtime($t); # aktuelle Stunde in 24h format (00-23) - + _calcSunPosition ( { hash => $hash, name => $name, type => $hash->{TYPE}, t => $t, day => $day, - chour => $chour, + chour => $chour, debug => $debug } ); @@ -5482,31 +5492,31 @@ sub _addDynAttr { my $hash = shift; my $type = $hash->{TYPE}; - + ## Attr ctrlWeatherDevX zur Laufzeit hinzufügen ################################################# my $adwds = ''; my @alldwd = devspec2array ("TYPE=DWD_OpenData"); $adwds = join ",", @alldwd if(@alldwd); my @deva = split " ", $modules{$type}{AttrList}; - + my $atd = 'ctrlWeatherDev'; @deva = grep {!/$atd/} @deva; push @deva, ($adwds ? "ctrlWeatherDev1:$adwds " : "ctrlWeatherDev1:noArg"); - + for my $step (1..$weatherDevMax) { push @deva, ($adwds ? "ctrlWeatherDev".$step.":$adwds " : "ctrlWeatherDev1:noArg"); } - $hash->{".AttrList"} = join " ", @deva; + $hash->{".AttrList"} = join " ", @deva; return; } ################################################################ # Ermittlung der Sonnenpositionen -# Az,Alt = Azimuth und Höhe (in Dezimalgrad) des Körpers +# Az,Alt = Azimuth und Höhe (in Dezimalgrad) des Körpers # über dem Horizont ################################################################ sub _calcSunPosition { @@ -5517,40 +5527,40 @@ sub _calcSunPosition { my $t = $paref->{t}; # Epoche Zeit my $chour = $paref->{chour}; my $day = $paref->{day}; - + debugLog ($paref, 'collectData', "collect Sun position data =>"); for my $num (0..46) { my ($fd, $fh) = _calcDayHourMove ($chour, $num); last if($fd > 1); - + my $tstr = (timestampToTimestring ($t + ($num * 3600)))[3]; my ($date, $h, $m, $s) = split /[ :]/, $tstr; $tstr = $date.' '.$h.':30:00'; - + my ($az, $alt); eval { - $az = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAz', $tstr); # statt Astro_Get geht auch FHEM::Astro::Get + $az = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAz', $tstr); # statt Astro_Get geht auch FHEM::Astro::Get $alt = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAlt', $tstr); }; - + if ($@) { Log3 ($name, 1, "$name - ERROR - $@"); - return; + return; } - + my $hod = sprintf "%02d", $h + 1; my $nhtstr = "NextHour".sprintf "%02d", $num; - + $data{$type}{$name}{nexthours}{$nhtstr}{sunaz} = $az; $data{$type}{$name}{nexthours}{$nhtstr}{sunalt} = $alt; - + debugLog ($paref, 'collectData', "Sun position: hod: $hod, $tstr, azimuth: $az, altitude: $alt"); if ($fd == 0 && $hod) { # Sun Position in pvHistory speichern $paref->{nhour} = sprintf "%02d", $hod; - + $paref->{val} = $az; $paref->{histname} = 'sunaz'; setPVhistory ($paref); @@ -5564,7 +5574,7 @@ sub _calcSunPosition { delete $paref->{nhour}; } } - + debugLog ($paref, 'collectData', "All Sun position data collected"); return; @@ -5606,8 +5616,8 @@ sub centralTask { delete $data{$type}{$name}{aidectree}{airaw}{$idx}{wrp}; $data{$type}{$name}{aidectree}{airaw}{$idx}{temp} = 0 if(AiRawdataVal ($hash, $idx, 'temp', 0) eq '00'); $data{$type}{$name}{aidectree}{airaw}{$idx}{temp} = 5 if(AiRawdataVal ($hash, $idx, 'temp', 0) eq '05'); - $data{$type}{$name}{aidectree}{airaw}{$idx}{temp} = -5 if(AiRawdataVal ($hash, $idx, 'temp', 0) eq '-05'); - + $data{$type}{$name}{aidectree}{airaw}{$idx}{temp} = -5 if(AiRawdataVal ($hash, $idx, 'temp', 0) eq '-05'); + my $sunalt = AiRawdataVal ($hash, $idx, 'sunalt', ''); if (!$sunalt) { my $y = substr ($idx,0,4); # 14.02.2024 KI Hash auffüllen @@ -5616,12 +5626,12 @@ sub centralTask { my $h = substr ($idx,8,2); $h = sprintf "%02d", int $h - 1; my $tstr = "$y-$m-$d $h:30:00"; - + my $alt; eval { $alt = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAlt', $tstr); }; - + if ($@) { Log3 ($name, 1, "$name - add sunalt - idx: $idx, hod: $h, err: $@"); delete $data{$type}{$name}{aidectree}{airaw}{$idx}; @@ -5629,9 +5639,9 @@ sub centralTask { else { my $sabin = sunalt2bin ($alt); $data{$type}{$name}{aidectree}{airaw}{$idx}{sunalt} = $sabin; - } + } } - + my $sunaz = AiRawdataVal ($hash, $idx, 'sunaz', ''); if (!$sunaz) { my $y = substr ($idx,0,4); # 14.02.2024 KI Hash auffüllen @@ -5640,22 +5650,22 @@ sub centralTask { my $h = substr ($idx,8,2); $h = sprintf "%02d", int $h - 1; my $tstr = "$y-$m-$d $h:30:00"; - + my $az; eval { - $az = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAz', $tstr); + $az = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAz', $tstr); }; - + if ($@) { - delete $data{$type}{$name}{aidectree}{airaw}{$idx}; + delete $data{$type}{$name}{aidectree}{airaw}{$idx}; } else { - $data{$type}{$name}{aidectree}{airaw}{$idx}{sunaz} = $az if(defined $az); - } + $data{$type}{$name}{aidectree}{airaw}{$idx}{sunaz} = $az if(defined $az); + } } } } - + ## percentile in simple umsetzen # 05.02.2024 for my $idx (sort keys %{$data{$type}{$name}{circular}}) { if(defined $data{$type}{$name}{circular}{$idx}{pvcorrf}{percentile}) { @@ -5678,51 +5688,131 @@ sub centralTask { $data{$type}{$name}{circular}{$idx}{dnumsum}{simple} = $data{$type}{$name}{circular}{$idx}{dnumsum}{percentile}; delete $data{$type}{$name}{circular}{$idx}{dnumsum}{percentile}; } - } - + } + ## nicht-Bin Werte löschen / wrp löschen - my $ra = '0|00|05|5|10|15|20|25|30|35|40|45|50|55|60|65|70|75|80|85|90|95|100|2.{2}|percentile|simple'; - + my $ra = '0|00|05|5|10|15|20|25|30|35|40|45|50|55|60|65|70|75|80|85|90|95|100|.*\..*|simple'; + for my $hod (keys %{$data{$type}{$name}{circular}}) { # 30.01.2024 for my $range (keys %{$data{$type}{$name}{circular}{$hod}{pvcorrf}}) { delete $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range} if($range !~ /^($ra)$/xs); + + if($range !~ /simple|\./xs) { # 24.02.2024 + $data{$type}{$name}{circular}{$hod}{pvcorrf}{"5.$range"} = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + $data{$type}{$name}{circular}{$hod}{pvcorrf}{"10.$range"} = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + $data{$type}{$name}{circular}{$hod}{pvcorrf}{"15.$range"} = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + $data{$type}{$name}{circular}{$hod}{pvcorrf}{"20.$range"} = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + $data{$type}{$name}{circular}{$hod}{pvcorrf}{"25.$range"} = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + $data{$type}{$name}{circular}{$hod}{pvcorrf}{"30.$range"} = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + $data{$type}{$name}{circular}{$hod}{pvcorrf}{"35.$range"} = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + $data{$type}{$name}{circular}{$hod}{pvcorrf}{"40.$range"} = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + $data{$type}{$name}{circular}{$hod}{pvcorrf}{"45.$range"} = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + delete $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}; + } + delete $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range} if(!defined $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range}); } - + for my $range (keys %{$data{$type}{$name}{circular}{$hod}{quality}}) { delete $data{$type}{$name}{circular}{$hod}{quality}{$range} if($range !~ /^($ra)$/xs); + + if($range !~ /simple|\./xs) { # 24.02.2024 + $data{$type}{$name}{circular}{$hod}{quality}{"5.$range"} = $data{$type}{$name}{circular}{$hod}{quality}{$range}; + $data{$type}{$name}{circular}{$hod}{quality}{"10.$range"} = $data{$type}{$name}{circular}{$hod}{quality}{$range}; + $data{$type}{$name}{circular}{$hod}{quality}{"15.$range"} = $data{$type}{$name}{circular}{$hod}{quality}{$range}; + $data{$type}{$name}{circular}{$hod}{quality}{"20.$range"} = $data{$type}{$name}{circular}{$hod}{quality}{$range}; + $data{$type}{$name}{circular}{$hod}{quality}{"25.$range"} = $data{$type}{$name}{circular}{$hod}{quality}{$range}; + $data{$type}{$name}{circular}{$hod}{quality}{"30.$range"} = $data{$type}{$name}{circular}{$hod}{quality}{$range}; + $data{$type}{$name}{circular}{$hod}{quality}{"35.$range"} = $data{$type}{$name}{circular}{$hod}{quality}{$range}; + $data{$type}{$name}{circular}{$hod}{quality}{"40.$range"} = $data{$type}{$name}{circular}{$hod}{quality}{$range}; + $data{$type}{$name}{circular}{$hod}{quality}{"45.$range"} = $data{$type}{$name}{circular}{$hod}{quality}{$range}; + delete $data{$type}{$name}{circular}{$hod}{quality}{$range}; + } + delete $data{$type}{$name}{circular}{$hod}{quality}{$range} if(!defined $data{$type}{$name}{circular}{$hod}{quality}{$range}); } - + + for my $range (keys %{$data{$type}{$name}{circular}{$hod}{pvrlsum}}) { + delete $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range} if($range !~ /^($ra)$/xs); + + if($range !~ /simple|\./xs) { # 24.02.2024 + $data{$type}{$name}{circular}{$hod}{pvrlsum}{"5.$range"} = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvrlsum}{"10.$range"} = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvrlsum}{"15.$range"} = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvrlsum}{"20.$range"} = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvrlsum}{"25.$range"} = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvrlsum}{"30.$range"} = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvrlsum}{"35.$range"} = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvrlsum}{"40.$range"} = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvrlsum}{"45.$range"} = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + delete $data{$type}{$name}{circular}{$hod}{pvrlsum}{$range}; + } + } + + for my $range (keys %{$data{$type}{$name}{circular}{$hod}{pvfcsum}}) { + delete $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range} if($range !~ /^($ra)$/xs); + + if($range !~ /simple|\./xs) { # 24.02.2024 + $data{$type}{$name}{circular}{$hod}{pvfcsum}{"5.$range"} = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvfcsum}{"10.$range"} = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvfcsum}{"15.$range"} = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvfcsum}{"20.$range"} = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvfcsum}{"25.$range"} = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvfcsum}{"30.$range"} = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvfcsum}{"35.$range"} = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvfcsum}{"40.$range"} = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + $data{$type}{$name}{circular}{$hod}{pvfcsum}{"45.$range"} = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + delete $data{$type}{$name}{circular}{$hod}{pvfcsum}{$range}; + } + } + + for my $range (keys %{$data{$type}{$name}{circular}{$hod}{dnumsum}}) { + delete $data{$type}{$name}{circular}{$hod}{dnumsum}{$range} if($range !~ /^($ra)$/xs); + + if($range !~ /simple|\./xs) { # 24.02.2024 + $data{$type}{$name}{circular}{$hod}{dnumsum}{"5.$range"} = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + $data{$type}{$name}{circular}{$hod}{dnumsum}{"10.$range"} = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + $data{$type}{$name}{circular}{$hod}{dnumsum}{"15.$range"} = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + $data{$type}{$name}{circular}{$hod}{dnumsum}{"20.$range"} = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + $data{$type}{$name}{circular}{$hod}{dnumsum}{"25.$range"} = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + $data{$type}{$name}{circular}{$hod}{dnumsum}{"30.$range"} = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + $data{$type}{$name}{circular}{$hod}{dnumsum}{"35.$range"} = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + $data{$type}{$name}{circular}{$hod}{dnumsum}{"40.$range"} = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + $data{$type}{$name}{circular}{$hod}{dnumsum}{"45.$range"} = $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + delete $data{$type}{$name}{circular}{$hod}{dnumsum}{$range}; + } + } + for my $wp (keys %{$data{$type}{$name}{circular}{$hod}}) { # 19.02.204 next if($wp ne 'wrp'); delete $data{$type}{$name}{circular}{$hod}{$wp}; } } + ## currentWeatherDev in Attr umsetzen my $cwd = ReadingsVal ($name, 'currentWeatherDev', ''); # 30.01.2024 if ($cwd) { CommandAttr (undef, "$name ctrlWeatherDev1 $cwd"); readingsDelete ($hash, 'currentWeatherDev') if(AttrVal ($name, 'ctrlWeatherDev1', '')); # erst prüfen ob gesetzt } - + ## Reading umsetzen my $mdr = ReadingsVal ($name, 'moduleDirection', undef); # 09.02.2024 if ($mdr) { readingsSingleUpdate ($hash, 'moduleAzimuth', $mdr, 0); readingsDelete ($hash, 'moduleDirection'); - } - + } + my $mta = ReadingsVal ($name, 'moduleTiltAngle', undef); # 09.02.2024 if ($mta) { readingsSingleUpdate ($hash, 'moduleDeclination', $mta, 0); readingsDelete ($hash, 'moduleTiltAngle'); } - + ####################################################################################################################### return if(!$init_done); setModel ($hash); # Model setzen - + my (undef, $disabled, $inactive) = controller ($name); return if($disabled || $inactive); # disabled / inactive @@ -5732,7 +5822,7 @@ sub centralTask { return; } - if (!CurrentVal ($hash, 'allStringsFullfilled', 0)) { # die String Konfiguration erstellen wenn noch nicht erfolgreich ausgeführt + if (!CurrentVal ($hash, 'allStringsFullfilled', 0)) { # die String Konfiguration erstellen wenn noch nicht erfolgreich ausgeführt my $ret = createStringConfig ($hash); if ($ret) { @@ -5780,7 +5870,7 @@ sub centralTask { collectAllRegConsumers ($centpars); # alle Verbraucher Infos laden _specialActivities ($centpars); # zusätzliche Events generieren + Sonderaufgaben _transferWeatherValues ($centpars); # Wetterwerte übertragen - + readingsDelete ($hash, 'AllPVforecastsToEvent'); _getRoofTopData ($centpars); # Strahlungswerte/Forecast-Werte in solcastapi-Hash erstellen @@ -6224,34 +6314,34 @@ sub _transferWeatherValues { my $fcname = AttrVal ($name, 'ctrlWeatherDev1', ''); # Standard Weather Forecast Device return if(!$fcname || !$defs{$fcname}); - + my $type = $paref->{type}; - + delete $data{$type}{$name}{weatherdata}; # Wetterdaten Hash löschen $paref->{fcname} = $fcname; __sunRS ($paref); # Sonnenauf- und untergang delete $paref->{fcname}; - + for my $step (1..$weatherDevMax) { - $paref->{step} = $step; + $paref->{step} = $step; __readDataWeather ($paref); # Wetterdaten in einen Hash einlesen delete $paref->{step}; } - + __mergeDataWeather ($paref); # Wetterdaten zusammenfügen - + for my $num (0..46) { my ($fd, $fh) = _calcDayHourMove ($chour, $num); last if($fd > 1); - + my $wid = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{ww}; # signifikantes Wetter my $wwd = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{wwd}; # Wetter Beschreibung my $neff = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{neff}; # Effektive Wolkendecke - my $rr1c = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{rr1c}; # Gesamtniederschlag (1-stündig) letzte 1 Stunde + my $rr1c = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{rr1c}; # Gesamtniederschlag (1-stündig) letzte 1 Stunde my $temp = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{ttt}; # Außentemperatur my $don = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{don}; # Tag/Nacht-Grenze - + my $nhtstr = "NextHour".sprintf "%02d", $num; $data{$type}{$name}{nexthours}{$nhtstr}{weatherid} = $wid; $data{$type}{$name}{nexthours}{$nhtstr}{cloudcover} = $neff; @@ -6261,7 +6351,7 @@ sub _transferWeatherValues { $data{$type}{$name}{nexthours}{$nhtstr}{DoN} = $don; my $fh1 = $fh + 1; # = hod - + if ($num < 23 && $fh < 24) { # Ringspeicher Weather Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 $data{$type}{$name}{circular}{sprintf("%02d",$fh1)}{weatherid} = $wid; $data{$type}{$name}{circular}{sprintf("%02d",$fh1)}{weathertxt} = $wwd; @@ -6273,7 +6363,7 @@ sub _transferWeatherValues { $data{$type}{$name}{current}{temp} = $temp; } } - + if ($fd == 0 && $fh1) { # Weather in pvHistory speichern $paref->{val} = $wid // -1; $paref->{histname} = 'weatherid'; @@ -6307,11 +6397,11 @@ return; sub __readDataWeather { my $paref = shift; my $hash = $paref->{hash}; - my $name = $paref->{name}; + my $name = $paref->{name}; my $chour = $paref->{chour}; # aktuelles Datum my $type = $paref->{type}; my $step = $paref->{step}; - + my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$step, ''); # Weather Forecast Device return if(!$fcname || !$defs{$fcname}); @@ -6323,29 +6413,29 @@ sub __readDataWeather { for my $n (0..46) { my ($fd, $fh) = _calcDayHourMove ($chour, $n); last if($fd > 1); - - my $wid = ReadingsNum ($fcname, "fc${fd}_${fh}_ww", -1); # Signifikantes Wetter zum Vorhersagezeitpunkt + + my $wid = ReadingsNum ($fcname, "fc${fd}_${fh}_ww", -1); # Signifikantes Wetter zum Vorhersagezeitpunkt my $wwd = ReadingsVal ($fcname, "fc${fd}_${fh}_wwd", ''); # Wetter Beschreibung my $neff = ReadingsNum ($fcname, "fc${fd}_${fh}_Neff", 0); # Effektiver Bedeckungsgrad zum Vorhersagezeitpunkt - my $temp = ReadingsNum ($fcname, "fc${fd}_${fh}_TTT", 0); # 2m-Temperatur zum Vorhersagezeitpunkt + my $temp = ReadingsNum ($fcname, "fc${fd}_${fh}_TTT", 0); # 2m-Temperatur zum Vorhersagezeitpunkt my $sunup = ReadingsNum ($fcname, "fc${fd}_${fh}_SunUp", 0); # 1 - Tag - - my $fh1 = $fh + 1; + + my $fh1 = $fh + 1; my $fd1 = $fd; - + if ($fh1 == 24) { $fh1 = 0; $fd1++; } - + last if($fd1 > 1); - - my $rr1c = ReadingsNum ($fcname, "fc${fd1}_${fh1}_RR1c", 0); # Gesamtniederschlag (1-stündig) letzte 1 Stunde + + my $rr1c = ReadingsNum ($fcname, "fc${fd1}_${fh1}_RR1c", 0); # Gesamtniederschlag (1-stündig) letzte 1 Stunde if (!$sunup) { $wid += 100; } - + debugLog ($paref, 'collectData', "Weather $step: fc${fd}_${fh}, don: $sunup, ww: $wid, RR1c: $rr1c, TTT: $temp, Neff: $neff, wwd: $wwd"); $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{ww} = $wid; @@ -6364,61 +6454,61 @@ return; ################################################################ sub __mergeDataWeather { my $paref = shift; - my $name = $paref->{name}; + my $name = $paref->{name}; my $type = $paref->{type}; - + debugLog ($paref, 'collectData', "merge Weather data =>"); - + my $ds = 0; - + for my $wd (1..$weatherDevMax) { my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$wd, ''); # Weather Forecast Device $ds++ if($fcname && $defs{$fcname}); } - + my ($q, $m) = (0,0); for my $key (sort keys %{$data{$type}{$name}{weatherdata}}) { my ($z, $neff, $rr1c, $temp) = (0,0,0,0); - + $data{$type}{$name}{weatherdata}{$key}{merge}{don} = $data{$type}{$name}{weatherdata}{$key}{1}{don}; $data{$type}{$name}{weatherdata}{$key}{merge}{ww} = $data{$type}{$name}{weatherdata}{$key}{1}{ww}; - $data{$type}{$name}{weatherdata}{$key}{merge}{wwd} = $data{$type}{$name}{weatherdata}{$key}{1}{wwd}; + $data{$type}{$name}{weatherdata}{$key}{merge}{wwd} = $data{$type}{$name}{weatherdata}{$key}{1}{wwd}; $data{$type}{$name}{weatherdata}{$key}{merge}{neff} = $data{$type}{$name}{weatherdata}{$key}{1}{neff}; $data{$type}{$name}{weatherdata}{$key}{merge}{rr1c} = $data{$type}{$name}{weatherdata}{$key}{1}{rr1c}; $data{$type}{$name}{weatherdata}{$key}{merge}{ttt} = $data{$type}{$name}{weatherdata}{$key}{1}{ttt}; - - for my $step (1..$ds) { + + for my $step (1..$ds) { $q++; - + my $n = $data{$type}{$name}{weatherdata}{$key}{$step}{neff}; my $r = $data{$type}{$name}{weatherdata}{$key}{$step}{rr1c}; my $t = $data{$type}{$name}{weatherdata}{$key}{$step}{ttt}; - + next if(!isNumeric ($n) || !isNumeric ($r) || !isNumeric ($t)); - + $neff += $n; $rr1c += $r; $temp += $t; $z++; $m++; - } - + } + next if(!$z); - + $data{$type}{$name}{weatherdata}{$key}{merge}{neff} = sprintf "%.0f", ($neff / $z); $data{$type}{$name}{weatherdata}{$key}{merge}{rr1c} = sprintf "%.2f", ($rr1c / $z); $data{$type}{$name}{weatherdata}{$key}{merge}{ttt} = sprintf "%.2f", ($temp / $z); - + debugLog ($paref, 'collectData', "Weather merged: $key, ". "don: $data{$type}{$name}{weatherdata}{$key}{merge}{don}, ". "ww: $data{$type}{$name}{weatherdata}{$key}{1}{ww}, ". "RR1c: $data{$type}{$name}{weatherdata}{$key}{merge}{rr1c}, ". "TTT: $data{$type}{$name}{weatherdata}{$key}{merge}{ttt}, ". "Neff: $data{$type}{$name}{weatherdata}{$key}{merge}{neff}, ". - "wwd: $data{$type}{$name}{weatherdata}{$key}{merge}{wwd}"); + "wwd: $data{$type}{$name}{weatherdata}{$key}{merge}{wwd}"); } - + debugLog ($paref, 'collectData', "Number of Weather datasets mergers - delivered: $q, merged: $m, failures: ".($q - $m)); return; @@ -6440,7 +6530,7 @@ sub __sunRS { my ($fc0_sr, $fc0_ss, $fc1_sr, $fc1_ss); my ($cset, $lat, $lon) = locCoordinates(); - + debugLog ($paref, 'collectData', "collect sunrise/sunset times - device: $fcname =>"); if ($cset) { @@ -6456,7 +6546,7 @@ sub __sunRS { $fc1_sr = ReadingsVal ($fcname, 'fc1_SunRise', '23:59'); $fc1_ss = ReadingsVal ($fcname, 'fc1_SunSet', '00:00'); } - + $data{$type}{$name}{current}{sunriseToday} = $date.' '.$fc0_sr.':00'; $data{$type}{$name}{current}{sunriseTodayTs} = timestringToTimestamp ($date.' '.$fc0_sr.':00'); @@ -6469,7 +6559,7 @@ sub __sunRS { storeReading ('Today_SunSet', $fc0_ss); storeReading ('Tomorrow_SunRise', $fc1_sr); storeReading ('Tomorrow_SunSet', $fc1_ss); - + my $fc0_sr_mm = sprintf "%02d", (split ":", $fc0_sr)[0]; my $fc0_ss_mm = sprintf "%02d", (split ":", $fc0_ss)[0]; my $fc1_sr_mm = sprintf "%02d", (split ":", $fc1_sr)[0]; @@ -6545,27 +6635,27 @@ sub _transferAPIRadiationValues { if ($msg eq 'accurate' || $msg eq 'spreaded') { my $aivar = 100; $aivar = 100 * $pvaifc / $est if($est); - + if ($msg eq 'accurate' && $aivar >= $aiAccLowLim && $aivar <= $aiAccUpLim) { # KI liefert 'accurate' Treffer -> verwenden $data{$type}{$name}{nexthours}{$nhtstr}{aihit} = 1; $pvfc = $pvaifc; - $useai = 1; + $useai = 1; - debugLog ($paref, 'aiData', qq{AI Hit - accurate result found -> variance $aivar, hod: $hod, Rad1h: $rad1h, pvfc: $pvfc Wh}); + debugLog ($paref, 'aiData', qq{AI Hit - accurate result found -> variance $aivar, hod: $hod, Rad1h: $rad1h, pvfc: $pvfc Wh}); } - + if ($msg eq 'spreaded' && $aivar >= $aiSpreadLowLim && $aivar <= $aiSpreadUpLim) { # Abweichung AI von Standardvorhersage begrenzen $data{$type}{$name}{nexthours}{$nhtstr}{aihit} = 1; $pvfc = $pvaifc; $useai = 1; - + debugLog ($paref, 'aiData', qq{AI Hit - spreaded result found and is in tolerance -> hod: $hod, Rad1h: $rad1h, pvfc: $pvfc Wh}); } } else { debugLog ($paref, 'aiData', $msg); } - + if ($useai) { $data{$type}{$name}{nexthours}{$nhtstr}{pvaifc} = $pvaifc; # durch AI gelieferte PV Forecast } @@ -6573,10 +6663,10 @@ sub _transferAPIRadiationValues { delete $data{$type}{$name}{nexthours}{$nhtstr}{pvaifc}; $data{$type}{$name}{nexthours}{$nhtstr}{aihit} = 0; $pvfc = $est; - + debugLog ($paref, 'aiData', "use PV from API (no AI or AI result tolerance overflow) -> hod: $hod, Rad1h: ".(defined $rad1h ? $rad1h : '-').", pvfc: $pvfc Wh"); } - + $data{$type}{$name}{nexthours}{$nhtstr}{pvfc} = $pvfc; # resultierende PV Forecast zuweisen if ($num < 23 && $fh < 24) { # Ringspeicher PV forecast Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 @@ -6779,17 +6869,17 @@ sub ___readCandQ { my $cc = $paref->{cloudcover}; my ($acu, $aln) = isAutoCorrUsed ($name); # Autokorrekturmodus - my $hc = ReadingsNum ($name, 'pvCorrectionFactor_'.sprintf("%02d",$fh1), 1.00); # Voreinstellung RAW-Korrekturfaktor + my $sunalt = NexthoursVal ($hash, "NextHour".sprintf("%02d",$num), 'sunalt', ''); # Sun Altitude + my $sabin = sunalt2bin ($sunalt); + my $hc = ReadingsNum ($name, 'pvCorrectionFactor_'.sprintf("%02d",$fh1), 1.00); # Voreinstellung RAW-Korrekturfaktor my $hq = '-'; # keine Qualität definiert + my $crang = 'simple'; delete $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{cloudrange}; - - my $sunalt = NexthoursVal ($hash, "NextHour".sprintf("%02d",$num), 'sunalt', ''); # Sun Altitude - my $safac = CircularSunaltkorrVal ($hash, sprintf("%02d",$fh1), $sunalt, 0); # Faktor gespeichert für eine Sun Altitude if ($acu =~ /on_complex/xs) { # Autokorrektur complex soll genutzt werden - my $crang = cloud2bin ($cc); # Range errechnen - ($hc, $hq) = CircularCloudkorrVal ($hash, sprintf("%02d",$fh1), $crang, undef); # Korrekturfaktor/Qualität der Stunde des Tages (complex) + $crang = cloud2bin ($cc); # Range errechnen + ($hc, $hq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$fh1), $sabin, $crang, undef); # Korrekturfaktor/Qualität der Stunde des Tages (complex) $hq //= '-'; $hc //= 1; # Korrekturfaktor = 1 (keine Korrektur) # keine Qualität definiert $hc = 1 if(1 * $hc == 0); # 0.0-Werte ignorieren (Schleifengefahr) @@ -6797,19 +6887,21 @@ sub ___readCandQ { $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{cloudrange} = $crang; } elsif ($acu =~ /on_simple/xs) { - ($hc, $hq) = CircularCloudkorrVal ($hash, sprintf("%02d",$fh1), 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) + ($hc, $hq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$fh1), $sabin, 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) $hq //= '-'; $hc //= 1; # Korrekturfaktor = 1 $hc = 1 if(1 * $hc == 0); # 0.0-Werte ignorieren (Schleifengefahr) } else { # keine Autokorrektur - ($hc, $hq) = CircularCloudkorrVal ($hash, sprintf("%02d",$fh1), 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) + ($hc, $hq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$fh1), $sabin, 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) $hq //= '-'; $hc = 1; } $hc = sprintf "%.2f", $hc; + debugLog ($paref, 'pvCorrectionRead', "read parameters - fd: $fd, hod: ".sprintf("%02d",$fh1).", Sun Altitude Bin: $sabin, Cloud range: $crang, corrf: $hc, quality: $hq"); + $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{pvcorrf} = $hc."/".$hq; if($fd == 0 && $fh1) { @@ -7322,9 +7414,9 @@ sub _transferBatteryValues { $paref->{val} = $soc; $paref->{nhour} = 99; $paref->{histname} = 'batmaxsoc'; - + setPVhistory ($paref); - + delete $paref->{histname}; delete $paref->{nhour}; delete $paref->{val}; @@ -7415,31 +7507,31 @@ sub _batSocTarget { ## Aufladewahrscheinlichkeit beachten ####################################### - my $pvfctm = ReadingsNum ($name, 'Tomorrow_PVforecast', 0); # PV Prognose morgen - my $pvfctd = ReadingsNum ($name, 'RestOfDayPVforecast', 0); # PV Prognose Rest heute - my $csopt = ReadingsNum ($name, 'Battery_OptimumTargetSoC', $lowSoc); # aktuelles SoC Optimum + my $pvfctm = ReadingsNum ($name, 'Tomorrow_PVforecast', 0); # PV Prognose morgen + my $pvfctd = ReadingsNum ($name, 'RestOfDayPVforecast', 0); # PV Prognose Rest heute + my $csopt = ReadingsNum ($name, 'Battery_OptimumTargetSoC', $lowSoc); # aktuelles SoC Optimum + my $pvexpect = $pvfctm > $pvfctd ? $pvfctm : $pvfctd; + my $batinstcap = CurrentVal ($hash, 'batinstcap', 0); # installierte Batteriekapazität Wh + my $cantarget = 100 - (100 / $batinstcap) * $pvexpect; # berechneter möglicher Min SOC nach Berücksichtigung Ladewahrscheinlichkeit - my $batinstcap = CurrentVal ($hash, 'batinstcap', 0); # installierte Batteriekapazität Wh - my $cantarget = 100 - (100 / $batinstcap) * $pvexpect; # berechneter möglicher Min SOC nach Berücksichtigung Ladewahrscheinlichkeit - - my $newtarget = sprintf "%.0f", ($cantarget < $target ? $cantarget : $target); # Abgleich möglicher Min SOC gg. berechneten Min SOC + my $newtarget = sprintf "%.0f", ($cantarget < $target ? $cantarget : $target); # Abgleich möglicher Min SOC gg. berechneten Min SOC my $logadd = ''; - if ($newtarget > $csopt && $t > $delayts) { # Erhöhung des SoC (wird ab Sonnenuntergang angewendet) + if ($newtarget > $csopt && $t > $delayts) { # Erhöhung des SoC (wird ab Sonnenuntergang angewendet) $target = $newtarget; $logadd = "(new target > $csopt % and Sunset has passed)"; } - elsif ($newtarget > $csopt && $t <= $delayts) { # bisheriges Optimum bleibt vorerst + elsif ($newtarget > $csopt && $t <= $delayts) { # bisheriges Optimum bleibt vorerst $target = $csopt; $nt = (timestampToTimestring ($delayts, $paref->{lang}))[0]; $logadd = "(calculated new target $newtarget % is only activated after $nt)"; } - elsif ($newtarget < $csopt) { # Targetminderung sofort umsetzen -> Freiplatz für Ladeprognose + elsif ($newtarget < $csopt) { # Targetminderung sofort umsetzen -> Freiplatz für Ladeprognose $target = $newtarget; $logadd = "(new target < current Target SoC $csopt)"; } - else { # bisheriges Optimum bleibt + else { # bisheriges Optimum bleibt $target = $newtarget; $logadd = "(no change)"; } @@ -7477,9 +7569,9 @@ sub _batSocTarget { $paref->{val} = $target; $paref->{nhour} = 99; $paref->{histname} = 'batsetsoc'; - + setPVhistory ($paref); - + delete $paref->{histname}; delete $paref->{nhour}; delete $paref->{val}; @@ -7577,33 +7669,33 @@ sub _createSummaries { $pvfc = 0 if($pvfc < 0); # PV Prognose darf nicht negativ sein $confc = 0 if($confc < 0); # Verbrauchsprognose darf nicht negativ sein - if($h == 1) { + if ($h == 1) { $next1HoursSum->{PV} += $pvfc / 60 * $minute; $next1HoursSum->{Consumption} += $confc / 60 * $minute; } - if($h <= 2) { + if ($h <= 2) { $next2HoursSum->{PV} += $pvfc if($h < 2); $next2HoursSum->{PV} += $pvfc / 60 * $minute if($h == 2); $next2HoursSum->{Consumption} += $confc if($h < 2); $next2HoursSum->{Consumption} += $confc / 60 * $minute if($h == 2); } - if($h <= 3) { + if ($h <= 3) { $next3HoursSum->{PV} += $pvfc if($h < 3); $next3HoursSum->{PV} += $pvfc / 60 * $minute if($h == 3); $next3HoursSum->{Consumption} += $confc if($h < 3); $next3HoursSum->{Consumption} += $confc / 60 * $minute if($h == 3); } - if($h <= 4) { + if ($h <= 4) { $next4HoursSum->{PV} += $pvfc if($h < 4); $next4HoursSum->{PV} += $pvfc / 60 * $minute if($h == 4); $next4HoursSum->{Consumption} += $confc if($h < 4); $next4HoursSum->{Consumption} += $confc / 60 * $minute if($h == 4); } - if($istdy) { + if ($istdy) { $restOfDaySum->{PV} += $pvfc; $restOfDaySum->{Consumption} += $confc; $tdConFcTillSunset += $confc if($don); @@ -7800,7 +7892,7 @@ sub _manageConsumerData { $paref->{val} = ceil ConsumerVal ($hash, $c, "minutesOn", 0); # Verbrauchsminuten akt. Stunde des Consumers $paref->{histname} = "minutescsm${c}"; setPVhistory ($paref); - + delete $paref->{histname}; delete $paref->{val}; @@ -9526,7 +9618,7 @@ sub _calcCaQcomplex { 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"); + debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the complex correction factor"); return; } @@ -9537,22 +9629,22 @@ sub _calcCaQcomplex { storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done'); return; } - + my $chwcc = HistoryVal ($hash, $day, sprintf("%02d",$h), 'wcc', 0); # Wolkenbedeckung Heute & abgefragte Stunde my $sunalt = HistoryVal ($hash, $day, sprintf("%02d",$h), 'sunalt', 0); # Sonne Altitude my $crang = cloud2bin ($chwcc); my $sabin = sunalt2bin ($sunalt); - - $paref->{pvrl} = $pvrl; + + $paref->{pvrl} = $pvrl; $paref->{pvfc} = $pvfc; - $paref->{crang} = $crang; - $paref->{sabin} = $sabin; + $paref->{crang} = $crang; + $paref->{sabin} = $sabin; $paref->{calc} = 'Complex'; - + my ($oldfac, $factor, $dnum) = __calcNewFactor ($paref); - - delete $paref->{pvrl}; - delete $paref->{pvfc}; + + delete $paref->{pvrl}; + delete $paref->{pvfc}; delete $paref->{crang}; delete $paref->{sabin}; delete $paref->{calc}; @@ -9560,7 +9652,7 @@ sub _calcCaQcomplex { storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done'); if ($acu =~ /on_complex/xs) { - storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac, cloudiness range: $crang, days in range: $dnum)"); + storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac, Sun Alt range: $sabin, Cloud range: $crang, Days in range: $dnum)"); storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h).'_autocalc', 'done'); } @@ -9584,13 +9676,13 @@ sub _calcCaQsimple { 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"); + # debugLog ($paref, 'pvCorrectionWrite', "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"); + debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the simple correction factor"); return; } @@ -9602,22 +9694,27 @@ sub _calcCaQsimple { return; } - $paref->{pvrl} = $pvrl; + my $sunalt = HistoryVal ($hash, $day, sprintf("%02d",$h), 'sunalt', 0); # Sonne Altitude + my $sabin = sunalt2bin ($sunalt); + + $paref->{pvrl} = $pvrl; $paref->{pvfc} = $pvfc; - $paref->{crang} = 'simple'; - $paref->{calc} = 'Simple'; - + $paref->{sabin} = $sabin; + $paref->{crang} = 'simple'; + $paref->{calc} = 'Simple'; + my ($oldfac, $factor, $dnum) = __calcNewFactor ($paref); - - delete $paref->{pvrl}; - delete $paref->{pvfc}; - delete $paref->{crang}; + + delete $paref->{pvrl}; + delete $paref->{pvfc}; + delete $paref->{sabin}; + delete $paref->{crang}; delete $paref->{calc}; storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done'); if ($acu =~ /on_simple/xs) { - storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac, days in range: $dnum)"); + storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac, Days in range: $dnum)"); storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h).'_autocalc', 'done'); } @@ -9644,16 +9741,20 @@ sub __calcNewFactor { my $pvrlsum = $pvrl; my $pvfcsum = $pvfc; - my ($oldfac, $oldq) = CircularCloudkorrVal ($hash, sprintf("%02d",$h), $crang, 0); # bisher definierter Korrekturfaktor - my ($pvhis, $fchis, $dnum) = CircularSumVal ($hash, sprintf("%02d",$h), $crang, 0); + debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> Start calculation correction factor for hour: $h"); + + my ($oldfac, $oldq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$h), $sabin, $crang, 0); # bisher definierter Korrekturfaktor / Qualität + my ($pvhis, $fchis, $dnum) = CircularSumVal ($hash, sprintf("%02d",$h), $sabin, $crang, 0); $oldfac = 1 if(1 * $oldfac == 0); + + debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> read historical values: pv real sum: $pvhis, pv forecast sum: $fchis, days sum: $dnum"); if ($dnum) { # Werte in History vorhanden -> haben Prio ! $dnum++; $pvrlsum = $pvrl + $pvhis; $pvfcsum = $pvfc + $fchis; - $pvrl = $pvrlsum / $dnum; - $pvfc = $pvfcsum / $dnum; + $pvrl = $pvrlsum / $dnum; + $pvfc = $pvfcsum / $dnum; $factor = sprintf "%.2f", ($pvrl / $pvfc); # Faktorberechnung: reale PV / Prognose } elsif ($oldfac && (!$pvhis || !$fchis)) { # Circular Hash liefert einen vorhandenen Korrekturfaktor aber keine gespeicherten PV-Werte @@ -9665,11 +9766,7 @@ sub __calcNewFactor { $dnum = 1; $factor = sprintf "%.2f", ($pvrl / $pvfc); } - - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$crang} = $pvrlsum; # PV Erzeugung Summe speichern - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$crang} = $pvfcsum; # PV Prognose Summe speichern - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$crang} = $dnum; # Anzahl aller historischen Tade dieser Range - + my $maxvar = AttrVal ($name, 'affectMaxDayVariance', $defmaxvar); # max. Korrekturvarianz $factor = 1.00 if(1 * $factor == 0); # 0.00-Werte ignorieren (Schleifengefahr) @@ -9680,24 +9777,31 @@ sub __calcNewFactor { else { Log3 ($name, 3, "$name - new $calc correction factor for hour $h calculated: $factor (old: $oldfac)"); } - + $pvrl = sprintf "%.0f", $pvrl; $pvfc = sprintf "%.0f", $pvfc; - - my $qual = __calcFcQuality ($pvfc, $pvrl); # Qualität der Vorhersage für die vergangene Stunde - - debugLog ($paref, 'pvCorrection', "$calc Corrf -> start calculation correction factor for hour: $h"); - debugLog ($paref, 'pvCorrection', "$calc Corrf -> determined values - hour: $h, cloudiness range: $crang, old corrf: $oldfac, new corrf: $factor, days: $dnum"); - debugLog ($paref, 'pvCorrection|saveData2Cache', "$calc Corrf -> write correction values into Circular - hour: $h, cloudiness range: $crang, factor: $factor, quality: $qual"); - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$crang} = $factor; # Korrekturfaktor nach "Cloudcover" oder "Simple" - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{$crang} = $qual; - - if (defined $sabin) { # Korrekturfaktor für Sonne Altitude - $sabin = 200 + $sabin; # 200 + value für pvcorrf Sonne Altitude - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$sabin} = $factor; + my $qual = __calcFcQuality ($pvfc, $pvrl); # Qualität der Vorhersage für die vergangene Stunde + + debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> determined values - hour: $h, 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: $h, Sun Altitude range: $sabin, Cloud range: $crang, factor: $factor, quality: $qual, days: $dnum"); + + if ($crang ne 'simple') { + my $idx = $sabin.'.'.$crang; # value für pvcorrf Sonne Altitude + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$idx} = $pvrlsum; # PV Erzeugung Summe speichern + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$idx} = $pvfcsum; # PV Prognose Summe speichern + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$idx} = $dnum; # Anzahl aller historischen Tade dieser Range + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$idx} = $factor; + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{$idx} = $qual; } - + else { + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$crang} = $pvrlsum; + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$crang} = $pvfcsum; + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$crang} = $dnum; + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$crang} = $factor; + $data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{$crang} = $qual; + } + return ($oldfac, $factor, $dnum); } @@ -10416,13 +10520,13 @@ sub _checkSetupNotComplete { if ($cwd) { CommandAttr (undef, "$name ctrlWeatherDev1 $cwd"); } - + my $mdr = ReadingsVal ($name, 'moduleDirection', undef); # 09.02.2024 if ($mdr) { readingsSingleUpdate ($hash, 'moduleAzimuth', $mdr, 0); } - my $mta = ReadingsVal ($name, 'moduleTiltAngle', undef); + my $mta = ReadingsVal ($name, 'moduleTiltAngle', undef); if ($mta) { readingsSingleUpdate ($hash, 'moduleDeclination', $mta, 0); } @@ -10435,8 +10539,8 @@ sub _checkSetupNotComplete { my $medev = ReadingsVal ($name, 'currentMeterDev', undef); # Meter Device my $peaks = ReadingsVal ($name, 'modulePeakString', undef); # String Peak - my $dir = ReadingsVal ($name, 'moduleAzimuth', undef); # Modulausrichtung Konfig (Azimut) - my $ta = ReadingsVal ($name, 'moduleDeclination', undef); # Modul Neigungswinkel Konfig + my $maz = ReadingsVal ($name, 'moduleAzimuth', undef); # Modulausrichtung Konfig (Azimut) + my $mdec = ReadingsVal ($name, 'moduleDeclination', undef); # Modul Neigungswinkel Konfig my $mrt = ReadingsVal ($name, 'moduleRoofTops', undef); # RoofTop Konfiguration (SolCast API) my $vrmcr = SolCastAPIVal ($hash, '?VRM', '?API', 'credentials', ''); # Victron VRM Credentials gesetzt @@ -10452,7 +10556,7 @@ sub _checkSetupNotComplete { my $lang = getLang ($hash); my (undef, $disabled, $inactive) = controller ($name); - + if ($disabled || $inactive) { $ret .= ""; $ret .= ""; @@ -10472,9 +10576,9 @@ sub _checkSetupNotComplete { my $chkicon = "$img"; my $chktitle = $htitles{plchk}{$lang}; - if (!$is || !$wedev || !$radev || !$indev || !$medev || !$peaks || - (isSolCastUsed ($hash) ? (!$rip || !$mrt) : isVictronKiUsed ($hash) ? !$vrmcr : (!$dir || !$ta )) || - (isForecastSolarUsed ($hash) ? !$coset : '') || + if (!$is || !$wedev || !$radev || !$indev || !$medev || !$peaks || + (isSolCastUsed ($hash) ? (!$rip || !$mrt) : isVictronKiUsed ($hash) ? !$vrmcr : (!$maz || !$mdec )) || + (isForecastSolarUsed ($hash) ? !$coset : '') || !defined $pv0) { $ret .= "
"; $ret .= ""; @@ -10484,6 +10588,12 @@ sub _checkSetupNotComplete { if (!$wedev) { ## no critic 'Cascading' $ret .= $hqtxt{cfd}{$lang}; } + elsif (!$is) { + $ret .= $hqtxt{ist}{$lang}; + } + elsif (!$peaks) { + $ret .= $hqtxt{mps}{$lang}; + } elsif (!$radev) { $ret .= $hqtxt{crd}{$lang}; } @@ -10493,22 +10603,16 @@ sub _checkSetupNotComplete { elsif (!$medev) { $ret .= $hqtxt{mid}{$lang}; } - elsif (!$is) { - $ret .= $hqtxt{ist}{$lang}; - } - elsif (!$peaks) { - $ret .= $hqtxt{mps}{$lang}; - } elsif (!$rip && isSolCastUsed ($hash)) { $ret .= $hqtxt{rip}{$lang}; } elsif (!$mrt && isSolCastUsed ($hash)) { $ret .= $hqtxt{mrt}{$lang}; } - elsif (!$dir && !isSolCastUsed ($hash) && !isVictronKiUsed ($hash)) { + elsif (!$maz && !isSolCastUsed ($hash) && !isVictronKiUsed ($hash)) { $ret .= $hqtxt{mdr}{$lang}; } - elsif (!$ta && !isSolCastUsed ($hash) && !isVictronKiUsed ($hash)) { + elsif (!$mdec && !isSolCastUsed ($hash) && !isVictronKiUsed ($hash)) { $ret .= $hqtxt{mta}{$lang}; } elsif (!$vrmcr && isVictronKiUsed ($hash)) { @@ -10621,10 +10725,10 @@ sub _graphicHeader { $img = FW_makeImage('edit_settings@grey'); my $chkicon = "$img"; my $chktitle = $htitles{plchk}{$lang}; - + ## Forum Thread-Icon ###################### - $img = FW_makeImage('time_note@grey'); + $img = FW_makeImage('time_note@grey'); my $fthicon = "$img"; my $fthtitle = $htitles{jtsfft}{$lang}; @@ -10646,9 +10750,9 @@ sub _graphicHeader { ## Solare API Sektion ######################## my $api = isSolCastUsed ($hash) ? 'SolCast:' : - isForecastSolarUsed ($hash) ? 'Forecast.Solar:' : - isVictronKiUsed ($hash) ? 'VictronVRM:' : - isDWDUsed ($hash) ? 'DWD:' : + isForecastSolarUsed ($hash) ? 'Forecast.Solar:' : + isVictronKiUsed ($hash) ? 'VictronVRM:' : + isDWDUsed ($hash) ? 'DWD:' : q{}; my $nscc = ReadingsVal ($name, 'nextSolCastCall', '?'); @@ -10740,14 +10844,14 @@ sub _graphicHeader { elsif ($api =~ /DWD/xs) { $nscc = ReadingsVal ($name, 'nextCycletime', '?'); $api .= ' '.$lrt; - + if ($scrm eq 'success') { my $ptr = CurrentVal ($hash, 'dwdRad1hAge', '-'); $img = FW_makeImage('10px-kreis-gruen.png', $htitles{scaresps}{$lang}.' '.$htitles{predtime}{$lang}.' '.$ptr); - + if ($paref->{t} - CurrentVal ($hash, 'dwdRad1hAgeTS', 0) > 7200) { my $agetit = $htitles{arsrad2o}{$lang}; - $agetit =~ s//$name/xs; + $agetit =~ s//$name/xs; $img = FW_makeImage('10px-kreis-gelb.png', $agetit.' '.$htitles{predtime}{$lang}.' '.$ptr); } } @@ -11685,6 +11789,8 @@ sub _beamGraphicFirstHour { $hfcg->{0}{weather} = HistoryVal ($hash, $hfcg->{0}{day_str}, $hfcg->{0}{time_str}, 'weatherid', 999); $hfcg->{0}{wcc} = HistoryVal ($hash, $hfcg->{0}{day_str}, $hfcg->{0}{time_str}, 'wcc', '-'); + $hfcg->{0}{sunalt} = HistoryVal ($hash, $hfcg->{0}{day_str}, $hfcg->{0}{time_str}, 'sunalt', '-'); + $hfcg->{0}{sunaz} = HistoryVal ($hash, $hfcg->{0}{day_str}, $hfcg->{0}{time_str}, 'sunaz', '-'); } else { $val1 = CircularVal ($hash, $hfcg->{0}{time_str}, 'pvfc', 0); @@ -11755,6 +11861,8 @@ sub _beamGraphicRemainingHours { $hfcg->{$i}{weather} = HistoryVal ($hash, $ds, $hfcg->{$i}{time_str}, 'weatherid', 999); $hfcg->{$i}{wcc} = HistoryVal ($hash, $ds, $hfcg->{$i}{time_str}, 'wcc', '-'); + $hfcg->{$i}{sunalt} = HistoryVal ($hash, $ds, $hfcg->{$i}{time_str}, 'sunalt', '-'); + $hfcg->{$i}{sunaz} = HistoryVal ($hash, $ds, $hfcg->{$i}{time_str}, 'sunaz', '-'); } else { $nh = sprintf '%02d', $i + $offset; @@ -11769,6 +11877,8 @@ sub _beamGraphicRemainingHours { $val4 = NexthoursVal ($hash, 'NextHour'.$nh, 'confc', 0); $hfcg->{$i}{weather} = NexthoursVal ($hash, 'NextHour'.$nh, 'weatherid', 999); $hfcg->{$i}{wcc} = NexthoursVal ($hash, 'NextHour'.$nh, 'cloudcover', '-'); + $hfcg->{$i}{sunalt} = NexthoursVal ($hash, 'NextHour'.$nh, 'sunalt', '-'); + $hfcg->{$i}{sunaz} = NexthoursVal ($hash, 'NextHour'.$nh, 'sunaz', '-'); #$val4 = (ReadingsVal($name,"NextHour".$ii."_IsConsumptionRecommended",'no') eq 'yes') ? $icon : undef; } @@ -12158,6 +12268,12 @@ sub __weatherOnBeam { $wcc += 0 if(isNumeric ($wcc)); # Javascript Fehler vermeiden: https://forum.fhem.de/index.php/topic,117864.msg1233661.html#msg1233661 $title .= ': '.$wcc; + $title .= ' '; + $title .= $htitles{sunpos}{$lang}.':'; + $title .= ' '; + $title .= $htitles{elevatio}{$lang}.' '.$hfcg->{$i}{sunalt}; + $title .= ' '; + $title .= $htitles{azimuth}{$lang}.' '.$hfcg->{$i}{sunaz}; if ($icon_name eq 'unknown') { debugLog ($paref, "graphic", "unknown weather id: ".$hfcg->{$i}{weather}.", please inform the maintainer"); @@ -12708,7 +12824,7 @@ sub _addHourAiRawdata { if (!$aln) { storeReading ('.signaldone_'.sprintf("%02d",$h), 'done'); - debugLog ($paref, 'pvCorrection', "Autolearning is switched off for hour: $h -> skip add AI raw data"); + debugLog ($paref, 'pvCorrectionRead', "Autolearning is switched off for hour: $h -> skip add AI raw data"); return; } @@ -12735,7 +12851,7 @@ sub __calcFcQuality { my $pvrl = shift; # PV reale Erzeugung return if(!$pvfc || !$pvrl); - + $pvrl = sprintf "%.0f", $pvrl; $pvfc = sprintf "%.0f", $pvfc; @@ -12982,7 +13098,7 @@ return; ################################################################ # AI Ergebnis für ermitteln ################################################################ -sub aiGetResult { +sub aiGetResult { my $paref = shift; my $hash = $paref->{hash}; my $name = $paref->{name}; @@ -13000,7 +13116,7 @@ sub aiGetResult { my $rad1h = NexthoursVal ($hash, $nhidx, "rad1h", 0); return "no rad1h for hod: $hod" if($rad1h <= 0); - + debugLog ($paref, 'aiData', "Start AI result check for hod: $hod"); my $wcc = NexthoursVal ($hash, $nhidx, 'cloudcover', 0); @@ -13080,7 +13196,7 @@ sub _aiGetSpread { debugLog ($paref, 'aiData', qq{AI no accurate result found with initial value "Rad1h: $rad1h" (hod: $hod)}); debugLog ($paref, 'aiData', qq{AI test Rad1h variance "$dtn" and positive/negative spread with step size "$step"}); - + my $gra = { temp => $temp, wcc => $wcc, @@ -13093,7 +13209,7 @@ sub _aiGetSpread { for ($p = $rad1h + $step; $p <= $rad1h + $dtn; $p += $step) { $p = sprintf "%.2f", $p; $gra->{rad1h} = $p; - + debugLog ($paref, 'aiData', qq{AI positive test value "Rad1h: $p"}); eval { $pos = $dtree->get_result (attributes => $gra); @@ -13113,7 +13229,7 @@ sub _aiGetSpread { last if($n <= 0); $n = sprintf "%.2f", $n; $gra->{rad1h} = $n; - + debugLog ($paref, 'aiData', qq{AI negative test value "Rad1h: $n"}); eval { $neg = $dtree->get_result (attributes => $gra); @@ -13215,7 +13331,7 @@ sub aiAddRawData { my $rr1c = HistoryVal ($hash, $pvd, $hod, 'rr1c', 0); my $sunalt = HistoryVal ($hash, $pvd, $hod, 'sunalt', 0); my $sunaz = HistoryVal ($hash, $pvd, $hod, 'sunaz', 0); - + my $tbin = temp2bin ($temp); my $cbin = cloud2bin ($wcc); my $sabin = sunalt2bin ($sunalt); @@ -13324,7 +13440,7 @@ sub setPVhistory { my $batinthishour = $paref->{batinthishour}; # Batterieladung in Stunde my $btotin = $paref->{batintotal}; # totale Batterieladung my $batoutthishour = $paref->{batoutthishour}; # Batterieentladung in Stunde - my $btotout = $paref->{batouttotal}; # totale Batterieentladung + my $btotout = $paref->{batouttotal}; # totale Batterieentladung my $gcthishour = $paref->{gctotthishour} // 0; # Netzbezug my $fithishour = $paref->{gftotthishour} // 0; # Netzeinspeisung my $con = $paref->{con} // 0; # realer Hausverbrauch Energie @@ -13468,7 +13584,7 @@ sub setPVhistory { if ($histname =~ /sunaz|sunalt/xs) { # Sonnenstand Azimuth / Altitude $data{$type}{$name}{pvhist}{$day}{$nhour}{$histname} = $val; } - + if ($histname =~ /cyclescsm[0-9]+$/xs) { # Anzahl Tageszyklen des Verbrauchers $data{$type}{$name}{pvhist}{$day}{99}{$histname} = $val; } @@ -13517,7 +13633,7 @@ sub setPVhistory { $data{$type}{$name}{pvhist}{$day}{99}{wcc} = q{}; } - if ($histname eq 'totalrain') { # Gesamtniederschlag (1-stündig) letzte 1 Stunde + if ($histname eq 'totalrain') { # Gesamtniederschlag (1-stündig) letzte 1 Stunde $data{$type}{$name}{pvhist}{$day}{$nhour}{rr1c} = $val; $data{$type}{$name}{pvhist}{$day}{99}{rr1c} = q{}; } @@ -13743,7 +13859,7 @@ sub listDataPool { if (!keys %{$h}) { return qq{Circular cache is empty.}; } - + for my $idx (sort keys %{$h}) { my $pvrl = CircularVal ($hash, $idx, 'pvrl', '-'); my $pvfc = CircularVal ($hash, $idx, 'pvfc', '-'); @@ -13779,11 +13895,11 @@ sub listDataPool { my $idbotot = CircularVal ($hash, $idx, "initdaybatouttot", '-'); my $botot = CircularVal ($hash, $idx, "batouttot", '-'); my $rtaitr = CircularVal ($hash, $idx, "runTimeTrainAI", '-'); - + 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 $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); @@ -13815,7 +13931,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', '-'); @@ -13837,7 +13953,7 @@ sub listDataPool { my $don = NexthoursVal ($hash, $idx, 'DoN', '-'); my $sunaz = NexthoursVal ($hash, $idx, 'sunaz', '-'); my $sunalt = NexthoursVal ($hash, $idx, 'sunalt', '-'); - + $sq .= "\n" if($sq); $sq .= $idx." => "; $sq .= "starttime: $nhts, hourofday: $hod, today: $today"; @@ -13860,16 +13976,17 @@ sub listDataPool { for my $idx (sort keys %{$h}) { my $nhfc = NexthoursVal ($hash, $idx, 'pvfc', undef); next if(!$nhfc); - + my $nhts = NexthoursVal ($hash, $idx, 'starttime', '-'); my $pvcorrf = NexthoursVal ($hash, $idx, 'pvcorrf', '-/-'); my $aihit = NexthoursVal ($hash, $idx, 'aihit', '-'); my $pvfc = NexthoursVal ($hash, $idx, 'pvfc', '-'); my $neff = NexthoursVal ($hash, $idx, 'cloudcover', '-'); - + my $sunalt = NexthoursVal ($hash, $idx, 'sunalt', '-'); + my ($f,$q) = split "/", $pvcorrf; $sq .= "\n" if($sq); - $sq .= "Start: $nhts, Quality: $q, Factor: $f, AI usage: $aihit, PV expect: $pvfc Wh, Cloud: $neff"; + $sq .= "Start: $nhts, Quality: $q, Factor: $f, AI usage: $aihit, PV expect: $pvfc Wh, Sun Alt: $sunalt, Cloud: $neff"; } } @@ -13963,7 +14080,7 @@ return $sq; } ################################################################ -# Hashwert aus CircularVal in formatierten String umwandeln +# Hashwert aus CircularVal in formatierten String umwandeln ################################################################ sub _ldchash2val { my $paref = shift; @@ -13974,31 +14091,31 @@ sub _ldchash2val { my $ret = qq{}; my $ret2 = qq{}; - + if (ref $cval eq 'HASH') { no warnings 'numeric'; - + for my $f (sort {$a<=>$b} keys %{$pool->{$idx}{$key}}) { next if($f eq 'simple'); - if ($f !~ /2.{2}/xs) { + if ($f !~ /\./xs) { $ret .= " " if($ret); $ret .= "$f=".$pool->{$idx}{$key}{$f}; my $ct = ($ret =~ tr/=// // 0) / 10; - $ret .= "\n " if($ct =~ /^([1-9])?$/); + $ret .= "\n " if($ct =~ /^[1-9](.{1})?$/); } - elsif ($f =~ /2.{2}/xs) { + elsif ($f =~ /\./xs) { $ret2 .= " " if($ret2); $ret2 .= "$f=".$pool->{$idx}{$key}{$f}; my $ct2 = ($ret2 =~ tr/=// // 0) / 10; - $ret2 .= "\n " if($ct2 =~ /^([1-9])?$/); + $ret2 .= "\n " if($ct2 =~ /^[1-9](.{1})?$/); } } - + if ($ret2) { $ret .= "\n " if($ret && $ret !~ /\n\s+$/xs); $ret .= $ret2; } - + use warnings; if (defined $pool->{$idx}{$key}{simple}) { @@ -14119,20 +14236,20 @@ sub checkPlantConfig { ## Check Attribute DWD Wetterdevice ##################################### - for my $step (1..$weatherDevMax) { - my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$step, ''); + for my $step (1..$weatherDevMax) { + my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$step, ''); next if(!$fcname && $step ne 1); if (!$fcname || !$defs{$fcname}) { $result->{'DWD Weather Attributes'}{state} = $nok; - + if (!$fcname) { $result->{'DWD Weather Attributes'}{result} .= qq{No DWD device is defined in attribute "ctrlWeatherDev$step".
}; } else { $result->{'DWD Weather Attributes'}{result} .= qq{The DWD device "$fcname" doesn't exist.
}; } - + $result->{'DWD Weather Attributes'}{fault} = 1; } else { @@ -14144,9 +14261,9 @@ sub checkPlantConfig { $result->{'DWD Weather Attributes'}{fault} = 1; } else { - $result->{'DWD Weather Attributes'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: ctrlWeatherDev$step)
"; + $result->{'DWD Weather Attributes'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: ctrlWeatherDev$step)
"; } - + $result->{'DWD Weather Attributes'}{note} .= qq{checked parameters and attributes of device "$fcname":
}; $result->{'DWD Weather Attributes'}{note} .= 'forecastProperties -> '.join (' ', @dweattrmust).'
'; } @@ -14174,25 +14291,25 @@ sub checkPlantConfig { $result->{'DWD Radiation Properties'}{note} .= qq{
Check the parameters set in device '$raname': attribute 'forecastProperties'
}; $result->{'DWD Radiation Properties'}{fault} = 1; } - + if (time() - CurrentVal ($hash, 'dwdRad1hAgeTS', 0) > 7200) { $result->{'DWD Radiation Properties'}{state} = $warn; $result->{'DWD Radiation Properties'}{note} .= qq{The Prediction time of radiation data (Rad1h) is older than 2 hours.
}; $result->{'DWD Radiation Properties'}{note} .= qq{Check the DWD device '$raname' for proper functioning of the data retrieval.
}; - $result->{'DWD Radiation Properties'}{warn} = 1; + $result->{'DWD Radiation Properties'}{warn} = 1; } - + if (!$err) { $result->{'DWD Radiation Properties'}{result} .= $hqtxt{fulfd}{$lang}.'
'; } } - + if (!$result->{'DWD Radiation Properties'}{fault}) { $result->{'DWD Radiation Properties'}{result} = $hqtxt{fulfd}{$lang}; $result->{'DWD Radiation Properties'}{note} .= qq{
checked parameters and attributes device "$raname":
}; $result->{'DWD Radiation Properties'}{note} .= 'Age of Rad1h data
'; $result->{'DWD Radiation Properties'}{note} .= 'forecastProperties -> '.join (' ', @draattrmust).'
'; - } + } } ## Check Rooftop und Roof Ident Pair Settings (SolCast) @@ -14284,7 +14401,7 @@ sub checkPlantConfig { $result->{'Common Settings'}{note} .= qq{Set the coordinates of your installation in the longitude attribute of the global device.
}; $result->{'Common Settings'}{warn} = 1; } - + if (!$alt) { $result->{'Common Settings'}{state} = $nok; $result->{'Common Settings'}{result} .= qq{Attribute altitude in global device is not set.
}; @@ -14460,7 +14577,7 @@ sub checkPlantConfig { $result->{'Common Settings'}{note} .= qq{pvCorrectionFactor_Auto, global dnsServer, vrmCredentials
}; } } - + if (!$result->{'Common Settings'}{fault}) { $result->{'Common Settings'}{result} = $hqtxt{fulfd}{$lang}; $result->{'Common Settings'}{note} .= qq{global latitude, global longitude, global altitude, global language
}; @@ -14541,10 +14658,10 @@ sub checkPlantConfig { $cf = $result->{$key}{fault} if($result->{$key}{fault}); $wn = $result->{$key}{warn} if($result->{$key}{warn}); $io = $result->{$key}{info} if($result->{$key}{info}); - + $result->{$key}{state} = $warn if($result->{$key}{warn}); $result->{$key}{state} = $nok if($result->{$key}{fault}); - + $out .= qq{
}; $out .= qq{}; $out .= qq{}; @@ -14799,11 +14916,11 @@ sub createAssociatedWith { 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] // ""; @@ -14845,14 +14962,14 @@ sub createAssociatedWith { push @nd, $indev; push @nd, $medev; push @nd, $badev; - + 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); } @@ -15966,7 +16083,7 @@ return; # batouttotal - totale Batterieentladung (Wh) # batout - Batterieentladung der Stunde (Wh) # batmsoc - max. SOC des Tages (%) -# batmaxsoc - maximum SOC (%) des Tages +# batmaxsoc - maximum SOC (%) des Tages # batsetsoc - optimaler (berechneter) SOC (%) für den Tag # weatherid - Wetter ID # wcc - Grad der Bewölkung @@ -16026,8 +16143,7 @@ return $def; # rr1c - Gesamtniederschlag (1-stündig) letzte 1 Stunde kg/m2 # temp - Außentemperatur # pvcorrf - PV Autokorrekturfaktoren (HASH), -# - ohne Wertesummand: Faktoren bezogen auf Cloudcover -# - 200 + sunalt: Faktoren bezogen auf Sonne Altitude +# - . # lastTsMaxSocRchd - Timestamp des letzten Erreichens von SoC >= maxSoC # nextTsMaxSocChge - Timestamp bis zu dem die Batterie mindestens einmal maxSoC erreichen soll # days2care - verbleibende Tage bis der Batterie Pflege-SoC (default $maxSoCdef) erreicht sein soll @@ -16063,107 +16179,75 @@ sub CircularVal { return $def; } -################################################################ -# Wert des Autokorrekturfaktors und dessen Qualität -# für eine bestimmte Bewölkungs-Range aus dem circular-Hash -# zurückliefern -# Usage: -# ($f,$q) = CircularCloudkorrVal ($hash, $hod, $crang, $def) +################################################################### +# Wert des Autokorrekturfaktors +# für eine bestimmte Sun Altitude-Range aus dem Circular-Hash +# zurückliefern +# Usage: +# $f = CircularSunCloudkorrVal ($hash, $hod, $sabin, $crang, $def) # -# $f: Korrekturfaktor f. Stunde des Tages -# $q: Qualität des Korrekturfaktors +# $f: Korrekturfaktor f. Stunde des Tages # -# $hod: Stunde des Tages (01,02,...,24) -# $crang: Range Bewölkung (1...100) oder "simple" -# $def: Defaultwert +# $hod: Stunde des Tages (01,02,...,24) +# $sabin: Sun Altitude Bin (0..90) +# $crang: Bewölkung Bin (0..100) oder "simple" +# $def: Defaultwert # -################################################################ -sub CircularCloudkorrVal { +################################################################### +sub CircularSunCloudkorrVal { my $hash = shift; my $hod = shift; + my $sabin = shift; my $crang = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my $corrf = $def; + my $qual = $def; + my $idx = 'simple'; - my $corrf = $def; - my $quality = $def; + if ($crang ne 'simple') { + $idx = $sabin.'.'.$crang; + } 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}{$crang})) { - $corrf = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$crang}; + defined($data{$type}{$name}{circular}{$hod}{pvcorrf}{$idx})) { + $corrf = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$idx}; } 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}{$crang})) { - $quality = $data{$type}{$name}{circular}{$hod}{quality}{$crang}; + defined($data{$type}{$name}{circular}{$hod}{quality}{$idx})) { + $qual = $data{$type}{$name}{circular}{$hod}{quality}{$idx}; } -return ($corrf, $quality); -} - -################################################################ -# Wert des Autokorrekturfaktors -# für eine bestimmte Sun Altitude-Range aus dem Circular-Hash -# zurückliefern -# Usage: -# $f = CircularSunaltkorrVal ($hash, $hod, $crang, $def) -# -# $f: Korrekturfaktor f. Stunde des Tages -# -# $hod: Stunde des Tages (01,02,...,24) -# $sabin: Range Sun Altitude (0..90) -# $def: Defaultwert -# -################################################################ -sub CircularSunaltkorrVal { - my $hash = shift; - my $hod = shift; - my $sunalt = shift; - my $def = shift; - - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - my $corrf = $def; - - return $def if(!$sunalt); - - my $sabin = sunalt2bin ($sunalt); - $sabin = 200 + $sabin; - - 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}{$sabin})) { - $corrf = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$sabin}; - } - -return ($corrf); +return ($corrf, $qual); } ######################################################################################################## # 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, $crang, $def) +# ($pvrlsum, $pvfcsum, $dnumsum) = CircularSumVal ($hash, $hod, $sabin, $crang, $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) -# $crang: Range Bewölkung (1...100) oder "simple" +# $hod: Stunde des Tages (01,02,..,24) +# $sabin: Sun Altitude Bin (0..90) +# $crang: Bewölkung Bin (1..100) oder "simple" # $def: Defaultwert # ####################################################################################################### sub CircularSumVal { my $hash = shift; my $hod = shift; + my $sabin = shift; my $crang = shift; my $def = shift; @@ -16173,26 +16257,31 @@ sub CircularSumVal { my $pvrlsum = $def; my $pvfcsum = $def; my $dnumsum = $def; + my $idx = 'simple'; + + if ($crang ne 'simple') { + $idx = $sabin.'.'.$crang; + } 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}{$crang})) { - $pvrlsum = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$crang}; + defined($data{$type}{$name}{circular}{$hod}{pvrlsum}{$idx})) { + $pvrlsum = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$idx}; } 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}{$crang})) { - $pvfcsum = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$crang}; + defined($data{$type}{$name}{circular}{$hod}{pvfcsum}{$idx})) { + $pvfcsum = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$idx}; } 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}{$crang})) { - $dnumsum = $data{$type}{$name}{circular}{$hod}{dnumsum}{$crang}; + defined($data{$type}{$name}{circular}{$hod}{dnumsum}{$idx})) { + $dnumsum = $data{$type}{$name}{circular}{$hod}{dnumsum}{$idx}; } return ($pvrlsum, $pvfcsum, $dnumsum); @@ -16366,7 +16455,7 @@ return $def; # wcc - Bewölkung als Bin # rr1c - Gesamtniederschlag (1-stündig) letzte 1 Stunde kg/m2 # hod - Stunde des Tages -# sunalt - Höhe der Sonne (in Dezimalgrad) +# sunalt - Höhe der Sonne (in Dezimalgrad) # pvrl - reale PV Erzeugung # # $def: Defaultwert @@ -16578,7 +16667,7 @@ to ensure that the system configuration is correct. After the definition of the device, depending on the forecast sources used, it is mandatory to store additional plant-specific information with the corresponding set commands.
- The following set commands and attributes are used to store information that is relevant for the function of the + The following set commands and attributes are used to store information that is relevant for the function of the module:

    @@ -16945,7 +17034,7 @@ to ensure that the system configuration is correct.

- +
  • moduleAzimuth <Stringname1>=<dir> [<Stringname2>=<dir> <Stringname3>=<dir> ...]
    @@ -16984,7 +17073,7 @@ to ensure that the system configuration is correct.

- +
  • moduleDeclination <Stringname1>=<Angle> [<Stringname2>=<Angle> <Stringname3>=<Angle> ...]
    @@ -17122,7 +17211,7 @@ to ensure that the system configuration is correct. Switches the automatic prediction correction on/off. The mode of operation differs depending on the selected method. - The correction behaviour can be influenced with the + The correction behaviour can be influenced with the affectMaxDayVariance attribute.
    (default: off) @@ -17326,12 +17415,12 @@ to ensure that the system configuration is correct.

- +
  • dwdCatalog

    The German Weather Service (DWD) provides a catalog of MOSMIX stations.
    - The stations provide data whose meaning is explained in this + The stations provide data whose meaning is explained in this Overview. The DWD distinguishes between MOSMIX_L and MOSMIX_S stations, which differ in terms of update frequency and data volume.
    @@ -17339,10 +17428,10 @@ to ensure that the system configuration is correct. ./FHEM/FhemUtils/DWDcat_SolarForecast.
    The catalog can be extensively filtered and saved in GPS Exchange Format (GPX). The latitude and logitude coordinates are displayed in decimal degrees.
    - Regex expressions in the corresponding keys are used for filtering. The Regex is enclosed in + Regex expressions in the corresponding keys are used for filtering. The Regex is enclosed in ^...$ for evaluation.
    The following parameters can be specified. Without parameters, the entire catalog is output:

    - +
$key $result->{$key}{state}
@@ -17364,7 +17453,7 @@ to ensure that the system configuration is correct. get <name> dwdCatalog byName exportgpx lat=(48|49|50|51|52)\..* lon=([5-9]|10|11|12|13|14|15)\..*
# filters the stations largely to German locations beginning with "ST" and exports the data in GPS Exchange format - +
@@ -17461,7 +17550,7 @@ to ensure that the system configuration is correct. - +
temp predicted outdoor temperature
today has value '1' if start date on current day
rr1c Total precipitation during the last hour kg/m2
rrange range of total rain
rrange range of total rain
wid ID of the predicted weather
wcc predicted degree of cloudiness
@@ -17505,7 +17594,7 @@ to ensure that the system configuration is correct. pvcorrf Autocorrection factor used / forecast quality achieved rad1h global radiation (kJ/m2) sunalt Altitude of the sun (in decimal degrees) - sunaz Azimuth of the sun (in decimal degrees) + sunaz Azimuth of the sun (in decimal degrees) wid Weather identification number wcc effective cloud cover rr1c Total precipitation during the last hour kg/m2 @@ -17522,6 +17611,8 @@ to ensure that the system configuration is correct. The hours 01 - 24 refer to the hour of the day, e.g. the hour 09 refers to the time from 08 - 09 o'clock.
Hour 99 has a special function.
+ The values of the keys pvcorrf, quality, pvrlsum, pvfcsum and dnumsum are coded in the form + <range sun elevation>.<cloud cover range>.
Explanation of the values:


- +
  • ctrlWeatherDevX

    Defines the device (type DWD_OpenData), which provides the required weather data (cloudiness, precipitation, etc.).
    - If no device of this type exists, the selection list is empty and a device must first be defined + 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 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:

    @@ -18211,8 +18303,8 @@ to ensure that the system configuration is correct.
    - - Note: If the latitude and longitude attributes are set in the global device, the sunrise and sunset + + Note: If the latitude and longitude attributes are set in the global device, the sunrise and sunset result from this information.

  • @@ -19067,7 +19159,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
    - +
    - +
    - +
    - - Hinweis: Sind die Attribute latitude und longitude im global Device gesetzt, ergibt sich der + + Hinweis: Sind die Attribute latitude und longitude im global Device gesetzt, ergibt sich der Sonnenauf- und Sonnenuntergang aus diesen Angaben.