diff --git a/fhem/CHANGED b/fhem/CHANGED index 8537ab873..b1876a91c 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: 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 - change: 93_DbLog: insertmode 0 - improved output of errors in Logfile diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 7eb99ab69..bbaaa8d75 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -157,6 +157,8 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "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 ". "include FHEM::SynoModules::ErrCodes ", "1.38.0" => "30.11.2024 optimize data handling, rename getter solApiData to radiationApiData, ". @@ -2121,7 +2123,10 @@ sub _setreset { ## no critic "not used" $dday = sprintf "%02d", $dday; if ($dhour) { $dhour = sprintf "%02d", $dhour; + + delHashRefDeep ($data{$type}{$name}{pvhist}{$dday}{$dhour}); delete $data{$type}{$name}{pvhist}{$dday}{$dhour}; + Log3 ($name, 3, qq{$name - Day "$dday" hour "$dhour" deleted from pvHistory}); $paref->{reorg} = 1; # den Tag Stunde "99" reorganisieren @@ -2135,11 +2140,13 @@ sub _setreset { ## no critic "not used" delete $paref->{histname}; } else { + delHashRefDeep ($data{$type}{$name}{pvhist}{$dday}); delete $data{$type}{$name}{pvhist}{$dday}; Log3 ($name, 3, qq{$name - Day "$dday" deleted from pvHistory}); } } else { + delHashRefDeep ($data{$type}{$name}{pvhist}); delete $data{$type}{$name}{pvhist}; Log3 ($name, 3, qq{$name - all days deleted from pvHistory}); } @@ -5672,8 +5679,8 @@ sub _attrconsumer { ## no critic "not used" $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); + InternalTimer (gettimeofday() + 0.5, 'FHEM::SolarForecast::centralTask', [$name, 0], 0); + InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); return; } @@ -5918,9 +5925,9 @@ sub _attrProducerDev { ## no critic "not used" } } - InternalTimer (gettimeofday()+0.5, 'FHEM::SolarForecast::centralTask', [$name, 0], 0); - InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); - InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben + InternalTimer (gettimeofday() + 0.5, 'FHEM::SolarForecast::centralTask', [$name, 0], 0); + InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); + InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben return; } @@ -5997,9 +6004,9 @@ sub _attrInverterDev { ## no critic "not used" } } - InternalTimer (gettimeofday()+0.5, 'FHEM::SolarForecast::centralTask', [$name, 0], 0); - InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); - InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben + InternalTimer (gettimeofday() + 0.5, 'FHEM::SolarForecast::centralTask', [$name, 0], 0); + InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); + InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben return; } @@ -6175,6 +6182,7 @@ sub _attrBatteryDev { ## no critic "not used" delete $data{$type}{$name}{current}{powerbatin}; delete $data{$type}{$name}{current}{batcharge}; delete $data{$type}{$name}{current}{batinstcap}; + delete $data{$type}{$name}{current}{batasynchron}; } InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); @@ -6355,7 +6363,7 @@ sub Notify { my $debug = getDebug ($myHash); # Debug Mode - my ($err, $medev, $h, $async); + my ($err, $medev, $badev, $h, $async); ## Meter Event? ################# @@ -6370,6 +6378,41 @@ sub Notify { } if ($async) { + if (CurrentVal ($myHash, 'ctrunning', 0)) { + if ($debug =~ /notifyHandling/x) { + Log3 ($myName, 1, qq{$myName DEBUG> notifyHandling - central task was called from NOTIFY when it is already running ... end this call}); + } + + return; + } + + centralTask ($myHash, 0); # keine Events in SolarForecast außer 'state' + return; + } + } + } + + ## Battery Event? + ################### + ($err, $badev, $h) = isDeviceValid ( { name => $myName, obj => 'setupBatteryDev', method => 'attr' } ); + + if (!$err) { + if ($devName eq $badev) { + $async = $h->{asynchron} // 0; + + if ($debug =~ /notifyHandling/x) { + Log3 ($myName, 1, qq{$myName DEBUG> notifyHandling - Event of Battery device >$devName< received - asynchronous mode: $async}); + } + + if ($async) { + if (CurrentVal ($myHash, 'ctrunning', 0)) { + if ($debug =~ /notifyHandling/x) { + Log3 ($myName, 1, qq{$myName DEBUG> notifyHandling - central task was called from NOTIFY when it is already running ... end this call}); + } + + return; + } + centralTask ($myHash, 0); # keine Events in SolarForecast außer 'state' return; } @@ -6390,6 +6433,14 @@ sub Notify { } if ($iasync) { + if (CurrentVal ($myHash, 'ctrunning', 0)) { + if ($debug =~ /notifyHandling/x) { + Log3 ($myName, 1, qq{$myName DEBUG> notifyHandling - central task was called from NOTIFY when it is already running ... end this call}); + } + + return; + } + centralTask ($myHash, 0); # keine Events in SolarForecast außer 'state' return; } @@ -7014,6 +7065,7 @@ sub runTask { Log3 ($name, 1, "$name DEBUG> INFO - runTask starts data collection at the end of an hour"); } + releaseCentralTask ($hash); centralTask ($hash, 1); } } @@ -7028,7 +7080,8 @@ sub runTask { if ($debug =~ /collectData/x) { Log3 ($name, 1, "$name DEBUG> INFO - runTask starts data collection at the beginning of an hour"); } - + + releaseCentralTask ($hash); centralTask ($hash, 1); } } @@ -7188,17 +7241,12 @@ sub centralTask { $data{$type}{$name}{statusapi}{'?VRM'}{'?API'}{credentials} = $vrmcr; delete $data{$type}{$name}{solcastapi}{'?VRM'}; } + ########################################################################################################################## my (undef, $disabled, $inactive) = controller ($name); return if($disabled || $inactive); # 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; - return; - } - if (!CurrentVal ($hash, 'allStringsFullfilled', 0)) { # die String Konfiguration erstellen wenn noch nicht erfolgreich ausgeführt my $ret = createStringConfig ($hash); @@ -7211,6 +7259,11 @@ sub centralTask { return; } } + + if (CurrentVal ($hash, 'ctrunning', 0)) { + Log3 ($name, 3, "$name - INFO - central task was called when it was already running ... end this call"); + return; + } my $t = time; # aktuelle Unix-Zeit my $date = strftime "%Y-%m-%d", localtime($t); # aktuelles Datum @@ -7222,6 +7275,8 @@ sub centralTask { $data{$type}{$name}{current}{ctrunning} = 1; # Central Task running Statusbit + InternalTimer (gettimeofday() + 1.2, "FHEM::SolarForecast::releaseCentralTask", $hash, 0); # Freigabe centralTask + my $centpars = { name => $name, type => $type, @@ -7282,15 +7337,13 @@ sub centralTask { if ($evt) { $centpars->{evt} = $evt; - InternalTimer(gettimeofday()+1, "FHEM::SolarForecast::singleUpdateState", {hash => $hash, state => $centpars->{state}, evt => $centpars->{evt}}, 0); + InternalTimer (gettimeofday() + 1, "FHEM::SolarForecast::singleUpdateState", {hash => $hash, state => $centpars->{state}, evt => $centpars->{evt}}, 0); } else { $centpars->{evt} = 1; singleUpdateState ( {hash => $hash, state => $centpars->{state}, evt => $centpars->{evt}} ); } - $data{$type}{$name}{current}{ctrunning} = 0; - return; } @@ -7580,7 +7633,7 @@ sub _collectAllRegConsumers { my ($err) = isDeviceValid ( { name => $name, obj => $dswitch, method => 'string' } ); next if($err); - push @{$data{$type}{$name}{current}{consumerdevs}}, $dswitch; # Switchdevice zusätzlich in CurrentHash eintragen + push @{$data{$type}{$name}{current}{consumerdevs}}, $dswitch if($dswitch ne $consumer); # Switchdevice zusätzlich in CurrentHash eintragen } else { $dswitch = $consumer; @@ -7810,6 +7863,7 @@ sub _specialActivities { $data{$type}{$name}{circular}{99}{ydayDvtn} = CircularVal ($hash, 99, 'tdayDvtn', '-'); delete $data{$type}{$name}{circular}{99}{tdayDvtn}; + delHashRefDeep ($data{$type}{$name}{pvhist}{$day}); delete $data{$type}{$name}{pvhist}{$day}; # den (alten) aktuellen Tag aus History löschen writeCacheToFile ($hash, 'plantconfig', $plantcfg.$name); # Anlagenkonfiguration sichern @@ -7971,7 +8025,10 @@ sub __delObsoleteAPIData { ## Wetter-API Daten löschen ############################# if (keys %{$data{$type}{$name}{weatherapi}}) { - delete $data{$type}{$name}{weatherapi}{OpenMeteo} if($wapi ne 'OpenMeteo'); + if ($wapi ne 'OpenMeteo') { + delHashRefDeep ($data{$type}{$name}{weatherapi}{OpenMeteo}); + delete $data{$type}{$name}{weatherapi}{OpenMeteo}; + } } ## Status-API Daten löschen @@ -7998,9 +8055,13 @@ sub __delObsoleteAPIData { } } + ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! + ########################################################################################################################## + # 01.12.2024 for my $idx (keys %{$data{$type}{$name}{solcastapi}{'?All'}}) { # Wetterindexe löschen (kann später raus) delete $data{$type}{$name}{solcastapi}{'?All'}{$idx} if($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs); } + ##################################################################################################################### } ## veraltete Strings aus Strings-Hash löschen @@ -9354,9 +9415,10 @@ sub _transferBatteryValues { storeReading ('Current_PowerBatOut', (int $pbo).' W'); storeReading ('Current_BatCharge', $soc.' %'); - $data{$type}{$name}{current}{powerbatin} = int $pbi; # Hilfshash Wert aktuelle Batterieladung - $data{$type}{$name}{current}{powerbatout} = int $pbo; # Hilfshash Wert aktuelle Batterieentladung - $data{$type}{$name}{current}{batcharge} = $soc; # aktuelle Batterieladung + $data{$type}{$name}{current}{powerbatin} = int $pbi; # Hilfshash Wert aktuelle Batterieladung + $data{$type}{$name}{current}{powerbatout} = int $pbo; # Hilfshash Wert aktuelle Batterieentladung + $data{$type}{$name}{current}{batcharge} = $soc; # aktuelle Batterieladung + $data{$type}{$name}{current}{batasynchron} = $h->{asynchron} if($h->{asynchron}); # asynchroner Modus = X push @{$data{$type}{$name}{current}{socslidereg}}, $soc; # Schieberegister Batterie SOC limitArray ($data{$type}{$name}{current}{socslidereg}, $slidenumdef); @@ -17822,6 +17884,43 @@ sub singleUpdateState { return; } +################################################################ +# Zentralschleife freigeben +################################################################ +sub releaseCentralTask { + my $hash = shift; + + RemoveInternalTimer ($hash, 'FHEM::SolarForecast::releaseCentralTask'); + + $data{$hash->{TYPE}}{$hash->{NAME}}{current}{ctrunning} = 0; + +return; +} + +################################################################ +# Hash Referenz rekursiv löschen +# +# Stellt sicher, dass alle verschachtelten Strukturen explizit +# gelöscht werden. Dies ist besonders nützlich um +# sicherzustellen, dass keine zirkulären Referenzen bestehen, +# die die Speicherfreigabe verhindern könnten. +################################################################ +sub delHashRefDeep { + my $href = shift; + + for my $key (keys %{$href}) { + if (ref $href->{$key} eq 'HASH') { + delHashRefDeep ($href->{$key}); + } + + delete $href->{$key}; + } + + $href = undef; # Optional: Garbage Collection erzwingen + +return; +} + ################################################################ # erstellt einen Debug-Eintrag im Log ################################################################ @@ -17874,28 +17973,29 @@ sub createAssociatedWith { my $fcdev1 = AttrVal ($name, 'setupWeatherDev1', ''); # Weather forecast Device 1 ($afc,$h) = parseParams ($fcdev1); - $fcdev1 = $afc->[0] // ""; + $fcdev1 = $afc->[0] // ''; my $fcdev2 = AttrVal ($name, 'setupWeatherDev2', ''); # Weather forecast Device 2 ($afc,$h) = parseParams ($fcdev2); - $fcdev2 = $afc->[0] // ""; + $fcdev2 = $afc->[0] // ''; my $fcdev3 = AttrVal ($name, 'setupWeatherDev3', ''); # Weather forecast Device 3 ($afc,$h) = parseParams ($fcdev3); - $fcdev3 = $afc->[0] // ""; + $fcdev3 = $afc->[0] // ''; my $radev = AttrVal ($name, 'setupRadiationAPI', ''); # Radiation forecast Device ($ara,$h) = parseParams ($radev); - $radev = $ara->[0] // ""; + $radev = $ara->[0] // ''; my $medev = AttrVal ($name, 'setupMeterDev', ''); # Meter Device ($ame,$h) = parseParams ($medev); - $medev = $ame->[0] // ""; + $medev = $ame->[0] // ''; push @cd, $medev; my $badev = AttrVal ($name, 'setupBatteryDev', ''); # Battery Device ($aba,$h) = parseParams ($badev); - $badev = $aba->[0] // ""; + $badev = $aba->[0] // ''; + push @cd, $badev; for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { # Consumer Devices my $consumer = AttrVal ($name, "consumer${c}", ""); @@ -21960,7 +22060,7 @@ to ensure that the system configuration is correct.
  • setupBatteryDev <Battery Device Name> pin=<Readingname>:<Unit> pout=<Readingname>:<Unit> [intotal=<Readingname>:<Unit>] [outtotal=<Readingname>:<Unit>] - cap=<Option> [charge=<Readingname>]

    + cap=<Option> [charge=<Readingname>] [asynchron=<Option>]

    Specifies an arbitrary Device and its Readings to deliver the battery performance data. The module assumes that the numerical value of the readings is always positive. @@ -21979,6 +22079,11 @@ to ensure that the system configuration is correct. <Readingname>:<unit> - Reading which provides the capacity and unit (Wh, kWh) charge Reading which provides the current state of charge (SOC in percent) (optional) Unit the respective unit (W,Wh,kW,kWh) + + asynchron Data collection mode according to the ctrlInterval setting (synchronous) or additionally by + event processing (asynchronous). + 0 - no data collection after receiving an event from the device (default) + 1 - trigger a data collection when an event is received from the device
    @@ -22122,7 +22227,6 @@ to ensure that the system configuration is correct. event processing (asynchronous). 0 - no data collection after receiving an event from the device (default) 1 - trigger a data collection when an event is received from the device -
    @@ -24379,7 +24483,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
  • setupBatteryDev <Batterie Device Name> pin=<Readingname>:<Einheit> pout=<Readingname>:<Einheit> [intotal=<Readingname>:<Einheit>] [outtotal=<Readingname>:<Einheit>] - cap=<Option> [charge=<Readingname>]

    + cap=<Option> [charge=<Readingname>] [asynchron=<Option>]

    Legt ein beliebiges Device und seine Readings zur Lieferung der Batterie Leistungsdaten fest. Das Modul geht davon aus, dass der numerische Wert der Readings immer positiv ist. @@ -24398,6 +24502,11 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. <Readingname>:<Einheit> - Reading welches die Kapazität liefert und Einheit (Wh, kWh) charge Reading welches den aktuellen Ladezustand (SOC in Prozent) liefert (optional) Einheit die jeweilige Einheit (W,Wh,kW,kWh) + + asynchron Modus der Datensammlung entsprechend Einstellung ctrlInterval (synchron) oder zusätzlich durch + Eventverarbeitung (asynchron). + 0 - keine Datensammlung nach Empfang eines Events des Gerätes (default) + 1 - auslösen einer Datensammlung bei Empfang eines Events des Gerätes