diff --git a/fhem/CHANGED b/fhem/CHANGED index b458b8f72..f355c1141 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 76_SolarForecast: consumerXX: notbefore, notafter format hh[:mm] - feature: 76_SolarForecast: add operationMode: active/inactive - feature: 74_AutomowerConnect: New setter confirmError for Testing - bugfix: 72_FRITZBOX: fehlen von Data::Dumper in einer debuf sub diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index a9abaa2dd..479af576b 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -157,6 +157,7 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.10.0" => "24.01.2024 consumerXX: notbefore, notafter format extended to hh[:mm], new sub __checkcode, __checkhhmm ", "1.9.0" => "23.01.2024 modify disable, add operationMode: active/inactive ", "1.8.0" => "22.01.2024 add 'noLearning' Option to Setter pvCorrectionFactor_Auto ", "1.7.1" => "20.01.2024 optimize battery management ", @@ -1104,7 +1105,7 @@ sub Set { my $prop1 = shift @a; my $prop2 = shift @a; - return if((controlParams($name))[1]); + return if((controller($name))[1]); my ($setlist,@fcdevs,@cfs,@condevs); my ($fcd,$ind,$med,$cf,$sp,$coms) = ('','','','','',''); @@ -1207,7 +1208,7 @@ sub Set { ## inactive (Setter überschreiben) #################################### - if ((controlParams($name))[2]) { + if ((controller($name))[2]) { $setlist = "operationMode:active,inactive "; } @@ -2379,7 +2380,7 @@ sub Get { $getlist .= "valDecTree:aiRawData,aiRuleStrings "; } - return if((controlParams($name))[1] || (controlParams($name))[2]); + return if((controller($name))[1] || (controller($name))[2]); my $params = { hash => $hash, @@ -4188,31 +4189,13 @@ sub Attr { delete $data{$type}{$name}{func}{ghoValForm}; return; } - - if(!$aVal || $aVal !~ m/^\s*\{.*\}\s*$/xs) { - return "Usage of $aName is wrong. The function has to be specified as \"{}\" "; - } - - my $attrVal = $aVal; - my %specials = ( "%DEVICE" => $name, - "%READING" => $name, - "%VALUE" => 1, - "%UNIT" => 'kW', - ); - - my $err = perlSyntaxCheck($attrVal, %specials); + + my $err; + my $code = $aVal; + ($err, $code) = __checkcode ($name, $code); return $err if($err); - - if ($attrVal =~ m/^\{.*\}$/xs && $attrVal =~ m/=>/ && $attrVal !~ m/\$/ ) { # Attr wurde als Hash definiert - my $av = eval $attrVal; - - return $@ if($@); - - $av = eval $attrVal; - $attrVal = $av if(ref $av eq "HASH"); - } - - $data{$type}{$name}{func}{ghoValForm} = $attrVal; + + $data{$type}{$name}{func}{ghoValForm} = $code; } if ($cmd eq 'set') { @@ -4240,7 +4223,7 @@ sub Attr { } } - if ($aName eq 'ctrlUserExitFn' && $init_done) { + if ($aName eq 'ctrlUserExitFn' && $init_done) { if(!$aVal || $aVal !~ m/^\s*(\{.*\})\s*$/xs) { return "Usage of $aName is wrong. The function has to be specified as \"{}\" "; } @@ -4317,7 +4300,17 @@ sub _attrconsumer { ## no critic "not used" } if (exists $h->{mode} && $h->{mode} !~ /^(?:can|must)$/xs) { - return qq{The mode "$h->{mode}" isn't allowed!} + return qq{The mode "$h->{mode}" isn't allowed!}; + } + + if (exists $h->{notbefore}) { + my $valid = __checkhhmm ($h->{notbefore}); + return qq{The syntax "$h->{notbefore}" is wrong!} if(!$valid); + } + + if (exists $h->{notafter}) { + my $valid = __checkhhmm ($h->{notafter}); + return qq{The syntax "$h->{notafter}" is wrong!} if(!$valid); } if (exists $h->{interruptable}) { # Check Regex/Hysterese @@ -4389,6 +4382,53 @@ sub _attrconsumer { ## no critic "not used" return; } +################################################################ +# prüfen Angabe hh[:mm] +################################################################ +sub __checkhhmm { + my $val = shift; + + my $valid = 0; + + if ($val =~ /^([0-9]{1,2})(:[0-5]{1}[0-9]{1})?$/xs) { + $valid = 1 if(int $1 < 24); + } + +return $valid; +} + +################################################################ +# prüfen validen Code in $val +################################################################ +sub __checkcode { + my $name = shift; + my $val = shift; + + if (!$val || $val !~ m/^\s*\{.*\}\s*$/xs) { + return qq{Usage of $name is wrong. The function has to be specified as "{}"}; + } + + my %specials = ( "%DEVICE" => $name, + "%READING" => $name, + "%VALUE" => 1, + "%UNIT" => 'kW', + ); + + my $err = perlSyntaxCheck ($val, %specials); + return $err if($err); + + if ($val =~ m/^\{.*\}$/xs && $val =~ m/=>/ && $val !~ m/\$/ ) { # Attr wurde als Hash definiert + my $av = eval $val; + + return $@ if($@); + + $av = eval $val; + $val = $av if(ref $av eq "HASH"); + } + +return ('', $val); +} + ################################################################ # Attr ctrlConsRecommendReadings ################################################################ @@ -4446,7 +4486,7 @@ sub Notify { my $myName = $myHash->{NAME}; # Name des eigenen Devices my $devName = $dev_hash->{NAME}; # Device welches Events erzeugt hat - return if((controlParams($myName))[1] || !$myHash->{NOTIFYDEV}); + return if((controller($myName))[1] || !$myHash->{NOTIFYDEV}); my $events = deviceEvents($dev_hash, 1); return if(!$events); @@ -4640,7 +4680,7 @@ sub periodicWriteCachefiles { RemoveInternalTimer($hash, "FHEM::SolarForecast::periodicWriteCachefiles"); InternalTimer (gettimeofday()+$whistrepeat, "FHEM::SolarForecast::periodicWriteCachefiles", $hash, 0); - return if((controlParams($name))[1] || (controlParams($name))[2]); + return if((controller($name))[1] || (controller($name))[2]); writeCacheToFile ($hash, "circular", $pvccache.$name); # Cache File PV Circular schreiben writeCacheToFile ($hash, "pvhist", $pvhcache.$name); # Cache File PV History schreiben @@ -4777,7 +4817,7 @@ sub runCentralTask { my $t = time; my $second = int (strftime "%S", localtime($t)); # aktuelle Sekunde (00-61) my $minute = int (strftime "%M", localtime($t)); # aktuelle Minute (00-59) - my $interval = (controlParams ($name))[0]; # Interval + my $interval = (controller ($name))[0]; # Interval if (!$interval) { $hash->{MODE} = 'Manual'; @@ -4785,12 +4825,12 @@ sub runCentralTask { return; } - if ((controlParams($name))[1]) { + if ((controller($name))[1]) { $hash->{MODE} = 'disabled'; return; } - if ((controlParams($name))[2]) { + if ((controller($name))[2]) { $hash->{MODE} = 'inactive'; return; } @@ -4905,7 +4945,7 @@ sub centralTask { setModel ($hash); # Model setzen - return if((controlParams($name))[1] || (controlParams($name))[2]); # disabled / inactive + return if((controller($name))[1] || (controller($name))[2]); # disabled / inactive if (CurrentVal ($hash, 'ctrunning', 0)) { Log3 ($name, 3, "$name - INFO - central task was called when it was already running ... end this call"); @@ -5183,7 +5223,7 @@ return $az; ################################################################ # Steuerparameter berechnen / festlegen ################################################################ -sub controlParams { +sub controller { my $name = shift; my $interval = AttrVal ($name, 'ctrlInterval', $definterval); # 0 wenn manuell gesteuert @@ -7566,6 +7606,7 @@ return; ################################################################ # Einschaltgrenzen berücksichtigen und Korrektur # zurück liefern +# notbefore, notafter muß in der Form "hh[:mm]" vorliegen ################################################################ sub ___switchonTimelimits { my $paref = shift; @@ -7587,10 +7628,17 @@ sub ___switchonTimelimits { debugLog ($paref, "consumerPlanning", qq{consumer "$c" - starttime is set to >$starttime< due to >SunPath< is used}); } - my $origtime = $starttime; - my $notbefore = ConsumerVal ($hash, $c, "notbefore", 0); - my $notafter = ConsumerVal ($hash, $c, "notafter", 0); - + my $origtime = $starttime; + my $notbefore = ConsumerVal ($hash, $c, "notbefore", '00:00'); + my $notafter = ConsumerVal ($hash, $c, "notafter", '00:00'); + + my ($nbfhh, $nbfmm) = split ":", $notbefore; + my ($nafhh, $nafmm) = split ":", $notafter; + $nbfmm //= '00'; + $nafmm //= '00'; + $notbefore = (int $nbfhh) . $nbfmm; + $notafter = (int $nafhh) . $nbfmm; + my $change = q{}; if ($t > timestringToTimestamp ($starttime)) { @@ -7598,17 +7646,18 @@ sub ___switchonTimelimits { $change = 'current time'; } - my ($starthour) = $starttime =~ /\s(\d{2}):/xs; + my ($starthour, $startminute) = $starttime =~ /\s(\d{2}):(\d{2}):/xs; + my $start = (int $starthour) . $startminute; - if ($notbefore && int $starthour < int $notbefore) { - $starthour = sprintf("%02d", $notbefore); - $starttime =~ s/\s(\d{2}):/ $starthour:/x; + if ($notbefore && $start < $notbefore) { + $nbfhh = sprintf "%02d", $nbfhh; + $starttime =~ s/\s(\d{2}):(\d{2}):/ $nbfhh:$nbfmm:/x; $change = 'notbefore'; } - if ($notafter && int $starthour > int $notafter) { - $starthour = sprintf("%02d", $notafter); - $starttime =~ s/\s(\d{2}):/ $starthour:/x; + if ($notafter && $start > $notafter) { + $nafhh = sprintf "%02d", $nafhh; + $starttime =~ s/\s(\d{2}):(\d{2}):/ $nafhh:$nafmm:/x; $change = 'notafter'; } @@ -9170,7 +9219,7 @@ sub _checkSetupNotComplete { my $height = AttrNum ($name, 'graphicBeamHeight', 200); my $lang = getLang ($hash); - if ((controlParams($name))[1] || (controlParams($name))[2]) { + if ((controller($name))[1] || (controller($name))[2]) { $ret .= ""; $ret .= ""; $ret .= " - + - + @@ -16777,8 +16826,8 @@ to ensure that the system configuration is correct. 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 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 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=7 notafter=20 auto=automatic pcurr=actpow:W on=on off=off mintime=SunPath:60:-120 interruptable=1
+ 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=20 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=noShow
@@ -18671,7 +18720,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
  • consumerXX <Device Name> type=<type> power=<power> [switchdev=<device>]
    [mode=<mode>] [icon=<Icon>] [mintime=<minutes> | SunPath[:<Offset_Sunrise>:<Offset_Sunset>]]
    [on=<Kommando>] [off=<Kommando>] [swstate=<Readingname>:<on-Regex>:<off-Regex>] [asynchron=<Option>]
    - [notbefore=<Stunde>] [notafter=<Stunde>] [locktime=<offlt>[:<onlt>]]
    + [notbefore=<Stunde>[:<Minute>]] [notafter=<Stunde>[:<Minute>]] [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>]
    [interruptable=<Option>] [noshow=<Option>]

    @@ -18768,9 +18817,9 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
  • - + - + @@ -18834,8 +18883,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. 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 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 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=7 notafter=20 auto=automatic pcurr=actpow:W on=on off=off mintime=SunPath:60:-120 interruptable=1
    + 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=20 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=noShow
    "; @@ -16612,8 +16661,8 @@ to ensure that the system configuration is correct.
  • consumerXX <Device Name> type=<type> power=<power> [switchdev=<device>]
    [mode=<mode>] [icon=<Icon>] [mintime=<minutes> | SunPath[:<Offset_Sunrise>:<Offset_Sunset>]]
    - [on=<command>] [off=<command>] [swstate=<Readingname>:<on-Regex>:<off-Regex>] [asynchron=<Option>]
    - [notbefore=<Hour>] [notafter=<Hour>] [locktime=<offlt>[:<onlt>]]
    + [on=<command>] [off=<command>] [swstate=<Readingname>:<on-Regex>:<off-Regex>] [asynchron=<Option>]
    + [notbefore=<hour>[:<minute>]] [notafter=<hour>[:<minute>]] [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>]
    [interruptable=<Option>] [noshow=<Option>]

    @@ -16711,9 +16760,9 @@ to ensure that the system configuration is correct.
  • 0 - only synchronous processing of switching states (default)
    1 - additional asynchronous processing of switching states through event processing
    notbefore Schedule start time consumer not before specified hour (01..23) (optional)
    notbefore Schedule start time consumer not before specified time hh[:mm] (optional)
    notafter Schedule start time consumer not after specified hour (01..23) (optional)
    notafter Schedule start time consumer not after specified time hh[:mm] (optional)
    auto Reading in the consumer device which enables or blocks the switching of the consumer (optional)
    If the key switchdev is given, the reading is set and evaluated in this device.
    0 - ausschließlich synchrone Verarbeitung von Schaltzuständen (default)
    1 - zusätzlich asynchrone Verarbeitung von Schaltzuständen durch Eventverarbeitung
    notbefore Startzeitpunkt Verbraucher nicht vor angegebener Stunde (01..23) einplanen (optional)
    notbefore Startzeitpunkt Verbraucher nicht vor angegebener Zeit hh[:mm] einplanen (optional)
    notafter Startzeitpunkt Verbraucher nicht nach angegebener Stunde (01..23) einplanen (optional)
    notafter Startzeitpunkt Verbraucher nicht nach angegebener Zeit hh[:mm] einplanen (optional)
    auto Reading im Verbraucherdevice welches das Schalten des Verbrauchers freigibt bzw. blockiert (optional)
    Ist der Schlüssel switchdev angegeben, wird das Reading in diesem Device gesetzt und ausgewertet.