diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 57aeb3177..be5b2cab0 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -157,10 +157,20 @@ BEGIN { # Versions History intern my %vNotesIntern = ( - "1.7.0" => "15.01.2024 Changeover Start centralTask completely to runCentralTask, ". + "1.13.0" => "27.01.2024 minor change of deleteOldBckpFiles, Setter writeHistory replaced by operatingMemory ". + "save, backup and recover in-memory operating data ", + "1.12.0" => "26.01.2024 create backup files and delete old generations of them ", + "1.11.1" => "26.01.2024 fix ___switchonTimelimits ", + "1.11.0" => "25.01.2024 consumerXX: notbefore, notafter format extended to possible perl code {...} ", + "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 ", + "1.7.0" => "18.01.2024 Changeover Start centralTask completely to runCentralTask, ". "aiAddRawData: Weekday from pvHistory not taken into account greater than current day ". "__reviewSwitchTime: new function for review consumer planning state ". "___switchonTimelimits: The current time is taken into account during planning ". + "take info-tag into consumerxx Reading ". "fix deletion of currentBatteryDev, currentInverterDev, currentMeterDev ", "1.6.5" => "10.01.2024 new function runCentralTask in ReadyFn to run centralTask definitely at end/begin of an hour ", "1.6.4" => "09.01.2024 fix get Automatic State, use key switchdev for auto-Reading if switchdev is set in consumer attr ", @@ -289,6 +299,7 @@ my $batSocChgDay = 5; my @widgetreadings = (); # Array der Hilfsreadings als Attributspeicher my $root = $attr{global}{modpath}; # Pfad zu dem Verzeichnis der FHEM Module +my $cachedir = $root."/FHEM/FhemUtils"; # Directory für Cachefiles my $pvhcache = $root."/FHEM/FhemUtils/PVH_SolarForecast_"; # Filename-Fragment für PV History (wird mit Devicename ergänzt) my $pvccache = $root."/FHEM/FhemUtils/PVC_SolarForecast_"; # Filename-Fragment für PV Circular (wird mit Devicename ergänzt) my $plantcfg = $root."/FHEM/FhemUtils/PVCfg_SolarForecast_"; # Filename-Fragment für PV Anlagenkonfiguration (wird mit Devicename ergänzt) @@ -405,6 +416,7 @@ my %hset = ( # Ha energyH4Trigger => { fn => \&_setTrigger }, plantConfiguration => { fn => \&_setplantConfiguration }, batteryTrigger => { fn => \&_setTrigger }, + operationMode => { fn => \&_setoperationMode }, powerTrigger => { fn => \&_setTrigger }, pvCorrectionFactor_05 => { fn => \&_setpvCorrectionFactor }, pvCorrectionFactor_06 => { fn => \&_setpvCorrectionFactor }, @@ -429,7 +441,7 @@ my %hset = ( # Ha moduleRoofTops => { fn => \&_setmoduleRoofTops }, moduleTiltAngle => { fn => \&_setmoduleTiltAngle }, moduleDirection => { fn => \&_setmoduleDirection }, - writeHistory => { fn => \&_setwriteHistory }, + operatingMemory => { fn => \&_setoperatingMemory }, vrmCredentials => { fn => \&_setVictronCredentials }, aiDecTree => { fn => \&_setaiDecTree }, ); @@ -597,14 +609,14 @@ my %hqtxt = ( DE => qq{bis zum Sonnenuntergang warten} }, snbefb => { EN => qq{Should not be empty. Maybe the device has just been redefined.}, DE => qq{Sollte nicht leer sein. Vielleicht wurde das Device erst neu definiert.} }, - scnp => { EN => qq{The scheduling of the consumer is not provided}, + scnp => { EN => qq{Scheduling of the consumer is not provided}, DE => qq{Die Einplanung des Verbrauchers ist nicht vorgesehen} }, vrmcr => { EN => qq{Please set the Victron VRM Portal credentials with "set LINK vrmCredentials".}, DE => qq{Bitte setzen sie die Victron VRM Portal Zugangsdaten mit "set LINK vrmCredentials". } }, awd => { EN => qq{LINK is waiting for solar forecast data ...
}, DE => qq{LINK wartet auf Solarvorhersagedaten ...
} }, wexso => { EN => qq{switched externally}, - DE => qq{von extern umgeschaltet} }, + DE => qq{von extern umgeschaltet} }, strok => { EN => qq{Congratulations 😊, the system configuration is error-free. Please note any information ().}, DE => qq{Herzlichen Glückwunsch 😊, die Anlagenkonfiguration ist fehlerfrei. Bitte eventuelle Hinweise () beachten.} }, strwn => { EN => qq{Looks quite good 😐, the system configuration is basically OK. Please note the warnings ().}, @@ -903,6 +915,7 @@ sub Initialize { "ctrlAIdataStorageDuration ". "ctrlAutoRefresh:selectnumbers,120,0.2,1800,0,log10 ". "ctrlAutoRefreshFW:$fwd ". + "ctrlBackupFilesKeep:select,1,2,3,4,5,6,7,8,9,10 ". "ctrlBatSocManagement:textField-long ". "ctrlConsRecommendReadings:multiple-strict,$allcs ". "ctrlDebug:multiple-strict,$dm,#14 ". @@ -995,29 +1008,35 @@ sub Define { use version 0.77; our $VERSION = moduleVersion ($params); # Versionsinformationen setzen createAssociatedWith ($hash); - + $params->{file} = $pvhcache.$name; # Cache File PV History einlesen wenn vorhanden $params->{cachename} = 'pvhist'; + $params->{title} = 'pvHistory'; _readCacheFile ($params); $params->{file} = $pvccache.$name; # Cache File PV Circular einlesen wenn vorhanden $params->{cachename} = 'circular'; + $params->{title} = 'pvCircular'; _readCacheFile ($params); $params->{file} = $csmcache.$name; # Cache File Consumer einlesen wenn vorhanden $params->{cachename} = 'consumers'; + $params->{title} = 'consumerMaster'; _readCacheFile ($params); $params->{file} = $scpicache.$name; # Cache File SolCast API Werte einlesen wenn vorhanden $params->{cachename} = 'solcastapi'; + $params->{title} = 'solApiData'; _readCacheFile ($params); $params->{file} = $aitrained.$name; # AI Cache File einlesen wenn vorhanden $params->{cachename} = 'aitrained'; + $params->{title} = 'aiTrainedData'; _readCacheFile ($params); $params->{file} = $airaw.$name; # AI Rawdaten File einlesen wenn vorhanden $params->{cachename} = 'airaw'; + $params->{title} = 'aiRawData'; _readCacheFile ($params); singleUpdateState ( {hash => $hash, state => 'initialized', evt => 1} ); @@ -1038,6 +1057,7 @@ sub _readCacheFile { my $type = $paref->{type}; my $file = $paref->{file}; my $cachename = $paref->{cachename}; + my $title = $paref->{title}; if ($cachename eq 'aitrained') { my ($err, $dtree) = fileRetrieve ($file); @@ -1048,7 +1068,7 @@ sub _readCacheFile { if ($valid) { $data{$type}{$name}{aidectree}{aitrained} = $dtree; $data{$type}{$name}{current}{aitrainstate} = 'ok'; - Log3 ($name, 3, qq{$name - cached data "$cachename" restored}); + Log3 ($name, 3, qq{$name - cached data "$title" restored}); } } @@ -1061,7 +1081,7 @@ sub _readCacheFile { if (!$err && $data) { $data{$type}{$name}{aidectree}{airaw} = $data; $data{$type}{$name}{current}{aitrawstate} = 'ok'; - Log3 ($name, 3, qq{$name - cached data "$cachename" restored}); + Log3 ($name, 3, qq{$name - cached data "$title" restored}); } return; @@ -1069,16 +1089,16 @@ sub _readCacheFile { my ($error, @content) = FileRead ($file); - if(!$error) { + if (!$error) { my $json = join "", @content; my ($success) = evaljson ($hash, $json); - if($success) { + if ($success) { $data{$hash->{TYPE}}{$name}{$cachename} = decode_json ($json); - Log3 ($name, 3, qq{$name - cached data "$cachename" restored}); + Log3 ($name, 3, qq{$name - cached data "$title" restored}); } else { - Log3 ($name, 1, qq{$name - WARNING - The content of file "$file" is not readable and may be corrupt}); + Log3 ($name, 1, qq{$name - WARNING - The content of file "$file" is not readable or may be corrupt}); } } @@ -1099,10 +1119,11 @@ sub Set { my $prop1 = shift @a; my $prop2 = shift @a; - return if(IsDisabled($name)); + return if((controller($name))[1]); - my ($setlist,@fcdevs,@cfs,@condevs); + my ($setlist,@fcdevs,@cfs,@condevs,@bkps); my ($fcd,$ind,$med,$cf,$sp,$coms) = ('','','','','',''); + my $type = $hash->{TYPE}; my @re = qw( aiData batteryTriggerSet @@ -1128,6 +1149,7 @@ sub Set { push @fcdevs, 'SolCast-API'; push @fcdevs, 'ForecastSolar-API'; push @fcdevs, 'VictronKI-API'; + my $rdd = join ",", @fcdevs; for my $h (@chours) { @@ -1135,16 +1157,27 @@ sub Set { } $cf = join " ", @cfs; - my $type = $hash->{TYPE}; - for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { push @condevs, $c if($c); } - $coms = @condevs ? join ",", @condevs : 'noArg'; + + $coms = @condevs ? join ",", @condevs : 'noArg'; + my $ipai = isPrepared4AI ($hash); + + opendir (DIR, $cachedir); + + while (my $file = readdir (DIR)) { + next unless (-f "$cachedir/$file"); + next unless ($file =~ /_${name}_/); + next unless ($file =~ /_\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}$/); + push @bkps, 'recover-'.$file; + } + + closedir (DIR); + my $rf = @bkps ? ','.join ",", reverse sort @bkps : ''; ## allg. gültige Setter ######################### - my $ipai = isPrepared4AI ($hash); $setlist = "Unknown argument $opt, choose one of ". "consumerImmediatePlanning:$coms ". "consumerNewPlanning:$coms ". @@ -1156,11 +1189,12 @@ sub Set { "energyH4Trigger:textField-long ". "inverterStrings ". "modulePeakString ". + "operatingMemory:backup,save".$rf." ". + "operationMode:active,inactive ". "plantConfiguration:check,save,restore ". "powerTrigger:textField-long ". - "pvCorrectionFactor_Auto:on_simple".($ipai ? ',on_simple_ai,' : ',')."on_complex".($ipai ? ',on_complex_ai,' : ',')."off ". + "pvCorrectionFactor_Auto:noLearning,on_simple".($ipai ? ',on_simple_ai,' : ',')."on_complex".($ipai ? ',on_complex_ai,' : ',')."off ". "reset:$resets ". - "writeHistory:noArg ". $cf." " ; @@ -1198,6 +1232,12 @@ sub Set { $setlist .= "batteryTrigger:textField-long "; } + ## inactive (Setter überschreiben) + #################################### + if ((controller($name))[2]) { + $setlist = "operationMode:active,inactive "; + } + my $params = { hash => $hash, name => $name, @@ -1272,7 +1312,7 @@ sub _setconsumerImmediatePlanning { ## no critic "not used" my $planstate = ConsumerVal ($hash, $c, 'planstate', ''); my $calias = ConsumerVal ($hash, $c, 'alias', ''); - + writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben Log3 ($name, 3, qq{$name - Consumer "$calias" $planstate}) if($planstate); @@ -1516,7 +1556,7 @@ sub _setinverterDevice { ## no critic "not used" return qq{The syntax of "$opt" is not correct. Please consider the commandref.}; } - readingsSingleUpdate ($hash, "currentInverterDev", $arg, 1); + readingsSingleUpdate ($hash, 'currentInverterDev', $arg, 1); createAssociatedWith ($hash); writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration File schreiben @@ -1652,6 +1692,20 @@ sub _setbatteryDevice { ## no critic "not used" return; } +################################################################ +# Setter operationMode +################################################################ +sub _setoperationMode { ## no critic "not used" + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $prop = $paref->{prop} // return qq{no mode specified}; + + singleUpdateState ( {hash => $hash, state => $prop, evt => 1} ); + +return; +} + ################################################################ # Setter powerTrigger / batterytrigger / energyH4Trigger ################################################################ @@ -1816,7 +1870,7 @@ sub _setplantConfiguration { ## no critic "not used" $arg = 'check' if (!$arg); - if($arg eq "check") { + if ($arg eq "check") { my $out = checkPlantConfig ($hash); $out = qq{$out}; @@ -1829,9 +1883,9 @@ sub _setplantConfiguration { ## no critic "not used" return $out; } - if($arg eq "save") { - $err = writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration File schreiben - if($err) { + if ($arg eq "save") { + $err = writeCacheToFile ($hash, 'plantconfig', $plantcfg.$name); # Anlagenkonfiguration File schreiben + if ($err) { return $err; } else { @@ -1839,11 +1893,12 @@ sub _setplantConfiguration { ## no critic "not used" } } - if($arg eq "restore") { + if ($arg eq "restore") { ($err, @pvconf) = FileRead ($plantcfg.$name); - if(!$err) { + if (!$err) { my $rbit = 0; + for my $elem (@pvconf) { my ($reading, $val) = split "<>", $elem; next if(!$reading || !defined $val); @@ -1851,7 +1906,7 @@ sub _setplantConfiguration { ## no critic "not used" $rbit = 1; } - if($rbit) { + if ($rbit) { return qq{Plant Configuration restored from file "}.$plantcfg.$name.qq{"}; } else { @@ -1917,9 +1972,14 @@ sub _setpvCorrectionFactorAuto { ## no critic "not used" my $opt = $paref->{opt}; my $prop = $paref->{prop} // return qq{no correction value specified}; - readingsSingleUpdate($hash, "pvCorrectionFactor_Auto", $prop, 1); + if ($prop eq 'noLearning') { + my $pfa = ReadingsVal ($name, 'pvCorrectionFactor_Auto', 'off'); # aktuelle Autokorrektureinstellung + $prop = $pfa.' '.$prop; + } - if($prop eq "off") { + readingsSingleUpdate($hash, 'pvCorrectionFactor_Auto', $prop, 1); + + if ($prop eq 'off') { for my $n (1..24) { $n = sprintf "%02d", $n; my $rv = ReadingsVal ($name, "pvCorrectionFactor_${n}", ""); @@ -1929,7 +1989,7 @@ sub _setpvCorrectionFactorAuto { ## no critic "not used" deleteReadingspec ($hash, "pvCorrectionFactor_.*_autocalc"); } - writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration sichern + writeCacheToFile ($hash, 'plantconfig', $plantcfg.$name); # Anlagenkonfiguration sichern return; } @@ -2155,7 +2215,7 @@ sub _setreset { ## no critic "not used" if ($prop eq 'currentInverterSet') { undef @{$data{$type}{$name}{current}{genslidereg}}; readingsDelete ($hash, "Current_PV"); - readingsDelete ($hash, 'currentInverterDev'); + readingsDelete ($hash, "currentInverterDev"); deleteReadingspec ($hash, ".*_PVreal" ); writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration File schreiben } @@ -2200,19 +2260,47 @@ return; } ################################################################ -# Setter writeHistory +# Setter operatingMemory +# (Ersatz für Setter writeHistory) ################################################################ -sub _setwriteHistory { ## no critic "not used" +sub _setoperatingMemory { ## no critic "not used" my $paref = shift; my $hash = $paref->{hash}; my $name = $paref->{name}; + my $prop = $paref->{prop} // return qq{no operation specified for command}; - my $ret = writeCacheToFile ($hash, "circular", $pvccache.$name); # Cache File für PV Circular schreiben - return $ret if($ret); + if ($prop eq 'save') { + periodicWriteCachefiles ($hash); # Cache File für PV History, PV Circular schreiben + } - $ret = writeCacheToFile ($hash, "pvhist", $pvhcache.$name); # Cache File für PV History schreiben + if ($prop eq 'backup') { + periodicWriteCachefiles ($hash, 'bckp'); # Backup Files erstellen und alte Versionen löschen + } -return $ret; + if ($prop =~ /^recover-/xs) { # Sicherung wiederherstellen + my $file = (split "-", $prop)[1]; + + Log3 ($name, 3, "$name - recover saved cache file: $file"); + + if ($file =~ /^PVH_/xs) { # Cache File PV History einlesen + $paref->{cachename} = 'pvhist'; + $paref->{title} = 'pvHistory'; + } + + if ($file =~ /^PVC_/xs) { # Cache File PV Circular einlesen + $paref->{cachename} = 'circular'; + $paref->{title} = 'pvCircular'; + } + + $paref->{file} = "$cachedir/$file"; + _readCacheFile ($paref); + + delete $paref->{file}; + delete $paref->{cachename}; + delete $paref->{title}; + } + +return; } ################################################################ @@ -2239,7 +2327,7 @@ sub _setclientAction { ## no critic "not used" my $cname = shift @args; # Consumername my $tail = join " ", map { my $p = $_; $p =~ s/\s//xg; $p; } @args; ## no critic 'Map blocks' # restliche Befehlsargumente - Log3($name, 4, qq{$name - Client Action received / execute: "$action $cname $tail"}); + Log3 ($name, 4, qq{$name - Client Action received / execute: "$action $cname $tail"}); if($action eq 'set') { CommandSet (undef, "$cname $tail"); @@ -2347,7 +2435,7 @@ sub Get { $getlist .= "valDecTree:aiRawData,aiRuleStrings "; } - return if(IsDisabled($name)); + return if((controller($name))[1] || (controller($name))[2]); my $params = { hash => $hash, @@ -3696,7 +3784,7 @@ return; sub _getdata { my $paref = shift; my $hash = $paref->{hash}; - + return centralTask ($hash); } @@ -4100,7 +4188,7 @@ sub Attr { my $hash = $defs{$name}; my $type = $hash->{TYPE}; - my ($do,$val); + my ($do,$val, $err); # $cmd can be "del" or "set" # $name is device name @@ -4157,30 +4245,11 @@ sub Attr { 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 $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') { @@ -4209,13 +4278,8 @@ sub Attr { } 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 \"{}\" "; - } - - $aVal = $1; - eval $aVal; - return $@ if($@); + ($err) = checkCode ($name, $aVal, 'cc1'); + return $err if($err); } } @@ -4253,7 +4317,7 @@ sub _attrconsumer { ## no critic "not used" return if(!$init_done); # Forum: https://forum.fhem.de/index.php/topic,117864.msg1159959.html#msg1159959 - my $err; + my ($err, $valid); if ($cmd eq "set") { my ($a,$h) = parseParams ($aVal); @@ -4285,7 +4349,29 @@ 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}) { + if ($h->{notbefore} =~ m/^\s*\{.*\}\s*$/xs) { + ($err) = checkCode ($name, $h->{notbefore}, 'cc1'); + return $err if($err); + } + else { + $valid = checkhhmm ($h->{notbefore}); + return qq{The syntax "notbefore=$h->{notbefore}" is wrong!} if(!$valid); + } + } + + if (exists $h->{notafter}) { + if ($h->{notafter} =~ m/^\s*\{.*\}\s*$/xs) { + ($err) = checkCode ($name, $h->{notafter}, 'cc1'); + return $err if($err); + } + else { + $valid = checkhhmm ($h->{notafter}); + return qq{The syntax "notafter=$h->{notafter}" is wrong!} if(!$valid); + } } if (exists $h->{interruptable}) { # Check Regex/Hysterese @@ -4348,9 +4434,9 @@ sub _attrconsumer { ## no critic "not used" } writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben - + $data{$type}{$name}{current}{consumerCollected} = 0; # Consumer neu sammeln - + InternalTimer (gettimeofday()+0.5, 'FHEM::SolarForecast::centralTask', [$name, 0], 0); InternalTimer (gettimeofday()+2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); @@ -4414,7 +4500,7 @@ sub Notify { my $myName = $myHash->{NAME}; # Name des eigenen Devices my $devName = $dev_hash->{NAME}; # Device welches Events erzeugt hat - return if(IsDisabled($myName) || !$myHash->{NOTIFYDEV}); + return if((controller($myName))[1] || !$myHash->{NOTIFYDEV}); my $events = deviceEvents($dev_hash, 1); return if(!$events); @@ -4603,16 +4689,76 @@ return; ################################################################ sub periodicWriteCachefiles { my $hash = shift; + my $bckp = shift // ''; + my $name = $hash->{NAME}; - RemoveInternalTimer($hash, "FHEM::SolarForecast::periodicWriteCachefiles"); - InternalTimer (gettimeofday()+$whistrepeat, "FHEM::SolarForecast::periodicWriteCachefiles", $hash, 0); + RemoveInternalTimer ($hash, "FHEM::SolarForecast::periodicWriteCachefiles"); + InternalTimer (gettimeofday()+$whistrepeat, "FHEM::SolarForecast::periodicWriteCachefiles", $hash, 0); - return if(IsDisabled($name)); + 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 + if ($bckp) { + my $tstr = (timestampToTimestring (0))[2]; + $tstr =~ s/[-: ]/_/g; + + writeCacheToFile ($hash, "circular", $pvccache.$name.'_'.$tstr); # Cache File PV Circular Sicherung schreiben + writeCacheToFile ($hash, "pvhist", $pvhcache.$name.'_'.$tstr); # Cache File PV History Sicherung schreiben + + deleteOldBckpFiles ($name, 'PVH_SolarForecast_'.$name); # alte Backup Files löschen + deleteOldBckpFiles ($name, 'PVC_SolarForecast_'.$name); + } + +return; +} + +################################################################ +# Backupfiles löschen +################################################################ +sub deleteOldBckpFiles { + my $name = shift; + my $file = shift; + + my $dfk = AttrVal ($name, 'ctrlBackupFilesKeep', 3); + my $bfform = $file.'_.*'; + + if (!opendir (DH, $cachedir)) { + Log3 ($name, 1, "$name - ERROR - Can't open path '$cachedir'"); + return; + } + + my @files = sort grep {/^$bfform$/} readdir(DH); + return if(!@files); + + my $fref = stat ("$cachedir/$file"); + + if ($fref) { + if ($fref =~ /ARRAY/) { + @files = sort { (@{stat "$cachedir/$a"})[9] cmp (@{stat "$cachedir/$b"})[9] } @files; + } + else { + @files = sort { (stat "$cachedir/$a")[9] cmp (stat "$cachedir/$b")[9] } @files; + } + } + + closedir (DH); + + Log3 ($name, 4, "$name - Backup files were found in '$cachedir' directory: ".join(', ',@files)); + + my $max = int @files - $dfk; + + for (my $i = 0; $i < $max; $i++) { + my $done = 1; + unlink "$cachedir/$files[$i]" or do { Log3 ($name, 1, "$name - WARNING - Could not delete '$cachedir/$files[$i]': $!"); + $done = 0; + }; + + Log3 ($name, 3, "$name - old backup file '$cachedir/$files[$i]' deleted") if($done); + } + return; } @@ -4733,64 +4879,73 @@ return @pvconf; ################################################################ sub runCentralTask { my $hash = shift; - + return if(!$init_done); - + my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - + return if(CurrentVal ($hash, 'ctrunning', 0)); - + my $debug; 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); + my $second = int (strftime "%S", localtime($t)); # aktuelle Sekunde (00-61) + my $minute = int (strftime "%M", localtime($t)); # aktuelle Minute (00-59) + my $interval = (controller ($name))[0]; # Interval if (!$interval) { $hash->{MODE} = 'Manual'; storeReading ('nextCycletime', 'Manual'); - + return; + } + + if ((controller($name))[1]) { + $hash->{MODE} = 'disabled'; + return; + } + + if ((controller($name))[2]) { + $hash->{MODE} = 'inactive'; return; } my $nct = CurrentVal ($hash, 'nextCycleTime', 0); # gespeicherte nächste CyleTime - if(!IsDisabled($name) && $t >= $nct) { + if ($t >= $nct) { my $new = $t + $interval; # nächste Wiederholungszeit - $hash->{MODE} = "Automatic - next Cycletime: ".FmtTime($new); - + $hash->{MODE} = 'Automatic - next Cycletime: '.FmtTime($new); + $data{$type}{$name}{current}{nextCycleTime} = $new; - + storeReading ('nextCycletime', FmtTime($new)); centralTask ($hash, 1); } - - if ($minute == 59 && $second > 48 && $second < 58) { + + if ($minute == 59 && $second > 48 && $second < 58) { if (!exists $hash->{HELPER}{S58DONE}) { $debug = getDebug ($hash); $hash->{HELPER}{S58DONE} = 1; - + if ($debug =~ /collectData/x) { Log3 ($name, 1, "$name DEBUG> Start of unscheduled data collection at the end of an hour"); } - + centralTask ($hash, 1); } } else { delete $hash->{HELPER}{S58DONE}; } - + if ($minute == 0 && $second > 3 && $second < 20) { if (!exists $hash->{HELPER}{S20DONE}) { - $debug = getDebug ($hash); + $debug = getDebug ($hash); $hash->{HELPER}{S20DONE} = 1; - + if ($debug =~ /collectData/x) { Log3 ($name, 1, "$name DEBUG> Start of unscheduled data collection at the beginning of an hour"); } - + centralTask ($hash, 1); } } @@ -4861,11 +5016,11 @@ sub centralTask { ############################################################################################ return if(!$init_done); - + setModel ($hash); # Model setzen - return if(IsDisabled($name)); - + 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"); $data{$type}{$name}{current}{ctrunning} = 0; @@ -4880,7 +5035,7 @@ sub centralTask { return; } } - + my $t = time; # aktuelle Unix-Zeit my $date = strftime "%Y-%m-%d", localtime($t); # aktuelles Datum my $chour = strftime "%H", localtime($t); # aktuelle Stunde in 24h format (00-23) @@ -4888,9 +5043,9 @@ sub centralTask { my $day = strftime "%d", localtime($t); # aktueller Tag (range 01 to 31) my $dayname = strftime "%a", localtime($t); # aktueller Wochentagsname my $debug = getDebug ($hash); # Debug Module - - $data{$type}{$name}{current}{ctrunning} = 1; # Central Task running Statusbit - + + $data{$type}{$name}{current}{ctrunning} = 1; # Central Task running Statusbit + my $centpars = { hash => $hash, name => $name, @@ -4960,7 +5115,7 @@ sub centralTask { $centpars->{evt} = 1; singleUpdateState ($centpars); } - + $data{$type}{$name}{current}{ctrunning} = 0; return; @@ -5142,12 +5297,15 @@ return $az; ################################################################ # Steuerparameter berechnen / festlegen ################################################################ -sub controlParams { +sub controller { my $name = shift; - - my $interval = AttrVal ($name, 'ctrlInterval', $definterval); # 0 wenn manuell gesteuert -return $interval; + my $interval = AttrVal ($name, 'ctrlInterval', $definterval); # 0 wenn manuell gesteuert + my $idval = IsDisabled ($name); + my $disabled = $idval == 1 ? 1 : 0; + my $inactive = $idval == 3 ? 1 : 0; + +return ($interval, $disabled, $inactive); } ################################################################ @@ -5197,7 +5355,6 @@ sub _specialActivities { ## bestimmte einmalige Aktionen ################################## - my $tlim = "00"; if ($chour =~ /^($tlim)$/x) { if (!exists $hash->{HELPER}{H00DONE}) { @@ -5285,6 +5442,8 @@ sub _specialActivities { aiAddInstance ($paref); # AI füllen, trainieren und sichern delete $paref->{taa}; + periodicWriteCachefiles ($hash, 'bckp'); # Backup Files erstellen und alte Versionen löschen + $hash->{HELPER}{H00DONE} = 1; } } @@ -5662,10 +5821,10 @@ sub __calcPVestimates { my $reld = $fd == 0 ? "today" : $fd == 1 ? "tomorrow" : "unknown"; - my $rainprob = NexthoursVal ($hash, "NextHour".sprintf ("%02d", $num), "rainprob", 0); # Niederschlagswahrscheinlichkeit> 0,1 mm während der letzten Stunde - my $cloudcover = NexthoursVal ($hash, "NextHour".sprintf ("%02d", $num), "cloudcover", 0); # effektive Wolkendecke nächste Stunde X - my $temp = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "temp", $tempbasedef); # vorhergesagte Temperatur Stunde X - my $acu = isAutoCorrUsed ($name); + my $rainprob = NexthoursVal ($hash, "NextHour".sprintf ("%02d", $num), "rainprob", 0); # Niederschlagswahrscheinlichkeit> 0,1 mm während der letzten Stunde + my $cloudcover = NexthoursVal ($hash, "NextHour".sprintf ("%02d", $num), "cloudcover", 0); # effektive Wolkendecke nächste Stunde X + my $temp = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "temp", $tempbasedef); # vorhergesagte Temperatur Stunde X + my ($acu, $aln) = isAutoCorrUsed ($name); $paref->{cloudcover} = $cloudcover; my ($hc, $hq) = ___readCandQ ($paref); # liest den anzuwendenden Korrekturfaktor @@ -5778,9 +5937,9 @@ sub ___readCandQ { my $fd = $paref->{fd}; my $cc = $paref->{cloudcover}; - my $acu = isAutoCorrUsed ($name); # Autokorrekturmodus - my $hc = ReadingsNum ($name, 'pvCorrectionFactor_'.sprintf("%02d",$fh1), 1.00); # Voreinstellung RAW-Korrekturfaktor - my $hq = '-'; # keine Qualität definiert + my ($acu, $aln) = isAutoCorrUsed ($name); # Autokorrekturmodus + my $hc = ReadingsNum ($name, 'pvCorrectionFactor_'.sprintf("%02d",$fh1), 1.00); # Voreinstellung RAW-Korrekturfaktor + my $hq = '-'; # keine Qualität definiert delete $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{cloudrange}; @@ -5925,7 +6084,7 @@ sub _transferInverterValues { my $chour = $paref->{chour}; my $day = $paref->{day}; - my $indev = ReadingsVal($name, "currentInverterDev", ""); + my $indev = ReadingsVal($name, 'currentInverterDev', ''); my ($a,$h) = parseParams ($indev); $indev = $a->[0] // ""; return if(!$indev || !$defs{$indev}); @@ -5962,8 +6121,10 @@ sub _transferInverterValues { if (!$histetot) { # etotal der aktuelle Stunde gesetzt ? $paref->{etotal} = $etotal; $paref->{nhour} = sprintf("%02d",$nhour); - $paref->{histname} = "etotal"; + $paref->{histname} = 'etotal'; + setPVhistory ($paref); + delete $paref->{histname}; my $etot = CurrentVal ($hash, "etotal", $etotal); @@ -5982,10 +6143,16 @@ sub _transferInverterValues { storeReading ('Today_Hour'.sprintf("%02d",$nhour).'_PVreal', $ethishour.' Wh'); $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{pvrl} = $ethishour; # Ringspeicher PV real Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 + my ($acu, $aln) = isAutoCorrUsed ($name); + $paref->{ethishour} = $ethishour; - $paref->{nhour} = sprintf("%02d",$nhour); - $paref->{histname} = "pvrl"; + $paref->{nhour} = sprintf "%02d", $nhour; + $paref->{histname} = 'pvrl'; + $paref->{pvrlvd} = $aln; # 1: beim Learning berücksichtigen, 0: nicht + setPVhistory ($paref); + + delete $paref->{pvrlvd}; delete $paref->{histname}; return; @@ -6357,7 +6524,7 @@ sub _batSocTarget { delete $paref->{careCycle}; my $nt; - my $chargereq = 0; # Ladeanforedrung wenn SoC unter Minimum SoC gefallen ist + my $chargereq = 0; # Ladeanforderung wenn SoC unter Minimum SoC gefallen ist my $target = $lowSoc; my $yday = strftime "%d", localtime($t - 86400); # Vortag (range 01 to 31) my $batymaxsoc = HistoryVal ($hash, $yday, 99, 'batmaxsoc', 0); # gespeicherter max. SOC des Vortages @@ -6367,15 +6534,15 @@ sub _batSocTarget { $batymaxsoc >= $maxsoc ? $batysetsoc - $batSocChgDay : $batysetsoc; # neuer Min SOC für den laufenden Tag - debugLog ($paref, 'batteryManagement', "SoC calc Step1 - compare with SoC history -> Target: $target %"); - + debugLog ($paref, 'batteryManagement', "SoC calc Step1 - compare with SoC history -> new Target: $target %"); + ## Pflege-SoC (Soll SoC $maxSoCdef bei $batSocChgDay % Steigerung p. Tag) ########################################################################### my $sunset = CurrentVal ($hash, 'sunsetTodayTs', $t); - my $delayts = $sunset - 5400; # Pflege-SoC/Erhöhung SoC erst ab 1,5 h vor Sonnenuntergang berechnen/anwenden + my $delayts = $sunset - 5400; # Pflege-SoC/Erhöhung SoC erst ab 1,5h vor Sonnenuntergang berechnen/anwenden my $la = ''; - - if ($t > $delayts) { + + if ($t > $delayts) { my $ntsmsc = CircularVal ($hash, 99, 'nextTsMaxSocChge', $t); my $days2care = ceil (($ntsmsc - $t) / 86400); # verbleibende Tage bis der Batterie Pflege-SoC (default 95%) erreicht sein soll @@ -6391,7 +6558,7 @@ sub _batSocTarget { $nt = (timestampToTimestring ($delayts, $paref->{lang}))[0]; $la = "note remaining days until care SoC -> calculation & activation postponed to after $nt"; } - + debugLog ($paref, 'batteryManagement', "SoC calc Step2 - $la"); ## Aufladewahrscheinlichkeit beachten @@ -6402,21 +6569,19 @@ sub _batSocTarget { my $pvexpect = $pvfctm > $pvfctd ? $pvfctm : $pvfctd; my $batinstcap = CurrentVal ($hash, 'batinstcap', 0); # installierte Batteriekapazität Wh - my $needcharge = $batinstcap - ($batinstcap / 100 * $batcharge); # vorläufige benötigte Ladeenergie (Wh) bis 100% SOC - my $cancharge = $pvexpect > $needcharge ? $pvexpect : $needcharge; # resultierende benötigte Ladeenergie (Wh) - my $cantarget = 100 - (100 / $batinstcap) * $cancharge; # berechneter möglicher Min SOC nach Berücksichtigung Ladewahrscheinlichkeit + my $cantarget = 100 - (100 / $batinstcap) * $pvexpect; # berechneter möglicher Min SOC nach Berücksichtigung Ladewahrscheinlichkeit my $newtarget = sprintf "%.0f", ($cantarget < $target ? $cantarget : $target); # Abgleich möglicher Min SOC gg. berechneten Min SOC my $logadd = ''; - if ($newtarget > $csopt && $t > $delayts) { # Erhöhung des SoC erst ab Sonnenuntergang anwenden + if ($newtarget > $csopt && $t > $delayts) { # Erhöhung des SoC (wird ab Sonnenuntergang angewendet) $target = $newtarget; $logadd = "(new target > $csopt % and Sunset has passed)"; } elsif ($newtarget > $csopt && $t <= $delayts) { # bisheriges Optimum bleibt vorerst $target = $csopt; $nt = (timestampToTimestring ($delayts, $paref->{lang}))[0]; - $logadd = "(new target $newtarget % is only activated after $nt)"; + $logadd = "(calculated new target $newtarget % is only activated after $nt)"; } elsif ($newtarget < $csopt) { # Targetminderung sofort umsetzen -> Freiplatz für Ladeprognose $target = $newtarget; @@ -6443,7 +6608,7 @@ sub _batSocTarget { my $rmn = $target - ($flo * 5); my $add = $rmn <= 2.5 ? 0 : 5; $target = ($flo * 5) + $add; - + debugLog ($paref, 'batteryManagement', "SoC calc Step5 - rounding the SoC to steps of 5 -> Target: $target %"); ## Zwangsladeanforderung @@ -6451,9 +6616,9 @@ sub _batSocTarget { if ($batcharge < $target) { $chargereq = 1; } - + debugLog ($paref, 'batteryManagement', "SoC calc Step6 - force charging request: ". - ($chargereq ? 'yes (battery charge is below minimum SoC)' : 'no (sufficient battery charge)')); + ($chargereq ? 'yes (battery charge is below minimum SoC)' : 'no (Battery is sufficiently charged)')); ## pvHistory/Readings schreiben ################################# @@ -6810,7 +6975,7 @@ sub _manageConsumerData { } $paref->{consumer} = $c; - + __getAutomaticState ($paref); # Automatic Status des Consumers abfragen __calcEnergyPieces ($paref); # Energieverbrauch auf einzelne Stunden für Planungsgrundlage aufteilen __planInitialSwitchTime ($paref); # Consumer Switch Zeiten planen @@ -6819,7 +6984,7 @@ sub _manageConsumerData { __switchConsumer ($paref); # Consumer schalten __reviewSwitchTime ($paref); # Planungsdaten überprüfen und ggf. neu planen __remainConsumerTime ($paref); # Restlaufzeit Verbraucher ermitteln - __setPhysSwState ($paref); # physischen Schaltzustand festhalten + __setPhysSwState ($paref); # physischen Schaltzustand festhalten ## Consumer Schaltstatus und Schaltzeit für Readings ermitteln ################################################################ @@ -6831,7 +6996,9 @@ sub _manageConsumerData { my ($pstate,$starttime,$stoptime,$supplmnt) = __getPlanningStateAndTimes ($paref); my ($iilt,$rlt) = isInLocktime ($paref); # Sperrzeit Status ermitteln - my $constate = "name='$alias' state='$costate' planningstate='$pstate'"; + my $mode = ConsumerVal ($hash, $c, 'mode', 'can'); + my $constate = "name='$alias' state='$costate'"; + $constate .= " mode='$mode' planningstate='$pstate'"; $constate .= " remainLockTime='$rlt'" if($rlt); $constate .= " info='$supplmnt'" if($supplmnt); @@ -6846,7 +7013,7 @@ return; } ################################################################ -# Consumer Status Automatic Modus abfragen und im +# Consumer Status Automatic Modus abfragen und im # Hash consumers aktualisieren ################################################################ sub __getAutomaticState { @@ -6857,13 +7024,13 @@ sub __getAutomaticState { my $c = $paref->{consumer}; my $consumer = ConsumerVal ($hash, $c, 'name', ''); # Name Consumer Device - + if (!$consumer || !$defs{$consumer}) { my $err = qq{ERROR - the device "$consumer" doesn't exist anymore! Delete or change the attribute "consumer${c}".}; Log3 ($name, 1, "$name - $err"); return; } - + my $dswitch = ConsumerVal ($hash, $c, 'dswitch', ''); # alternatives Schaltdevice if ($dswitch) { @@ -6876,7 +7043,7 @@ sub __getAutomaticState { else { $dswitch = $consumer; } - + my $autord = ConsumerVal ($hash, $c, 'autoreading', ''); # Readingname f. Automatiksteuerung my $auto = 1; $auto = ReadingsVal ($dswitch, $autord, 1) if($autord); # Reading für Ready-Bit -> Einschalten möglich ? @@ -7084,7 +7251,7 @@ return; ################################################################### sub __planInitialSwitchTime { my $paref = shift; - + my $hash = $paref->{hash}; my $name = $paref->{name}; my $c = $paref->{consumer}; @@ -7098,7 +7265,7 @@ sub __planInitialSwitchTime { qq{ alias: }.ConsumerVal ($hash, $c, 'alias', '')); Log3 ($name, 4, qq{$name DEBUG> Planning consumer "$c" - $dnp}); } - + return; } @@ -7155,36 +7322,40 @@ return $dnp; } ################################################################### -# Consumer Review Schaltzeiten und neu planen wenn der +# Consumer Review Schaltzeiten und neu planen wenn der # Consumer noch nicht in Operation oder finished ist # (nach Consumer Schaltung) ################################################################### sub __reviewSwitchTime { my $paref = shift; - + my $hash = $paref->{hash}; my $c = $paref->{consumer}; - my $pstate = ConsumerVal ($hash, $c, 'planstate', ''); - my $simpCstat = simplifyCstate ($pstate); - - if ($simpCstat =~ /planned|suspended/xs) { - my $minute = $paref->{minute}; - - for my $m (qw(15 45)) { - if (int $minute >= $m) { - if (!exists $hash->{HELPER}{$c.'M'.$m.'DONE'}) { - my $name = $paref->{name}; - $hash->{HELPER}{$c.'M'.$m.'DONE'} = 1; - - debugLog ($paref, "consumerPlanning", qq{consumer "$c" - Review switch time planning name: }.ConsumerVal ($hash, $c, 'name', ''). - qq{ alias: }.ConsumerVal ($hash, $c, 'alias', '')); + my $pstate = ConsumerVal ($hash, $c, 'planstate', ''); + my $plswon = ConsumerVal ($hash, $c, 'planswitchon', 0); # bisher geplante Switch on Zeit + my $simpCstat = simplifyCstate ($pstate); + my $t = $paref->{t}; - ___doPlanning ($paref); + if ($simpCstat =~ /planned|suspended/xs) { + if ($t < $plswon || $t > $plswon + 300) { # geplante Switch-On Zeit ist 5 Min überschritten und immer noch "planned" + my $minute = $paref->{minute}; + + for my $m (qw(15 45)) { + if (int $minute >= $m) { + if (!exists $hash->{HELPER}{$c.'M'.$m.'DONE'}) { + my $name = $paref->{name}; + $hash->{HELPER}{$c.'M'.$m.'DONE'} = 1; + + debugLog ($paref, "consumerPlanning", qq{consumer "$c" - Review switch time planning name: }.ConsumerVal ($hash, $c, 'name', ''). + qq{ alias: }.ConsumerVal ($hash, $c, 'alias', '')); + + ___doPlanning ($paref); + } + } + else { + delete $hash->{HELPER}{$c.'M'.$m.'DONE'}; } } - else { - delete $hash->{HELPER}{$c.'M'.$m.'DONE'}; - } } } else { @@ -7200,7 +7371,7 @@ return; ################################################################### sub ___doPlanning { my $paref = shift; - + my $hash = $paref->{hash}; my $name = $paref->{name}; my $c = $paref->{consumer}; @@ -7231,7 +7402,7 @@ sub ___doPlanning { my $order = 1; for my $k (reverse sort{$a<=>$b} keys %max) { my $ts = timestringToTimestamp ($max{$k}{starttime}); - + $max{$order}{spexp} = $k; $max{$order}{ts} = $ts; $max{$order}{starttime} = $max{$k}{starttime}; @@ -7260,9 +7431,9 @@ sub ___doPlanning { debugLog ($paref, "consumerPlanning", qq{consumer "$c" - epiece1: $epiece1}); - my $mode = ConsumerVal ($hash, $c, "mode", "can"); - my $calias = ConsumerVal ($hash, $c, "alias", ""); - my $mintime = ConsumerVal ($hash, $c, "mintime", $defmintime); # Einplanungsdauer + my $mode = ConsumerVal ($hash, $c, 'mode', 'can'); + my $calias = ConsumerVal ($hash, $c, 'alias', ''); + my $mintime = ConsumerVal ($hash, $c, 'mintime', $defmintime); # Einplanungsdauer debugLog ($paref, "consumerPlanning", qq{consumer "$c" - mode: $mode, mintime: $mintime, relevant method: surplus}); @@ -7294,7 +7465,7 @@ sub ___doPlanning { } for my $ts (sort{$a<=>$b} keys %mtimes) { - + if ($mtimes{$ts}{spexp} >= $epiece1) { # die früheste Startzeit sofern Überschuß größer als Bedarf my $starttime = $mtimes{$ts}{starttime}; @@ -7318,11 +7489,13 @@ sub ___doPlanning { last; } else { - $paref->{ps} = "suspended: the max expected surplus is less $epiece1"; + $paref->{supplement} = "expected max surplus less $epiece1"; + $paref->{ps} = 'suspended:'; ___setConsumerPlanningState ($paref); delete $paref->{ps}; + delete $paref->{supplement}; } } } @@ -7334,34 +7507,37 @@ sub ___doPlanning { qq{nexthour: $max{$o}{nexthour}, today: $max{$o}{today}}); } } - + my $done; - + for my $o (sort{$a<=>$b} keys %max) { - next if(!$max{$o}{today}); # der max-Wert ist _nicht_ heute - + next if(!$max{$o}{today}); # der max-Wert von heute ist auszuwählen + $paref->{elem} = $o; ___planMust ($paref); delete $paref->{elem}; - + $done = 1; - + last; } - - if (!$done) { # es konnte keine Planung mit max für den aktuellen Tag erstellt werden -> Zwangsplanung mit ersten Wert - my $p = (sort{$a<=>$b} keys %max)[0]; - - $paref->{elem} = $p; - ___planMust ($paref); - delete $paref->{elem}; + + if (!$done) { + $paref->{supplement} = 'no max surplus found for current day'; + $paref->{ps} = 'suspended:'; + + ___setConsumerPlanningState ($paref); + + delete $paref->{ps}; + delete $paref->{supplement}; } } - my $planstate = ConsumerVal ($hash, $c, 'planstate', ''); + my $planstate = ConsumerVal ($hash, $c, 'planstate', ''); + my $planspmlt = ConsumerVal ($hash, $c, 'planSupplement', ''); if ($planstate) { - Log3 ($name, 3, qq{$name - Consumer "$calias" $planstate}); + Log3 ($name, 3, qq{$name - Consumer "$calias" $planstate $planspmlt}); } writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben @@ -7426,11 +7602,11 @@ sub ___setConsumerPlanningState { my $lonts = $paref->{lastAutoOnTs}; # Timestamp des letzten On-Schaltens bzw. letzter Fortsetzung im Automatikmodus my $loffts = $paref->{lastAutoOffTs}; # Timestamp des letzten Off-Schaltens bzw. letzter Unterbrechnung im Automatikmodus my $lang = $paref->{lang}; - + $data{$type}{$name}{consumers}{$c}{planSupplement} = $supplmnt; - - return if(!$ps); - + + return if(!$ps); + my ($starttime,$stoptime); if (defined $lonts) { @@ -7505,6 +7681,7 @@ return; ################################################################ # Einschaltgrenzen berücksichtigen und Korrektur # zurück liefern +# notbefore, notafter muß in der Form "hh[:mm]" vorliegen ################################################################ sub ___switchonTimelimits { my $paref = shift; @@ -7517,7 +7694,7 @@ sub ___switchonTimelimits { my $t = $paref->{t}; my $startts; - + if (isSunPath ($hash, $c)) { # SunPath ist in mintime gesetzt my ($riseshift, $setshift) = sunShift ($hash, $c); $startts = CurrentVal ($hash, 'sunriseTodayTs', 0) + $riseshift; @@ -7525,32 +7702,76 @@ 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 ($starthour) = $starttime =~ /\s(\d{2}):/xs; - my $change = q{}; - - if ($t > timestringToTimestamp ($starttime)) { - $starttime = (timestampToTimestring ($t, $lang))[3]; - $change = 'current time'; + my $origtime = $starttime; + my $notbefore = ConsumerVal ($hash, $c, "notbefore", 0); + my $notafter = ConsumerVal ($hash, $c, "notafter", 0); + + my ($err, $vala, $valb); + + if ($notbefore =~ m/^\s*\{.*\}\s*$/xs) { # notbefore als Perl-Code definiert + ($err, $valb) = checkCode ($name, $notbefore, 'cc1'); + if (!$err && checkhhmm ($valb)) { + $notbefore = $valb; + debugLog ($paref, "consumerPlanning", qq{consumer "$c" - got 'notbefore' function result: $valb}); + } + else { + Log3 ($name, 1, "$name - ERROR - the result of the Perl code in 'notbefore' is incorrect: $valb"); + $notbefore = 0; + } } - if ($notbefore && int $starthour < int $notbefore) { - $starthour = $notbefore; + if ($notafter =~ m/^\s*(\{.*\})\s*$/xs) { # notafter als Perl-Code definiert + ($err, $vala) = checkCode ($name, $notafter, 'cc1'); + if (!$err && checkhhmm ($vala)) { + $notafter = $vala; + debugLog ($paref, "consumerPlanning", qq{consumer "$c" - got 'notafter' function result: $vala}) + } + else { + Log3 ($name, 1, "$name - ERROR - the result of the Perl code in the 'notafter' key is incorrect: $vala"); + $notafter = 0; + } + } + + my ($nbfhh, $nbfmm, $nafhh, $nafmm); + + if ($notbefore) { + ($nbfhh, $nbfmm) = split ":", $notbefore; + $nbfmm //= '00'; + $notbefore = (int $nbfhh) . $nbfmm; + } + + if ($notafter) { + ($nafhh, $nafmm) = split ":", $notafter; + $nafmm //= '00'; + $notafter = (int $nafhh) . $nafmm; + } + + debugLog ($paref, "consumerPlanning", qq{consumer "$c" - used 'notbefore' term: }.(defined $notbefore ? $notbefore : '')); + debugLog ($paref, "consumerPlanning", qq{consumer "$c" - used 'notafter' term: } .(defined $notafter ? $notafter : '')); + + my $change = q{}; + + if ($t > timestringToTimestamp ($starttime)) { + $starttime = (timestampToTimestring ($t, $lang))[3]; + $change = 'current time'; + } + + my ($starthour, $startminute) = $starttime =~ /\s(\d{2}):(\d{2}):/xs; + my $start = (int $starthour) . $startminute; + + 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 = $notafter; + if ($notafter && $start > $notafter) { + $nafhh = sprintf "%02d", $nafhh; + $starttime =~ s/\s(\d{2}):(\d{2}):/ $nafhh:$nafmm:/x; $change = 'notafter'; } - $starthour = sprintf("%02d", $starthour); - $starttime =~ s/\s(\d{2}):/ $starthour:/x; - if ($change) { my $cname = ConsumerVal ($hash, $c, "name", ""); debugLog ($paref, "consumerPlanning", qq{consumer "$c" - Planned starttime of "$cname" changed from "$origtime" to "$starttime" due to $change condition}); @@ -7619,7 +7840,7 @@ sub __setConsRcmdState { my $hash = $paref->{hash}; my $name = $paref->{name}; my $type = $paref->{type}; - my $c = $paref->{consumer}; + my $c = $paref->{consumer}; my $debug = $paref->{debug}; my $surplus = CurrentVal ($hash, 'surplus', 0); # aktueller Energieüberschuß @@ -7684,7 +7905,7 @@ sub ___switchConsumerOn { Log3 ($name, 1, "$name - $state"); return $state; } - + my $pstate = ConsumerVal ($hash, $c, 'planstate', ''); my $startts = ConsumerVal ($hash, $c, 'planswitchon', undef); # geplante Unix Startzeit my $oncom = ConsumerVal ($hash, $c, 'oncom', ''); # Set Command für "on" @@ -7692,9 +7913,9 @@ sub ___switchConsumerOn { my $calias = ConsumerVal ($hash, $c, 'alias', $cname); # Consumer Device Alias my $simpCstat = simplifyCstate ($pstate); my $isInTime = isInTimeframe ($hash, $c); - + my ($swoncond,$swoffcond,$infon,$infoff,$err); - + ($swoncond,$infon,$err) = isAddSwitchOnCond ($hash, $c); # zusätzliche Switch on Bedingung Log3 ($name, 1, "$name - $err") if($err); @@ -7726,12 +7947,20 @@ sub ___switchConsumerOn { Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - switching on postponed by >isInLocktime<}); } } - + my $isintable = isInterruptable ($hash, $c, 0, 1); # mit Ausgabe Interruptable Info im Debug my $isConsRcmd = isConsRcmd ($hash, $c); + $paref->{supplement} = 'swoncond not met' if(!$swoncond); + $paref->{supplement} = 'swoffcond met' if($swoffcond); + + if ($paref->{supplement}) { + ___setConsumerPlanningState ($paref); + delete $paref->{supplement}; + } + if ($auto && $oncom && $swoncond && !$swoffcond && !$iilt && # kein Einschalten wenn zusätzliche Switch off Bedingung oder Sperrzeit zutrifft - $simpCstat =~ /planned|priority|starting/xs && $isInTime) { # Verbraucher Start ist geplant && Startzeit überschritten + $simpCstat =~ /planned|priority|starting/xs && $isInTime) { # Verbraucher Start ist geplant && Startzeit überschritten my $mode = ConsumerVal ($hash, $c, "mode", $defcmode); # Consumer Planungsmode my $enable = ___enableSwitchByBatPrioCharge ($paref); # Vorrangladung Batterie ? @@ -7740,9 +7969,9 @@ sub ___switchConsumerOn { if ($mode eq "can" && !$enable) { # Batterieladung - keine Verbraucher "Einschalten" Freigabe $paref->{ps} = "priority charging battery"; - ___setConsumerPlanningState ($paref); + ___setConsumerPlanningState ($paref); - delete $paref->{ps}; + delete $paref->{ps}; } elsif ($mode eq "must" || $isConsRcmd) { # "Muss"-Planung oder Überschuß > Leistungsaufnahme (can) CommandSet(undef,"$dswname $oncom"); @@ -7868,7 +8097,7 @@ return $state; } ################################################################ -# Consumer aktuelle Schaltzustände ermitteln & +# Consumer aktuelle Schaltzustände ermitteln & # logische Zustände ableiten/setzen ################################################################ sub ___setConsumerSwitchingState { @@ -7885,7 +8114,7 @@ sub ___setConsumerSwitchingState { my $auto = ConsumerVal ($hash, $c, 'auto', 1); my $oldpsw = ConsumerVal ($hash, $c, 'physoffon', 'off'); # gespeicherter physischer Schaltzustand my $dowri = 0; - + debugLog ($paref, "consumerSwitching", qq{consumer "$c" - current planning state: $simpCstat \n}); if (isConsumerPhysOn ($hash, $c) && $simpCstat eq 'starting') { @@ -7949,7 +8178,7 @@ sub ___setConsumerSwitchingState { delete $paref->{lastAutoOffTs}; $state = qq{Consumer '$calias' switched off (interrupted)}; - $dowri = 1; + $dowri = 1; } elsif ($oldpsw eq 'off' && isConsumerPhysOn ($hash, $c)){ $paref->{supplement} = "$hqtxt{wexso}{$paref->{lang}}"; @@ -7971,7 +8200,7 @@ sub ___setConsumerSwitchingState { $state = qq{Consumer '$calias' was external switched off}; $dowri = 1; } - + if ($dowri) { writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben Log3 ($name, 2, "$name - $state"); @@ -8014,8 +8243,8 @@ sub __setPhysSwState { my $type = $paref->{type}; my $c = $paref->{consumer}; - my $pon = isConsumerPhysOn ($hash, $c) ? 'on' : 'off'; - + my $pon = isConsumerPhysOn ($hash, $c) ? 'on' : 'off'; + $data{$type}{$name}{consumers}{$c}{physoffon} = $pon; return; @@ -8629,7 +8858,7 @@ sub collectAllRegConsumers { my $hash = $paref->{hash}; my $name = $paref->{name}; my $type = $paref->{type}; - + return if(CurrentVal ($hash, 'consumerCollected', 0)); # Abbruch wenn Consumer bereits gesammelt delete $data{$type}{$name}{current}{consumerdevs}; @@ -8778,7 +9007,7 @@ sub collectAllRegConsumers { $data{$type}{$name}{consumers}{$c}{sunriseshift} = $riseshift if(defined $riseshift); # Verschiebung (Sekunden) Sonnenaufgang bei SunPath Verwendung $data{$type}{$name}{consumers}{$c}{sunsetshift} = $setshift if(defined $setshift); # Verschiebung (Sekunden) Sonnenuntergang bei SunPath Verwendung } - + $data{$type}{$name}{current}{consumerCollected} = 1; Log3 ($name, 3, "$name - all registered consumers collected"); @@ -9078,16 +9307,16 @@ sub _checkSetupNotComplete { #} ########################################################################################## - my $is = ReadingsVal ($name, 'inverterStrings', undef); # String Konfig - my $wedev = ReadingsVal ($name, 'currentWeatherDev', undef); # Device Vorhersage Wetterdaten (Bewölkung etc.) - my $radev = ReadingsVal ($name, 'currentRadiationAPI', undef); # Device Strahlungsdaten Vorhersage - my $indev = ReadingsVal ($name, 'currentInverterDev', undef); # Inverter Device - my $medev = ReadingsVal ($name, 'currentMeterDev', undef); # Meter Device + my $is = ReadingsVal ($name, 'inverterStrings', undef); # String Konfig + my $wedev = ReadingsVal ($name, 'currentWeatherDev', undef); # Device Vorhersage Wetterdaten (Bewölkung etc.) + my $radev = ReadingsVal ($name, 'currentRadiationAPI', undef); # Device Strahlungsdaten Vorhersage + my $indev = ReadingsVal ($name, 'currentInverterDev', undef); # Inverter Device + my $medev = ReadingsVal ($name, 'currentMeterDev', undef); # Meter Device - my $peaks = ReadingsVal ($name, 'modulePeakString', undef); # String Peak - my $dir = ReadingsVal ($name, 'moduleDirection', undef); # Modulausrichtung Konfig (Azimut) - my $ta = ReadingsVal ($name, 'moduleTiltAngle', undef); # Modul Neigungswinkel Konfig - my $mrt = ReadingsVal ($name, 'moduleRoofTops', undef); # RoofTop Konfiguration (SolCast API) + my $peaks = ReadingsVal ($name, 'modulePeakString', undef); # String Peak + my $dir = ReadingsVal ($name, 'moduleDirection', undef); # Modulausrichtung Konfig (Azimut) + my $ta = ReadingsVal ($name, 'moduleTiltAngle', undef); # Modul Neigungswinkel Konfig + my $mrt = ReadingsVal ($name, 'moduleRoofTops', undef); # RoofTop Konfiguration (SolCast API) my $vrmcr = SolCastAPIVal ($hash, '?VRM', '?API', 'credentials', ''); # Victron VRM Credentials gesetzt @@ -9101,11 +9330,11 @@ sub _checkSetupNotComplete { my $height = AttrNum ($name, 'graphicBeamHeight', 200); my $lang = getLang ($hash); - if(IsDisabled($name)) { + if ((controller($name))[1] || (controller($name))[2]) { $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= "
"; - $ret .= qq{SolarForecast device $link is disabled}; + $ret .= qq{SolarForecast device $link is disabled or inactive}; $ret .= "
"; @@ -9120,7 +9349,7 @@ sub _checkSetupNotComplete { my $chkicon = "$img"; my $chktitle = $htitles{plchk}{$lang}; - if(!$is || !$wedev || !$radev || !$indev || !$medev || !$peaks || + if (!$is || !$wedev || !$radev || !$indev || !$medev || !$peaks || (isSolCastUsed ($hash) ? (!$rip || !$mrt) : isVictronKiUsed ($hash) ? !$vrmcr : (!$dir || !$ta )) || (isForecastSolarUsed ($hash) ? !$coset : '') || !defined $pv0) { @@ -9559,8 +9788,8 @@ sub __createAutokorrIcon { my $lang = $paref->{lang}; my $aciimg; - my $acitit = q{}; - my $acu = isAutoCorrUsed ($name); + my $acitit = q{}; + my ($acu, $aln) = isAutoCorrUsed ($name); if ($acu =~ /on/xs) { $aciimg = FW_makeImage ('10px-kreis-gruen.png', $htitles{on}{$lang}." ($acu)"); @@ -11336,35 +11565,37 @@ sub calcValueImproves { my $chour = $paref->{chour}; my $t = $paref->{t}; # aktuelle Unix-Zeit - my $idts = ReadingsTimestamp ($name, "currentInverterDev", ""); # Definitionstimestamp des Inverterdevice + my $idts = ReadingsTimestamp ($name, 'currentInverterDev', ''); # Definitionstimestamp des Inverterdevice return if(!$idts); - my $acu = isAutoCorrUsed ($name); + my ($acu, $aln) = isAutoCorrUsed ($name); if ($acu) { $idts = timestringToTimestamp ($idts); - readingsSingleUpdate ($hash, ".pvCorrectionFactor_Auto_Soll", $acu, 0) if($acu =~ /on/xs); + + readingsSingleUpdate ($hash, '.pvCorrectionFactor_Auto_Soll', ($aln ? $acu : $acu.' noLearning'), 0) if($acu =~ /on/xs); if ($t - $idts < 7200) { my $rmh = sprintf "%.1f", ((7200 - ($t - $idts)) / 3600); + readingsSingleUpdate ($hash, 'pvCorrectionFactor_Auto', "standby (remains in standby for $rmh hours)", 0); Log3 ($name, 4, "$name - Correction usage is in standby. It starts in $rmh hours."); - readingsSingleUpdate ($hash, "pvCorrectionFactor_Auto", "standby (remains in standby for $rmh hours)", 0); return; } else { - my $acuset = ReadingsVal ($name, ".pvCorrectionFactor_Auto_Soll", 'on_simple'); - readingsSingleUpdate ($hash, 'pvCorrectionFactor_Auto', $acuset, 0); + my $acuset = ReadingsVal ($name, '.pvCorrectionFactor_Auto_Soll', 'on_simple'); + readingsSingleUpdate ($hash, 'pvCorrectionFactor_Auto', $acuset, 0); } } else { - readingsSingleUpdate ($hash, ".pvCorrectionFactor_Auto_Soll", "off", 0); + readingsSingleUpdate ($hash, '.pvCorrectionFactor_Auto_Soll', 'off', 0); } Log3 ($name, 4, "$name - INFO - The correction factors are now calculated and stored proactively independent of the autocorrection usage"); $paref->{acu} = $acu; + $paref->{aln} = $aln; for my $h (1..23) { next if(!$chour || $h > $chour); @@ -11377,6 +11608,7 @@ sub calcValueImproves { delete $paref->{h}; } + delete $paref->{aln}; delete $paref->{acu}; return; @@ -11392,16 +11624,23 @@ sub _calcCaQcomplex { my $name = $paref->{name}; my $debug = $paref->{debug}; my $acu = $paref->{acu}; + my $aln = $paref->{aln}; # Autolearning my $h = $paref->{h}; - my $maxvar = AttrVal ($name, 'affectMaxDayVariance', $defmaxvar); # max. Korrekturvarianz - my $sr = ReadingsVal ($name, ".pvCorrectionFactor_".sprintf("%02d",$h)."_cloudcover", ""); + my $maxvar = AttrVal ($name, 'affectMaxDayVariance', $defmaxvar); # max. Korrekturvarianz + my $sr = ReadingsVal ($name, '.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', ''); - if ($sr eq "done") { + if ($sr eq 'done') { # Log3 ($name, 1, "$name DEBUG> Complex Corrf -> factor Hour: ".sprintf("%02d",$h)." already calculated"); return; } + if (!$aln) { + storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done'); + debugLog ($paref, 'pvCorrection', "Autolearning is switched off for hour: $h -> skip the recalculation of the complex correction factor"); + return; + } + debugLog ($paref, 'pvCorrection', "start calculation complex correction factor for hour: $h"); my $pvre = CircularVal ($hash, sprintf("%02d",$h), 'pvrl', 0); @@ -11475,16 +11714,23 @@ sub _calcCaQsimple { my $name = $paref->{name}; my $date = $paref->{date}; my $acu = $paref->{acu}; + my $aln = $paref->{aln}; # Autolearning my $h = $paref->{h}; my $maxvar = AttrVal($name, 'affectMaxDayVariance', $defmaxvar); # max. Korrekturvarianz - my $sr = ReadingsVal ($name, ".pvCorrectionFactor_".sprintf("%02d",$h)."_apipercentil", ""); + my $sr = ReadingsVal ($name, '.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', ''); if($sr eq "done") { # debugLog ($paref, 'pvCorrection', "Simple Corrf factor Hour: ".sprintf("%02d",$h)." already calculated"); return; } + if (!$aln) { + storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done'); + debugLog ($paref, 'pvCorrection', "Autolearning is switched off for hour: $h -> skip the recalculation of the simple correction factor"); + return; + } + debugLog ($paref, 'pvCorrection', "start calculation simple correction factor for hour: $h"); my $pvre = CircularVal ($hash, sprintf("%02d",$h), 'pvrl', 0); @@ -11549,6 +11795,7 @@ sub _addHourAiRawdata { my $paref = shift; my $hash = $paref->{hash}; my $name = $paref->{name}; + my $aln = $paref->{aln}; # Autolearning my $h = $paref->{h}; my $rho = sprintf "%02d", $h; @@ -11556,6 +11803,12 @@ sub _addHourAiRawdata { return if($sr eq "done"); + if (!$aln) { + storeReading ('.signaldone_'.sprintf("%02d",$h), 'done'); + debugLog ($paref, 'pvCorrection', "Autolearning is switched off for hour: $h -> skip add AI raw data"); + return; + } + debugLog ($paref, 'aiProcess', "start add AI raw data for hour: $h"); $paref->{ood} = 1; # Only One Day @@ -11645,7 +11898,7 @@ sub __Pv_Fc_Complex_Dnum_Hist { last if( $dnum == $calcd); } else { - debugLog ($paref, 'pvCorrection', "Complex Corrf -> cloudiness range different: $range/$histwcc (current/historical) -> ignore stored $dayfa/$hour (Day/hour)"); + debugLog ($paref, 'pvCorrection', "Complex Corrf -> cloudiness range different: $range/$histwcc (current/historical) -> ignore stored Day:$dayfa, hour:$hour"); } } @@ -11874,7 +12127,8 @@ sub finishTrain { name => $name, type => $type, file => $aitrained.$name, - cachename => 'aitrained' + cachename => 'aitrained', + title => 'aiTrainedData' } ); } @@ -12228,16 +12482,23 @@ sub aiAddRawData { for my $pvd (sort keys %{$data{$type}{$name}{pvhist}}) { next if(!$pvd); - + if ($ood) { next if($pvd ne $paref->{day}); } - + last if(int $pvd > int $day); for my $hod (sort keys %{$data{$type}{$name}{pvhist}{$pvd}}) { next if(!$hod || $hod eq '99' || ($rho && $hod ne $rho)); + my $pvrlvd = HistoryVal ($hash, $pvd, $hod, 'pvrlvd', 1); + + if (!$pvrlvd) { # Datensatz ignorieren wenn als invalid gekennzeichnet + debugLog ($paref, 'aiProcess', qq{AI raw data is marked as invalid and is ignored - day: $pvd, hod: $hod}); + next; + } + my $rad1h = HistoryVal ($hash, $pvd, $hod, 'rad1h', undef); next if(!$rad1h || $rad1h <= 0); @@ -12263,7 +12524,7 @@ sub aiAddRawData { $dosave = 1; - debugLog ($paref, 'aiProcess', qq{AI Raw data added - idx: $ridx, day: $pvd, hod: $hod, rad1h: $rad1h, pvrl: $pvrl, wcc: $cbin, wrp: $rbin, temp: $tbin}); + debugLog ($paref, 'aiProcess', qq{AI raw data added - idx: $ridx, day: $pvd, hod: $hod, rad1h: $rad1h, pvrl: $pvrl, wcc: $cbin, wrp: $rbin, temp: $tbin}); } } @@ -12351,6 +12612,7 @@ sub setPVhistory { my $day = $paref->{day}; my $dayname = $paref->{dayname}; # aktueller Wochentagsname my $histname = $paref->{histname} // qq{}; + my $pvrlvd = $paref->{pvrlvd}; # 1: Eintrag 'pvrl' wird im Lernprozess berücksichtigt my $ethishour = $paref->{ethishour} // 0; my $etotal = $paref->{etotal}; my $batinthishour = $paref->{batinthishour}; # Batterieladung in Stunde @@ -12413,14 +12675,15 @@ sub setPVhistory { $data{$type}{$name}{pvhist}{$day}{99}{batsetsoc} = $batsetsoc; } - if ($histname eq "pvrl") { # realer Energieertrag + if ($histname eq 'pvrl') { # realer Energieertrag $val = $ethishour; - $data{$type}{$name}{pvhist}{$day}{$nhour}{pvrl} = $ethishour; + $data{$type}{$name}{pvhist}{$day}{$nhour}{pvrl} = $ethishour; + $data{$type}{$name}{pvhist}{$day}{$nhour}{pvrlvd} = $pvrlvd; my $pvrlsum = 0; for my $k (keys %{$data{$type}{$name}{pvhist}{$day}}) { next if($k eq "99"); - $pvrlsum += HistoryVal ($hash, $day, $k, "pvrl", 0); + $pvrlsum += HistoryVal ($hash, $day, $k, 'pvrl', 0); } $data{$type}{$name}{pvhist}{$day}{99}{pvrl} = $pvrlsum; } @@ -12636,6 +12899,7 @@ sub listDataPool { my $ret; for my $key (sort {$a<=>$b} keys %{$h->{$day}}) { my $pvrl = HistoryVal ($hash, $day, $key, 'pvrl', '-'); + my $pvrlvd = HistoryVal ($hash, $day, $key, 'pvrlvd', '-'); my $pvfc = HistoryVal ($hash, $day, $key, 'pvfc', '-'); my $gcon = HistoryVal ($hash, $day, $key, 'gcons', '-'); my $con = HistoryVal ($hash, $day, $key, 'con', '-'); @@ -12657,7 +12921,7 @@ sub listDataPool { my $rad1h = HistoryVal ($hash, $day, $key, 'rad1h', '-'); $ret .= "\n " if($ret); - $ret .= $key." => etotal: $etotal, pvfc: $pvfc, pvrl: $pvrl, rad1h: $rad1h"; + $ret .= $key." => etotal: $etotal, pvfc: $pvfc, pvrl: $pvrl, pvrlvd: $pvrlvd, rad1h: $rad1h"; $ret .= "\n "; $ret .= "confc: $confc, con: $con, gcon: $gcon, gfeedin: $gfeedin"; $ret .= "\n "; @@ -12772,7 +13036,7 @@ sub listDataPool { $cret .= $ckey." => ".ConsumerVal ($hash, $idx, $ckey, "")."\n "; } } - + $sq .= $idx." => ".$cret."\n"; } } @@ -12787,7 +13051,7 @@ sub listDataPool { my $pvaifc = CircularVal ($hash, $idx, "pvaifc", '-'); my $pvapifc = CircularVal ($hash, $idx, "pvapifc", '-'); my $aihit = CircularVal ($hash, $idx, "aihit", '-'); - my $pvrl = CircularVal ($hash, $idx, "pvrl", '-'); + my $pvrl = CircularVal ($hash, $idx, 'pvrl', '-'); my $confc = CircularVal ($hash, $idx, "confc", '-'); my $gcons = CircularVal ($hash, $idx, "gcons", '-'); my $gfeedin = CircularVal ($hash, $idx, "gfeedin", '-'); @@ -13050,10 +13314,10 @@ sub checkPlantConfig { my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - my $lang = AttrVal ($name, 'ctrlLanguage', AttrVal ('global', 'language', $deflang)); - my $pcf = ReadingsVal ($name, 'pvCorrectionFactor_Auto', ''); - my $raname = ReadingsVal ($name, 'currentRadiationAPI', ''); - my $acu = isAutoCorrUsed ($name); + my $lang = AttrVal ($name, 'ctrlLanguage', AttrVal ('global', 'language', $deflang)); + my $pcf = ReadingsVal ($name, 'pvCorrectionFactor_Auto', ''); + my $raname = ReadingsVal ($name, 'currentRadiationAPI', ''); + my ($acu, $aln) = isAutoCorrUsed ($name); my $cf = 0; # config fault: 1 -> Konfig fehlerhaft, 0 -> Konfig ok my $wn = 0; # Warnung wenn 1 @@ -13587,7 +13851,7 @@ return; ################################################################ sub timestampToTimestring { my $epoch = shift; - my $lang = shift; + my $lang = shift // ''; return if($epoch !~ /[0-9]/xs); @@ -13772,23 +14036,23 @@ sub createAssociatedWith { my (@cd,@nd); my ($afc,$ara,$ain,$ame,$aba,$h); - my $fcdev = ReadingsVal($name, "currentWeatherDev", ""); # Weather forecast Device + my $fcdev = ReadingsVal($name, 'currentWeatherDev', ''); # Weather forecast Device ($afc,$h) = parseParams ($fcdev); $fcdev = $afc->[0] // ""; - my $radev = ReadingsVal($name, "currentRadiationAPI", ""); # Radiation forecast Device + my $radev = ReadingsVal($name, 'currentRadiationAPI', ''); # Radiation forecast Device ($ara,$h) = parseParams ($radev); $radev = $ara->[0] // ""; - my $indev = ReadingsVal($name, "currentInverterDev", ""); # Inverter Device + my $indev = ReadingsVal($name, 'currentInverterDev', ''); # Inverter Device ($ain,$h) = parseParams ($indev); $indev = $ain->[0] // ""; - my $medev = ReadingsVal($name, "currentMeterDev", ""); # Meter Device + my $medev = ReadingsVal($name, 'currentMeterDev', ''); # Meter Device ($ame,$h) = parseParams ($medev); $medev = $ame->[0] // ""; - my $badev = ReadingsVal($name, "currentBatteryDev", ""); # Battery Device + my $badev = ReadingsVal($name, 'currentBatteryDev', ''); # Battery Device ($aba,$h) = parseParams ($badev); $badev = $aba->[0] // ""; @@ -13899,9 +14163,9 @@ sub isPrepared4AI { my $hash = shift; my $full = shift // q{}; # wenn true -> auch Auswertung ob on_.*_ai gesetzt ist - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - my $acu = isAutoCorrUsed ($name); + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my ($acu, $aln) = isAutoCorrUsed ($name); my $err; @@ -14395,13 +14659,15 @@ return $ret; ################################################################ # welche PV Autokorrektur wird verwendet ? # Standard bei nur "on" -> on_simple +# $aln: 1 - Lernen aktiviert (default) +# 0 - Lernen deaktiviert ################################################################ sub isAutoCorrUsed { my $name = shift; my $cauto = ReadingsVal ($name, 'pvCorrectionFactor_Auto', 'off'); - my $ret = $cauto =~ /on_simple_ai/xs ? 'on_simple_ai' : + my $acu = $cauto =~ /on_simple_ai/xs ? 'on_simple_ai' : $cauto =~ /on_simple/xs ? 'on_simple' : $cauto =~ /on_complex_ai/xs ? 'on_complex_ai' : $cauto =~ /on_complex/xs ? 'on_complex' : @@ -14409,7 +14675,9 @@ sub isAutoCorrUsed { $cauto =~ /on/xs ? 'on_simple' : q{}; -return $ret; + my $aln = $cauto =~ /noLearning/xs ? 0 : 1; + +return ($acu, $aln); } ################################################################ @@ -14534,6 +14802,78 @@ sub checkRegex { 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; + my $cc1 = shift // 0; # wenn 1 checkCode1 ausführen + + my $err; + + if (!$val || $val !~ m/^\s*\{.*\}\s*$/xs) { + return qq{Usage of $name is wrong. The function has to be specified as "{}"}; + } + + if ($cc1) { + ($err, $val) = checkCode1 ($name, $val); + return ($err, $val); + } + + my %specials = ( "%DEVICE" => $name, + "%READING" => $name, + "%VALUE" => 1, + "%UNIT" => 'kW', + ); + + $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); +} + +################################################################ +# prüfen validen Code in $val +################################################################ +sub checkCode1 { + my $name = shift; + my $val = shift; + + my $hash = $defs{$name}; + + $val =~ m/^\s*(\{.*\})\s*$/xs; + $val = $1; + $val = eval $val; + return $@ if($@); + +return ('', $val); +} + ################################################################ # die eingestellte Modulsprache ermitteln ################################################################ @@ -15234,7 +15574,7 @@ return $def; # #################################################################################################################### sub ConsumerVal { - my $hash = shift; + my $hash = shift; my $co = shift; my $key = shift; my $def = shift; @@ -15450,15 +15790,15 @@ to ensure that the system configuration is correct.
- +
  • batteryTrigger <1on>=<Value> <1off>=<Value> [<2on>=<Value> <2off>=<Value> ...]

    Generates triggers when the battery charge exceeds or falls below certain values (SoC in %).
    - If the last three SoC measurements exceed a defined Xon-Bedingung, the reading batteryTrigger_X = on + If the last three SoC measurements exceed a defined Xon-Bedingung, the reading batteryTrigger_X = on is created/set.
    - If the last three SoC measurements fall below a defined Xoff-Bedingung, the reading + If the last three SoC measurements fall below a defined Xoff-Bedingung, the reading batteryTrigger_X = off is created/set.
    Any number of trigger conditions can be specified. Xon/Xoff conditions do not necessarily have to be defined in pairs.
    @@ -15746,8 +16086,9 @@ to ensure that the system configuration is correct. which real strings exist.

      - Example:
      + Examples:
      set <name> inverterStrings eastroof,southgarage,S3
      + set <name> inverterStrings KI-based
@@ -15763,7 +16104,7 @@ to ensure that the system configuration is correct. a sum to the string name KI-based.

    - Example:
    + Examples:
    set <name> modulePeakString eastroof=5.1 southgarage=2.0 S3=7.2
    set <name> modulePeakString KI-based=14.3 (for AI based API)
@@ -15846,6 +16187,43 @@ to ensure that the system configuration is correct.
+
    + +
  • operatingMemory backup | save | recover-<File>

    + + The pvHistory (PVH) and pvCircular (PVC) components of the internal cache database are stored in the file system.
    + The target directory is "../FHEM/FhemUtils". This process is carried out regularly by the module in the background.

    + +
      + + + + + + + + + + + + +
      backup Saves the active in-memory structures with the current timestamp.
      ctrlBackupFilesKeep generations of the files are saved. Older versions are deleted.
      Files: PVH_SolarForecast_<name>_<Timestamp>, PVC_SolarForecast_<name>_<Timestamp>
      save The active in-memory structures are saved.
      Files: PVH_SolarForecast_<name>, PVC_SolarForecast_<name>
      recover-<File> Restores the data of the selected backup file as an active in-memory structure.
      To avoid inconsistencies, the PVH.* and PVC.* files should be restored in pairs
      with the same time stamp.
      +
    +
    +
+ +
+ +
    + +
  • operationMode

    + The SolarForecast device is deactivated with inactive. The active option reactivates the device. + The behavior corresponds to the "disable" attribute, but is particularly suitable for use in Perl scripts as + compared to the "disable" attribute, it is not necessary to save the device configuration. +
  • +
+
+
  • plantConfiguration

    @@ -16083,17 +16461,6 @@ to ensure that the system configuration is correct.

-
    - -
  • writeHistory

    - - The internal cache database is stored in the file system. By default, this process is - regularly in the background. In the internal "LCACHEFILE", the last file written and the time - of the last storage is documented.
    -
  • -
-
-
@@ -16220,29 +16587,30 @@ to ensure that the system configuration is correct.
    - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + +
    etotal total energy yield (Wh) at the beginning of the hour
    pvfc the predicted PV yield (Wh)
    pvrl real PV generation (Wh)
    gcon real power consumption (Wh) from the electricity grid
    confc expected energy consumption (Wh)
    con real energy consumption (Wh) of the house
    gfeedin real feed-in (Wh) into the electricity grid
    batintotal total battery charge (Wh) at the beginning of the hour
    batin Hour battery charge (Wh)
    batouttotal total battery discharge (Wh) at the beginning of the hour
    batout Battery discharge of the hour (Wh)
    batmaxsoc maximum SOC (%) of the day
    batsetsoc optimum SOC setpoint (%) for the day
    wid Weather identification number
    wcc effective cloud cover
    wrp Probability of precipitation > 0.1 mm during the respective hour
    pvcorrf Autocorrection factor used / forecast quality achieved
    rad1h global radiation (kJ/m2)
    csmtXX total energy consumption of ConsumerXX
    csmeXX Energy consumption of ConsumerXX in the hour of the day (hour 99 = daily energy consumption)
    minutescsmXX total active minutes in the hour of ConsumerXX
    hourscsmeXX average hours of an active cycle of ConsumerXX of the day
    cyclescsmXX Number of active cycles of ConsumerXX of the day
    etotal total energy yield (Wh) at the beginning of the hour
    pvfc the predicted PV yield (Wh)
    pvrl real PV generation (Wh)
    pvrlvd 1-'pvrl' is valid and is taken into account in the learning process, 0-'pvrl' is assessed as abnormal
    gcon real power consumption (Wh) from the electricity grid
    confc expected energy consumption (Wh)
    con real energy consumption (Wh) of the house
    gfeedin real feed-in (Wh) into the electricity grid
    batintotal total battery charge (Wh) at the beginning of the hour
    batin Hour battery charge (Wh)
    batouttotal total battery discharge (Wh) at the beginning of the hour
    batout Battery discharge of the hour (Wh)
    batmaxsoc maximum SOC (%) of the day
    batsetsoc optimum SOC setpoint (%) for the day
    wid Weather identification number
    wcc effective cloud cover
    wrp Probability of precipitation > 0.1 mm during the respective hour
    pvcorrf Autocorrection factor used / forecast quality achieved
    rad1h global radiation (kJ/m2)
    csmtXX total energy consumption of ConsumerXX
    csmeXX Energy consumption of ConsumerXX in the hour of the day (hour 99 = daily energy consumption)
    minutescsmXX total active minutes in the hour of ConsumerXX
    hourscsmeXX average hours of an active cycle of ConsumerXX of the day
    cyclescsmXX Number of active cycles of ConsumerXX of the day
@@ -16428,6 +16796,8 @@ to ensure that the system configuration is correct.
  • affectMaxDayVariance <Zahl>
    Maximum change size of the PV prediction factor (Reading pvCorrectionFactor_XX) per day.
    + This setting has no influence on the learning and forecasting behavior of any AI support used + (pvCorrectionFactor_Auto).
    (default: 0.5)

  • @@ -16490,9 +16860,9 @@ 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>]]
    + [mode=<mode>] [icon=<Icon>] [mintime=<minutes> | SunPath[:<Offset_Sunrise>:<Offset_Sunset>]]
    + [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>]
    [interruptable=<Option>] [noshow=<Option>]

    @@ -16556,7 +16926,8 @@ to ensure that the system configuration is correct. can - Scheduling takes place at the time when there is probably enough PV surplus available (default).          The consumer is not started at the time of planning if the PV surplus is insufficient. must - The consumer is optimally planned, even if there will probably not be enough PV surplus. -            The consumer is started even if there is insufficient PV surplus. +            The load is started even if there is insufficient PV surplus, provided that + a set "swoncond" condition is met and "swoffcond" is not met. icon Icon to represent the consumer in the overview graphic (optional) @@ -16589,9 +16960,11 @@ 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 'hour[:minute]' (optional) + The <Expression> has the format hh[:mm] or is Perl code enclosed in {...} that returns hh[:mm]. - notafter Schedule start time consumer not after specified hour (01..23) (optional) + notafter Schedule start time consumer not after specified time 'hour[:minute]' (optional) + The <Expression> has the format hh[:mm] or is Perl code enclosed in {...} that returns hh[:mm]. 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. @@ -16655,8 +17028,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={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=noShow
  • @@ -16684,14 +17057,22 @@ to ensure that the system configuration is correct. should be regularly reloaded.
    + + +
  • ctrlBackupFilesKeep
    + Defines the number of generations of backup files + (see also set <name> operatingMemory backup).
    + (default: 3) +
  • +
    -
  • ctrlBatSocManagement lowSoc=<Value> upSoC=<Value> [maxSoC=<Value>] [careCycle=<Value>]
    +
  • ctrlBatSocManagement lowSoc=<Value> upSoC=<Value> [maxSoC=<Value>] [careCycle=<Value>]

    If a battery device (currentBatteryDev) is installed, this attribute activates the battery SoC management.
    The Battery_OptimumTargetSoC reading contains the optimum minimum SoC calculated by the module.
    The Battery_ChargeRequest reading is set to '1' if the current SoC has fallen below the minimum SoC.
    In this case, the battery should be forcibly charged, possibly with mains power.
    - The readings can be used to control the SoC (State of Charge) and to control the charging current used for the + The readings can be used to control the SoC (State of Charge) and to control the charging current used for the battery.
    The module itself does not control the battery.

    @@ -16789,9 +17170,9 @@ to ensure that the system configuration is correct.
  • ctrlInterval <Sekunden>
    Repetition interval of the data collection.
    - Regardless of the set interval, data is collected automatically a few seconds before the end and after the start + Regardless of the set interval, data is collected automatically a few seconds before the end and after the start of a full hour.
    - If ctrlInterval is explicitly set to "0", no automatic data collection takes place and must be carried out + If ctrlInterval is explicitly set to "0", no automatic data collection takes place and must be carried out externally with "get <name> data".
    (default: 70)

  • @@ -17781,8 +18162,9 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. welche realen Strings existieren.

      - Beispiel:
      + Beispiele:
      set <name> inverterStrings Ostdach,Südgarage,S3
      + set <name> inverterStrings KI-based
    @@ -17798,7 +18180,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Strings als Summe dem Stringnamen KI-based zuzuordnen.

      - Beispiel:
      + Beispiele:
      set <name> modulePeakString Ostdach=5.1 Südgarage=2.0 S3=7.2
      set <name> modulePeakString KI-based=14.3 (bei KI basierender API)
    @@ -17881,6 +18263,43 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
    +
      + +
    • operatingMemory backup | save | recover-<Datei>

      + + Die Komponenten pvHistory (PVH) und pvCircular (PVC) der internen Cache Datenbank werden im Filesystem gespeichert.
      + Das Zielverzeichnis ist "../FHEM/FhemUtils". Dieser Vorgang wird vom Modul regelmäßig im Hintergrund ausgeführt.

      + +
        + + + + + + + + + + + + +
        backup Sichert die aktiven In-Memory Strukturen mit dem aktuellen Zeitstempel.
        Es werden ctrlBackupFilesKeep Generationen der Dateien gespeichert. Ältere Versionen werden gelöscht.
        Dateien: PVH_SolarForecast_<name>_<Zeitstempel>, PVC_SolarForecast_<name>_<Zeitstempel>
        save Die aktiven In-Memory Strukturen werden gespeichert.
        Dateien: PVH_SolarForecast_<name>, PVC_SolarForecast_<name>
        recover-<Datei> Stellt die Daten der ausgewählten Sicherungsdatei als aktive In-Memory Struktur wieder her.
        Um Inkonsistenzen zu vermeiden, sollten die Dateien PVH.* und PVC.* mit dem gleichen
        Zeitstempel paarweise recovert werden.
        +
      +
      +
    + +
    + +
      + +
    • operationMode

      + Mit inactive wird das SolarForecast Gerät deaktiviert. Die active Option aktiviert das Gerät wieder. + Das Verhalten entspricht dem "disable"-Attribut, eignet sich aber vor allem zum Einsatz in Perl-Skripten da + gegenüber dem "disable"-Attribut keine Speicherung der Gerätekonfiguration nötig ist. +
    • +
    +
    +
    • plantConfiguration

      @@ -17926,13 +18345,19 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
    • pvCorrectionFactor_Auto

      Schaltet die automatische Vorhersagekorrektur ein/aus. - Die Wirkungsweise unterscheidet sich je nach gewählter Methode. + Die Wirkungsweise unterscheidet sich je nach gewählter Methode.
      Das Korrekturverhalten kann mit den Attributen affectNumHistDays und affectMaxDayVariance beeinflusst werden.
      (default: off)

      + noLearning:
      + Mit dieser Option wird die erzeugte PV Energie der aktuellen Stunde vom Lernprozess (Korrekturfaktoren + sowie KI) ausgeschlossen.
      + Die zuvor eingestellte Autokorrekturmethode wird weiterhin angewendet. +

      + on_simple(_ai):
      Bei dieser Methode wird die stündlich vorhergesagte mit der real erzeugten Energiemenge verglichen und daraus ein für die Zukunft verwendeter Korrekturfaktor für die jeweilige Stunde erstellt. Die von der gewählten API gelieferten @@ -18101,7 +18526,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. user Benutzername für das Victron VRM Portal pwd Paßwort für den Zugang zum Victron VRM Portal idsite idSite ist der Bezeichner "XXXXXX" in der Victron VRM Portal Dashboard URL. - URL des Victron VRM Dashboard ist: + URL des Victron VRM Dashboard ist: https://vrm.victronenergy.com/installation/XXXXXX/dashboard
    @@ -18119,17 +18544,6 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
    -
      - -
    • writeHistory

      - - Die interne Cache Datenbank wird im Filesystem gespeichert. Dieser Vorgang wird per default - regelmäßig im Hintergrund ausgeführt. Im Internal "LCACHEFILE" wird die letzte geschriebene Datei und der Zeitpunkt - der letzten Speicherung dokumentiert.
      -
    • -
    -
    -
    @@ -18260,6 +18674,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. etotal totaler Energieertrag (Wh) zu Beginn der Stunde pvfc der prognostizierte PV Ertrag (Wh) pvrl reale PV Erzeugung (Wh) + pvrlvd 1-'pvrl' ist gültig und wird im Lernprozess berücksichtigt, 0-'pvrl' ist als abnormal bewertet gcon realer Leistungsbezug (Wh) aus dem Stromnetz confc erwarteter Energieverbrauch (Wh) con realer Energieverbrauch (Wh) des Hauses @@ -18464,6 +18879,9 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
  • affectMaxDayVariance <Zahl>
    Maximale Änderungsgröße des PV Vorhersagefaktors (Reading pvCorrectionFactor_XX) pro Tag.
    + Auf das Lern- und Prognoseverhalten einer eventuell verwendeten KI-Unterstützung + (pvCorrectionFactor_Auto) hat diese Einstellung keinen + Einfluß.
    (default: 0.5)

  • @@ -18526,10 +18944,10 @@ 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>]]
    - [auto=<Readingname>] [pcurr=<Readingname>:<Einheit>[:<Schwellenwert>]] [etotal=<Readingname>:<Einheit>[:<Schwellenwert>]]
    + [mode=<mode>] [icon=<Icon>] [mintime=<minutes> | SunPath[:<Offset_Sunrise>:<Offset_Sunset>]]
    + [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>]
    [interruptable=<Option>] [noshow=<Option>]


    @@ -18591,7 +19009,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. can - Die Einplanung erfolgt zum Zeitpunkt mit wahrscheinlich genügend verfügbaren PV Überschuß (default)          Der Start des Verbrauchers zum Planungszeitpunkt unterbleibt bei ungenügendem PV-Überschuß. must - der Verbraucher wird optimiert eingeplant auch wenn wahrscheinlich nicht genügend PV Überschuß vorhanden sein wird -            Der Start des Verbrauchers erfolgt auch bei ungenügendem PV-Überschuß. +            Der Start des Verbrauchers erfolgt auch bei ungenügendem PV-Überschuß sofern eine + gesetzte "swoncond" Bedingung erfüllt und "swoffcond" nicht erfüllt ist. icon Icon zur Darstellung des Verbrauchers in der Übersichtsgrafik (optional) @@ -18624,9 +19043,11 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. 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 'Stunde[:Minute]' einplanen (optional) + Der <Ausdruck> hat das Format hh[:mm] oder ist in {...} eingeschlossener Perl-Code der hh[:mm] zurückgibt. - notafter Startzeitpunkt Verbraucher nicht nach angegebener Stunde (01..23) einplanen (optional) + notafter Startzeitpunkt Verbraucher nicht nach angegebener Zeit 'Stunde[:Minute]' einplanen (optional) + Der <Ausdruck> hat das Format hh[:mm] oder ist in {...} eingeschlossener Perl-Code der hh[:mm] zurückgibt. 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. @@ -18690,8 +19111,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={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=noShow
  • @@ -18719,16 +19140,24 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. regelmäßig neu geladen werden sollen.
    + + +
  • ctrlBackupFilesKeep
    + Legt die Anzahl der Generationen von Sicherungsdateien + (siehe set <name> operatingMemory backup) fest.
    + (default: 3) +
  • +
    -
  • ctrlBatSocManagement lowSoc=<Wert> upSoC=<Wert> [maxSoC=<Wert>] [careCycle=<Wert>]
    +
  • ctrlBatSocManagement lowSoc=<Wert> upSoC=<Wert> [maxSoC=<Wert>] [careCycle=<Wert>]

    Sofern ein Batterie Device (currentBatteryDev) installiert ist, aktiviert dieses Attribut das Batterie SoC-Management.
    Das Reading Battery_OptimumTargetSoC enthält den vom Modul berechneten optimalen Mindest-SoC.
    Das Reading Battery_ChargeRequest wird auf '1' gesetzt, wenn der aktuelle SoC unter den Mindest-SoC gefallen ist.
    In diesem Fall sollte die Batterie, unter Umständen mit Netzstrom, zwangsgeladen werden.
    - Die Readings können zur Steuerung des SoC (State of Charge) sowie zur Steuerung des verwendeten Ladestroms + Die Readings können zur Steuerung des SoC (State of Charge) sowie zur Steuerung des verwendeten Ladestroms der Batterie verwendet werden.
    Durch das Modul selbst findet keine Steuerung der Batterie statt.

    @@ -18826,8 +19255,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
  • ctrlInterval <Sekunden>
    Wiederholungsintervall der Datensammlung.
    - Unabhängig vom eingestellten Intervall erfolgt einige Sekunden vor dem Ende sowie nach dem Beginn einer - vollen Stunde eine automatische Datensammlung.
    + Unabhängig vom eingestellten Intervall erfolgt einige Sekunden vor dem Ende sowie nach dem Beginn einer + vollen Stunde eine automatische Datensammlung.
    Ist ctrlInterval explizit auf "0" gesetzt, erfolgt keinerlei automatische Datensammlung und muss mit "get <name> data" extern erfolgen.
    (default: 70)