diff --git a/fhem/CHANGED b/fhem/CHANGED index b1876a91c..63ed627d4 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it + - feature: 76_SolarForecast: consumerkey 'mode' can device/reading combination - feature: 76_SolarForecast: possible asynchron mode Battery Dev, code change - feature: 76_SMAInverter.pm: add installer login, code optimized - feature: 76_SolarForecast: possible asynchron mode for Meter & Inverter diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index bbaaa8d75..75ae46c80 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -157,6 +157,7 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.39.2" => "08.12.2024 rollout delHashRefDeep, extended consumer key 'mode' by device/reading combination ", "1.39.1" => "07.12.2024 new control releaseCentralTask, new delHashRefDeep in some cases ". "possible asynchron mode for setupBatteryDev ", "1.39.0" => "04.12.2024 possible asynchron mode for setupMeterDev, setupInverterDevXX ". @@ -431,8 +432,8 @@ my $epiecMaxCycles = 10; my @ctypes = qw(dishwasher dryer washingmachine heater charger other noSchedule); # erlaubte Consumer Typen my $defmintime = 60; # default Einplanungsdauer in Minuten -my $defctype = "other"; # default Verbrauchertyp -my $defcmode = "can"; # default Planungsmode der Verbraucher +my $defctype = 'other'; # default Verbrauchertyp +my $defcmode = 'can'; # default Planungsmode der Verbraucher my $defpopercent = 1.0; # Standard % aktuelle Leistung an nominaler Leistung gemäß Typenschild my $defhyst = 0; # default Hysterese @@ -1421,6 +1422,7 @@ sub _readCacheFile { my $valid = $dtree->isa('AI::DecisionTree'); if ($valid) { + delHashRefDeep ($data{$type}{$name}{aidectree}{aitrained}); delete $data{$type}{$name}{aidectree}{aitrained}; $data{$type}{$name}{aidectree}{aitrained} = $dtree; $data{$type}{$name}{current}{aitrainstate} = 'ok'; @@ -1439,6 +1441,7 @@ sub _readCacheFile { my ($err, $data) = fileRetrieve ($file); if (!$err && $data) { + delHashRefDeep ($data{$type}{$name}{aidectree}{airaw}); delete $data{$type}{$name}{aidectree}{airaw}; $data{$type}{$name}{aidectree}{airaw} = $data; $data{$type}{$name}{current}{aitrawstate} = 'ok'; @@ -1452,6 +1455,7 @@ sub _readCacheFile { my ($err, $statapi) = fileRetrieve ($file); if (!$err && $statapi) { + delHashRefDeep ($data{$type}{$name}{statusapi}); delete $data{$type}{$name}{statusapi}; $data{$type}{$name}{statusapi} = $statapi; Log3 ($name, 3, qq{$name - cached data "$title" restored}); @@ -1464,6 +1468,7 @@ sub _readCacheFile { my ($err, $wthtapi) = fileRetrieve ($file); if (!$err && $wthtapi) { + delHashRefDeep ($data{$type}{$name}{weatherapi}); delete $data{$type}{$name}{weatherapi}; $data{$type}{$name}{weatherapi} = $wthtapi; Log3 ($name, 3, qq{$name - cached data "$title" restored}); @@ -1476,6 +1481,7 @@ sub _readCacheFile { my ($err, $dwdc) = fileRetrieve ($file); if (!$err && $dwdc) { + delHashRefDeep ($data{$type}{$name}{dwdcatalog}); delete $data{$type}{$name}{dwdcatalog}; $data{$type}{$name}{dwdcatalog} = $dwdc; debugLog ($paref, 'dwdComm', qq{$title restored}); @@ -1797,6 +1803,7 @@ sub _setVictronCredentials { ## no critic "not used" my ($a,$h) = parseParams ($arg); if ($a->[0] && $a->[0] eq 'delete') { + delHashRefDeep ($data{$type}{$name}{statusapi}{'?VRM'}); delete $data{$type}{$name}{statusapi}{'?VRM'}; $msg = qq{Credentials for the Victron VRM API are deleted. }; } @@ -2290,6 +2297,7 @@ sub _setreset { ## no critic "not used" Log3 ($name, 3, qq{$name - roofIdentPair: pair key "$pk" deleted}); } else { + delHashRefDeep ($data{$type}{$name}{statusapi}{'?IdPair'}); delete $data{$type}{$name}{statusapi}{'?IdPair'}; Log3($name, 3, qq{$name - roofIdentPair: all pair keys deleted}); } @@ -5595,7 +5603,19 @@ sub _attrconsumer { ## no critic "not used" } if (exists $h->{mode} && $h->{mode} !~ /^(?:can|must)$/xs) { - return qq{The mode "$h->{mode}" isn't allowed!}; + if ($h->{mode} =~ /.*:.*/xs) { + my ($dv, $rd) = split ':', $h->{mode}; + ($err) = isDeviceValid ( { name => $hash->{NAME}, obj => $dv, method => 'string' } ); + return $err if($err); + + my $mode = ReadingsVal ($dv, $rd, ''); + if ($mode !~ /^(?:can|must)$/xs) { + return "The reading '$rd' of device '$dv' is invalid or doesn't contain a valid mode"; + } + } + else { + return qq{The mode "$h->{mode}" isn't allowed!}; + } } my $valid; @@ -6570,6 +6590,7 @@ sub Rename { my $type = (split '::', __PACKAGE__)[1]; $data{$type}{$new_name} = $data{$type}{$old_name}; + delHashRefDeep ($data{$type}{$old_name}); delete $data{$type}{$old_name}; my @ftd = _searchCacheFiles ($old_name); @@ -6653,6 +6674,7 @@ sub Delete { my $type = $hash->{TYPE}; + delHashRefDeep ($data{$type}{$name}); delete $data{$type}{$name}; return; @@ -6793,6 +6815,7 @@ sub delConsumerFromMem { } } + delHashRefDeep ($data{$type}{$name}{consumers}{$c}); delete $data{$type}{$name}{consumers}{$c}; Log3 ($name, 3, qq{$name - Consumer "$c - $calias" deleted from memory}); @@ -7356,6 +7379,7 @@ sub createStringConfig { ## no critic "not used" my $name = $hash->{NAME}; my $type = $hash->{TYPE}; + delHashRefDeep ($data{$type}{$name}{strings}); delete $data{$type}{$name}{strings}; # Stringhash zurücksetzen $data{$type}{$name}{current}{allStringsFullfilled} = 0; @@ -8096,6 +8120,7 @@ sub _transferWeatherValues { my $type = $paref->{type}; + delHashRefDeep ($data{$type}{$name}{weatherdata}); delete $data{$type}{$name}{weatherdata}; # Wetterdaten Hash löschen $paref->{apiu} = $apiu; # API wird verwendet @@ -10458,7 +10483,7 @@ sub ___doPlanning { debugLog ($paref, "consumerPlanning", qq{consumer "$c" - epiece1: $epiece1}); - my $mode = ConsumerVal ($hash, $c, 'mode', 'can'); + my $mode = getConsumerPlanningMode ($hash, $c); # Planungsmode 'can' oder 'must' my $calias = ConsumerVal ($hash, $c, 'alias', ''); my $mintime = ConsumerVal ($hash, $c, 'mintime', $defmintime); # Einplanungsdauer my $oldplanstate = ConsumerVal ($hash, $c, 'planstate', ''); # V. 1.35.0 @@ -10999,8 +11024,8 @@ sub ___switchConsumerOn { } 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 - my $mode = ConsumerVal ($hash, $c, "mode", $defcmode); # Consumer Planungsmode + $simpCstat =~ /planned|priority|starting/xs && $isInTime) { # Verbraucher Start ist geplant && Startzeit überschritten + my $mode = getConsumerPlanningMode ($hash, $c); # Planungsmode 'can' oder 'must' my $enable = ___enableSwitchByBatPrioCharge ($paref); # Vorrangladung Batterie ? debugLog ($paref, "consumerSwitching${c}", qq{Consumer switch enable by battery state: $enable}); @@ -11075,9 +11100,9 @@ sub ___switchConsumerOff { my $stopts = ConsumerVal ($hash, $c, "planswitchoff", undef); # geplante Unix Stopzeit my $auto = ConsumerVal ($hash, $c, "auto", 1); my $calias = ConsumerVal ($hash, $c, "alias", ""); # Consumer Device Alias - my $mode = ConsumerVal ($hash, $c, "mode", $defcmode); # Consumer Planungsmode my $hyst = ConsumerVal ($hash, $c, "hysteresis", $defhyst); # Hysterese + my $mode = getConsumerPlanningMode ($hash, $c); # Planungsmode 'can' oder 'must' my $offcom = ConsumerVal ($hash, $c, 'offcom', ''); # Set Command für "off" my ($swoffcond,$infoff,$err) = isAddSwitchOffCond ($hash, $c); # zusätzliche Switch off Bedingung my $simpCstat = simplifyCstate ($pstate); @@ -17908,12 +17933,14 @@ return; sub delHashRefDeep { my $href = shift; - for my $key (keys %{$href}) { - if (ref $href->{$key} eq 'HASH') { - delHashRefDeep ($href->{$key}); - } - - delete $href->{$key}; + if (ref $href eq 'HASH') { + for my $key (keys %{$href}) { + if (ref $href->{$key} eq 'HASH') { + delHashRefDeep ($href->{$key}); + } + + delete $href->{$key}; + } } $href = undef; # Optional: Garbage Collection erzwingen @@ -18055,6 +18082,44 @@ sub createAssociatedWith { return; } +################################################################ +# Funktion liefert den Planungsmodus eines Verbrauchers +# mode kann sein: +# can +# must +################################################################ +sub getConsumerPlanningMode { + my $hash = shift; + my $c = shift; + + my $name = $hash->{NAME}; + my $mode = ConsumerVal ($hash, $c, 'mode', $defcmode); # Consumer Planungsmode + + if ($mode =~ /^(?:can|must)$/xs) { + return $mode; + } + + ## Mode kann über Device:Reading gesteuert sein + ################################################# + my ($dv, $rd) = split ':', $mode; + my ($err) = isDeviceValid ( { name => $hash->{NAME}, obj => $dv, method => 'string' } ); + + if ($err) { + Log3 ($name, 1, qq{$name - ERROR - consumer >$c< - The device '$dv' in consumer key 'mode' doesn't exist. Fall back to '$defcmode' mode.}); + return $defcmode; + } + + $err = q{}; + $mode = ReadingsVal ($dv, $rd, ''); + + if ($mode !~ /^(?:can|must)$/xs) { + Log3 ($name, 1, qq{$name - ERROR - consumer >$c< - The reading '$rd' of device '$dv' is invalid or doesn't contain a valid mode. Fall back to '$defcmode' mode.}); + return $defcmode; + } + +return $mode; +} + ################################################################ # Planungsdaten Consumer löschen # $c - Consumer Nummer @@ -21210,124 +21275,125 @@ to ensure that the system configuration is correct.
@@ -23655,6 +23721,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. 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ß sofern eine gesetzte "swoncond" Bedingung erfüllt und "swoffcond" nicht erfüllt ist. + Device:Reading - Device/Reading Kombination um den Planungsmodus dynamisch ändern zu können. Das Reading muß 'can' oder 'must' zurückgeben. icon Icon und ggf. dessen Farbe zur Darstellung des Verbrauchers in der Übersichtsgrafik (optional)