diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 100df1d14..49726d3d4 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -120,6 +120,9 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "0.67.4 "=> "28.08.2022 ___switchConsumerOn -> no switch on if additional switch off condition is true ". + "__setConsRcmdState -> Consumer can be switched on in case of missing PV power if key power=0 is set ". + "new process and additional split for hysteresis ", "0.67.3 "=> "22.08.2022 show cloudcover in weather __weatherOnBeam ", "0.67.2 "=> "11.08.2022 fix no disabled Link after restart and disable=1 ", "0.67.1 "=> "10.08.2022 fix warning, Forum: https://forum.fhem.de/index.php/topic,117864.msg1231050.html#msg1231050 ", @@ -1762,6 +1765,13 @@ sub _attrconsumer { ## no critic "not used" if($h->{mode} && $h->{mode} !~ /^(?:can|must)$/xs) { return qq{The mode "$h->{mode}" isn't allowed!} } + + if($h->{interruptable}) { # Check Hysterese auf numeric + my (undef,undef,undef,$hyst) = split ":", $h->{interruptable}; + if ($hyst && !IsNumeric ($hyst)) { + return qq{The hysteresis of key "interruptable" must be a numeric value like "0.5" or "2"}; + } + } } else { my $day = strftime "%d", localtime(time); # aktueller Tag (range 01 to 31) @@ -3181,7 +3191,7 @@ sub __planSwitchTimes { } if($debug) { # nur für Debugging - Log (1, "DEBUG> $name - consumer: $c, epiece1: $epiece1"); + Log (1, qq{DEBUG> $name consumer "$c" - epiece1: $epiece1}); } my $mode = ConsumerVal ($hash, $c, "mode", "can"); @@ -3195,9 +3205,9 @@ sub __planSwitchTimes { if($mode eq "can") { # Verbraucher kann geplant werden if($debug) { # nur für Debugging - Log (1, "DEBUG> $name - consumer: $c, mode: $mode, relevant hash: mtimes"); + Log (1, qq{DEBUG> $name consumer "$c" - mode: $mode, relevant hash: mtimes}); for my $m (sort{$a<=>$b} keys %mtimes) { - Log (1, "DEBUG> $name - hash: mtimes, surplus: $mtimes{$m}{surplus}, starttime: $mtimes{$m}{starttime}, nexthour: $mtimes{$m}{nexthour}, today: $mtimes{$m}{today}"); + Log (1, qq{DEBUG> $name consumer "$c" - hash: mtimes, surplus: $mtimes{$m}{surplus}, starttime: $mtimes{$m}{starttime}, nexthour: $mtimes{$m}{nexthour}, today: $mtimes{$m}{today}}); } } @@ -3234,9 +3244,9 @@ sub __planSwitchTimes { } else { # Verbraucher _muß_ geplant werden if($debug) { # nur für Debugging - Log (1, "DEBUG> $name - consumer: $c, mode: $mode, relevant hash: max"); + Log (1, qq{DEBUG> $name consumer "$c" - mode: $mode, relevant hash: max}); for my $o (sort{$a<=>$b} keys %max) { - Log (1, "DEBUG> $name - hash: max, surplus: $max{$o}{surplus}, starttime: $max{$o}{starttime}, nexthour: $max{$o}{nexthour}, today: $max{$o}{today}"); + Log (1, qq{DEBUG> $name consumer "$c" - hash: max, surplus: $max{$o}{surplus}, starttime: $max{$o}{starttime}, nexthour: $max{$o}{nexthour}, today: $max{$o}{today}}); } } @@ -3430,7 +3440,7 @@ sub __setConsRcmdState { my $nompower = ConsumerVal ($hash, $c, "power", 0); # Consumer nominale Leistungsaufnahme (W) my $ccr = AttrVal ($name, 'createConsumptionRecReadings', ''); # Liste der Consumer für die ConsumptionRecommended-Readings erstellt werden sollen - if ($surplus >= $nompower) { + if (!$nompower || $surplus >= $nompower) { $data{$type}{$name}{consumers}{$c}{isConsumptionRecommended} = 1; # Einschalten des Consumers günstig } else { @@ -3485,19 +3495,25 @@ sub ___switchConsumerOn { my $cname = ConsumerVal ($hash, $c, "name", ""); # Consumer Device Name my $calias = ConsumerVal ($hash, $c, "alias", ""); # Consumer Device Alias - my ($swoncond,$info,$err) = isAddSwitchOnCond ($hash, $c); # zusätzliche Switch on Bedingung - + my ($swoncond,$swoffcond,$info,$err); + ($swoncond,$info,$err) = isAddSwitchOnCond ($hash, $c); # zusätzliche Switch on Bedingung + Log3 ($name, 1, "$name - $err") if($err); + + ($swoffcond,$info,$err) = isAddSwitchOffCond ($hash, $c); # zusätzliche Switch off Bedingung Log3 ($name, 1, "$name - $err") if($err); if ($debug) { # nur für Debugging - Log (1, qq{DEBUG> $name - Parameters for switch on decision consumer "$c": }. - qq{swoncond: $swoncond, auto mode: $auto, on-command: $oncom, }. + Log (1, qq{DEBUG> $name consumer "$c" - general switching parameters: }. + qq{auto mode: $auto, }. qq{planning state: $pstate, start timestamp: }.($startts ? $startts : "undef").", ". qq{timestamp: $t} - ); + ); + Log (1, qq{DEBUG> $name consumer "$c" - Context of switching "on": }. + qq{swoncond: $swoncond, on-command: $oncom } + ); } - if ($swoncond && $auto && $oncom && + if ($auto && $oncom && $swoncond && !$swoffcond && # kein Einschalten wenn zusätzliche Switch off Bedingung zutrifft simplifyCstate($pstate) =~ /planned|priority|starting/xs && isInTimeframe ($hash, $c)) { # Verbraucher Start ist geplant && Startzeit überschritten my $mode = ConsumerVal ($hash, $c, "mode", $defcmode); # Consumer Planungsmode @@ -3572,18 +3588,17 @@ sub ___switchConsumerOff { my $cname = ConsumerVal ($hash, $c, "name", ""); # Consumer Device Name my $calias = ConsumerVal ($hash, $c, "alias", ""); # Consumer Device Alias my $mode = ConsumerVal ($hash, $c, "mode", $defcmode); # Consumer Planungsmode + my $hyst = ConsumerVal ($hash, $c, "hysteresis", 0); # Hysterese my $offcom = ConsumerVal ($hash, $c, "offcom", ""); # Set Command für "off" - my ($swoffcond,$info,$err) = isAddSwitchOffCond ($hash, $c); # zusätzliche Switch on Bedingung + my ($swoffcond,$info,$err) = isAddSwitchOffCond ($hash, $c); # zusätzliche Switch off Bedingung my $caution; Log3 ($name, 1, "$name - $err") if($err); if($debug) { # nur für Debugging - Log (1, qq{DEBUG> $name - Parameters for switch off decision consumer "$c": }. - qq{swoffcond: $swoffcond, auto mode: $auto, off-command: $offcom, }. - qq{planning state: $pstate, stop timestamp: }.($stopts ? $stopts : "undef").", ". - qq{timestamp: $t} + Log (1, qq{DEBUG> $name consumer "$c" - Context of switching "off": }. + qq{swoffcond: $swoffcond, off-command: $offcom } ); } @@ -3604,8 +3619,8 @@ sub ___switchConsumerOff { Log3 ($name, 2, "$name - $state (Automatic = $auto)"); } - elsif (((isInterruptable($hash, $c) && !isConsRcmd ($hash, $c)) || isInterruptable($hash, $c) == 2) && # Consumer unterbrechen - isInTimeframe ($hash, $c) && simplifyCstate ($pstate) =~ /started|continued|interrupting/xs && + elsif (((isInterruptable($hash, $c, $hyst) && !isConsRcmd ($hash, $c)) || isInterruptable($hash, $c, $hyst) == 2) && # Consumer unterbrechen + isInTimeframe ($hash, $c) && simplifyCstate ($pstate) =~ /started|continued|interrupting/xs && $auto && $offcom) { CommandSet(undef,"$cname $offcom"); @@ -3616,7 +3631,7 @@ sub ___switchConsumerOff { delete $paref->{ps}; - $caution = isInterruptable($hash, $c) == 2 ? 'interrupt condition' : 'surplus shortage'; + $caution = isInterruptable($hash, $c, $hyst) == 2 ? 'interrupt condition' : 'surplus shortage'; $state = qq{switching Consumer "$calias" to "$offcom", caution: $caution}; writeDataToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben @@ -4437,8 +4452,10 @@ sub collectAllRegConsumers { } my $interruptable = 0; - if(exists $hc->{interruptable}) { - $interruptable = $hc->{interruptable} if($hc->{interruptable} ne '0'); + my ($hyst); + if(exists $hc->{interruptable} && $hc->{interruptable} ne '0') { + $interruptable = $hc->{interruptable}; + ($interruptable,$hyst) = $interruptable =~ /(.*):(.*)$/xs if($interruptable ne '1'); } my $rauto = $hc->{auto} // q{}; @@ -4476,6 +4493,7 @@ sub collectAllRegConsumers { $data{$type}{$name}{consumers}{$c}{rswoffcond} = $rswoffcond // q{}; # Reading zur Lieferung einer vorrangigen Ausschaltbedingung $data{$type}{$name}{consumers}{$c}{swoffcondregex} = $swoffcondregex // q{}; # Regex einer vorrangigen Ausschaltbedingung $data{$type}{$name}{consumers}{$c}{interruptable} = $interruptable; # Ein-Zustand des Verbrauchers ist unterbrechbar + $data{$type}{$name}{consumers}{$c}{hysteresis} = $hyst // 0; # Hysterese } Log3 ($name, 5, "$name - all registered consumers:\n".Dumper $data{$type}{$name}{consumers}); @@ -7599,9 +7617,9 @@ sub isAddSwitchOnCond { 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 $condstate = ReadingsVal ($dswoncond, $rswoncond, ""); + my $condval = ReadingsVal ($dswoncond, $rswoncond, ""); # Wert zum Vergleich mit Regex - if ($condstate =~ m/^$swoncondregex$/x) { + if ($condval =~ m/^$swoncondregex$/x) { return (1, $info, $err); } @@ -7625,6 +7643,7 @@ sub isAddSwitchOffCond { my $hash = shift; my $c = shift; my $cond = shift // q{}; + my $hyst = shift // 0; # Hysterese my $info = q{}; my $err = q{}; @@ -7646,13 +7665,22 @@ sub isAddSwitchOffCond { return (0, $info, $err); } - my $condstate = ReadingsVal ($dswoffcond, $rswoffcond, ""); + my $condval = ReadingsVal ($dswoffcond, $rswoffcond, ""); - if ($condstate && $condstate =~ m/^$swoffcondregex$/x) { + if ($hyst && IsNumeric ($condval)) { # Hysterese berücksichtigen + $condval -= $hyst; + } + + if ($condval && $condval =~ m/^$swoffcondregex$/x) { + $info = qq{value "$condval" (hysteresis = $hyst) match the Regex "$swoffcondregex" \n}. + qq{-> Switch-off condition or interrupt in the "switch-off context", DO NOT switch on or DO NOT continue in the "switch-on context"\n} + ; return (1, $info, $err); } - $info = qq{The device "$dswoffcond", reading "$rswoffcond" doesn't match the Regex "$swoffcondregex"}; + $info = qq{The device: "$dswoffcond", reading: "$rswoffcond" , value: "$condval" (hysteresis = $hyst) doesn't match Regex: "$swoffcondregex" \n}. + qq{-> DO NOT Switch-off or DO NOT interrupt in the "switch-off context", Switching on or continuing in the "switch-on" context\n} + ; return (0, $info, $err); } @@ -7683,7 +7711,9 @@ return ConsumerVal ($hash, $c, 'isConsumptionRecommended', 0); sub isInterruptable { my $hash = shift; my $c = shift; - + my $hyst = shift // 0; + + my $name = $hash->{NAME}; my $intable = ConsumerVal ($hash, $c, 'interruptable', 0); if ($intable eq '0') { @@ -7693,7 +7723,13 @@ sub isInterruptable { return 1; } - my ($swoffcond,$info,$err) = isAddSwitchOffCond ($hash, $c, $intable); + my ($swoffcond,$info,$err) = isAddSwitchOffCond ($hash, $c, $intable, $hyst); + Log3 ($name, 1, "$name - $err") if($err); + + my $debug = AttrVal ($name, "debug", 0); + if ($debug) { # nur für Debugging + Log (1, qq{DEBUG> $name consumer "$c" - isInterruptable Info: $info}); + } if ($swoffcond) { return 2; @@ -7705,6 +7741,21 @@ sub isInterruptable { return; } +################################################################ +# Prüfung auf numerischen Wert (vorzeichenbehaftet) +################################################################ +sub IsNumeric { + my $val = shift // q{empty}; + + my $ret = 0; + + if($val =~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/xs) { + $ret = 1; + } + +return $ret; +} + ################################################################ # liefert die Zeit des letzten Schaltvorganges ################################################################ @@ -7991,7 +8042,7 @@ return $def; # swoffcondregex - Regex einer einer vorrangige Ausschaltbedingung # isIntimeframe - ist Zeit innerhalb der Planzeit ein/aus # interruptable - Consumer "on" ist während geplanter "ein"-Zeit unterbrechbar -# +# hysteresis - Hysterese # $def: Defaultwert # #################################################################################################################### @@ -8793,6 +8844,11 @@ Ein/Ausschaltzeiten sowie deren Ausführung vom SolarForecast Modul übernehmen Der Verbraucher wird temporär ausgeschaltet (interrupted) und wieder eingeschaltet (continued) wenn die Interrupt-Bedingung nicht mehr vorliegt. Die verbleibende Laufzeit wird durch einen Interrupt nicht beeinflusst ! +

+ + Der Schlüssel power gibt die nominale Leistungsaufnahme des Verbrauchers gemäß seines Datenblattes an. + Dieser Wert wird verwendet um das Schalten des Verbrauchers in Abhängigkeit des aktuellen PV-Überschusses zu + steuern. Ist power=0 gesetzt, wird der Verbraucher unabhängig von einem ausreichenden PV-Überschuß geschaltet.