From 8a3112158cbf24a7208cb296cf1db0d80992219e Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Tue, 22 Oct 2024 08:05:31 +0000 Subject: [PATCH] 76_SolarForecast: see Forum: ..index.php?msg=1323142 git-svn-id: https://svn.fhem.de/fhem/trunk@29279 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/76_SolarForecast.pm | 1403 +++++++++++++++++++-------------- 2 files changed, 794 insertions(+), 610 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index f2ea67b64..053d30529 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it + - feature: 76_SolarForecast: see Forum: ..index.php?msg=1323142 - bufgix: 72_FRITZBOX: reconnect Fehler nach offline Device - bufgix: 72_FRITZBOX: kleinere Schönheits-Fehler - feature: 72_FRITZBOX: Neue Readings box_usb... diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 7ecfe394c..2fe6e4ce6 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -4,7 +4,7 @@ # 76_SolarForecast.pm # # (c) 2020-2024 by Heiko Maaz e-mail: Heiko dot Maaz at t-online dot de -# with credits to: kask, Prof. Dr. Peter Henning, Wzut (and much more FHEM users) +# with credits to: kask, Prof. Dr. Peter Henning, Wzut, ch.eick (and much more FHEM users) # # This script is part of fhem. # @@ -156,6 +156,11 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.37.0" => "22.10.2024 attr setupInverterDevXX up to 03 inverters with accorded strings, setupInverterDevXX: keys strings and feed ". + "_flowGraphic: controlhash for producer, new attr flowGraphicControl and replace the attributes: ". + "flowGraphicAnimate flowGraphicConsumerDistance flowGraphicShowConsumer flowGraphicShowConsumerDummy ". + "flowGraphicShowConsumerPower flowGraphicShowConsumerRemainTime flowGraphicShift flowGraphicCss ". + "flowGraphicControl: new keys strokecolina, strokecolsig, strokecolstd, strokewidth ", "1.36.1" => "14.10.2024 _flowGraphic: consumer distance modified by kask, Coloring of icons corrected when creating 0 ", "1.36.0" => "13.10.2024 new Getter valInverter, valStrings and valProducer, preparation for multiple inverters ". "rename setupInverterDev to setupInverterDev01, new attr affectConsForecastLastDays ". @@ -439,7 +444,7 @@ my $tempbasedef = 25; my $maxconsumer = 16; # maximale Anzahl der möglichen Consumer (Attribut) my $maxproducer = 3; # maximale Anzahl der möglichen anderen Produzenten (Attribut) -my $maxinverter = 1; # maximale Anzahl der möglichen Inverter +my $maxinverter = 3; # maximale Anzahl der möglichen Inverter my $epiecMaxCycles = 10; # Anzahl Einschaltzyklen (Consumer) für verbraucherspezifische Energiestück Ermittlung my @ctypes = qw(dishwasher dryer washingmachine heater charger other @@ -465,7 +470,11 @@ my $b4coldef = 'DBDBD0'; my $b4fontcoldef = '000000'; # default Schriftfarbe Beam 4 my $fgCDdef = 130; # Abstand Verbrauchericons zueinander -my $fgscaledef = 0.10; # Scale Normativ Icons in Flowgreafik +my $fgscaledef = 0.10; # Flußgrafik: Scale Normativ Icons +my $strokcolstddef = 'darkorange'; # Flußgrafik: Standardfarbe aktive normale Kette +my $strokcolsigdef = 'red'; # Flußgrafik: Standardfarbe aktive Signal-Kette +my $strokcolinadef = 'gray'; # Flußgrafik: Standardfarbe inaktive Kette +my $strokwidthdef = 25; # Flußgrafik: Standard Breite der Kette my $prodicondef = 'sani_garden_pump'; # default Producer-Icon my $cicondef = 'light_light_dim_100'; # default Consumer-Icon my $ciconcoldef = 'darkorange'; # default Consumer-Icon Färbung @@ -481,21 +490,6 @@ my $bPath = 'https://svn.fhem.de/trac/browser/trunk/fhem/contrib/SolarForecast/' my $pPath = '?format=txt'; # Download Format my $cfile = 'controls_solarforecast.txt'; # Name des Controlfiles - # default CSS-Style -my $cssdef = qq{.flowg.text { stroke: none; fill: gray; font-size: 60px; } \n}. - qq{.flowg.bat25 { stroke: red; fill: red; } \n}. - qq{.flowg.bat50 { stroke: darkorange; fill: darkorange; } \n}. - qq{.flowg.bat75 { stroke: green; fill: green; } \n}. - qq{.flowg.grid_color1 { fill: green; } \n}. - qq{.flowg.grid_color2 { fill: red; } \n}. - qq{.flowg.grid_color3 { fill: gray; } \n}. - qq{.flowg.inactive_in { stroke: gray; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.2; } \n}. - qq{.flowg.inactive_out { stroke: gray; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.2; } \n}. - qq{.flowg.active_in { stroke: red; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.8; animation: dash 0.5s linear; animation-iteration-count: infinite; } \n}. - qq{.flowg.active_out { stroke: darkorange; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.8; animation: dash 0.5s linear; animation-iteration-count: infinite; } \n}. - qq{.flowg.active_bat_in { stroke: darkorange; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.8; animation: dash 0.5s linear; animation-iteration-count: infinite; } \n}. - qq{.flowg.active_bat_out { stroke: green; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.8; animation: dash 0.5s linear; animation-iteration-count: infinite; } \n} - ; # initiale Hashes für Stunden Consumption Forecast inkl. und exkl. Verbraucher my $conhfc = { "01" => 0, "02" => 0, "03" => 0, "04" => 0, "05" => 0, "06" => 0, "07" => 0, "08" => 0, @@ -550,9 +544,7 @@ my @aconfigs = qw( affect70percentRule affectBatteryPreferredCharge affectConsFo ctrlSolCastAPIoptimizeReq ctrlStatisticReadings ctrlUserExitFn setupWeatherDev1 setupWeatherDev2 setupWeatherDev3 disable - flowGraphicSize flowGraphicAnimate flowGraphicConsumerDistance flowGraphicShowConsumer - flowGraphicShowConsumerDummy flowGraphicShowConsumerPower flowGraphicShowConsumerRemainTime - flowGraphicCss graphicBeamWidth + flowGraphicControl graphicBeamWidth graphicBeamHeightLevel1 graphicBeamHeightLevel2 graphicBeam1Content graphicBeam2Content graphicBeam3Content graphicBeam4Content graphicBeam1Color graphicBeam2Color graphicBeam3Color graphicBeam4Color @@ -565,10 +557,10 @@ my @aconfigs = qw( affect70percentRule affectBatteryPreferredCharge affectConsFo setupRoofTops ); -for my $cinit (1..$maxconsumer) { - $cinit = sprintf "%02d", $cinit; - push @aconfigs, "consumer${cinit}"; # Anlagenkonfiguration: add Consumer Attribute - push @dd, "consumerSwitching${cinit}"; # ctrlDebug: add specific Consumer +for my $cn (1..$maxconsumer) { + $cn = sprintf "%02d", $cn; + push @aconfigs, "consumer${cn}"; # Anlagenkonfiguration: add Consumer Attribute + push @dd, "consumerSwitching${cn}"; # ctrlDebug: add specific Consumer } for my $in (1..$maxinverter) { @@ -576,9 +568,9 @@ for my $in (1..$maxinverter) { push @aconfigs, "setupInverterDev${in}"; # Anlagenkonfiguration: add Inverter Attribute } -for my $prn (1..$maxproducer) { - $prn = sprintf "%02d", $prn; - push @aconfigs, "setupOtherProducer${prn}"; # Anlagenkonfiguration: add Producer Attribute +for my $pn (1..$maxproducer) { + $pn = sprintf "%02d", $pn; + push @aconfigs, "setupOtherProducer${pn}"; # Anlagenkonfiguration: add Producer Attribute } my $allwidgets = 'icon|sortable|uzsu|knob|noArg|time|text|slider|multiple|select|bitfield|widgetList|colorpicker'; @@ -658,6 +650,7 @@ my %hattr = ( # H setupRadiationAPI => { fn => \&_attrRadiationAPI }, setupStringPeak => { fn => \&_attrStringPeak }, setupRoofTops => { fn => \&_attrRoofTops }, + flowGraphicControl => { fn => \&_attrflowGraphicControl }, ); for my $in (1..$maxinverter) { @@ -705,7 +698,7 @@ my %hqtxt = ( crd => { EN => qq{Please select the radiation forecast service with "attr LINK setupRadiationAPI"}, DE => qq{Bitte geben sie den Strahlungsvorhersage Dienst mit "attr LINK setupRadiationAPI" an} }, cid => { EN => qq{Please specify the Inverter device with "attr LINK setupInverterDev01"}, - DE => qq{Bitte geben sie das Wechselrichter Device mit "attr LINK setupInverterDev01" an} }, + DE => qq{Bitte geben sie das Wechselrichter Device mit "attr LINK setupInverterDev01" an} }, mid => { EN => qq{Please specify the device for energy measurement with "attr LINK setupMeterDev"}, DE => qq{Bitte geben sie das Device zur Energiemessung mit "attr LINK setupMeterDev" an} }, ist => { EN => qq{Please define all of your used string names with "attr LINK setupInverterStrings"}, @@ -1264,15 +1257,7 @@ sub Initialize { "ctrlStatisticReadings:multiple-strict,$srd ". "ctrlUserExitFn:textField-long ". "disable:1,0 ". - "flowGraphicSize ". - "flowGraphicAnimate:1,0 ". - "flowGraphicConsumerDistance:slider,80,10,500 ". - "flowGraphicShift:slider,-80,5,80 ". - "flowGraphicShowConsumer:1,0 ". - "flowGraphicShowConsumerDummy:1,0 ". - "flowGraphicShowConsumerPower:0,1 ". - "flowGraphicShowConsumerRemainTime:0,1 ". - "flowGraphicCss:textField-long ". + "flowGraphicControl:textField-long ". "graphicBeamHeightLevel1 ". "graphicBeamHeightLevel2 ". "graphicBeamWidth:slider,20,5,100 ". @@ -1320,6 +1305,13 @@ sub Initialize { $setupprod. $consumer. $readingFnAttributes; + + ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! + ########################################################################################################################## + my $av = 'obsolete#-#use#attr#flowGraphicControl#instead'; + $hash->{AttrList} .= " flowGraphicCss:$av flowGraphicSize:$av flowGraphicAnimate:$av flowGraphicConsumerDistance:$av flowGraphicShowConsumer:$av flowGraphicShowConsumerDummy:$av flowGraphicShowConsumerPower:$av flowGraphicShowConsumerRemainTime:$av flowGraphicShift:$av "; + + ########################################################################################################################## $hash->{FW_hideDisplayName} = 1; # Forum 88667 @@ -5356,14 +5348,16 @@ sub Attr { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ###################################################################################################################### - #if ($cmd eq 'set' && $aName eq 'affectMaxDayVariance') { - # if (!$init_done) { - # return qq{Device "$name" -> The attribute '$aName' is obsolete and will be deleted soon. Please press "save config" when restart is finished.}; - # } - # else { - # return qq{The attribute '$aName' is obsolete and will be deleted soon.}; - # } - #} + # 20.10.2024 + if ($cmd eq 'set' && $aName =~ /^flowGraphicCss|flowGraphicSize|flowGraphicAnimate|flowGraphicConsumerDistance|flowGraphicShowConsumer|flowGraphicShowConsumerDummy|flowGraphicShowConsumerPower|flowGraphicShowConsumerRemainTime|flowGraphicShift$/) { + if (!$init_done) { + # return qq{Device "$name" -> The attribute '$aName' is replaced by 'flowGraphicControl'. Please press "save config" when restart is finished.}; + Log3 ($name, 1, qq{$name - The attribute '$aName' is replaced by 'flowGraphicControl'. Please press "save config" when restart is finished.}); + } + else { + return qq{The attribute '$aName' is obsolete and replaced by 'flowGraphicControl'.}; + } + } ###################################################################################################################### if ($aName eq 'disable') { @@ -5406,7 +5400,6 @@ sub Attr { } if ($aName eq 'ctrlGenPVdeviation' && $aVal eq 'daily') { - my $type = $hash->{TYPE}; readingsDelete ($hash, 'Today_PVdeviation'); delete $data{$type}{$name}{circular}{99}{tdayDvtn}; } @@ -5669,6 +5662,69 @@ sub _attrctrlDebug { ## no critic "not used" return; } +################################################################ +# Attr flowGraphicControl +################################################################ +sub _attrflowGraphicControl { ## no critic "not used" + my $paref = shift; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $aVal = $paref->{aVal}; + my $cmd = $paref->{cmd}; + + my $hash = $defs{$name}; + + for my $av ( qw( animate + consumerdist + shift + showconsumer + showconsumerremaintime + size + showconsumerdummy + showconsumerpower + strokecolstd + strokecolsig + strokecolina + strokewidth + ) ) { + + delete $data{$type}{$name}{current}{$av}; + } + + if ($cmd eq 'set') { + my $valid = { + animate => '0|1', + consumerdist => '[89]\d{1}|[1234]\d{2}|500', + shift => '-?[0-7]\d{0,1}|-?80', + size => '\d+', + showconsumer => '0|1', + showconsumerdummy => '0|1', + showconsumerremaintime => '0|1', + showconsumerpower => '0|1', + strokecolstd => '.*', + strokecolsig => '.*', + strokecolina => '.*', + strokewidth => '\d+', + }; + + my ($a, $h) = parseParams ($aVal); + + for my $key (keys %{$h}) { + my $comp = $valid->{$key}; + next if(!$comp); + + if ($h->{$key} =~ /^$comp$/xs) { + $data{$type}{$name}{current}{$key} = $h->{$key}; + } + else { + return "The key '$key=$h->{$key}' is not specified correctly. Please use a valid value."; + } + } + } + +return; +} + ################################################################ # Attr setupMeterDev ################################################################ @@ -5795,18 +5851,37 @@ sub _attrInverterDev { ## no critic "not used" if ($paref->{cmd} eq 'set') { my ($err, $indev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); + + if ($in ne '01' && !AttrVal ($name, 'setupInverterDev01', '')) { + return qq{Set the first Inverter device with attribute 'setupInverterDev01'}; + } if (!$h->{pv} || !$h->{etotal}) { - return qq{The syntax of '$aName' is not correct. Please consider the commandref.}; + return qq{The syntax of '$aName' is not valid. Please consider the commandref.}; } if ($h->{capacity} && !isNumeric($h->{capacity})) { - return qq{The syntax of key 'capacity' is not correct. Please consider the commandref.}; + return qq{The syntax of key 'capacity' is not valid. Please consider the commandref.}; } - + + if ($h->{feed} && $h->{feed} !~ /^grid|bat$/xs) { + return qq{The value of key 'feed' is not valid. Please consider the commandref.}; + } + + if ($h->{strings}) { + for my $s (split ',', $h->{strings}) { + if (!grep /^$s$/, keys %{$data{$type}{$name}{strings}}) { + return qq{The string '$s' is not a valid string name defined in attribute 'setupInverterStrings'.}; + } + } + } + $data{$type}{$name}{circular}{99}{attrInvChangedTs} = int time; + delete $data{$type}{$name}{inverters}{$in}{invertercap}; delete $data{$type}{$name}{inverters}{$in}{iicon}; + delete $data{$type}{$name}{inverters}{$in}{istrings}; + delete $data{$type}{$name}{inverters}{$in}{ifeed}; } elsif ($paref->{cmd} eq 'del') { for my $k (keys %{$data{$type}{$name}{inverters}}) { @@ -6843,6 +6918,30 @@ sub centralTask { $n = sprintf "%02d", $n; readingsDelete ($hash, "pvCorrectionFactor_${n}_autocalc"); } + + my $fg1 = AttrVal ($name, 'flowGraphicSize', undef); # 20.10.2024 + my $fg2 = AttrVal ($name, 'flowGraphicAnimate', undef); + my $fg3 = AttrVal ($name, 'flowGraphicConsumerDistance', undef); + my $fg4 = AttrVal ($name, 'flowGraphicShowConsumer', undef); + my $fg5 = AttrVal ($name, 'flowGraphicShowConsumerDummy', undef); + my $fg6 = AttrVal ($name, 'flowGraphicShowConsumerPower', undef); + my $fg7 = AttrVal ($name, 'flowGraphicShowConsumerRemainTime', undef); + my $fg8 = AttrVal ($name, 'flowGraphicShift', undef); + + my $newval; + $newval .= "size=$fg1 " if(defined $fg1); + $newval .= "animate=$fg2 " if(defined $fg2); + $newval .= "consumerdist=$fg3 " if(defined $fg3); + $newval .= "showconsumer=$fg4 " if(defined $fg4); + $newval .= "showconsumerdummy=$fg5 " if(defined $fg5); + $newval .= "showconsumerpower=$fg6 " if(defined $fg6); + $newval .= "showconsumerremaintime=$fg7 " if(defined $fg7); + $newval .= "shift=$fg8 " if(defined $fg8); + + if ($newval) { + CommandAttr (undef, "$name flowGraphicControl $newval"); + ::CommandDeleteAttr (undef, "$name flowGraphicSize|flowGraphicAnimate|flowGraphicConsumerDistance|flowGraphicShowConsumer|flowGraphicShowConsumerDummy|flowGraphicShowConsumerPower|flowGraphicShowConsumerRemainTime|flowGraphicShift"); + } ########################################################################################################################## setModel ($hash); # Model setzen @@ -6910,14 +7009,13 @@ sub centralTask { readingsDelete ($hash, 'AllPVforecastsToEvent'); _getRoofTopData ($centpars); # Strahlungswerte/Forecast-Werte in solcastapi-Hash erstellen + _transferInverterValues ($centpars); # WR Werte übertragen _transferAPIRadiationValues ($centpars); # Raw Erzeugungswerte aus solcastapi-Hash übertragen und Forecast mit/ohne Korrektur erstellen _calcMaxEstimateToday ($centpars); # heutigen Max PV Estimate & dessen Tageszeit ermitteln - _transferInverterValues ($centpars); # WR Werte übertragen _transferProducerValues ($centpars); # Werte anderer Erzeuger übertragen _transferMeterValues ($centpars); # Energy Meter auswerten _transferBatteryValues ($centpars); # Batteriewerte einsammeln _batSocTarget ($centpars); # Batterie Optimum Ziel SOC berechnen - #_createSummaries ($centpars); # Zusammenfassungen erstellen _manageConsumerData ($centpars); # Consumer Daten sammeln und Zeiten planen _estConsumptionForecast ($centpars); # Verbrauchsprognose erstellen _evaluateThresholds ($centpars); # Schwellenwerte bewerten und signalisieren @@ -7584,7 +7682,7 @@ sub __deletePvCorffReadings { ($pcf) = split " / ", $pcf if($pcf =~ /\s\/\s/xs); if ($pcf !~ /manual/xs) { # manuell gesetzte pcf-Readings nicht löschen - deleteReadingspec ($hash, "pvCorrectionFactor_${n}.*"); + readingsDelete ($hash, "pvCorrectionFactor_${n}"); # V 1.37.0 } else { readingsSingleUpdate ($hash, "pvCorrectionFactor_${n}", $pcf, 0); @@ -8201,16 +8299,16 @@ sub __calcPVestimates { my $fd = $paref->{fd}; my $num = $paref->{num}; my $debug = $paref->{debug}; - my $hash = $defs{$name}; - + + my $hash = $defs{$name}; my $reld = $fd == 0 ? "today" : $fd == 1 ? "tomorrow" : "unknown"; - my $rr1c = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "rr1c", 0); # Gesamtniederschlag während der letzten Stunde kg/m2 - my $wcc = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "wcc", 0); # effektive Wolkendecke nächste Stunde X - my $temp = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "temp", $tempbasedef); # vorhergesagte Temperatur Stunde X + my $rr1c = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "rr1c", 0); # Gesamtniederschlag während der letzten Stunde kg/m2 + my $wcc = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "wcc", 0); # effektive Wolkendecke nächste Stunde X + my $temp = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "temp", $tempbasedef); # vorhergesagte Temperatur Stunde X my ($acu, $aln) = isAutoCorrUsed ($name); $paref->{wcc} = $wcc; - my ($hc, $hq) = ___readCandQ ($paref); # liest den anzuwendenden Korrekturfaktor + my ($hc, $hq) = ___readCandQ ($paref); # liest den anzuwendenden Korrekturfaktor delete $paref->{wcc}; my ($lh,$sq,$peakloss, $modtemp); @@ -8237,6 +8335,23 @@ sub __calcPVestimates { my $est = SolCastAPIVal ($hash, $string, $wantdt, 'pv_estimate50', 0); my $pv = sprintf "%.1f", ($est * $hc); # Korrekturfaktor anwenden + my $invcap = 0; + + for my $in (keys %{$data{$type}{$name}{inverters}}) { + my $istrings = InverterVal ($hash, $in, 'istrings', ''); # dem Inverter zugeordnete Strings + next if(!grep /^$string$/, (split ',', $istrings)); + + $invcap = InverterVal ($hash, $in, 'invertercap', 0); # Max. Leistung des Inverters + + last; + } + + if ($invcap && $pv > $invcap) { + $pv = $invcap; # PV Vorhersage auf WR Kapazität begrenzen + + debugLog ($paref, "radiationProcess", "PV forecast start time $wantdt limited to $pv Wh due to inverter capacity"); + } + if ($debug =~ /radiationProcess/xs) { $lh = { # Log-Hash zur Ausgabe "String Peak" => $peak. " W", @@ -8264,21 +8379,16 @@ sub __calcPVestimates { } $data{$type}{$name}{current}{allstringspeak} = $peaksum; # temperaturbedingte Korrektur der installierten Peakleistung in W - - $pvsum = $peaksum if($peaksum && $pvsum > $peaksum); # Vorhersage nicht größer als die Summe aller PV-Strings Peak - - my $invcap = InverterVal ($hash, '01', 'invertercap', 0); # Max. Leistung des Invertrs - - if ($invcap && $pvsum > $invcap) { - $pvsum = $invcap; # PV Vorhersage auf WR Kapazität begrenzen - - debugLog ($paref, "radiationProcess", "PV forecast start time $wantdt limited to $pvsum Watt due to inverter capacity"); - } + $pvsum = $peaksum if($peaksum && $pvsum > $peaksum); # Vorhersage nicht größer als die Summe aller PV-Strings Peak my $logao = qq{}; $paref->{pvsum} = $pvsum; $paref->{peaksum} = $peaksum; + ($pvsum, $logao) = ___70percentRule ($paref); + + delete $paref->{peaksum}; + delete $paref->{pvsum}; if ($debug =~ /radiationProcess/xs) { $lh = { # Log-Hash zur Ausgabe @@ -8532,19 +8642,23 @@ sub _transferInverterValues { $warn = ' (WARNING invalid real PV occured - see Logfile)'; } - $data{$type}{$name}{inverters}{$in}{igeneration} = $pv; # Hilfshash Wert current generation, Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 - $data{$type}{$name}{inverters}{$in}{ietotal} = $etotal; # aktuellen etotal des WR speichern - $data{$type}{$name}{inverters}{$in}{iname} = $indev; # Name des Inverterdevices - $data{$type}{$name}{inverters}{$in}{ialias} = AttrVal ($indev, 'alias', $indev); # Alias Inverter - $data{$type}{$name}{inverters}{$in}{invertercap} = $h->{capacity} if(defined $h->{capacity}); # optionale Angabe max. WR-Leistung - $data{$type}{$name}{inverters}{$in}{iicon} = $h->{icon} if($h->{icon}); # Icon des Inverters - + my $feed = $h->{feed} // 'default'; + + $data{$type}{$name}{inverters}{$in}{igeneration} = $pv; # Hilfshash Wert current generation, Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 + $data{$type}{$name}{inverters}{$in}{ietotal} = $etotal; # aktuellen etotal des WR speichern + $data{$type}{$name}{inverters}{$in}{iname} = $indev; # Name des Inverterdevices + $data{$type}{$name}{inverters}{$in}{ialias} = AttrVal ($indev, 'alias', $indev); # Alias Inverter + $data{$type}{$name}{inverters}{$in}{invertercap} = $h->{capacity} if(defined $h->{capacity}); # optionale Angabe max. WR-Leistung + $data{$type}{$name}{inverters}{$in}{iicon} = $h->{icon} if($h->{icon}); # Icon des Inverters + $data{$type}{$name}{inverters}{$in}{istrings} = $h->{strings} if($h->{strings}); # dem Inverter zugeordnete Strings + $data{$type}{$name}{inverters}{$in}{ifeed} = $feed; # Eigenschaften der Energielieferung + $pvsum += $pv; $ethishoursum += $ethishour; writeToHistory ( { paref => $paref, key => 'pvrl'.$in, val => $ethishour, hour => $nhour } ); - debugLog ($paref, "collectData", "collect Inverter $in data - device: $indev =>"); + debugLog ($paref, "collectData", "collect Inverter $in data - device: $indev, delivery: $feed =>"); debugLog ($paref, "collectData", "pv: $pv W, etotal: $etotal Wh"); } @@ -8615,6 +8729,7 @@ sub _transferProducerValues { $data{$type}{$name}{producers}{$pn}{pname} = $prdev; # Name des Producerdevices $data{$type}{$name}{producers}{$pn}{palias} = AttrVal ($prdev, 'alias', $prdev); # Alias Producer $data{$type}{$name}{producers}{$pn}{picon} = $h->{icon} if($h->{icon}); # Icon des Producers + $data{$type}{$name}{producers}{$pn}{pfeed} = 'default'; # Eigenschaften der Energielieferung if ($ethishour < 0) { $ethishour = 0; @@ -8659,10 +8774,10 @@ sub _transferMeterValues { my $type = $paref->{type}; - my ($gc,$gcunit) = split ":", $h->{gcon}; # Readingname/Unit für aktuellen Netzbezug - my ($gf,$gfunit) = split ":", $h->{gfeedin}; # Readingname/Unit für aktuelle Netzeinspeisung - my ($gt,$ctunit) = split ":", $h->{contotal}; # Readingname/Unit für Bezug total - my ($ft,$ftunit) = split ":", $h->{feedtotal}; # Readingname/Unit für Einspeisung total + my ($gc, $gcunit) = split ":", $h->{gcon}; # Readingname/Unit für aktuellen Netzbezug + my ($gf, $gfunit) = split ":", $h->{gfeedin}; # Readingname/Unit für aktuelle Netzeinspeisung + my ($gt, $ctunit) = split ":", $h->{contotal}; # Readingname/Unit für Bezug total + my ($ft, $ftunit) = split ":", $h->{feedtotal}; # Readingname/Unit für Einspeisung total return if(!$gc || !$gf || !$gt || !$ft); @@ -8728,19 +8843,19 @@ sub _transferMeterValues { my $gcuf = $gcunit =~ /^kW$/xi ? 1000 : 1; my $gfuf = $gfunit =~ /^kW$/xi ? 1000 : 1; - $gco = ReadingsNum ($medev, $gc, 0) * $gcuf; # aktueller Bezug (W) - $gfin = ReadingsNum ($medev, $gf, 0) * $gfuf; # aktuelle Einspeisung (W) + $gco = ReadingsNum ($medev, $gc, 0) * $gcuf; # aktueller Bezug (W) + $gfin = ReadingsNum ($medev, $gf, 0) * $gfuf; # aktuelle Einspeisung (W) my $params; - if ($gc eq '-gfeedin') { # Spezialfall gcon bei neg. gfeedin # Spezialfall: bei negativen gfeedin -> $gco = abs($gf), $gf = 0 + if ($gc eq '-gfeedin') { # Spezialfall gcon bei neg. gfeedin # Spezialfall: bei negativen gfeedin -> $gco = abs($gf), $gf = 0 $params = { dev => $medev, rdg => $gf, rdgf => $gfuf }; - ($gfin,$gco) = substSpecialCases ($params); + ($gfin, $gco) = substSpecialCases ($params); } if ($gf eq '-gcon') { # Spezialfall gfeedin bei neg. gcon @@ -8750,15 +8865,9 @@ sub _transferMeterValues { rdgf => $gcuf }; - ($gco,$gfin) = substSpecialCases ($params); + ($gco, $gfin) = substSpecialCases ($params); } - storeReading ('Current_GridConsumption', (int $gco).' W'); - $data{$type}{$name}{current}{gridconsumption} = int $gco; # Hilfshash Wert current grid consumption Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 - - storeReading ('Current_GridFeedIn', (int $gfin).' W'); - $data{$type}{$name}{current}{gridfeedin} = int $gfin; # Hilfshash Wert current grid Feed in - my $ctuf = $ctunit =~ /^kWh$/xi ? 1000 : 1; my $gctotal = ReadingsNum ($medev, $gt, 0) * $ctuf; # Bezug total (Wh) @@ -8767,21 +8876,24 @@ sub _transferMeterValues { $data{$type}{$name}{circular}{99}{gridcontotal} = $gctotal; # Total Netzbezug speichern $data{$type}{$name}{circular}{99}{feedintotal} = $fitotal; # Total Feedin speichern + $data{$type}{$name}{current}{gridconsumption} = int $gco; # Current grid consumption Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 + $data{$type}{$name}{current}{gridfeedin} = int $gfin; # Wert current grid Feed in debugLog ($paref, "collectData", "collect Meter data - device: $medev =>"); debugLog ($paref, "collectData", "gcon: $gco W, gfeedin: $gfin W, contotal: $gctotal Wh, feedtotal: $fitotal Wh"); + + ## Management aus dem Netz bezogener Energie + ############################################## my $gcdaypast = 0; my $gfdaypast = 0; + my $docon = 0; for my $hour (0..int $chour) { # alle bisherigen Erzeugungen des Tages summieren $gcdaypast += ReadingsNum ($name, "Today_Hour".sprintf("%02d",$hour)."_GridConsumption", 0); $gfdaypast += ReadingsNum ($name, "Today_Hour".sprintf("%02d",$hour)."_GridFeedIn", 0); } - - ## Management aus dem Netz bezogener Energie - ############################################## - my $docon = 0; + my $idgcon = CircularVal ($hash, 99, 'initdaygcon', undef); if (!$gctotal) { @@ -8816,8 +8928,8 @@ sub _transferMeterValues { writeToHistory ( { paref => $paref, key => 'gcons', val => $gctotthishour, hour => $nhour } ); } - ## Management der in das Netz eingespeister Energie - ##################################################### + ## Management der in das Netz eingespeister (nur vom Meter gemessene) Energie + ############################################################################### my $dofeed = 0; my $idfin = CircularVal ($hash, 99, 'initdayfeedin', undef); @@ -8834,7 +8946,7 @@ sub _transferMeterValues { } } elsif (!defined $idfin) { - $data{$type}{$name}{circular}{99}{initdayfeedin} = $fitotal - $gfdaypast - ReadingsNum ($name, "Today_Hour".sprintf("%02d",$chour+1)."_GridFeedIn", 0); + $data{$type}{$name}{circular}{99}{initdayfeedin} = $fitotal - $gfdaypast - ReadingsNum ($name, 'Today_Hour'.sprintf("%02d",$chour+1).'_GridFeedIn', 0); } else { $dofeed = 1; @@ -9290,37 +9402,41 @@ sub _createSummaries { push @{$data{$type}{$name}{current}{h4fcslidereg}}, int $next4HoursSum->{PV}; # Schieberegister 4h Summe Forecast limitArray ($data{$type}{$name}{current}{h4fcslidereg}, $slidenumdef); - my $gcon = CurrentVal ($hash, 'gridconsumption', 0); # aktueller Netzbezug - my $tconsum = CurrentVal ($hash, 'tomorrowconsumption', undef); # Verbrauchsprognose für folgenden Tag - my $gfeedin = CurrentVal ($hash, 'gridfeedin', 0); - my $batin = CurrentVal ($hash, 'powerbatin', 0); # aktuelle Batterieladung - my $batout = CurrentVal ($hash, 'powerbatout', 0); # aktuelle Batterieentladung + my $gcon = CurrentVal ($hash, 'gridconsumption', 0); # aktueller Netzbezug + my $tconsum = CurrentVal ($hash, 'tomorrowconsumption', undef); # Verbrauchsprognose für folgenden Tag + my $gfeedin = CurrentVal ($hash, 'gridfeedin', 0); + my $batin = CurrentVal ($hash, 'powerbatin', 0); # aktuelle Batterieladung + my $batout = CurrentVal ($hash, 'powerbatout', 0); # aktuelle Batterieentladung my $pvgen = 0; + my $pv2grid = 0; # PV-Erzeugung zu Grid-only for my $in (1..$maxinverter) { # Summe alle Inverter - $in = sprintf "%02d", $in; - $pvgen += InverterVal ($hash, $in, 'igeneration', 0); + $in = sprintf "%02d", $in; + my $pvi = InverterVal ($hash, $in, 'igeneration', 0); + my $feed = InverterVal ($hash, $in, 'ifeed', ''); + $pvgen += $pvi; + $pv2grid += $pvi if($feed eq 'grid'); } my $othprod = 0; # Summe Otherproducer - for my $pn (1..$maxproducer) { # V1.32.0 : Erzeugung sonstiger Producer (01..03) hinzufügen + for my $pn (1..$maxproducer) { # Erzeugung sonstiger Producer hinzufügen $pn = sprintf "%02d", $pn; $othprod += ProducerVal ($hash, $pn, 'pgeneration', 0); } - my $consumption = int ($pvgen + $othprod - $gfeedin + $gcon - $batin + $batout); - my $selfconsumption = int ($pvgen - $gfeedin - $batin); + my $consumption = int ($pvgen - $pv2grid + $othprod - $gfeedin + $gcon - $batin + $batout); # ohne PV2Grid + my $selfconsumption = int ($pvgen - $pv2grid - $gfeedin - $batin); $selfconsumption = $selfconsumption < 0 ? 0 : $selfconsumption; - my $surplus = int ($pvgen + $othprod - $consumption); # aktueller Überschuß + my $surplus = int ($pvgen - $pv2grid + $othprod - $consumption); # aktueller Überschuß $surplus = 0 if($surplus < 0); # wegen Vergleich nompower vs. surplus my $selfconsumptionrate = 0; my $autarkyrate = 0; my $divi = $selfconsumption + $batout + $gcon; - $selfconsumptionrate = sprintf "%.0f", $selfconsumption / $pvgen * 100 if($pvgen * 1 > 0); + $selfconsumptionrate = sprintf "%.0f", ($selfconsumption / $pvgen * 100) if($pvgen * 1 > 0); $autarkyrate = sprintf "%.0f", ($selfconsumption + $batout) / $divi * 100 if($divi); # vermeide Illegal division by zero $data{$type}{$name}{current}{consumption} = $consumption; @@ -9330,6 +9446,8 @@ sub _createSummaries { $data{$type}{$name}{current}{surplus} = $surplus; $data{$type}{$name}{current}{tdConFcTillSunset} = $tdConFcTillSunset; + storeReading ('Current_GridFeedIn', (int $gfeedin). ' W'); # V 1.37.0 + storeReading ('Current_GridConsumption', (int $gcon). ' W'); # V 1.37.0 storeReading ('Current_Consumption', $consumption. ' W'); storeReading ('Current_SelfConsumption', $selfconsumption. ' W'); storeReading ('Current_SelfConsumptionRate', $selfconsumptionrate. ' %'); @@ -9591,7 +9709,7 @@ sub __calcEnergyPieces { my $cotype = ConsumerVal ($hash, $c, "type", $defctype ); my $mintime = ConsumerVal ($hash, $c, "mintime", $defmintime); - if (isSunPath ($hash, $c)) { # SunPath ist in mintime gesetzt + if (isSunPath ($hash, $c)) { # SunPath ist in mintime gesetzt my ($riseshift, $setshift) = sunShift ($hash, $c); my $tdiff = (CurrentVal ($hash, 'sunsetTodayTs', 0) + $setshift) - (CurrentVal ($hash, 'sunriseTodayTs', 0) + $riseshift); @@ -9600,8 +9718,7 @@ sub __calcEnergyPieces { my $hours = ceil ($mintime / 60); # Einplanungsdauer in h my $ctote = ConsumerVal ($hash, $c, "avgenergy", undef); # gemessener durchschnittlicher Energieverbrauch pro Stunde (Wh) - $ctote = $ctote ? - $ctote : + $ctote = $ctote ? $ctote : ConsumerVal ($hash, $c, "power", 0); # alternativer nominaler Energieverbrauch in W (bzw. Wh bezogen auf 1 h) if (int($hef{$cotype}{f}) == 1) { # bei linearen Verbrauchertypen die nominale Leistungsangabe verwenden statt Durchschnitt @@ -11514,8 +11631,8 @@ sub __calcNewFactor { 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); - + $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 ! @@ -11569,7 +11686,9 @@ sub __calcNewFactor { $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$crang} = $factor; $data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{$crang} = $qual; } - + + $oldfac = sprintf "%.2f", $oldfac; + return ($oldfac, $factor, $dnum); } @@ -12001,44 +12120,43 @@ sub entryGraphic { modulo => 1, dstyle => qq{style='padding-left: 10px; padding-right: 10px; padding-top: 3px; padding-bottom: 3px; white-space:nowrap;'}, # TD-Style offset => $offset, - hourstyle => AttrVal ($name, 'graphicHourStyle', ''), - colorb1 => AttrVal ($name, 'graphicBeam1Color', $b1coldef), - colorb2 => AttrVal ($name, 'graphicBeam2Color', $b2coldef), - fcolor1 => AttrVal ($name, 'graphicBeam1FontColor', $b1fontcoldef), - fcolor2 => AttrVal ($name, 'graphicBeam2FontColor', $b2fontcoldef), - beam1cont => AttrVal ($name, 'graphicBeam1Content', 'pvReal'), - beam2cont => AttrVal ($name, 'graphicBeam2Content', 'pvForecast'), - beam3cont => AttrVal ($name, 'graphicBeam3Content', ''), - beam4cont => AttrVal ($name, 'graphicBeam4Content', ''), - caicon => AttrVal ($name, 'consumerAdviceIcon', $caicondef), # Consumer AdviceIcon - clegend => AttrVal ($name, 'consumerLegend', 'icon_top'), # Lage und Art Cunsumer Legende - clink => AttrVal ($name, 'consumerLink' , 1), # Detail-Link zum Verbraucher - lotype => AttrVal ($name, 'graphicLayoutType', 'double'), - kw => AttrVal ($name, 'graphicEnergyUnit', 'Wh'), - height => AttrNum ($name, 'graphicBeamHeightLevel1', 200), + hourstyle => AttrVal ($name, 'graphicHourStyle', ''), + colorb1 => AttrVal ($name, 'graphicBeam1Color', $b1coldef), + colorb2 => AttrVal ($name, 'graphicBeam2Color', $b2coldef), + fcolor1 => AttrVal ($name, 'graphicBeam1FontColor', $b1fontcoldef), + fcolor2 => AttrVal ($name, 'graphicBeam2FontColor', $b2fontcoldef), + beam1cont => AttrVal ($name, 'graphicBeam1Content', 'pvReal'), + beam2cont => AttrVal ($name, 'graphicBeam2Content', 'pvForecast'), + beam3cont => AttrVal ($name, 'graphicBeam3Content', ''), + beam4cont => AttrVal ($name, 'graphicBeam4Content', ''), + caicon => AttrVal ($name, 'consumerAdviceIcon', $caicondef), # Consumer AdviceIcon + clegend => AttrVal ($name, 'consumerLegend', 'icon_top'), # Lage und Art Cunsumer Legende + clink => AttrVal ($name, 'consumerLink' , 1), # Detail-Link zum Verbraucher + lotype => AttrVal ($name, 'graphicLayoutType', 'double'), + kw => AttrVal ($name, 'graphicEnergyUnit', 'Wh'), + height => AttrNum ($name, 'graphicBeamHeightLevel1', 200), width => $width, - fsize => AttrNum ($name, 'graphicSpaceSize', 24), - maxVal => AttrNum ($name, 'graphicBeam1MaxVal', 0), # dyn. Anpassung der Balkenhöhe oder statisch ? - show_night => AttrNum ($name, 'graphicShowNight', 0), # alle Balken (Spalten) anzeigen ? - show_diff => AttrVal ($name, 'graphicShowDiff', 'no'), # zusätzliche Anzeige $di{} in allen Typen - weather => AttrNum ($name, 'graphicShowWeather', 1), # Wetter Icons anzeigen - colorw => AttrVal ($name, 'graphicWeatherColor', $wthcolddef), # Wetter Icon Farbe Tag - colorwn => AttrVal ($name, 'graphicWeatherColorNight', $wthcolndef), # Wetter Icon Farbe Nacht - wlalias => AttrVal ($name, 'alias', $name), - sheader => AttrNum ($name, 'graphicHeaderShow', 1), # Anzeigen des Grafik Headers - hdrDetail => AttrVal ($name, 'graphicHeaderDetail', 'all'), # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten - flowgsize => AttrVal ($name, 'flowGraphicSize', $flowGSizedef), # Größe Energieflußgrafik - flowgani => AttrVal ($name, 'flowGraphicAnimate', 0), # Animation Energieflußgrafik - flowgshift => AttrVal ($name, 'flowGraphicShift', 0), # Verschiebung der Flußgrafikbox (muß negiert werden) - flowgcons => AttrVal ($name, 'flowGraphicShowConsumer', 1), # Verbraucher in der Energieflußgrafik anzeigen - flowgconX => AttrVal ($name, 'flowGraphicShowConsumerDummy', 1), # Dummyverbraucher in der Energieflußgrafik anzeigen - flowgconsPower => AttrVal ($name, 'flowGraphicShowConsumerPower' , 1), # Verbraucher Leistung in der Energieflußgrafik anzeigen - flowgconsTime => AttrVal ($name, 'flowGraphicShowConsumerRemainTime', 1), # Verbraucher Restlaufeit in der Energieflußgrafik anzeigen - flowgconsDist => AttrVal ($name, 'flowGraphicConsumerDistance', $fgCDdef), # Abstand Verbrauchericons zueinander - css => AttrVal ($name, 'flowGraphicCss', $cssdef), # flowGraphicCss Styles - genpvdva => AttrVal ($name, 'ctrlGenPVdeviation', 'daily'), # Methode der Abweichungsberechnung - lang => getLang ($hash), - debug => getDebug ($hash), # Debug Module + fsize => AttrNum ($name, 'graphicSpaceSize', 24), + maxVal => AttrNum ($name, 'graphicBeam1MaxVal', 0), # dyn. Anpassung der Balkenhöhe oder statisch ? + show_night => AttrNum ($name, 'graphicShowNight', 0), # alle Balken (Spalten) anzeigen ? + show_diff => AttrVal ($name, 'graphicShowDiff', 'no'), # zusätzliche Anzeige $di{} in allen Typen + weather => AttrNum ($name, 'graphicShowWeather', 1), # Wetter Icons anzeigen + colorw => AttrVal ($name, 'graphicWeatherColor', $wthcolddef), # Wetter Icon Farbe Tag + colorwn => AttrVal ($name, 'graphicWeatherColorNight', $wthcolndef), # Wetter Icon Farbe Nacht + wlalias => AttrVal ($name, 'alias', $name), + sheader => AttrNum ($name, 'graphicHeaderShow', 1), # Anzeigen des Grafik Headers + hdrDetail => AttrVal ($name, 'graphicHeaderDetail', 'all'), # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten + flowgsize => CurrentVal ($hash, 'size', $flowGSizedef), # Größe Energieflußgrafik + flowgani => CurrentVal ($hash, 'animate', 0), # Animation Energieflußgrafik + flowgshift => CurrentVal ($hash, 'shift', 0), # Verschiebung der Flußgrafikbox (muß negiert werden) + flowgcons => CurrentVal ($hash, 'showconsumer', 1), # Verbraucher in der Energieflußgrafik anzeigen + flowgconX => CurrentVal ($hash, 'showconsumerdummy', 1), # Dummyverbraucher in der Energieflußgrafik anzeigen + flowgconsPower => CurrentVal ($hash, 'showconsumerpower', 1), # Verbraucher Leistung in der Energieflußgrafik anzeigen + flowgconsTime => CurrentVal ($hash, 'showconsumerremaintime', 1), # Verbraucher Restlaufeit in der Energieflußgrafik anzeigen + flowgconsDist => CurrentVal ($hash, 'consumerdist', $fgCDdef), # Abstand Verbrauchericons zueinander + genpvdva => AttrVal ($name, 'ctrlGenPVdeviation', 'daily'), # Methode der Abweichungsberechnung + lang => getLang ($hash), + debug => getDebug ($hash), # Debug Module }; my $ret = q{}; @@ -12206,23 +12324,23 @@ sub _checkSetupNotComplete { ########################################################################################## - my $is = AttrVal ($name, 'setupInverterStrings', undef); # String Konfig - my $wedev = AttrVal ($name, 'setupWeatherDev1', undef); # Device Vorhersage Wetterdaten (Bewölkung etc.) - my $radev = AttrVal ($name, 'setupRadiationAPI', undef); # Device Strahlungsdaten Vorhersage - my $indev = AttrVal ($name, 'setupInverterDev01', undef); # Inverter Device - my $medev = AttrVal ($name, 'setupMeterDev', undef); # Meter Device - my $peaks = AttrVal ($name, 'setupStringPeak', undef); # String Peak - my $maz = ReadingsVal ($name, 'setupStringAzimuth', undef); # Modulausrichtung Konfig (Azimut) - my $mdec = ReadingsVal ($name, 'setupStringDeclination', undef); # Modul Neigungswinkel Konfig - my $mrt = AttrVal ($name, 'setupRoofTops', undef); # RoofTop Konfiguration (SolCast API) + my $strings = AttrVal ($name, 'setupInverterStrings', undef); # String Konfig + my $wedev = AttrVal ($name, 'setupWeatherDev1', undef); # Device Vorhersage Wetterdaten (Bewölkung etc.) + my $radev = AttrVal ($name, 'setupRadiationAPI', undef); # Device Strahlungsdaten Vorhersage + my $indev = AttrVal ($name, 'setupInverterDev01', undef); # Inverter Device + my $medev = AttrVal ($name, 'setupMeterDev', undef); # Meter Device + my $peaks = AttrVal ($name, 'setupStringPeak', undef); # String Peak + my $maz = ReadingsVal ($name, 'setupStringAzimuth', undef); # Modulausrichtung Konfig (Azimut) + my $mdec = ReadingsVal ($name, 'setupStringDeclination', undef); # Modul Neigungswinkel Konfig + my $mrt = AttrVal ($name, 'setupRoofTops', undef); # RoofTop Konfiguration (SolCast API) - my $vrmcr = SolCastAPIVal ($hash, '?VRM', '?API', 'credentials', ''); # Victron VRM Credentials gesetzt + my $vrmcr = SolCastAPIVal ($hash, '?VRM', '?API', 'credentials', ''); # Victron VRM Credentials gesetzt - my ($coset, $lat, $lon) = locCoordinates(); # Koordinaten im global device + my ($coset, $lat, $lon) = locCoordinates(); # Koordinaten im global device my $rip; - $rip = 1 if(exists $data{$type}{$name}{solcastapi}{'?IdPair'}); # es existiert mindestens ein Paar RoofTop-ID / API-Key + $rip = 1 if(exists $data{$type}{$name}{solcastapi}{'?IdPair'}); # es existiert mindestens ein Paar RoofTop-ID / API-Key - my $pv0 = NexthoursVal ($hash, 'NextHour00', 'pvfc', undef); # der erste PV ForeCast Wert + my $pv0 = NexthoursVal ($hash, 'NextHour00', 'pvfc', undef); # der erste PV ForeCast Wert my $link = qq{$name}; my $height = AttrNum ($name, 'graphicBeamHeightLevel1', 200); @@ -12249,7 +12367,7 @@ sub _checkSetupNotComplete { my $chkicon = "$img"; my $chktitle = $htitles{plchk}{$lang}; - if (!$is || !$wedev || !$radev || !$indev || !$medev || !$peaks || + if (!$strings || !$wedev || !$radev || !$indev || !$medev || !$peaks || (isSolCastUsed ($hash) ? (!$rip || !$mrt) : isVictronKiUsed ($hash) ? !$vrmcr : (!$maz || !$mdec )) || (isForecastSolarUsed ($hash) ? !$coset : '') || (isOpenMeteoUsed ($hash) ? !$coset : '') || @@ -12262,7 +12380,7 @@ sub _checkSetupNotComplete { if (!$wedev) { ## no critic 'Cascading' $ret .= $hqtxt{cfd}{$lang}; } - elsif (!$is) { + elsif (!$strings) { $ret .= $hqtxt{ist}{$lang}; } elsif (!$peaks) { @@ -14077,79 +14195,97 @@ sub __weatherOnBeam { return $ret; } -################################################################ +###################################################################################### # Energieflußgrafik -################################################################ +# M - MoveTo setzt den aktuellen Punkt fest, von dem aus der Pfad starten soll +# (https://wiki.selfhtml.org/wiki/SVG/Tutorials/Pfade#MoveTo) +# L - LineTo zeichnet eine Linie vom aktuellen zum angegebenen Punkt +# (https://wiki.selfhtml.org/wiki/SVG/Tutorials/Pfade#LineTo) +###################################################################################### sub _flowGraphic { - my $paref = shift; - my $hash = $paref->{hash}; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $flowgsize = $paref->{flowgsize}; - my $flowgani = $paref->{flowgani}; - my $flowgshift = $paref->{flowgshift}; # Verschiebung der Flußgrafikbox (muß negiert werden) - my $flowgcons = $paref->{flowgcons}; # Verbraucher in der Energieflußgrafik anzeigen - my $flowgconTime = $paref->{flowgconsTime}; # Verbraucher Restlaufeit in der Energieflußgrafik anzeigen - my $flowgconX = $paref->{flowgconX}; - my $flowgconPower = $paref->{flowgconsPower}; - my $consDist = $paref->{flowgconsDist}; - my $css = $paref->{css}; - my $lang = $paref->{lang}; + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $flowgsize = $paref->{flowgsize}; + my $flowgani = $paref->{flowgani}; + my $flowgshift = $paref->{flowgshift}; # Verschiebung der Flußgrafikbox (muß negiert werden) + my $flowgcons = $paref->{flowgcons}; # Verbraucher in der Energieflußgrafik anzeigen + my $flowgconsTime = $paref->{flowgconsTime}; # Verbraucher Restlaufeit in der Energieflußgrafik anzeigen + my $flowgconX = $paref->{flowgconX}; + my $flowgconsPower = $paref->{flowgconsPower}; + my $flowgPrdsPower = 1; # initial Producer akt. Erzeugung anzeigen + my $cdist = $paref->{flowgconsDist}; # Abstand Consumer zueinander + my $lang = $paref->{lang}; - my $style = 'width:98%; height:'.$flowgsize.'px;'; - my $animation = $flowgani ? '@keyframes dash { to { stroke-dashoffset: 0; } }' : ''; # Animation Ja/Nein my $cgc = ReadingsNum ($name, 'Current_GridConsumption', 0); - my $cgfi = ReadingsNum ($name, 'Current_GridFeedIn', 0); - my $csc = ReadingsNum ($name, 'Current_SelfConsumption', 0); + my $node2grid = ReadingsNum ($name, 'Current_GridFeedIn', 0); # vom Knoten zum Grid + my $cself = ReadingsNum ($name, 'Current_SelfConsumption', 0); my $cc = CurrentVal ($hash, 'consumption', 0); my $batin = ReadingsNum ($name, 'Current_PowerBatIn', undef); - my $batout = ReadingsNum ($name, 'Current_PowerBatOut', undef); + my $bat2home = ReadingsNum ($name, 'Current_PowerBatOut', undef); my $soc = ReadingsNum ($name, 'Current_BatCharge', 100); my $cc_dummy = $cc; my $scale = $fgscaledef; - my $hasbat = 1; # initial Batterie vorhanden + my $pdist = 130; # Abstand Producer zueinander + my $hasbat = 1; # initial Batterie vorhanden + my $lcp; ## definierte Producer + Inverter ermitteln und zusammenfassen ################################################################ - my $pdcr = {}; # Hashref Producer - my $ppall = 0; # Summe Erzeugung alle nicht PV-Producer - my $pvall = 0; # Summe Erzeugung alle Inverter - my $lfn = 0; + my $pdcr = {}; # Hashref Producer + my $ppall = 0; # Summe Erzeugung alle nicht PV-Producer + my $pv2node = 0; # Summe PV-Erzeugung alle Inverter + my $pv2grid = 0; + my $pv2bat = 0; + my $lfn = 0; for my $pn (1..$maxproducer) { - $pn = sprintf "%02d", $pn; - my $p = ProducerVal ($hash, $pn, 'pgeneration', undef); + $pn = sprintf "%02d", $pn; + my $p = ProducerVal ($hash, $pn, 'pgeneration', undef); + my $feed = ProducerVal ($hash, $pn, 'pfeed', 'default'); if (defined $p) { $p = __normDecPlaces ($p); - $pdcr->{$lfn}{p} = $p; # aktuelle Erzeugung nicht PV-Producer - $pdcr->{$lfn}{pn} = $pn; # Producernummer - $pdcr->{$lfn}{ptyp} = 'producer'; # Typ des Producers - $ppall += $p; # aktuelle Erzeuguung aller nicht PV-Producer + $pdcr->{$lfn}{p} = $p; # aktuelle Erzeugung nicht PV-Producer + $pdcr->{$lfn}{pn} = $pn; # Producernummer + $pdcr->{$lfn}{feed} = $feed; # Eigenschaft der Energielieferung + $pdcr->{$lfn}{ptyp} = 'producer'; # Typ des Producers + $ppall += $p; # aktuelle Erzeuguung aller nicht PV-Producer $lfn++; } } for my $in (1..$maxinverter) { - $in = sprintf "%02d", $in; - my $p = InverterVal ($hash, $in, 'igeneration', undef); + $in = sprintf "%02d", $in; + my $p = InverterVal ($hash, $in, 'igeneration', undef); + my $feed = InverterVal ($hash, $in, 'ifeed', 'default'); if (defined $p) { $p = __normDecPlaces ($p); - $pdcr->{$lfn}{p} = $p; # aktuelle Erzeugung Inverter - $pdcr->{$lfn}{pn} = $in; # Inverternummer - $pdcr->{$lfn}{ptyp} = 'inverter'; # Typ des Producers - $pvall += $p; + $pdcr->{$lfn}{pn} = $in; # Inverternummer + $pdcr->{$lfn}{feed} = $feed; # Eigenschaft der Energielieferung + $pdcr->{$lfn}{ptyp} = 'inverter'; # Typ des Producers + $pdcr->{$lfn}{p} = $p; # aktuelle PV + $pv2node += $p if($feed eq 'default'); # PV-Erzeugung Inverter für das Hausnetz + $pv2grid += $p if($feed eq 'grid'); # PV nur für das öffentliche Netz + $pv2bat += $p if($feed eq 'bat'); # Direktladen PV nur in die Batterie $lfn++; } } - my $pallsum = __normDecPlaces ($ppall + $pvall); - my $producercount = keys %{$pdcr}; - my @producers = sort{$a<=>$b} keys %{$pdcr}; + ## Producer Koordninaten Steuerhash + ##################################### + my ($togrid, $tonode, $tobat) = __sortProducer ($pdcr); # lfn Producer sortiert nach ptyp und feed + + my $psorted = { + '1togrid' => { xicon => -100, xchain => 350, ychain => 420, step => 70, count => scalar @{$togrid}, sorted => $togrid }, # Producer/PV nur zu Grid + '2tonode' => { xicon => 350, xchain => 700, ychain => 200, step => $pdist, count => scalar @{$tonode}, sorted => $tonode }, # Producer/PV zum Knoten + '3tobat' => { xicon => 750, xchain => 1100, ychain => 430, step => 40, count => scalar @{$tobat}, sorted => $tobat }, # Producer/PV nur zu Batterie + }; ## definierte Verbraucher ermitteln ##################################### @@ -14166,114 +14302,142 @@ sub _flowGraphic { ## Batterie + Werte festlegen ############################### - my $bat_color = $soc < 26 ? 'flowg bat25' : - $soc < 76 ? 'flowg bat50' : - 'flowg bat75'; + my $bat_color = $soc < 26 ? "$name bat25" : + $soc < 76 ? "$name bat50" : + "$name bat75"; - if (!defined($batin) && !defined($batout)) { - $hasbat = 0; - $batin = 0; - $batout = 0; - $soc = 0; + if (!defined $batin && !defined $bat2home) { + $hasbat = 0; + $batin = 0; + $bat2home = 0; + $soc = 0; } + + my $grid2home_style = $cgc ? "$name active_sig" : "$name inactive"; # cgc current GridConsumption + my $bat2home_style = $bat2home ? "$name active_normal" : "$name inactive"; + my $cgc_direction = "M490,515 L670,590"; - my $grid_color = $cgfi ? 'flowg grid_color1' : 'flowg grid_color2'; - my $cgc_style = $cgc ? 'flowg active_in' : 'flowg inactive_in'; - my $batout_style = $batout ? 'flowg active_out active_bat_out' : 'flowg inactive_in'; - $grid_color = 'flowg grid_color3' if(!$cgfi && !$cgc && $batout); # dritte Farbe - my $cgc_direction = 'M490,515 L670,590'; # Batterientladung ins Netz - - if ($batout) { # Batterie wird entladen - my $cgfo = $cgfi - $pvall; + if ($bat2home) { # Batterie wird ins Haus entladen + my $cgfo = $node2grid - $pv2node; if ($cgfo > 1) { - $cgc_style = 'flowg active_out'; - $cgc_direction = 'M670,590 L490,515'; - $cgfi -= $cgfo; - $cgc = $cgfo; + $grid2home_style = "$name active_normal"; + $cgc_direction = "M670,590 L490,515"; + $node2grid -= $cgfo; + $cgc = $cgfo; } } + + my $bat2home_direction = "M902,515 L730,590"; + my $node2bat = $batin; - my $batout_direction = 'M902,515 L730,590'; + if ($batin) { # Batterie wird geladen + my $home2bat = $batin - ($pv2node + $pv2bat); - if ($batin) { # Batterie wird geladen - my $gbi = $batin - $pvall; - - if ($gbi > 1) { # Batterieladung anteilig aus Hausnetz geladen - $batin -= $gbi; - $batout_style = 'flowg active_in'; - $batout_direction = 'M730,590 L902,515'; - $batout = $gbi; + if ($home2bat > 1) { # Batterieladung wird anteilig aus Hausnetz geladen + $node2bat -= $home2bat; + $bat2home_style = "$name active_sig"; + $bat2home_direction = "M730,590 L902,515"; + $bat2home = $home2bat; } } ## Werte / SteuerungVars anpassen ################################### - $flowgcons = 0 if(!$consumercount); # Consumer Anzeige ausschalten wenn keine Consumer definiert - my $p2home = __normDecPlaces ($csc + $ppall); # Energiefluß von Knoten zum Haus: Selbstverbrauch + alle Producer + my $pnodesum = __normDecPlaces ($ppall + $pv2node); # Erzeugung Summe im Knoten + $pnodesum += abs $node2bat if($node2bat < 0); # Batterie ist voll und SolarLader liefert an Knoten + $node2bat -= $pv2bat; # Knoten-Bat -> abzüglich Direktladung (pv2bat) + $flowgcons = 0 if(!$consumercount); # Consumer Anzeige ausschalten wenn keine Consumer definiert + my $node2home = __normDecPlaces ($cself + $ppall); # Energiefluß vom Knoten zum Haus: Selbstverbrauch + alle Producer (Batterie-In/Solar-Ladegeräte sind nicht in SelfConsumtion enthalten) + ## SVG Box initialisieren mit Grid-Icon ######################################### - my $vbwidth = 800; # width and height specify the viewBox size - my $vbminx = -10 * $flowgshift; # min-x and min-y represent the smallest X and Y coordinates that the viewBox may have + my $vbwidth = 800; # width and height specify the viewBox size + my $vbminx = -10 * $flowgshift; # min-x and min-y represent the smallest X and Y coordinates that the viewBox may have my $vbminy = -25; - my $vbhight = !$flowgcons ? 380 : - !$flowgconTime ? 590 : + my $vbhight = !$flowgcons ? 380 : + !$flowgconsTime ? 590 : 610; $vbhight += 100; - my $vbox = "$vbminx $vbminy $vbwidth $vbhight"; + my $vbox = "$vbminx $vbminy $vbwidth $vbhight"; + my $svgstyle = 'width:98%; height:'.$flowgsize.'px;'; + my $animation = $flowgani ? '@keyframes dash { to { stroke-dashoffset: 0; } }' : ''; # Animation Ja/Nein + + my $grid_color = $node2grid ? "$name grid_green" : + !$node2grid && !$cgc && $bat2home ? "$name grid_gray" : + "$name grid_red"; + + my $strokecolstd = CurrentVal ($hash, 'strokecolstd', $strokcolstddef); + my $strokecolsig = CurrentVal ($hash, 'strokecolsig', $strokcolsigdef); + my $strokecolina = CurrentVal ($hash, 'strokecolina', $strokcolinadef); + my $strokewidth = CurrentVal ($hash, 'strokewidth', $strokwidthdef); my $ret = << "END0"; - + - + END0 - ## Producer Liste und Icons in Grafik anzeigen - ################################################ - my $pos_left = 0; - my $producer_start = 0; - my $producerPower = 0; - - if ($producercount % 2) { - $producer_start = 350 - ($consDist * (($producercount -1) / 2)); - } - else { - $producer_start = 350 - (($consDist / 2) * ($producercount-1)); - } - - $pos_left = $producer_start + 5; - - for my $lfn (@producers) { - my $pn = $pdcr->{$lfn}{pn}; - my ($picon, $ptxt) = __substituteIcon ( { hash => $hash, # Icon des Producerdevices - name => $name, - pn => $pn, - ptyp => $pdcr->{$lfn}{ptyp}, - don => NexthoursVal ($hash, 'NextHour00', 'DoN', 0), # Tag oder Nacht - pcurr => $pdcr->{$lfn}{p}, - lang => $lang - } - ); - - $picon = FW_makeImage ($picon, ''); - ($scale, $picon) = __normIconScale ($picon, $name); + ## Producer Icon - in Reihenfolge: zum Grid - zum Knoten - zur Batterie + ######################################################################### + for my $st (sort keys %{$psorted}) { + my $left = 0; + my $xicon = $psorted->{$st}{xicon}; + my $count = $psorted->{$st}{count}; + my @sorted = @{$psorted->{$st}{sorted}}; - $ret .= qq{}; - $ret .= "$ptxt".$picon; - $ret .= ' '; + if ($count % 2) { + $xicon = $xicon - ($pdist * ($count - 1) / 2); + } + else { + $xicon = $xicon - ($pdist / 2 * ($count - 1)); + } + + $psorted->{$st}{start} = $xicon; + $left = $xicon + 5; - $pos_left += $consDist; + for my $lfn (@sorted) { + my $pn = $pdcr->{$lfn}{pn}; + my ($picon, $ptxt) = __substituteIcon ( { hash => $hash, # Icon des Producerdevices + name => $name, + pn => $pn, + ptyp => $pdcr->{$lfn}{ptyp}, + don => NexthoursVal ($hash, 'NextHour00', 'DoN', 0), # Tag oder Nacht + pcurr => $pdcr->{$lfn}{p}, + lang => $lang + } + ); + + $picon = FW_makeImage ($picon, ''); + ($scale, $picon) = __normIconScale ($picon, $name); + + $ret .= qq{}; + $ret .= "$ptxt".$picon; + $ret .= ' '; + + $left += $pdist; + } } ## Knoten Icon @@ -14281,7 +14445,7 @@ END0 my ($nicon, $ntxt) = __substituteIcon ( { hash => $hash, name => $name, ptyp => 'node', - pcurr => $pallsum, + pcurr => $pnodesum, lang => $lang } ); @@ -14289,25 +14453,25 @@ END0 $nicon = FW_makeImage ($nicon, ''); ($scale, $nicon) = __normIconScale ($nicon, $name); - $ret .= qq{}; # translate(X-Koordinate,Y-Koordinate), scale()-> Koordinaten ändern sich bei Größenänderung + $ret .= qq{}; # translate(X-Koordinate,Y-Koordinate), scale()-> Koordinaten ändern sich bei Größenänderung $ret .= "$ntxt".$nicon; $ret .= ' '; ## Consumer Liste und Icons in Grafik anzeigen ################################################ - $pos_left = 0; + my $cons_left = 0; my $consumer_start = 0; my $currentPower = 0; if ($flowgcons) { if ($consumercount % 2) { - $consumer_start = 350 - ($consDist * (($consumercount -1) / 2)); + $consumer_start = 350 - ($cdist * ($consumercount -1) / 2); } else { - $consumer_start = 350 - (($consDist / 2) * ($consumercount-1)); + $consumer_start = 350 - ($cdist / 2 * ($consumercount-1)); } - $pos_left = $consumer_start + 15; + $cons_left = $consumer_start + 15; for my $c (@consumers) { my $calias = ConsumerVal ($hash, $c, 'alias', ''); # Name des Consumerdevices @@ -14326,11 +14490,11 @@ END0 $cicon = FW_makeImage ($cicon, ''); ($scale, $cicon) = __normIconScale ($cicon, $name); - $ret .= qq{}; + $ret .= qq{}; $ret .= "$calias".$cicon; $ret .= ' '; - $pos_left += $consDist; + $cons_left += $cdist; } } @@ -14355,7 +14519,7 @@ END1 my $hicon = FW_makeImage ($homeicondef, ''); ($scale, $hicon) = __normIconScale ($hicon, $name); - $ret .= qq{}; # translate(X-Koordinate,Y-Koordinate), scale()-> Koordinaten ändern sich bei Größenänderung + $ret .= qq{}; # translate(X-Koordinate,Y-Koordinate), scale()-> Koordinaten ändern sich bei Größenänderung $ret .= "Home".$hicon; $ret .= ' '; @@ -14367,89 +14531,98 @@ END1 my $dicon = FW_makeImage ($cicondef.$dumcol, ''); ($scale, $dicon) = __normIconScale ($dicon, $name); - $ret .= qq{}; + $ret .= qq{}; $ret .= "$dumtxt".$dicon; $ret .= ' '; } - ## Laufketten PV->Home, PV->Grid, Grid->Home - ############################################## - my $csc_style = $p2home ? 'flowg active_out' : 'flowg inactive_out'; - my $cgfi_style = $cgfi ? 'flowg active_out' : 'flowg inactive_out'; - $ret .= << "END2"; + ## Laufketten Node->Home, Node->Grid, Bat->Home + ################################################# + my $node2home_style = $node2home ? "$name active_normal" : "$name inactive"; + my $node2grid_style = $node2grid ? "$name active_normal" : "$name inactive"; + + $ret .= << "END2"; - - - + + + END2 ## Laufketten PV->Batterie, Batterie->Home ############################################## if ($hasbat) { - my $batin_style = $batin ? 'flowg active_in active_bat_in' : 'flowg inactive_out'; - $ret .= << "END3"; - - + my $node2bat_style = $node2bat ? "$name active_normal" : "$name inactive"; + my $batin_direction = $node2bat < 0 ? "M910,480 L730,400" : "M730,400 L910,480"; + $node2bat = abs $node2bat; + + $ret .= << "END3"; + + END3 } ## Dummy Consumer Laufketten ############################## if ($flowgconX) { - my $consumer_style = 'flowg inactive_out'; - $consumer_style = 'flowg active_in' if($cc_dummy > 1); - my $chain_color = ""; # Farbe der Laufkette Consumer-Dummy + my $consumer_style = "$name inactive"; + $consumer_style = "$name active_sig" if($cc_dummy > 1); + my $chain_color = ""; # Farbe der Laufkette Consumer-Dummy if ($cc_dummy > 0.5) { $chain_color = 'style="stroke: #'.substr(Color::pahColor(0,500,1000,$cc_dummy,[0,255,0, 127,255,0, 255,255,0, 255,127,0, 255,0,0]),0,6).';"'; #$chain_color = 'style="stroke: #DF0101;"'; } - $ret .= qq{}; + $ret .= qq{}; } + + ## Producer Laufketten - in Reihenfolge: zum Grid - zum Knoten - zur Batterie + ############################################################################### + for my $st (sort keys %{$psorted}) { + my $left = $psorted->{$st}{start} * 2; # Übertrag aus Producer Icon Abschnitt + my $count = $psorted->{$st}{count}; + my $xchain = $psorted->{$st}{xchain}; # X- Koordinate Kette am Ziel + my $ychain = $psorted->{$st}{ychain}; # Y- Koordinate Kette am Ziel + my $step = $psorted->{$st}{step}; + my @sorted = @{$psorted->{$st}{sorted}}; - ## Producer Laufketten - ######################## - $pos_left = $producer_start * 2; - my $pos_left_start_con = 0; - my $distance_prd = 65; - - if ($producercount % 2) { - $pos_left_start_con = 700 - ($distance_prd * (($producercount -1) / 2)); - } - else { - $pos_left_start_con = 700 - ((($distance_prd ) / 2) * ($producercount-1)); - } - - for my $lfn (@producers) { - my $pn = $pdcr->{$lfn}{pn}; - my $p = $pdcr->{$lfn}{p}; - my $consumer_style = 'flowg inactive_out'; - $consumer_style = 'flowg active_out' if($p > 0); - my $chain_color = ''; # Farbe der Laufkette des Producers - - if ($p) { - #$chain_color = 'style="stroke: #'.substr(Color::pahColor(0,50,100,$p,[0,255,0, 127,255,0, 255,255,0, 255,127,0, 255,0,0]),0,6).';"'; - $chain_color = 'style="stroke: darkorange;"'; + if ($count % 2) { + $xchain = $xchain - ($pdist * ($count -1) / 2); + } + else { + $xchain = $xchain - ($pdist / 2 * ($count - 1)); } - $ret .= qq{}; # Design Consumer Laufkette - $pos_left += ($consDist * 2); - $pos_left_start_con += $distance_prd; + for my $lfn (@sorted) { + my $pn = $pdcr->{$lfn}{pn}; + my $p = $pdcr->{$lfn}{p}; + my $consumer_style = "$name inactive"; + $consumer_style = "$name active_normal" if($p > 0); + my $chain_color = ''; # Farbe der Laufkette des Producers + + if ($p) { + #$chain_color = 'style="stroke: #'.substr(Color::pahColor(0,50,100,$p,[0,255,0, 127,255,0, 255,255,0, 255,127,0, 255,0,0]),0,6).';"'; + $chain_color = 'style="stroke: darkorange;"'; + } + + $ret .= qq{}; + $left += ($pdist * 2); + $xchain += $step; + } } ## Consumer Laufketten ######################## if ($flowgcons) { - $pos_left = $consumer_start * 2; - my $pos_left_start = 0; - my $distance_con = 65; + $cons_left = $consumer_start * 2; + my $cons_left_start = 0; + my $distance_con = 65; if ($consumercount % 2) { - $pos_left_start = 700 - ($distance_con * (($consumercount -1) / 2)); + $cons_left_start = 700 - ($distance_con * ($consumercount -1) / 2); } else { - $pos_left_start = 700 - ((($distance_con ) / 2) * ($consumercount-1)); + $cons_left_start = 700 - ($distance_con / 2 * ($consumercount-1)); } for my $c (@consumers) { @@ -14463,8 +14636,8 @@ END3 my $p = $currentPower; $p = (($currentPower / $power) * 100) if ($power > 0); - my $consumer_style = 'flowg inactive_out'; - $consumer_style = 'flowg active_out' if($p > $defpopercent); + my $consumer_style = "$name inactive"; + $consumer_style = "$name active_normal" if($p > $defpopercent); my $chain_color = ""; # Farbe der Laufkette des Consumers if ($p > 0.5) { @@ -14472,61 +14645,62 @@ END3 #$chain_color = 'style="stroke: #DF0101;"'; } - $ret .= qq{}; - $pos_left += ($consDist * 2); - $pos_left_start += $distance_con; + $ret .= qq{}; + $cons_left += ($cdist * 2); + $cons_left_start += $distance_con; } } ## Textangaben an Grafikelementen ################################### $cc_dummy = sprintf("%.0f", $cc_dummy); # Verbrauch Dummy-Consumer - $ret .= qq{$pallsum} if ($pallsum > 0); - $ret .= qq{$soc %} if ($hasbat); # Lage Text Batterieladungszustand - $ret .= qq{$p2home} if ($p2home); - $ret .= qq{$cgfi} if ($cgfi); - $ret .= qq{$cgc} if ($cgc); - $ret .= qq{$batout} if ($batout && $hasbat); - $ret .= qq{$batin} if ($batin && $hasbat); - $ret .= qq{$cc}; # Current_Consumption Anlage - $ret .= qq{$cc_dummy} if ($flowgconX && $flowgconPower); # Current_Consumption Dummy - - my $lcp; + $ret .= qq{$pnodesum} if ($pnodesum > 0); + $ret .= qq{$soc %} if ($hasbat); # Lage Text Batterieladungszustand + $ret .= qq{$node2home} if ($node2home); + $ret .= qq{$node2grid} if ($node2grid); + $ret .= qq{$cgc} if ($cgc); + $ret .= qq{$bat2home} if ($bat2home && $hasbat); + $ret .= qq{$node2bat} if ($node2bat && $hasbat); + $ret .= qq{$cc}; # Current_Consumption Anlage + $ret .= qq{$cc_dummy} if ($flowgconX && $flowgconsPower); # Current_Consumption Dummy - ## Textangabe Producer - ######################## - $pos_left = $producer_start * 2 - 70; # -XX -> Start Lage Producer Beschriftung - - for my $lfn (@producers) { - my $pn = $pdcr->{$lfn}{pn}; - $currentPower = $pdcr->{$lfn}{p}; - $lcp = length $currentPower; - - # Leistungszahl abhängig von der Größe entsprechend auf der x-Achse verschieben - ############################################################################### - if ($lcp >= 5) {$pos_left -= 10} - elsif ($lcp == 4) {$pos_left += 10} - elsif ($lcp == 3) {$pos_left += 15} - elsif ($lcp == 2) {$pos_left += 20} - elsif ($lcp == 1) {$pos_left += 40} - - $ret .= qq{$currentPower} if($flowgconPower); # Lage producer Consumption - - # Leistungszahl wieder zurück an den Ursprungspunkt - #################################################### - if ($lcp >= 5) {$pos_left += 10} - elsif ($lcp == 4) {$pos_left -= 10} - elsif ($lcp == 3) {$pos_left -= 15} - elsif ($lcp == 2) {$pos_left -= 20} - elsif ($lcp == 1) {$pos_left -= 40} + ## Textangabe Producer - in Reihenfolge: zum Grid - zum Knoten - zur Batterie + ############################################################################### + for my $st (sort keys %{$psorted}) { + my $left = $psorted->{$st}{start} * 2 - 70; # Übertrag aus Producer Icon Abschnitt, -XX -> Start Lage Producer Beschriftung + my @sorted = @{$psorted->{$st}{sorted}}; - $pos_left += ($consDist * 2); + for my $lfn (@sorted) { + my $pn = $pdcr->{$lfn}{pn}; + $currentPower = $pdcr->{$lfn}{p}; + $lcp = length $currentPower; + + # Leistungszahl abhängig von der Größe entsprechend auf der x-Achse verschieben + ############################################################################### + if ($lcp >= 5) {$left -= 10} + elsif ($lcp == 4) {$left += 10} + elsif ($lcp == 3) {$left += 15} + elsif ($lcp == 2) {$left += 20} + elsif ($lcp == 1) {$left += 40} + + $ret .= qq{$currentPower} if($flowgPrdsPower); + + # Leistungszahl wieder zurück an den Ursprungspunkt + #################################################### + if ($lcp >= 5) {$left += 10} + elsif ($lcp == 4) {$left -= 10} + elsif ($lcp == 3) {$left -= 15} + elsif ($lcp == 2) {$left -= 20} + elsif ($lcp == 1) {$left -= 40} + + $left += ($pdist * 2); + } } ## Textangabe Consumer ######################## if ($flowgcons) { - $pos_left = ($consumer_start * 2) - 50; # -XX -> Start Lage Consumer Beschriftung + $cons_left = ($consumer_start * 2) - 50; # -XX -> Start Lage Consumer Beschriftung for my $c (@consumers) { $currentPower = sprintf "%.1f", $cnsmr->{$c}{p}; @@ -14540,29 +14714,29 @@ END3 $lcp = length $currentPower; - #$ret .= qq{$currentPower} if ($flowgconPower); # Lage Consumer Consumption - #$ret .= qq{$consumerTime} if ($flowgconTime); # Lage Consumer Restlaufzeit + #$ret .= qq{$currentPower} if ($flowgconsPower); # Lage Consumer Consumption + #$ret .= qq{$consumerTime} if ($flowgconsTime); # Lage Consumer Restlaufzeit # Verbrauchszahl abhängig von der Größe entsprechend auf der x-Achse verschieben ################################################################################## - if ($lcp >= 5) {$pos_left -= 40} - elsif ($lcp == 4) {$pos_left -= 25} - elsif ($lcp == 3) {$pos_left -= 5 } - elsif ($lcp == 2) {$pos_left += 7 } - elsif ($lcp == 1) {$pos_left += 25} + if ($lcp >= 5) {$cons_left -= 40} + elsif ($lcp == 4) {$cons_left -= 25} + elsif ($lcp == 3) {$cons_left -= 5 } + elsif ($lcp == 2) {$cons_left += 7 } + elsif ($lcp == 1) {$cons_left += 25} - $ret .= qq{$currentPower} if ($flowgconPower); # Lage Consumer Consumption - $ret .= qq{$consumerTime} if ($flowgconTime); # Lage Consumer Restlaufzeit + $ret .= qq{$currentPower} if ($flowgconsPower); # Lage Consumer Consumption + $ret .= qq{$consumerTime} if ($flowgconsTime); # Lage Consumer Restlaufzeit # Verbrauchszahl wieder zurück an den Ursprungspunkt ###################################################### - if ($lcp >= 5) {$pos_left += 40} - elsif ($lcp == 4) {$pos_left += 25} - elsif ($lcp == 3) {$pos_left += 5 } - elsif ($lcp == 2) {$pos_left -= 7 } - elsif ($lcp == 1) {$pos_left -= 25} + if ($lcp >= 5) {$cons_left += 40} + elsif ($lcp == 4) {$cons_left += 25} + elsif ($lcp == 3) {$cons_left += 5 } + elsif ($lcp == 2) {$cons_left -= 7 } + elsif ($lcp == 1) {$cons_left -= 25} - $pos_left += ($consDist * 2); + $cons_left += ($cdist * 2); } } @@ -14571,6 +14745,43 @@ END3 return $ret; } +################################################################ +# erzeugt eine Liste der Producernummern sortiert von +# links nach rechts: +# -> alle Inverter mit Feed-Typ 'grid' +# -> alle Producer (nicht PV) +# -> alle Inverter mit Feed-Typ 'default' +# -> alle Inverter mit Feed-Typ 'bat' +################################################################ +sub __sortProducer { + my $pdcr = shift; # Hashref Producer + + my @igrid = (); + my @togrid = (); + my @prod = (); + my @idef = (); + my @tonode = (); + my @ibat = (); + my @tobat = (); + + for my $lfn (sort{$a<=>$b} keys %{$pdcr}) { + my $ptyp = $pdcr->{$lfn}{ptyp}; # producer | inverter + my $feed = $pdcr->{$lfn}{feed}; # default | grid | bat + + push @igrid, $lfn if($ptyp eq 'inverter' && $feed eq 'grid'); + push @prod, $lfn if($ptyp eq 'producer'); + push @idef, $lfn if($ptyp eq 'inverter' && $feed eq 'default'); + push @ibat, $lfn if($ptyp eq 'inverter' && $feed eq 'bat'); + } + + push @togrid, @igrid; + push @tonode, @prod; + push @tonode, @idef; + push @tobat, @ibat; + +return (\@togrid, \@tonode, \@tobat); +} + ################################################################ # prüfe ob Icon + Farbe angegeben ist # und setze ggf. Ersatzwerte @@ -14703,8 +14914,6 @@ sub __normIconScale { $icon =~ s/width="(.*?)"/width="$widthnormpt"/; $icon =~ s/height="(.*?)"/height="$heightnormpt"/; - # Log3 ($name, 2, "$name - widthnormpt: $widthnormpt, heightnormpt: $heightnormpt"); - return ($fgscaledef, $icon); } @@ -16468,7 +16677,7 @@ sub checkPlantConfig { for my $sn (sort keys %{$data{$type}{$name}{strings}}) { my $sp = $sn." => ".$sub->($sn)."
"; - Log3 ($name, 1, "$name - sp: $sp"); + $result->{'String Configuration'}{note} .= $sn." => ".$sub->($sn)."
"; if ($data{$type}{$name}{strings}{$sn}{peak} >= 500) { @@ -20184,11 +20393,13 @@ to ensure that the system configuration is correct. + +
ietotal total energy generated by the inverter to date (Wh)
ifeed Energy supply characteristics
igeneration current PV generation (W)
iicon any icons defined for displaying the device in the graphic
ialias Alias of the device
iname Name of the device
invertercap the nominal power (W) of the inverter (if defined)
istrings List of strings assigned to the inverter (if defined)
@@ -20204,11 +20415,12 @@ to ensure that the system configuration is correct. @@ -20814,88 +21026,61 @@ to ensure that the system configuration is correct.
- - -
  • flowGraphicCss
    - Defines the style for the energy flow graph. The attribute is automatically preset. - To change the flowGraphicCss attribute, please accept the default and adjust it:

    + + +
  • flowGraphicControl <Key1=Value1> <Key2=Value2> ...
    + By optionally specifying the key=value pairs listed below, various display properties of the energy flow + graph can be influenced.
    + The entry can be made in several lines. +

    - -
  • -
    - - -
  • flowGraphicAnimate
    - Animates the energy flow graph if displayed. - Siehe auch Attribut graphicSelect.
    - (default: 0) -
  • -
    - - -
  • flowGraphicConsumerDistance
    - Controls the spacing between consumer icons in the energy flow graph if displayed. - Siehe auch Attribut flowGraphicShowConsumer.
    - (default: 130) -
  • -
    - - -
  • flowGraphicShowConsumer
    - Suppresses the display of loads in the energy flow graph when set to "0".
    - (default: 1) -
  • -
    - - -
  • flowGraphicShift <Pixel/10>
    - Horizontal shift of the energy flow graph.
    - (default: 0) -
  • -
    - - -
  • flowGraphicShowConsumerDummy
    - Shows or suppresses the dummy consumer in the energy flow graph.
    - The dummy consumer is assigned the energy consumption that could not be assigned to other consumers.
    - (default: 1) -
  • -
    - - -
  • flowGraphicShowConsumerPower
    - Shows or suppresses the energy consumption of the loads in the energy flow graph.
    - (default: 1) -
  • -
    - - -
  • flowGraphicShowConsumerRemainTime
    - Shows or suppresses the remaining time (in minutes) of the loads in the energy flow graph.
    - (default: 1) -
  • -
    - - -
  • flowGraphicSize <Pixel>
    - Size of the energy flow graph if displayed. - Siehe auch Attribut graphicSelect.
    - (default: 400) + + +

  • @@ -21081,7 +21266,6 @@ to ensure that the system configuration is correct. Consumer<br>Quickstart:consumerImmediatePlanning : : : Weather:graphicShowWeather : : : History:graphicHistoryHour : : : - GraphicSize:flowGraphicSize : : : ShowNight:graphicShowNight : : : Debug:ctrlDebug : : : @@ -21201,9 +21385,7 @@ to ensure that the system configuration is correct.
  • graphicSelect
    - Selects the graphic segments of the module to be displayed.
    - To customize the energy flow graphic, the flowGraphicCss - attribute is available in addition to the flowGraphic.* attributes. + Selects the graphic segments of the module to be displayed.