From 73d60afe3c876fbbbd9a87658ef6db50a4fb2a54 Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Sat, 19 Apr 2025 12:10:51 +0000 Subject: [PATCH] 76_SolarForecast: contrib 1.51.1 git-svn-id: https://svn.fhem.de/fhem/trunk@29876 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/DS_Starter/76_SolarForecast.pm | 778 +++++++++++--------- 1 file changed, 431 insertions(+), 347 deletions(-) diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index bb569cde8..a98498c69 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -160,9 +160,17 @@ BEGIN { # Versions History intern my %vNotesIntern = ( - "1.50.4" => "15.04.2025 Consumer Strokes: fix __dynColor, new key flowGraphicControl->strokeCmrRedColLimit ". + "1.51.1" => "19.04.2025 consumer: interruptable, swoncond, swoffcond can be perl code enclosed by {..} ". + "check key is valid in plantControl, aiControl, flowGraphicControl, consumerControl, setupMeterDev ". + "setupOtherProducer, setupInverterDev, setupBatteryDev, consumer ". + "writeCacheToFile: bugfix - cache File on OS is deleted if cache is empty ", + "1.51.0" => "16.04.2025 obsolete Attr deleted: affectBatteryPreferredCharge, affectConsForecastInPlanning, ctrlShowLink, ctrlBackupFilesKeep ". + "affectConsForecastIdentWeekdays, affectConsForecastLastDays, ctrlInterval, ctrlGenPVdeviation ". + "affectSolCastPercentile, ctrlSolCastAPIoptimizeReq, consumerAdviceIcon, consumerLink, consumerLegend ", + "1.50.4" => "16.04.2025 Consumer Strokes: fix __dynColor, new key flowGraphicControl->strokeCmrRedColLimit ". "__getopenMeteoData: fix get calclated call interval, new Setter cycleInterval ". - "normBeamWidth: decouple content batsocforecast_, energycosts, feedincome from the conversion Wh -> kWh ", + "normBeamWidth: decouple content batsocforecast_, energycosts, feedincome from the conversion Wh -> kWh ". + "___getFWwidget: textField-long -> textFieldNL-long ", "1.50.3" => "12.04.2025 __calcPVestimates: Fix missing limitation for strings if more than one string is assigned to an inverter ". "code change in _attrInverterStrings, _attrStringPeak, checkPlantConfig: improved string check ", "1.50.2" => "11.04.2025 take inverter cap into account if no strings key is set, ctrlSpecialReadings: new option tomorrowConsumptionForecast ". @@ -387,17 +395,13 @@ my %vNotesIntern = ( "rename graphicBeamHeight to graphicBeamHeightLevel1 ", "1.17.12"=> "06.05.2024 attr ctrlInterval: immediate impact when set ", "1.17.11"=> "04.05.2024 correction in commandref, delete attr affectMaxDayVariance ", - "1.17.10"=> "19.04.2024 _calcTodayPVdeviation: avoid Illegal division by zero, Forum: https://forum.fhem.de/index.php?msg=1311121 ", - "1.17.9" => "17.04.2024 _batSocTarget: fix Illegal division by zero, Forum: https://forum.fhem.de/index.php?msg=1310930 ", - "1.17.8" => "16.04.2024 _calcTodayPVdeviation: change of calculation ", - "1.17.7" => "09.04.2024 export pvHistory to CSV, making attr affectMaxDayVariance obsolete ", "0.1.0" => "09.12.2020 initial Version " ); ## Konstanten ###################### use constant { - INFINIITY => ~0 >> 1, # "Unendlich" + INFINITE => ~0 >> 1, # "Unendlich" LPOOLLENLIM => 140, # Breitenbegrenzung der Ausgabe von List Pooldaten KJ2KWH => 0.0002777777778, # Umrechnungsfaktor kJ in kWh KJ2WH => 0.2777777778, # Umrechnungsfaktor kJ in Wh @@ -1595,12 +1599,10 @@ sub Initialize { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ########################################################################################################################## - my $av = 'obsolete#-#use#attr#plantControl#instead'; - my $av1 = 'obsolete#-#will#be#deleted#soon'; - my $av2 = 'obsolete#-#use#attr#consumerControl#instead'; - $hash->{AttrList} .= " affectBatteryPreferredCharge:$av affectConsForecastInPlanning:$av ctrlShowLink:$av ctrlBackupFilesKeep:$av affectConsForecastIdentWeekdays:$av affectConsForecastLastDays:$av ctrlInterval:$av ctrlGenPVdeviation:$av"; # 31.03.2025 - $hash->{AttrList} .= " affectSolCastPercentile:$av1 ctrlSolCastAPIoptimizeReq:$av1"; # 29.03.2025 - $hash->{AttrList} .= " consumerAdviceIcon:$av2 consumerLink:$av2 consumerLegend:$av2"; # 05.04.2025 + #my $av = 'obsolete#-#use#attr#plantControl#instead'; + #my $av1 = 'obsolete#-#will#be#deleted#soon'; + #my $av2 = 'obsolete#-#use#attr#consumerControl#instead'; + #$hash->{AttrList} .= " affectBatteryPreferredCharge:$av "; ########################################################################################################################## $hash->{FW_hideDisplayName} = 1; # Forum 88667 @@ -1879,10 +1881,6 @@ sub _setcycleInterval { ## no critic "not used" return if(!$init_done); - if ($prop !~ /^\d+$/xs) { - return "The $opt must be specified by an Integer."; - } - my $pc = AttrVal ($name, 'plantControl', undef); my $new; @@ -1899,6 +1897,8 @@ sub _setcycleInterval { ## no critic "not used" } my $ret = CommandAttr (undef, "$new"); + return $ret if($ret); + ::CommandSave (undef, undef) if(!$ret); return; @@ -2421,10 +2421,10 @@ sub _setreset { ## no critic "not used" ); for my $f (@ftd) { - my $err = FileDelete($f); + my $err = FileDelete ($f); if ($err) { - Log3 ($name, 1, qq{$name - Message while deleting file "$f": $err}); + Log3 ($name, 1, qq{$name - ERROR deleting file $err}); } } @@ -2497,7 +2497,7 @@ sub _setreset { ## no critic "not used" } delete $paref->{c}; - $data{$name}{current}{consumerCollected} = 0; # Consumer neu sammeln + $data{$name}{current}{consumerCollected} = 0; # Consumer neu sammeln writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben centralTask ($hash, 0); @@ -5903,37 +5903,37 @@ sub Attr { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ###################################################################################################################### - if ($cmd eq 'set' && $aName =~ /^affectBatteryPreferredCharge|affectConsForecastInPlanning|ctrlShowLink|ctrlBackupFilesKeep|affectConsForecastIdentWeekdays|affectConsForecastLastDays|ctrlInterval|ctrlGenPVdeviation$/) { # 31.03.2025 - my $msg = "The attribute $aName is replaced by 'plantControl'."; - if (!$init_done) { - Log3 ($name, 1, "$name - $msg"); - } - else { - return $msg; - } - } + #if ($cmd eq 'set' && $aName =~ /^affectBatteryPreferredCharge$/) { + # my $msg = "The attribute $aName is replaced by 'plantControl'."; + # if (!$init_done) { + # Log3 ($name, 1, "$name - $msg"); + # } + # else { + # return $msg; + # } + #} - if ($cmd eq 'set' && $aName =~ /^consumerAdviceIcon|consumerLink|consumerLegend$/) { # 04.04.2025 - my $msg = "The attribute $aName is replaced by 'consumerControl'."; - if (!$init_done) { - Log3 ($name, 1, "$name - $msg"); - } - else { - return $msg; - } - } + #if ($cmd eq 'set' && $aName =~ /^consumerAdviceIcon$/) { + # my $msg = "The attribute $aName is replaced by 'consumerControl'."; + # if (!$init_done) { + # Log3 ($name, 1, "$name - $msg"); + # } + # else { + # return $msg; + # } + #} - if ($cmd eq 'set' && $aName =~ /^affectSolCastPercentile|ctrlSolCastAPIoptimizeReq$/) { # 29.03.2025 - my $msg1 = "The attribute $aName is obsolete and will be deleted soon. Please press 'save config' when restart is finished."; - my $msg2 = "The attribute $aName is obsolete and will be deleted soon."; - if (!$init_done) { - Log3 ($name, 1, "$name - $msg1"); - return $msg1; - } - else { - return $msg2; - } - } + #if ($cmd eq 'set' && $aName =~ /^affectSolCastPercentile$/) { + # my $msg1 = "The attribute $aName is obsolete and will be deleted soon. Please press 'save config' when restart is finished."; + # my $msg2 = "The attribute $aName is obsolete and will be deleted soon."; + # if (!$init_done) { + # Log3 ($name, 1, "$name - $msg1"); + # return $msg1; + # } + # else { + # return $msg2; + # } + #} ###################################################################################################################### if ($aName eq 'disable') { @@ -6022,10 +6022,42 @@ sub _attrconsumer { ## no critic "not used" return if(!$init_done); # Forum: https://forum.fhem.de/index.php/topic,117864.msg1159959.html#msg1159959 my $hash = $defs{$name}; + + my $valid = { + type => '', + power => '', + switchdev => '', + mode => '', + icon => '', + mintime => '', + on => '', + off => '', + swstate => '', + asynchron => '', + notbefore => '', + notafter => '', + auto => '', + pcurr => '', + etotal => '', + swoncond => '', + swoffcond => '', + surpmeth => '', + spignorecond => '', + interruptable => '', + locktime => '', + noshow => '', + exconfc => '', + }; if ($cmd eq "set") { my ($err, $codev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); + + for my $key (keys %{$h}) { + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + } if (!$h->{type} || !exists $h->{power}) { return qq{The syntax of "$aName" is not correct. Please consider the command reference.}; @@ -6104,16 +6136,34 @@ sub _attrconsumer { ## no critic "not used" } } - if (exists $h->{interruptable}) { # Check Regex/Hysterese + if (exists $h->{interruptable}) { # Check Regex,Code/Hysterese if ($h->{interruptable} !~ /^[01]$/xs) { - my ($dev,$rd,$regex,$hyst) = split ":", $h->{interruptable}; - - if (!$dev || !$rd || !defined $regex) { - return qq{A Device, Reading and Regex must be specified for the 'interruptable' key!}; + my ($dev, $rd, $code, $hyst); + + if ($h->{interruptable} =~ m/\{.*\}/xs) { # interruptable prüft Perl-Code + if ($h->{interruptable} =~ m/:\{.*\}:/xs) { + return qq{The Code specified for the 'interruptable' key must not end with a hysteresis value}; + } + + ($dev, $rd, $code) = split ":", $h->{interruptable}, 3; + } + else { + ($dev, $rd, $code, $hyst) = split ":", $h->{interruptable}; } - $err = checkRegex ($regex); - return "interruptable: $err" if($err); + if (!$dev || !$rd || !defined $code) { + return qq{A Device, Reading and Regex/Code must be specified for the 'interruptable' key!}; + } + + if ($code =~ m/^\s*\{.*\}\s*$/xs) { # interruptable prüft Perl-Code + $code =~ s/\s//xg; + ($err) = checkCode ($name, $code); + return "interruptable: $err" if($err); + } + else { # interruptable prüft Regex + $err = checkRegex ($code); + return "interruptable: $err" if($err); + } if ($hyst && !isNumeric ($hyst)) { return qq{The hysteresis of key "interruptable" must be a numeric value}; @@ -6122,17 +6172,31 @@ sub _attrconsumer { ## no critic "not used" } if (exists $h->{swoncond}) { # Check Regex - my (undef,undef,$regex) = split ":", $h->{swoncond}; + my (undef, undef, $code) = split ":", $h->{swoncond}, 3; - $err = checkRegex ($regex); - return "swoncond: $err" if($err); + if ($code =~ m/^\s*\{.*\}\s*$/xs) { # swoncond prüft Perl-Code + $code =~ s/\s//xg; + ($err) = checkCode ($name, $code); + return "swoncond: $err" if($err); + } + else { # swoncond prüft Regex + $err = checkRegex ($code); + return "swoncond: $err" if($err); + } } if (exists $h->{swoffcond}) { # Check Regex - my (undef,undef,$regex) = split ":", $h->{swoffcond}; + my (undef, undef, $code) = split ":", $h->{swoffcond}, 3; - $err = checkRegex ($regex); - return "swoffcond: $err" if($err); + if ($code =~ m/^\s*\{.*\}\s*$/xs) { # swoffcond prüft Perl-Code + $code =~ s/\s//xg; + ($err) = checkCode ($name, $code); + return "swoffcond: $err" if($err); + } + else { # swoffcond prüft Regex + $err = checkRegex ($code); + return "swoffcond: $err" if($err); + } } if (exists $h->{swstate}) { # Check Regex @@ -6168,9 +6232,10 @@ sub _attrconsumer { ## no critic "not used" my ($c) = $aName =~ /consumer([0-9]+)/xs; $paref->{c} = $c; - delConsumerFromMem ($paref); # Consumerdaten aus History löschen - - deleteReadingspec ($hash, "consumer${c}.*"); + delConsumerFromMem ($paref); # Consumerdaten aus Speicher löschen + delete $paref->{c}; + + deleteReadingspec ($hash, "consumer${c}.*"); } writeCacheToFile ($hash, 'consumers', $csmcache.$name); # Cache File Consumer schreiben @@ -6189,6 +6254,7 @@ return; sub _attrconsumerControl { ## no critic "not used" my $paref = shift; my $name = $paref->{name}; + my $aName = $paref->{aName}; my $aVal = $paref->{aVal}; my $cmd = $paref->{cmd}; @@ -6207,6 +6273,10 @@ sub _attrconsumerControl { ## no critic "not used" if ($cmd eq 'set') { for my $key (keys %{$h}) { + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + my $comp = $valid->{$key}{comp}; next if(!$comp); @@ -6312,57 +6382,44 @@ return; sub _attrflowGraphicControl { ## no critic "not used" my $paref = shift; my $name = $paref->{name}; + my $aName = $paref->{aName}; my $aVal = $paref->{aVal}; my $cmd = $paref->{cmd}; my $hash = $defs{$name}; - - for my $av ( qw( animate - consumerdist - h2consumerdist - homenodedyncol - shiftx - shifty - showconsumer - showconsumerremaintime - size - showconsumerdummy - showconsumerpower - strokeconsumerdyncol - strokeCmrRedColLimit - strokecolstd - strokecolsig - strokecolina - strokewidth - ) ) { - + + my $valid = { + animate => '(0|1)', + consumerdist => '[89]\d{1}|[1234]\d{2}|500', + h2consumerdist => '\d{1,3}', + homenodedyncol => '(0|1)', + shiftx => '-?[0-7]\d{0,1}|-?80', + shifty => '\d+', + size => '\d+', + showconsumer => '(0|1)', + showconsumerdummy => '(0|1)', + showconsumerremaintime => '(0|1)', + showconsumerpower => '(0|1)', + strokeconsumerdyncol => '(0|1)', + strokeCmrRedColLimit => '\d+', + strokecolstd => '.*', + strokecolsig => '.*', + strokecolina => '.*', + strokewidth => '\d+', + }; + + for my $av (keys %{$valid}) { delete $data{$name}{current}{$av}; } + + my ($a, $h) = parseParams ($aVal); if ($cmd eq 'set') { - my $valid = { - animate => '(0|1)', - consumerdist => '[89]\d{1}|[1234]\d{2}|500', - h2consumerdist => '\d{1,3}', - homenodedyncol => '(0|1)', - shiftx => '-?[0-7]\d{0,1}|-?80', - shifty => '\d+', - size => '\d+', - showconsumer => '(0|1)', - showconsumerdummy => '(0|1)', - showconsumerremaintime => '(0|1)', - showconsumerpower => '(0|1)', - strokeconsumerdyncol => '(0|1)', - strokeCmrRedColLimit => '\d+', - strokecolstd => '.*', - strokecolsig => '.*', - strokecolina => '.*', - strokewidth => '\d+', - }; - - my ($a, $h) = parseParams ($aVal); - for my $key (keys %{$h}) { + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + my $comp = $valid->{$key}; next if(!$comp); @@ -6384,6 +6441,7 @@ return; sub _attraiControl { ## no critic "not used" my $paref = shift; my $name = $paref->{name}; + my $aName = $paref->{aName}; my $aVal = $paref->{aVal}; my $cmd = $paref->{cmd}; @@ -6401,6 +6459,10 @@ sub _attraiControl { ## no critic "not used" if ($cmd eq 'set') { for my $key (keys %{$h}) { + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + my $comp = $valid->{$key}{comp}; next if(!$comp); @@ -6434,6 +6496,7 @@ return; sub _attrplantControl { ## no critic "not used" my $paref = shift; my $name = $paref->{name}; + my $aName = $paref->{aName}; my $aVal = $paref->{aVal}; my $cmd = $paref->{cmd}; @@ -6457,6 +6520,10 @@ sub _attrplantControl { ## no critic "not used" if ($cmd eq 'set') { for my $key (keys %{$h}) { + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + my $comp = $valid->{$key}{comp}; next if(!$comp); @@ -6497,10 +6564,26 @@ sub _attrMeterDev { ## no critic "not used" return if(!$init_done); my $hash = $defs{$name}; + + my $valid = { + gcon => '', + contotal => '', + gfeedin => '', + feedtotal => '', + conprice => '', + feedprice => '', + asynchron => '', + }; if ($paref->{cmd} eq 'set') { my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); + + for my $key (keys %{$h}) { + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + } if (!$h->{gcon} || !$h->{contotal} || !$h->{gfeedin} || !$h->{feedtotal}) { return qq{The syntax of '$aName' is not correct. Please consider the commandref.}; @@ -6560,10 +6643,22 @@ sub _attrProducerDev { ## no critic "not used" my $hash = $defs{$name}; my $pn = (split 'Producer', $aName)[1]; + + my $valid = { + icon => '', + pcurr => '', + etotal => '', + }; if ($paref->{cmd} eq 'set') { my ($err, $dev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); + + for my $key (keys %{$h}) { + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + } if (!$h->{pcurr} || !$h->{etotal}) { return qq{The syntax of '$aName' is not correct. Please consider the commandref.}; @@ -6605,10 +6700,27 @@ sub _attrInverterDev { ## no critic "not used" my $hash = $defs{$name}; my $in = (split 'setupInverterDev', $aName)[1]; + + my $valid = { + pv => '', + etotal => '', + capacity => '', + strings => '', + feed => '', + limit => '', + icon => '', + asynchron => '', + }; if ($paref->{cmd} eq 'set') { my ($err, $indev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); + + for my $key (keys %{$h}) { + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + } if ($in ne '01' && !AttrVal ($name, 'setupInverterDev01', '')) { return qq{Set the first Inverter device with attribute 'setupInverterDev01'}; @@ -6812,10 +6924,30 @@ sub _attrBatteryDev { ## no critic "not used" my $hash = $defs{$name}; my $bn = (split 'setupBatteryDev', $aName)[1]; + + my $valid = { + pin => '', + pout => '', + pinmax => '', + poutmax => '', + intotal => '', + outtotal => '', + cap => '', + charge => '', + icon => '', + show => '', + asynchron => '', + }; if ($paref->{cmd} eq 'set') { my ($err, $badev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); + + for my $key (keys %{$h}) { + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + } if (!$h->{pin} || !$h->{pout} || !$h->{cap}) { return qq{One or more of the keys 'pin, pout, cap' are missing. Please note the command reference.}; @@ -7304,12 +7436,12 @@ sub Shutdown { my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - writeCacheToFile ($hash, 'pvhist', $pvhcache.$name); # Cache File für PV History schreiben - writeCacheToFile ($hash, 'circular', $pvccache.$name); # Cache File für PV Circular schreiben - writeCacheToFile ($hash, 'consumers', $csmcache.$name); # Cache File Consumer schreiben - writeCacheToFile ($hash, 'solcastapi', $scpicache.$name); # Cache File SolCast API Werte schreiben - writeCacheToFile ($hash, 'statusapi', $statcache.$name); # Status-API Cache sichern - writeCacheToFile ($hash, 'weatherapi', $weathercache.$name); # Weather-API Cache sichern + writeCacheToFile ($hash, 'pvhist', $pvhcache.$name, 'nolog'); # Cache File für PV History schreiben + writeCacheToFile ($hash, 'circular', $pvccache.$name, 'nolog'); # Cache File für PV Circular schreiben + writeCacheToFile ($hash, 'consumers', $csmcache.$name, 'nolog'); # Cache File Consumer schreiben + writeCacheToFile ($hash, 'solcastapi', $scpicache.$name, 'nolog'); # Cache File SolCast API Werte schreiben + writeCacheToFile ($hash, 'statusapi', $statcache.$name, 'nolog'); # Status-API Cache sichern + writeCacheToFile ($hash, 'weatherapi', $weathercache.$name, 'nolog'); # Weather-API Cache sichern return; } @@ -7354,7 +7486,7 @@ sub Delete { my $err = FileDelete ($f); if ($err) { - Log3 ($name, 1, qq{$name - Message while deleting file "$f": $err}); + Log3 ($name, 1, qq{$name - ERROR deleting file $err}); } else { Log3 ($name, 3, qq{$name - INFO - File "$f" deleted.}); @@ -7453,13 +7585,12 @@ return; } ################################################################ -# Consumer Daten aus History löschen +# Consumer Daten aus Speicher löschen ################################################################ sub delConsumerFromMem { my $paref = shift; my $name = $paref->{name}; - my $type = $paref->{type}; - my $c = $paref->{c}; + my $c = $paref->{c} // return; my $hash = $defs{$name}; my $calias = ConsumerVal ($hash, $c, 'alias', ''); @@ -7481,7 +7612,7 @@ sub delConsumerFromMem { delete $data{$name}{consumers}{$c}; - Log3 ($name, 3, qq{$name - Consumer "$c - $calias" deleted from memory}); + Log3 ($name, 2, qq{$name - Consumer "$c - $calias" deleted from memory}); return; } @@ -7700,6 +7831,7 @@ sub writeCacheToFile { my $hash = shift; my $cachename = shift; my $file = shift; + my $nolog = shift // ''; my $name; if (ref $hash eq 'HASH') { @@ -7829,7 +7961,16 @@ sub writeCacheToFile { return ('', $nr, $na); } - return if(!keys %{$data{$name}{$cachename}}); + if (!keys %{$data{$name}{$cachename}}) { + my $err = FileDelete ($file); + + if ($err) { + Log3 ($name, 1, qq{$name - ERROR deleting file $err}) if(!$nolog); + } + + return; + } + push my @arr, encode_json ($data{$name}{$cachename}); $error = FileWrite ($file, @arr); @@ -8123,152 +8264,25 @@ sub centralTask { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ########################################################################################################################## - readingsDelete ($hash, '.migrated'); # 01.02.25 + #my $pcb = AttrVal ($name, 'affectBatteryPreferredCharge', undef); + #my $apc = AttrVal ($name, 'plantControl', ''); + + #if (defined $pcb) { + # my $newval = $apc." batteryPreferredCharge=$pcb"; + # CommandAttr (undef, "$name plantControl $newval"); + # ::CommandDeleteAttr (undef, "$name affectBatteryPreferredCharge"); + #} - my $pcb = AttrVal ($name, 'affectBatteryPreferredCharge', undef); # 22.03.2025 - my $apc = AttrVal ($name, 'plantControl', ''); - - if (defined $pcb) { - my $newval = $apc." batteryPreferredCharge=$pcb"; - CommandAttr (undef, "$name plantControl $newval"); - ::CommandDeleteAttr (undef, "$name affectBatteryPreferredCharge"); - } - ###### - - my $afp = AttrVal ($name, 'affectConsForecastInPlanning', undef); # 22.03.2025 - my $pc1 = AttrVal ($name, 'plantControl', ''); - - if (defined $afp) { - my $newval = $pc1." consForecastInPlanning=$afp"; - CommandAttr (undef, "$name plantControl $newval"); - ::CommandDeleteAttr (undef, "$name affectConsForecastInPlanning"); - } - ###### - - my $csl = AttrVal ($name, 'ctrlShowLink', undef); # 22.03.2025 - my $pc2 = AttrVal ($name, 'plantControl', ''); - - if (defined $csl) { - my $newval = $pc2." showLink=$csl"; - CommandAttr (undef, "$name plantControl $newval"); - ::CommandDeleteAttr (undef, "$name ctrlShowLink"); - } - ###### - - my $cbk = AttrVal ($name, 'ctrlBackupFilesKeep', undef); # 25.03.2025 - my $pc3 = AttrVal ($name, 'plantControl', ''); - - if (defined $cbk) { - my $newval = $pc3." backupFilesKeep=$cbk"; - CommandAttr (undef, "$name plantControl $newval"); - ::CommandDeleteAttr (undef, "$name ctrlBackupFilesKeep"); - } - ###### - - my $fiw = AttrVal ($name, 'affectConsForecastIdentWeekdays', undef); # 29.03.2025 - my $pc4 = AttrVal ($name, 'plantControl', ''); - - if (defined $fiw) { - my $newval = $pc4." consForecastIdentWeekdays=$fiw"; - CommandAttr (undef, "$name plantControl $newval"); - ::CommandDeleteAttr (undef, "$name affectConsForecastIdentWeekdays"); - } - ###### - - my $cfl = AttrVal ($name, 'affectConsForecastLastDays', undef); # 29.03.2025 - my $pc5 = AttrVal ($name, 'plantControl', ''); - - if (defined $cfl) { - my $newval = $pc5." consForecastLastDays=$cfl"; - CommandAttr (undef, "$name plantControl $newval"); - ::CommandDeleteAttr (undef, "$name affectConsForecastLastDays"); - } - ###### - - my $civ = AttrVal ($name, 'ctrlInterval', undef); # 31.03.2025 - my $pc6 = AttrVal ($name, 'plantControl', ''); - - if (defined $civ) { - my $newval = $pc6." cycleInterval=$civ"; - CommandAttr (undef, "$name plantControl $newval"); - ::CommandDeleteAttr (undef, "$name ctrlInterval"); - } - ###### - - my $cgd = AttrVal ($name, 'ctrlGenPVdeviation', undef); # 31.03.2025 - my $pc7 = AttrVal ($name, 'plantControl', ''); - - if (defined $cgd) { - my $newval = $pc7." genPVdeviation=$cgd"; - CommandAttr (undef, "$name plantControl $newval"); - ::CommandDeleteAttr (undef, "$name ctrlGenPVdeviation"); - } - ###### - - my $cai = AttrVal ($name, 'consumerAdviceIcon', undef); # 04.04.2025 - my $pc8 = AttrVal ($name, 'consumerControl', ''); - - if (defined $cai) { - my $newval = $pc8." adviceIcon=$cai"; - CommandAttr (undef, "$name consumerControl $newval"); - ::CommandDeleteAttr (undef, "$name consumerAdviceIcon"); - } - ###### - - my $cli = AttrVal ($name, 'consumerLink', undef); # 04.04.2025 - my $pc9 = AttrVal ($name, 'consumerControl', ''); - - if (defined $cli) { - my $newval = $pc9." detailLink=$cli"; - CommandAttr (undef, "$name consumerControl $newval"); - ::CommandDeleteAttr (undef, "$name consumerLink"); - } - ###### - - my $cle = AttrVal ($name, 'consumerLegend', undef); # 05.04.2025 - my $pc10 = AttrVal ($name, 'consumerControl', ''); - - if (defined $cle) { - my $newval = $pc10." showLegend=$cle"; - CommandAttr (undef, "$name consumerControl $newval"); - ::CommandDeleteAttr (undef, "$name consumerLegend"); - } - ###### - - my $n = 0; # 01.02.25 -> Datenmigration pvrlsum, pvfcsum, dnumsum in pvrl_*, pvfc_* - for my $hh (1..24) { - $hh = sprintf "%02d", $hh; - - for my $cul (sort keys %{$data{$name}{circular}{$hh}}) { - next if($cul ne 'dnumsum'); - - for my $dns (sort keys %{$data{$name}{circular}{$hh}{$cul}}) { - next if($dns eq 'simple'); - - my ($sabin, $crang) = split /\./, $dns; - my ($pvsum, $fcsum, $dnum) = CircularSumVal ($hash, $hh, $sabin, $crang, undef); - - delete $data{$name}{circular}{$hh}{pvrlsum}{$dns}; - delete $data{$name}{circular}{$hh}{pvfcsum}{$dns}; - delete $data{$name}{circular}{$hh}{dnumsum}{$dns}; - - next if(!defined $pvsum || !defined $fcsum || !$dnum); - - my $pvavg = sprintf "%.0f", ($pvsum / $dnum); - my $fcavg = sprintf "%.0f", ($fcsum / $dnum); - - push @{$data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}}, $pvavg; - push @{$data{$name}{circular}{$hh}{'pvfc_'.$sabin}{"$crang"}}, $fcavg; - - $n++; + if (CurrentVal ($hash, 'consumerCollected', 0)) { + for my $c (1..MAXCONSUMER) { # 19.04.2025 + $c = sprintf "%02d", $c; + if (defined $data{$name}{consumers} && defined $data{$name}{consumers}{$c}) { + delete $data{$name}{consumers}{$c}{swoncondregex} if(exists $data{$name}{consumers}{$c}{swoncondregex}); + delete $data{$name}{consumers}{$c}{swoffcondregex} if(exists $data{$name}{consumers}{$c}{swoffcondregex}); } } } - - if ($n) { - Log3 ($name, 1, "$name - NOTE - the stored PV real and forecast datasets (quantity: $n) were migrated to the new module structure"); - } - + #Log3 ($name, 1, "$name - Consumers: ".Dumper $data{$name}{consumers}); ########################################################################################################################## if (!CurrentVal ($hash, 'allStringsFullfilled', 0)) { # die String Konfiguration erstellen wenn noch nicht erfolgreich ausgeführt @@ -8589,24 +8603,24 @@ sub _collectAllRegConsumers { $exconfc = $hc->{exconfc}; } - my ($rswstate,$onreg,$offreg); + my ($rswstate, $onreg, $offreg); if(exists $hc->{swstate}) { - ($rswstate,$onreg,$offreg) = split ":", $hc->{swstate}; + ($rswstate, $onreg, $offreg) = split ":", $hc->{swstate}, 3; } - my ($dswoncond,$rswoncond,$swoncondregex); + my ($dswoncond, $rswoncond, $swoncondition); if (exists $hc->{swoncond}) { # zusätzliche Einschaltbedingung - ($dswoncond,$rswoncond,$swoncondregex) = split ":", $hc->{swoncond}; + ($dswoncond, $rswoncond, $swoncondition) = split ":", $hc->{swoncond}, 3; } - my ($dswoffcond,$rswoffcond,$swoffcondregex); + my ($dswoffcond, $rswoffcond, $swoffcondition); if (exists $hc->{swoffcond}) { # vorrangige Ausschaltbedingung - ($dswoffcond,$rswoffcond,$swoffcondregex) = split ":", $hc->{swoffcond}; + ($dswoffcond, $rswoffcond, $swoffcondition) = split ":", $hc->{swoffcond}, 3; } - my ($dspignorecond,$rigncond,$spignorecondregex); + my ($dspignorecond, $rigncond, $spignorecondregex); if (exists $hc->{spignorecond}) { # Bedingung um vorhandenen PV Überschuß zu ignorieren - ($dspignorecond,$rigncond,$spignorecondregex) = split ":", $hc->{spignorecond}; + ($dspignorecond, $rigncond, $spignorecondregex) = split ":", $hc->{spignorecond}, 3; } my $interruptable = 0; @@ -8615,8 +8629,16 @@ sub _collectAllRegConsumers { $interruptable = $hc->{interruptable}; if ($interruptable ne '1') { - (my $dv, my $rd, my $reg, $hyst) = split ':', $interruptable; - $interruptable = "$dv:$rd:$reg"; + my ($dv, $rd, $code); + + if ($interruptable =~ m/:\{.*\}/xs) { # interruptable prüft Perl-Code + ($dv, $rd, $code) = split ":", $interruptable, 3; + } + else { + ($dv, $rd, $code, $hyst) = split ":", $interruptable; + } + + $interruptable = "$dv:$rd:$code"; } } @@ -8675,10 +8697,10 @@ sub _collectAllRegConsumers { $data{$name}{consumers}{$c}{offreg} = $offreg // 'off'; # Regex für 'aus' $data{$name}{consumers}{$c}{dswoncond} = $dswoncond // q{}; # Device zur Lieferung einer zusätzliche Einschaltbedingung $data{$name}{consumers}{$c}{rswoncond} = $rswoncond // q{}; # Reading zur Lieferung einer zusätzliche Einschaltbedingung - $data{$name}{consumers}{$c}{swoncondregex} = $swoncondregex // q{}; # Regex einer zusätzliche Einschaltbedingung + $data{$name}{consumers}{$c}{swoncondition} = $swoncondition // q{}; # Regex einer zusätzliche Einschaltbedingung $data{$name}{consumers}{$c}{dswoffcond} = $dswoffcond // q{}; # Device zur Lieferung einer vorrangigen Ausschaltbedingung $data{$name}{consumers}{$c}{rswoffcond} = $rswoffcond // q{}; # Reading zur Lieferung einer vorrangigen Ausschaltbedingung - $data{$name}{consumers}{$c}{swoffcondregex} = $swoffcondregex // q{}; # Regex einer vorrangigen Ausschaltbedingung + $data{$name}{consumers}{$c}{swoffcondition} = $swoffcondition // q{}; # Regex einer vorrangigen Ausschaltbedingung $data{$name}{consumers}{$c}{dspignorecond} = $dspignorecond // q{}; # Device liefert Ignore Bedingung $data{$name}{consumers}{$c}{rigncond} = $rigncond // q{}; # Reading liefert Ignore Bedingung $data{$name}{consumers}{$c}{spignorecondregex} = $spignorecondregex // q{}; # Regex der Ignore Bedingung @@ -8919,12 +8941,6 @@ sub __deleteEveryHourControls { for my $n (0..24) { $n = sprintf "%02d", $n; - ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! - ########################################################################################################################## - readingsDelete ($hash, ".pvCorrectionFactor_${n}_cloudcover"); # 01.02.2025 - readingsDelete ($hash, ".pvCorrectionFactor_${n}_apipercentil"); - ########################################################################################################################## - readingsDelete ($hash, ".signaldone_${n}"); if (ReadingsVal ($name, 'pvCorrectionFactor_Auto', 'off') =~ /on/xs) { @@ -10308,8 +10324,8 @@ sub _transferBatteryValues { my ($bout,$boutunit) = split ":", $h->{outtotal} // "-:-"; # Readingname/Unit der total aus der Batterie entnommenen Energie (Zähler) my $batchr = $h->{charge} // ''; # Readingname Ladezustand Batterie my $instcap = $h->{cap}; # numerischer Wert (Wh) oder Readingname installierte Batteriekapazität - my $pinmax = $h->{pinmax} // INFINIITY; # max. mögliche Ladeleistung - my $poutmax = $h->{poutmax} // INFINIITY; # max. mögliche Entladeleistung + my $pinmax = $h->{pinmax} // INFINITE; # max. mögliche Ladeleistung + my $poutmax = $h->{poutmax} // INFINITE; # max. mögliche Entladeleistung return if(!$pin || !$pou); @@ -10770,7 +10786,7 @@ sub _batChargeRecmd { my $hash = $defs{$name}; my $pvCu = ReadingsNum ($name, 'Current_PV', 0); # aktuelle PV Erzeugung my $curcon = ReadingsNum ($name, 'Current_Consumption', 0); # aktueller Verbrauch - my $feedinlim = CurrentVal ($name, 'feedinPowerLimit', INFINIITY); # Einspeiselimit in W + my $feedinlim = CurrentVal ($name, 'feedinPowerLimit', INFINITE); # Einspeiselimit in W my $bpin = CurrentVal ($name, 'batpowerinsum', 0); # aktuelle Batterie Ladeleistung (Summe über alle Batterien) my $gfeedin = CurrentVal ($name, 'gridfeedin', 0); # aktuelle Netzeinspeisung my $inplim = 0; @@ -10817,8 +10833,8 @@ sub _batChargeRecmd { my $batoptsoc = ReadingsNum ($name, 'Battery_OptimumTargetSoC_'.$bn, 0); # aktueller optimierter SoC my $confcss = CurrentVal ($name, 'tdConFcTillSunset', 0); # Verbrauchsprognose bis Sonnenuntergang my $csoc = BatteryVal ($hash, $bn, 'bcharge', 0); # aktuelle Ladung in % - my $bpinmax = BatteryVal ($hash, $bn, 'bpinmax', INFINIITY); # max. mögliche Ladeleistung W - my $bpoutmax = BatteryVal ($hash, $bn, 'bpoutmax', INFINIITY); # max. mögliche Entladeleistung W + my $bpinmax = BatteryVal ($hash, $bn, 'bpinmax', INFINITE); # max. mögliche Ladeleistung W + my $bpoutmax = BatteryVal ($hash, $bn, 'bpoutmax', INFINITE); # max. mögliche Entladeleistung W my $cgbt = AttrVal ($name, 'ctrlBatSocManagement'.$bn, undef); my $sf = __batCapShareFactor ($hash, $bn); # Anteilsfaktor der Batterie XX Kapazität an Gesamtkapazität my $lowSoc = 0; @@ -12226,12 +12242,12 @@ sub ___switchConsumerOn { my $simpCstat = simplifyCstate ($pstate); my $isInTime = isInTimeframe ($hash, $c); - my ($swoncond,$swoffcond,$infon,$infoff); + my ($swoncond, $swoffcond, $infon, $infoff); - ($swoncond,$infon,$err) = isAddSwitchOnCond ($hash, $c); # zusätzliche Switch on Bedingung + ($swoncond, $infon, $err) = isAddSwitchOnCond ($hash, $c); # zusätzliche Switch on Bedingung Log3 ($name, 1, "$name - $err") if($err); - ($swoffcond,$infoff,$err) = isAddSwitchOffCond ($hash, $c); # zusätzliche Switch off Bedingung + ($swoffcond, $infoff, $err) = isAddSwitchOffCond ($hash, $c); # zusätzliche Switch off Bedingung Log3 ($name, 1, "$name - $err") if($err); my ($iilt,$rlt) = isInLocktime ($paref); # Sperrzeit Status ermitteln @@ -12249,7 +12265,7 @@ 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" - 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" - device '$dswname' is used as switching device}); @@ -15034,10 +15050,14 @@ sub ___getFWwidget { if ($allc =~ /\s$elm:?(.*?)\s/xs) { # Element in allen Sets oder Attr enthalten my $arg = $1; - if (!$arg || $arg eq 'textField' || $arg eq 'textField-long') { # Label (Reading) ausblenden -> siehe fhemweb.js function FW_createTextField Zeile 1657 + if (!$arg || $arg eq 'textField') { # Label (Reading) ausblenden -> siehe fhemweb.js function FW_createTextField Zeile 1657 $arg = 'textFieldNL'; } + if ($arg eq 'textField-long') { # Label (Reading) ausblenden -> siehe fhemweb.js function FW_createTextField Zeile 1657 + $arg = 'textFieldNL-long'; + } + if ($arg !~ /^\#/xs && $arg !~ /^$allwidgets/xs) { $arg = '#,'.$arg; } @@ -15100,7 +15120,7 @@ sub ___widgetFallback { my $current = ReadingsVal ($name, $reading, undef); if (!defined $current) { - $reading = 'state'; + $reading = ' '; $current = ' '; } @@ -15111,8 +15131,7 @@ sub ___widgetFallback { $current =~ s/$elm //; $current = ReplaceEventMap ($dev, $current, 1); - return "
"; + return "
"; } ################################################################ @@ -21306,7 +21325,9 @@ sub isAddSwitchOnCond { my $hash = shift; my $c = shift; + my $name = $hash->{NAME}; my $info = q{}; + my $swon = 0; my $dswoncond = ConsumerVal ($hash, $c, 'dswoncond', ''); # Device zur Lieferung einer zusätzlichen Einschaltbedingung my ($err) = isDeviceValid ( { name => $hash->{NAME}, @@ -21317,21 +21338,42 @@ sub isAddSwitchOnCond { if ($dswoncond && $err) { $err = qq{ERROR - the device "$dswoncond" doesn't exist! Check the key "swoncond" in attribute "consumer${c}"}; - return (0, $info, $err); + return ($swon, $info, $err); } - $err = q{}; - my $rswoncond = ConsumerVal ($hash, $c, 'rswoncond', ''); # Reading zur Lieferung einer zusätzlichen Einschaltbedingung - my $swoncondregex = ConsumerVal ($hash, $c, 'swoncondregex', ''); # Regex einer zusätzliche Einschaltbedingung - my $condval = ReadingsVal ($dswoncond, $rswoncond, ''); # Wert zum Vergleich mit Regex + $err = q{}; + my $rswoncond = ConsumerVal ($hash, $c, 'rswoncond', ''); # Reading zur Lieferung einer zusätzlichen Einschaltbedingung + my $swoncode = ConsumerVal ($hash, $c, 'swoncondition', ''); # Regex einer zusätzliche Einschaltbedingung + my $condval = ReadingsVal ($dswoncond, $rswoncond, ''); # Wert zum Vergleich mit Regex - if ($condval =~ m/^$swoncondregex$/x) { - return (1, $info, $err); + if ($swoncode =~ m/^\{.*\}$/xs) { # wertet Perl-Code aus + my $VALUE = $condval; + my $true = eval $swoncode; + + if ($@) { + Log3 ($name, 1, "$name - ERROR in swoncond Code execution: ".$@); + } + + if ($true) { + $info = qq{the value “$condval” resulted in 'true' after exec "$swoncode" \n}; + $info .= "-> Check successful "; + $swon = 1; + } + else { + $info = qq{the value “$condval” resulted in 'false' after exec "$swoncode" \n}; + $swon = 0; + } + } + elsif ($condval =~ m/^$swoncode$/x) { # wertet Regex aus + $info = qq{value "$condval" matches the Regex "$swoncode" \n}; + $info .= "-> Check successful "; + $swon = 1; + } + else { + $info = qq{The device "$dswoncond", reading "$rswoncond" doesn't match the condition "$swoncode"}; } - $info = qq{The device "$dswoncond", reading "$rswoncond" doesn't match the Regex "$swoncondregex"}; - -return (0, $info, $err); +return ($swon, $info, $err); } ################################################################ @@ -21351,22 +21393,23 @@ sub isAddSwitchOffCond { my $cond = shift // q{}; my $hyst = shift // 0; # Hysterese - my $swoff = 0; - my $info = q{}; - my $dswoffcond = q{}; # Device zur Lieferung einer Ausschaltbedingung - my $rswoffcond = q{}; # Reading zur Lieferung einer Ausschaltbedingung - my $swoffcondregex = q{}; # Regex der Ausschaltbedingung (wenn wahr) + my $name = $hash->{NAME}; + my $swoff = 0; + my $info = q{}; + my $dswoffcond = q{}; # Device zur Lieferung einer Ausschaltbedingung + my $rswoffcond = q{}; # Reading zur Lieferung einer Ausschaltbedingung + my $swoffcode = q{}; # Code/Regex der Ausschaltbedingung (wenn wahr) if ($cond) { - ($dswoffcond, $rswoffcond, $swoffcondregex) = split ":", $cond; + ($dswoffcond, $rswoffcond, $swoffcode) = split ":", $cond, 3; } else { - $dswoffcond = ConsumerVal ($hash, $c, 'dswoffcond', ''); - $rswoffcond = ConsumerVal ($hash, $c, 'rswoffcond', ''); - $swoffcondregex = ConsumerVal ($hash, $c, 'swoffcondregex', ''); + $dswoffcond = ConsumerVal ($hash, $c, 'dswoffcond', ''); + $rswoffcond = ConsumerVal ($hash, $c, 'rswoffcond', ''); + $swoffcode = ConsumerVal ($hash, $c, 'swoffcondition', ''); } - my ($err) = isDeviceValid ( { name => $hash->{NAME}, obj => $dswoffcond, method => 'string' } ); + my ($err) = isDeviceValid ( { name => $name, obj => $dswoffcond, method => 'string' } ); if ($dswoffcond && $err) { $err = qq{ERROR - the device "$dswoffcond" doesn't exist! Check the key "swoffcond" or "interruptable" in attribute "consumer${c}"}; @@ -21377,26 +21420,44 @@ sub isAddSwitchOffCond { my $condval = ReadingsVal ($dswoffcond, $rswoffcond, undef); if (defined $condval) { - if ($condval =~ m/^$swoffcondregex$/x) { - $info = qq{value "$condval" matches the Regex "$swoffcondregex" \n}; + if ($swoffcode =~ m/^\{.*\}$/xs) { # wertet Perl-Code aus + my $VALUE = $condval; + my $true = eval $swoffcode; + + if ($@) { + Log3 ($name, 1, "$name - ERROR in interruptable or swoffcond Code execution: ".$@); + } + + if ($true) { + $info = qq{the 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}; + $swoff = 0; + } + } + elsif ($condval =~ m/^$swoffcode$/x) { # wertet Regex aus + $info = qq{value "$condval" matches the Regex "$swoffcode" \n}; $info .= "-> Check successful "; $swoff = 1; } else { - $info = qq{value "$condval" doesn't match the Regex "$swoffcondregex" \n}; + $info = qq{value "$condval" doesn't match the Regex "$swoffcode" \n}; $swoff = 0; } if ($hyst && isNumeric ($condval)) { # Hysterese berücksichtigen $condval -= $hyst; - if ($condval =~ m/^$swoffcondregex$/x) { - $info = qq{value "$condval" (included hysteresis = $hyst) matches the Regex "$swoffcondregex" \n}; + if ($condval =~ m/^$swoffcode$/x) { + $info = qq{value "$condval" (included hysteresis = $hyst) matches the Regex "$swoffcode" \n}; $info .= "-> Check successful "; $swoff = 1; } else { - $info = qq{device: "$dswoffcond", reading: "$rswoffcond" , value: "$condval" (included hysteresis = $hyst) doesn't match Regex: "$swoffcondregex" \n}; + $info = qq{device: "$dswoffcond", reading: "$rswoffcond" , value: "$condval" (included hysteresis = $hyst) doesn't match Regex: "$swoffcode" \n}; $swoff = 0; } } @@ -21533,7 +21594,7 @@ return $valid; } ################################################################ -# ist Consumer $c unterbrechbar (1|2) oder nicht (0|3) +# ist Consumer $c unterbrechbar (1|2) oder nicht (2|3) ################################################################ sub isInterruptable { my $hash = shift; @@ -21551,7 +21612,7 @@ sub isInterruptable { return 1; } - my ($swoffcond,$info,$err) = isAddSwitchOffCond ($hash, $c, $intable, $hyst); + my ($swoffcond, $info, $err) = isAddSwitchOffCond ($hash, $c, $intable, $hyst); Log3 ($name, 1, "$name - $err") if($err); my $debug = getDebug ($hash); # Debug Module @@ -22151,7 +22212,7 @@ sub getCDnames { my $cname = ConsumerVal ($hash, $c, "name", ""); # Name des Consumerdevices my $dswname = ConsumerVal ($hash, $c, 'dswitch', $cname); # alternatives Switch Device my ($err) = isDeviceValid ( { name => $hash->{NAME}, obj => $dswname, method => 'string' } ); - $err = qq{$err Please check device names in consumer '$c' attribute} if($err); + $err = qq{$err. Please check device names in consumer '$c' attribute} if($err); return ($err, $cname, $dswname); } @@ -22957,10 +23018,10 @@ return $def; # planswitchoff - geplante Switch-Off Zeit # planSupplement - Ergänzung zum Planungsstatus # rswoncond - Reading zur Lieferung einer zusätzliche Einschaltbedingung -# swoncondregex - Regex einer zusätzliche Einschaltbedingung +# swoncondition - Regex einer zusätzliche Einschaltbedingung # dswoffcond - Device zur Lieferung einer vorrangige Ausschaltbedingung # rswoffcond - Reading zur Lieferung einer vorrangige Ausschaltbedingung -# swoffcondregex - Regex einer einer vorrangige Ausschaltbedingung +# swoffcondition - Regex einer einer vorrangige Ausschaltbedingung # isIntimeframe - ist Zeit innerhalb der Planzeit ein/aus # interruptable - Consumer "on" ist während geplanter "ein"-Zeit unterbrechbar # lastAutoOnTs - Timestamp des letzten On-Schaltens bzw. letzter Fortsetzung (nur Automatik-Modus) @@ -24360,8 +24421,8 @@ to ensure that the system configuration is correct. [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>]]
- [swoncond=<Device>:<Reading>:<Regex>] [swoffcond=<Device>:<Reading>:<Regex>] [spignorecond=<Device>:<Reading>:<Regex>]
- [surpmeth=<Option>] [interruptable=<Option>] [noshow=<Option>] [exconfc=<Option>]
+ [swoncond=<Device>:<Reading>:<Condition>] [swoffcond=<Device>:<Reading>:<Condition>]
+ [spignorecond=<Device>:<Reading>:<Regex>] [surpmeth=<Option>] [interruptable=<Option>] [noshow=<Option>] [exconfc=<Option>]

Registers a consumer <Device> with the SolarForecast Device. An optional alias can be specified.
@@ -24482,12 +24543,17 @@ to ensure that the system configuration is correct. 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 - Regex - regular expression that must be satisfied for a 'true' condition to be true + 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. 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 - Regex - regular expression that must be satisfied for a 'true' condition to be true + 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. 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) @@ -24503,13 +24569,19 @@ to ensure that the system configuration is correct. Reading - Reading which contains the condition Regex - regular expression that must be satisfied for a 'true' condition to be true - interruptable defines the possible interruption options for the consumer after it has been started (optional) + 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) 1 - Load is temporarily switched off if the PV surplus falls below the required energy - Device:Reading:Regex[:Hysteresis] - Load is temporarily interrupted if the value of the specified - Device:Readings match on the regex or if is insufficient PV surplus (if power not equal to 0). - If the value no longer matches, the interrupted load is switched on again if there is sufficient - PV surplus provided (if power is not 0). + + Device:Reading:{Perl-Code} - Load is temporarily interrupted if the Perl code returns 'true' or insufficient + PV surplus (if power is not equal to 0) and is switched on again if the Perl code returns 'false' and PV surplus + (if power is not equal to 0). The value of Device:Reading is passed to the code with the variable $VALUE. + The code must be enclosed in {..} and must not contain any spaces. + + Device:Reading:Regex[:Hysteresis] - Load is temporarily interrupted when the value of the specified + Device:Readings on the Regex matched or there is insufficient PV surplus (if power is not equal to 0). + The interrupted load is switched on again when the value is no longer matched and there is sufficient PV surplus + is present (if power is not equal to 0). If the optional hysteresis is specified, the hysteresis value is subtracted from the reading value and the regex is then applied. If this and the original reading value match, the consumer is temporarily interrupted. The consumer is continued if both the original and the subtracted readings value do not (or no longer) match. @@ -24541,11 +24613,11 @@ to ensure that the system configuration is correct. Examples:
attr <name> consumer01 wallplug icon=scene_dishwasher@orange type=dishwasher mode=can power=2500 on=on off=off notafter=20 etotal=total:kWh:5
attr <name> consumer02 WPxw type=heater mode=can power=3000 mintime=180 on="on-for-timer 3600" notafter=12 auto=automatic
- attr <name> consumer03 Shelly.shellyplug2 type=other power=300 mode=must icon=it_ups_on_battery mintime=120 on=on off=off swstate=state:on:off auto=automatic pcurr=relay_0_power:W etotal:relay_0_energy_Wh:Wh swoncond=EcoFlow:data_data_socSum:-?([1-7][0-9]|[0-9]) swoffcond:EcoFlow:data_data_socSum:100
- 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:05 notafter={return'20:05'} auto=automatic pcurr=actpow:W on=on off=off mintime=SunPath:60:-120 interruptable=1
- 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=1
+ attr <name> consumer03 Shelly.shellyplug2 type=other power=300 mode=must icon=it_ups_on_battery mintime=120 on=on off=off swstate=state:on:off auto=automatic pcurr=relay_0_power:W etotal:relay_0_energy_Wh:Wh swoncond=EcoFlow:data_data_socSum:-?([1-7][0-9]|[0-9]) swoffcond:EcoFlow:data_data_socSum:{$VALUE==100?1:0}
+ 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:05 notafter={return'20:05'} auto=automatic pcurr=actpow:W on=on off=off mintime=SunPath:60:-120 interruptable=1
+ 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=1

@@ -25226,7 +25298,7 @@ to ensure that the system configuration is correct. @@ -26863,8 +26935,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. [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>]]
- [swoncond=<Device>:<Reading>:<Regex>] [swoffcond=<Device>:<Reading>:<Regex>] [spignorecond=<Device>:<Reading>:<Regex>]
- [surpmeth=<Option>] [interruptable=<Option>] [noshow=<Option>] [exconfc=<Option>]
+ [swoncond=<Device>:<Reading>:<Bedingung>] [swoffcond=<Device>:<Reading>:<Bedingung>]
+ [spignorecond=<Device>:<Reading>:<Regex>] [surpmeth=<Option>] [interruptable=<Option>] [noshow=<Option>] [exconfc=<Option>]

Registriert einen Verbraucher <Device> beim SolarForecast Device. Ein optionaler Alias kann angegeben werden.
@@ -26981,15 +27053,21 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. etotal Reading:Einheit (Wh/kWh) des Consumer Device, welches die Summe der verbrauchten Energie liefert (optional) :<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 Verbraucher einzuschalten (optional). Der geplante Zyklus wird gestartet. + 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. 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. 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) @@ -27005,13 +27083,19 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Reading - Reading welches die Bedingung enthält Regex - regulärer Ausdruck der für eine 'wahre' Bedingung erfüllt sein muß - interruptable definiert die möglichen Unterbrechungsoptionen für den Verbraucher nachdem er gestartet wurde (optional) + 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) 1 - Verbraucher wird temporär ausgeschaltet falls der PV Überschuß die benötigte Energie unterschreitet - Device:Reading:Regex[:Hysterese] - Verbraucher wird temporär unterbrochen wenn der Wert des angegebenen - Device:Readings auf den Regex matched oder unzureichender PV Überschuß (wenn power ungleich 0) vorliegt. - Matched der Wert nicht mehr, wird der unterbrochene Verbraucher wieder eingeschaltet sofern ausreichender - PV Überschuß (wenn power ungleich 0) vorliegt. + + Device:Reading:{Perl-Code} - Verbraucher wird temporär unterbrochen, wenn der Perl-Code 'wahr' zurückgibt oder unzureichender + PV Überschuß (wenn power ungleich 0) vorliegt und wird wieder eingeschaltet, wenn der Perl-Code 'falsch' zurückgibt und PV Überschuß + (wenn power ungleich 0) vorliegt. Der Wert von Device:Reading wird dem Code mit der Variable $VALUE übergeben. + Der Code ist in {..} einzuschließen und darf keine Leerzeichen enthalten. + + Device:Reading:Regex[:Hysterese] - Verbraucher wird temporär unterbrochen, wenn der Wert des angegebenen + Device:Readings auf den Regex matched oder unzureichender PV Überschuß (wenn power ungleich 0) vorliegt. + Der unterbrochene Verbraucher wird wieder eingeschaltet, wenn der Wert nicht mehr matched und ausreichender PV Überschuß + (wenn power ungleich 0) vorliegt. Ist die optionale Hysterese angegeben, wird der Hysteresewert vom Readingswert subtrahiert und danach der Regex angewendet. Matched dieser und der originale Readingswert, wird der Verbraucher temporär unterbrochen. Der Verbraucher wird fortgesetzt, wenn sowohl der originale als auch der substrahierte Readingswert nicht (mehr) matchen. @@ -27043,7 +27127,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Beispiele:
attr <name> consumer01 wallplug icon=scene_dishwasher@orange type=dishwasher mode=can power=2500 on=on off=off notafter=20 etotal=total:kWh:5
attr <name> consumer02 WPxw type=heater mode=can power=3000 mintime=180 on="on-for-timer 3600" notafter=12 auto=automatic
- attr <name> consumer03 Shelly.shellyplug2 type=other power=300 mode=must icon=it_ups_on_battery mintime=120 on=on off=off swstate=state:on:off auto=automatic pcurr=relay_0_power:W etotal:relay_0_energy_Wh:Wh swoncond=EcoFlow:data_data_socSum:-?([1-7][0-9]|[0-9]) swoffcond:EcoFlow:data_data_socSum:100
+ attr <name> consumer03 Shelly.shellyplug2 type=other power=300 mode=must icon=it_ups_on_battery mintime=120 on=on off=off swstate=state:on:off auto=automatic pcurr=relay_0_power:W etotal:relay_0_energy_Wh:Wh swoncond=EcoFlow:data_data_socSum:-?([1-7][0-9]|[0-9]) swoffcond:EcoFlow:data_data_socSum:{$VALUE==100?1:0}
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