diff --git a/fhem/CHANGED b/fhem/CHANGED index 15659d4b0..6f9fe5c28 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 + - change: 76_SolarForecast: remove graphicBeam1MaxVal,ctrlAreaFactorUsage - feature: 76_SolarForecast: new special readings todayBatInSum,todayBatOutSum - change: 73_AutoShuttersControl: Change version - feature: 76_SolarForecast: show historical battery SoC when displaying the diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 13490fc24..cae8be787 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -34,6 +34,7 @@ package FHEM::SolarForecast; use strict; use warnings; +#use lib qw(/opt/fhem/FHEM /opt/fhem/lib); # für Syntaxcheck mit: perl -c /opt/fhem/FHEM/76_SolarForecast.pm use POSIX; use GPUtils qw(GP_Import GP_Export); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt use Time::HiRes qw(gettimeofday tv_interval); @@ -159,6 +160,8 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.44.4" => "26.01.2025 _getlistPVCircular: change width of output, new sub _listDataPoolPvHist, fix bug in hrepl Hash ". + "remove Attr graphicBeam1MaxVal,ctrlAreaFactorUsage ", "1.44.3" => "25.01.2025 Notification System: minor changes, special Readings todayBatInSum todayBatOutSum ", "1.44.2" => "23.01.2025 _batChargeRecmd: user storeffdef, show historical battery SoC when displaying the battery in the bar graph ", "1.44.1" => "20.01.2025 Notification system: minor fixes, integration of controls_solarforecast_messages_test/prod ". @@ -174,7 +177,7 @@ my %vNotesIntern = ( "trackFlex now default in DWD Model, replace title Charging recommendation by Charging release ". "_saveEnergyConsumption: add dowrite flag, edit comref ", "1.43.1" => "11.01.2025 _batChargeRecmd: bugfix PV daily surplus update, _collectAllRegConsumers: fix interruptable hysteresis ". - "__batRcmdOnBeam: show soc forecast for hour 00 and fix english translation ". + "__batteryOnBeam: show soc forecast for hour 00 and fix english translation ". "_batChargeRecmd: consider battery capacity as part of total capacity ", "1.43.0" => "10.01.2025 graphicShowNight: add possible Time Sync of chart bar level 1 and the other ". "_addDynAttr: minor fix for graphicBeamXContent, new attr ctrlNextHoursSoCForecastReadings ", @@ -707,17 +710,17 @@ my %hmoon = ( ); my %hrepl = ( # Zeichenersetzungen - '0' => 'a', - '1' => 'b', - '2' => 'c', - '3' => 'd', - '4' => 'e', - '5' => 'f', - '6' => 'g', - '7' => 'h', - '8' => 'i', - '9' => 'j', - '.' => 'k', + '0' => 'a', + '1' => 'b', + '2' => 'c', + '3' => 'd', + '4' => 'e', + '5' => 'f', + '6' => 'g', + '7' => 'h', + '8' => 'i', + '9' => 'j', + '\.' => 'k', ); my %hqtxt = ( # Hash (Setup) Texte @@ -1448,12 +1451,11 @@ sub Initialize { $readingFnAttributes; ## Hinweis: graphicBeamXContent wird in _addDynAttr hinzugefügt - - + ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ########################################################################################################################## - my $av1 = "obsolete#-#the#attribute#will#be#deleted#soon"; # 12.01.25 - $hash->{AttrList} .= " graphicBeam1MaxVal:$av1 ctrlAreaFactorUsage:$av1 "; + # my $av1 = "obsolete#-#the#attribute#will#be#deleted#soon"; + # $hash->{AttrList} .= " graphicBeam1MaxVal:$av1 ctrlAreaFactorUsage:$av1 "; ########################################################################################################################## $hash->{FW_hideDisplayName} = 1; # Forum 88667 @@ -4743,7 +4745,7 @@ sub _getlistPVCircular { my $hash = $defs{$name}; my $ret = listDataPool ($hash, 'circular', $arg); - $ret .= lineFromSpaces ($ret, 20); + $ret .= lineFromSpaces ($ret, 5); return $ret; } @@ -5536,16 +5538,16 @@ sub Attr { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ###################################################################################################################### - if ($cmd eq 'set' && $aName =~ /^graphicBeam1MaxVal|ctrlAreaFactorUsage$/) { # 12.01.25 - my $msg = "The attribute $aName is obsolete and will be deleted soon. Please save your Configuration."; - if (!$init_done) { - Log3 ($name, 1, "$name - $msg"); - return qq{Device "$name" -> $msg}; - } - else { - return $msg; - } - } + #if ($cmd eq 'set' && $aName =~ /^graphicBeam1MaxVal|ctrlAreaFactorUsage$/) { + # my $msg = "The attribute $aName is obsolete and will be deleted soon. Please save your Configuration."; + # if (!$init_done) { + # Log3 ($name, 1, "$name - $msg"); + # return qq{Device "$name" -> $msg}; + # } + # else { + # return $msg; + # } + #} ###################################################################################################################### if ($aName eq 'disable') { @@ -8289,14 +8291,6 @@ sub __delObsoleteAPIData { delete $data{$name}{solcastapi}{$idx}{$scd} if($ds && $ds < $refts); } } - - ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! - ########################################################################################################################## - # 01.12.2024 - for my $idx (keys %{$data{$name}{solcastapi}{'?All'}}) { # Wetterindexe löschen (kann später raus) - delete $data{$name}{solcastapi}{'?All'}{$idx} if($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs); - } - ##################################################################################################################### } ## veraltete Strings aus Strings-Hash löschen @@ -14848,7 +14842,7 @@ sub _beamGraphic { my $ret = q{}; $ret .= __weatherOnBeam ($paref) if($weather); - $ret .= __batRcmdOnBeam ($paref); + $ret .= __batteryOnBeam ($paref); my $m = $paref->{modulo} % 2; if ($show_diff eq 'top') { # Zusätzliche Zeile Ertrag - Verbrauch @@ -15252,7 +15246,7 @@ return $ret; ################################################################ # Batterieladeempfehlung in Balkengrafik ################################################################ -sub __batRcmdOnBeam { +sub __batteryOnBeam { my $paref = shift; my $name = $paref->{name}; my $maxhours = $paref->{maxhours}; @@ -15369,15 +15363,14 @@ sub __batRcmdOnBeam { } ); - $title .= defined $currsoc ? "\n".$htitles{socbacur}{$lang}.": ".$currsoc." %" : ''; - - debugLog ($paref, 'graphic', "Battery $bn pos >$i< day: $day_str, time: $time_str, Power ('-' = out): ".(defined $bpower ? $bpower : 'undef'). - " W, Rcmd: ".(defined $hfcg->{$i}{'rcdchargebat'.$bn} ? $hfcg->{$i}{'rcdchargebat'.$bn} : 'undef'). - ", SoC: ".(defined $hfcg->{$i}{'soc'.$bn} ? $hfcg->{$i}{'soc'.$bn} : 'undef')." %"); - + $title .= defined $currsoc ? "\n".$htitles{socbacur}{$lang}.": ".$currsoc." %" : ''; my $image = defined $hfcg->{$i}{'rcdchargebat'.$bn} ? FW_makeImage ($bicon) : ''; $ret .= "$image"; + + debugLog ($paref, 'graphic', "Battery $bn pos >$i< day: $day_str, time: $time_str, Power ('-' = out): ".(defined $bpower ? $bpower : 'undef'). + " W, Rcmd: ".(defined $hfcg->{$i}{'rcdchargebat'.$bn} ? $hfcg->{$i}{'rcdchargebat'.$bn} : 'undef'). + ", SoC: ".(defined $hfcg->{$i}{'soc'.$bn} ? $hfcg->{$i}{'soc'.$bn} : 'undef')." %"); } $ret .= "" if($ret); # freier Platz am Ende der Icon Zeile @@ -17631,7 +17624,70 @@ sub listDataPool { my $par = shift // q{}; my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + my ($sq, $h); + + if ($htol eq "pvhist") { + $sq = _listDataPoolPvHist ($hash, $par); + } + + if ($htol =~ /consumers|inverters|producers|strings|batteries/xs) { + $sq = _listDataPoolVarious ($hash, $htol, $par); + } + + if ($htol eq "circular") { + $sq = _listDataPoolCircular ($hash, $par); + } + + if ($htol eq "nexthours") { + $sq = _listDataPoolNextHours ($name, $par); + } + + if ($htol eq "qualities") { + $sq = _listDataPoolQualities ($name, $par); + } + + if ($htol eq "current") { + $sq = _listDataPoolCurrent ($name, $par); + } + + if ($htol =~ /radiationApiData|weatherApiData|statusApiData/xs) { + $sq = _listDataPoolApiData ($name, $htol, $par); + } + + if ($htol eq "aiRawData") { + $h = $data{$name}{aidectree}{airaw}; + my $maxcnt = keys %{$h}; + if (!$maxcnt) { + return qq{aiRawData values cache is empty.}; + } + + $sq = "Number of datasets: ".$maxcnt."\n"; + + for my $idx (sort keys %{$h}) { + my $hod = AiRawdataVal ($name, $idx, 'hod', '-'); + my $sunalt = AiRawdataVal ($name, $idx, 'sunalt', '-'); + my $sunaz = AiRawdataVal ($name, $idx, 'sunaz', '-'); + my $rad1h = AiRawdataVal ($name, $idx, 'rad1h', '-'); + my $wcc = AiRawdataVal ($name, $idx, 'wcc', '-'); + my $rr1c = AiRawdataVal ($name, $idx, 'rr1c', '-'); + my $pvrl = AiRawdataVal ($name, $idx, 'pvrl', '-'); + my $temp = AiRawdataVal ($name, $idx, 'temp', '-'); + $sq .= "\n"; + $sq .= "$idx => hod: $hod, sunaz: $sunaz, sunalt: $sunalt, rad1h: $rad1h, wcc: $wcc, rr1c: $rr1c, pvrl: $pvrl, temp: $temp"; + } + } + +return $sq; +} + +################################################################ +# Listing des pvHistory Speichers +################################################################ +sub _listDataPoolPvHist { + my $hash = shift; + my $par = shift // q{}; + + my $name = $hash->{NAME}; my ($sq, $h, $hexp); my $export = q{}; @@ -17875,288 +17931,89 @@ sub listDataPool { return $ret; }; - if ($htol eq "pvhist") { - $h = $data{$name}{pvhist}; + $h = $data{$name}{pvhist}; - if (!keys %{$h}) { - return qq{PV cache is empty.}; - } + if (!keys %{$h}) { + return qq{PV cache is empty.}; + } - for my $i (keys %{$h}) { - if (!isNumeric ($i)) { - delete $data{$name}{pvhist}{$i}; - Log3 ($name, 2, qq{$name - INFO - invalid key "$i" was deleted from pvHistory storage}); - } - } - - for my $idx (sort keys %{$h}) { - next if($par && $idx ne $par); - $sq .= $idx." => ".$sub->($idx)."\n"; - } - - if ($export eq 'csv') { - return _writeAsCsv ($hash, $hexp, $pvhexprtcsv.$name.'.csv'); + for my $i (keys %{$h}) { + if (!isNumeric ($i)) { + delete $data{$name}{pvhist}{$i}; + Log3 ($name, 2, qq{$name - INFO - invalid key "$i" was deleted from pvHistory storage}); } } - if ($htol =~ /consumers|inverters|producers|strings|batteries/xs) { - my $sub = $htol eq 'consumers' ? \&ConsumerVal : - $htol eq 'inverters' ? \&InverterVal : - $htol eq 'producers' ? \&ProducerVal : - $htol eq 'strings' ? \&StringVal : - $htol eq 'batteries' ? \&BatteryVal : - ''; - - $h = $data{$name}{$htol}; - - if (!keys %{$h}) { - return ucfirst($htol).qq{ cache is empty.}; - } + for my $idx (sort keys %{$h}) { + next if($par && $idx ne $par); + $sq .= $idx." => ".$sub->($idx)."\n"; + } - for my $i (keys %{$h}) { - if ($i !~ /^[0-9]{2}$/ix && $htol ne 'strings') { # bereinigen ungültige Position, Forum: https://forum.fhem.de/index.php/topic,117864.msg1173219.html#msg1173219 - delete $data{$name}{$htol}{$i}; - Log3 ($name, 2, qq{$name - INFO - invalid key "$i" was deleted from }.ucfirst($htol).qq{ storage}); + if ($export eq 'csv') { + return _writeAsCsv ($hash, $hexp, $pvhexprtcsv.$name.'.csv'); + } + +return $sq; +} + +################################################################ +# Listing des verschiedene Speicher +################################################################ +sub _listDataPoolVarious { + my $hash = shift; + my $htol = shift; + my $par = shift // q{}; + + my $name = $hash->{NAME}; + + my $func = $htol eq 'consumers' ? \&ConsumerVal : + $htol eq 'inverters' ? \&InverterVal : + $htol eq 'producers' ? \&ProducerVal : + $htol eq 'strings' ? \&StringVal : + $htol eq 'batteries' ? \&BatteryVal : + ''; + + my $h = $data{$name}{$htol}; + + if (!keys %{$h}) { + return ucfirst($htol).qq{ cache is empty.}; + } + + for my $i (keys %{$h}) { + if ($i !~ /^[0-9]{2}$/ix && $htol ne 'strings') { # bereinigen ungültige Position, Forum: https://forum.fhem.de/index.php/topic,117864.msg1173219.html#msg1173219 + delete $data{$name}{$htol}{$i}; + Log3 ($name, 2, qq{$name - INFO - invalid key "$i" was deleted from }.ucfirst($htol).qq{ storage}); + } + } + + my $sq; + + for my $idx (sort keys %{$h}) { + next if($par && $idx ne $par); + my ($cret, $s1); + my $sp1 = _ldpspaces ($idx, q{}); + + for my $ckey (sort keys %{$h->{$idx}}) { + if (ref $h->{$idx}{$ckey} eq 'ARRAY') { + my $aser = join " ", @{$h->{$idx}{$ckey}}; + $cret .= ($s1 ? $sp1 : "").$ckey." => ".$aser."\n"; } - } - - for my $idx (sort keys %{$h}) { - next if($par && $idx ne $par); - my ($cret, $s1); - my $sp1 = _ldpspaces ($idx, q{}); - for my $ckey (sort keys %{$h->{$idx}}) { - if (ref $h->{$idx}{$ckey} eq 'ARRAY') { - my $aser = join " ", @{$h->{$idx}{$ckey}}; - $cret .= ($s1 ? $sp1 : "").$ckey." => ".$aser."\n"; - } - - if (ref $h->{$idx}{$ckey} eq 'HASH') { - my $hk = qq{}; - for my $f (sort {$a<=>$b} keys %{$h->{$idx}{$ckey}}) { - $hk .= " " if($hk); - $hk .= "$f=".$h->{$idx}{$ckey}{$f}; - } - $cret .= ($s1 ? $sp1 : "").$ckey." => ".$hk."\n"; - } - else { - $cret .= ($s1 ? $sp1 : "").$ckey." => ". &{$sub} ($hash, $idx, $ckey, "")."\n"; - } - - $s1 = 1; - } - $sq .= $idx." => ".$cret."\n"; - } - } - - if ($htol eq "circular") { - $sq = _listDataPoolCircular ($hash, $par); - } - - if ($htol eq "nexthours") { - $h = $data{$name}{nexthours}; - - if (!keys %{$h}) { - return qq{NextHours cache is empty.}; - } - - for my $idx (sort keys %{$h}) { - my $nhts = NexthoursVal ($name, $idx, 'starttime', '-'); - my $hod = NexthoursVal ($name, $idx, 'hourofday', '-'); - my $today = NexthoursVal ($name, $idx, 'today', '-'); - my $pvfc = NexthoursVal ($name, $idx, 'pvfc', '-'); - my $pvapifc = NexthoursVal ($name, $idx, 'pvapifc', '-'); # PV Forecast der API - my $pvaifc = NexthoursVal ($name, $idx, 'pvaifc', '-'); # PV Forecast der KI - my $aihit = NexthoursVal ($name, $idx, 'aihit', '-'); # KI ForeCast Treffer Status - my $wid = NexthoursVal ($name, $idx, 'weatherid', '-'); - my $wcc = NexthoursVal ($name, $idx, 'wcc', '-'); - my $crang = NexthoursVal ($name, $idx, 'cloudrange', '-'); - my $rr1c = NexthoursVal ($name, $idx, 'rr1c', '-'); - my $rrange = NexthoursVal ($name, $idx, 'rainrange', '-'); - my $rad1h = NexthoursVal ($name, $idx, 'rad1h', '-'); - my $pvcorrf = NexthoursVal ($name, $idx, 'pvcorrf', '-'); - my $temp = NexthoursVal ($name, $idx, 'temp', '-'); - my $confc = NexthoursVal ($name, $idx, 'confc', '-'); - my $confcex = NexthoursVal ($name, $idx, 'confcEx', '-'); - my $don = NexthoursVal ($name, $idx, 'DoN', '-'); - my $sunaz = NexthoursVal ($name, $idx, 'sunaz', '-'); - my $sunalt = NexthoursVal ($name, $idx, 'sunalt', '-'); - - my ($rcdbat, $socs); - for my $bn (1..$maxbatteries) { # alle Batterien - $bn = sprintf "%02d", $bn; - my $rcdcharge = NexthoursVal ($name, $idx, 'rcdchargebat'.$bn, '-'); - my $socxx = NexthoursVal ($name, $idx, 'soc'.$bn, '-'); - $rcdbat .= ', ' if($rcdbat); - $rcdbat .= "rcdchargebat${bn}: $rcdcharge"; - $socs .= ', ' if($socs); - $socs .= "soc${bn}: $socxx"; - } - - $sq .= "\n" if($sq); - $sq .= $idx." => "; - $sq .= "starttime: $nhts, hourofday: $hod, today: $today"; - $sq .= "\n "; - $sq .= "pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, confc: $confc"; - $sq .= "\n "; - $sq .= "confcEx: $confcex, DoN: $don, weatherid: $wid, wcc: $wcc, rr1c: $rr1c, temp=$temp"; - $sq .= "\n "; - $sq .= "rad1h: $rad1h, sunaz: $sunaz, sunalt: $sunalt"; - $sq .= "\n "; - $sq .= "rrange: $rrange, crange: $crang, correff: $pvcorrf"; - $sq .= "\n "; - $sq .= $socs; - $sq .= "\n "; - $sq .= $rcdbat; - } - } - - if ($htol eq "qualities") { - $h = $data{$name}{nexthours}; - if (!keys %{$h}) { - return qq{NextHours cache is empty.}; - } - 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 $wcc = NexthoursVal ($hash, $idx, 'wcc', '-'); - 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, Sun Alt: $sunalt, Cloud: $wcc"; - } - } - - if ($htol eq "current") { - $h = $data{$name}{current}; - - if (!keys %{$h}) { - return qq{Current values cache is empty.}; - } - - for my $idx (sort keys %{$h}) { - if (ref $h->{$idx} eq 'ARRAY') { - my $aser = join " ",@{$h->{$idx}}; - $sq .= $idx." => ".$aser."\n"; - } - elsif (ref $h->{$idx} eq 'HASH') { - my $s1; - my $sp1 = _ldpspaces ($idx, q{}); - $sq .= $idx." => "; - - for my $idx1 (sort keys %{$h->{$idx}}) { - if (ref $h->{$idx}{$idx1} eq 'HASH') { - my $s2; - my $sp2 = _ldpspaces ($idx1, $sp1); - $sq .= ($s1 ? $sp1 : "").$idx1." => "; - - for my $idx2 (sort keys %{$h->{$idx}{$idx1}}) { - my $s3; - my $sp3 = _ldpspaces ($idx2, $sp2); - $sq .= ($s2 ? $sp2 : "").$idx2." => "; - - if (ref $h->{$idx}{$idx1}{$idx2} eq 'HASH') { - for my $idx3 (sort keys %{$h->{$idx}{$idx1}{$idx2}}) { - $sq .= ($s3 ? $sp3 : "").$idx3." => ".(defined $h->{$idx}{$idx1}{$idx2}{$idx3} ? $h->{$idx}{$idx1}{$idx2}{$idx3} : '')."\n"; - $s3 = 1; - } - } - else { - $sq .= (defined $h->{$idx}{$idx1}{$idx2} ? $h->{$idx}{$idx1}{$idx2} : '')."\n"; - } - - $s1 = 1; - $s2 = 1; - } - } - else { - $sq .= (defined $h->{$idx}{$idx1} ? $h->{$idx}{$idx1} : '')."\n"; - } + if (ref $h->{$idx}{$ckey} eq 'HASH') { + my $hk = qq{}; + for my $f (sort {$a<=>$b} keys %{$h->{$idx}{$ckey}}) { + $hk .= " " if($hk); + $hk .= "$f=".$h->{$idx}{$ckey}{$f}; } + $cret .= ($s1 ? $sp1 : "").$ckey." => ".$hk."\n"; } else { - $sq .= $idx." => ".(defined $h->{$idx} ? $h->{$idx} : '')."\n"; + $cret .= ($s1 ? $sp1 : "").$ckey." => ". &{$func} ($hash, $idx, $ckey, "")."\n"; } + + $s1 = 1; } - } - - my $git = sub { - my $it = shift; - my @sorted = sort { $a cmp $b } keys %$it; - my $key = shift @sorted; - - my $ret = {}; - $ret = { $key => $it->{$key} } if($key); - - return $ret; - }; - - if ($htol =~ /radiationApiData|weatherApiData|statusApiData/xs) { - $h = $data{$name}{solcastapi}; - $h = $data{$name}{weatherapi} if($htol eq 'weatherApiData'); - $h = $data{$name}{statusapi} if($htol eq 'statusApiData'); - - if (!keys %{$h}) { - return qq{The API values cache is empty.}; - } - - my $pve = q{}; - my $itref = dclone $h; # Deep Copy von $h - - for my $idx (sort keys %{$itref}) { - my $s1; - my $sp1 = _ldpspaces ($idx, q{}); - $sq .= $idx." => "; - - while (my ($tag, $item) = each %{$git->($itref->{$idx})}) { - $sq .= ($s1 ? $sp1 : "").$tag." => "; - - if (ref $item eq 'HASH') { - my $s2; - my $sp2 = _ldpspaces ($tag, $sp1); - - while (my ($tag1, $item1) = each %{$git->($itref->{$idx}{$tag})}) { - $sq .= ($s2 ? $sp2 : "")."$tag1: ".$item1."\n"; - $s2 = 1; - delete $itref->{$idx}{$tag}{$tag1}; - } - } - - $s1 = 1; - $sq .= "\n" if($sq !~ /\n$/xs); - - delete $itref->{$idx}{$tag}; - } - } - } - - if ($htol eq "aiRawData") { - $h = $data{$name}{aidectree}{airaw}; - my $maxcnt = keys %{$h}; - if (!$maxcnt) { - return qq{aiRawData values cache is empty.}; - } - - $sq = "Number of datasets: ".$maxcnt."\n"; - - for my $idx (sort keys %{$h}) { - my $hod = AiRawdataVal ($hash, $idx, 'hod', '-'); - my $sunalt = AiRawdataVal ($hash, $idx, 'sunalt', '-'); - my $sunaz = AiRawdataVal ($hash, $idx, 'sunaz', '-'); - my $rad1h = AiRawdataVal ($hash, $idx, 'rad1h', '-'); - my $wcc = AiRawdataVal ($hash, $idx, 'wcc', '-'); - my $rr1c = AiRawdataVal ($hash, $idx, 'rr1c', '-'); - my $pvrl = AiRawdataVal ($hash, $idx, 'pvrl', '-'); - my $temp = AiRawdataVal ($hash, $idx, 'temp', '-'); - $sq .= "\n"; - $sq .= "$idx => hod: $hod, sunaz: $sunaz, sunalt: $sunalt, rad1h: $rad1h, wcc: $wcc, rr1c: $rr1c, pvrl: $pvrl, temp: $temp"; - } + $sq .= $idx." => ".$cret."\n"; } return $sq; @@ -18178,35 +18035,6 @@ sub _listDataPoolCircular { my $sq; - ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! - ########################################################################################################################## - delete $data{$name}{circular}{'01'}{pvrl_5}; - delete $data{$name}{circular}{'01'}{pvrl_10}; - delete $data{$name}{circular}{'01'}{pvrl_25}; - delete $data{$name}{circular}{'01'}{pvrl_60}; - delete $data{$name}{circular}{'01'}{pvrl_65}; - delete $data{$name}{circular}{'01'}{pvrl_90}; - - #push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4561; - #push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4562; - #push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4563; - #push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4564; - #push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4565; - # - #push @{$data{$name}{circular}{'01'}{pvrl_65}{60}}, 3561; - #push @{$data{$name}{circular}{'01'}{pvrl_65}{60}}, 3562; - # - #push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4561; - #push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4562; - #push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4563; - #push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4564; - #push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4565; - - #push @{$data{$name}{circular}{'01'}{pvrl_90}{60}}, 3561; - #push @{$data{$name}{circular}{'01'}{pvrl_90}{60}}, 3562; - - ############################################################################################################ - for my $idx (sort keys %{$h}) { next if($par && $idx ne $par); @@ -18348,6 +18176,228 @@ sub _listDataPoolCircular { return $sq; } +################################################################ +# Listing des NextHours Speicher +################################################################ +sub _listDataPoolNextHours { + my $name = shift; + my $par = shift // q{}; + + my $h = $data{$name}{nexthours}; + my $sq; + + if (!keys %{$h}) { + return qq{NextHours cache is empty.}; + } + + for my $idx (sort keys %{$h}) { + my $nhts = NexthoursVal ($name, $idx, 'starttime', '-'); + my $hod = NexthoursVal ($name, $idx, 'hourofday', '-'); + my $today = NexthoursVal ($name, $idx, 'today', '-'); + my $pvfc = NexthoursVal ($name, $idx, 'pvfc', '-'); + my $pvapifc = NexthoursVal ($name, $idx, 'pvapifc', '-'); # PV Forecast der API + my $pvaifc = NexthoursVal ($name, $idx, 'pvaifc', '-'); # PV Forecast der KI + my $aihit = NexthoursVal ($name, $idx, 'aihit', '-'); # KI ForeCast Treffer Status + my $wid = NexthoursVal ($name, $idx, 'weatherid', '-'); + my $wcc = NexthoursVal ($name, $idx, 'wcc', '-'); + my $crang = NexthoursVal ($name, $idx, 'cloudrange', '-'); + my $rr1c = NexthoursVal ($name, $idx, 'rr1c', '-'); + my $rrange = NexthoursVal ($name, $idx, 'rainrange', '-'); + my $rad1h = NexthoursVal ($name, $idx, 'rad1h', '-'); + my $pvcorrf = NexthoursVal ($name, $idx, 'pvcorrf', '-'); + my $temp = NexthoursVal ($name, $idx, 'temp', '-'); + my $confc = NexthoursVal ($name, $idx, 'confc', '-'); + my $confcex = NexthoursVal ($name, $idx, 'confcEx', '-'); + my $don = NexthoursVal ($name, $idx, 'DoN', '-'); + my $sunaz = NexthoursVal ($name, $idx, 'sunaz', '-'); + my $sunalt = NexthoursVal ($name, $idx, 'sunalt', '-'); + + my ($rcdbat, $socs); + for my $bn (1..$maxbatteries) { # alle Batterien + $bn = sprintf "%02d", $bn; + my $rcdcharge = NexthoursVal ($name, $idx, 'rcdchargebat'.$bn, '-'); + my $socxx = NexthoursVal ($name, $idx, 'soc'.$bn, '-'); + $rcdbat .= ', ' if($rcdbat); + $rcdbat .= "rcdchargebat${bn}: $rcdcharge"; + $socs .= ', ' if($socs); + $socs .= "soc${bn}: $socxx"; + } + + $sq .= "\n" if($sq); + $sq .= $idx." => "; + $sq .= "starttime: $nhts, hourofday: $hod, today: $today"; + $sq .= "\n "; + $sq .= "pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, confc: $confc"; + $sq .= "\n "; + $sq .= "confcEx: $confcex, DoN: $don, weatherid: $wid, wcc: $wcc, rr1c: $rr1c, temp=$temp"; + $sq .= "\n "; + $sq .= "rad1h: $rad1h, sunaz: $sunaz, sunalt: $sunalt"; + $sq .= "\n "; + $sq .= "rrange: $rrange, crange: $crang, correff: $pvcorrf"; + $sq .= "\n "; + $sq .= $socs; + $sq .= "\n "; + $sq .= $rcdbat; + } + +return $sq; +} + +################################################################ +# Listing des Qualities Speicher +################################################################ +sub _listDataPoolQualities { + my $name = shift; + my $par = shift // q{}; + + my $h = $data{$name}{nexthours}; + my $sq; + + if (!keys %{$h}) { + return qq{NextHours cache is empty.}; + } + + for my $idx (sort keys %{$h}) { + my $nhfc = NexthoursVal ($name, $idx, 'pvfc', undef); + next if(!$nhfc); + + my $nhts = NexthoursVal ($name, $idx, 'starttime', '-'); + my $pvcorrf = NexthoursVal ($name, $idx, 'pvcorrf', '-/-'); + my $aihit = NexthoursVal ($name, $idx, 'aihit', '-'); + my $pvfc = NexthoursVal ($name, $idx, 'pvfc', '-'); + my $wcc = NexthoursVal ($name, $idx, 'wcc', '-'); + my $sunalt = NexthoursVal ($name, $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, Sun Alt: $sunalt, Cloud: $wcc"; + } + +return $sq; +} + +################################################################ +# Listing des Current Speicher +################################################################ +sub _listDataPoolCurrent { + my $name = shift; + my $par = shift // q{}; + + my $h = $data{$name}{current}; + my $sq; + + if (!keys %{$h}) { + return qq{Current values cache is empty.}; + } + + for my $idx (sort keys %{$h}) { + if (ref $h->{$idx} eq 'ARRAY') { + my $aser = join " ",@{$h->{$idx}}; + $sq .= $idx." => ".$aser."\n"; + } + elsif (ref $h->{$idx} eq 'HASH') { + my $s1; + my $sp1 = _ldpspaces ($idx, q{}); + $sq .= $idx." => "; + + for my $idx1 (sort keys %{$h->{$idx}}) { + if (ref $h->{$idx}{$idx1} eq 'HASH') { + my $s2; + my $sp2 = _ldpspaces ($idx1, $sp1); + $sq .= ($s1 ? $sp1 : "").$idx1." => "; + + for my $idx2 (sort keys %{$h->{$idx}{$idx1}}) { + my $s3; + my $sp3 = _ldpspaces ($idx2, $sp2); + $sq .= ($s2 ? $sp2 : "").$idx2." => "; + + if (ref $h->{$idx}{$idx1}{$idx2} eq 'HASH') { + for my $idx3 (sort keys %{$h->{$idx}{$idx1}{$idx2}}) { + $sq .= ($s3 ? $sp3 : "").$idx3." => ".(defined $h->{$idx}{$idx1}{$idx2}{$idx3} ? $h->{$idx}{$idx1}{$idx2}{$idx3} : '')."\n"; + $s3 = 1; + } + } + else { + $sq .= (defined $h->{$idx}{$idx1}{$idx2} ? $h->{$idx}{$idx1}{$idx2} : '')."\n"; + } + + $s1 = 1; + $s2 = 1; + } + } + else { + $sq .= (defined $h->{$idx}{$idx1} ? $h->{$idx}{$idx1} : '')."\n"; + } + } + } + else { + $sq .= $idx." => ".(defined $h->{$idx} ? $h->{$idx} : '')."\n"; + } + } + +return $sq; +} + +################################################################ +# Listing der APiData Speicher +################################################################ +sub _listDataPoolApiData { + my $name = shift; + my $htol = shift; + my $par = shift // q{}; + + my $h = $data{$name}{solcastapi}; + $h = $data{$name}{weatherapi} if($htol eq 'weatherApiData'); + $h = $data{$name}{statusapi} if($htol eq 'statusApiData'); + + if (!keys %{$h}) { + return qq{The API values cache is empty.}; + } + + my $git = sub { + my $it = shift; + my @sorted = sort { $a cmp $b } keys %$it; + my $key = shift @sorted; + + my $ret = {}; + $ret = { $key => $it->{$key} } if($key); + + return $ret; + }; + + my $sq; + my $pve = q{}; + my $itref = dclone $h; # Deep Copy von $h + + for my $idx (sort keys %{$itref}) { + my $s1; + my $sp1 = _ldpspaces ($idx, q{}); + $sq .= $idx." => "; + + while (my ($tag, $item) = each %{$git->($itref->{$idx})}) { + $sq .= ($s1 ? $sp1 : "").$tag." => "; + + if (ref $item eq 'HASH') { + my $s2; + my $sp2 = _ldpspaces ($tag, $sp1); + + while (my ($tag1, $item1) = each %{$git->($itref->{$idx}{$tag})}) { + $sq .= ($s2 ? $sp2 : "")."$tag1: ".$item1."\n"; + $s2 = 1; + delete $itref->{$idx}{$tag}{$tag1}; + } + } + + $s1 = 1; + $sq .= "\n" if($sq !~ /\n$/xs); + + delete $itref->{$idx}{$tag}; + } + } + +return $sq; +} + ################################################################ # Hashwert aus CircularVal in formatierten String umwandeln ################################################################