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