From 266d87f73eba55ebdc0b25cdfbd4914243c848f1 Mon Sep 17 00:00:00 2001 From: DS_Starter Date: Wed, 26 May 2021 20:47:27 +0000 Subject: [PATCH] 76_SolarForecast.pm: contrib 0.47.0 git-svn-id: https://svn.fhem.de/fhem/trunk@24517 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/DS_Starter/76_SolarForecast.pm | 420 +++++++++++++------- 1 file changed, 283 insertions(+), 137 deletions(-) diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 13aebb126..9f8c1bb4d 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -116,6 +116,7 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "0.47.0" => "26.05.2021 add flowGraphic, attr flowGraphicSize ", "0.46.1" => "21.05.2021 set <> reset pvHistory ", "0.46.0" => "16.05.2021 integrate intotal, outtotal to currentBatteryDev, set maxconsumer to 9 ", "0.45.1" => "13.05.2021 change the calc of etotal at the beginning of every hour in _transferInverterValues ". @@ -262,8 +263,8 @@ my %hqtxt = ( ist => { EN => qq{Please define all of your used string names with "set LINK inverterStrings"}, DE => qq{Bitte geben sie alle von Ihnen verwendeten Stringnamen mit "set LINK inverterStrings" an} }, mps => { EN => qq{Please specify the total peak power for every string with "set LINK modulePeakString"}, - DE => qq{Bitte geben sie die Gesamtspitzenleistung für jeden String mit "set LINK modulePeakString" an} }, - mdr => { EN => qq{Please specify the module direction with "set LINK moduleDirection"}, + DE => qq{Bitte geben sie die Gesamtspitzenleistung von jedem String mit "set LINK modulePeakString" an} }, + mdr => { EN => qq{Please specify the module direction with "set LINK moduleDirection"}, DE => qq{Bitte geben Sie die Modulausrichtung mit "set LINK moduleDirection" an} }, mta => { EN => qq{Please specify the module tilt angle with "set LINK moduleTiltAngle"}, DE => qq{Bitte geben Sie den Modulneigungswinkel mit "set LINK moduleTiltAngle" an} }, @@ -471,6 +472,7 @@ sub Initialize { # "consumerAdviceIcon ". "cloudFactorDamping:slider,0,1,100 ". "disable:1,0 ". + "flowGraphicSize:slider,100,50,500 ". "follow70percentRule:1,dynamic,0 ". "forcePageRefresh:1,0 ". "headerAlignment:center,left,right ". @@ -3202,39 +3204,45 @@ return; # FHEMWEB Fn ################################################################ sub FwFn { - my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn. - my $hash = $defs{$d}; + my ($FW_wname, $name, $room, $pageHash) = @_; # pageHash is set for summaryFn. + my $hash = $defs{$name}; my $height; RemoveInternalTimer($hash, \&pageRefresh); $hash->{HELPER}{FW} = $FW_wname; - my $link = forecastGraphic ($d); - - my $alias = AttrVal($d, "alias", $d); # Linktext als Aliasname oder Devicename setzen - my $dlink = qq{$alias}; + my $foreg = forecastGraphic ($name); - my $ret = ""; - if(IsDisabled($d)) { - $height = AttrNum($d, 'beamHeight', 200); + my $flowgh = AttrVal ($name, "flowGraphicSize", 300); + my $flowg = flowGraphic ($name, $flowgh); + + my $alias = AttrVal($name, "alias", $name); # Linktext als Aliasname oder Devicename setzen + my $dlink = qq{$alias}; + + my $ret = q{}; + + if(IsDisabled($name)) { + $height = AttrNum($name, 'beamHeight', 200); $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= "
"; - $ret .= qq{Solar forecast graphic device $d is disabled}; + $ret .= qq{Solar forecast graphic device $name is disabled}; $ret .= "
"; } else { - $ret .= "$dlink
" if(AttrVal($d,"showLink",0)); - $ret .= $link; + $ret .= "$dlink
" if(AttrVal($name,"showLink",0)); + $ret .= $foreg; + $ret .= "\n"; + $ret .= $flowg; } # Autorefresh nur des aufrufenden FHEMWEB-Devices - my $al = AttrVal($d, "autoRefresh", 0); + my $al = AttrVal($name, "autoRefresh", 0); if($al) { InternalTimer(gettimeofday()+$al, \&pageRefresh, $hash, 0); - Log3 ($d, 5, "$d - next start of autoRefresh: ".FmtDateTime(gettimeofday()+$al)); + Log3 ($name, 5, "$name - next start of autoRefresh: ".FmtDateTime(gettimeofday()+$al)); } return $ret; @@ -3243,17 +3251,17 @@ return $ret; ################################################################ sub pageRefresh { my $hash = shift; - my $d = $hash->{NAME}; + my $name = $hash->{NAME}; # Seitenrefresh festgelegt durch SolarForecast-Attribut "autoRefresh" und "autoRefreshFW" - my $rd = AttrVal($d, "autoRefreshFW", $hash->{HELPER}{FW}); + my $rd = AttrVal($name, "autoRefreshFW", $hash->{HELPER}{FW}); { map { FW_directNotify("#FHEMWEB:$_", "location.reload('true')", "") } $rd } ## no critic 'Map blocks' - my $al = AttrVal($d, "autoRefresh", 0); + my $al = AttrVal($name, "autoRefresh", 0); if($al) { InternalTimer(gettimeofday()+$al, \&pageRefresh, $hash, 0); - Log3 ($d, 5, "$d - next start of autoRefresh: ".FmtDateTime(gettimeofday()+$al)); + Log3 ($name, 5, "$name - next start of autoRefresh: ".FmtDateTime(gettimeofday()+$al)); } else { RemoveInternalTimer($hash, \&pageRefresh); @@ -3297,91 +3305,39 @@ return $ret; } ############################################################################### -# Subroutine für Vorhersagegrafik +# Vorhersagegrafik ############################################################################### sub forecastGraphic { ## no critic 'complexity' my $name = shift; my $ftui = shift // ""; - my $hash = $defs{$name}; + my $ret = ""; - my ($val,$height); - my ($z2,$z3,$z4); - my $he; # Balkenhöhe + my ($val,$z2,$z3,$z4,$he); my $hfcg = $data{$hash->{TYPE}}{$name}{html}; #(hfcg = hash forecast graphic) - ########################################################## # Kontext des SolarForecast-Devices speichern für Refresh + ########################################################## $hash->{HELPER}{SPGDEV} = $name; # Name des aufrufenden SMAPortalSPG-Devices $hash->{HELPER}{SPGROOM} = $FW_room ? $FW_room : ""; # Raum aus dem das SMAPortalSPG-Device die Funktion aufrief $hash->{HELPER}{SPGDETAIL} = $FW_detail ? $FW_detail : ""; # Name des SMAPortalSPG-Devices (wenn Detailansicht) - my $lang = AttrVal ("global", "language", "EN"); - my $fcdev = ReadingsVal ($name, "currentForecastDev", "" ); # Forecast Device (Wetter) - my $radev = ReadingsVal ($name, "currentRadiationDev", "" ); # Forecast Device (Wetter) - my $indev = ReadingsVal ($name, "currentInverterDev", "" ); # Inverter Device - my $medev = ReadingsVal ($name, "currentMeterDev", "" ); # Meter Device - - my $pv0 = NexthoursVal ($hash, "NextHour00", "pvforecast", undef); - my $is = ReadingsVal ($name, "inverterStrings", undef); # String Konfig - my $peak = ReadingsVal ($name, "modulePeakString", undef); # String Peak - my $dir = ReadingsVal ($name, "moduleDirection", undef); # Modulausrichtung Konfig - my $ta = ReadingsVal ($name, "moduleTiltAngle", undef); # Modul Neigungswinkel Konfig + # Setup Vollständigkeit prüfen + ############################### + my $incomplete = _checkSetupComplete ($hash); + return $incomplete if($incomplete); - if(!$is || !$fcdev || !$radev || !$indev || !$medev || !$peak || !defined $pv0 || !$dir || !$ta) { - my $link = qq{$name}; - $height = AttrNum($name, 'beamHeight', 200); - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= "
"; - - if(!$fcdev) { ## no critic 'Cascading' - $ret .= $hqtxt{cfd}{$lang}; - } - elsif(!$radev) { - $ret .= $hqtxt{crd}{$lang}; - } - elsif(!$indev) { - $ret .= $hqtxt{cid}{$lang}; - } - elsif(!$medev) { - $ret .= $hqtxt{mid}{$lang}; - } - elsif(!$is) { - $ret .= $hqtxt{ist}{$lang}; - } - elsif(!$peak) { - $ret .= $hqtxt{mps}{$lang}; - } - elsif(!$dir) { - $ret .= $hqtxt{mdr}{$lang}; - } - elsif(!$ta) { - $ret .= $hqtxt{mta}{$lang}; - } - elsif(!defined $pv0) { - $ret .= $hqtxt{awd}{$lang}; - } - - $ret .= "
"; - $ret =~ s/LINK/$link/gxs; - return $ret; - } - - my $cclv = "L05"; - my @pgCDev = split(',',AttrVal($name,"consumerList","")); # definierte Verbraucher ermitteln - my ($legend_style, $legend) = split('_',AttrVal($name,'consumerLegend','icon_top')); - - $legend = '' if(($legend_style eq 'none') || (!int(@pgCDev))); - ################################### # Verbraucherlegende und Steuerung ################################### my $legend_txt; + my $cclv = "L05"; + my @pgCDev = split(',',AttrVal($name,"consumerList","")); # definierte Verbraucher ermitteln + my ($legend_style, $legend) = split('_',AttrVal($name,'consumerLegend','icon_top')); + $legend = '' if(($legend_style eq 'none') || (!int(@pgCDev))); + if ($legend) { for (@pgCDev) { my($txt,$im) = split(':',$_); # $txt ist der Verbrauchername @@ -3419,46 +3375,38 @@ sub forecastGraphic { } } - ################################### # Parameter f. Anzeige extrahieren ################################### - my $maxhours = AttrNum ($name, 'hourCount', 24 ); - my $offset = AttrNum ($name, 'historyHour', 0 ); - - my $hourstyle = AttrVal ($name, 'hourStyle', '' ); - - my $colorfc = AttrVal ($name, 'beam1Color', '000000'); - my $colorc = AttrVal ($name, 'beam2Color', 'C4C4A7'); - my $fcolor1 = AttrVal ($name, 'beam1FontColor', 'C4C4A7'); - my $fcolor2 = AttrVal ($name, 'beam2FontColor', '000000'); - - my $beam1cont = AttrVal ($name, 'beam1Content', 'pvForecast'); - my $beam2cont = AttrVal ($name, 'beam2Content', 'pvForecast'); - - my $icon = AttrVal ($name, 'consumerAdviceIcon', undef ); - my $html_start = AttrVal ($name, 'htmlStart', undef ); # beliebige HTML Strings die vor der Grafik ausgegeben werden - my $html_end = AttrVal ($name, 'htmlEnd', undef ); # beliebige HTML Strings die nach der Grafik ausgegeben werden - - my $lotype = AttrVal ($name, 'layoutType', 'single' ); - my $kw = AttrVal ($name, 'Wh/kWh', 'Wh' ); - - $height = AttrNum ($name, 'beamHeight', 200 ); - my $width = AttrNum ($name, 'beamWidth', 6 ); # zu klein ist nicht problematisch - my $w = $width*$maxhours; # gesammte Breite der Ausgabe , WetterIcon braucht ca. 34px - my $fsize = AttrNum ($name, 'spaceSize', 24 ); - my $maxVal = AttrNum ($name, 'maxValBeam', 0 ); # dyn. Anpassung der Balkenhöhe oder statisch ? - - my $show_night = AttrNum ($name, 'showNight', 0 ); # alle Balken (Spalten) anzeigen ? - my $show_diff = AttrVal ($name, 'showDiff', 'no' ); # zusätzliche Anzeige $di{} in allen Typen - my $weather = AttrNum ($name, 'showWeather', 1 ); - my $colorw = AttrVal ($name, 'weatherColor', 'FFFFFF' ); # Wetter Icon Farbe - my $colorwn = AttrVal ($name, 'weatherColorNight', $colorw ); # Wetter Icon Farbe Nacht - - my $wlalias = AttrVal ($name, 'alias', $name ); - my $header = AttrNum ($name, 'showHeader', 1 ); - my $hdrAlign = AttrVal ($name, 'headerAlignment', 'center' ); # ermöglicht per attr die Ausrichtung der Tabelle zu setzen - my $hdrDetail = AttrVal ($name, 'headerDetail', 'all' ); # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten - + my $maxhours = AttrNum ($name, 'hourCount', 24); + my $offset = AttrNum ($name, 'historyHour', 0); + my $hourstyle = AttrVal ($name, 'hourStyle', ''); + my $colorfc = AttrVal ($name, 'beam1Color', '000000'); + my $colorc = AttrVal ($name, 'beam2Color', 'C4C4A7'); + my $fcolor1 = AttrVal ($name, 'beam1FontColor', 'C4C4A7'); + my $fcolor2 = AttrVal ($name, 'beam2FontColor', '000000'); + my $beam1cont = AttrVal ($name, 'beam1Content', 'pvForecast'); + my $beam2cont = AttrVal ($name, 'beam2Content', 'pvForecast'); + my $icon = AttrVal ($name, 'consumerAdviceIcon', undef); + my $html_start = AttrVal ($name, 'htmlStart', undef); # beliebige HTML Strings die vor der Grafik ausgegeben werden + my $html_end = AttrVal ($name, 'htmlEnd', undef); # beliebige HTML Strings die nach der Grafik ausgegeben werden + my $lotype = AttrVal ($name, 'layoutType', 'single'); + my $kw = AttrVal ($name, 'Wh/kWh', 'Wh'); + my $height = AttrNum ($name, 'beamHeight', 200); + my $width = AttrNum ($name, 'beamWidth', 6); # zu klein ist nicht problematisch + my $w = $width*$maxhours; # gesammte Breite der Ausgabe , WetterIcon braucht ca. 34px + my $fsize = AttrNum ($name, 'spaceSize', 24); + my $maxVal = AttrNum ($name, 'maxValBeam', 0); # dyn. Anpassung der Balkenhöhe oder statisch ? + my $show_night = AttrNum ($name, 'showNight', 0); # alle Balken (Spalten) anzeigen ? + my $show_diff = AttrVal ($name, 'showDiff', 'no'); # zusätzliche Anzeige $di{} in allen Typen + my $weather = AttrNum ($name, 'showWeather', 1); + my $colorw = AttrVal ($name, 'weatherColor', 'FFFFFF'); # Wetter Icon Farbe + my $colorwn = AttrVal ($name, 'weatherColorNight', $colorw); # Wetter Icon Farbe Nacht + my $wlalias = AttrVal ($name, 'alias', $name); + my $header = AttrNum ($name, 'showHeader', 1); + my $hdrAlign = AttrVal ($name, 'headerAlignment', 'center'); # ermöglicht per attr die Ausrichtung der Tabelle zu setzen + my $hdrDetail = AttrVal ($name, 'headerDetail', 'all'); # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten + my $lang = AttrVal ("global", 'language', 'EN'); + # Icon Erstellung, mit @ ergänzen falls einfärben # Beispiel mit Farbe: $icon = FW_makeImage('light_light_dim_100.svg@green'); @@ -3502,7 +3450,6 @@ sub forecastGraphic { $pvCu .= " W"; } - ########################## # Headerzeile generieren ########################## if ($header) { @@ -3530,8 +3477,8 @@ sub forecastGraphic { $header = ""; - ######################################### - # Header Link + Status + Update Button + # Header Link + Status + Update Button + ######################################### if($hdrDetail eq "all" || $hdrDetail eq "statusLink") { my ($year, $month, $day, $time) = $lup =~ /(\d{4})-(\d{2})-(\d{2})\s+(.*)/x; @@ -3597,8 +3544,9 @@ sub forecastGraphic { $header .= ""; } - ######################## + # Header Information pv + ######################## if($hdrDetail eq "all" || $hdrDetail eq "pv" || $hdrDetail eq "pvco") { $header .= ""; $header .= ""; @@ -3609,8 +3557,9 @@ sub forecastGraphic { $header .= ""; } - ######################## - # Header Information co + + # Header Information co + ######################## if($hdrDetail eq "all" || $hdrDetail eq "co" || $hdrDetail eq "pvco") { $header .= ""; $header .= ""; @@ -3624,7 +3573,6 @@ sub forecastGraphic { $header .= "
".$autoct." " .$acicon."".$lbpcq." " .$pcqicon. "
PV =>
CO =>
"; } - ########################## # Werte aktuelle Stunde ########################## my $day; @@ -3685,7 +3633,6 @@ sub forecastGraphic { $lotype = 'single' if ($beam1cont eq $beam2cont); # User Auswahl überschreiben wenn beide Werte die gleiche Basis haben ! - ########################################################### # get consumer list and display it in portalGraphics ########################################################### for (@pgCDev) { @@ -3693,8 +3640,8 @@ sub forecastGraphic { $itemName =~ s/^\s+|\s+$//gx; # trim it, if blanks were used $_ =~ s/^\s+|\s+$//gx; # trim it, if blanks were used - ################################## #check if listed device is planned + ################################## if (ReadingsVal($name, $itemName."_Planned", "no") eq "yes") { #get start and end hour my ($start, $end); # werden auf Balken Pos 0 - 23 umgerechnet, nicht auf Stunde !!, Pos = 24 -> ungültige Pos = keine Anzeige @@ -3712,8 +3659,8 @@ sub forecastGraphic { $end = int($end); my $flag = 0; # default kein Tagesverschieber - ####################################### #correct the hour for accurate display + ####################################### if ($start < $hfcg->{0}{time}) { # gridconsumption seems to be tomorrow $start = 24-$hfcg->{0}{time}+$start; $flag = 1; @@ -3803,7 +3750,7 @@ sub forecastGraphic { } #Log3 ($hash,3,Dumper($hfcg)); - ###################################### + # Tabellen Ausgabe erzeugen ###################################### @@ -4004,8 +3951,8 @@ sub forecastGraphic { my $sicon = 1; #$ret .= $is{$i} if (defined ($is{$i}) && $sicon); - ################################## # inject the new icon if defined + ################################## #$ret .= consinject($hash,$i,@pgCDev) if($s); $ret .= ""; @@ -4047,8 +3994,8 @@ sub forecastGraphic { $ret .= ""; $ret .= "$val"; - ################################## # inject the new icon if defined + ################################## #$ret .= consinject($hash,$i,@pgCDev) if($s); $ret .= ""; @@ -4117,8 +4064,8 @@ sub forecastGraphic { $ret .= ""; - ################### # Legende unten + ################# if ($legend_txt && ($legend eq 'bottom')) { $ret .= ""; $ret .= ""; @@ -4132,6 +4079,205 @@ sub forecastGraphic { return $ret; } +################################################################ +# Vollständigkeit Setup prüfen +################################################################ +sub _checkSetupComplete { + my $hash = shift; + my $ret = q{}; + my $name = $hash->{NAME}; + + my $is = ReadingsVal ($name, "inverterStrings", undef); # String Konfig + my $fcdev = ReadingsVal ($name, "currentForecastDev", undef); # Forecast Device (Wetter) + my $radev = ReadingsVal ($name, "currentRadiationDev", undef); # Forecast Device (Wetter) + my $indev = ReadingsVal ($name, "currentInverterDev", undef); # Inverter Device + my $medev = ReadingsVal ($name, "currentMeterDev", undef); # Meter Device + my $peak = ReadingsVal ($name, "modulePeakString", undef); # String Peak + my $pv0 = NexthoursVal ($hash, "NextHour00", "pvforecast", undef); + my $dir = ReadingsVal ($name, "moduleDirection", undef); # Modulausrichtung Konfig + my $ta = ReadingsVal ($name, "moduleTiltAngle", undef); # Modul Neigungswinkel Konfig + + if(!$is || !$fcdev || !$radev || !$indev || !$medev || !$peak || !defined $pv0 || !$dir || !$ta) { + my $link = qq{$name}; + my $height = AttrNum ($name, 'beamHeight', 200); + my $lang = AttrVal ("global", "language", "EN"); + + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= "
"; + + if(!$fcdev) { ## no critic 'Cascading' + $ret .= $hqtxt{cfd}{$lang}; + } + elsif(!$radev) { + $ret .= $hqtxt{crd}{$lang}; + } + elsif(!$indev) { + $ret .= $hqtxt{cid}{$lang}; + } + elsif(!$medev) { + $ret .= $hqtxt{mid}{$lang}; + } + elsif(!$is) { + $ret .= $hqtxt{ist}{$lang}; + } + elsif(!$peak) { + $ret .= $hqtxt{mps}{$lang}; + } + elsif(!$dir) { + $ret .= $hqtxt{mdr}{$lang}; + } + elsif(!$ta) { + $ret .= $hqtxt{mta}{$lang}; + } + elsif(!defined $pv0) { + $ret .= $hqtxt{awd}{$lang}; + } + + $ret .= "
"; + $ret =~ s/LINK/$link/gxs; + return $ret; + } + +return; +} + +################################################################ +# Energieflußgrafik +################################################################ +sub flowGraphic { + my $name = shift; + my $h = shift; + + my $fs = ($h < 300) ? '48px' : '32px'; + my $style = 'width:'.$h.'px; height:'.$h.'px;'; + + my $inactive = 'stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.2;'; + my $active = 'stroke-dashoffset: 20; stroke-dasharray: 10; animation: dash 0.5s linear; animation-iteration-count: infinite; opacity: 0.8;' ; + + my $cpv = ReadingsNum($name, 'Current_PV', 0); + my $sun_color = ($cpv) ? 'orange' : 'gray'; + + my $cgc = ReadingsNum($name, 'Current_GridConsumption', 0); + my $cgc_style = ($cgc) ? $active : $inactive; + my $cgc_color = ($cgc) ? 'red' : 'gray'; + + my $cgfi = ReadingsNum($name, 'Current_GridFeedIn', 0); + my $cgfi_style = ($cgfi) ? $active : $inactive; + my $cgfi_color = ($cgfi) ? 'yellow' : 'gray'; + + my $csc = ReadingsNum($name, 'Current_SelfConsumption', 0); + my $csc_style = ($csc) ? $active : $inactive; + my $csc_color = ($csc) ? 'yellow' : 'gray'; + + my $batin = ReadingsNum($name, 'Current_PowerBatIn', 0); + my $batin_style = ($batin) ? $active : $inactive; + my $batin_color = ($batin) ? 'yellow' : 'gray'; + + my $batout = ReadingsNum($name, 'Current_PowerBatOut' , 0); + my $batout_style = ($batout) ? $active : $inactive; + my $batout_color = ($batout) ? 'yellow' : 'gray'; + + my $grid_color = ($cgfi) ? 'green' : 'red'; + $grid_color = 'gray' if (!$cgfi && !$cgc && $batout); # dritte Farbe + + my $ret = qq{ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $csc + + + $cgfi + + + $cgc + + + + $batout + + + $batin + + +
+ }; + +return $ret; +} + ################################################################ # Inject consumer icon ################################################################