From 33210f17e34e509966e7373f8760ba42e4b2d34f Mon Sep 17 00:00:00 2001 From: DS_Starter Date: Tue, 22 Jul 2025 19:20:12 +0000 Subject: [PATCH] 76_SolarForecast: Version 1.54.4 git-svn-id: https://svn.fhem.de/fhem/trunk@30146 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/76_SolarForecast.pm | 444 ++++++++++++-------- fhem/contrib/DS_Starter/76_SolarForecast.pm | 58 +-- 3 files changed, 295 insertions(+), 208 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 0dcabbfdf..dfaefe271 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: Version 1.54.4 - feature: 76_SolarForecast: ctrlDebug new collectData_long - change: 76_SolarForecast: version 1.54.2, more debug info - feature: 72_FRITZBOX: Vorbereitung auf Fritz!OS 8.10 diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 571e09b6b..f2ae72465 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -38,7 +38,7 @@ use POSIX; use GPUtils qw(GP_Import GP_Export); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt use Time::HiRes qw(gettimeofday tv_interval); use Math::Trig; -use List::Util qw(min max shuffle); +use List::Util qw(sum min max shuffle); use Scalar::Util qw(blessed weaken); eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval' @@ -160,6 +160,11 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.54.4" => "22.07.2025 replace length by new sub strlength, Consumer attr new key 'aliasshort', change code of medianArray ". + "medianArray: can optional use newest 3..20 elements, avgArray: use the newest elements if num is set ". + "Debug consumerSwitching: print out info message of compare operation, remove attr graphicShowDiff ". + "store surpmeth calc result in key surpmethResult in Consumer master record, __readFileMessages: refactored code ". + "surpmeth: use average[_2..20] instead of numeric values 2.20 only ", "1.54.3" => "19.07.2025 ctrlDebug: add collectData_long ", "1.54.2" => "18.07.2025 _createSummaries: add debug infos ", "1.54.1" => "08.07.2025 userExit: new coding, __createReduceIcon: fix Wide character in syswrite - https://forum.fhem.de/index.php?msg=1344368 ". @@ -1693,10 +1698,10 @@ sub Initialize { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ########################################################################################################################## - my $av = 'obsolete#-#use#attr#graphicControl#instead'; + # my $av = 'obsolete#-#use#attr#graphicControl#instead'; # my $av1 = 'obsolete#-#will#be#deleted#soon'; # my $av2 = 'obsolete#-#use#attr#graphicSelect#instead'; - $hash->{AttrList} .= " graphicShowDiff:$av "; + # $hash->{AttrList} .= " graphicShowDiff:$av "; ########################################################################################################################## $hash->{FW_hideDisplayName} = 1; # Forum 88667 @@ -5886,7 +5891,7 @@ sub __updPreFile { return $err; } - if ($lencheck && length $remFile ne $cmlen) { + if ($lencheck && length ($remFile) ne $cmlen) { $err = "update ERROR: length of $file is not $cmlen Bytes"; Log3 ($name, 1, "$name - $err"); return $err; @@ -5947,22 +5952,21 @@ sub __updWriteFile { my $content = shift; my $fPath = "$root/$fName"; - my $err; + + open my $fh, '>:raw', $fPath or return "update ERROR open $fPath failed: $!"; - if (!open(FD, ">$fPath")) { - $err = "update ERROR open $fPath failed: $!"; - return $err; + my $bytes = encode ('UTF-8', $content); + my $written = syswrite $fh, $bytes; + close $fh or return "update ERROR closing $fPath failed: $!"; + + unless (defined $written) { + return "update ERROR writing $fPath failed: $!"; } + + my $expected = length $bytes; - binmode(FD); - print FD $content; - close(FD); - - my $written = -s "$fPath"; - - if ($written != length $content) { - $err = "update ERROR writing $fPath failed: $!"; - return $err; + if ($written != $expected) { + return sprintf "update ERROR wrote %d of %d bytes to %s", $written, $expected, $fPath; } return; @@ -5987,15 +5991,15 @@ sub Attr { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ###################################################################################################################### - if ($cmd eq 'set' && $aName =~ /^graphicShowDiff$/) { # 25.06. - my $msg = "The attribute $aName is replaced by 'graphicControl'."; - if (!$init_done) { - Log3 ($name, 1, "$name - $msg"); - } - else { - return $msg; - } - } + #if ($cmd eq 'set' && $aName =~ /^graphicShowDiff$/) { # 25.06. + # my $msg = "The attribute $aName is replaced by 'graphicControl'."; + # if (!$init_done) { + # Log3 ($name, 1, "$name - $msg"); + # } + # else { + # return $msg; + # } + #} #if ($cmd eq 'set' && $aName =~ /^graphicHeaderShow$/) { # 15.04. # my $msg = "The attribute $aName is replaced by 'graphicSelect'."; @@ -6085,6 +6089,7 @@ sub _attrconsumer { ## no critic "not used" my $hash = $defs{$name}; my $valid = { + aliasshort => '', type => '', power => '', switchdev => '', @@ -6143,6 +6148,11 @@ sub _attrconsumer { ## no critic "not used" if (defined $h->{exconfc} && $h->{exconfc} !~ /^[012]$/xs) { return qq{The key "exconfc" is not set correct. Please consider the command reference.}; } + + if (exists $h->{aliasshort}) { # Kurzalias + return qq{The short alias "$h->{aliasshort}" longer than allowed. See command reference.} + if(strlength ($h->{aliasshort})> 10); + } if (exists $h->{mode} && $h->{mode} !~ /^(?:can|must)$/xs) { if ($h->{mode} =~ /.*:.*/xs) { @@ -6170,8 +6180,8 @@ sub _attrconsumer { ## no critic "not used" return "The reading '$rd' of device '$dv' is invalid or doesn't contain a valid numeric value"; } } - elsif ($h->{surpmeth} !~ /^[2-9]$|^1[0-9]$|^20$|^median$|^default$/xs) { - return qq{The surpmeth "$h->{surpmeth}" is wrong. It must contain a ':', 'median', 'default' or an integer value of '2 .. 20'.}; + elsif ($h->{surpmeth} !~ /^(?:median|average)(?:_(?:[2-9]|1[0-9]|20))?$|^default$/xs) { + return qq{The surpmeth "$h->{surpmeth}" is wrong. It must contain a ':', 'median[_2..20]', 'average[_2..20]' or 'default'.}; } } @@ -8840,15 +8850,16 @@ sub centralTask { # ::CommandDeleteAttr (undef, "$name graphicBeamWidth"); #} - my $gsd = AttrVal ($name, 'graphicShowDiff ', undef); # 25.06. - my $gco = AttrVal ($name, 'graphicControl', ''); - - if (defined $gsd) { - my $newval = $gco." showDiff=$gsd"; - CommandAttr (undef, "$name graphicControl $newval"); - ::CommandDeleteAttr (undef, "$name graphicShowDiff"); + for my $c (1..MAXCONSUMER) { # 23.07. + $c = sprintf "%02d", $c; + my $surpmeth = ConsumerVal ($hash, $c, 'surpmeth', ''); + + if ($surpmeth =~ /^[2-9]$|^1[0-9]$|^20$/xs) { + fhem ("set $name attrKeyVal consumer${c} surpmeth=average_${surpmeth}"); + } } - + + ########################################################################################################################## if (!CurrentVal ($hash, 'allStringsFullfilled', 0)) { # die String Konfiguration erstellen wenn noch nicht erfolgreich ausgeführt @@ -9236,6 +9247,7 @@ sub _collectAllRegConsumers { $data{$name}{consumers}{$c}{name} = $consumer; # Name des Verbrauchers (Device) $data{$name}{consumers}{$c}{alias} = $alias; # Alias des Verbrauchers (Device) + $data{$name}{consumers}{$c}{aliasshort} = $hc->{aliasshort} // q{}; # Kurzalias des Verbrauchers $data{$name}{consumers}{$c}{type} = $hc->{type} // DEFCTYPE; # Typ des Verbrauchers $data{$name}{consumers}{$c}{power} = $hc->{power}; # Leistungsaufnahme des Verbrauchers in W $data{$name}{consumers}{$c}{avgenergy} = q{}; # Initialwert Energieverbrauch (evtl. Überschreiben in manageConsumerData) @@ -9315,10 +9327,10 @@ sub _specialActivities { ########################################################################## for my $c (keys %{$data{$name}{consumers}}) { - next if(ConsumerVal ($hash, $c, "plandelete", "regular") eq "regular"); + next if(ConsumerVal ($hash, $c, 'plandelete', 'regular') eq 'regular'); - my $planswitchoff = ConsumerVal ($hash, $c, "planswitchoff", $t); - my $simpCstat = simplifyCstate (ConsumerVal ($hash, $c, "planstate", "")); + my $planswitchoff = ConsumerVal ($hash, $c, 'planswitchoff', $t); + my $simpCstat = simplifyCstate (ConsumerVal ($hash, $c, 'planstate', '')); if ($t > $planswitchoff && $simpCstat =~ /planned|finished|unknown/xs) { deleteConsumerPlanning ($hash, $c); @@ -12003,8 +12015,8 @@ sub _manageConsumerData { for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { $paref->{consumer} = $c; - my $consumer = ConsumerVal ($hash, $c, "name", ""); - my $alias = ConsumerVal ($hash, $c, "alias", ""); + my $consumer = ConsumerVal ($hash, $c, 'name', ''); + my $alias = ConsumerVal ($hash, $c, 'alias', ''); ## aktuelle Leistung auslesen ############################## @@ -12083,15 +12095,15 @@ sub _manageConsumerData { $paref->{pcurr} = $pcurr; - __getAutomaticState ($paref); # Automatic Status des Consumers abfragen - __calcEnergyPieces ($paref); # Energieverbrauch auf einzelne Stunden für Planungsgrundlage aufteilen - __planInitialSwitchTime ($paref); # Consumer Switch Zeiten planen - __setTimeframeState ($paref); # Timeframe Status ermitteln - __setConsRcmdState ($paref); # Consumption Recommended Status setzen - __switchConsumer ($paref); # Consumer schalten - __getCyclesAndRuntime ($paref); # Verbraucher - Laufzeit, Tagesstarts und Aktivminuten pro Stunde ermitteln - __reviewSwitchTime ($paref); # Planungsdaten überprüfen und ggf. neu planen - __remainConsumerTime ($paref); # Restlaufzeit Verbraucher ermitteln + __getAutomaticState ($paref); # Automatic Status des Consumers abfragen + __calcEnergyPieces ($paref); # Energieverbrauch auf einzelne Stunden für Planungsgrundlage aufteilen + __planInitialSwitchTime ($paref); # Consumer Switch Zeiten planen + __setTimeframeState ($paref); # Timeframe Status ermitteln + __setConsRcmdState ($paref); # Consumption Recommended Status setzen + __switchConsumer ($paref); # Consumer schalten + __getCyclesAndRuntime ($paref); # Verbraucher - Laufzeit, Tagesstarts und Aktivminuten pro Stunde ermitteln + __reviewSwitchTime ($paref); # Planungsdaten überprüfen und ggf. neu planen + __remainConsumerTime ($paref); # Restlaufzeit Verbraucher ermitteln delete $paref->{pcurr}; @@ -12987,16 +12999,23 @@ sub __setConsRcmdState { my ($method, $surplus) = determSurplus ($hash, $c); # Consumer spezifische Ermittlung des Energieüberschußes + $data{$name}{consumers}{$c}{surpmethResult} = $surplus; # Ergebnis der Surplus Ermittlung im Consumerstammsatz speichern, Forum: https://forum.fhem.de/index.php?msg=1345058 + if ($debug =~ /consumerSwitching${c}/x) { + my $splref = CurrentVal ($name, 'surplusslidereg', '.'); + my $spser = ref $splref eq 'ARRAY' ? join ' ', @{$splref} : undef; + Log3 ($name, 1, qq{$name DEBUG> ############### consumerSwitching consumer "$c" ###############}); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - ConsumptionRecommended calc method: $method, surplus: }. (defined $surplus ? $surplus : 'undef')); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - method base: $spser}) if($method =~ /average|median/xs); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - additional consumption after switching on (if currently 'off'): $rescons W}); } my ($spignore, $info, $err) = isSurplusIgnoCond ($hash, $c, $debug); # PV Überschuß ignorieren? Log3 ($name, 1, "$name - $err") if($err); + if ($debug =~ /consumerSwitching${c}/x && $info) { Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - IgnoreCondition - $info}); } @@ -13100,8 +13119,8 @@ sub ___switchConsumerOn { Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - Check Context 'switch on' => }. qq{swoncond: $swoncond, on-command: $oncom } ); - Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isAddSwitchOnCond Info: $infon}) if($swoncond && $infon); - Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isAddSwitchOffCond Info: $infoff}) if($swoffcond && $infoff); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isAddSwitchOnCond Info: $infon}) if($infon); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isAddSwitchOffCond Info: $infoff}) if($infoff); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - device '$dswname' is used as switching device}); if ($simpCstat =~ /planned|priority|starting|continuing/xs && $isInTime && $iilt) { @@ -13230,7 +13249,7 @@ sub ___switchConsumerOff { qq{swoffcond: $swoffcond, off-command: $offcom} ); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - is Consumption recommended: $isConsRcmd}); - Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isAddSwitchOffCond Info: $infoff}) if($swoffcond && $infoff); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isAddSwitchOffCond Info: $infoff}) if($infoff); if ($stopts && $t >= $stopts && $iilt) { Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - switching off postponed by >isInLocktime<}); @@ -13677,8 +13696,7 @@ sub _calcConsForecast_circular { @conh = splice (@conh, $acld * -1); $hnum = scalar @conh; } - - # my $hcon = sprintf "%.0f", medianArray (\@conh); + my $hcon = $ncds <= $nhist ? (sprintf "%.0f", avgArray (\@conh, $hnum)) : (sprintf "%.0f", medianArray (\@conh)); # V 1.52.8 $usage{$hh}{con} = $hcon; # prognostizierter Verbrauch (Median) der Stunde hh (Hour of Day) @@ -13691,7 +13709,6 @@ sub _calcConsForecast_circular { $hnumtom = scalar @conhtom; } - # my $hcontom = sprintf "%.0f", medianArray (\@conhtom); my $hcontom = $ncds <= $nhist ? (sprintf "%.0f", avgArray (\@conhtom, $hnumtom)) : (sprintf "%.0f", medianArray (\@conhtom)); # V 1.52.8 $usage{tom}{con} += $hcontom; # Summe prognostizierter Verbrauch (Median) des Tages @@ -17746,7 +17763,7 @@ sub _flowGraphic { my $flowgxshift = $paref->{flowgxshift}; # X-Verschiebung der Flußgrafikbox (muß negiert werden) my $flowgyshift = $paref->{flowgyshift}; # Y-Verschiebung der Flußgrafikbox (muß negiert werden) my $flowgconsumer = $paref->{flowgconsumer}; # Verbraucher in der Energieflußgrafik anzeigen - my $flowgconsTime = $paref->{flowgconsTime}; # Verbraucher Restlaufeit in der Energieflußgrafik anzeigen + my $flowgconsTime = $paref->{flowgconsTime}; # Verbraucher Restlaufzeit in der Energieflußgrafik anzeigen my $flowgconX = $paref->{flowgconX}; my $flowgconsPower = $paref->{flowgconsPower}; my $cdist = $paref->{flowgconsDist}; # Abstand Consumer zueinander @@ -17764,7 +17781,7 @@ sub _flowGraphic { my $stna = $name; $stna .= int (rand (1500)); - my ($y_pos, $y_pos1, $err); + my ($y_pos, $y_pos1, $y_pos2, $err); for my $re (keys %hrepl) { # V 1.37.1 Ziffern etc. eliminieren, Forum: https://forum.fhem.de/index.php?msg=1323229 $stna =~ s/$re/$hrepl{$re}/gxs; @@ -17913,17 +17930,23 @@ sub _flowGraphic { ## definierte Verbraucher ermitteln ##################################### - my $cnsmr = {}; # Hashref Consumer current power + my $cnsmr = {}; # Consumer Hilfshash Referenz - for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { # definierte Verbraucher ermitteln - next if(isConsumerNoshow ($hash, $c) =~ /[13]/xs); # auszublendende Consumer nicht berücksichtigen - $cnsmr->{$c}{p} = ReadingsNum ($name, "consumer${c}_currentPower", 0); - $cnsmr->{$c}{ptyp} = 'consumer'; + for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { # definierte Verbraucher ermitteln + next if(isConsumerNoshow ($hash, $c) =~ /[13]/xs); # auszublendende Consumer nicht berücksichtigen + $cnsmr->{$c}{p} = ReadingsNum ($name, "consumer${c}_currentPower", 0); + $cnsmr->{$c}{shortalias} = ConsumerVal ($name, $c, 'aliasshort', ''); # Consumer Kurzalias + $cnsmr->{$c}{ptyp} = 'consumer'; } my $consumercount = keys %{$cnsmr}; - $flowgconsumer = 0 if(!$consumercount); # Consumer Anzeige ausschalten wenn keine Consumer definiert + $flowgconsumer = 0 if(!$consumercount); # Consumer Anzeige ausschalten wenn keine Consumer definiert my @consumers = sort{$a<=>$b} keys %{$cnsmr}; + + my $total_shortalias_length = sum map { my $a = $_->{shortalias} // ''; + strlength ($a); # Länge in Zeichen nach Zeichen-Dekodierung + } + values %{$cnsmr}; ## Producer / Inverter Koordinaten Steuerhash @@ -17951,8 +17974,9 @@ sub _flowGraphic { $vbminy -= 150 if($showproducers); # mehr Platz oben schaffen wenn Poducerreihe angezeigt $vbminy -= INPUTROWSHIFT if($showgenerators); # mehr Platz oben schaffen wenn Zellen/Input-Reihe angezeigt - my $vbhight = 610; + my $vbhight = 630; $vbhight -= 20 if(!$flowgconsTime); + $vbhight -= 20 if(!$total_shortalias_length); $vbhight -= 230 if(!$flowgconsumer); $vbhight += PRDCRROWSHIFT if($showproducers); # Höhe Box vergrößern wenn Poducerreihe angezeigt @@ -18329,7 +18353,7 @@ END3 $ytext = $showproducers ? $ytext - PRDCRROWSHIFT + 5 : $ytext + PRDCRROWSHIFT - 30; # Unterscheidung wenn ProducerZeile angezeigt werden soll my $genpow = __getGeneratorPower ( { pdcr => $pdcr, lfn => $lfn } ); # aktuelle Generatorleistung - my $lpv1 = length $genpow; + my $lpv1 = strlength ($genpow); # Leistungszahl abhängig von der Größe entsprechend auf der x-Achse verschieben ################################################################################# @@ -18359,7 +18383,7 @@ END3 $xtext = $xtext * 2 - 70; # Korrektur Start X-Koordinate des Textes my $pdrpow = __getProducerPower ( { pdcr => $pdcr, lfn => $lfn } ); - my $lpv1 = length $pdrpow; + my $lpv1 = strlength ($pdrpow); # Leistungszahl abhängig von der Größe entsprechend auf der x-Achse verschieben ############################################################################### @@ -18378,10 +18402,28 @@ END3 ######################## if ($flowgconsumer) { $cons_left = ($consumer_start * 2) - 50; # -XX -> Start Lage Consumer Beschriftung - $y_pos = 1110 + 2 * $exth2cdist; - $y_pos1 = 1170 + 2 * $exth2cdist; + + my %offset = ( + 0b00 => 0, # weder Power noch Time + 0b01 => 60, # nur Power + 0b10 => 60, # nur Time + 0b11 => 120, # beide + ); + + my $y_base = 1110 + 2 * $exth2cdist; + $y_pos = $y_base; # flowgconsPower + my $mask = $flowgconsPower ? 1 : 0; + $y_pos1 = $y_base + $offset{$mask}; # flowgconsTime + + $mask = $flowgconsPower && $flowgconsTime ? 3 : + $flowgconsTime ? 2 : + $flowgconsPower ? 1 : + 0; + + $y_pos2 = $y_base + $offset{$mask}; # shortalias for my $c (@consumers) { + my $shortalias = $cnsmr->{$c}{shortalias} // ''; $cnsmrpower = sprintf "%.1f", $cnsmr->{$c}{p}; $cnsmrpower = sprintf "%.0f", $cnsmrpower if($cnsmrpower > 10); my $consumerTime = ConsumerVal ($name, $c, 'remainTime', ''); # Restlaufzeit @@ -18391,31 +18433,54 @@ END3 $cnsmrpower = isConsumerPhysOn($hash, $c) ? 'on' : 'off'; } - my $lcp = length $cnsmrpower; + my $lcp = strlength ($cnsmrpower); + my $lct = strlength ($consumerTime); + my $lcs = strlength ($shortalias); - #$ret .= qq{$cnsmrpower} if($flowgconsPower); # Lage Consumer Consumption - #$ret .= qq{$consumerTime} if($flowgconsTime); # Lage Consumer Restlaufzeit + # Texte abhängig von ihrer Größe entsprechend auf der x-Achse verschieben + ########################################################################### + if ($flowgconsPower) { # Lage Consumer Consumption + my $lcp_cons_left = $cons_left; + + if ($lcp >= 5) {$lcp_cons_left -= 40} + elsif ($lcp == 4) {$lcp_cons_left -= 25} + elsif ($lcp == 3) {$lcp_cons_left -= 5 } + elsif ($lcp == 2) {$lcp_cons_left += 7 } + elsif ($lcp == 1) {$lcp_cons_left += 25} - # Verbrauchszahl abhängig von der Größe entsprechend auf der x-Achse verschieben - ################################################################################## - 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{$cnsmrpower} if($flowgconsPower); # Lage Consumer Consumption - $ret .= qq{$consumerTime} if($flowgconsTime); # Lage Consumer Restlaufzeit - - # Verbrauchszahl wieder zurück an den Ursprungspunkt - ###################################################### - 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} - - $cons_left += ($cdist * 2); + $ret .= qq{$cnsmrpower}; + } + + if ($flowgconsTime) { # Lage Consumer Restlaufzeit + my $lct_cons_left = $cons_left; + + if ($lct >= 5) {$lct_cons_left -= 40} + elsif ($lct == 4) {$lct_cons_left -= 25} + elsif ($lct == 3) {$lct_cons_left -= 5 } + elsif ($lct == 2) {$lct_cons_left += 7 } + elsif ($lct == 1) {$lct_cons_left += 25} + + $ret .= qq{$consumerTime}; + } + + if ($shortalias) { # Lage Consumer Kurzalias + my $lcs_cons_left = $cons_left; + + if ($lcs >= 10) {$lcs_cons_left -= 85} + elsif ($lcs == 9) {$lcs_cons_left -= 85} + elsif ($lcs == 8) {$lcs_cons_left -= 70} + elsif ($lcs == 7) {$lcs_cons_left -= 60} + elsif ($lcs == 6) {$lcs_cons_left -= 35} + elsif ($lcs == 5) {$lcs_cons_left -= 20} + elsif ($lcs == 4) {$lcs_cons_left -= 10} + elsif ($lcs == 3) {$lcs_cons_left -= 0 } + elsif ($lcs == 2) {$lcs_cons_left += 7 } + elsif ($lcs == 1) {$lcs_cons_left += 25} + + $ret .= qq{$shortalias}; + } + + $cons_left += ($cdist * 2); } } @@ -19263,26 +19328,31 @@ sub __readFileMessages { my $name = $paref->{name}; my $tsnext = $paref->{tsnext}; - my $hash = $defs{$name}; + my $file = "$root/FHEM/$messagefile"; - open (FD, "$root/FHEM/$messagefile") or do { return $! }; + open my $fh, '<:encoding(UTF-8)', $file or return "Cannot open $file: $!"; delete $data{$name}{filemessages}; - my @locList = map { $_ =~ s/[\r\n]//; $_ } ; - close (FD); + my $count = 0; + + while (my $line = <$fh>) { + chomp $line; + next if $line =~ /^\s*#/; # Kommentarzeilen überspringen - Log3 ($name, 4, "$name - Notification System - read local Message File >$messagefile< with ".scalar @locList." entries."); + my ($id, $lang, $msg) = split /\|/, $line, 3; + next if(!isNumeric ($id)); # nur numeric IDs + next if($lang !~ /^(DE|EN|SV)$/xs); # nur gültige Sprachen - for my $l (@locList) { - next if ($l =~ /^\#/xs); - my @l = split /\|/, $l, 3; - next if(!isNumeric ($l[0])); - next if($l[1] !~ /^(DE|EN|SV)$/xs); - - $data{$name}{filemessages}{$l[0]}{$l[1]} = $l[2]; + $data{$name}{filemessages}{$id}{$lang} = $msg; + $count++; } + close $fh; + + Log3 ($name, 4, "$name - Notification System - read local Message File >$messagefile< with $count entries."); + + $data{$name}{filemessages}{999000}{TS} = time; $data{$name}{filemessages}{999000}{TSNEXT} = $tsnext; @@ -19863,7 +19933,6 @@ sub aiGetResult { if ($tprnum) { my $avg_prediction = sprintf '%.0f', avgArray (\@total_prediction, $tprnum); - # my $avg_prediction = sprintf '%.0f', medianArray (\@total_prediction); debugLog ($paref, 'aiData', qq{AI accurate result found: pvaifc: $avg_prediction (hod: $hod, sunaz: $sunaz, sunalt: $sabin, Rad1h: $rad1h, wcc: $wcc, rr1c: $rr1c, temp: $tbin)}); return ('accurate', $avg_prediction); @@ -21161,11 +21230,11 @@ sub _ldchash2val { if (ref $pool->{$idx}{$key}{$f} eq 'ARRAY') { my @sub_arrays = arraySplitBy (20, @{$pool->{$idx}{$key}{$f}}); # Array in Teil-Arrays zu je 20 Elemente aufteilen - my $ln0 = length $key; + my $ln0 = strlength ($key); my $blk0 = ' ' x 17; my $blkadd0 = ' ' x (7 - ($ln0 > 7 ? 0 : $ln0)); - my $ln1 = length $f; + my $ln1 = strlength ($f); my $blkadd1 = ' ' x (3 - ($ln1 > 3 ? 0 : $ln1)); for my $suaref (@sub_arrays) { # für jedes Teil-Array Join ausführen @@ -21226,7 +21295,7 @@ sub _ldpspaces { my $sp = shift // q{}; my $const = shift // 4; - my $le = $const + length Encode::decode('UTF-8', $str); + my $le = $const + strlength ($str); my $spn = $sp; for (my $i = 0; $i < $le; $i++) { @@ -21990,18 +22059,20 @@ sub determSurplus { my ($surplus, $fallback); - if ($surpmeth eq 'median') { # Median der Werte in surplusslidereg, !kann UNDEF sein! - $surplus = medianArray ($splref); - $method = 'median'; + if ($surpmeth =~ /median/xs) { # Median der Werte in surplusslidereg, !kann UNDEF sein! + my $num = (split '_', $surpmeth)[1]; # Anzahl der (letzten) Array-Elemente die für Median verwendet werden sollen + $surplus = medianArray ($splref, $num); + $method = $num ? "median:$num" : "median:all"; + } + elsif ($surpmeth =~ /average/xs) { # Average Ermittlung, !kann UNDEF sein! + my $num = (split '_', $surpmeth)[1]; + $surplus = avgArray ($splref, $num); + $method = $num ? "average:$num" : "average:all"; } elsif ($surpmeth eq 'default') { # aktueller Energieüberschuß $surplus = CurrentVal ($hash, 'surplus', 0); $method = 'default'; } - elsif ($surpmeth =~ /^[2-9]$|^1[0-9]$|^20$/xs) { - $surplus = avgArray ($splref, $surpmeth); # Average Ermittlung, !kann UNDEF sein! - $method = "average:$surpmeth"; - } elsif ($surpmeth =~ /.*:.*/xs) { my ($dv, $rd) = split ':', $surpmeth; $method = "$dv:$rd"; @@ -22088,12 +22159,11 @@ sub avgArray { my $num = shift // SLIDENUMMAX; return undef if(ref $aref ne 'ARRAY' || scalar @{$aref} < $num); - + + my @tail = @{$aref}[-$num .. -1]; # es werden die neuesten num Elemente verwendet + my $sum = 0; - - for my $i (0 .. $num-1) { - $sum += ${$aref}[$i]; - } + $sum += $_ for @tail; my $avg = $sum / $num; @@ -22105,24 +22175,28 @@ return $avg; # (https://www.ionos.de/digitalguide/online-marketing/web-analyse/median-berechnen/) # # $aref = Referenz zum Array +# $num = Anzahl der neuesten zu verwendenden Array Elemente # ###################################################################################### sub medianArray { my $aref = shift; + my $num = shift; - return undef if(ref $aref ne 'ARRAY' || !scalar @{$aref}); - - my $enum = scalar @{$aref}; # Anzahl der Elemente im Array - my @sorted = sort { $a <=> $b } @{$aref}; # Numerisch aufsteigend - - if ($enum % 2) { # Array enthält ungerade Anzahl Elemente - return $sorted[$enum/2]; # ungerade Elemente -> Median Element steht in der Mitte von @sorted - } - else { - return ($sorted[$enum/2 - 1] + $sorted[$enum/2]) / 2; # gerade Elemente -> Median ist der Durchschnitt der beiden mittleren Elemente + return if(ref $aref ne 'ARRAY' || !scalar @{$aref}); + + if (defined $num) { # Anzahl der (neuesten) Elemente die verwendet werden sollen + return unless $num =~ /^\d+$/ && $num > 0 && $num <= @$aref; } + + my @tail = defined $num ? @{$aref}[-$num .. -1] : @{$aref}; + my @sorted = sort { $a <=> $b } @tail; # Numerisch aufsteigend + my $n = scalar @sorted; + my $mid = int ($n/2); -return; + my $median = $n % 2 ? $sorted[$mid] : # ungerade Elemente -> Median Element steht in der Mitte von @sorted + ($sorted[$mid - 1] + $sorted[$mid]) / 2; # gerade Elemente -> Median ist der Durchschnitt der beiden mittleren Elemente + +return $median; } ################################################################ @@ -22194,7 +22268,7 @@ sub timestampToTimestring { return if($epoch !~ /[0-9]/xs); - if (length ($epoch) == 13) { # Millisekunden + if (strlength ($epoch) == 13) { # Millisekunden $epoch = $epoch / 1000; } @@ -22286,7 +22360,7 @@ sub timestringsFromOffset { return if($epoch !~ /^-?[0-9]*(.[0-9]*)?$/xs); - if (length ($epoch) == 13) { # Millisekunden + if (strlength ($epoch) == 13) { # Millisekunden $epoch = $epoch / 1000; } @@ -23074,12 +23148,12 @@ sub isAddSwitchOffCond { } if ($true) { - $info = qq{The value “$condval” resulted in 'true' after exec "$swoffcode" \n}; + $info = qq{The reference value “$condval” resulted in 'true' after exec "$swoffcode" \n}; $info .= "-> Check successful "; $swoff = 1; } else { - $info = qq{The value “$condval” resulted in 'false' after exec "$swoffcode" \n}; + $info = qq{The reference value “$condval” resulted in 'false' after exec "$swoffcode" \n}; $swoff = 0; } } @@ -23107,7 +23181,7 @@ sub isAddSwitchOffCond { } } - $info .= qq{-> the effect depends on the switch context}; + $info .= qq{(the effect depends on the switch context)}; } return ($swoff, $info, $err); @@ -23816,6 +23890,17 @@ sub simplifyCstate { return $ps; } +################################################################ +# Länge eines Strings (auch mit Umlauten) +################################################################ +sub strlength { + my $string = shift // return 0; + + my $decoded = decode ('UTF-8', $string); + +return length ($decoded); +} + ################################################################ # Prüfung eines übergebenen Regex ################################################################ @@ -24239,7 +24324,7 @@ sub lineFromSpaces { my $mlen = 1; for my $s (@sps) { - my $len = length ($s); + my $len = strlength ($s); $mlen = $len if($len && $len > $mlen); } @@ -26145,7 +26230,7 @@ to ensure that the system configuration is correct.
  • consumerXX <Device>[:<Alias>] type=<type> power=<power> [switchdev=<device>]
    - [mode=<mode>] [icon=<Icon>[@<Color>]] [mintime=<Option>]
    + [aliasshort=<String>] [mode=<mode>] [icon=<Icon>[@<Color>]] [mintime=<Option>]
    [on=<command>] [off=<command>] [swstate=<Readingname>:<on-Regex>:<off-Regex>] [asynchron=<Option>]
    [notbefore=<Expression>] [notafter=<Expression>] [locktime=<offlt>[:<onlt>]]
    [auto=<Readingname>] [pcurr=<Readingname>:<Unit>[:<Threshold>]] [etotal=<Readingname>:<Einheit>[:<Threshold>]]
    @@ -26194,6 +26279,8 @@ to ensure that the system configuration is correct. If the consumer consists of different devices/channels (e.g. Homematic), the energy meter is defined as a <Device>. The associated switching device is specified with the key 'switchdev'. + aliasshort Short alias of the consumer for display in the flow chart. A maximum of 10 characters and no spaces are allowed. + type Type of consumer. The following types are allowed: dishwasher - Consumer is a dishwasher dryer - Consumer is a tumble dryer @@ -26269,36 +26356,34 @@ to ensure that the system configuration is correct. :<Threshold> (Wh) - From this energy consumption per hour, the consumption is considered valid. Optional specification (default: 0) swoncond Condition that must also be fulfilled in order to switch on the consumer (optional). The scheduled cycle is started. - Device - Device to supply the additional switch-on condition - Reading - Reading for delivery of the additional switch-on condition - The condition can be formulated as a regular expression or as Perl code enclosed in {..}: - Regex - regular expression that must be fulfilled for a 'true' condition - {Perl-Code} - the Perl code enclosed in {..} must return 'true' to fulfill the condition. It must not contain spaces. - The value of Device:Reading is transferred to the code with the variable $VALUE. + Device:Reading - the device/reading combination returns the check value $VALUE (‘undef’ is ignored) + The check can be formulated as a regular expression or as Perl code enclosed in {..}: + Regex - regular expression for checking $VALUE which must return ‘true’ if successful + {Perl-Code} - the Perl code enclosed in {..} must not contain any spaces. The variable $VALUE can be evaluated by the code. + The return value must be ‘true’ if successful. swoffcond priority condition to switch off the consumer (optional). The scheduled cycle is stopped. - Device - Device to supply the priority switch-off condition - Reading - Reading for the delivery of the priority switch-off condition - The condition can be formulated as a regular expression or as Perl code enclosed in {..}: - Regex - regular expression that must be fulfilled for a 'true' condition - {Perl-Code} - the Perl code enclosed in {..} must return 'true' to fulfill the condition. It must not contain spaces. + Device:Reading - the device/reading combination returns the check value $VALUE (‘undef’ is ignored) + The check can be formulated as a regular expression or as Perl code enclosed in {..}: + Regex - regular expression for checking $VALUE which must return ‘true’ if successful + {Perl-Code} - the Perl code enclosed in {..} must not contain any spaces. The variable $VALUE can be evaluated by the code. + The return value must be ‘true’ if successful. surpmeth The possible options define the procedure for determining the PV surplus. (optional) default - the PV surplus is read directly from the 'Current_Surplus' reading. (default) - median - the median of the last PV surplus measurements (max. 20) is used. - 2 .. 20 - the PV surplus used is calculated from the average of the specified number of measured values. + median[_2..20] - The median of the last PV surplus values is used. The optional specification ‘_XX’ uses the last XX measured values. + average[_2..20] - is the average of 20 PV surplus values. The optional specification ‘_XX’ uses the last XX measured values. <Device>:<Reading> - Device/Reading combination that provides a numerical PV surplus value in Watt determined or calculated by the user. spignorecond Condition to ignore a missing PV surplus (optional). If the condition is fulfilled, the load is switched on according to the planning even if there is no PV surplus at the time. CAUTION: Using both keys spignorecond and interruptable can lead to undesired behaviour! - Device - Device to deliver the condition - Reading - Reading which contains the condition - The condition can be formulated as a regular expression or as Perl code enclosed in {..}: - Regex - regular expression that must be fulfilled for a 'true' condition - {Perl-Code} - the Perl code enclosed in {..} must return 'true' to fulfill the condition. It must not contain spaces. - The value of Device:Reading is transferred to the code with the variable $VALUE. + Device:Reading - the device/reading combination returns the check value $VALUE (‘undef’ is ignored) + The check can be formulated as a regular expression or as Perl code enclosed in {..}: + Regex - regular expression for checking $VALUE which must return ‘true’ if successful + {Perl-Code} - the Perl code enclosed in {..} must not contain any spaces. The variable $VALUE can be evaluated by the code. + The return value must be ‘true’ if successful. interruptable defines the possible interruption options for the consumer after it has been started (optional). Options can be: 0 - Load is not temporarily switched off even if the PV surplus falls below the required energy (default) @@ -26353,7 +26438,7 @@ to ensure that the system configuration is correct. attr <name> consumer04 Shelly.shellyplug3 icon=scene_microwave_oven@ed type=heater power=2000 mode=must notbefore=07 mintime=600 on=on off=off etotal=relay_0_energy_Wh:Wh pcurr=relay_0_power:W auto=automatic interruptable=eg.wz.wandthermostat:diff-temp:(22)(\.[2-9])|([2-9][3-9])(\.[0-9]):0.2
    attr <name> consumer05 Shelly.shellyplug4 icon=sani_buffer_electric_heater_side type=heater mode=must power=1000 notbefore=7 notafter=20:10 auto=automatic pcurr=actpow:W on=on off=off mintime=SunPath interruptable=1
    attr <name> consumer06 Shelly.shellyplug5 icon=sani_buffer_electric_heater_side type=heater mode=must power=1000 notbefore=07:20 notafter={return'20:05'} auto=automatic pcurr=actpow:W on=on off=off mintime=SunPath:60:-120 interruptable=1 spignorecond=SolCast:Current_PV:{($VALUE)=split/\s/,$VALUE;$VALUE>10?1:0;}
    - attr <name> consumer07 SolCastDummy icon=sani_buffer_electric_heater_side type=heater mode=can power=600 auto=automatic pcurr=actpow:W on=on off=off mintime=15 asynchron=1 locktime=300:1200 interruptable=1 noshow=39
    + attr <name> consumer07 SolCastDummy icon=sani_buffer_electric_heater_side type=heater mode=can power=600 auto=automatic pcurr=actpow:W on=on off=off mintime=15 asynchron=1 locktime=300:1200 interruptable=1 noshow=39 surpmeth=median_10

  • @@ -28804,7 +28889,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
  • consumerXX <Device>[:<Alias>] type=<type> power=<power> [switchdev=<device>]
    - [mode=<mode>] [icon=<Icon>[@<Farbe>]] [mintime=<Option>]
    + [aliasshort=<String>] [mode=<mode>] [icon=<Icon>[@<Farbe>]] [mintime=<Option>]
    [on=<Kommando>] [off=<Kommando>] [swstate=<Readingname>:<on-Regex>:<off-Regex>] [asynchron=<Option>]
    [notbefore=<Ausdruck>] [notafter=<Ausdruck>] [locktime=<offlt>[:<onlt>]]
    [auto=<Readingname>] [pcurr=<Readingname>:<Einheit>[:<Schwellenwert>]] [etotal=<Readingname>:<Einheit>[:<Schwellenwert>]]
    @@ -28852,6 +28937,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Besteht der Verbraucher aus verschiedenen Geräten/Kanälen (z.B. Homematic), wird der Energiemesser als <Device> definiert. Das dazugehörige Schalt-Gerät wird mit dem Schlüssel 'switchdev' spezifiziert. + aliasshort Kurzalias des Verbrauchers zur Anzeige in der Flußgrafik. Es sind maximal 10 Zeichen und keine Leerzeichen erlaubt. + type Typ des Verbrauchers. Folgende Typen sind erlaubt: dishwasher - Verbraucher ist eine Spülmaschine dryer - Verbraucher ist ein Wäschetrockner @@ -28927,37 +29014,34 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. :<Schwellenwert> (Wh) - Ab diesem Energieverbrauch pro Stunde wird der Verbrauch als gültig gewertet. Optionale Angabe (default: 0) swoncond Bedingung die zusätzlich erfüllt sein muß um den geplanten Zyklus zu starten und den Verbraucher einzuschalten (optional). - Device - Device zur Lieferung der zusätzlichen Einschaltbedingung - Reading - Reading zur Lieferung der zusätzlichen Einschaltbedingung - Die Bedingung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: - Regex - regulärer Ausdruck der für eine 'wahre' Bedingung erfüllt sein muß - {Perl-Code} - der in {..} eingeschlossene Perl-Code muß 'wahr' liefern um die Bedingung zu erfüllen. Er darf keine Leerzeichen enthalten. - Der Wert von Device:Reading wird dem Code mit der Variable $VALUE übergeben. + Device:Reading - die Device/Reading Kombination liefert den Prüfwert $VALUE ('undef' wird ignoriert) + Die Prüfung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: + Regex - regulärer Ausdruck zur Prüfung von $VALUE der im Erfolgsfall 'wahr' liefern muß + {Perl-Code} - der in {..} eingeschlossene Perl-Code darf keine Leerzeichen enthalten. Die Variable $VALUE kann vom Code ausgewertet werden. + Der return Wert muß im Erfolgsfall 'wahr' sein. swoffcond vorrangige Bedingung um den Verbraucher auszuschalten (optional). Der geplante Zyklus wird gestoppt. - Device - Device zur Lieferung der vorrangigen Ausschaltbedingung - Reading - Reading zur Lieferung der vorrangigen Ausschaltbedingung - Die Bedingung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: - Regex - regulärer Ausdruck der für eine 'wahre' Bedingung erfüllt sein muß - {Perl-Code} - der in {..} eingeschlossene Perl-Code muß 'wahr' liefern um die Bedingung zu erfüllen. Er darf keine Leerzeichen enthalten. - Der Wert von Device:Reading wird dem Code mit der Variable $VALUE übergeben. + Device:Reading - die Device/Reading Kombination liefert den Prüfwert $VALUE ('undef' wird ignoriert) + Die Prüfung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: + Regex - regulärer Ausdruck zur Prüfung von $VALUE der im Erfolgsfall 'wahr' liefern muß + {Perl-Code} - der in {..} eingeschlossene Perl-Code darf keine Leerzeichen enthalten. Die Variable $VALUE kann vom Code ausgewertet werden. + Der return Wert muß im Erfolgsfall 'wahr' sein. surpmeth Die möglichen Optionen legen das Verfahren zur Ermittlung des PV-Überschusses fest. (optional) default - der PV-Überschuß wird aus dem Reading 'Current_Surplus' direkt ausgelesen. (default) - median - es wird der Median der letzten PV-Überschuß Messungen (max. 20) verwendet. - 2 .. 20 - der verwendete PV-Überschuß wird als Durchschnitt der angegebenen Anzahl Meßwerte gebildet. + median[_2..20] - es wird der Median der letzten PV-Überschuß Werte verwendet. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. + average[_2..20] - bildet den Durchschnitt von 20 PV-Überschuß Werten. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. <Device>:<Reading> - Device/Reading-Kombination die einen vom Nutzer bestimmten bzw. berechneten numerischen PV-Überschuß in Watt liefert. spignorecond Bedingung um einen fehlenden PV Überschuß zu ignorieren (optional). Bei erfüllter Bedingung wird der Verbraucher entsprechend der Planung eingeschaltet auch wenn zu dem Zeitpunkt kein PV Überschuß vorliegt. ACHTUNG: Die Verwendung beider Schlüssel spignorecond und interruptable kann zu einem unerwünschten Verhalten führen! - Device - Device zur Lieferung der Bedingung - Reading - Reading welches die Bedingung enthält - Die Bedingung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: - Regex - regulärer Ausdruck der für eine 'wahre' Bedingung erfüllt sein muß - {Perl-Code} - der in {..} eingeschlossene Perl-Code muß 'wahr' liefern um die Bedingung zu erfüllen. Er darf keine Leerzeichen enthalten. - Der Wert von Device:Reading wird dem Code mit der Variable $VALUE übergeben. + Device:Reading - die Device/Reading Kombination liefert den Prüfwert $VALUE ('undef' wird ignoriert) + Die Prüfung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: + Regex - regulärer Ausdruck zur Prüfung von $VALUE der im Erfolgsfall 'wahr' liefern muß + {Perl-Code} - der in {..} eingeschlossene Perl-Code darf keine Leerzeichen enthalten. Die Variable $VALUE kann vom Code ausgewertet werden. + Der return Wert muß im Erfolgsfall 'wahr' sein. interruptable definiert die möglichen Unterbrechungsoptionen für den Verbraucher nachdem er gestartet wurde (optional). Optionen können sein: 0 - Verbraucher wird nicht temporär ausgeschaltet auch wenn der PV Überschuß die benötigte Energie unterschreitet (default) @@ -29012,7 +29096,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. attr <name> consumer04 Shelly.shellyplug3 icon=scene_microwave_oven@red type=heater power=2000 mode=must notbefore=07 mintime=600 on=on off=off etotal=relay_0_energy_Wh:Wh pcurr=relay_0_power:W auto=automatic interruptable=eg.wz.wandthermostat:diff-temp:(22)(\.[2-9])|([2-9][3-9])(\.[0-9]):0.2
    attr <name> consumer05 Shelly.shellyplug4 icon=sani_buffer_electric_heater_side type=heater mode=must power=1000 notbefore=7 notafter=20:10 auto=automatic pcurr=actpow:W on=on off=off mintime=SunPath interruptable=1
    attr <name> consumer06 Shelly.shellyplug5 icon=sani_buffer_electric_heater_side type=heater mode=must power=1000 notbefore=07:20 notafter={return'20:05'} auto=automatic pcurr=actpow:W on=on off=off mintime=SunPath:60:-120 interruptable=1 spignorecond=SolCast:Current_PV:{($VALUE)=split/\s/,$VALUE;$VALUE>10?1:0;}
    - attr <name> consumer07 SolCastDummy icon=sani_buffer_electric_heater_side type=heater mode=can power=600 auto=automatic pcurr=actpow:W on=on off=off mintime=15 asynchron=1 locktime=300:1200 interruptable=1 noshow=39
    + attr <name> consumer07 SolCastDummy icon=sani_buffer_electric_heater_side type=heater mode=can power=600 auto=automatic pcurr=actpow:W on=on off=off mintime=15 asynchron=1 locktime=300:1200 interruptable=1 noshow=39 surpmeth=median_10

  • diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 0fbdd349f..f2ae72465 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -160,10 +160,11 @@ BEGIN { # Versions History intern my %vNotesIntern = ( - "1.54.4" => "21.07.2025 replace length by new sub strlength, Consumer attr new key 'aliasshort', change code of medianArray ". + "1.54.4" => "22.07.2025 replace length by new sub strlength, Consumer attr new key 'aliasshort', change code of medianArray ". "medianArray: can optional use newest 3..20 elements, avgArray: use the newest elements if num is set ". "Debug consumerSwitching: print out info message of compare operation, remove attr graphicShowDiff ". - "store surpmeth calc result in key surpmethResult in Consumer master record, __readFileMessages: refactored code ", + "store surpmeth calc result in key surpmethResult in Consumer master record, __readFileMessages: refactored code ". + "surpmeth: use average[_2..20] instead of numeric values 2.20 only ", "1.54.3" => "19.07.2025 ctrlDebug: add collectData_long ", "1.54.2" => "18.07.2025 _createSummaries: add debug infos ", "1.54.1" => "08.07.2025 userExit: new coding, __createReduceIcon: fix Wide character in syswrite - https://forum.fhem.de/index.php?msg=1344368 ". @@ -6179,8 +6180,8 @@ sub _attrconsumer { ## no critic "not used" return "The reading '$rd' of device '$dv' is invalid or doesn't contain a valid numeric value"; } } - elsif ($h->{surpmeth} !~ /^[2-9]$|^1[0-9]$|^20$|^median(?:_(?:[3-9]|1[0-9]|20))?$|^default$/xs) { - return qq{The surpmeth "$h->{surpmeth}" is wrong. It must contain a ':', 'median[_3..20]', 'default' or an integer value of '2 .. 20'.}; + elsif ($h->{surpmeth} !~ /^(?:median|average)(?:_(?:[2-9]|1[0-9]|20))?$|^default$/xs) { + return qq{The surpmeth "$h->{surpmeth}" is wrong. It must contain a ':', 'median[_2..20]', 'average[_2..20]' or 'default'.}; } } @@ -8849,15 +8850,16 @@ sub centralTask { # ::CommandDeleteAttr (undef, "$name graphicBeamWidth"); #} - my $gsd = AttrVal ($name, 'graphicShowDiff ', undef); # 25.06. - my $gco = AttrVal ($name, 'graphicControl', ''); - - if (defined $gsd) { - my $newval = $gco." showDiff=$gsd"; - CommandAttr (undef, "$name graphicControl $newval"); - ::CommandDeleteAttr (undef, "$name graphicShowDiff"); + for my $c (1..MAXCONSUMER) { # 23.07. + $c = sprintf "%02d", $c; + my $surpmeth = ConsumerVal ($hash, $c, 'surpmeth', ''); + + if ($surpmeth =~ /^[2-9]$|^1[0-9]$|^20$/xs) { + fhem ("set $name attrKeyVal consumer${c} surpmeth=average_${surpmeth}"); + } } - + + ########################################################################################################################## if (!CurrentVal ($hash, 'allStringsFullfilled', 0)) { # die String Konfiguration erstellen wenn noch nicht erfolgreich ausgeführt @@ -9325,10 +9327,10 @@ sub _specialActivities { ########################################################################## for my $c (keys %{$data{$name}{consumers}}) { - next if(ConsumerVal ($hash, $c, "plandelete", "regular") eq "regular"); + next if(ConsumerVal ($hash, $c, 'plandelete', 'regular') eq 'regular'); - my $planswitchoff = ConsumerVal ($hash, $c, "planswitchoff", $t); - my $simpCstat = simplifyCstate (ConsumerVal ($hash, $c, "planstate", "")); + my $planswitchoff = ConsumerVal ($hash, $c, 'planswitchoff', $t); + my $simpCstat = simplifyCstate (ConsumerVal ($hash, $c, 'planstate', '')); if ($t > $planswitchoff && $simpCstat =~ /planned|finished|unknown/xs) { deleteConsumerPlanning ($hash, $c); @@ -12013,8 +12015,8 @@ sub _manageConsumerData { for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { $paref->{consumer} = $c; - my $consumer = ConsumerVal ($hash, $c, "name", ""); - my $alias = ConsumerVal ($hash, $c, "alias", ""); + my $consumer = ConsumerVal ($hash, $c, 'name', ''); + my $alias = ConsumerVal ($hash, $c, 'alias', ''); ## aktuelle Leistung auslesen ############################## @@ -13001,8 +13003,7 @@ sub __setConsRcmdState { if ($debug =~ /consumerSwitching${c}/x) { my $splref = CurrentVal ($name, 'surplusslidereg', '.'); - my $spser = 'undef'; - $spser = join " ", @{$splref} if(ref $splref eq 'ARRAY'); + my $spser = ref $splref eq 'ARRAY' ? join ' ', @{$splref} : undef; Log3 ($name, 1, qq{$name DEBUG> ############### consumerSwitching consumer "$c" ###############}); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - ConsumptionRecommended calc method: $method, surplus: }. @@ -22063,14 +22064,15 @@ sub determSurplus { $surplus = medianArray ($splref, $num); $method = $num ? "median:$num" : "median:all"; } + elsif ($surpmeth =~ /average/xs) { # Average Ermittlung, !kann UNDEF sein! + my $num = (split '_', $surpmeth)[1]; + $surplus = avgArray ($splref, $num); + $method = $num ? "average:$num" : "average:all"; + } elsif ($surpmeth eq 'default') { # aktueller Energieüberschuß $surplus = CurrentVal ($hash, 'surplus', 0); $method = 'default'; } - elsif ($surpmeth =~ /^[2-9]$|^1[0-9]$|^20$/xs) { - $surplus = avgArray ($splref, $surpmeth); # Average Ermittlung, !kann UNDEF sein! - $method = "average:$surpmeth"; - } elsif ($surpmeth =~ /.*:.*/xs) { my ($dv, $rd) = split ':', $surpmeth; $method = "$dv:$rd"; @@ -23179,7 +23181,7 @@ sub isAddSwitchOffCond { } } - $info .= qq{-> the effect depends on the switch context}; + $info .= qq{(the effect depends on the switch context)}; } return ($swoff, $info, $err); @@ -26369,8 +26371,8 @@ to ensure that the system configuration is correct. surpmeth The possible options define the procedure for determining the PV surplus. (optional) default - the PV surplus is read directly from the 'Current_Surplus' reading. (default) - median[_3..20] - The median of the last PV surplus values is used. The optional specification ‘_XX’ uses the last XX measured values. - 2 .. 20 - the PV surplus used is calculated from the average of the specified number of measured values. + median[_2..20] - The median of the last PV surplus values is used. The optional specification ‘_XX’ uses the last XX measured values. + average[_2..20] - is the average of 20 PV surplus values. The optional specification ‘_XX’ uses the last XX measured values. <Device>:<Reading> - Device/Reading combination that provides a numerical PV surplus value in Watt determined or calculated by the user. @@ -29027,8 +29029,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. surpmeth Die möglichen Optionen legen das Verfahren zur Ermittlung des PV-Überschusses fest. (optional) default - der PV-Überschuß wird aus dem Reading 'Current_Surplus' direkt ausgelesen. (default) - median[_3..20] - es wird der Median der letzten PV-Überschuß Werte verwendet. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. - 2 .. 20 - der verwendete PV-Überschuß wird als Durchschnitt der angegebenen Anzahl Meßwerte gebildet. + median[_2..20] - es wird der Median der letzten PV-Überschuß Werte verwendet. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. + average[_2..20] - bildet den Durchschnitt von 20 PV-Überschuß Werten. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. <Device>:<Reading> - Device/Reading-Kombination die einen vom Nutzer bestimmten bzw. berechneten numerischen PV-Überschuß in Watt liefert.