diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index be29d29bc..ab8f49c27 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -144,6 +144,8 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.0.10" => "31.10.2023 fix warnings, edit comref ", + "1.0.9" => "29.10.2023 _aiGetSpread: set spread from 50 to 20 ", "1.0.8" => "22.10.2023 codechange: add central readings store array, new function storeReading, writeCacheToFile ". "solcastapi in sub __delObsoleteAPIData, save freespace if flowGraphicShowConsumer=0 is set ". "pay attention to attr graphicEnergyUnit in __createOwnSpec ", @@ -3588,7 +3590,7 @@ sub __VictronVRM_ApiRequestForecast { my $idsite = $paref->{idsite}; my $tstart = time; - my $tend = time + 259200; # 172800 = 2 Tage + my $tend = time + 259200; # 172800 = 2 Tage my $url = "https://vrmapi.victronenergy.com/v2/installations/$idsite/stats?type=forecast&interval=hours&start=$tstart&end=$tend"; @@ -3694,7 +3696,7 @@ sub __VictronVRM_ApiResponseForecast { my $k = 0; while ($jdata->{'records'}{'solar_yield_forecast'}[$k]) { next if(ref $jdata->{'records'}{'solar_yield_forecast'}[$k] ne "ARRAY"); # Forum: https://forum.fhem.de/index.php?msg=1288637 - + my $starttmstr = $jdata->{'records'}{'solar_yield_forecast'}[$k][0]; # Millisekunden geliefert my $val = $jdata->{'records'}{'solar_yield_forecast'}[$k][1]; $starttmstr = (timestampToTimestring ($starttmstr, $lang))[3]; @@ -3715,7 +3717,7 @@ sub __VictronVRM_ApiResponseForecast { $k = 0; while ($jdata->{'records'}{'vrm_consumption_fc'}[$k]) { next if(ref $jdata->{'records'}{'vrm_consumption_fc'}[$k] ne "ARRAY"); # Forum: https://forum.fhem.de/index.php?msg=1288637 - + my $starttmstr = $jdata->{'records'}{'vrm_consumption_fc'}[$k][0]; # Millisekunden geliefert my $val = $jdata->{'records'}{'vrm_consumption_fc'}[$k][1]; $starttmstr = (timestampToTimestring ($starttmstr, $lang))[3]; @@ -4008,7 +4010,7 @@ sub Attr { my $name = shift; my $aName = shift; my $aVal = shift; - + my $hash = $defs{$name}; my ($do,$val); @@ -4034,7 +4036,7 @@ sub Attr { if($aName eq 'ctrlNextDayForecastReadings') { deleteReadingspec ($hash, "Tomorrow_Hour.*"); } - + if($aName eq 'ctrlGenPVdeviation' && $aVal eq 'daily') { my $type = $hash->{TYPE}; deleteReadingspec ($hash, 'Today_PVdeviation'); @@ -4752,9 +4754,9 @@ sub centralTask { saveEnergyConsumption ($centpars); # Energie Hausverbrauch speichern genStatisticReadings ($centpars); # optionale Statistikreadings erstellen - userExit ($centpars); # User spezifische Funktionen ausführen + userExit ($centpars); # User spezifische Funktionen ausführen setTimeTracking ($hash, $cst, 'runTimeCentralTask'); # Zyklus-Laufzeit ermitteln - + createReadingsFromArray ($hash, $evt); # Readings erzeugen if ($evt) { @@ -5013,7 +5015,7 @@ sub _specialActivities { $pvfc = ReadingsNum ($name, "Today_Hour24_PVforecast", 0); storeReading ('LastHourPVforecast', "$pvfc Wh", $ts); - + $pvrl = ReadingsNum ($name, "Today_Hour24_PVreal", 0); storeReading ('LastHourPVreal', "$pvrl Wh", $ts); @@ -5142,7 +5144,7 @@ sub __delObsoleteAPIData { delete $data{$type}{$name}{solcastapi}{$idx}{$scd} if ($ds && $ds < $refts); } } - + writeCacheToFile ($hash, "solcastapi", $scpicache.$name); # Cache File SolCast API Werte schreiben my @as = split ",", ReadingsVal($name, 'inverterStrings', ''); @@ -5380,7 +5382,7 @@ sub _transferAPIRadiationValues { delete $paref->{histname}; } } - + storeReading ('.lastupdateForecastValues', $t); # Statusreading letzter update return; @@ -6168,7 +6170,7 @@ sub _createSummaries { $todaySumFc->{PV} += ReadingsNum ($name, "Today_Hour".sprintf("%02d",$th)."_PVforecast", 0); $todaySumRe->{PV} += ReadingsNum ($name, "Today_Hour".sprintf("%02d",$th)."_PVreal", 0); } - + my $pvre = int $todaySumRe->{PV}; push @{$data{$type}{$name}{current}{h4fcslidereg}}, int $next4HoursSum->{PV}; # Schieberegister 4h Summe Forecast @@ -6208,7 +6210,7 @@ sub _createSummaries { storeReading ('Current_AutarkyRate', $autarkyrate. ' %'); storeReading ('Today_PVreal', $pvre. ' Wh') if($pvre > ReadingsNum ($name, 'Today_PVreal', 0)); storeReading ('Tomorrow_ConsumptionForecast', $tconsum. ' Wh') if(defined $tconsum); - + storeReading ('NextHours_Sum01_PVforecast', (int $next1HoursSum->{PV}). ' Wh'); storeReading ('NextHours_Sum02_PVforecast', (int $next2HoursSum->{PV}). ' Wh'); storeReading ('NextHours_Sum03_PVforecast', (int $next3HoursSum->{PV}). ' Wh'); @@ -6402,9 +6404,9 @@ sub _manageConsumerData { my ($iilt,$rlt) = isInLocktime ($paref); # Sperrzeit Status ermitteln my $constate = "name='$alias' state='$costate' planningstate='$pstate'"; $constate .= " remainLockTime='$rlt'" if($rlt); - - storeReading ("consumer${c}", $constate); # Consumer Infos - storeReading ("consumer${c}_planned_start", $starttime) if($starttime); # Consumer Start geplant + + storeReading ("consumer${c}", $constate); # Consumer Infos + storeReading ("consumer${c}_planned_start", $starttime) if($starttime); # Consumer Start geplant storeReading ("consumer${c}_planned_stop", $stoptime) if($stoptime); # Consumer Stop geplant } @@ -7745,15 +7747,15 @@ sub _calcTodayPVdeviation { my $pvfc = ReadingsNum ($name, 'Today_PVforecast', 0); my $pvre = ReadingsNum ($name, 'Today_PVreal', 0); - + return if(!$pvre); - + my $dp; - + if (AttrVal($name, 'ctrlGenPVdeviation', 'daily') eq 'daily') { my $sstime = timestringToTimestamp ($date.' '.ReadingsVal ($name, "Today_SunSet", '22:00').':00'); return if($t < $sstime); - + my $diff = $pvfc - $pvre; $dp = sprintf "%.2f" , (100 * $diff / $pvre); } @@ -7762,7 +7764,7 @@ sub _calcTodayPVdeviation { my $dayfc = $pvre + $rodfc; # laufende Tagesprognose aus PVreal + Prognose Resttag $dp = sprintf "%.2f", (100 * ($pvfc - $dayfc) / $dayfc); } - + $data{$type}{$name}{circular}{99}{tdayDvtn} = $dp; storeReading ('Today_PVdeviation', $dp.' %'); @@ -8009,14 +8011,14 @@ sub genStatisticReadings { $dono = $don; } - + my $sttmp = timestringToTimestamp ($sttm) // return; - $sttmp += 3600; # Beginnzeitstempel auf volle Stunde ergänzen + $sttmp += 3600; # Beginnzeitstempel auf volle Stunde ergänzen my $mhrs = $hrs * 60; # berücksichtigte volle Minuten my $mtsr = ($sttmp - $t) / 60; # Minuten bis nächsten Sonnenaufgang (gerundet) $confc = $confc / $mhrs * $mtsr; - + storeReading ('statistic_'.$kpi, ($confc ? (sprintf "%.0f", $confc).$hcsr{$kpi}{unit} : '-')); } } @@ -8085,7 +8087,7 @@ sub collectAllRegConsumers { if(exists $hc->{asynchron}) { $asynchron = $hc->{asynchron}; } - + my $noshow; if(exists $hc->{noshow}) { # Consumer ausblenden in Grafik $noshow = $hc->{noshow}; @@ -8596,7 +8598,7 @@ sub _graphicHeader { my $hdrDetail = $paref->{hdrDetail}; # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten my $ftui = $paref->{ftui}; my $lang = $paref->{lang}; - my $name = $paref->{name}; + my $name = $paref->{name}; my $hash = $paref->{hash}; my $kw = $paref->{kw}; my $dstyle = $paref->{dstyle}; # TD-Style @@ -8671,8 +8673,8 @@ sub _graphicHeader { my $chktitle = $htitles{plchk}{$lang}; ## Update-Icon - ################ - my $upicon = __createUpdateIcon ($paref); + ################ + my $upicon = __createUpdateIcon ($paref); ## Sonnenauf- und untergang ############################ @@ -8818,11 +8820,11 @@ sub _graphicHeader { $tdayDvtn =~ s/\,0//; $ydayDvtn =~ s/\./,/; $ydayDvtn =~ s/,0//; - + my $genpvdva = $paref->{genpvdva}; my $dvtntxt = $hqtxt{dvtn}{$lang}.' '; - my $tdaytxt = ($genpvdva eq 'daily' ? $hqtxt{tday}{$lang} : $hqtxt{ctnsly}{$lang}).': '."".$tdayDvtn.""; + my $tdaytxt = ($genpvdva eq 'daily' ? $hqtxt{tday}{$lang} : $hqtxt{ctnsly}{$lang}).': '."".$tdayDvtn.""; my $ydaytxt = $hqtxt{yday}{$lang}.': '."".$ydayDvtn.""; my $text_tdayDvtn = $tdayDvtn =~ /^-[1-9]/? $hqtxt{pmtp}{$lang} : @@ -8895,7 +8897,7 @@ sub _graphicHeader { $header .= qq{
}; $header .= qq{}; } - + # Header User Spezifikation ############################# my $ownv = __createOwnSpec ($paref); @@ -8911,23 +8913,23 @@ return $header; ################################################################ sub __createUpdateIcon { my $paref = shift; - + my $hash = $paref->{hash}; my $name = $paref->{name}; my $lang = $paref->{lang}; my $ftui = $paref->{ftui}; - + my $upstate = ReadingsVal ($name, 'state', ''); my $naup = ReadingsVal ($name, 'nextCycletime', ''); - + my $cmdupdate = qq{"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name clientAction - 0 get $name data')"}; # Update Button generieren if ($ftui eq 'ftui') { $cmdupdate = qq{"ftui.setFhemStatus('set $name clientAction - 0 get $name data')"}; } - + my ($img, $upicon); - + if ($upstate =~ /updated|successfully|switched/ix) { $img = FW_makeImage('10px-kreis-gruen.png', $htitles{upd}{$lang}.' '.$htitles{natc}{$lang}.' '.$naup.''); $upicon = "$img"; @@ -8953,11 +8955,11 @@ return $upicon; ################################################################ sub __createAutokorrIcon { my $paref = shift; - + my $hash = $paref->{hash}; my $name = $paref->{name}; my $lang = $paref->{lang}; - + my $aciimg; my $acitit = q{}; my $acu = isAutoCorrUsed ($name); @@ -8984,20 +8986,20 @@ return $acicon; ################################################################ # erstelle Qualitäts-Icon -################################################################ +################################################################ sub __createQuaIcon { my $paref = shift; - + my $hash = $paref->{hash}; my $name = $paref->{name}; my $lang = $paref->{lang}; my $ftui = $paref->{ftui}; - + my $pvfc00 = NexthoursVal ($hash, 'NextHour00', 'pvfc', undef); my $pvcorrf00 = NexthoursVal ($hash, "NextHour00", "pvcorrf", "-/-"); my ($pcf,$pcq) = split "/", $pvcorrf00; my $pvcanz = qq{factor: $pcf / quality: $pcq}; - + my $cmdfcqal = qq{"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=get $name forecastQualities imgget', function(data){FW_okDialog(data)})"}; if ($ftui eq 'ftui') { @@ -9027,7 +9029,7 @@ return $pcqicon; ################################################################ sub __createAIicon { my $paref = shift; - + my $hash = $paref->{hash}; my $name = $paref->{name}; my $lang = $paref->{lang}; @@ -9055,83 +9057,85 @@ return $aiicon; ################################################################ # erstelle Übersicht eigener Readings -################################################################ +################################################################ sub __createOwnSpec { my $paref = shift; - + my $hash = $paref->{hash}; my $name = $paref->{name}; my $dstyle = $paref->{dstyle}; # TD-Style my $hdrDetail = $paref->{hdrDetail}; - - my $vinr = 4; # Spezifikationen in einer Zeile + + my $vinr = 4; # Spezifikationen in einer Zeile my $spec = AttrVal ($name, 'graphicHeaderOwnspec', ''); my $uatr = AttrVal ($name, 'graphicEnergyUnit', 'Wh'); my $show = $hdrDetail =~ /all|own/xs ? 1 : 0; - + return if(!$spec || !$show); - - my @fields = split (/\s+/sx, $spec); - + + my @fields = split (/\s+/sx, $spec); + my (@cats, @vals); - + for my $f (@fields) { if ($f =~ /^\#(.*)/xs) { push @cats, $1; next; } - + push @vals, $f; } - - my $ownv; + + my $ownv; my $rows = ceil (scalar(@vals) / $vinr); my $col = 0; - + for (my $i = 1 ; $i <= $rows; $i++) { - my ($h, $v, $u); - + my ($h, $v, $u); + for (my $k = 0 ; $k < $vinr; $k++) { ($h->{$k}{label}, $h->{$k}{rdg}) = split ":", $vals[$col] if($vals[$col]); $col++; + + if (!$h->{$k}{label}) { + undef $h->{$k}{label}; + next; + } + + ($v->{$k}, $u->{$k}) = split /\s+/, ReadingsVal ($name, $h->{$k}{rdg}, ''); } - - ($v->{0}, $u->{0}) = split /\s+/, ReadingsVal ($name, $h->{0}{rdg}, ''); - ($v->{1}, $u->{1}) = split /\s+/, ReadingsVal ($name, $h->{1}{rdg}, ''); - ($v->{2}, $u->{2}) = split /\s+/, ReadingsVal ($name, $h->{2}{rdg}, ''); - ($v->{3}, $u->{3}) = split /\s+/, ReadingsVal ($name, $h->{3}{rdg}, ''); - - if ($uatr eq 'kWh') { + + if ($uatr eq 'kWh') { for (my $r = 0 ; $r < $vinr; $r++) { next if(!$u->{$r}); - - if ($u->{$r} =~ /^Wh/xs) { + + if ($u->{$r} =~ /^Wh/xs) { $v->{$r} = sprintf "%.1f",($v->{$r} / 1000); $u->{$r} = 'kWh'; } } } - + if ($uatr eq 'Wh') { for (my $r = 0 ; $r < $vinr; $r++) { next if(!$u->{$r}); - - if ($u->{$r} =~ /^kWh/xs) { + + if ($u->{$r} =~ /^kWh/xs) { $v->{$r} = sprintf "%.0f",($v->{$r} * 1000); $u->{$r} = 'Wh'; } } } - + $ownv .= ""; $ownv .= "".($cats[$i-1] ? ''.$cats[$i-1].'' : '').""; - $ownv .= "".$h->{0}{label}.": ".$v->{0}." ".$u->{0}."" if($h->{0}{label}); - $ownv .= "".$h->{1}{label}.": ".$v->{1}." ".$u->{1}."" if($h->{1}{label}); - $ownv .= "".$h->{2}{label}.": ".$v->{2}." ".$u->{2}."" if($h->{2}{label}); - $ownv .= "".$h->{3}{label}.": ".$v->{3}." ".$u->{3}."" if($h->{3}{label}); + $ownv .= "".$h->{0}{label}.": ".$v->{0}." ".$u->{0}."" if(defined $h->{0}{label}); + $ownv .= "".$h->{1}{label}.": ".$v->{1}." ".$u->{1}."" if(defined $h->{1}{label}); + $ownv .= "".$h->{2}{label}.": ".$v->{2}." ".$u->{2}."" if(defined $h->{2}{label}); + $ownv .= "".$h->{3}{label}.": ".$v->{3}." ".$u->{3}."" if(defined $h->{3}{label}); $ownv .= ""; } - + $ownv .= qq{}; $ownv .= qq{
}; $ownv .= qq{}; @@ -9246,7 +9250,7 @@ sub _graphicConsumerLegend { $ctable .= qq{    }; my $cnum = @consumers; - + if ($cnum > 1) { $ctable .= qq{ $hqtxt{cnsm}{$lang} }; $ctable .= qq{ }; @@ -9274,7 +9278,7 @@ sub _graphicConsumerLegend { for my $c (@consumers) { next if(isConsumerNoshow ($hash, $c) =~ /^[12]$/xs); # Consumer ausblenden - + my $caicon = $paref->{caicon}; # Consumer AdviceIcon my ($cname, $dswname) = getCDnames ($hash, $c); # Consumer und Switch Device Name my $calias = ConsumerVal ($hash, $c, 'alias', $cname); # Alias des Consumerdevices @@ -10055,10 +10059,10 @@ sub _flowGraphic { my $batin_style = $batin ? 'flowg active_in active_bat_in' : 'flowg inactive_out'; my $csc_style = $csc && $cpv ? 'flowg active_out' : 'flowg inactive_out'; my $cgfi_style = $cgfi ? 'flowg active_out' : 'flowg inactive_out'; - - my $vbox_default = !$flowgcons ? '5 -25 800 480' : - $flowgconTime ? '5 -25 800 700' : - '5 -25 800 680'; + + my $vbox_default = !$flowgcons ? '5 -25 800 480' : + $flowgconTime ? '5 -25 800 700' : + '5 -25 800 680'; my $ret = << "END0";