76_SolarForecast: Version 1.54.4

git-svn-id: https://svn.fhem.de/fhem/trunk@30146 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
DS_Starter
2025-07-22 19:20:12 +00:00
parent 0adcac07a3
commit 33210f17e3
3 changed files with 295 additions and 208 deletions

View File

@@ -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

View File

@@ -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;
if (!open(FD, ">$fPath")) {
$err = "update ERROR open $fPath failed: $!";
return $err;
open my $fh, '>:raw', $fPath or return "update ERROR open $fPath failed: $!";
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: $!";
}
binmode(FD);
print FD $content;
close(FD);
my $expected = length $bytes;
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 => '',
@@ -6144,6 +6149,11 @@ sub _attrconsumer { ## no critic "not used"
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) {
my ($dv, $rd) = split ':', $h->{mode};
@@ -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 '<device>:<reading>', '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 '<device>:<reading>', 'median[_2..20]', 'average[_2..20]' or 'default'.};
}
}
@@ -8840,14 +8850,15 @@ sub centralTask {
# ::CommandDeleteAttr (undef, "$name graphicBeamWidth");
#}
my $gsd = AttrVal ($name, 'graphicShowDiff ', undef); # 25.06.
my $gco = AttrVal ($name, 'graphicControl', '');
for my $c (1..MAXCONSUMER) { # 23.07.
$c = sprintf "%02d", $c;
my $surpmeth = ConsumerVal ($hash, $c, 'surpmeth', '');
if (defined $gsd) {
my $newval = $gco." showDiff=$gsd";
CommandAttr (undef, "$name graphicControl $newval");
::CommandDeleteAttr (undef, "$name graphicShowDiff");
if ($surpmeth =~ /^[2-9]$|^1[0-9]$|^20$/xs) {
fhem ("set $name attrKeyVal consumer${c} surpmeth=average_${surpmeth}");
}
}
##########################################################################################################################
@@ -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
##############################
@@ -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<});
@@ -13678,7 +13697,6 @@ sub _calcConsForecast_circular {
$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,11 +17930,12 @@ 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}{shortalias} = ConsumerVal ($name, $c, 'aliasshort', ''); # Consumer Kurzalias
$cnsmr->{$c}{ptyp} = 'consumer';
}
@@ -17925,6 +17943,11 @@ sub _flowGraphic {
$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,29 +18433,52 @@ 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{<text class="$stna text" id="consumertxt_${c}_$stna" x="$cons_left" y="1110" style="text-anchor: start;">$cnsmrpower</text>} if($flowgconsPower); # Lage Consumer Consumption
#$ret .= qq{<text class="$stna text" id="consumertxt_time_${c}_$stna" x="$cons_left" y="1170" style="text-anchor: start;">$consumerTime</text>} 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;
# 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}
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}
$ret .= qq{<text class="$stna text" id="consumertxt_${c}_$stna" x="$cons_left" y="$y_pos">$cnsmrpower</text>} if($flowgconsPower); # Lage Consumer Consumption
$ret .= qq{<text class="$stna text" id="consumertxt_time_${c}_$stna" x="$cons_left" y="$y_pos1">$consumerTime</text>} if($flowgconsTime); # Lage Consumer Restlaufzeit
$ret .= qq{<text class="$stna text" id="consumertxt_${c}_$stna" x="$lcp_cons_left" y="$y_pos">$cnsmrpower</text>};
}
# 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}
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{<text class="$stna text" id="consumertxt_time_${c}_$stna" x="$lct_cons_left" y="$y_pos1">$consumerTime</text>};
}
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{<text class="$stna text" id="consumertxtalias_${c}_$stna" x="$lcs_cons_left" y="$y_pos2">$shortalias</text>};
}
$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]//; $_ } <FD>;
close (FD);
my $count = 0;
Log3 ($name, 4, "$name - Notification System - read local Message File >$messagefile< with ".scalar @locList." entries.");
while (my $line = <$fh>) {
chomp $line;
next if $line =~ /^\s*#/; # Kommentarzeilen überspringen
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);
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
$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 = '&nbsp;' x 17;
my $blkadd0 = '&nbsp;' x (7 - ($ln0 > 7 ? 0 : $ln0));
my $ln1 = length $f;
my $ln1 = strlength ($f);
my $blkadd1 = '&nbsp;' 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";
@@ -22089,11 +22160,10 @@ sub avgArray {
return undef if(ref $aref ne 'ARRAY' || scalar @{$aref} < $num);
my $sum = 0;
my @tail = @{$aref}[-$num .. -1]; # es werden die neuesten num Elemente verwendet
for my $i (0 .. $num-1) {
$sum += ${$aref}[$i];
}
my $sum = 0;
$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});
return 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
if (defined $num) { # Anzahl der (neuesten) Elemente die verwendet werden sollen
return unless $num =~ /^\d+$/ && $num > 0 && $num <= @$aref;
}
return;
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);
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.
<a id="SolarForecast-attr-consumer" data-pattern="consumer.*"></a>
<li><b>consumerXX &lt;Device&gt;[:&lt;Alias&gt;] type=&lt;type&gt; power=&lt;power&gt; [switchdev=&lt;device&gt;] <br>
[mode=&lt;mode&gt;] [icon=&lt;Icon&gt;[@&lt;Color&gt;]] [mintime=&lt;Option&gt;] <br>
[aliasshort=&lt;String&gt;] [mode=&lt;mode&gt;] [icon=&lt;Icon&gt;[@&lt;Color&gt;]] [mintime=&lt;Option&gt;] <br>
[on=&lt;command&gt;] [off=&lt;command&gt;] [swstate=&lt;Readingname&gt;:&lt;on-Regex&gt;:&lt;off-Regex&gt;] [asynchron=&lt;Option&gt;] <br>
[notbefore=&lt;Expression&gt;] [notafter=&lt;Expression&gt;] [locktime=&lt;offlt&gt;[:&lt;onlt&gt;]] <br>
[auto=&lt;Readingname&gt;] [pcurr=&lt;Readingname&gt;:&lt;Unit&gt;[:&lt;Threshold&gt;]] [etotal=&lt;Readingname&gt;:&lt;Einheit&gt;[:&lt;Threshold&gt;]] <br>
@@ -26194,6 +26279,8 @@ to ensure that the system configuration is correct.
<tr><td> </td><td>If the consumer consists of different devices/channels (e.g. Homematic), the energy meter is defined as a &lt;Device&gt;. </td></tr>
<tr><td> </td><td>The associated switching device is specified with the key 'switchdev'. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>aliasshort</b> </td><td>Short alias of the consumer for display in the flow chart. A maximum of 10 characters and no spaces are allowed. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>type</b> </td><td>Type of consumer. The following types are allowed: </td></tr>
<tr><td> </td><td><b>dishwasher</b> - Consumer is a dishwasher </td></tr>
<tr><td> </td><td><b>dryer</b> - Consumer is a tumble dryer </td></tr>
@@ -26269,36 +26356,34 @@ to ensure that the system configuration is correct.
<tr><td> </td><td>:&lt;Threshold&gt (Wh) - From this energy consumption per hour, the consumption is considered valid. Optional specification (default: 0) </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>swoncond</b> </td><td>Condition that must also be fulfilled in order to switch on the consumer (optional). The scheduled cycle is started. </td></tr>
<tr><td> </td><td><b>Device</b> - Device to supply the additional switch-on condition </td></tr>
<tr><td> </td><td><b>Reading</b> - Reading for delivery of the additional switch-on condition </td></tr>
<tr><td> </td><td>The condition can be formulated as a regular expression or as Perl code enclosed in {..}: </td></tr>
<tr><td> </td><td><b>Regex</b> - regular expression that must be fulfilled for a 'true' condition </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - the Perl code enclosed in {..} must return 'true' to fulfill the condition. It must not contain spaces. </td></tr>
<tr><td> </td><td>The value of Device:Reading is transferred to the code with the variable $VALUE. </td></tr>
<tr><td> </td><td><b>Device:Reading</b> - the device/reading combination returns the check value $VALUE (undef is ignored) </td></tr>
<tr><td> </td><td>The check can be formulated as a regular expression or as Perl code enclosed in {..}: </td></tr>
<tr><td> </td><td><b>Regex</b> - regular expression for checking $VALUE which must return true if successful </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - the Perl code enclosed in {..} must not contain any spaces. The variable $VALUE can be evaluated by the code. </td></tr>
<tr><td> </td><td>The return value must be true if successful. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>swoffcond</b> </td><td>priority condition to switch off the consumer (optional). The scheduled cycle is stopped. </td></tr>
<tr><td> </td><td><b>Device</b> - Device to supply the priority switch-off condition </td></tr>
<tr><td> </td><td><b>Reading</b> - Reading for the delivery of the priority switch-off condition </td></tr>
<tr><td> </td><td>The condition can be formulated as a regular expression or as Perl code enclosed in {..}: </td></tr>
<tr><td> </td><td><b>Regex</b> - regular expression that must be fulfilled for a 'true' condition </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - the Perl code enclosed in {..} must return 'true' to fulfill the condition. It must not contain spaces. </td></tr>
<tr><td> </td><td><b>Device:Reading</b> - the device/reading combination returns the check value $VALUE (undef is ignored) </td></tr>
<tr><td> </td><td>The check can be formulated as a regular expression or as Perl code enclosed in {..}: </td></tr>
<tr><td> </td><td><b>Regex</b> - regular expression for checking $VALUE which must return true if successful </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - the Perl code enclosed in {..} must not contain any spaces. The variable $VALUE can be evaluated by the code. </td></tr>
<tr><td> </td><td>The return value must be true if successful. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>surpmeth</b> </td><td>The possible options define the procedure for determining the PV surplus. (optional) </td></tr>
<tr><td> </td><td><b>default</b> - the PV surplus is read directly from the 'Current_Surplus' reading. (default) </td></tr>
<tr><td> </td><td><b>median</b> - the median of the last PV surplus measurements (max. 20) is used. </td></tr>
<tr><td> </td><td><b>2 .. 20</b> - the PV surplus used is calculated from the average of the specified number of measured values. </td></tr>
<tr><td> </td><td><b>median[_2..20]</b> - The median of the last PV surplus values is used. The optional specification _XX uses the last XX measured values. </td></tr>
<tr><td> </td><td><b>average[_2..20]</b> - is the average of 20 PV surplus values. The optional specification _XX uses the last XX measured values. </td></tr>
<tr><td> </td><td><b>&lt;Device&gt;:&lt;Reading&gt;</b> - Device/Reading combination that provides a numerical PV surplus value in Watt
determined or calculated by the user. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>spignorecond</b> </td><td>Condition to ignore a missing PV surplus (optional). If the condition is fulfilled, the load is switched on according to </td></tr>
<tr><td> </td><td>the planning even if there is no PV surplus at the time. </td></tr>
<tr><td> </td><td><b>CAUTION:</b> Using both keys <I>spignorecond</I> and <I>interruptable</I> can lead to undesired behaviour! </td></tr>
<tr><td> </td><td><b>Device</b> - Device to deliver the condition </td></tr>
<tr><td> </td><td><b>Reading</b> - Reading which contains the condition </td></tr>
<tr><td> </td><td>The condition can be formulated as a regular expression or as Perl code enclosed in {..}: </td></tr>
<tr><td> </td><td><b>Regex</b> - regular expression that must be fulfilled for a 'true' condition </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - the Perl code enclosed in {..} must return 'true' to fulfill the condition. It must not contain spaces. </td></tr>
<tr><td> </td><td>The value of Device:Reading is transferred to the code with the variable $VALUE. </td></tr>
<tr><td> </td><td><b>Device:Reading</b> - the device/reading combination returns the check value $VALUE (undef is ignored) </td></tr>
<tr><td> </td><td>The check can be formulated as a regular expression or as Perl code enclosed in {..}: </td></tr>
<tr><td> </td><td><b>Regex</b> - regular expression for checking $VALUE which must return true if successful </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - the Perl code enclosed in {..} must not contain any spaces. The variable $VALUE can be evaluated by the code. </td></tr>
<tr><td> </td><td>The return value must be true if successful. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>interruptable</b> </td><td>defines the possible interruption options for the consumer after it has been started (optional). Options can be: </td></tr>
<tr><td> </td><td><b>0</b> - Load is not temporarily switched off even if the PV surplus falls below the required energy (default) </td></tr>
@@ -26353,7 +26438,7 @@ to ensure that the system configuration is correct.
<b>attr &lt;name&gt; consumer04</b> 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 <br>
<b>attr &lt;name&gt; consumer05</b> 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 <br>
<b>attr &lt;name&gt; consumer06</b> 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;} <br>
<b>attr &lt;name&gt; consumer07</b> 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 <br>
<b>attr &lt;name&gt; consumer07</b> 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 <br>
</ul>
</li>
<br>
@@ -28804,7 +28889,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<a id="SolarForecast-attr-consumer" data-pattern="consumer.*"></a>
<li><b>consumerXX &lt;Device&gt;[:&lt;Alias&gt;] type=&lt;type&gt; power=&lt;power&gt; [switchdev=&lt;device&gt;] <br>
[mode=&lt;mode&gt;] [icon=&lt;Icon&gt;[@&lt;Farbe&gt;]] [mintime=&lt;Option&gt;] <br>
[aliasshort=&lt;String&gt;] [mode=&lt;mode&gt;] [icon=&lt;Icon&gt;[@&lt;Farbe&gt;]] [mintime=&lt;Option&gt;] <br>
[on=&lt;Kommando&gt;] [off=&lt;Kommando&gt;] [swstate=&lt;Readingname&gt;:&lt;on-Regex&gt;:&lt;off-Regex&gt;] [asynchron=&lt;Option&gt;] <br>
[notbefore=&lt;Ausdruck&gt;] [notafter=&lt;Ausdruck&gt;] [locktime=&lt;offlt&gt;[:&lt;onlt&gt;]] <br>
[auto=&lt;Readingname&gt;] [pcurr=&lt;Readingname&gt;:&lt;Einheit&gt;[:&lt;Schwellenwert&gt]] [etotal=&lt;Readingname&gt;:&lt;Einheit&gt;[:&lt;Schwellenwert&gt;]] <br>
@@ -28852,6 +28937,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<tr><td> </td><td>Besteht der Verbraucher aus verschiedenen Geräten/Kanälen (z.B. Homematic), wird der Energiemesser als &lt;Device&gt; definiert. </td></tr>
<tr><td> </td><td>Das dazugehörige Schalt-Gerät wird mit dem Schlüssel 'switchdev' spezifiziert. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>aliasshort</b> </td><td>Kurzalias des Verbrauchers zur Anzeige in der Flußgrafik. Es sind maximal 10 Zeichen und keine Leerzeichen erlaubt. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>type</b> </td><td>Typ des Verbrauchers. Folgende Typen sind erlaubt: </td></tr>
<tr><td> </td><td><b>dishwasher</b> - Verbraucher ist eine Spülmaschine </td></tr>
<tr><td> </td><td><b>dryer</b> - Verbraucher ist ein Wäschetrockner </td></tr>
@@ -28927,37 +29014,34 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<tr><td> </td><td>:&lt;Schwellenwert&gt (Wh) - Ab diesem Energieverbrauch pro Stunde wird der Verbrauch als gültig gewertet. Optionale Angabe (default: 0) </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>swoncond</b> </td><td>Bedingung die zusätzlich erfüllt sein muß um den geplanten Zyklus zu starten und den Verbraucher einzuschalten (optional). </td></tr>
<tr><td> </td><td><b>Device</b> - Device zur Lieferung der zusätzlichen Einschaltbedingung </td></tr>
<tr><td> </td><td><b>Reading</b> - Reading zur Lieferung der zusätzlichen Einschaltbedingung </td></tr>
<tr><td> </td><td>Die Bedingung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: </td></tr>
<tr><td> </td><td><b>Regex</b> - regulärer Ausdruck der für eine 'wahre' Bedingung erfüllt sein muß </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - der in {..} eingeschlossene Perl-Code muß 'wahr' liefern um die Bedingung zu erfüllen. Er darf keine Leerzeichen enthalten. </td></tr>
<tr><td> </td><td>Der Wert von Device:Reading wird dem Code mit der Variable $VALUE übergeben. </td></tr>
<tr><td> </td><td><b>Device:Reading</b> - die Device/Reading Kombination liefert den Prüfwert $VALUE ('undef' wird ignoriert) </td></tr>
<tr><td> </td><td>Die Prüfung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: </td></tr>
<tr><td> </td><td><b>Regex</b> - regulärer Ausdruck zur Prüfung von $VALUE der im Erfolgsfall 'wahr' liefern muß </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - der in {..} eingeschlossene Perl-Code darf keine Leerzeichen enthalten. Die Variable $VALUE kann vom Code ausgewertet werden. </td></tr>
<tr><td> </td><td>Der return Wert muß im Erfolgsfall 'wahr' sein. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>swoffcond</b> </td><td>vorrangige Bedingung um den Verbraucher auszuschalten (optional). Der geplante Zyklus wird gestoppt. </td></tr>
<tr><td> </td><td><b>Device</b> - Device zur Lieferung der vorrangigen Ausschaltbedingung </td></tr>
<tr><td> </td><td><b>Reading</b> - Reading zur Lieferung der vorrangigen Ausschaltbedingung </td></tr>
<tr><td> </td><td>Die Bedingung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: </td></tr>
<tr><td> </td><td><b>Regex</b> - regulärer Ausdruck der für eine 'wahre' Bedingung erfüllt sein muß </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - der in {..} eingeschlossene Perl-Code muß 'wahr' liefern um die Bedingung zu erfüllen. Er darf keine Leerzeichen enthalten. </td></tr>
<tr><td> </td><td>Der Wert von Device:Reading wird dem Code mit der Variable $VALUE übergeben. </td></tr>
<tr><td> </td><td><b>Device:Reading</b> - die Device/Reading Kombination liefert den Prüfwert $VALUE ('undef' wird ignoriert) </td></tr>
<tr><td> </td><td>Die Prüfung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: </td></tr>
<tr><td> </td><td><b>Regex</b> - regulärer Ausdruck zur Prüfung von $VALUE der im Erfolgsfall 'wahr' liefern muß </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - der in {..} eingeschlossene Perl-Code darf keine Leerzeichen enthalten. Die Variable $VALUE kann vom Code ausgewertet werden. </td></tr>
<tr><td> </td><td>Der return Wert muß im Erfolgsfall 'wahr' sein. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>surpmeth</b> </td><td>Die möglichen Optionen legen das Verfahren zur Ermittlung des PV-Überschusses fest. (optional) </td></tr>
<tr><td> </td><td><b>default</b> - der PV-Überschuß wird aus dem Reading 'Current_Surplus' direkt ausgelesen. (default) </td></tr>
<tr><td> </td><td><b>median</b> - es wird der Median der letzten PV-Überschuß Messungen (max. 20) verwendet. </td></tr>
<tr><td> </td><td><b>2 .. 20</b> - der verwendete PV-Überschuß wird als Durchschnitt der angegebenen Anzahl Meßwerte gebildet. </td></tr>
<tr><td> </td><td><b>median[_2..20]</b> - es wird der Median der letzten PV-Überschuß Werte verwendet. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. </td></tr>
<tr><td> </td><td><b>average[_2..20]</b> - bildet den Durchschnitt von 20 PV-Überschuß Werten. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. </td></tr>
<tr><td> </td><td><b>&lt;Device&gt;:&lt;Reading&gt;</b> - Device/Reading-Kombination die einen vom Nutzer bestimmten bzw. berechneten
numerischen PV-Überschuß in Watt liefert. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>spignorecond</b> </td><td>Bedingung um einen fehlenden PV Überschuß zu ignorieren (optional). Bei erfüllter Bedingung wird der Verbraucher entsprechend </td></tr>
<tr><td> </td><td>der Planung eingeschaltet auch wenn zu dem Zeitpunkt kein PV Überschuß vorliegt. </td></tr>
<tr><td> </td><td><b>ACHTUNG:</b> Die Verwendung beider Schlüssel <I>spignorecond</I> und <I>interruptable</I> kann zu einem unerwünschten Verhalten führen! </td></tr>
<tr><td> </td><td><b>Device</b> - Device zur Lieferung der Bedingung </td></tr>
<tr><td> </td><td><b>Reading</b> - Reading welches die Bedingung enthält </td></tr>
<tr><td> </td><td>Die Bedingung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: </td></tr>
<tr><td> </td><td><b>Regex</b> - regulärer Ausdruck der für eine 'wahre' Bedingung erfüllt sein muß </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - der in {..} eingeschlossene Perl-Code muß 'wahr' liefern um die Bedingung zu erfüllen. Er darf keine Leerzeichen enthalten. </td></tr>
<tr><td> </td><td>Der Wert von Device:Reading wird dem Code mit der Variable $VALUE übergeben. </td></tr>
<tr><td> </td><td><b>Device:Reading</b> - die Device/Reading Kombination liefert den Prüfwert $VALUE ('undef' wird ignoriert) </td></tr>
<tr><td> </td><td>Die Prüfung kann als regulärer Ausdruck oder als in {..} eingeschlossener Perl-Code formuliert sein: </td></tr>
<tr><td> </td><td><b>Regex</b> - regulärer Ausdruck zur Prüfung von $VALUE der im Erfolgsfall 'wahr' liefern muß </td></tr>
<tr><td> </td><td><b>{Perl-Code}</b> - der in {..} eingeschlossene Perl-Code darf keine Leerzeichen enthalten. Die Variable $VALUE kann vom Code ausgewertet werden. </td></tr>
<tr><td> </td><td>Der return Wert muß im Erfolgsfall 'wahr' sein. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>interruptable</b> </td><td>definiert die möglichen Unterbrechungsoptionen für den Verbraucher nachdem er gestartet wurde (optional). Optionen können sein: </td></tr>
<tr><td> </td><td><b>0</b> - Verbraucher wird nicht temporär ausgeschaltet auch wenn der PV Überschuß die benötigte Energie unterschreitet (default) </td></tr>
@@ -29012,7 +29096,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<b>attr &lt;name&gt; consumer04</b> 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 <br>
<b>attr &lt;name&gt; consumer05</b> 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 <br>
<b>attr &lt;name&gt; consumer06</b> 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;} <br>
<b>attr &lt;name&gt; consumer07</b> 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 <br>
<b>attr &lt;name&gt; consumer07</b> 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 <br>
</ul>
</li>
<br>

View File

@@ -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 '<device>:<reading>', '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 '<device>:<reading>', 'median[_2..20]', 'average[_2..20]' or 'default'.};
}
}
@@ -8849,14 +8850,15 @@ sub centralTask {
# ::CommandDeleteAttr (undef, "$name graphicBeamWidth");
#}
my $gsd = AttrVal ($name, 'graphicShowDiff ', undef); # 25.06.
my $gco = AttrVal ($name, 'graphicControl', '');
for my $c (1..MAXCONSUMER) { # 23.07.
$c = sprintf "%02d", $c;
my $surpmeth = ConsumerVal ($hash, $c, 'surpmeth', '');
if (defined $gsd) {
my $newval = $gco." showDiff=$gsd";
CommandAttr (undef, "$name graphicControl $newval");
::CommandDeleteAttr (undef, "$name graphicShowDiff");
if ($surpmeth =~ /^[2-9]$|^1[0-9]$|^20$/xs) {
fhem ("set $name attrKeyVal consumer${c} surpmeth=average_${surpmeth}");
}
}
##########################################################################################################################
@@ -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.
<tr><td> </td><td> </td></tr>
<tr><td> <b>surpmeth</b> </td><td>The possible options define the procedure for determining the PV surplus. (optional) </td></tr>
<tr><td> </td><td><b>default</b> - the PV surplus is read directly from the 'Current_Surplus' reading. (default) </td></tr>
<tr><td> </td><td><b>median[_3..20]</b> - The median of the last PV surplus values is used. The optional specification _XX uses the last XX measured values. </td></tr>
<tr><td> </td><td><b>2 .. 20</b> - the PV surplus used is calculated from the average of the specified number of measured values. </td></tr>
<tr><td> </td><td><b>median[_2..20]</b> - The median of the last PV surplus values is used. The optional specification _XX uses the last XX measured values. </td></tr>
<tr><td> </td><td><b>average[_2..20]</b> - is the average of 20 PV surplus values. The optional specification _XX uses the last XX measured values. </td></tr>
<tr><td> </td><td><b>&lt;Device&gt;:&lt;Reading&gt;</b> - Device/Reading combination that provides a numerical PV surplus value in Watt
determined or calculated by the user. </td></tr>
<tr><td> </td><td> </td></tr>
@@ -29027,8 +29029,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<tr><td> </td><td> </td></tr>
<tr><td> <b>surpmeth</b> </td><td>Die möglichen Optionen legen das Verfahren zur Ermittlung des PV-Überschusses fest. (optional) </td></tr>
<tr><td> </td><td><b>default</b> - der PV-Überschuß wird aus dem Reading 'Current_Surplus' direkt ausgelesen. (default) </td></tr>
<tr><td> </td><td><b>median[_3..20]</b> - es wird der Median der letzten PV-Überschuß Werte verwendet. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. </td></tr>
<tr><td> </td><td><b>2 .. 20</b> - der verwendete PV-Überschuß wird als Durchschnitt der angegebenen Anzahl Meßwerte gebildet. </td></tr>
<tr><td> </td><td><b>median[_2..20]</b> - es wird der Median der letzten PV-Überschuß Werte verwendet. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. </td></tr>
<tr><td> </td><td><b>average[_2..20]</b> - bildet den Durchschnitt von 20 PV-Überschuß Werten. Die optionale Angabe '_XX' verwendet die letzten XX Meßwerte. </td></tr>
<tr><td> </td><td><b>&lt;Device&gt;:&lt;Reading&gt;</b> - Device/Reading-Kombination die einen vom Nutzer bestimmten bzw. berechneten
numerischen PV-Überschuß in Watt liefert. </td></tr>
<tr><td> </td><td> </td></tr>