From 03bbb6ca4805be4256395a9854d8c7fe1410ebb6 Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Sun, 22 Dec 2024 09:48:13 +0000 Subject: [PATCH] 76_SolarForecast: contrib 1.40.0 git-svn-id: https://svn.fhem.de/fhem/trunk@29445 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/DS_Starter/76_SolarForecast.pm | 3938 ++++++++++--------- 1 file changed, 2012 insertions(+), 1926 deletions(-) diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 22f9b8dfc..15e09e176 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -42,6 +42,7 @@ use List::Util qw(max); eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval' eval "use FHEM::Utility::CTZ qw(:all);1;" or my $ctzAbsent = 1; ## no critic 'eval' +#use Test::Memory::Usage; # https://metacpan.org/pod/Test::Memory::Usage use Encode; use Color; @@ -52,9 +53,9 @@ eval "use AI::DecisionTree;1;" or my $aidtabs = 'AI::DecisionTree'; use FHEM::SynoModules::ErrCodes qw(:all); # Error Code Modul use FHEM::SynoModules::SMUtils qw (checkModVer + delClHash evaljson getClHash - delClHash moduleVersion trim ); # Hilfsroutinen Modul @@ -157,6 +158,16 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.40.0" => "21.12.2024 new consumer key 'surpmeth' to calculate surplus in various variants for cunsumer switching ", + "1.39.8" => "21.12.2024 prepare of new consumer key 'surpmeth', _batSocTarget: improve care SoC management when dark doldrums ", + "1.39.7" => "18.12.2024 ConsumptionRecommended calc method medianArray, change local owndata to global data ", + "1.39.6" => "17.12.2024 replace global data-store by local owndata-store, remove sub _composeRemoteObj, delHashRefDeep removed ". + "add current key (array) 'surplusslidereg' + sub avgArray, batteryManagement: fix care SoC management ", + "1.39.5" => "12.12.2024 __createAdditionalEvents: Warning in fhem Log if 'AllPVforecastsToEvent' events not created ". + "Notify: create cetralTask Events ", + "1.39.4" => "10.12.2024 fix Check Rooftop and Roof Ident Pair Settings (SolCast) ", + "1.39.3" => "09.12.2024 fix mode in consumerXX-Reading if mode is device/reading combination, show Mode in ". + "consumer legend mouse-over ", "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 ", @@ -167,7 +178,7 @@ my %vNotesIntern = ( "attr setupRadiationAPI and setupWeatherDev1 can be set largely independently of each other ". "rename sub SolCastAPIVal to RadiationAPIVal ", "1.37.9" => "29.11.2024 activate StatusAPI-Hash, Separation of radiation API-data, API-state data, weather-API data ", - "1.37.8" => "28.11.2024 edit commref, func _searchCacheFiles for renaming Cache files when device is renamed ". + "1.37.8" => "28.11.2024 edit commref, func searchCacheFiles for renaming Cache files when device is renamed ". "_saveEnergyConsumption: extended for Debug collectData, preparation of weatherApiData ". "new func WeatherAPIVal, StatusAPIVal ", "1.37.7" => "26.11.2024 Attr flowGraphicControl: key shift changed to shiftx, new key shifty ". @@ -327,44 +338,12 @@ my %vNotesIntern = ( "1.10.0" => "24.01.2024 consumerXX: notbefore, notafter format extended to hh[:mm], new sub checkCode, checkhhmm ", "1.9.0" => "23.01.2024 modify disable, add operationMode: active/inactive ", "1.8.0" => "22.01.2024 add 'noLearning' Option to Setter pvCorrectionFactor_Auto ", - "1.7.1" => "20.01.2024 optimize battery management ", - "1.7.0" => "18.01.2024 Changeover Start centralTask completely to runTask, ". - "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 runTask 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 ", - "1.6.3" => "08.01.2024 optimize battery management once more ", - "1.6.2" => "07.01.2024 optimize battery management ", - "1.6.1" => "04.01.2024 new sub __setPhysLogSwState, edit ___setConsumerPlanningState, boost performance of _collectAllRegConsumers ". - "CurrentVal ctrunning - Central Task running Statusbit, edit comref ", - "1.6.0" => "22.12.2023 store daily batmaxsoc in pvHistory, new attr ctrlBatSocManagement, reading Battery_OptimumTargetSoC ". - "currentBatteryDev: new optional key 'cap', adapt cloud2bin,temp2bin,rain2bin ". - "minor internal changes, isAddSwitchOffCond: change hysteresis algo, ctrlDebug: new entry batteryManagement ". - "check longitude, latitude in general audit, use coordinates (if set) for sun calc ", - "1.5.1" => "07.12.2023 function _getftui can now process arguments (compatibility to new ftui widgets), plant check ". - "reviews SolarForecast widget files ", - "1.5.0" => "05.12.2023 new getter ftuiFramefiles ", - "1.4.3" => "03.12.2023 hidden set or attr commands in user specific header area when called by 'get ... html' ". - "plantConfig: check module update in repo ", - "1.4.2" => "02.12.2023 ___getFWwidget: codechange ___getFWwidget using __widgetFallback function ", - "1.4.1" => "01.12.2023 ___getFWwidget: adjust for FHEMWEB feature forum:#136019 ", - "1.4.0" => "29.11.2023 graphicHeaderOwnspec: can manage attr / sets of other devs by @ ", - "1.3.0" => "27.11.2023 new Attr graphicHeaderOwnspecValForm ", - "1.2.0" => "25.11.2023 graphicHeaderOwnspec: show readings of other devs by @, Set/reset batteryTrigger ", - "1.1.3" => "24.11.2023 rename reset arguments according possible adjustable textField width ", - "1.1.2" => "20.11.2023 ctrlDebug Adjustment of column width, must have new fhemweb.js Forum:#135850 ", - "1.1.1" => "19.11.2023 graphicHeaderOwnspec: fix ignoring the last element of allsets/allattr ", - "1.1.0" => "14.11.2023 graphicHeaderOwnspec: possible add set/attr commands, new setter consumerNewPlanning ", - "1.0.10" => "31.10.2023 fix warnings, edit comref ", "0.1.0" => "09.12.2020 initial Version " ); ## Standardvariablen ###################### -my @da; # zentraler Readings-Store +my @da; # zentraler temporärer Readings-Store my $deflang = 'EN'; # default Sprache wenn nicht konfiguriert my @chours = (5..21); # Stunden des Tages mit möglichen Korrekturwerten my $kJtokWh = 0.0002777777778; # Umrechnungsfaktor kJ in kWh @@ -372,7 +351,8 @@ my $kJtoWh = 0.2777777778; my $WhtokJ = 3.6; # Umrechnungsfaktor Wh in kJ my $defmaxvar = 0.5; # max. Varianz pro Tagesberechnung Autokorrekturfaktor my $definterval = 70; # Standard Abfrageintervall -my $slidenumdef = 3; # max. Anzahl der Arrayelemente in Schieberegistern +my $slidenummax = 3; # max. Anzahl der Arrayelemente in Schieberegistern +my $splslidenummax = 20; # max. Anzahl der Arrayelemente in Schieberegister PV Überschuß my $weatherDevMax = 3; # max. Anzahl Wetter Devices (Attr setupWeatherDevX) my $maxSoCdef = 95; # default Wert (%) auf den die Batterie maximal aufgeladen werden soll bzw. als aufgeladen gilt my $carecycledef = 20; # max. Anzahl Tage die zwischen der Batterieladung auf maxSoC liegen dürfen @@ -826,13 +806,13 @@ my %hqtxt = ( # H wexso => { EN => qq{switched externally}, 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.} }, + 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 ().}, - DE => qq{Sieht ganz gut aus 😐, die Anlagenkonfiguration ist prinzipiell in Ordnung. Bitte beachten Sie die Warnungen ().} }, + DE => qq{Sieht ganz gut aus 😐, die Anlagenkonfiguration ist prinzipiell in Ordnung. Bitte beachten Sie die Warnungen ().} }, strnok => { EN => qq{Oh no 🙁, the system configuration is incorrect. Please check the settings and notes!}, - DE => qq{Oh nein 😢, die Anlagenkonfiguration ist fehlerhaft. Bitte überprüfen Sie die Einstellungen und Hinweise!} }, - pstate => { EN => qq{Planning status: 
Info: 
On: 
Off: 
Remaining lock time:  seconds}, - DE => qq{Planungsstatus: 
Info: 
Ein: 
Aus: 
verbleibende Sperrzeit:  Sekunden} }, + DE => qq{Oh nein 😢, die Anlagenkonfiguration ist fehlerhaft. Bitte überprüfen Sie die Einstellungen und Hinweise!} }, + pstate => { EN => qq{Planning status: 
Info: 
Mode: 
On: 
Off: 
Remaining lock time:  seconds}, + DE => qq{Planungsstatus: 
Info: 
Modus: 
Ein: 
Aus: 
verbleibende Sperrzeit:  Sekunden} }, ); my %htitles = ( # Hash Hilfetexte (Mouse Over) @@ -928,8 +908,8 @@ my %htitles = ( DE => qq{Perl Modul AI::DecisionTree ist nicht vorhanden} }, dumtxt => { EN => qq{Consumption that cannot be allocated to registered consumers}, DE => qq{Verbrauch der den registrierten Verbrauchern nicht zugeordnet werden kann} }, - pstate => { EN => qq{Planning status: \nInfo: \n\nOn: \nOff: \nRemaining lock time:  seconds}, - DE => qq{Planungsstatus: \nInfo: \n\nEin: \nAus: \nverbleibende Sperrzeit:  Sekunden} }, + pstate => { EN => qq{Planning status: \nInfo: \n\nMode: \nOn: \nOff: \nRemaining lock time:  seconds}, + DE => qq{Planungsstatus: \nInfo: \n\nModus: \nEin: \nAus: \nverbleibende Sperrzeit:  Sekunden} }, ainuse => { EN => qq{AI Perl module is installed, but the AI support is not used.\nRun 'set plantConfiguration check' for hints.}, DE => qq{KI Perl Modul ist installiert, aber die KI Unterstützung wird nicht verwendet.\nPrüfen sie 'set plantConfiguration check' für Hinweise.} }, arsrad2o => { EN => qq{API query successful but the radiation values are outdated.\nCheck the plant with 'set plantConfiguration check'.}, @@ -1166,25 +1146,28 @@ my %hfspvh = ( # Information zu verwendeten internen Datenhashes -# $data{$type}{$name}{circular} # Ringspeicher -# $data{$type}{$name}{current} # current values -# $data{$type}{$name}{current}{x_remote} # Steuerung und Werte remote Devices -# $data{$type}{$name}{pvhist} # historische Werte -# $data{$type}{$name}{nexthours} # NextHours Werte -# $data{$type}{$name}{consumers} # Consumer Hash -# $data{$type}{$name}{inverters} # Inverter Hash -# $data{$type}{$name}{producers} # non-PV Producer Hash -# $data{$type}{$name}{strings} # Stringkonfiguration Hash -# $data{$type}{$name}{solcastapi} # Zwischenspeicher API-Solardaten -# $data{$type}{$name}{weatherapi} # Zwischenspeicher API-Wetterdaten -# $data{$type}{$name}{statusapi} # Zwischenspeicher API-Status -# $data{$type}{$name}{aidectree}{object} # AI Decision Tree Object -# $data{$type}{$name}{aidectree}{aitrained} # AI Decision Tree trainierte Daten -# $data{$type}{$name}{aidectree}{airaw} # Rohdaten für AI Input = Raw Trainigsdaten -# $data{$type}{$name}{func} # interne Funktionen -# $data{$type}{$name}{weatherdata} # temporärer Speicher Wetterdaten -# $data{$type}{$name}{dwdcatalog} # DWD Stationskatalog +#################################################### +# Daten die ein Reload überleben und mit reloadCacheFiles nachgeladen werden müssen: +# $data{$name}{pvhist} # historische Werte +# $data{$name}{weatherapi} # Zwischenspeicher API-Wetterdaten +# $data{$name}{solcastapi} # Zwischenspeicher API-Solardaten +# $data{$name}{statusapi} # Zwischenspeicher API-Status +# $data{$name}{circular} # Ringspeicher +# $data{$name}{consumers} # Consumer Daten +# $data{$name}{aidectree}{aitrained} # AI Decision Tree trainierte Daten +# $data{$name}{aidectree}{airaw} # Rohdaten für AI Input = Raw Trainigsdaten +# temporäre Daten die pro Zyklus neu erstellt werden: +# $data{$name}{current} # temporärer Speicher Current Daten (enthält beim Lesen Cachefiles geladene Statusdaten) +# $data{$name}{nexthours} # temporärer Speicher NextHours Daten +# $data{$name}{inverters} # temporärer Speicher Inverter Daten +# $data{$name}{producers} # temporärer Speicher non-PV Producer Daten +# $data{$name}{batteries} # temporärer Speicher Battery Daten +# $data{$name}{weatherdata} # temporärer Speicher Wetterdaten +# $data{$name}{func} # temporäre interne Funktionen +# $data{$name}{dwdcatalog} # temporärer Speicher DWD Stationskatalog +# $data{$name}{strings} # temporärer Speicher Stringkonfiguration +# $data{$name}{aidectree}{object} # AI Decision Tree Object ################################################################ # Init Fn @@ -1353,48 +1336,8 @@ sub Define { delete $params->{hash}; 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} = 'radiationApiData'; - _readCacheFile ($params); - - $params->{file} = $statcache.$name; # Cache File API-Status einlesen wenn vorhanden - $params->{cachename} = 'statusapi'; - $params->{title} = 'statusApiData'; - _readCacheFile ($params); - - $params->{file} = $weathercache.$name; # Cache File Weather-API Daten einlesen wenn vorhanden - $params->{cachename} = 'weatherapi'; - $params->{title} = 'weatherApiData'; - _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} ); + reloadCacheFiles ($params); + singleUpdateState ( {hash => $hash, state => 'initialized', evt => 1} ); $readyfnlist{$name} = $hash; # Registrierung in Ready-Schleife InternalTimer (gettimeofday()+$whistrepeat, "FHEM::SolarForecast::periodicWriteMemcache", $hash, 0); # Einstieg periodisches Schreiben historische Daten @@ -1402,125 +1345,6 @@ sub Define { return; } -################################################################ -# Cachefile lesen -################################################################ -sub _readCacheFile { - my $paref = shift; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $file = $paref->{file}; - my $cachename = $paref->{cachename}; - my $title = $paref->{title}; - - my $hash = $defs{$name}; - - if ($cachename eq 'aitrained') { - my ($err, $dtree) = fileRetrieve ($file); - - if (!$err && $dtree) { - 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'; - - Log3 ($name, 3, qq{$name - cached data "$title" restored}); - return; - } - } - - delete $data{$type}{$name}{circular}{99}{aitrainLastFinishTs}; - delete $data{$type}{$name}{circular}{99}{runTimeTrainAI}; - return; - } - - if ($cachename eq 'airaw') { - 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'; - Log3 ($name, 3, qq{$name - cached data "$title" restored}); - } - - return; - } - - if ($cachename eq 'statusapi') { - 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}); - } - - return; - } - - if ($cachename eq 'weatherapi') { - 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}); - } - - return; - } - - if ($cachename eq 'dwdcatalog') { - 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}); - } - - return; - } - - if ($cachename eq 'plantconfig') { - my ($err, $plantcfg) = fileRetrieve ($file); - return $err if($err); - my ($nr, $na); - - if ($plantcfg) { - ($nr, $na) = _restorePlantConfig ($hash, $plantcfg); - Log3 ($name, 3, qq{$name - cached data "$title" restored. Number of restored Readings/Attributes: $nr/$na}); - } - - return ('', $nr, $na); - } - - my ($error, @content) = FileRead ($file); - - if (!$error) { - my $json = join "", @content; - my ($success) = evaljson ($hash, $json); - - if ($success) { - $data{$hash->{TYPE}}{$name}{$cachename} = decode_json ($json); - Log3 ($name, 3, qq{$name - cached data "$title" restored}); - } - else { - Log3 ($name, 1, qq{$name - WARNING - The content of file "$file" is not readable or may be corrupt}); - } - } - -return; -} - ############################################################### # SolarForecast Set ############################################################### @@ -1571,7 +1395,7 @@ sub Set { } $cf = join " ", @cfs; - for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { + for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { push @condevs, $c if($c); } @@ -1769,8 +1593,8 @@ sub _setroofIdentPair { ## no critic "not used" return qq{The syntax of "$opt" is not correct. Please consider the commandref.}; } - $data{$type}{$name}{statusapi}{'?IdPair'}{'?'.$pk}{rtid} = $h->{rtid}; - $data{$type}{$name}{statusapi}{'?IdPair'}{'?'.$pk}{apikey} = $h->{apikey}; + $data{$name}{statusapi}{'?IdPair'}{'?'.$pk}{rtid} = $h->{rtid}; + $data{$name}{statusapi}{'?IdPair'}{'?'.$pk}{apikey} = $h->{apikey}; writeCacheToFile ($hash, 'statusapi', $statcache.$name); # Status-API Cache sichern @@ -1803,8 +1627,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'}; + delete $data{$name}{statusapi}{'?VRM'}; $msg = qq{Credentials for the Victron VRM API are deleted. }; } else { @@ -1816,7 +1639,7 @@ sub _setVictronCredentials { ## no critic "not used" } or do { return "Serialization ERROR: $@" }; - $data{$type}{$name}{statusapi}{'?VRM'}{'?API'}{credentials} = chew ($serial); + $data{$name}{statusapi}{'?VRM'}{'?API'}{credentials} = chew ($serial); $msg = qq{Credentials for the Victron VRM API has been saved.}; } @@ -1915,7 +1738,7 @@ sub _setstringDeclination { ## no critic "not used" return if(_checkSetupNotComplete ($hash)); # keine Stringkonfiguration wenn Setup noch nicht komplett - my $ret = createStringConfig ($hash); + my $ret = _createStringConfig ($hash); return $ret if($ret); return; @@ -1954,7 +1777,7 @@ sub _setstringAzimuth { ## no critic "not used" return if(_checkSetupNotComplete ($hash)); # keine Stringkonfiguration wenn Setup noch nicht komplett - my $ret = createStringConfig ($hash); + my $ret = _createStringConfig ($hash); return $ret if($ret); return; @@ -2002,7 +1825,7 @@ sub _setplantConfiguration { ## no critic "not used" $paref->{file} = $plantcfg.$name; $paref->{cachename} = 'plantconfig'; $paref->{title} = 'Plant Configuration'; - ($err, $nr, $na) = _readCacheFile ($paref); + ($err, $nr, $na) = readCacheFile ($paref); if (!$err) { if ($nr || $na) { @@ -2131,8 +1954,7 @@ sub _setreset { ## no critic "not used" if ($dhour) { $dhour = sprintf "%02d", $dhour; - delHashRefDeep ($data{$type}{$name}{pvhist}{$dday}{$dhour}); - delete $data{$type}{$name}{pvhist}{$dday}{$dhour}; + delete $data{$name}{pvhist}{$dday}{$dhour}; Log3 ($name, 3, qq{$name - Day "$dday" hour "$dhour" deleted from pvHistory}); @@ -2147,14 +1969,12 @@ sub _setreset { ## no critic "not used" delete $paref->{histname}; } else { - delHashRefDeep ($data{$type}{$name}{pvhist}{$dday}); - delete $data{$type}{$name}{pvhist}{$dday}; + delete $data{$name}{pvhist}{$dday}; Log3 ($name, 3, qq{$name - Day "$dday" deleted from pvHistory}); } } else { - delHashRefDeep ($data{$type}{$name}{pvhist}); - delete $data{$type}{$name}{pvhist}; + delete $data{$name}{pvhist}; Log3 ($name, 3, qq{$name - all days deleted from pvHistory}); } @@ -2167,7 +1987,7 @@ sub _setreset { ## no critic "not used" if ($dday) { if ($dhour) { - delete $data{$type}{$name}{pvhist}{$dday}{$dhour}{con}; + delete $data{$name}{pvhist}{$dday}{$dhour}{con}; Log3 ($name, 3, qq{$name - consumption day "$dday" hour "$dhour" deleted from pvHistory}); $paref->{reorg} = 1; # den Tag Stunde "99" reorganisieren @@ -2181,17 +2001,17 @@ sub _setreset { ## no critic "not used" delete $paref->{histname}; } else { - for my $hr (sort keys %{$data{$type}{$name}{pvhist}{$dday}}) { - delete $data{$type}{$name}{pvhist}{$dday}{$hr}{con}; + for my $hr (sort keys %{$data{$name}{pvhist}{$dday}}) { + delete $data{$name}{pvhist}{$dday}{$hr}{con}; } Log3 ($name, 3, qq{$name - consumption day "$dday" deleted from pvHistory}); } } else { - for my $dy (sort keys %{$data{$type}{$name}{pvhist}}) { - for my $hr (sort keys %{$data{$type}{$name}{pvhist}{$dy}}) { - delete $data{$type}{$name}{pvhist}{$dy}{$hr}{con}; + for my $dy (sort keys %{$data{$name}{pvhist}}) { + for my $hr (sort keys %{$data{$name}{pvhist}{$dy}}) { + delete $data{$name}{pvhist}{$dy}{$hr}{con}; } } @@ -2212,31 +2032,31 @@ sub _setreset { ## no critic "not used" if ($circ eq 'cached') { if ($circh) { - delete $data{$type}{$name}{circular}{$circh}{pvcorrf}; - delete $data{$type}{$name}{circular}{$circh}{quality}; - delete $data{$type}{$name}{circular}{$circh}{pvrlsum}; - delete $data{$type}{$name}{circular}{$circh}{pvfcsum}; - delete $data{$type}{$name}{circular}{$circh}{dnumsum}; + delete $data{$name}{circular}{$circh}{pvcorrf}; + delete $data{$name}{circular}{$circh}{quality}; + delete $data{$name}{circular}{$circh}{pvrlsum}; + delete $data{$name}{circular}{$circh}{pvfcsum}; + delete $data{$name}{circular}{$circh}{dnumsum}; - for my $hid (keys %{$data{$type}{$name}{pvhist}}) { - delete $data{$type}{$name}{pvhist}{$hid}{$circh}{pvcorrf}; + for my $hid (keys %{$data{$name}{pvhist}}) { + delete $data{$name}{pvhist}{$hid}{$circh}{pvcorrf}; } Log3($name, 3, qq{$name - stored PV correction factor of hour "$circh" from pvCircular and pvHistory deleted}); return; } - for my $hod (keys %{$data{$type}{$name}{circular}}) { - delete $data{$type}{$name}{circular}{$hod}{pvcorrf}; - delete $data{$type}{$name}{circular}{$hod}{quality}; - delete $data{$type}{$name}{circular}{$hod}{pvrlsum}; - delete $data{$type}{$name}{circular}{$hod}{pvfcsum}; - delete $data{$type}{$name}{circular}{$hod}{dnumsum}; + for my $hod (keys %{$data{$name}{circular}}) { + delete $data{$name}{circular}{$hod}{pvcorrf}; + delete $data{$name}{circular}{$hod}{quality}; + delete $data{$name}{circular}{$hod}{pvrlsum}; + delete $data{$name}{circular}{$hod}{pvfcsum}; + delete $data{$name}{circular}{$hod}{dnumsum}; } - for my $hid (keys %{$data{$type}{$name}{pvhist}}) { - for my $hidh (keys %{$data{$type}{$name}{pvhist}{$hid}}) { - delete $data{$type}{$name}{pvhist}{$hid}{$hidh}{pvcorrf}; + for my $hid (keys %{$data{$name}{pvhist}}) { + for my $hidh (keys %{$data{$name}{pvhist}{$hid}}) { + delete $data{$name}{pvhist}{$hid}{$hidh}{pvcorrf}; } } @@ -2247,10 +2067,10 @@ sub _setreset { ## no critic "not used" } if ($prop eq 'aiData') { - delete $data{$type}{$name}{current}{aiinitstate}; - delete $data{$type}{$name}{current}{aitrainstate}; - delete $data{$type}{$name}{current}{aiaddistate}; - delete $data{$type}{$name}{current}{aigetresult}; + delete $data{$name}{current}{aiinitstate}; + delete $data{$name}{current}{aitrainstate}; + delete $data{$name}{current}{aiaddistate}; + delete $data{$name}{current}{aigetresult}; my @ftd = ( $airaw.$name, $aitrained.$name @@ -2293,12 +2113,11 @@ sub _setreset { ## no critic "not used" my $pk = $paref->{prop1} // ""; # ein bestimmter PairKey angegeben ? if ($pk) { - delete $data{$type}{$name}{statusapi}{'?IdPair'}{'?'.$pk}; + delete $data{$name}{statusapi}{'?IdPair'}{'?'.$pk}; Log3 ($name, 3, qq{$name - roofIdentPair: pair key "$pk" deleted}); } else { - delHashRefDeep ($data{$type}{$name}{statusapi}{'?IdPair'}); - delete $data{$type}{$name}{statusapi}{'?IdPair'}; + delete $data{$name}{statusapi}{'?IdPair'}; Log3($name, 3, qq{$name - roofIdentPair: all pair keys deleted}); } @@ -2313,7 +2132,7 @@ sub _setreset { ## no critic "not used" deleteConsumerPlanning ($hash, $c); } else { - for my $cs (keys %{$data{$type}{$name}{consumers}}) { + for my $cs (keys %{$data{$name}{consumers}}) { deleteConsumerPlanning ($hash, $cs); } } @@ -2329,14 +2148,14 @@ sub _setreset { ## no critic "not used" delConsumerFromMem ($paref); # spezifischen Consumer aus History löschen } else { - for my $c (keys %{$data{$type}{$name}{consumers}}) { + for my $c (keys %{$data{$name}{consumers}}) { $paref->{c} = $c; delConsumerFromMem ($paref); # alle Consumer aus History löschen } } delete $paref->{c}; - $data{$type}{$name}{current}{consumerCollected} = 0; # Consumer neu sammeln + $data{$name}{current}{consumerCollected} = 0; # Consumer neu sammeln writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben centralTask ($hash, 0); @@ -2382,7 +2201,7 @@ sub _setoperatingMemory { ## no critic "not used" } $paref->{file} = "$cachedir/$file"; - _readCacheFile ($paref); + readCacheFile ($paref); delete $paref->{file}; delete $paref->{cachename}; @@ -2497,11 +2316,11 @@ sub Get { none ); - my @pha = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$type}{$name}{pvhist}}; - my @vcm = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$type}{$name}{consumers}}; - my @vin = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$type}{$name}{inverters}}; - my @vpn = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$type}{$name}{producers}}; - my @vst = sort keys %{$data{$type}{$name}{strings}}; + my @pha = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$name}{pvhist}}; + my @vcm = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$name}{consumers}}; + my @vin = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$name}{inverters}}; + my @vpn = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$name}{producers}}; + my @vst = sort keys %{$data{$name}{strings}}; my $hol = join ",", @ho; my $pvl = join ",", @pha; @@ -2578,8 +2397,8 @@ sub _getRoofTopData { my $type = $paref->{type}; my $hash = $defs{$name}; - delete $data{$type}{$name}{current}{dwdRad1hAge}; - delete $data{$type}{$name}{current}{dwdRad1hAgeTS}; + delete $data{$name}{current}{dwdRad1hAge}; + delete $data{$name}{current}{dwdRad1hAgeTS}; my ($rapi, $wapi) = getStatusApiName ($hash); # $rapi - Radiation-API, $wapi - Weather-API my $ret = "$name is not a valid Radiation ($rapi) and/or Weather ($wapi) Model"; @@ -2649,7 +2468,7 @@ sub __getSolCastData { my $type = $paref->{type}; - for my $pk (keys %{$data{$type}{$name}{statusapi}{'?IdPair'}}) { + for my $pk (keys %{$data{$name}{statusapi}{'?IdPair'}}) { my $apikey = StatusAPIVal ($hash, '?IdPair', $pk, 'apikey', ''); next if(!$apikey); @@ -2661,8 +2480,8 @@ sub __getSolCastData { my $madc = sprintf "%.0f", ($apimaxreq / $maxcnt); # max. tägliche Anzahl API Calls my $mpk = $maxcnt; # Requestmultiplikator - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{solCastAPIcallMultiplier} = $mpk; - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{todayMaxAPIcalls} = $madc; + $data{$name}{statusapi}{SolCast}{'?All'}{solCastAPIcallMultiplier} = $mpk; + $data{$name}{statusapi}{SolCast}{'?All'}{todayMaxAPIcalls} = $madc; ######################### @@ -2810,11 +2629,11 @@ sub __solCast_ApiResponse { Log3 ($name, 1, "$name - $msg"); - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{response_message} = $err; + $data{$name}{statusapi}{SolCast}{'?All'}{response_message} = $err; singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -2827,8 +2646,8 @@ sub __solCast_ApiResponse { Log3 ($name, 1, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -2854,15 +2673,15 @@ sub __solCast_ApiResponse { ___setSolCastAPIcallKeyData ($paref); - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{response_message} = $jdata->{'response_status'}{'message'}; + $data{$name}{statusapi}{SolCast}{'?All'}{response_message} = $jdata->{'response_status'}{'message'}; if ($jdata->{'response_status'}{'error_code'} eq 'TooManyRequests') { - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{todayRemainingAPIrequests} = 0; + $data{$name}{statusapi}{SolCast}{'?All'}{todayRemainingAPIrequests} = 0; } singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln if ($debug =~ /apiProcess|apiCall/x) { my $apimaxreq = AttrVal ($name, 'ctrlSolCastAPImaxReq', $solcmaxreqdef); @@ -2899,13 +2718,13 @@ sub __solCast_ApiResponse { $period =~ s/.*(\d\d).*/$1/; # -> die neuen Daten (in dem Fall nur die einer halben Stunde) im nächsten Schritt addiert werden my $est50 = RadiationAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0) / (60/$period); - $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = sprintf "%.0f", $est50 if($est50); + $data{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = sprintf "%.0f", $est50 if($est50); $k++; next; } - delete $data{$type}{$name}{solcastapi}{$string}{$starttmstr}; + delete $data{$name}{solcastapi}{$string}{$starttmstr}; $k++; } @@ -2929,13 +2748,13 @@ sub __solCast_ApiResponse { $pvest50 = sprintf "%.0f", ($pvest50 * ($period/60) * 1000); if ($debug =~ /apiProcess/x) { # nur für Debugging - if (exists $data{$type}{$name}{solcastapi}{$string}{$starttmstr}) { + if (exists $data{$name}{solcastapi}{$string}{$starttmstr}) { Log3 ($name, 1, qq{$name DEBUG> SolCast API Hash - Start Date/Time: }. $starttmstr); Log3 ($name, 1, qq{$name DEBUG> SolCast API Hash - pv_estimate50 add: }.$pvest50.qq{, contains already: }.RadiationAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0)); } } - $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} += $pvest50; + $data{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} += $pvest50; $k++; } @@ -2945,7 +2764,7 @@ sub __solCast_ApiResponse { ___setSolCastAPIcallKeyData ($paref); - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{response_message} = 'success'; + $data{$name}{statusapi}{SolCast}{'?All'}{response_message} = 'success'; my $param = { name => $name, @@ -2955,8 +2774,8 @@ sub __solCast_ApiResponse { lang => $lang }; - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return &$caller($param); } @@ -3020,8 +2839,8 @@ sub ___setSolCastAPIcallKeyData { my $hash = $defs{$name}; - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{lastretrieval_timestamp} = $t; # letzter Abrufzeitstempel + $data{$name}{statusapi}{SolCast}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit + $data{$name}{statusapi}{SolCast}{'?All'}{lastretrieval_timestamp} = $t; # letzter Abrufzeitstempel my $apimaxreq = AttrVal ($name, 'ctrlSolCastAPImaxReq', $solcmaxreqdef); my $mpl = StatusAPIVal ($hash, 'SolCast', '?All', 'solCastAPIcallMultiplier', 1); @@ -3031,14 +2850,14 @@ sub ___setSolCastAPIcallKeyData { my $drc = StatusAPIVal ($hash, 'SolCast', '?All', 'todayMaxAPIcalls', $apimaxreq / $mpl) - $ddc; # verbleibende SolCast API Calls am aktuellen Tag $drc = 0 if($drc < 0); - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{todayDoneAPIrequests} = $ddc * $mpl; + $data{$name}{statusapi}{SolCast}{'?All'}{todayDoneAPIrequests} = $ddc * $mpl; my $drr = $apimaxreq - ($mpl * $ddc); $drr = 0 if($drr < 0); - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{todayRemainingAPIrequests} = $drr; - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{todayRemainingAPIcalls} = $drc; - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{todayDoneAPIcalls} = $ddc; + $data{$name}{statusapi}{SolCast}{'?All'}{todayRemainingAPIrequests} = $drr; + $data{$name}{statusapi}{SolCast}{'?All'}{todayRemainingAPIcalls} = $drc; + $data{$name}{statusapi}{SolCast}{'?All'}{todayDoneAPIcalls} = $ddc; debugLog ($paref, "apiProcess|apiCall", "SolCast API Call - done API Calls: $ddc"); @@ -3052,13 +2871,13 @@ sub ___setSolCastAPIcallKeyData { $dart = 0 if($dart < 0); $drc += 1; - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{currentAPIinterval} = $solapirepdef; - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{currentAPIinterval} = int ($dart / $drc) if($dart && $drc); + $data{$name}{statusapi}{SolCast}{'?All'}{currentAPIinterval} = $solapirepdef; + $data{$name}{statusapi}{SolCast}{'?All'}{currentAPIinterval} = int ($dart / $drc) if($dart && $drc); debugLog ($paref, "apiProcess|apiCall", "SolCast API Call - Sunset: $sunset, remain Sec to Sunset: $dart, new interval: ".StatusAPIVal ($hash, 'SolCast', '?All', 'currentAPIinterval', $solapirepdef)); } else { - $data{$type}{$name}{statusapi}{SolCast}{'?All'}{currentAPIinterval} = $solapirepdef; + $data{$name}{statusapi}{SolCast}{'?All'}{currentAPIinterval} = $solapirepdef; } #### @@ -3146,7 +2965,7 @@ sub __forecastSolar_ApiRequest { my $hash = $defs{$name}; if (!$allstrings) { # alle Strings wurden abgerufen - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{todayDoneAPIcalls} += 1; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{todayDoneAPIcalls} += 1; return; } @@ -3231,11 +3050,11 @@ sub __forecastSolar_ApiResponse { Log3 ($name, 1, "$name - $msg"); - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{response_message} = $err; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{response_message} = $err; singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -3248,8 +3067,8 @@ sub __forecastSolar_ApiResponse { Log3 ($name, 1, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -3276,16 +3095,16 @@ sub __forecastSolar_ApiResponse { singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{response_message} = $jdata->{'message'}{'text'}; - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{response_code} = $jdata->{'message'}{'code'}; - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{lastretrieval_timestamp} = $t; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{response_message} = $jdata->{'message'}{'text'}; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{response_code} = $jdata->{'message'}{'code'}; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit + $data{$name}{statusapi}{ForecastSolar}{'?All'}{lastretrieval_timestamp} = $t; if (defined $jdata->{'message'}{'ratelimit'}{'remaining'}) { - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{requests_remaining} = $jdata->{'message'}{'ratelimit'}{'remaining'}; # verbleibende Requests in Periode + $data{$name}{statusapi}{ForecastSolar}{'?All'}{requests_remaining} = $jdata->{'message'}{'ratelimit'}{'remaining'}; # verbleibende Requests in Periode } else { - delete $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{requests_remaining}; # verbleibende Requests unbestimmt + delete $data{$name}{statusapi}{ForecastSolar}{'?All'}{requests_remaining}; # verbleibende Requests unbestimmt } if ($debug =~ /apiCall/x) { @@ -3299,14 +3118,14 @@ sub __forecastSolar_ApiResponse { if ($rtyat) { my $rtyatts = timestringToTimestamp ($rtyat); - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{retryat_time} = $rtyat; - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{retryat_timestamp} = $rtyatts; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{retryat_time} = $rtyat; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{retryat_timestamp} = $rtyatts; debugLog ($paref, "apiCall", "ForecastSolar API Call - retry at: ".$rtyat." ($rtyatts)"); } - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln ___setForeCastAPIcallKeyData ($paref); @@ -3316,14 +3135,14 @@ sub __forecastSolar_ApiResponse { my $rt = timestringFormat ($jdata->{'message'}{'info'}{'time'}); my $rts = timestringToTimestamp ($rt); - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{lastretrieval_time} = $rt; # letzte Abrufzeit - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{lastretrieval_timestamp} = $rts; # letzter Abrufzeitstempel - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{response_message} = $jdata->{'message'}{'type'}; - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{response_code} = $jdata->{'message'}{'code'}; - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{requests_remaining} = $jdata->{'message'}{'ratelimit'}{'remaining'}; # verbleibende Requests in Periode - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{requests_limit_period} = $jdata->{'message'}{'ratelimit'}{'period'}; # Requests Limit Periode - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{requests_limit} = $jdata->{'message'}{'ratelimit'}{'limit'}; # Requests Limit in Periode - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{place} = encode ("utf8", $jdata->{'message'}{'info'}{'place'}); + $data{$name}{statusapi}{ForecastSolar}{'?All'}{lastretrieval_time} = $rt; # letzte Abrufzeit + $data{$name}{statusapi}{ForecastSolar}{'?All'}{lastretrieval_timestamp} = $rts; # letzter Abrufzeitstempel + $data{$name}{statusapi}{ForecastSolar}{'?All'}{response_message} = $jdata->{'message'}{'type'}; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{response_code} = $jdata->{'message'}{'code'}; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{requests_remaining} = $jdata->{'message'}{'ratelimit'}{'remaining'}; # verbleibende Requests in Periode + $data{$name}{statusapi}{ForecastSolar}{'?All'}{requests_limit_period} = $jdata->{'message'}{'ratelimit'}{'period'}; # Requests Limit Periode + $data{$name}{statusapi}{ForecastSolar}{'?All'}{requests_limit} = $jdata->{'message'}{'ratelimit'}{'limit'}; # Requests Limit in Periode + $data{$name}{statusapi}{ForecastSolar}{'?All'}{place} = encode ("utf8", $jdata->{'message'}{'info'}{'place'}); if ($debug =~ /apiCall/x) { Log3 ($name, 1, qq{$name DEBUG> ForecastSolar API Call - server response for PV string "$string"}); @@ -3336,7 +3155,7 @@ sub __forecastSolar_ApiResponse { my $kts = (timestringToTimestamp ($k)) - 3600; # Endezeit der Periode auf Startzeit umrechnen my $starttmstr = (timestampToTimestring ($kts, $lang))[3]; - $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = $jdata->{'result'}{$k}; + $data{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = $jdata->{'result'}{$k}; debugLog ($paref, "apiProcess", "ForecastSolar API Call - PV estimate: ".$starttmstr.' => '.$jdata->{'result'}{$k}.' Wh'); } @@ -3354,8 +3173,8 @@ sub __forecastSolar_ApiResponse { lang => $lang }; - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return &$caller($param); } @@ -3373,7 +3192,7 @@ sub ___setForeCastAPIcallKeyData { my $hash = $defs{$name}; - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{todayDoneAPIrequests} += 1; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{todayDoneAPIrequests} += 1; ## Berechnung des optimalen Request Intervalls ################################################ @@ -3381,12 +3200,12 @@ sub ___setForeCastAPIcallKeyData { my $period = StatusAPIVal ($hash, 'ForecastSolar', '?All', 'requests_limit_period', 3600); # Requests Limit Periode my $limit = StatusAPIVal ($hash, 'ForecastSolar', '?All', 'requests_limit', 12); # Request Limit in Periode - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{currentAPIinterval} = $forapirepdef; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{currentAPIinterval} = $forapirepdef; my $interval = int ($period / ($limit / $snum)); $interval = 900 if($interval < 900); - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{currentAPIinterval} = $interval; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{currentAPIinterval} = $interval; #### @@ -3396,7 +3215,7 @@ sub ___setForeCastAPIcallKeyData { if ($rtyatts && $rtyatts > $t) { # Zwangswartezeit durch API berücksichtigen $apiitv = $rtyatts - $t; - $data{$type}{$name}{statusapi}{ForecastSolar}{'?All'}{currentAPIinterval} = $apiitv; + $data{$name}{statusapi}{ForecastSolar}{'?All'}{currentAPIinterval} = $apiitv; $smt = '(forced waiting time)'; } @@ -3459,17 +3278,17 @@ sub __getDWDSolarData { my $cafd = AttrVal ($name, 'ctrlAreaFactorUsage', 'fix'); # Art der Flächenfaktor Berechnung my $stime = $date.' 00:00:00'; # Startzeit Soll Übernahmedaten my $sts = timestringToTimestamp ($stime); - my @strings = sort keys %{$data{$type}{$name}{strings}}; + my @strings = sort keys %{$data{$name}{strings}}; my $ret = q{}; - $data{$type}{$name}{statusapi}{DWD}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; - $data{$type}{$name}{statusapi}{DWD}{'?All'}{lastretrieval_timestamp} = $t; - $data{$type}{$name}{statusapi}{DWD}{'?All'}{todayDoneAPIrequests} += 1; + $data{$name}{statusapi}{DWD}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; + $data{$name}{statusapi}{DWD}{'?All'}{lastretrieval_timestamp} = $t; + $data{$name}{statusapi}{DWD}{'?All'}{todayDoneAPIrequests} += 1; my $fctime = ReadingsVal ($raname, 'fc_time', '-'); - $data{$type}{$name}{current}{dwdRad1hDev} = $raname; - $data{$type}{$name}{current}{dwdRad1hAge} = $fctime; - $data{$type}{$name}{current}{dwdRad1hAgeTS} = timestringToTimestamp ($fctime); + $data{$name}{current}{dwdRad1hDev} = $raname; + $data{$name}{current}{dwdRad1hAge} = $fctime; + $data{$name}{current}{dwdRad1hAgeTS} = timestringToTimestamp ($fctime); debugLog ($paref, "apiCall", "DWD API - collect DWD Radiation data with start >$stime<- device: $raname =>"); @@ -3485,7 +3304,7 @@ sub __getDWDSolarData { if ($runh == 12 && !$rad) { $ret = "The reading 'fc${fd}_${runh}_Rad1h' does not appear to be present or has an unusual value.\nRun 'set $name plantConfiguration check' for further information."; - $data{$type}{$name}{statusapi}{DWD}{'?All'}{response_message} = $ret; + $data{$name}{statusapi}{DWD}{'?All'}{response_message} = $ret; debugLog ($paref, "apiCall", "DWD API - ERROR - got unusual data of starttime: $dateTime. ".$ret); } @@ -3493,17 +3312,17 @@ sub __getDWDSolarData { debugLog ($paref, "apiCall", "DWD API - got data -> starttime: $dateTime, reading: fc${fd}_${runh}_Rad1h, rad: $rad kJ/m2"); } - $data{$type}{$name}{solcastapi}{'?All'}{$dateTime}{Rad1h} = sprintf "%.0f", $rad; + $data{$name}{solcastapi}{'?All'}{$dateTime}{Rad1h} = sprintf "%.0f", $rad; my ($ddate, $dtime) = split ' ', $dateTime; # abzurufendes Datum + Zeit my $hod = sprintf "%02d", ((split ':', $dtime)[0] + 1); # abzurufende Zeit my $dday = (split '-', $ddate)[2]; # abzurufender Tag: 01, 02 ... 31 for my $string (@strings) { # für jeden String der Config .. - my $peak = $data{$type}{$name}{strings}{$string}{peak}; # String Peak (kWp) + my $peak = $data{$name}{strings}{$string}{peak}; # String Peak (kWp) $peak *= 1000; # kWp in Wp umrechnen - my $ti = $data{$type}{$name}{strings}{$string}{tilt}; # Neigungswinkel Solarmodule - my $az = $data{$type}{$name}{strings}{$string}{azimut}; # Ausrichtung der Solarmodule + my $ti = $data{$name}{strings}{$string}{tilt}; # Neigungswinkel Solarmodule + my $az = $data{$name}{strings}{$string}{azimut}; # Ausrichtung der Solarmodule $az += 180; # Umsetzung -180 - 180 in 0 - 360 my ($af, $pv, $sdr, $wcc); @@ -3543,13 +3362,13 @@ sub __getDWDSolarData { $pv = sprintf "%.1f", ($rad * $af * $kJtokWh * $peak * $prdef); # Rad wird in kW/m2 erwartet } - $data{$type}{$name}{solcastapi}{$string}{$dateTime}{pv_estimate50} = $pv; # Startzeit wird verwendet, nicht laufende Stunde + $data{$name}{solcastapi}{$string}{$dateTime}{pv_estimate50} = $pv; # Startzeit wird verwendet, nicht laufende Stunde debugLog ($paref, "apiProcess", "DWD API - PV estimate String >$string< => $dateTime, $pv Wh, Afactor: $af ($cafd)"); } } - $data{$type}{$name}{statusapi}{DWD}{'?All'}{response_message} = 'success' if(!$ret); + $data{$name}{statusapi}{DWD}{'?All'}{response_message} = 'success' if(!$ret); return; } @@ -3724,7 +3543,7 @@ sub __VictronVRM_ApiRequestLogin { Log3 ($name, 2, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $msg; + $data{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $msg; return; } @@ -3780,9 +3599,9 @@ sub __VictronVRM_ApiResponseLogin { Log3 ($name, 1, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $err; - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $err; + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -3794,8 +3613,8 @@ sub __VictronVRM_ApiResponseLogin { Log3 ($name, 1, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -3807,12 +3626,12 @@ sub __VictronVRM_ApiResponseLogin { Log3 ($name, 3, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $jdata->{'error_code'}; - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_timestamp} = $t; + $data{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $jdata->{'error_code'}; + $data{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit + $data{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_timestamp} = $t; if ($debug =~ /apiProcess|apiCall/x) { Log3 ($name, 1, "$name DEBUG> SolCast API Call - error_code: ".$jdata->{'error_code'}); @@ -3822,23 +3641,23 @@ sub __VictronVRM_ApiResponseLogin { return; } else { - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{response_message} = 'success'; - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{idUser} = $jdata->{'idUser'}; - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{verification_mode} = $jdata->{'verification_mode'}; - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_timestamp} = $t; + $data{$name}{statusapi}{VictronKi}{'?All'}{response_message} = 'success'; + $data{$name}{statusapi}{VictronKi}{'?All'}{idUser} = $jdata->{'idUser'}; + $data{$name}{statusapi}{VictronKi}{'?All'}{verification_mode} = $jdata->{'verification_mode'}; + $data{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit + $data{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_timestamp} = $t; if ($debug eq 'apiProcess') { Log3 ($name, 1, qq{$name DEBUG> Victron VRM API response Login:\n}. Dumper $jdata); } if (defined $jdata->{'token'}) { - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{token} = 'got successful at '.StatusAPIVal ($hash, 'VictronKi', '?All', 'lastretrieval_time', '-'); - $paref->{token} = $jdata->{'token'}; + $data{$name}{statusapi}{VictronKi}{'?All'}{token} = 'got successful at '.StatusAPIVal ($hash, 'VictronKi', '?All', 'lastretrieval_time', '-'); + $paref->{token} = $jdata->{'token'}; __VictronVRM_ApiRequestForecast ($paref); } else { - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{response_message} = 'empty Token'; + $data{$name}{statusapi}{VictronKi}{'?All'}{response_message} = 'empty Token'; } } } @@ -3920,9 +3739,9 @@ sub __VictronVRM_ApiResponseForecast { Log3 ($name, 1, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $err; - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $err; + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -3934,8 +3753,8 @@ sub __VictronVRM_ApiResponseForecast { Log3 ($name, 1, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -3947,12 +3766,12 @@ sub __VictronVRM_ApiResponseForecast { Log3 ($name, 3, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $jdata->{'error_code'}; - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_timestamp} = $t; + $data{$name}{statusapi}{VictronKi}{'?All'}{response_message} = $jdata->{'error_code'}; + $data{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit + $data{$name}{statusapi}{VictronKi}{'?All'}{lastretrieval_timestamp} = $t; if ($debug =~ /apiProcess|apiCall/x) { Log3 ($name, 1, "$name DEBUG> SolCast API Call - error_code: ".$jdata->{'error_code'}); @@ -3962,8 +3781,8 @@ sub __VictronVRM_ApiResponseForecast { return; } else { - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{todayDoneAPIrequests} += 1; - $data{$type}{$name}{statusapi}{VictronKi}{'?All'}{todayDoneAPIcalls} += 1; + $data{$name}{statusapi}{VictronKi}{'?All'}{todayDoneAPIrequests} += 1; + $data{$name}{statusapi}{VictronKi}{'?All'}{todayDoneAPIcalls} += 1; my $k = 0; while ($jdata->{'records'}{'solar_yield_forecast'}[$k]) { @@ -3983,7 +3802,7 @@ sub __VictronVRM_ApiResponseForecast { my $string = AttrVal ($name, 'setupInverterStrings', '?'); - $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = $val; + $data{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = $val; } $k++; @@ -4006,7 +3825,7 @@ sub __VictronVRM_ApiResponseForecast { $val = sprintf "%.2f", $val; my $string = AttrVal ($name, 'setupInverterStrings', '?'); - $data{$type}{$name}{solcastapi}{$string.'_co'}{$starttmstr}{co_estimate} = $val; + $data{$name}{solcastapi}{$string.'_co'}{$starttmstr}{co_estimate} = $val; } $k++; @@ -4014,8 +3833,8 @@ sub __VictronVRM_ApiResponseForecast { } } - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval ($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval ($stc) - tv_interval ($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval ($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval ($stc) - tv_interval ($sta)); # API Laufzeit ermitteln __VictronVRM_ApiRequestLogout ($paref); @@ -4191,7 +4010,7 @@ sub __openMeteoDWD_ApiRequest { my $apiitv = StatusAPIVal ($hash, 'OpenMeteo', '?All', 'currentAPIinterval', $ometeorepdef); readingsSingleUpdate ($hash, 'nextRadiationAPICall', $hqtxt{after}{$lang}.' '.(timestampToTimestring ($t + $apiitv, $lang))[0], 1); - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{todayDoneAPIcalls} += 1; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{todayDoneAPIcalls} += 1; return; } @@ -4314,11 +4133,11 @@ sub __openMeteoDWD_ApiResponse { Log3 ($name, 1, "$name - $msg"); - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{response_message} = $err; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{response_message} = $err; singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -4331,8 +4150,8 @@ sub __openMeteoDWD_ApiResponse { Log3 ($name, 1, "$name - $msg"); singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } @@ -4340,8 +4159,8 @@ sub __openMeteoDWD_ApiResponse { my $rt = (timestampToTimestring ($t, $lang))[3]; my $jdata = decode_json ($myjson); - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{lastretrieval_time} = $rt; - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{lastretrieval_timestamp} = $t; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{lastretrieval_time} = $rt; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{lastretrieval_timestamp} = $t; ## bei Fehler in API intern kommt ################################### @@ -4355,15 +4174,15 @@ sub __openMeteoDWD_ApiResponse { singleUpdateState ( {hash => $hash, state => $msg, evt => 1} ); - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{response_message} = $jdata->{'reason'}; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{response_message} = $jdata->{'reason'}; - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return; } - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{response_message} = 'success'; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{response_message} = 'success'; if ($debug =~ /apiCall/xs) { Log3 ($name, 1, qq{$name DEBUG> Open-Meteo API Call - server response for PV string "$string"}); @@ -4447,35 +4266,35 @@ sub __openMeteoDWD_ApiResponse { } if ($requestmode eq 'MODEL') { - $data{$type}{$name}{solcastapi}{$string}{$pvtmstr}{pv_estimate50} = $pv; # Startstunde verschieben + $data{$name}{solcastapi}{$string}{$pvtmstr}{pv_estimate50} = $pv; # Startstunde verschieben if ($paref->{begin}) { # im ersten Call den DS löschen -> dann Aufsummierung - delete $data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h}; + delete $data{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h}; } - $data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h} += $rad; # Startstunde verschieben, Rad Werte aller Strings addieren + $data{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h} += $rad; # Startstunde verschieben, Rad Werte aller Strings addieren } ## Wetterdaten ################ my $fwtg = formatWeatherTimestrg ($pvtmstr); # Zeit gemäß DWD_OpenData-Format - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{rr1c} = $rain; - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $pvtmstr; + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{rr1c} = $rain; + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $pvtmstr; $fwtg = formatWeatherTimestrg ($otmstr); # Zeit gemäß DWD_OpenData-Format - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{don} = $don; - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $wcc; - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{ww} = $wid; - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{ttt} = $temp; - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{UpdateTime} = $rt; - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $otmstr; + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{don} = $don; + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $wcc; + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{ww} = $wid; + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{ttt} = $temp; + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{UpdateTime} = $rt; + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $otmstr; if ($k == 0) { - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $curwcc if(defined $curwcc); - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{ww} = $curwid if(defined $curwid); - $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{ttt} = $curtmp if(defined $curtmp); + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $curwcc if(defined $curwcc); + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{ww} = $curwid if(defined $curwid); + $data{$name}{weatherapi}{OpenMeteo}{$fwtg}{ttt} = $curtmp if(defined $curtmp); } $k++; @@ -4499,8 +4318,8 @@ sub __openMeteoDWD_ApiResponse { } if ($k == 0) { - $data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{today} = $sunrise; - $data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{today} = $sunset; + $data{$name}{weatherapi}{OpenMeteo}{sunrise}{today} = $sunrise; + $data{$name}{weatherapi}{OpenMeteo}{sunset}{today} = $sunset; if ($debug =~ /apiProcess/xs) { Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API - Sunrise Today: $sunrise"); @@ -4509,8 +4328,8 @@ sub __openMeteoDWD_ApiResponse { } if ($k == 1) { - $data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{tomorrow} = $sunrise; - $data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{tomorrow} = $sunset; + $data{$name}{weatherapi}{OpenMeteo}{sunrise}{tomorrow} = $sunrise; + $data{$name}{weatherapi}{OpenMeteo}{sunset}{tomorrow} = $sunset; if ($debug =~ /apiProcess/xs) { Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API - Sunrise Tomorrow: $sunrise"); @@ -4537,8 +4356,8 @@ sub __openMeteoDWD_ApiResponse { lang => $lang }; - $data{$type}{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln - $data{$type}{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln + $data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln + $data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln return &$caller($param); } @@ -4557,7 +4376,7 @@ sub ___setOpenMeteoAPIcallKeyData { my $hash = $defs{$name}; - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{todayDoneAPIrequests} += $cequ; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{todayDoneAPIrequests} += $cequ; my $dar = StatusAPIVal ($hash, 'OpenMeteo', '?All', 'todayDoneAPIrequests', 0); my $dac = StatusAPIVal ($hash, 'OpenMeteo', '?All', 'todayDoneAPIcalls', 0); @@ -4566,8 +4385,8 @@ sub ___setOpenMeteoAPIcallKeyData { my $drr = $ometmaxreq - $dar; $drr = 0 if($drr < 0); - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{todayRemainingAPIrequests} = $drr; - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{currentAPIinterval} = $ometeorepdef; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{todayRemainingAPIrequests} = $drr; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{currentAPIinterval} = $ometeorepdef; ## Berechnung des optimalen Request Intervalls ################################################ @@ -4579,7 +4398,7 @@ sub ___setOpenMeteoAPIcallKeyData { my $optrep = $rmdif / ($drr / ($cequ * $asc)); $optrep = $ometeorepdef if($optrep < $ometeorepdef); - $data{$type}{$name}{statusapi}{OpenMeteo}{'?All'}{currentAPIinterval} = $optrep; + $data{$name}{statusapi}{OpenMeteo}{'?All'}{currentAPIinterval} = $optrep; } debugLog ($paref, "apiProcess|apiCall", "Open-Meteo API Call - remaining API Requests: $drr, Request equivalents p. call: $cequ, new call interval: ".StatusAPIVal ($hash, 'OpenMeteo', '?All', 'currentAPIinterval', $ometeorepdef)); @@ -4662,7 +4481,7 @@ sub _getlistNextHours { my $hash = $defs{$name}; my $ret = listDataPool ($hash, 'nexthours'); - $ret .= lineFromSpaces ($ret, 10); + $ret .= lineFromSpaces ($ret, 20); return $ret; } @@ -4833,17 +4652,17 @@ sub _getdwdCatalog { return 'The DWD Station Catalog is forced to loaded into SolarForecast.'; } - if (!scalar keys %{$data{$type}{$name}{dwdcatalog}}) { # Katalog ist nicht geladen - _readCacheFile ({ name => $name, - type => $type, - debug => $paref->{debug}, - file => $dwdcatalog, - cachename => 'dwdcatalog', - title => 'DWD Station Catalog' - } - ); + if (!scalar keys %{$data{$name}{dwdcatalog}}) { # Katalog ist nicht geladen + readCacheFile ({ name => $name, + type => $type, + debug => $paref->{debug}, + file => $dwdcatalog, + cachename => 'dwdcatalog', + title => 'DWD Station Catalog' + } + ); - if (!scalar keys %{$data{$type}{$name}{dwdcatalog}}) { # Ladung von File nicht erfolgreich + if (!scalar keys %{$data{$name}{dwdcatalog}}) { # Ladung von File nicht erfolgreich __dwdStatCatalog_Request ($paref); return $msg; } @@ -4893,33 +4712,33 @@ sub __generateCatOut { my %temp; if ($sort eq 'byName') { - for my $id (keys %{$data{$type}{$name}{dwdcatalog}}) { + for my $id (keys %{$data{$name}{dwdcatalog}}) { $paref->{id} = $id; ($err, $isfil) = ___isCatFiltered ($paref); return (split " at", $err)[0] if($err); next if($isfil); - my $nid = $data{$type}{$name}{dwdcatalog}{$id}{stnam}; - $temp{$nid}{stnam} = $data{$type}{$name}{dwdcatalog}{$id}{stnam}; - $temp{$nid}{id} = $data{$type}{$name}{dwdcatalog}{$id}{id}; - $temp{$nid}{latdec} = $data{$type}{$name}{dwdcatalog}{$id}{latdec}; # Latitude Dezimalgrad - $temp{$nid}{londec} = $data{$type}{$name}{dwdcatalog}{$id}{londec}; # Longitude Dezimalgrad - $temp{$nid}{elev} = $data{$type}{$name}{dwdcatalog}{$id}{elev}; + my $nid = $data{$name}{dwdcatalog}{$id}{stnam}; + $temp{$nid}{stnam} = $data{$name}{dwdcatalog}{$id}{stnam}; + $temp{$nid}{id} = $data{$name}{dwdcatalog}{$id}{id}; + $temp{$nid}{latdec} = $data{$name}{dwdcatalog}{$id}{latdec}; # Latitude Dezimalgrad + $temp{$nid}{londec} = $data{$name}{dwdcatalog}{$id}{londec}; # Longitude Dezimalgrad + $temp{$nid}{elev} = $data{$name}{dwdcatalog}{$id}{elev}; } } elsif ($sort eq 'byID') { - for my $id (keys %{$data{$type}{$name}{dwdcatalog}}) { + for my $id (keys %{$data{$name}{dwdcatalog}}) { $paref->{id} = $id; ($err, $isfil) = ___isCatFiltered ($paref); return (split " at", $err)[0] if($err); next if($isfil); - $temp{$id}{stnam} = $data{$type}{$name}{dwdcatalog}{$id}{stnam}; - $temp{$id}{id} = $data{$type}{$name}{dwdcatalog}{$id}{id}; - $temp{$id}{latdec} = $data{$type}{$name}{dwdcatalog}{$id}{latdec}; # Latitude Dezimalgrad - $temp{$id}{londec} = $data{$type}{$name}{dwdcatalog}{$id}{londec}; # Longitude Dezimalgrad - $temp{$id}{elev} = $data{$type}{$name}{dwdcatalog}{$id}{elev}; + $temp{$id}{stnam} = $data{$name}{dwdcatalog}{$id}{stnam}; + $temp{$id}{id} = $data{$name}{dwdcatalog}{$id}{id}; + $temp{$id}{latdec} = $data{$name}{dwdcatalog}{$id}{latdec}; # Latitude Dezimalgrad + $temp{$id}{londec} = $data{$name}{dwdcatalog}{$id}{londec}; # Longitude Dezimalgrad + $temp{$id}{elev} = $data{$name}{dwdcatalog}{$id}{elev}; } } @@ -5014,9 +4833,9 @@ sub ___isCatFiltered { my $isfil = 0; eval {$isfil = 1 if($filtid && $id !~ /^$filtid$/ixs); - $isfil = 1 if($filtnam && $data{$type}{$name}{dwdcatalog}{$id}{stnam} !~ /^$filtnam$/ixs); - $isfil = 1 if($filtlat && $data{$type}{$name}{dwdcatalog}{$id}{latdec} !~ /^$filtlat$/ixs); - $isfil = 1 if($filtlon && $data{$type}{$name}{dwdcatalog}{$id}{londec} !~ /^$filtlon$/ixs); + $isfil = 1 if($filtnam && $data{$name}{dwdcatalog}{$id}{stnam} !~ /^$filtnam$/ixs); + $isfil = 1 if($filtlat && $data{$name}{dwdcatalog}{$id}{latdec} !~ /^$filtlat$/ixs); + $isfil = 1 if($filtlon && $data{$name}{dwdcatalog}{$id}{londec} !~ /^$filtlon$/ixs); }; if ($@) { @@ -5071,7 +4890,7 @@ return; sub __dwdStatCatalog_Response { my $paref = shift; my $err = shift; - my $data = shift; + my $dat = shift; my $name = $paref->{name}; my $stc = $paref->{stc}; # Startzeit API Abruf @@ -5087,8 +4906,8 @@ sub __dwdStatCatalog_Response { return; } - elsif ($data ne "") { - my @datarr = split "\n", $data; + elsif ($dat ne "") { + my @datarr = split "\n", $dat; my $type = $hash->{TYPE}; for my $s (@datarr) { @@ -5117,14 +4936,14 @@ sub __dwdStatCatalog_Response { my $latdec = sprintf "%.2f", ($latg + ($latm / 60)); my $londec = sprintf "%.2f", ($long + ($lonm / 60)); - $data{$type}{$name}{dwdcatalog}{$id}{id} = $id; - $data{$type}{$name}{dwdcatalog}{$id}{stnam} = $stnam; - $data{$type}{$name}{dwdcatalog}{$id}{icao} = $icao; - $data{$type}{$name}{dwdcatalog}{$id}{lat} = $lat; - $data{$type}{$name}{dwdcatalog}{$id}{latdec} = $latdec; # Latitude Dezimalgrad - $data{$type}{$name}{dwdcatalog}{$id}{lon} = $lon; - $data{$type}{$name}{dwdcatalog}{$id}{londec} = $londec; # Longitude Dezimalgrad - $data{$type}{$name}{dwdcatalog}{$id}{elev} = $elev; + $data{$name}{dwdcatalog}{$id}{id} = $id; + $data{$name}{dwdcatalog}{$id}{stnam} = $stnam; + $data{$name}{dwdcatalog}{$id}{icao} = $icao; + $data{$name}{dwdcatalog}{$id}{lat} = $lat; + $data{$name}{dwdcatalog}{$id}{latdec} = $latdec; # Latitude Dezimalgrad + $data{$name}{dwdcatalog}{$id}{lon} = $lon; + $data{$name}{dwdcatalog}{$id}{londec} = $londec; # Longitude Dezimalgrad + $data{$name}{dwdcatalog}{$id}{elev} = $elev; } $err = writeCacheToFile ($hash, 'dwdcatalog', $dwdcatalog); # DWD Stationskatalog speichern @@ -5136,14 +4955,14 @@ sub __dwdStatCatalog_Response { Log3 ($name, 1, "$name - ERROR - $err"); } - _readCacheFile ({ name => $name, - type => $type, - debug => $debug, - file => $dwdcatalog, - cachename => 'dwdcatalog', - title => 'DWD Station Catalog' - } - ); + readCacheFile ({ name => $name, + type => $type, + debug => $debug, + file => $dwdcatalog, + cachename => 'dwdcatalog', + title => 'DWD Station Catalog' + } + ); } my $prt = sprintf "%.4f", (tv_interval ($stc) - tv_interval ($sta)); # Laufzeit ermitteln @@ -5391,19 +5210,19 @@ sub __updGetUrl { $upd_connecthash{keepalive} = ($url =~ m/localUpdate/ ? 0 : 1); # Forum #49798 $upd_connecthash{forceEncoding} = '' if($unicodeEncoding); - my ($err, $data) = HttpUtils_BlockingGet (\%upd_connecthash); + my ($err, $dat) = HttpUtils_BlockingGet (\%upd_connecthash); if ($err) { $err = "update ERROR: $err"; return ($err, ''); } - if (!$data) { + if (!$dat) { $err = 'update ERROR: empty file received'; return ($err, ''); } -return ('', $data); +return ('', $dat); } ############################################################### @@ -5498,26 +5317,18 @@ sub Attr { deleteReadingspec ($hash, 'Battery_.*'); } - delete $data{$type}{$name}{circular}{99}{lastTsMaxSocRchd}; - delete $data{$type}{$name}{circular}{99}{nextTsMaxSocChge}; + delete $data{$name}{circular}{99}{lastTsMaxSocRchd}; + delete $data{$name}{circular}{99}{nextTsMaxSocChge}; } if ($aName eq 'ctrlGenPVdeviation' && $aVal eq 'daily') { readingsDelete ($hash, 'Today_PVdeviation'); - delete $data{$type}{$name}{circular}{99}{tdayDvtn}; + delete $data{$name}{circular}{99}{tdayDvtn}; } if ($aName eq 'graphicHeaderOwnspecValForm') { - if ($cmd ne 'set') { - delete $data{$type}{$name}{func}{ghoValForm}; - return; - } - - my $code = $aVal; - ($err, $code) = checkCode ($name, $code); + $err = isGhoValFormValid ($name, $aVal); return $err if($err); - - $data{$type}{$name}{func}{ghoValForm} = $code; } if ($cmd eq 'set') { @@ -5605,7 +5416,7 @@ sub _attrconsumer { ## no critic "not used" if (exists $h->{mode} && $h->{mode} !~ /^(?:can|must)$/xs) { if ($h->{mode} =~ /.*:.*/xs) { my ($dv, $rd) = split ':', $h->{mode}; - ($err) = isDeviceValid ( { name => $hash->{NAME}, obj => $dv, method => 'string' } ); + ($err) = isDeviceValid ( { name => $name, obj => $dv, method => 'string' } ); return $err if($err); my $mode = ReadingsVal ($dv, $rd, ''); @@ -5617,6 +5428,21 @@ sub _attrconsumer { ## no critic "not used" return qq{The mode "$h->{mode}" isn't allowed!}; } } + + if (exists $h->{surpmeth}) { + if ($h->{surpmeth} =~ /.*:.*/xs) { + my ($dv, $rd) = split ':', $h->{surpmeth}; + ($err) = isDeviceValid ( { name => $name, obj => $dv, method => 'string' } ); + return $err if($err); + + if (!isNumeric( ReadingsVal ($dv, $rd, '') )) { + return "The reading '$rd' of device '$dv' is invalid or doesn't contain a valid numeric value"; + } + } + elsif ($h->{surpmeth} !~ /^[2-9]$|^1[0-9]$|^20$|^median$|^default$/xs) { + return qq{The surpmeth "$h->{surpmeth}" is wrong. It must contain a ':', 'median', 'default' or an integer value of '2 .. 20'.}; + } + } my $valid; @@ -5697,7 +5523,7 @@ sub _attrconsumer { ## no critic "not used" writeCacheToFile ($hash, 'consumers', $csmcache.$name); # Cache File Consumer schreiben - $data{$type}{$name}{current}{consumerCollected} = 0; # Consumer neu sammeln + $data{$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); @@ -5805,7 +5631,7 @@ sub _attrflowGraphicControl { ## no critic "not used" strokewidth ) ) { - delete $data{$type}{$name}{current}{$av}; + delete $data{$name}{current}{$av}; } if ($cmd eq 'set') { @@ -5833,7 +5659,7 @@ sub _attrflowGraphicControl { ## no critic "not used" next if(!$comp); if ($h->{$key} =~ /^$comp$/xs) { - $data{$type}{$name}{current}{$key} = $h->{$key}; + $data{$name}{current}{$key} = $h->{$key}; } else { return "The key '$key=$h->{$key}' is not specified correctly. Please use a valid value."; @@ -5883,22 +5709,21 @@ sub _attrMeterDev { ## no critic "not used" elsif ($paref->{cmd} eq 'del') { readingsDelete ($hash, "Current_GridConsumption"); readingsDelete ($hash, "Current_GridFeedIn"); - delete $data{$type}{$name}{circular}{99}{initdayfeedin}; - delete $data{$type}{$name}{circular}{99}{gridcontotal}; - delete $data{$type}{$name}{circular}{99}{initdaygcon}; - delete $data{$type}{$name}{circular}{99}{feedintotal}; - delete $data{$type}{$name}{current}{gridconsumption}; - delete $data{$type}{$name}{current}{tomorrowconsumption}; - delete $data{$type}{$name}{current}{gridfeedin}; - delete $data{$type}{$name}{current}{consumption}; - delete $data{$type}{$name}{current}{autarkyrate}; - delete $data{$type}{$name}{current}{selfconsumption}; - delete $data{$type}{$name}{current}{selfconsumptionrate}; - delete $data{$type}{$name}{current}{eFeedInTariff}; - delete $data{$type}{$name}{current}{eFeedInTariffCcy}; - delete $data{$type}{$name}{current}{ePurchasePrice}; - delete $data{$type}{$name}{current}{ePurchasePriceCcy}; - delete $data{$type}{$name}{current}{x_remote}; + delete $data{$name}{circular}{99}{initdayfeedin}; + delete $data{$name}{circular}{99}{gridcontotal}; + delete $data{$name}{circular}{99}{initdaygcon}; + delete $data{$name}{circular}{99}{feedintotal}; + delete $data{$name}{current}{gridconsumption}; + delete $data{$name}{current}{tomorrowconsumption}; + delete $data{$name}{current}{gridfeedin}; + delete $data{$name}{current}{consumption}; + delete $data{$name}{current}{autarkyrate}; + delete $data{$name}{current}{selfconsumption}; + delete $data{$name}{current}{selfconsumptionrate}; + delete $data{$name}{current}{eFeedInTariff}; + delete $data{$name}{current}{eFeedInTariffCcy}; + delete $data{$name}{current}{ePurchasePrice}; + delete $data{$name}{current}{ePurchasePriceCcy}; } InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); @@ -5930,18 +5755,18 @@ sub _attrProducerDev { ## no critic "not used" return qq{The syntax of '$aName' is not correct. Please consider the commandref.}; } - delete $data{$type}{$name}{producers}{$pn}{picon}; + delete $data{$name}{producers}{$pn}{picon}; } elsif ($paref->{cmd} eq 'del') { - for my $k (keys %{$data{$type}{$name}{producers}}) { - delete $data{$type}{$name}{producers}{$k} if($k eq $pn); + for my $k (keys %{$data{$name}{producers}}) { + delete $data{$name}{producers}{$k} if($k eq $pn); } readingsDelete ($hash, 'Current_PP'.$pn); deleteReadingspec ($hash, ".*_PPreal".$pn); - for my $hod (keys %{$data{$type}{$name}{circular}}) { - delete $data{$type}{$name}{circular}{$hod}{'pprl'.$pn}; + for my $hod (keys %{$data{$name}{circular}}) { + delete $data{$name}{circular}{$hod}{'pprl'.$pn}; } } @@ -5995,32 +5820,32 @@ sub _attrInverterDev { ## no critic "not used" if ($h->{strings}) { for my $s (split ',', $h->{strings}) { - if (!grep /^$s$/, keys %{$data{$type}{$name}{strings}}) { + if (!grep /^$s$/, keys %{$data{$name}{strings}}) { return qq{The string '$s' is not a valid string name defined in attribute 'setupInverterStrings'.}; } } } - $data{$type}{$name}{circular}{99}{attrInvChangedTs} = int time; + $data{$name}{circular}{99}{attrInvChangedTs} = int time; - delete $data{$type}{$name}{inverters}{$in}{invertercap}; - delete $data{$type}{$name}{inverters}{$in}{ilimit}; - delete $data{$type}{$name}{inverters}{$in}{iicon}; - delete $data{$type}{$name}{inverters}{$in}{istrings}; - delete $data{$type}{$name}{inverters}{$in}{iasynchron}; - delete $data{$type}{$name}{inverters}{$in}{ifeed}; + delete $data{$name}{inverters}{$in}{invertercap}; + delete $data{$name}{inverters}{$in}{ilimit}; + delete $data{$name}{inverters}{$in}{iicon}; + delete $data{$name}{inverters}{$in}{istrings}; + delete $data{$name}{inverters}{$in}{iasynchron}; + delete $data{$name}{inverters}{$in}{ifeed}; } elsif ($paref->{cmd} eq 'del') { - for my $k (keys %{$data{$type}{$name}{inverters}}) { - delete $data{$type}{$name}{inverters}{$k} if($k eq $in); + for my $k (keys %{$data{$name}{inverters}}) { + delete $data{$name}{inverters}{$k} if($k eq $in); } readingsDelete ($hash, 'Current_PV'); - undef @{$data{$type}{$name}{current}{genslidereg}}; + undef @{$data{$name}{current}{genslidereg}}; if ($in eq '01') { # wenn der letzte Inverter gelöscht wurde deleteReadingspec ($hash, '.*_PVreal' ); - delete $data{$type}{$name}{circular}{99}{attrInvChangedTs}; + delete $data{$name}{circular}{99}{attrInvChangedTs}; } } @@ -6050,9 +5875,9 @@ sub _attrInverterStrings { ## no critic "not used" my @istrings = split ",", $aVal; - for my $k (keys %{$data{$type}{$name}{solcastapi}}) { + for my $k (keys %{$data{$name}{solcastapi}}) { next if ($k =~ /\?/xs || grep /^$k$/, @istrings); - delete $data{$type}{$name}{solcastapi}{$k}; + delete $data{$name}{solcastapi}{$k}; } } @@ -6191,18 +6016,20 @@ sub _attrBatteryDev { ## no critic "not used" readingsDelete ($hash, 'Current_PowerBatOut'); readingsDelete ($hash, 'Current_BatCharge'); deleteReadingspec ($hash, 'Battery_.*'); - undef @{$data{$type}{$name}{current}{socslidereg}}; - delete $data{$type}{$name}{circular}{99}{lastTsMaxSocRchd}; - delete $data{$type}{$name}{circular}{99}{nextTsMaxSocChge}; - delete $data{$type}{$name}{circular}{99}{initdaybatintot}; - delete $data{$type}{$name}{circular}{99}{initdaybatouttot}; - delete $data{$type}{$name}{circular}{99}{batintot}; - delete $data{$type}{$name}{circular}{99}{batouttot}; - delete $data{$type}{$name}{current}{powerbatout}; - delete $data{$type}{$name}{current}{powerbatin}; - delete $data{$type}{$name}{current}{batcharge}; - delete $data{$type}{$name}{current}{batinstcap}; - delete $data{$type}{$name}{current}{batasynchron}; + undef @{$data{$name}{current}{socslidereg}}; + + delete $data{$name}{circular}{99}{lastTsMaxSocRchd}; + delete $data{$name}{circular}{99}{nextTsMaxSocChge}; + delete $data{$name}{circular}{99}{initdaybatintot}; + delete $data{$name}{circular}{99}{initdaybatouttot}; + delete $data{$name}{circular}{99}{batintot}; + delete $data{$name}{circular}{99}{batouttot}; + + delete $data{$name}{current}{powerbatout}; + delete $data{$name}{current}{powerbatin}; + delete $data{$name}{current}{batcharge}; + delete $data{$name}{current}{batinstcap}; + delete $data{$name}{current}{batasynchron}; } InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); @@ -6303,7 +6130,7 @@ sub _attrRadiationAPI { ## no critic "not used" return qq{Please complete command "set $name setupStringAzimuth".} if(!$dir); } - $data{$type}{$name}{current}{allStringsFullfilled} = 0; # Stringkonfiguration neu prüfen lassen + $data{$name}{current}{allStringsFullfilled} = 0; # Stringkonfiguration neu prüfen lassen } readingsDelete ($hash, 'nextRadiationAPICall'); @@ -6406,7 +6233,7 @@ sub Notify { return; } - centralTask ($myHash, 0); # keine Events in SolarForecast außer 'state' + centralTask ($myHash, 1); return; } } @@ -6433,7 +6260,7 @@ sub Notify { return; } - centralTask ($myHash, 0); # keine Events in SolarForecast außer 'state' + centralTask ($myHash, 1); return; } } @@ -6461,7 +6288,7 @@ sub Notify { return; } - centralTask ($myHash, 0); # keine Events in SolarForecast außer 'state' + centralTask ($myHash, 1); return; } } @@ -6477,7 +6304,7 @@ sub Notify { my ($cname, $cindex, $dswname); my $type = $myHash->{TYPE}; - for my $c (sort{$a<=>$b} keys %{$data{$type}{$myName}{consumers}}) { + for my $c (sort{$a<=>$b} keys %{$data{$myName}{consumers}}) { ($err, $cname, $dswname) = getCDnames ($myHash, $c); if ($devName eq $cname) { @@ -6589,11 +6416,10 @@ sub Rename { my $hash = $defs{$old_name}; my $type = (split '::', __PACKAGE__)[1]; - $data{$type}{$new_name} = $data{$type}{$old_name}; - delHashRefDeep ($data{$type}{$old_name}); - delete $data{$type}{$old_name}; + $data{$new_name} = $data{$old_name}; + delete $data{$old_name}; - my @ftd = _searchCacheFiles ($old_name); + my @ftd = searchCacheFiles ($old_name); for my $oldf (@ftd) { my $newf = $oldf; @@ -6602,7 +6428,7 @@ sub Rename { Log3 ($new_name, 2, qq{$new_name - WARNING - File "$oldf" could not be renamed: $!}); } - # Log3 ($new_name, 1, qq{$new_name - Dump -> \n}. Dumper $data{$type}{$new_name}); + # Log3 ($new_name, 1, qq{$new_name - Dump -> \n}. Dumper $data{$new_name}); return; } @@ -6659,7 +6485,7 @@ sub Delete { my $arg = shift; my $name = $hash->{NAME}; - my @ftd = _searchCacheFiles ($name); + my @ftd = searchCacheFiles ($name); for my $f (@ftd) { my $err = FileDelete ($f); @@ -6672,36 +6498,11 @@ sub Delete { } } - my $type = $hash->{TYPE}; - - delHashRefDeep ($data{$type}{$name}); - delete $data{$type}{$name}; + delete $data{$name}; return; } -################################################################# -# Cache Files im Cache Directory suchen und als Array -# zurückliefern -################################################################# -sub _searchCacheFiles { - my $name = shift; - - my @ftd; - - opendir (DIR, $cachedir); - - while (my $file = readdir (DIR)) { - next unless (-f "$cachedir/$file"); - next unless ($file =~ /_SolarForecast_${name}/); - push @ftd, "$cachedir/$file"; - } - - closedir (DIR); - -return @ftd; -} - ################################################################ # Timer schreiben Memory Struktur in File ################################################################ @@ -6802,27 +6603,221 @@ sub delConsumerFromMem { for my $d (1..31) { $d = sprintf("%02d", $d); - delete $data{$type}{$name}{pvhist}{$d}{99}{"csme${c}"}; - delete $data{$type}{$name}{pvhist}{$d}{99}{"cyclescsm${c}"}; - delete $data{$type}{$name}{pvhist}{$d}{99}{"hourscsme${c}"}; - delete $data{$type}{$name}{pvhist}{$d}{99}{"avgcycmntscsm${c}"}; + delete $data{$name}{pvhist}{$d}{99}{"csme${c}"}; + delete $data{$name}{pvhist}{$d}{99}{"cyclescsm${c}"}; + delete $data{$name}{pvhist}{$d}{99}{"hourscsme${c}"}; + delete $data{$name}{pvhist}{$d}{99}{"avgcycmntscsm${c}"}; for my $i (1..24) { $i = sprintf("%02d", $i); - delete $data{$type}{$name}{pvhist}{$d}{$i}{"csmt${c}"}; - delete $data{$type}{$name}{pvhist}{$d}{$i}{"csme${c}"}; - delete $data{$type}{$name}{pvhist}{$d}{$i}{"minutescsm${c}"}; + delete $data{$name}{pvhist}{$d}{$i}{"csmt${c}"}; + delete $data{$name}{pvhist}{$d}{$i}{"csme${c}"}; + delete $data{$name}{pvhist}{$d}{$i}{"minutescsm${c}"}; } } - delHashRefDeep ($data{$type}{$name}{consumers}{$c}); - delete $data{$type}{$name}{consumers}{$c}; + delete $data{$name}{consumers}{$c}; Log3 ($name, 3, qq{$name - Consumer "$c - $calias" deleted from memory}); return; } +################################################################# +# Cache Files im Cache Directory suchen und als Array +# zurückliefern +################################################################# +sub searchCacheFiles { + my $name = shift; + + my @ftd; + + opendir (DIR, $cachedir); + + while (my $file = readdir (DIR)) { + next unless (-f "$cachedir/$file"); + next unless ($file =~ /_SolarForecast_${name}/); + push @ftd, "$cachedir/$file"; + } + + closedir (DIR); + +return @ftd; +} + +################################################################ +# gesicherte Cache-Files vom Filesystem nachladen falls die +# jeweiligen Online-Speicher nicht gefüllt sind (zum +# Beispiel nach einem reload 76_SolarForecast.pm) +################################################################ +sub reloadCacheFiles { + my $paref = shift; + my $name = $paref->{name}; + + return if(CurrentVal ($name, 'cachefilesloaded', 0)); + + $paref->{file} = $pvhcache.$name; # Cache File PV History einlesen wenn vorhanden + $paref->{cachename} = 'pvhist'; + $paref->{title} = 'pvHistory'; + readCacheFile ($paref); + + $paref->{file} = $pvccache.$name; # Cache File PV Circular einlesen wenn vorhanden + $paref->{cachename} = 'circular'; + $paref->{title} = 'pvCircular'; + readCacheFile ($paref); + + $paref->{file} = $csmcache.$name; # Cache File Consumer einlesen wenn vorhanden + $paref->{cachename} = 'consumers'; + $paref->{title} = 'consumerMaster'; + readCacheFile ($paref); + + $paref->{file} = $scpicache.$name; # Cache File SolCast API Werte einlesen wenn vorhanden + $paref->{cachename} = 'solcastapi'; + $paref->{title} = 'radiationApiData'; + readCacheFile ($paref); + + $paref->{file} = $statcache.$name; # Cache File API-Status einlesen wenn vorhanden + $paref->{cachename} = 'statusapi'; + $paref->{title} = 'statusApiData'; + readCacheFile ($paref); + + $paref->{file} = $weathercache.$name; # Cache File Weather-API Daten einlesen wenn vorhanden + $paref->{cachename} = 'weatherapi'; + $paref->{title} = 'weatherApiData'; + readCacheFile ($paref); + + $paref->{file} = $aitrained.$name; # AI Cache File einlesen wenn vorhanden + $paref->{cachename} = 'aitrained'; + $paref->{title} = 'aiTrainedData'; + readCacheFile ($paref); + + $paref->{file} = $airaw.$name; # AI Rawdaten File einlesen wenn vorhanden + $paref->{cachename} = 'airaw'; + $paref->{title} = 'aiRawData'; + readCacheFile ($paref); + + delete $paref->{file}; + delete $paref->{cachename}; + delete $paref->{title}; + + $data{$name}{current}{cachefilesloaded} = 1; + +return; +} + +################################################################ +# Cachefile lesen +################################################################ +sub readCacheFile { + my $paref = shift; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $file = $paref->{file}; + my $cachename = $paref->{cachename}; + my $title = $paref->{title}; + + my $hash = $defs{$name}; + + if ($cachename eq 'aitrained') { + my ($err, $dtree) = fileRetrieve ($file); + + if (!$err && $dtree) { + my $valid = $dtree->isa('AI::DecisionTree'); + + if ($valid) { + $data{$name}{aidectree}{aitrained} = $dtree; + $data{$name}{current}{aitrainstate} = 'ok'; + + Log3 ($name, 3, qq{$name - cached data "$title" restored}); + return; + } + } + + delete $data{$name}{circular}{99}{aitrainLastFinishTs}; + delete $data{$name}{circular}{99}{runTimeTrainAI}; + return; + } + + if ($cachename eq 'airaw') { + my ($err, $dat) = fileRetrieve ($file); + + if (!$err && $dat) { + $data{$name}{aidectree}{airaw} = $dat; + $data{$name}{current}{aitrawstate} = 'ok'; + + Log3 ($name, 3, qq{$name - cached data "$title" restored}); + } + + return; + } + + if ($cachename eq 'statusapi') { + my ($err, $statapi) = fileRetrieve ($file); + + if (!$err && $statapi) { + $data{$name}{statusapi} = $statapi; + + Log3 ($name, 3, qq{$name - cached data "$title" restored}); + } + + return; + } + + if ($cachename eq 'weatherapi') { + my ($err, $wthtapi) = fileRetrieve ($file); + + if (!$err && $wthtapi) { + $data{$name}{weatherapi} = $wthtapi; + + Log3 ($name, 3, qq{$name - cached data "$title" restored}); + } + + return; + } + + if ($cachename eq 'dwdcatalog') { + my ($err, $dwdc) = fileRetrieve ($file); + + if (!$err && $dwdc) { + $data{$name}{dwdcatalog} = $dwdc; + + debugLog ($paref, 'dwdComm', qq{$title restored}); + } + + return; + } + + if ($cachename eq 'plantconfig') { + my ($err, $plantcfg) = fileRetrieve ($file); + return $err if($err); + my ($nr, $na); + + if ($plantcfg) { + ($nr, $na) = _restorePlantConfig ($hash, $plantcfg); + Log3 ($name, 3, qq{$name - cached data "$title" restored. Number of restored Readings/Attributes: $nr/$na}); + } + + return ('', $nr, $na); + } + + my ($error, @content) = FileRead ($file); + + if (!$error) { + my $json = join "", @content; + my ($success) = evaljson ($hash, $json); + + if ($success) { + $data{$name}{$cachename} = decode_json ($json); + Log3 ($name, 3, qq{$name - cached data "$title" restored}); + } + else { + Log3 ($name, 1, qq{$name - WARNING - The content of file "$file" is not readable or may be corrupt}); + } + } + +return; +} + ################################################################ # Daten in File wegschreiben ################################################################ @@ -6865,10 +6860,10 @@ sub writeCacheToFile { } if ($cachename eq 'airaw') { - my $data = AiRawdataVal ($hash, '', '', ''); + my $dat = AiRawdataVal ($hash, '', '', undef); - if ($data) { - $error = fileStore ($data, $file); + if (defined $dat) { + $error = fileStore ($dat, $file); if ($error) { $err = qq{ERROR while writing AI data to file "$file": $error}; @@ -6885,8 +6880,8 @@ sub writeCacheToFile { } if ($cachename eq 'dwdcatalog') { - if (scalar keys %{$data{$type}{$name}{dwdcatalog}}) { - $error = fileStore ($data{$type}{$name}{dwdcatalog}, $file); + if (scalar keys %{$data{$name}{dwdcatalog}}) { + $error = fileStore ($data{$name}{dwdcatalog}, $file); if ($error) { $err = qq{ERROR while writing DWD Station Catalog to file "$file": $error}; @@ -6902,8 +6897,8 @@ sub writeCacheToFile { } if ($cachename eq 'statusapi') { - if (scalar keys %{$data{$type}{$name}{statusapi}}) { - $error = fileStore ($data{$type}{$name}{statusapi}, $file); + if (scalar keys %{$data{$name}{statusapi}}) { + $error = fileStore ($data{$name}{statusapi}, $file); if ($error) { $err = qq{ERROR while writing API Status to file "$file": $error}; @@ -6919,8 +6914,8 @@ sub writeCacheToFile { } if ($cachename eq 'weatherapi') { - if (scalar keys %{$data{$type}{$name}{weatherapi}}) { - $error = fileStore ($data{$type}{$name}{weatherapi}, $file); + if (scalar keys %{$data{$name}{weatherapi}}) { + $error = fileStore ($data{$name}{weatherapi}, $file); if ($error) { $err = qq{ERROR while writing API Status to file "$file": $error}; @@ -6954,11 +6949,10 @@ sub writeCacheToFile { return ('', $nr, $na); } - - return if(!$data{$type}{$name}{$cachename}); - + my @arr; - push @arr, encode_json ($data{$type}{$name}{$cachename}); + return if(!keys %{$data{$name}{$cachename}}); + push @arr, encode_json ($data{$name}{$cachename}); $error = FileWrite ($file, @arr); @@ -7122,10 +7116,12 @@ sub _newCycTime { my $hash = shift; my $t = shift; my $interval = shift; + + my $name = $hash->{NAME}; if (!$interval) { $hash->{MODE} = 'Manual / Event-controlled'; - $data{$hash->{TYPE}}{$hash->{NAME}}{current}{nextCycleTime} = 0; + $data{$name}{current}{nextCycleTime} = 0; storeReading ('nextCycletime', 'Manual / Event-controlled'); return; } @@ -7133,7 +7129,7 @@ sub _newCycTime { my $new = $t + $interval; # nächste Wiederholungszeit $hash->{MODE} = 'Automatic / Event-controlled - next planned Cycletime: '.FmtTime($new); - $data{$hash->{TYPE}}{$hash->{NAME}}{current}{nextCycleTime} = $new; + $data{$name}{current}{nextCycleTime} = $new; storeReading ('nextCycletime', FmtTime($new)); return; @@ -7215,63 +7211,66 @@ sub centralTask { RemoveInternalTimer ($hash, 'FHEM::SolarForecast::singleUpdateState'); return if(!$init_done); - + setModel ($hash); # Model setzen + + my (undef, $disabled, $inactive) = controller ($name); + return if($disabled || $inactive); # disabled / inactive + + reloadCacheFiles ( {name => $name, type => $type} ); # Cache-Files vom Filesystem nachladen falls nötig ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ########################################################################################################################## - if (exists $data{$type}{$name}{solcastapi}{'?IdPair'}) { # 29.11.2024 - for my $pk (keys %{$data{$type}{$name}{solcastapi}{'?IdPair'}}) { + if (exists $data{$name}{solcastapi}{'?IdPair'}) { # 29.11.2024 + for my $pk (keys %{$data{$name}{solcastapi}{'?IdPair'}}) { my $apikey = RadiationAPIVal ($hash, '?IdPair', $pk, 'apikey', ''); my $rtid = RadiationAPIVal ($hash, '?IdPair', $pk, 'rtid', ''); if ($apikey && $rtid) { - $data{$type}{$name}{statusapi}{'?IdPair'}{$pk}{rtid} = $rtid; - $data{$type}{$name}{statusapi}{'?IdPair'}{$pk}{apikey} = $apikey; + $data{$name}{statusapi}{'?IdPair'}{$pk}{rtid} = $rtid; + $data{$name}{statusapi}{'?IdPair'}{$pk}{apikey} = $apikey; - delete $data{$type}{$name}{solcastapi}{'?IdPair'}; + delete $data{$name}{solcastapi}{'?IdPair'}{$pk}; } } + delete $data{$name}{solcastapi}{'?IdPair'}; } - if (exists $data{$type}{$name}{solcastapi}{'?All'}{'?All'}) { # 29.11.2024 + if (exists $data{$name}{solcastapi}{'?All'}{'?All'}) { # 29.11.2024 my ($rapi, $wapi) = getStatusApiName ($hash); - for my $key (keys %{$data{$type}{$name}{solcastapi}{'?All'}{'?All'}}) { + for my $key (keys %{$data{$name}{solcastapi}{'?All'}{'?All'}}) { my $val = RadiationAPIVal ($hash, '?All', '?All', $key, ''); if ($rapi && $val) { - $data{$type}{$name}{statusapi}{$rapi}{'?All'}{$key} = $val; - delete $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{$key}; + $data{$name}{statusapi}{$rapi}{'?All'}{$key} = $val; + delete $data{$name}{solcastapi}{'?All'}{'?All'}{$key}; } } - delete $data{$type}{$name}{solcastapi}{'?All'}{'?All'}; + delete $data{$name}{solcastapi}{'?All'}{'?All'}; } - if (keys %{$data{$type}{$name}{solcastapi}{'?All'}}) { # 29.11.2024 - for my $idx (keys %{$data{$type}{$name}{solcastapi}{'?All'}}) { - delete $data{$type}{$name}{solcastapi}{'?All'}{$idx} if($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs); - delete $data{$type}{$name}{solcastapi}{'?All'}{$idx} if($idx eq 'sunrise'); - delete $data{$type}{$name}{solcastapi}{'?All'}{$idx} if($idx eq 'sunset'); + if (keys %{$data{$name}{solcastapi}{'?All'}}) { # 29.11.2024 + for my $idx (keys %{$data{$name}{solcastapi}{'?All'}}) { + delete $data{$name}{solcastapi}{'?All'}{$idx} if($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs); + delete $data{$name}{solcastapi}{'?All'}{$idx} if($idx eq 'sunrise'); + delete $data{$name}{solcastapi}{'?All'}{$idx} if($idx eq 'sunset'); } } - delete $data{$type}{$name}{solcastapi}{'?All'} if(!keys %{$data{$type}{$name}{solcastapi}{'?All'}}); + delete $data{$name}{solcastapi}{'?All'} if(!keys %{$data{$name}{solcastapi}{'?All'}}); my $vrmcr = RadiationAPIVal ($hash, '?VRM', '?API', 'credentials', ''); # 29.11.2024 if ($vrmcr) { - $data{$type}{$name}{statusapi}{'?VRM'}{'?API'}{credentials} = $vrmcr; - delete $data{$type}{$name}{solcastapi}{'?VRM'}; + $data{$name}{statusapi}{'?VRM'}{'?API'}{credentials} = $vrmcr; + delete $data{$name}{solcastapi}{'?VRM'}; } ########################################################################################################################## - my (undef, $disabled, $inactive) = controller ($name); - return if($disabled || $inactive); # disabled / inactive - if (!CurrentVal ($hash, 'allStringsFullfilled', 0)) { # die String Konfiguration erstellen wenn noch nicht erfolgreich ausgeführt - my $ret = createStringConfig ($hash); + my $ret = _createStringConfig ($hash); if ($ret) { if (!CurrentVal ($hash, 'setupcomplete', 0)) { @@ -7296,7 +7295,7 @@ sub centralTask { 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{$name}{current}{ctrunning} = 1; # Central Task running Statusbit InternalTimer (gettimeofday() + 1.2, "FHEM::SolarForecast::releaseCentralTask", $hash, 0); # Freigabe centralTask @@ -7326,7 +7325,6 @@ sub centralTask { $centpars->{state} = 'updated'; # kann durch Subs überschrieben werden! - # _composeRemoteObj ($centpars); # Remote Objekte identifizieren und zusammenstellen _getMoonPhase ($centpars); # aktuelle Mondphase ermittteln und speichern _collectAllRegConsumers ($centpars); # alle Verbraucher Infos laden _specialActivities ($centpars); # zusätzliche Events generieren + Sonderaufgaben @@ -7372,19 +7370,17 @@ return; ################################################################ # Erstellen der Stringkonfiguration -# Stringhash: $data{$type}{$name}{strings} ################################################################ -sub createStringConfig { ## no critic "not used" +sub _createStringConfig { ## no critic "not used" my $hash = shift; 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; + delete $data{$name}{strings}; # Stringhash zurücksetzen + $data{$name}{current}{allStringsFullfilled} = 0; my @istrings = split ",", AttrVal ($name, 'setupInverterStrings', ''); # Stringbezeichner - $data{$type}{$name}{current}{allstringscount} = scalar @istrings; # Anzahl der Anlagenstrings + $data{$name}{current}{allstringscount} = scalar @istrings; # Anzahl der Anlagenstrings if (!@istrings) { return qq{Define all used strings with command "attr $name setupInverterStrings" first.}; @@ -7394,12 +7390,12 @@ sub createStringConfig { ## no critic "not used" return qq{Please complete attribute 'setupStringPeak'} if(!$peak); my ($aa,$ha) = parseParams ($peak); - delete $data{$type}{$name}{current}{allstringspeak}; + delete $data{$name}{current}{allstringspeak}; while (my ($strg, $pp) = each %$ha) { if (grep /^$strg$/, @istrings) { - $data{$type}{$name}{strings}{$strg}{peak} = $pp; - $data{$type}{$name}{current}{allstringspeak} += $pp * 1000; # insgesamt installierte Peakleistung in W + $data{$name}{strings}{$strg}{peak} = $pp; + $data{$name}{current}{allstringspeak} += $pp * 1000; # insgesamt installierte Peakleistung in W } else { return qq{Check 'setupStringPeak' -> the stringname '$strg' is not defined as valid string in attribute 'setupInverterStrings'}; @@ -7414,7 +7410,7 @@ sub createStringConfig { ## no critic "not used" while (my ($is, $pk) = each %$hd) { if (grep /^$is$/, @istrings) { - $data{$type}{$name}{strings}{$is}{pk} = $pk; + $data{$name}{strings}{$is}{pk} = $pk; } else { return qq{Check "setupRoofTops" -> the stringname "$is" is not defined as valid string in attribute "setupInverterStrings"}; @@ -7437,7 +7433,7 @@ sub createStringConfig { ## no critic "not used" while (my ($key, $value) = each %$ht) { if (grep /^$key$/, @istrings) { - $data{$type}{$name}{strings}{$key}{tilt} = $value; + $data{$name}{strings}{$key}{tilt} = $value; } else { return qq{Check "setupStringDeclination" -> the stringname "$key" is not defined as valid string in attribute "setupInverterStrings"}; @@ -7452,7 +7448,7 @@ sub createStringConfig { ## no critic "not used" while (my ($key, $value) = each %$hd) { if (grep /^$key$/, @istrings) { - $data{$type}{$name}{strings}{$key}{azimut} = _ident2azimuth ($value) // return $iwrong; + $data{$name}{strings}{$key}{azimut} = __ident2azimuth ($value) // return $iwrong; } else { return qq{Check "setupStringAzimuth" -> the stringname "$key" is not defined as valid string in attribute "setupInverterStrings"}; @@ -7460,13 +7456,13 @@ sub createStringConfig { ## no critic "not used" } } - if (!keys %{$data{$type}{$name}{strings}}) { + if (!keys %{$data{$name}{strings}}) { return qq{The string configuration seems to be incomplete. \n}. qq{Please check the settings of setupInverterStrings, setupStringPeak, setupStringAzimuth, setupStringDeclination }. qq{and/or setupRoofTops if SolCast-API is used.}; } - my @sca = keys %{$data{$type}{$name}{strings}}; # Gegencheck ob nicht mehr Strings in setupInverterStrings enthalten sind als eigentlich verwendet + my @sca = keys %{$data{$name}{strings}}; # Gegencheck ob nicht mehr Strings in setupInverterStrings enthalten sind als eigentlich verwendet my @tom; for my $sn (@istrings) { @@ -7478,7 +7474,7 @@ sub createStringConfig { ## no critic "not used" return qq{Some Strings are not used. Please delete this string names from "setupInverterStrings" :}.join ",",@tom; } - $data{$type}{$name}{current}{allStringsFullfilled} = 1; + $data{$name}{current}{allStringsFullfilled} = 1; return; } @@ -7487,7 +7483,7 @@ return; # formt einen Azimut-Bezeichner in ein Azimut um # numerische werden direkt zurück gegeben ################################################################ -sub _ident2azimuth { +sub __ident2azimuth { my $id = shift; return $id if(isNumeric ($id)); @@ -7505,106 +7501,6 @@ sub _ident2azimuth { return $az; } -################################################################ -# Steuerparameter berechnen / festlegen -################################################################ -sub controller { - my $name = shift; - - 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); -} - -##################################################################### -# Remote Objekte identifizieren und zusammenstellen -# @fhem.myds.me:8088/api/ -##################################################################### -sub _composeRemoteObj { - my $paref = shift; - my $name = $paref->{name}; - - $paref->{obj} = 'setupMeterDev'; - $paref->{method} = 'attr'; - - __remoteMeterObj ($paref); - - delete $paref->{method}; - delete $paref->{obj}; - -return; -} - -##################################################################### -# Remote Meter Objekt identifizieren und zusammenstellen -##################################################################### -sub __remoteMeterObj { - my $paref = shift; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $obj = $paref->{obj}; - my $method = $paref->{method} // return qq{no extraction method for Object '$obj' defined}; - - my $err = ''; - my $dev = ''; - my $rem = ''; - my @acp = (); - my @afp = (); - - if ($method eq 'reading') { - $dev = ReadingsVal ($name, $obj, ''); - return qq{Reading '$obj' is not set or is empty} if(!$dev); - } - elsif ($method eq 'attr') { - $dev = AttrVal ($name, $obj, ''); - return qq{Attribute '$obj' is not set} if(!$dev); - } - elsif ($method eq 'string') { - return qq{Object '$obj' is empty} if(!$obj); - $dev = $obj; - } - - my ($a, $h) = parseParams ($dev); - $dev = $a->[0]; # das Device aus dem Object - ($dev, $rem) = split '@', $dev; # Meterdev extrahieren - - return if(!$rem); # keine remote Devicekonfiguration - - my ($server, $infix) = split '/', $rem; - my $gc = (split ":", $h->{gcon})[0]; # Readingname für aktuellen Netzbezug - my $gf = (split ":", $h->{gfeedin})[0]; # Readingname für aktuelle Netzeinspeisung - my $gt = (split ":", $h->{contotal})[0]; # Readingname für Bezug total - my $ft = (split ":", $h->{feedtotal})[0]; # Readingname für Einspeisung total - @acp = split ":", $h->{conprice} if($h->{conprice}); - @afp = split ":", $h->{feedprice} if($h->{feedprice}); - - $data{$type}{$name}{current}{x_remote}{$dev}{server} = $server; - $data{$type}{$name}{current}{x_remote}{$dev}{infix} = $infix; - $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$gt} = undef; - $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$ft} = undef; - $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$gc} = undef if($h->{gcon} ne '-gfeedin'); - $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$gf} = undef if($h->{gfeedin} ne '-gcon'); - - if (scalar(@acp) == 2) { - $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$acp[0]} = undef; # conprice wird durch Reading im Meterdev geliefert - } - elsif (scalar(@acp) == 3) { # conprice wird durch weiteres Device / Reading geliefert - $data{$type}{$name}{current}{x_remote}{$acp[0]}{readings}{$acp[1]} = undef; - } - - if (scalar(@afp) == 2) { - $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$afp[0]} = undef; # feedprice wird durch Reading im Meterdev geliefert - } - elsif (scalar(@afp) == 3) { # feedprice wird durch weiteres Device / Reading geliefert - $data{$type}{$name}{current}{x_remote}{$afp[0]}{readings}{$afp[1]} = undef; - } - -return; -} - ################################################################ # Ermittlung der Mondphase ################################################################ @@ -7625,7 +7521,7 @@ sub _getMoonPhase { return; }; - $data{$type}{$name}{current}{moonPhaseI} = $moonphasei; + $data{$name}{current}{moonPhaseI} = $moonphasei; return; } @@ -7642,14 +7538,14 @@ sub _collectAllRegConsumers { return if(CurrentVal ($hash, 'consumerCollected', 0)); # Abbruch wenn Consumer bereits gesammelt - delete $data{$type}{$name}{current}{consumerdevs}; + delete $data{$name}{current}{consumerdevs}; for my $c (1..$maxconsumer) { $c = sprintf "%02d", $c; my ($err, $consumer, $hc) = isDeviceValid ( { name => $name, obj => "consumer${c}", method => 'attr' } ); next if($err); - push @{$data{$type}{$name}{current}{consumerdevs}}, $consumer; # alle Consumerdevices in CurrentHash eintragen + push @{$data{$name}{current}{consumerdevs}}, $consumer; # alle Consumerdevices in CurrentHash eintragen my $dswitch = $hc->{switchdev}; # alternatives Schaltdevice @@ -7657,7 +7553,7 @@ sub _collectAllRegConsumers { my ($err) = isDeviceValid ( { name => $name, obj => $dswitch, method => 'string' } ); next if($err); - push @{$data{$type}{$name}{current}{consumerdevs}}, $dswitch if($dswitch ne $consumer); # Switchdevice zusätzlich in CurrentHash eintragen + push @{$data{$name}{current}{consumerdevs}}, $dswitch if($dswitch ne $consumer); # Switchdevice zusätzlich in CurrentHash eintragen } else { $dswitch = $consumer; @@ -7708,12 +7604,12 @@ sub _collectAllRegConsumers { } my ($dspignorecond,$rigncond,$spignorecondregex); - if(exists $hc->{spignorecond}) { # Bedingung um vorhandenen PV Überschuß zu ignorieren + if (exists $hc->{spignorecond}) { # Bedingung um vorhandenen PV Überschuß zu ignorieren ($dspignorecond,$rigncond,$spignorecondregex) = split ":", $hc->{spignorecond}; } my $interruptable = 0; - my ($hyst); + my $hyst; if (exists $hc->{interruptable} && $hc->{interruptable} ne '0') { $interruptable = $hc->{interruptable}; ($interruptable,$hyst) = $interruptable =~ /(.*):(.*)$/xs if($interruptable ne '1'); @@ -7734,58 +7630,59 @@ sub _collectAllRegConsumers { my $clt; if (exists $hc->{locktime}) { $clt = $hc->{locktime}; - } + } - delete $data{$type}{$name}{consumers}{$c}{sunriseshift}; - delete $data{$type}{$name}{consumers}{$c}{sunsetshift}; - delete $data{$type}{$name}{consumers}{$c}{icon}; + delete $data{$name}{consumers}{$c}{sunriseshift}; + delete $data{$name}{consumers}{$c}{sunsetshift}; + delete $data{$name}{consumers}{$c}{icon}; my $rauto = $hc->{auto} // q{}; my $ctype = $hc->{type} // $defctype; - $data{$type}{$name}{consumers}{$c}{name} = $consumer; # Name des Verbrauchers (Device) - $data{$type}{$name}{consumers}{$c}{alias} = $alias; # Alias des Verbrauchers (Device) - $data{$type}{$name}{consumers}{$c}{type} = $hc->{type} // $defctype; # Typ des Verbrauchers - $data{$type}{$name}{consumers}{$c}{power} = $hc->{power}; # Leistungsaufnahme des Verbrauchers in W - $data{$type}{$name}{consumers}{$c}{avgenergy} = q{}; # Initialwert Energieverbrauch (evtl. Überschreiben in manageConsumerData) - $data{$type}{$name}{consumers}{$c}{mintime} = $hc->{mintime} // $hef{$ctype}{mt}; # Initialwert min. Einplanungsdauer (evtl. Überschreiben in manageConsumerData) - $data{$type}{$name}{consumers}{$c}{mode} = $hc->{mode} // $defcmode; # Planungsmode des Verbrauchers - $data{$type}{$name}{consumers}{$c}{oncom} = $hc->{on} // q{}; # Setter Einschaltkommando - $data{$type}{$name}{consumers}{$c}{offcom} = $hc->{off} // q{}; # Setter Ausschaltkommando - $data{$type}{$name}{consumers}{$c}{dswitch} = $dswitch; # Switchdevice zur Kommandoausführung - $data{$type}{$name}{consumers}{$c}{autoreading} = $rauto; # Readingname zur Automatiksteuerung - $data{$type}{$name}{consumers}{$c}{retotal} = $rtot // q{}; # Reading der Leistungsmessung - $data{$type}{$name}{consumers}{$c}{uetotal} = $utot // q{}; # Unit der Leistungsmessung - $data{$type}{$name}{consumers}{$c}{rpcurr} = $rpcurr // q{}; # Reading der aktuellen Leistungsaufnahme - $data{$type}{$name}{consumers}{$c}{upcurr} = $upcurr // q{}; # Unit der aktuellen Leistungsaufnahme - $data{$type}{$name}{consumers}{$c}{energythreshold} = $ethreshold; # Schwellenwert (Wh pro Stunde) ab der ein Verbraucher als aktiv gewertet wird - $data{$type}{$name}{consumers}{$c}{powerthreshold} = $pthreshold; # Schwellenwert d. aktuellen Leistung(W) ab der ein Verbraucher als aktiv gewertet wird - $data{$type}{$name}{consumers}{$c}{notbefore} = $hc->{notbefore} // q{}; # nicht einschalten vor Stunde in 24h Format (00-23) - $data{$type}{$name}{consumers}{$c}{notafter} = $hc->{notafter} // q{}; # nicht einschalten nach Stunde in 24h Format (00-23) - $data{$type}{$name}{consumers}{$c}{rswstate} = $rswstate // 'state'; # Schaltstatus Reading - $data{$type}{$name}{consumers}{$c}{asynchron} = $asynchron // 0; # Arbeitsweise FHEM Consumer Device - $data{$type}{$name}{consumers}{$c}{noshow} = $noshow // 0; # ausblenden in Grafik - $data{$type}{$name}{consumers}{$c}{exconfc} = $exconfc // 0; # Verbrauch von Erstelleung der Verbrauchsprognose ausschließen - $data{$type}{$name}{consumers}{$c}{locktime} = $clt // '0:0'; # Sperrzeit im Automatikmodus ('offlt:onlt') - $data{$type}{$name}{consumers}{$c}{onreg} = $onreg // 'on'; # Regex für 'ein' - $data{$type}{$name}{consumers}{$c}{offreg} = $offreg // 'off'; # Regex für 'aus' - $data{$type}{$name}{consumers}{$c}{dswoncond} = $dswoncond // q{}; # Device zur Lieferung einer zusätzliche Einschaltbedingung - $data{$type}{$name}{consumers}{$c}{rswoncond} = $rswoncond // q{}; # Reading zur Lieferung einer zusätzliche Einschaltbedingung - $data{$type}{$name}{consumers}{$c}{swoncondregex} = $swoncondregex // q{}; # Regex einer zusätzliche Einschaltbedingung - $data{$type}{$name}{consumers}{$c}{dswoffcond} = $dswoffcond // q{}; # Device zur Lieferung einer vorrangigen Ausschaltbedingung - $data{$type}{$name}{consumers}{$c}{rswoffcond} = $rswoffcond // q{}; # Reading zur Lieferung einer vorrangigen Ausschaltbedingung - $data{$type}{$name}{consumers}{$c}{swoffcondregex} = $swoffcondregex // q{}; # Regex einer vorrangigen Ausschaltbedingung - $data{$type}{$name}{consumers}{$c}{dspignorecond} = $dspignorecond // q{}; # Device liefert Ignore Bedingung - $data{$type}{$name}{consumers}{$c}{rigncond} = $rigncond // q{}; # Reading liefert Ignore Bedingung - $data{$type}{$name}{consumers}{$c}{spignorecondregex} = $spignorecondregex // q{}; # Regex der Ignore Bedingung - $data{$type}{$name}{consumers}{$c}{interruptable} = $interruptable; # Ein-Zustand des Verbrauchers ist unterbrechbar - $data{$type}{$name}{consumers}{$c}{hysteresis} = $hyst // $defhyst; # Hysterese - $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}{consumers}{$c}{icon} = $hc->{icon} if(defined $hc->{icon}); # Icon für den Verbraucher + $data{$name}{consumers}{$c}{name} = $consumer; # Name des Verbrauchers (Device) + $data{$name}{consumers}{$c}{alias} = $alias; # Alias des Verbrauchers (Device) + $data{$name}{consumers}{$c}{type} = $hc->{type} // $defctype; # Typ des Verbrauchers + $data{$name}{consumers}{$c}{power} = $hc->{power}; # Leistungsaufnahme des Verbrauchers in W + $data{$name}{consumers}{$c}{avgenergy} = q{}; # Initialwert Energieverbrauch (evtl. Überschreiben in manageConsumerData) + $data{$name}{consumers}{$c}{mintime} = $hc->{mintime} // $hef{$ctype}{mt}; # Initialwert min. Einplanungsdauer (evtl. Überschreiben in manageConsumerData) + $data{$name}{consumers}{$c}{mode} = $hc->{mode} // $defcmode; # Planungsmode des Verbrauchers + $data{$name}{consumers}{$c}{oncom} = $hc->{on} // q{}; # Setter Einschaltkommando + $data{$name}{consumers}{$c}{offcom} = $hc->{off} // q{}; # Setter Ausschaltkommando + $data{$name}{consumers}{$c}{dswitch} = $dswitch; # Switchdevice zur Kommandoausführung + $data{$name}{consumers}{$c}{autoreading} = $rauto; # Readingname zur Automatiksteuerung + $data{$name}{consumers}{$c}{retotal} = $rtot // q{}; # Reading der Leistungsmessung + $data{$name}{consumers}{$c}{uetotal} = $utot // q{}; # Unit der Leistungsmessung + $data{$name}{consumers}{$c}{rpcurr} = $rpcurr // q{}; # Reading der aktuellen Leistungsaufnahme + $data{$name}{consumers}{$c}{upcurr} = $upcurr // q{}; # Unit der aktuellen Leistungsaufnahme + $data{$name}{consumers}{$c}{energythreshold} = $ethreshold; # Schwellenwert (Wh pro Stunde) ab der ein Verbraucher als aktiv gewertet wird + $data{$name}{consumers}{$c}{powerthreshold} = $pthreshold; # Schwellenwert d. aktuellen Leistung(W) ab der ein Verbraucher als aktiv gewertet wird + $data{$name}{consumers}{$c}{notbefore} = $hc->{notbefore} // q{}; # nicht einschalten vor Stunde in 24h Format (00-23) + $data{$name}{consumers}{$c}{notafter} = $hc->{notafter} // q{}; # nicht einschalten nach Stunde in 24h Format (00-23) + $data{$name}{consumers}{$c}{rswstate} = $rswstate // 'state'; # Schaltstatus Reading + $data{$name}{consumers}{$c}{asynchron} = $asynchron // 0; # Arbeitsweise FHEM Consumer Device + $data{$name}{consumers}{$c}{noshow} = $noshow // 0; # ausblenden in Grafik + $data{$name}{consumers}{$c}{exconfc} = $exconfc // 0; # Verbrauch von Erstelleung der Verbrauchsprognose ausschließen + $data{$name}{consumers}{$c}{surpmeth} = $hc->{surpmeth} // 'default'; # Ermittlungsmethode des PV-Überschusses, default -> direkte Messung + $data{$name}{consumers}{$c}{locktime} = $clt // '0:0'; # Sperrzeit im Automatikmodus ('offlt:onlt') + $data{$name}{consumers}{$c}{onreg} = $onreg // 'on'; # Regex für 'ein' + $data{$name}{consumers}{$c}{offreg} = $offreg // 'off'; # Regex für 'aus' + $data{$name}{consumers}{$c}{dswoncond} = $dswoncond // q{}; # Device zur Lieferung einer zusätzliche Einschaltbedingung + $data{$name}{consumers}{$c}{rswoncond} = $rswoncond // q{}; # Reading zur Lieferung einer zusätzliche Einschaltbedingung + $data{$name}{consumers}{$c}{swoncondregex} = $swoncondregex // q{}; # Regex einer zusätzliche Einschaltbedingung + $data{$name}{consumers}{$c}{dswoffcond} = $dswoffcond // q{}; # Device zur Lieferung einer vorrangigen Ausschaltbedingung + $data{$name}{consumers}{$c}{rswoffcond} = $rswoffcond // q{}; # Reading zur Lieferung einer vorrangigen Ausschaltbedingung + $data{$name}{consumers}{$c}{swoffcondregex} = $swoffcondregex // q{}; # Regex einer vorrangigen Ausschaltbedingung + $data{$name}{consumers}{$c}{dspignorecond} = $dspignorecond // q{}; # Device liefert Ignore Bedingung + $data{$name}{consumers}{$c}{rigncond} = $rigncond // q{}; # Reading liefert Ignore Bedingung + $data{$name}{consumers}{$c}{spignorecondregex} = $spignorecondregex // q{}; # Regex der Ignore Bedingung + $data{$name}{consumers}{$c}{interruptable} = $interruptable; # Ein-Zustand des Verbrauchers ist unterbrechbar + $data{$name}{consumers}{$c}{hysteresis} = $hyst // $defhyst; # Hysterese + $data{$name}{consumers}{$c}{sunriseshift} = $riseshift if(defined $riseshift); # Verschiebung (Sekunden) Sonnenaufgang bei SunPath Verwendung + $data{$name}{consumers}{$c}{sunsetshift} = $setshift if(defined $setshift); # Verschiebung (Sekunden) Sonnenuntergang bei SunPath Verwendung + $data{$name}{consumers}{$c}{icon} = $hc->{icon} if(defined $hc->{icon}); # Icon für den Verbraucher } - $data{$type}{$name}{current}{consumerCollected} = 1; + $data{$name}{current}{consumerCollected} = 1; Log3 ($name, 3, "$name - all registered consumers collected"); @@ -7822,7 +7719,7 @@ sub _specialActivities { ## Planungsdaten spezifisch löschen (Anfang und Ende nicht am selben Tag) ########################################################################## - for my $c (keys %{$data{$type}{$name}{consumers}}) { + for my $c (keys %{$data{$name}{consumers}}) { next if(ConsumerVal ($hash, $c, "plandelete", "regular") eq "regular"); my $planswitchoff = ConsumerVal ($hash, $c, "planswitchoff", $t); @@ -7873,22 +7770,21 @@ sub _specialActivities { } my ($rapi, $wapi) = getStatusApiName ($hash); - delete $data{$type}{$name}{statusapi}{$rapi}{'?All'} if($rapi); + delete $data{$name}{statusapi}{$rapi}{'?All'} if($rapi); - delete $data{$type}{$name}{circular}{99}{initdayfeedin}; - delete $data{$type}{$name}{circular}{99}{initdaygcon}; - delete $data{$type}{$name}{circular}{99}{initdaybatintot}; - delete $data{$type}{$name}{circular}{99}{initdaybatouttot}; - delete $data{$type}{$name}{current}{sunriseToday}; - delete $data{$type}{$name}{current}{sunriseTodayTs}; - delete $data{$type}{$name}{current}{sunsetToday}; - delete $data{$type}{$name}{current}{sunsetTodayTs}; + delete $data{$name}{circular}{99}{initdayfeedin}; + delete $data{$name}{circular}{99}{initdaygcon}; + delete $data{$name}{circular}{99}{initdaybatintot}; + delete $data{$name}{circular}{99}{initdaybatouttot}; + delete $data{$name}{current}{sunriseToday}; + delete $data{$name}{current}{sunriseTodayTs}; + delete $data{$name}{current}{sunsetToday}; + delete $data{$name}{current}{sunsetTodayTs}; - $data{$type}{$name}{circular}{99}{ydayDvtn} = CircularVal ($hash, 99, 'tdayDvtn', '-'); - delete $data{$type}{$name}{circular}{99}{tdayDvtn}; + $data{$name}{circular}{99}{ydayDvtn} = CircularVal ($hash, 99, 'tdayDvtn', '-'); + delete $data{$name}{circular}{99}{tdayDvtn}; - delHashRefDeep ($data{$type}{$name}{pvhist}{$day}); - delete $data{$type}{$name}{pvhist}{$day}; # den (alten) aktuellen Tag aus History löschen + delete $data{$name}{pvhist}{$day}; # den (alten) aktuellen Tag aus History löschen writeCacheToFile ($hash, 'plantconfig', $plantcfg.$name); # Anlagenkonfiguration sichern @@ -7908,7 +7804,7 @@ sub _specialActivities { Log3 ($name, 4, "$name - Daily special tasks - Task 2 started"); - for my $c (keys %{$data{$type}{$name}{consumers}}) { # Planungsdaten regulär löschen + for my $c (keys %{$data{$name}{consumers}}) { # Planungsdaten regulär löschen next if(ConsumerVal ($hash, $c, "plandelete", "regular") ne "regular"); deleteConsumerPlanning ($hash, $c); } @@ -8021,16 +7917,23 @@ sub __createAdditionalEvents { my $name = $paref->{name}; my $type = $paref->{type}; my $hash = $defs{$name}; + + my $done = 0; - for my $idx (sort keys %{$data{$type}{$name}{nexthours}}) { + for my $idx (sort keys %{$data{$name}{nexthours}}) { my $nhts = NexthoursVal ($hash, $idx, 'starttime', undef); my $nhfc = NexthoursVal ($hash, $idx, 'pvfc', undef); next if(!defined $nhts || !defined $nhfc); + $done = 1; my ($dt, $h) = $nhts =~ /([\w-]+)\s(\d{2})/xs; storeReading ('AllPVforecastsToEvent', "$nhfc Wh", $dt." ".$h.":59:59"); } - + + if (!$done) { + Log3 ($name, 2, "$name - WARNING - Events of 'AllPVforecastsToEvent' were not created due to no data in 'nexthours'"); + } + return; } @@ -8048,42 +7951,41 @@ sub __delObsoleteAPIData { ## Wetter-API Daten löschen ############################# - if (keys %{$data{$type}{$name}{weatherapi}}) { + if (keys %{$data{$name}{weatherapi}}) { if ($wapi ne 'OpenMeteo') { - delHashRefDeep ($data{$type}{$name}{weatherapi}{OpenMeteo}); - delete $data{$type}{$name}{weatherapi}{OpenMeteo}; + delete $data{$name}{weatherapi}{OpenMeteo}; } } ## Status-API Daten löschen ############################# - if (keys %{$data{$type}{$name}{statusapi}}) { - delete $data{$type}{$name}{statusapi}{OpenMeteo} if($rapi ne 'OpenMeteo' && $wapi ne 'OpenMeteo'); - delete $data{$type}{$name}{statusapi}{ForecastSolar} if($rapi ne 'ForecastSolar'); - delete $data{$type}{$name}{statusapi}{SolCast} if($rapi ne 'SolCast'); - delete $data{$type}{$name}{statusapi}{'?IdPair'} if($rapi ne 'SolCast'); - delete $data{$type}{$name}{statusapi}{DWD} if($rapi ne 'DWD'); - delete $data{$type}{$name}{statusapi}{VictronKi} if($rapi ne 'VictronKi'); - delete $data{$type}{$name}{statusapi}{'?VRM'} if($rapi ne 'VictronKi'); + if (keys %{$data{$name}{statusapi}}) { + delete $data{$name}{statusapi}{OpenMeteo} if($rapi ne 'OpenMeteo' && $wapi ne 'OpenMeteo'); + delete $data{$name}{statusapi}{ForecastSolar} if($rapi ne 'ForecastSolar'); + delete $data{$name}{statusapi}{SolCast} if($rapi ne 'SolCast'); + delete $data{$name}{statusapi}{'?IdPair'} if($rapi ne 'SolCast'); + delete $data{$name}{statusapi}{DWD} if($rapi ne 'DWD'); + delete $data{$name}{statusapi}{VictronKi} if($rapi ne 'VictronKi'); + delete $data{$name}{statusapi}{'?VRM'} if($rapi ne 'VictronKi'); } ## Solar-API Daten löschen ############################# - if (keys %{$data{$type}{$name}{solcastapi}}) { + if (keys %{$data{$name}{solcastapi}}) { my $refts = timestringToTimestamp ($date.' 00:00:00'); # Referenztimestring - for my $idx (sort keys %{$data{$type}{$name}{solcastapi}}) { # alle Datumschlüssel kleiner aktueller Tag 00:00:00 selektieren - for my $scd (sort keys %{$data{$type}{$name}{solcastapi}{$idx}}) { + for my $idx (sort keys %{$data{$name}{solcastapi}}) { # alle Datumschlüssel kleiner aktueller Tag 00:00:00 selektieren + for my $scd (sort keys %{$data{$name}{solcastapi}{$idx}}) { my $ds = timestringToTimestamp ($scd); - delete $data{$type}{$name}{solcastapi}{$idx}{$scd} if($ds && $ds < $refts); + delete $data{$name}{solcastapi}{$idx}{$scd} if($ds && $ds < $refts); } } ### 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); + for my $idx (keys %{$data{$name}{solcastapi}{'?All'}}) { # Wetterindexe löschen (kann später raus) + delete $data{$name}{solcastapi}{'?All'}{$idx} if($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs); } ##################################################################################################################### } @@ -8092,11 +7994,11 @@ sub __delObsoleteAPIData { ################################################ my @as = split ",", AttrVal ($name, 'setupInverterStrings', ''); if (scalar @as) { - for my $k (keys %{$data{$type}{$name}{strings}}) { + for my $k (keys %{$data{$name}{strings}}) { next if($k =~ /\?All/); next if(grep /^$k$/, @as); - delete $data{$type}{$name}{strings}{$k}; + delete $data{$name}{strings}{$k}; Log3 ($name, 2, "$name - obsolete PV-String >$k< was deleted from Strings-Hash"); } @@ -8115,13 +8017,12 @@ sub _transferWeatherValues { my $chour = $paref->{chour}; my $hash = $defs{$name}; - my ($valid, $fcname, $apiu) = isWeatherDevValid ($hash, 'setupWeatherDev1'); # Standard Weather Forecast Device + my ($valid, $fcname, $apiu) = isWeatherDevValid ($hash, 'setupWeatherDev1'); # Standard Weather Forecast Device return if(!$valid); my $type = $paref->{type}; - delHashRefDeep ($data{$type}{$name}{weatherdata}); - delete $data{$type}{$name}{weatherdata}; # Wetterdaten Hash löschen + delete $data{$name}{weatherdata}; # Wetterdaten Hash löschen $paref->{apiu} = $apiu; # API wird verwendet $paref->{fcname} = $fcname; @@ -8141,8 +8042,8 @@ sub _transferWeatherValues { $fctimets = StatusAPIVal ($hash, $wapi, '?All', 'lastretrieval_timestamp', '-'); } - $data{$type}{$name}{current}{dwdWfchAge} = $fctime; - $data{$type}{$name}{current}{dwdWfchAgeTS} = $fctimets; + $data{$name}{current}{dwdWfchAge} = $fctime; + $data{$name}{current}{dwdWfchAgeTS} = $fctimets; for my $step (1..$weatherDevMax) { $paref->{step} = $step; @@ -8156,32 +8057,32 @@ sub _transferWeatherValues { my ($fd, $fh) = calcDayHourMove ($chour, $num); last if($fd > 1); - my $wid = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{ww}; # signifikantes Wetter = Wetter ID - my $wwd = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{wwd}; # Wetter Beschreibung - my $wcc = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{neff}; # Effektive Wolkendecke - my $rr1c = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{rr1c}; # Gesamtniederschlag (1-stündig) letzte 1 Stunde - my $temp = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{ttt}; # Außentemperatur - my $don = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{don}; # Tag/Nacht-Grenze + my $wid = $data{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{ww}; # signifikantes Wetter = Wetter ID + my $wwd = $data{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{wwd}; # Wetter Beschreibung + my $wcc = $data{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{neff}; # Effektive Wolkendecke + my $rr1c = $data{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{rr1c}; # Gesamtniederschlag (1-stündig) letzte 1 Stunde + my $temp = $data{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{ttt}; # Außentemperatur + my $don = $data{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{don}; # Tag/Nacht-Grenze - my $nhtstr = "NextHour".sprintf "%02d", $num; - $data{$type}{$name}{nexthours}{$nhtstr}{weatherid} = $wid; - $data{$type}{$name}{nexthours}{$nhtstr}{wcc} = $wcc; - $data{$type}{$name}{nexthours}{$nhtstr}{rr1c} = $rr1c; - $data{$type}{$name}{nexthours}{$nhtstr}{rainrange} = $rr1c; - $data{$type}{$name}{nexthours}{$nhtstr}{temp} = $temp; - $data{$type}{$name}{nexthours}{$nhtstr}{DoN} = $don; + my $nhtstr = "NextHour".sprintf "%02d", $num; + $data{$name}{nexthours}{$nhtstr}{weatherid} = $wid; + $data{$name}{nexthours}{$nhtstr}{wcc} = $wcc; + $data{$name}{nexthours}{$nhtstr}{rr1c} = $rr1c; + $data{$name}{nexthours}{$nhtstr}{rainrange} = $rr1c; + $data{$name}{nexthours}{$nhtstr}{temp} = $temp; + $data{$name}{nexthours}{$nhtstr}{DoN} = $don; my $fh1 = $fh + 1; # = hod if ($num < 23 && $fh < 24) { # Ringspeicher Weather Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 - $data{$type}{$name}{circular}{sprintf("%02d",$fh1)}{weatherid} = $wid; - $data{$type}{$name}{circular}{sprintf("%02d",$fh1)}{weathertxt} = $wwd; - $data{$type}{$name}{circular}{sprintf("%02d",$fh1)}{wcc} = $wcc; - $data{$type}{$name}{circular}{sprintf("%02d",$fh1)}{rr1c} = $rr1c; - $data{$type}{$name}{circular}{sprintf("%02d",$fh1)}{temp} = $temp; + $data{$name}{circular}{sprintf("%02d",$fh1)}{weatherid} = $wid; + $data{$name}{circular}{sprintf("%02d",$fh1)}{weathertxt} = $wwd; + $data{$name}{circular}{sprintf("%02d",$fh1)}{wcc} = $wcc; + $data{$name}{circular}{sprintf("%02d",$fh1)}{rr1c} = $rr1c; + $data{$name}{circular}{sprintf("%02d",$fh1)}{temp} = $temp; if ($num == 0) { # aktuelle Außentemperatur - $data{$type}{$name}{current}{temp} = $temp; + $data{$name}{current}{temp} = $temp; } } @@ -8252,12 +8153,12 @@ sub __readDataWeather { debugLog ($paref, 'collectData', "Weather $step: fc${fd}_${fh}, don: $sunup, ww: ".(defined $wid ? $wid : '').", RR1c: $rr1c, TTT: $temp, Neff: $neff"); - $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{ww} = $wid; - $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{wwd} = $wwd; - $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{neff} = $neff; - $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{rr1c} = $rr1c; - $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{ttt} = $temp; - $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{don} = $sunup; + $data{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{ww} = $wid; + $data{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{wwd} = $wwd; + $data{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{neff} = $neff; + $data{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{rr1c} = $rr1c; + $data{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{ttt} = $temp; + $data{$name}{weatherdata}{"fc${fd}_${fh}"}{$step}{don} = $sunup; } return; @@ -8278,7 +8179,7 @@ sub ___readDataWeatherAPI { my ($rapi, $wapi) = getStatusApiName ($hash); - for my $idx (sort keys %{$data{$type}{$name}{weatherapi}{$wapi}}) { + for my $idx (sort keys %{$data{$name}{weatherapi}{$wapi}}) { if ($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs) { # valider Weather API Index my $rr1c = WeatherAPIVal ($hash, $wapi, $idx, 'rr1c', undef); my $wid = WeatherAPIVal ($hash, $wapi, $idx, 'ww', undef); @@ -8286,11 +8187,11 @@ sub ___readDataWeatherAPI { my $don = WeatherAPIVal ($hash, $wapi, $idx, 'don', undef); my $ttt = WeatherAPIVal ($hash, $wapi, $idx, 'ttt', undef); - $data{$type}{$name}{weatherdata}{$idx}{$step}{ww} = $wid if(defined $wid); - $data{$type}{$name}{weatherdata}{$idx}{$step}{neff} = $neff if(defined $neff); - $data{$type}{$name}{weatherdata}{$idx}{$step}{rr1c} = $rr1c if(defined $rr1c); - $data{$type}{$name}{weatherdata}{$idx}{$step}{ttt} = $ttt if(defined $ttt); - $data{$type}{$name}{weatherdata}{$idx}{$step}{don} = $don if(defined $don); + $data{$name}{weatherdata}{$idx}{$step}{ww} = $wid if(defined $wid); + $data{$name}{weatherdata}{$idx}{$step}{neff} = $neff if(defined $neff); + $data{$name}{weatherdata}{$idx}{$step}{rr1c} = $rr1c if(defined $rr1c); + $data{$name}{weatherdata}{$idx}{$step}{ttt} = $ttt if(defined $ttt); + $data{$name}{weatherdata}{$idx}{$step}{don} = $don if(defined $don); debugLog ($paref, 'collectData', "Weather $step: $idx". ", don: ". (defined $don ? $don : ''). @@ -8325,22 +8226,22 @@ sub __mergeDataWeather { my ($q, $m) = (0,0); - for my $key (sort keys %{$data{$type}{$name}{weatherdata}}) { + for my $key (sort keys %{$data{$name}{weatherdata}}) { my ($z, $neff, $rr1c, $temp) = (0,0,0,0); - $data{$type}{$name}{weatherdata}{$key}{merge}{don} = $data{$type}{$name}{weatherdata}{$key}{1}{don}; - $data{$type}{$name}{weatherdata}{$key}{merge}{ww} = $data{$type}{$name}{weatherdata}{$key}{1}{ww}; - $data{$type}{$name}{weatherdata}{$key}{merge}{wwd} = $data{$type}{$name}{weatherdata}{$key}{1}{wwd}; - $data{$type}{$name}{weatherdata}{$key}{merge}{neff} = $data{$type}{$name}{weatherdata}{$key}{1}{neff}; - $data{$type}{$name}{weatherdata}{$key}{merge}{rr1c} = $data{$type}{$name}{weatherdata}{$key}{1}{rr1c}; - $data{$type}{$name}{weatherdata}{$key}{merge}{ttt} = $data{$type}{$name}{weatherdata}{$key}{1}{ttt}; + $data{$name}{weatherdata}{$key}{merge}{don} = $data{$name}{weatherdata}{$key}{1}{don}; + $data{$name}{weatherdata}{$key}{merge}{ww} = $data{$name}{weatherdata}{$key}{1}{ww}; + $data{$name}{weatherdata}{$key}{merge}{wwd} = $data{$name}{weatherdata}{$key}{1}{wwd}; + $data{$name}{weatherdata}{$key}{merge}{neff} = $data{$name}{weatherdata}{$key}{1}{neff}; + $data{$name}{weatherdata}{$key}{merge}{rr1c} = $data{$name}{weatherdata}{$key}{1}{rr1c}; + $data{$name}{weatherdata}{$key}{merge}{ttt} = $data{$name}{weatherdata}{$key}{1}{ttt}; for my $step (1..$ds) { $q++; - my $n = $data{$type}{$name}{weatherdata}{$key}{$step}{neff}; - my $r = $data{$type}{$name}{weatherdata}{$key}{$step}{rr1c}; - my $t = $data{$type}{$name}{weatherdata}{$key}{$step}{ttt}; + my $n = $data{$name}{weatherdata}{$key}{$step}{neff}; + my $r = $data{$name}{weatherdata}{$key}{$step}{rr1c}; + my $t = $data{$name}{weatherdata}{$key}{$step}{ttt}; next if(!isNumeric ($n) || !isNumeric ($r) || !isNumeric ($t)); @@ -8353,16 +8254,16 @@ sub __mergeDataWeather { next if(!$z); - $data{$type}{$name}{weatherdata}{$key}{merge}{neff} = sprintf "%.0f", ($neff / $z); - $data{$type}{$name}{weatherdata}{$key}{merge}{rr1c} = sprintf "%.2f", ($rr1c / $z); - $data{$type}{$name}{weatherdata}{$key}{merge}{ttt} = sprintf "%.2f", ($temp / $z); + $data{$name}{weatherdata}{$key}{merge}{neff} = sprintf "%.0f", ($neff / $z); + $data{$name}{weatherdata}{$key}{merge}{rr1c} = sprintf "%.2f", ($rr1c / $z); + $data{$name}{weatherdata}{$key}{merge}{ttt} = sprintf "%.2f", ($temp / $z); debugLog ($paref, 'collectData', "Weather merged: $key, ". - "don: $data{$type}{$name}{weatherdata}{$key}{merge}{don}, ". - "ww: ".(defined $data{$type}{$name}{weatherdata}{$key}{1}{ww} ? $data{$type}{$name}{weatherdata}{$key}{1}{ww} : '').", ". - "RR1c: $data{$type}{$name}{weatherdata}{$key}{merge}{rr1c}, ". - "TTT: $data{$type}{$name}{weatherdata}{$key}{merge}{ttt}, ". - "Neff: $data{$type}{$name}{weatherdata}{$key}{merge}{neff}"); + "don: $data{$name}{weatherdata}{$key}{merge}{don}, ". + "ww: ".(defined $data{$name}{weatherdata}{$key}{1}{ww} ? $data{$name}{weatherdata}{$key}{1}{ww} : '').", ". + "RR1c: $data{$name}{weatherdata}{$key}{merge}{rr1c}, ". + "TTT: $data{$name}{weatherdata}{$key}{merge}{ttt}, ". + "Neff: $data{$name}{weatherdata}{$key}{merge}{neff}"); } debugLog ($paref, 'collectData', "Number of Weather datasets mergers - delivered: $q, merged: $m, failures: ".($q - $m)); @@ -8415,11 +8316,11 @@ sub __sunRS { } } - $data{$type}{$name}{current}{sunriseToday} = $date.' '.$fc0_sr.':00'; - $data{$type}{$name}{current}{sunriseTodayTs} = timestringToTimestamp ($date.' '.$fc0_sr.':00'); + $data{$name}{current}{sunriseToday} = $date.' '.$fc0_sr.':00'; + $data{$name}{current}{sunriseTodayTs} = timestringToTimestamp ($date.' '.$fc0_sr.':00'); - $data{$type}{$name}{current}{sunsetToday} = $date.' '.$fc0_ss.':00'; - $data{$type}{$name}{current}{sunsetTodayTs} = timestringToTimestamp ($date.' '.$fc0_ss.':00'); + $data{$name}{current}{sunsetToday} = $date.' '.$fc0_ss.':00'; + $data{$name}{current}{sunsetTodayTs} = timestringToTimestamp ($date.' '.$fc0_ss.':00'); debugLog ($paref, 'collectData', "sunrise/sunset today: $fc0_sr / $fc0_ss, sunrise/sunset tomorrow: $fc1_sr / $fc1_ss"); @@ -8436,456 +8337,6 @@ sub __sunRS { return ($fc0_sr_mm, $fc0_ss_mm, $fc1_sr_mm, $fc1_ss_mm); } -################################################################ -# Strahlungsvorhersage Werte aus solcastapi-Hash -# übertragen und PV Vorhersage berechnen / in Nexthours -# speichern -################################################################ -sub _transferAPIRadiationValues { - my $paref = shift; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $t = $paref->{t}; # Epoche Zeit - my $chour = $paref->{chour}; - my $date = $paref->{date}; - my $hash = $defs{$name}; - - return if(!keys %{$data{$type}{$name}{solcastapi}}); - - my @strings = sort keys %{$data{$type}{$name}{strings}}; - return if(!@strings); - - my $lang = $paref->{lang}; - - for my $num (0..47) { - my ($fd,$fh) = calcDayHourMove ($chour, $num); - - if ($fd > 1) { # überhängende Werte löschen - delete $data{$type}{$name}{nexthours}{"NextHour".sprintf "%02d", $num}; - next; - } - - my $fh1 = $fh + 1; - my $wantts = (timestringToTimestamp ($date.' '.$chour.':00:00')) + ($num * 3600); - my $wantdt = (timestampToTimestring ($wantts, $lang))[1]; - my $nhtstr = 'NextHour'.sprintf "%02d", $num; - my ($wtday, $wthour) = $wantdt =~ /(\d{2})\s(\d{2}):/xs; - my $hod = sprintf "%02d", int $wthour + 1; # Stunde des Tages - my $rad1h = RadiationAPIVal ($hash, '?All', $wantdt, 'Rad1h', undef); - - $paref->{wantdt} = $wantdt; - $paref->{wantts} = $wantts; - $paref->{wtday} = $wtday; - $paref->{hod} = $hod; - $paref->{nhtstr} = $nhtstr; - $paref->{num} = $num; - $paref->{fh1} = $fh1; - $paref->{fd} = $fd; - - $data{$type}{$name}{nexthours}{$nhtstr}{starttime} = $wantdt; - $data{$type}{$name}{nexthours}{$nhtstr}{hourofday} = $hod; - $data{$type}{$name}{nexthours}{$nhtstr}{today} = $fd == 0 ? 1 : 0; - $data{$type}{$name}{nexthours}{$nhtstr}{rad1h} = $rad1h; - - my $sunalt = HistoryVal ($hash, $wtday, $hod, 'sunalt', undef); - my $sunaz = HistoryVal ($hash, $wtday, $hod, 'sunaz', undef); - - if (!defined $sunalt || !defined $sunaz) { - __calcSunPosition ($paref); - $sunalt = HistoryVal ($hash, $wtday, $hod, 'sunalt', undef); - $sunaz = HistoryVal ($hash, $wtday, $hod, 'sunaz', undef); - } - - if (defined $sunaz) { - $data{$type}{$name}{nexthours}{$nhtstr}{sunaz} = $sunaz; - } - else { - $sunaz = NexthoursVal ($hash, $nhtstr, 'sunaz', 0); - } - - if (defined $sunalt) { - $data{$type}{$name}{nexthours}{$nhtstr}{sunalt} = $sunalt; - } - else { - $sunalt = NexthoursVal ($hash, $nhtstr, 'sunalt', 0); - } - - $paref->{sabin} = sunalt2bin ($sunalt); - my $est = __calcPVestimates ($paref); - my ($msg, $pvaifc) = aiGetResult ($paref); # KI Entscheidungen abfragen - - $data{$type}{$name}{nexthours}{$nhtstr}{pvapifc} = $est; # durch API gelieferte PV Forecast - - delete $paref->{fd}; - delete $paref->{fh1}; - delete $paref->{num}; - delete $paref->{nhtstr}; - delete $paref->{hod}; - delete $paref->{wtday}; - delete $paref->{wantdt}; - delete $paref->{wantts}; - delete $paref->{sabin}; - - my $useai = 0; - my $pvfc; - - if ($msg eq 'accurate' || $msg eq 'spreaded') { - my $airn = CircularVal ($hash, 99, 'aiRulesNumber', 0); - my $aivar = 100; - $aivar = sprintf "%.0f", (100 * $pvaifc / $est) if($est); # Übereinstimmungsgrad KI Forecast zu API Forecast in % - - if ($msg eq 'accurate') { # KI liefert 'accurate' Treffer -> verwenden - if ($airn >= $aiAccTRNMin || ($aivar >= $aiAccLowLim && $aivar <= $aiAccUpLim)) { - $data{$type}{$name}{nexthours}{$nhtstr}{aihit} = 1; - $pvfc = $pvaifc; - $useai = 1; - - debugLog ($paref, 'aiData', qq{AI Hit - accurate result used -> aiRulesNum: $airn, variance: $aivar, hod: $hod, Rad1h: $rad1h, pvfc: $pvfc Wh}); - } - } - elsif ($msg eq 'spreaded') { # Abweichung AI von Standardvorhersage begrenzen - if ($airn >= $aiSpreadTRNMin || ($aivar >= $aiSpreadLowLim && $aivar <= $aiSpreadUpLim)) { - $data{$type}{$name}{nexthours}{$nhtstr}{aihit} = 1; - $pvfc = $pvaifc; - $useai = 1; - - debugLog ($paref, 'aiData', qq{AI Hit - spreaded result used -> aiRulesNum: $airn, hod: $hod, Rad1h: $rad1h, pvfc: $pvfc Wh}); - } - } - } - else { - debugLog ($paref, 'aiData', $msg); - } - - if ($useai) { - $data{$type}{$name}{nexthours}{$nhtstr}{pvaifc} = $pvaifc; # durch AI gelieferte PV Forecast - } - else { - delete $data{$type}{$name}{nexthours}{$nhtstr}{pvaifc}; - $data{$type}{$name}{nexthours}{$nhtstr}{aihit} = 0; - $pvfc = $est; - - debugLog ($paref, 'aiData', "use PV from API (no AI or AI result tolerance overflow) -> hod: $hod, Rad1h: ".(defined $rad1h ? $rad1h : '-').", pvfc: $pvfc Wh"); - } - - $data{$type}{$name}{nexthours}{$nhtstr}{pvfc} = $pvfc; # resultierende PV Forecast zuweisen - - if ($num < 23 && $fh < 24) { # Ringspeicher PV forecast Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 - $data{$type}{$name}{circular}{sprintf "%02d",$fh1}{pvapifc} = NexthoursVal ($hash, $nhtstr, 'pvapifc', undef); - $data{$type}{$name}{circular}{sprintf "%02d",$fh1}{pvfc} = $pvfc; - $data{$type}{$name}{circular}{sprintf "%02d",$fh1}{pvaifc} = NexthoursVal ($hash, $nhtstr, 'pvaifc', undef); - $data{$type}{$name}{circular}{sprintf "%02d",$fh1}{aihit} = NexthoursVal ($hash, $nhtstr, 'aihit', 0); - } - - if ($fd == 0 && int $pvfc > 0) { # Vorhersagedaten des aktuellen Tages zum manuellen Vergleich in Reading speichern - storeReading ('Today_Hour'.sprintf ("%02d",$fh1).'_PVforecast', "$pvfc Wh"); - } - - if ($fd == 0 && $fh1) { - writeToHistory ( { paref => $paref, key => 'pvfc', val => $pvfc, hour => $fh1 } ); - writeToHistory ( { paref => $paref, key => 'radiation', val => $rad1h, hour => $fh1 } ); - } - } - - storeReading ('.lastupdateForecastValues', $t); # Statusreading letzter update - -return; -} - -################################################################ -# Ermittlung der Sonnenpositionen -# Az,Alt = Azimuth und Höhe (in Dezimalgrad) des Körpers -# über dem Horizont -################################################################ -sub __calcSunPosition { - my $paref = shift; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $t = $paref->{t}; # Epoche Zeit - my $chour = $paref->{chour}; - my $wtday = $paref->{wtday}; - my $num = $paref->{num}; - my $nhtstr = $paref->{nhtstr}; - my $hash = $defs{$name}; - - my ($fd, $fh) = calcDayHourMove ($chour, $num); - last if($fd > 1); - - my $tstr = (timestampToTimestring ($t + ($num * 3600)))[3]; - my ($date, $h, $m, $s) = split /[ :]/, $tstr; - $tstr = $date.' '.$h.':30:00'; - - my ($az, $alt); - - eval { - $az = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAz', $tstr); # statt Astro_Get geht auch FHEM::Astro::Get - $alt = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAlt', $tstr); - }; - - if ($@) { - Log3 ($name, 1, "$name - ERROR - $@"); - return; - } - - my $hodn = sprintf "%02d", $h + 1; - - $data{$type}{$name}{nexthours}{$nhtstr}{sunaz} = $az; - $data{$type}{$name}{nexthours}{$nhtstr}{sunalt} = $alt; - - debugLog ($paref, 'collectData', "Sun position: day: $wtday, hod: $hodn, $tstr, azimuth: $az, altitude: $alt"); - - if ($fd == 0 && $hodn) { # Sun Position in pvHistory speichern - writeToHistory ( { paref => $paref, key => 'sunaz', val => $az, hour => $hodn } ); - writeToHistory ( { paref => $paref, key => 'sunalt', val => $alt, hour => $hodn } ); - } - -return; -} - -######################################################################### -# API Erzeugungsprognose mit gewählter Autokorrekturmethode anpassen -######################################################################### -sub __calcPVestimates { - my $paref = shift; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $wantdt = $paref->{wantdt}; - my $hod = $paref->{hod}; - my $fd = $paref->{fd}; - my $num = $paref->{num}; - my $debug = $paref->{debug}; - - my $hash = $defs{$name}; - my $reld = $fd == 0 ? "today" : $fd == 1 ? "tomorrow" : "unknown"; - my $rr1c = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "rr1c", 0); # Gesamtniederschlag während der letzten Stunde kg/m2 - my $wcc = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "wcc", 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->{wcc} = $wcc; - my ($hc, $hq) = ___readCandQ ($paref); # liest den anzuwendenden Korrekturfaktor - delete $paref->{wcc}; - - my ($lh,$sq,$peakloss, $modtemp); - my $pvsum = 0; - my $peaksum = 0; - - for my $string (sort keys %{$data{$type}{$name}{strings}}) { - my $peak = StringVal ($hash, $string, 'peak', 0); # String Peak (kWp) - - if ($acu =~ /on_complex/xs) { - $paref->{peak} = $peak; - $paref->{wcc} = $wcc; - $paref->{temp} = $temp; - - ($peakloss, $modtemp) = ___calcPeaklossByTemp ($paref); # Reduktion Peakleistung durch Temperaturkoeffizienten der Module (vorzeichengehaftet) - $peak += $peakloss; - - delete $paref->{peak}; - delete $paref->{wcc}; - delete $paref->{temp}; - } - - $peak *= 1000; - my $est = RadiationAPIVal ($hash, $string, $wantdt, 'pv_estimate50', 0); - my $pv = sprintf "%.1f", ($est * $hc); # Korrekturfaktor anwenden - - my $invcap = 0; - - for my $in (keys %{$data{$type}{$name}{inverters}}) { - my $istrings = InverterVal ($hash, $in, 'istrings', ''); # dem Inverter zugeordnete Strings - next if(!grep /^$string$/, (split ',', $istrings)); - - $invcap = InverterVal ($hash, $in, 'invertercap', 0); # Max. Leistung des Inverters - - last; - } - - if ($invcap && $pv > $invcap) { - $pv = $invcap; # PV Vorhersage auf WR Kapazität begrenzen - - debugLog ($paref, "radiationProcess", "PV forecast start time $wantdt limited to $pv Wh due to inverter capacity"); - } - - if ($debug =~ /radiationProcess/xs) { - $lh = { # Log-Hash zur Ausgabe - "String Peak" => $peak. " W", - "Estimated PV generation (raw)" => $est. " Wh", - "Estimated PV generation (calc)" => $pv. " Wh", - "PV correction factor" => $hc, - "PV correction quality" => $hq, - }; - - if ($acu =~ /on_complex/xs) { - $lh->{"Module Temp (calculated)"} = $modtemp. " °C"; - $lh->{"Win(+)/Loss(-) String Peak Power by Temp"} = $peakloss." kWp"; - } - - $sq = q{}; - for my $idx (sort keys %{$lh}) { - $sq .= $idx." => ".$lh->{$idx}."\n"; - } - - Log3 ($name, 1, "$name DEBUG> PV API estimate for $reld Hour ".sprintf ("%02d", $hod)." string $string ->\n$sq"); - } - - $pvsum += $pv; - $peaksum += $peak; - } - - $data{$type}{$name}{current}{allstringspeak} = $peaksum; # temperaturbedingte Korrektur der installierten Peakleistung in W - $pvsum = $peaksum if($peaksum && $pvsum > $peaksum); # Vorhersage nicht größer als die Summe aller PV-Strings Peak - $pvsum = sprintf "%.0f", $pvsum; - - if ($debug =~ /radiationProcess/xs) { - $lh = { # Log-Hash zur Ausgabe - "Starttime" => $wantdt, - "Forecasted temperature" => $temp." °C", - "Cloudcover" => $wcc, - "Total Rain last hour" => $rr1c." kg/m2", - "PV Correction mode" => ($acu ? $acu : 'no'), - "PV generation forecast" => $pvsum." Wh", - }; - - $sq = q{}; - for my $idx (sort keys %{$lh}) { - $sq .= $idx." => ".$lh->{$idx}."\n"; - } - - Log3 ($name, 1, "$name DEBUG> PV API estimate for $reld Hour ".sprintf ("%02d", $hod)." summary: \n$sq"); - } - -return $pvsum; -} - -###################################################################### -# Complex: -# Liest bewölkungsabhängige Korrekturfaktor/Qualität aus pvCircular -# und speichert die Werte im Nexthours / pvHistory Hash -# -# Simple: -# Liest Korrekturfaktor/Qualität aus pvCircular simple und -# speichert die Werte im Nexthours / pvHistory Hash -###################################################################### -sub ___readCandQ { - my $paref = shift; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $num = $paref->{num}; - my $fh1 = $paref->{fh1}; - my $fd = $paref->{fd}; - my $wcc = $paref->{wcc}; - my $sabin = $paref->{sabin}; - my $hash = $defs{$name}; - - my ($acu, $aln) = isAutoCorrUsed ($name); # Autokorrekturmodus - my $sunalt = NexthoursVal ($hash, "NextHour".sprintf("%02d",$num), 'sunalt', undef); # Sun Altitude - my $hcraw = ReadingsNum ($name, 'pvCorrectionFactor_'.sprintf("%02d",$fh1), 1.00); # Voreinstellung RAW-Korrekturfaktor (evtl. manuell gesetzt) - my $cpcf = ReadingsVal ($name, 'pvCorrectionFactor_'.sprintf("%02d",$fh1), ''); # aktuelles pcf-Reading - my $hq = '-'; # keine Qualität definiert - my $crang = 'simple'; - my $hc; - - delete $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{cloudrange}; - - if ($acu =~ /on_complex/xs) { # Autokorrektur complex soll genutzt werden - $crang = cloud2bin ($wcc); # Range errechnen - ($hc, $hq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$fh1), $sabin, $crang, undef); # Korrekturfaktor/Qualität der Stunde des Tages (complex) - - $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{cloudrange} = $crang; - } - elsif ($acu =~ /on_simple/xs) { - ($hc, $hq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$fh1), $sabin, 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) - } - else { # keine Autokorrektur - ($hc, $hq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$fh1), $sabin, 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) - $hc = 1; - } - - $hq //= '-'; # keine Qualität definiert - $hq = sprintf "%.2f", $hq if(isNumeric ($hq)); - $hc //= $hcraw; # Korrekturfaktor Voreinstellung - $hc = 1 if(1 * $hc == 0); # 0.0-Werte ignorieren (Schleifengefahr) - $hc = sprintf "%.2f", $hc; - - if ($cpcf =~ /manual\sfix/xs) { # Voreinstellung pcf-Reading verwenden wenn 'manual fix' - $hc = $hcraw; - debugLog ($paref, 'pvCorrectionRead', "use 'manual fix' - fd: $fd, hod: ".sprintf("%02d",$fh1).", corrf: $hc, quality: $hq"); - } - else { - my $flex = $cpcf =~ /manual\sflex/xs ? "use 'manual flex'" : 'read parameters'; - debugLog ($paref, 'pvCorrectionRead', "$flex - fd: $fd, hod: ".sprintf("%02d",$fh1).", Sun Altitude Bin: $sabin, Cloud range: $crang, corrf: $hc, quality: $hq"); - } - - $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{pvcorrf} = $hc."/".$hq; - - if ($fd == 0 && $fh1) { - writeToHistory ( { paref => $paref, key => 'pvcorrfactor', val => $hc.'/'.$hq, hour => $fh1 } ); - } - -return ($hc, $hq); -} - -################################################################### -# Zellen Leistungskorrektur Einfluss durch Wärmekoeffizienten -# berechnen -# -# Die Nominalleistung der Module wird bei 25 Grad -# Umgebungstemperatur und bei 1.000 Watt Sonneneinstrahlung -# gemessen. -# Steigt die Temperatur um 1 Grad Celsius sinkt die Modulleistung -# typisch um 0,4 Prozent. Solartellen können im Sommer 70°C heiß -# werden. -# -# Das würde für eine 10 kWp Photovoltaikanlage folgenden -# Leistungsverlust bedeuten: -# -# Leistungsverlust = -0,4%/K * 45K * 10 kWp = 1,8 kWp -# -# https://www.enerix.de/photovoltaiklexikon/temperaturkoeffizient/ -# -################################################################### -sub ___calcPeaklossByTemp { - my $paref = shift; - my $name = $paref->{name}; - my $peak = $paref->{peak} // return (0,0); - my $wcc = $paref->{wcc} // return (0,0); # vorhergesagte Wolkendecke Stunde X - my $temp = $paref->{temp} // return (0,0); # vorhergesagte Temperatur Stunde X - - my $modtemp = $temp + ($tempmodinc * (1 - ($wcc/100))); # kalkulierte Modultemperatur - my $peakloss = sprintf "%.2f", $tempcoeffdef * ($modtemp - $tempbasedef) * $peak / 100; - -return ($peakloss, $modtemp); -} - -################################################################ -# den Maximalwert PV Vorhersage für Heute ermitteln -################################################################ -sub _calcMaxEstimateToday { - my $paref = shift; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $date = $paref->{date}; - - my $maxest = 0; - my $maxtim = '-'; - - for my $h (1..23) { - my $pvfc = ReadingsNum ($name, "Today_Hour".sprintf("%02d",$h)."_PVforecast", 0); - next if($pvfc <= $maxest); - - $maxtim = $date.' '.sprintf("%02d",$h-1).':00:00'; - $maxest = $pvfc; - } - - return if(!$maxest); - - storeReading ('Today_MaxPVforecast', $maxest.' Wh'); - storeReading ('Today_MaxPVforecastTime', $maxtim); - -return; -} - ################################################################ # Werte Inverter Device ermitteln und übertragen ################################################################ @@ -8961,16 +8412,16 @@ sub _transferInverterValues { my $feed = $h->{feed} // 'default'; - $data{$type}{$name}{inverters}{$in}{igeneration} = $pv; # Hilfshash Wert current generation, Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 - $data{$type}{$name}{inverters}{$in}{ietotal} = $etotal; # aktuellen etotal des WR speichern - $data{$type}{$name}{inverters}{$in}{iname} = $indev; # Name des Inverterdevices - $data{$type}{$name}{inverters}{$in}{ialias} = AttrVal ($indev, 'alias', $indev); # Alias Inverter - $data{$type}{$name}{inverters}{$in}{invertercap} = $h->{capacity} if(defined $h->{capacity}); # optionale Angabe max. WR-Leistung - $data{$type}{$name}{inverters}{$in}{ilimit} = $h->{limit} // 100; # Wirkleistungsbegrenzung - $data{$type}{$name}{inverters}{$in}{iicon} = $h->{icon} if($h->{icon}); # Icon des Inverters - $data{$type}{$name}{inverters}{$in}{istrings} = $h->{strings} if($h->{strings}); # dem Inverter zugeordnete Strings - $data{$type}{$name}{inverters}{$in}{iasynchron} = $h->{asynchron} if($h->{asynchron}); # Inverter Mode - $data{$type}{$name}{inverters}{$in}{ifeed} = $feed; # Eigenschaften der Energielieferung + $data{$name}{inverters}{$in}{igeneration} = $pv; # Hilfshash Wert current generation, Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 + $data{$name}{inverters}{$in}{ietotal} = $etotal; # aktuellen etotal des WR speichern + $data{$name}{inverters}{$in}{iname} = $indev; # Name des Inverterdevices + $data{$name}{inverters}{$in}{ialias} = AttrVal ($indev, 'alias', $indev); # Alias Inverter + $data{$name}{inverters}{$in}{invertercap} = $h->{capacity} if(defined $h->{capacity}); # optionale Angabe max. WR-Leistung + $data{$name}{inverters}{$in}{ilimit} = $h->{limit} // 100; # Wirkleistungsbegrenzung + $data{$name}{inverters}{$in}{iicon} = $h->{icon} if($h->{icon}); # Icon des Inverters + $data{$name}{inverters}{$in}{istrings} = $h->{strings} if($h->{strings}); # dem Inverter zugeordnete Strings + $data{$name}{inverters}{$in}{iasynchron} = $h->{asynchron} if($h->{asynchron}); # Inverter Mode + $data{$name}{inverters}{$in}{ifeed} = $feed; # Eigenschaften der Energielieferung $pvsum += $pv; $ethishoursum += $ethishour; @@ -8984,10 +8435,10 @@ sub _transferInverterValues { storeReading ('Current_PV', $pvsum.' W'); storeReading ('Today_Hour'.sprintf("%02d",$nhour).'_PVreal', $ethishoursum.' Wh'.$warn); - $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{pvrl} = $ethishoursum; # Ringspeicher PV real Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 + $data{$name}{circular}{sprintf("%02d",$nhour)}{pvrl} = $ethishoursum; # Ringspeicher PV real Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 - push @{$data{$type}{$name}{current}{genslidereg}}, $pvsum; # Schieberegister PV Erzeugung - limitArray ($data{$type}{$name}{current}{genslidereg}, $slidenumdef); + push @{$data{$name}{current}{genslidereg}}, $pvsum; # Schieberegister PV Erzeugung + limitArray ($data{$name}{current}{genslidereg}, $slidenummax); writeToHistory ( { paref => $paref, key => 'pvrl', val => $ethishoursum, hour => $nhour, valid => $aln } ); # valid=1: beim Learning berücksichtigen, 0: nicht @@ -8996,6 +8447,456 @@ sub _transferInverterValues { return; } +################################################################ +# Strahlungsvorhersage Werte aus solcastapi-Hash +# übertragen und PV Vorhersage berechnen / in Nexthours +# speichern +################################################################ +sub _transferAPIRadiationValues { + my $paref = shift; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $t = $paref->{t}; # Epoche Zeit + my $chour = $paref->{chour}; + my $date = $paref->{date}; + my $hash = $defs{$name}; + + return if(!keys %{$data{$name}{solcastapi}}); + + my @strings = sort keys %{$data{$name}{strings}}; + return if(!@strings); + + my $lang = $paref->{lang}; + + for my $num (0..47) { + my ($fd,$fh) = calcDayHourMove ($chour, $num); + + if ($fd > 1) { # überhängende Werte löschen + delete $data{$name}{nexthours}{"NextHour".sprintf "%02d", $num}; + next; + } + + my $fh1 = $fh + 1; + my $wantts = (timestringToTimestamp ($date.' '.$chour.':00:00')) + ($num * 3600); + my $wantdt = (timestampToTimestring ($wantts, $lang))[1]; + my $nhtstr = 'NextHour'.sprintf "%02d", $num; + my ($wtday, $wthour) = $wantdt =~ /(\d{2})\s(\d{2}):/xs; + my $hod = sprintf "%02d", int $wthour + 1; # Stunde des Tages + my $rad1h = RadiationAPIVal ($hash, '?All', $wantdt, 'Rad1h', undef); + + $paref->{wantdt} = $wantdt; + $paref->{wantts} = $wantts; + $paref->{wtday} = $wtday; + $paref->{hod} = $hod; + $paref->{nhtstr} = $nhtstr; + $paref->{num} = $num; + $paref->{fh1} = $fh1; + $paref->{fd} = $fd; + + $data{$name}{nexthours}{$nhtstr}{starttime} = $wantdt; + $data{$name}{nexthours}{$nhtstr}{hourofday} = $hod; + $data{$name}{nexthours}{$nhtstr}{today} = $fd == 0 ? 1 : 0; + $data{$name}{nexthours}{$nhtstr}{rad1h} = $rad1h; + + my $sunalt = HistoryVal ($hash, $wtday, $hod, 'sunalt', undef); + my $sunaz = HistoryVal ($hash, $wtday, $hod, 'sunaz', undef); + + if (!defined $sunalt || !defined $sunaz) { + __calcSunPosition ($paref); + $sunalt = HistoryVal ($hash, $wtday, $hod, 'sunalt', undef); + $sunaz = HistoryVal ($hash, $wtday, $hod, 'sunaz', undef); + } + + if (defined $sunaz) { + $data{$name}{nexthours}{$nhtstr}{sunaz} = $sunaz; + } + else { + $sunaz = NexthoursVal ($hash, $nhtstr, 'sunaz', 0); + } + + if (defined $sunalt) { + $data{$name}{nexthours}{$nhtstr}{sunalt} = $sunalt; + } + else { + $sunalt = NexthoursVal ($hash, $nhtstr, 'sunalt', 0); + } + + $paref->{sabin} = sunalt2bin ($sunalt); + my $est = __calcPVestimates ($paref); + my ($msg, $pvaifc) = aiGetResult ($paref); # KI Entscheidungen abfragen + + $data{$name}{nexthours}{$nhtstr}{pvapifc} = $est; # durch API gelieferte PV Forecast + + delete $paref->{fd}; + delete $paref->{fh1}; + delete $paref->{num}; + delete $paref->{nhtstr}; + delete $paref->{hod}; + delete $paref->{wtday}; + delete $paref->{wantdt}; + delete $paref->{wantts}; + delete $paref->{sabin}; + + my $useai = 0; + my $pvfc; + + if ($msg eq 'accurate' || $msg eq 'spreaded') { + my $airn = CircularVal ($hash, 99, 'aiRulesNumber', 0); + my $aivar = 100; + $aivar = sprintf "%.0f", (100 * $pvaifc / $est) if($est); # Übereinstimmungsgrad KI Forecast zu API Forecast in % + + if ($msg eq 'accurate') { # KI liefert 'accurate' Treffer -> verwenden + if ($airn >= $aiAccTRNMin || ($aivar >= $aiAccLowLim && $aivar <= $aiAccUpLim)) { + $data{$name}{nexthours}{$nhtstr}{aihit} = 1; + $pvfc = $pvaifc; + $useai = 1; + + debugLog ($paref, 'aiData', qq{AI Hit - accurate result used -> aiRulesNum: $airn, variance: $aivar, hod: $hod, Rad1h: $rad1h, pvfc: $pvfc Wh}); + } + } + elsif ($msg eq 'spreaded') { # Abweichung AI von Standardvorhersage begrenzen + if ($airn >= $aiSpreadTRNMin || ($aivar >= $aiSpreadLowLim && $aivar <= $aiSpreadUpLim)) { + $data{$name}{nexthours}{$nhtstr}{aihit} = 1; + $pvfc = $pvaifc; + $useai = 1; + + debugLog ($paref, 'aiData', qq{AI Hit - spreaded result used -> aiRulesNum: $airn, hod: $hod, Rad1h: $rad1h, pvfc: $pvfc Wh}); + } + } + } + else { + debugLog ($paref, 'aiData', $msg); + } + + if ($useai) { + $data{$name}{nexthours}{$nhtstr}{pvaifc} = $pvaifc; # durch AI gelieferte PV Forecast + } + else { + delete $data{$name}{nexthours}{$nhtstr}{pvaifc}; + $data{$name}{nexthours}{$nhtstr}{aihit} = 0; + $pvfc = $est; + + debugLog ($paref, 'aiData', "use PV from API (no AI or AI result tolerance overflow) -> hod: $hod, Rad1h: ".(defined $rad1h ? $rad1h : '-').", pvfc: $pvfc Wh"); + } + + $data{$name}{nexthours}{$nhtstr}{pvfc} = $pvfc; # resultierende PV Forecast zuweisen + + if ($num < 23 && $fh < 24) { # Ringspeicher PV forecast Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 + $data{$name}{circular}{sprintf "%02d",$fh1}{pvapifc} = NexthoursVal ($hash, $nhtstr, 'pvapifc', undef); + $data{$name}{circular}{sprintf "%02d",$fh1}{pvfc} = $pvfc; + $data{$name}{circular}{sprintf "%02d",$fh1}{pvaifc} = NexthoursVal ($hash, $nhtstr, 'pvaifc', undef); + $data{$name}{circular}{sprintf "%02d",$fh1}{aihit} = NexthoursVal ($hash, $nhtstr, 'aihit', 0); + } + + if ($fd == 0 && int $pvfc > 0) { # Vorhersagedaten des aktuellen Tages zum manuellen Vergleich in Reading speichern + storeReading ('Today_Hour'.sprintf ("%02d",$fh1).'_PVforecast', "$pvfc Wh"); + } + + if ($fd == 0 && $fh1) { + writeToHistory ( { paref => $paref, key => 'pvfc', val => $pvfc, hour => $fh1 } ); + writeToHistory ( { paref => $paref, key => 'radiation', val => $rad1h, hour => $fh1 } ); + } + } + + storeReading ('.lastupdateForecastValues', $t); # Statusreading letzter update + +return; +} + +################################################################ +# Ermittlung der Sonnenpositionen +# Az,Alt = Azimuth und Höhe (in Dezimalgrad) des Körpers +# über dem Horizont +################################################################ +sub __calcSunPosition { + my $paref = shift; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $t = $paref->{t}; # Epoche Zeit + my $chour = $paref->{chour}; + my $wtday = $paref->{wtday}; + my $num = $paref->{num}; + my $nhtstr = $paref->{nhtstr}; + my $hash = $defs{$name}; + + my ($fd, $fh) = calcDayHourMove ($chour, $num); + last if($fd > 1); + + my $tstr = (timestampToTimestring ($t + ($num * 3600)))[3]; + my ($date, $h, $m, $s) = split /[ :]/, $tstr; + $tstr = $date.' '.$h.':30:00'; + + my ($az, $alt); + + eval { + $az = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAz', $tstr); # statt Astro_Get geht auch FHEM::Astro::Get + $alt = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAlt', $tstr); + }; + + if ($@) { + Log3 ($name, 1, "$name - ERROR - $@"); + return; + } + + my $hodn = sprintf "%02d", $h + 1; + + $data{$name}{nexthours}{$nhtstr}{sunaz} = $az; + $data{$name}{nexthours}{$nhtstr}{sunalt} = $alt; + + debugLog ($paref, 'collectData', "Sun position: day: $wtday, hod: $hodn, $tstr, azimuth: $az, altitude: $alt"); + + if ($fd == 0 && $hodn) { # Sun Position in pvHistory speichern + writeToHistory ( { paref => $paref, key => 'sunaz', val => $az, hour => $hodn } ); + writeToHistory ( { paref => $paref, key => 'sunalt', val => $alt, hour => $hodn } ); + } + +return; +} + +######################################################################### +# API Erzeugungsprognose mit gewählter Autokorrekturmethode anpassen +######################################################################### +sub __calcPVestimates { + my $paref = shift; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $wantdt = $paref->{wantdt}; + my $hod = $paref->{hod}; + my $fd = $paref->{fd}; + my $num = $paref->{num}; + my $debug = $paref->{debug}; + + my $hash = $defs{$name}; + my $reld = $fd == 0 ? "today" : $fd == 1 ? "tomorrow" : "unknown"; + my $rr1c = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "rr1c", 0); # Gesamtniederschlag während der letzten Stunde kg/m2 + my $wcc = NexthoursVal ($hash, "NextHour".sprintf ("%02d",$num), "wcc", 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->{wcc} = $wcc; + my ($hc, $hq) = ___readCandQ ($paref); # liest den anzuwendenden Korrekturfaktor + delete $paref->{wcc}; + + my ($lh,$sq,$peakloss, $modtemp); + my $pvsum = 0; + my $peaksum = 0; + + for my $string (sort keys %{$data{$name}{strings}}) { + my $peak = StringVal ($hash, $string, 'peak', 0); # String Peak (kWp) + + if ($acu =~ /on_complex/xs) { + $paref->{peak} = $peak; + $paref->{wcc} = $wcc; + $paref->{temp} = $temp; + + ($peakloss, $modtemp) = ___calcPeaklossByTemp ($paref); # Reduktion Peakleistung durch Temperaturkoeffizienten der Module (vorzeichengehaftet) + $peak += $peakloss; + + delete $paref->{peak}; + delete $paref->{wcc}; + delete $paref->{temp}; + } + + $peak *= 1000; + my $est = RadiationAPIVal ($hash, $string, $wantdt, 'pv_estimate50', 0); + my $pv = sprintf "%.1f", ($est * $hc); # Korrekturfaktor anwenden + + my $invcap = 0; + + for my $in (keys %{$data{$name}{inverters}}) { + my $istrings = InverterVal ($hash, $in, 'istrings', ''); # dem Inverter zugeordnete Strings + next if(!grep /^$string$/, (split ',', $istrings)); + + $invcap = InverterVal ($hash, $in, 'invertercap', 0); # Max. Leistung des Inverters + + last; + } + + if ($invcap && $pv > $invcap) { + $pv = $invcap; # PV Vorhersage auf WR Kapazität begrenzen + + debugLog ($paref, "radiationProcess", "PV forecast start time $wantdt limited to $pv Wh due to inverter capacity"); + } + + if ($debug =~ /radiationProcess/xs) { + $lh = { # Log-Hash zur Ausgabe + "String Peak" => $peak. " W", + "Estimated PV generation (raw)" => $est. " Wh", + "Estimated PV generation (calc)" => $pv. " Wh", + "PV correction factor" => $hc, + "PV correction quality" => $hq, + }; + + if ($acu =~ /on_complex/xs) { + $lh->{"Module Temp (calculated)"} = $modtemp. " °C"; + $lh->{"Win(+)/Loss(-) String Peak Power by Temp"} = $peakloss." kWp"; + } + + $sq = q{}; + for my $idx (sort keys %{$lh}) { + $sq .= $idx." => ".$lh->{$idx}."\n"; + } + + Log3 ($name, 1, "$name DEBUG> PV API estimate for $reld Hour ".sprintf ("%02d", $hod)." string $string ->\n$sq"); + } + + $pvsum += $pv; + $peaksum += $peak; + } + + $data{$name}{current}{allstringspeak} = $peaksum; # temperaturbedingte Korrektur der installierten Peakleistung in W + $pvsum = $peaksum if($peaksum && $pvsum > $peaksum); # Vorhersage nicht größer als die Summe aller PV-Strings Peak + $pvsum = sprintf "%.0f", $pvsum; + + if ($debug =~ /radiationProcess/xs) { + $lh = { # Log-Hash zur Ausgabe + "Starttime" => $wantdt, + "Forecasted temperature" => $temp." °C", + "Cloudcover" => $wcc, + "Total Rain last hour" => $rr1c." kg/m2", + "PV Correction mode" => ($acu ? $acu : 'no'), + "PV generation forecast" => $pvsum." Wh", + }; + + $sq = q{}; + for my $idx (sort keys %{$lh}) { + $sq .= $idx." => ".$lh->{$idx}."\n"; + } + + Log3 ($name, 1, "$name DEBUG> PV API estimate for $reld Hour ".sprintf ("%02d", $hod)." summary: \n$sq"); + } + +return $pvsum; +} + +###################################################################### +# Complex: +# Liest bewölkungsabhängige Korrekturfaktor/Qualität aus pvCircular +# und speichert die Werte im Nexthours / pvHistory Hash +# +# Simple: +# Liest Korrekturfaktor/Qualität aus pvCircular simple und +# speichert die Werte im Nexthours / pvHistory Hash +###################################################################### +sub ___readCandQ { + my $paref = shift; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $num = $paref->{num}; + my $fh1 = $paref->{fh1}; + my $fd = $paref->{fd}; + my $wcc = $paref->{wcc}; + my $sabin = $paref->{sabin}; + my $hash = $defs{$name}; + + my ($acu, $aln) = isAutoCorrUsed ($name); # Autokorrekturmodus + my $sunalt = NexthoursVal ($hash, "NextHour".sprintf("%02d",$num), 'sunalt', undef); # Sun Altitude + my $hcraw = ReadingsNum ($name, 'pvCorrectionFactor_'.sprintf("%02d",$fh1), 1.00); # Voreinstellung RAW-Korrekturfaktor (evtl. manuell gesetzt) + my $cpcf = ReadingsVal ($name, 'pvCorrectionFactor_'.sprintf("%02d",$fh1), ''); # aktuelles pcf-Reading + my $hq = '-'; # keine Qualität definiert + my $crang = 'simple'; + my $hc; + + delete $data{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{cloudrange}; + + if ($acu =~ /on_complex/xs) { # Autokorrektur complex soll genutzt werden + $crang = cloud2bin ($wcc); # Range errechnen + ($hc, $hq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$fh1), $sabin, $crang, undef); # Korrekturfaktor/Qualität der Stunde des Tages (complex) + + $data{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{cloudrange} = $crang; + } + elsif ($acu =~ /on_simple/xs) { + ($hc, $hq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$fh1), $sabin, 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) + } + else { # keine Autokorrektur + ($hc, $hq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$fh1), $sabin, 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) + $hc = 1; + } + + $hq //= '-'; # keine Qualität definiert + $hq = sprintf "%.2f", $hq if(isNumeric ($hq)); + $hc //= $hcraw; # Korrekturfaktor Voreinstellung + $hc = 1 if(1 * $hc == 0); # 0.0-Werte ignorieren (Schleifengefahr) + $hc = sprintf "%.2f", $hc; + + if ($cpcf =~ /manual\sfix/xs) { # Voreinstellung pcf-Reading verwenden wenn 'manual fix' + $hc = $hcraw; + debugLog ($paref, 'pvCorrectionRead', "use 'manual fix' - fd: $fd, hod: ".sprintf("%02d",$fh1).", corrf: $hc, quality: $hq"); + } + else { + my $flex = $cpcf =~ /manual\sflex/xs ? "use 'manual flex'" : 'read parameters'; + debugLog ($paref, 'pvCorrectionRead', "$flex - fd: $fd, hod: ".sprintf("%02d",$fh1).", Sun Altitude Bin: $sabin, Cloud range: $crang, corrf: $hc, quality: $hq"); + } + + $data{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{pvcorrf} = $hc."/".$hq; + + if ($fd == 0 && $fh1) { + writeToHistory ( { paref => $paref, key => 'pvcorrfactor', val => $hc.'/'.$hq, hour => $fh1 } ); + } + +return ($hc, $hq); +} + +################################################################### +# Zellen Leistungskorrektur Einfluss durch Wärmekoeffizienten +# berechnen +# +# Die Nominalleistung der Module wird bei 25 Grad +# Umgebungstemperatur und bei 1.000 Watt Sonneneinstrahlung +# gemessen. +# Steigt die Temperatur um 1 Grad Celsius sinkt die Modulleistung +# typisch um 0,4 Prozent. Solartellen können im Sommer 70°C heiß +# werden. +# +# Das würde für eine 10 kWp Photovoltaikanlage folgenden +# Leistungsverlust bedeuten: +# +# Leistungsverlust = -0,4%/K * 45K * 10 kWp = 1,8 kWp +# +# https://www.enerix.de/photovoltaiklexikon/temperaturkoeffizient/ +# +################################################################### +sub ___calcPeaklossByTemp { + my $paref = shift; + my $name = $paref->{name}; + my $peak = $paref->{peak} // return (0,0); + my $wcc = $paref->{wcc} // return (0,0); # vorhergesagte Wolkendecke Stunde X + my $temp = $paref->{temp} // return (0,0); # vorhergesagte Temperatur Stunde X + + my $modtemp = $temp + ($tempmodinc * (1 - ($wcc/100))); # kalkulierte Modultemperatur + my $peakloss = sprintf "%.2f", $tempcoeffdef * ($modtemp - $tempbasedef) * $peak / 100; + +return ($peakloss, $modtemp); +} + +################################################################ +# den Maximalwert PV Vorhersage für Heute ermitteln +################################################################ +sub _calcMaxEstimateToday { + my $paref = shift; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $date = $paref->{date}; + + my $maxest = 0; + my $maxtim = '-'; + + for my $h (1..23) { + my $pvfc = ReadingsNum ($name, "Today_Hour".sprintf("%02d",$h)."_PVforecast", 0); + next if($pvfc <= $maxest); + + $maxtim = $date.' '.sprintf("%02d",$h-1).':00:00'; + $maxest = $pvfc; + } + + return if(!$maxest); + + storeReading ('Today_MaxPVforecast', $maxest.' Wh'); + storeReading ('Today_MaxPVforecastTime', $maxtim); + +return; +} + ################################################################ # Werte anderer Erzeuger ermitteln und übertragen ################################################################ @@ -9043,12 +8944,12 @@ sub _transferProducerValues { $ethishour = int ($etotal - $histetot); } - $data{$type}{$name}{producers}{$pn}{pgeneration} = $p; - $data{$type}{$name}{producers}{$pn}{petotal} = $etotal; # aktuellen etotal des WR speichern - $data{$type}{$name}{producers}{$pn}{pname} = $prdev; # Name des Producerdevices - $data{$type}{$name}{producers}{$pn}{palias} = AttrVal ($prdev, 'alias', $prdev); # Alias Producer - $data{$type}{$name}{producers}{$pn}{picon} = $h->{icon} if($h->{icon}); # Icon des Producers - $data{$type}{$name}{producers}{$pn}{pfeed} = 'default'; # Eigenschaften der Energielieferung + $data{$name}{producers}{$pn}{pgeneration} = $p; + $data{$name}{producers}{$pn}{petotal} = $etotal; # aktuellen etotal des WR speichern + $data{$name}{producers}{$pn}{pname} = $prdev; # Name des Producerdevices + $data{$name}{producers}{$pn}{palias} = AttrVal ($prdev, 'alias', $prdev); # Alias Producer + $data{$name}{producers}{$pn}{picon} = $h->{icon} if($h->{icon}); # Icon des Producers + $data{$name}{producers}{$pn}{pfeed} = 'default'; # Eigenschaften der Energielieferung if ($ethishour < 0) { $ethishour = 0; @@ -9067,7 +8968,7 @@ sub _transferProducerValues { storeReading ('Current_PP'.$pn, sprintf("%.1f", $p).' W'); storeReading ('Today_Hour'.sprintf("%02d",$nhour).'_PPreal'.$pn, $ethishour.' Wh'.$warn); - $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{'pprl'.$pn} = $ethishour; # Ringspeicher P real + $data{$name}{circular}{sprintf("%02d",$nhour)}{'pprl'.$pn} = $ethishour; # Ringspeicher P real writeToHistory ( { paref => $paref, key => 'pprl'.$pn, val => $ethishour, hour => $nhour } ); @@ -9106,17 +9007,17 @@ sub _transferMeterValues { my @acp = split ":", $h->{conprice}; if (scalar(@acp) == 3) { - $data{$type}{$name}{current}{ePurchasePrice} = ReadingsNum ($acp[0], $acp[1], 0); - $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[2]; + $data{$name}{current}{ePurchasePrice} = ReadingsNum ($acp[0], $acp[1], 0); + $data{$name}{current}{ePurchasePriceCcy} = $acp[2]; } elsif (scalar(@acp) == 2) { if (isNumeric($acp[0])) { - $data{$type}{$name}{current}{ePurchasePrice} = $acp[0]; - $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[1]; + $data{$name}{current}{ePurchasePrice} = $acp[0]; + $data{$name}{current}{ePurchasePriceCcy} = $acp[1]; } else { - $data{$type}{$name}{current}{ePurchasePrice} = ReadingsNum ($medev, $acp[0], 0); - $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[1]; + $data{$name}{current}{ePurchasePrice} = ReadingsNum ($medev, $acp[0], 0); + $data{$name}{current}{ePurchasePriceCcy} = $acp[1]; } } @@ -9132,17 +9033,17 @@ sub _transferMeterValues { my @afp = split ":", $h->{feedprice}; if (scalar(@afp) == 3) { - $data{$type}{$name}{current}{eFeedInTariff} = ReadingsNum ($afp[0], $afp[1], 0); - $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[2]; + $data{$name}{current}{eFeedInTariff} = ReadingsNum ($afp[0], $afp[1], 0); + $data{$name}{current}{eFeedInTariffCcy} = $afp[2]; } elsif (scalar(@afp) == 2) { if (isNumeric($afp[0])) { - $data{$type}{$name}{current}{eFeedInTariff} = $afp[0]; - $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[1]; + $data{$name}{current}{eFeedInTariff} = $afp[0]; + $data{$name}{current}{eFeedInTariffCcy} = $afp[1]; } else { - $data{$type}{$name}{current}{eFeedInTariff} = ReadingsNum ($medev, $afp[0], 0); - $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[1]; + $data{$name}{current}{eFeedInTariff} = ReadingsNum ($medev, $afp[0], 0); + $data{$name}{current}{eFeedInTariffCcy} = $afp[1]; } } @@ -9193,10 +9094,10 @@ sub _transferMeterValues { my $ftuf = $ftunit =~ /^kWh$/xi ? 1000 : 1; my $fitotal = ReadingsNum ($medev, $ft, 0) * $ftuf; # Einspeisung total (Wh) - $data{$type}{$name}{circular}{99}{gridcontotal} = $gctotal; # Total Netzbezug speichern - $data{$type}{$name}{circular}{99}{feedintotal} = $fitotal; # Total Feedin speichern - $data{$type}{$name}{current}{gridconsumption} = int $gco; # Current grid consumption Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 - $data{$type}{$name}{current}{gridfeedin} = int $gfin; # Wert current grid Feed in + $data{$name}{circular}{99}{gridcontotal} = $gctotal; # Total Netzbezug speichern + $data{$name}{circular}{99}{feedintotal} = $fitotal; # Total Feedin speichern + $data{$name}{current}{gridconsumption} = int $gco; # Current grid consumption Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 + $data{$name}{current}{gridfeedin} = int $gfin; # Wert current grid Feed in debugLog ($paref, "collectData", "collect Meter data - device: $medev =>"); debugLog ($paref, "collectData", "gcon: $gco W, gfeedin: $gfin W, contotal: $gctotal Wh, feedtotal: $fitotal Wh"); @@ -9216,7 +9117,7 @@ sub _transferMeterValues { my $idgcon = CircularVal ($hash, 99, 'initdaygcon', undef); if (!$gctotal) { - $data{$type}{$name}{circular}{99}{initdaygcon} = 0; + $data{$name}{circular}{99}{initdaygcon} = 0; Log3 ($name, 3, "$name - WARNING - '$medev' - the total energy drawn from grid was reset and is registered with >0<."); } elsif ($gcdaypast == 0) { # Management der Stundenberechnung auf Basis Totalwerte GridConsumtion @@ -9224,11 +9125,11 @@ sub _transferMeterValues { $docon = 1; } else { - $data{$type}{$name}{circular}{99}{initdaygcon} = $gctotal; + $data{$name}{circular}{99}{initdaygcon} = $gctotal; } } elsif (!defined $idgcon) { - $data{$type}{$name}{circular}{99}{initdaygcon} = $gctotal - $gcdaypast - ReadingsNum ($name, "Today_Hour".sprintf("%02d",$chour+1)."_GridConsumption", 0); + $data{$name}{circular}{99}{initdaygcon} = $gctotal - $gcdaypast - ReadingsNum ($name, "Today_Hour".sprintf("%02d",$chour+1)."_GridConsumption", 0); } else { $docon = 1; @@ -9242,7 +9143,7 @@ sub _transferMeterValues { } storeReading ('Today_Hour'.sprintf("%02d",$nhour).'_GridConsumption', $gctotthishour.' Wh'); - $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{gcons} = $gctotthishour; # Hilfshash Wert Bezug (Wh) Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 + $data{$name}{circular}{sprintf("%02d",$nhour)}{gcons} = $gctotthishour; # Hilfshash Wert Bezug (Wh) Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 writeToHistory ( { paref => $paref, key => 'gcons', val => $gctotthishour, hour => $nhour } ); } @@ -9253,7 +9154,7 @@ sub _transferMeterValues { my $idfin = CircularVal ($hash, 99, 'initdayfeedin', undef); if (!$fitotal) { - $data{$type}{$name}{circular}{99}{initdayfeedin} = 0; + $data{$name}{circular}{99}{initdayfeedin} = 0; Log3 ($name, 3, "$name - WARNING - '$medev' - the total energy feed in to grid was reset and is registered with >0<."); } elsif ($gfdaypast == 0) { # Management der Stundenberechnung auf Basis Totalwerte GridFeedIn @@ -9261,11 +9162,11 @@ sub _transferMeterValues { $dofeed = 1; } else { - $data{$type}{$name}{circular}{99}{initdayfeedin} = $fitotal; + $data{$name}{circular}{99}{initdayfeedin} = $fitotal; } } elsif (!defined $idfin) { - $data{$type}{$name}{circular}{99}{initdayfeedin} = $fitotal - $gfdaypast - ReadingsNum ($name, 'Today_Hour'.sprintf("%02d",$chour+1).'_GridFeedIn', 0); + $data{$name}{circular}{99}{initdayfeedin} = $fitotal - $gfdaypast - ReadingsNum ($name, 'Today_Hour'.sprintf("%02d",$chour+1).'_GridFeedIn', 0); } else { $dofeed = 1; @@ -9279,7 +9180,7 @@ sub _transferMeterValues { } storeReading ('Today_Hour'.sprintf("%02d",$nhour).'_GridFeedIn', $gftotthishour.' Wh'); - $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{gfeedin} = $gftotthishour; + $data{$name}{circular}{sprintf("%02d",$nhour)}{gfeedin} = $gftotthishour; writeToHistory ( { paref => $paref, key => 'gfeedin', val => $gftotthishour, hour => $nhour } ); } @@ -9335,10 +9236,10 @@ sub _transferBatteryValues { $instcap = $instcap * ($bcapunit =~ /^kWh$/xi ? 1000 : 1); } - $data{$type}{$name}{current}{batinstcap} = $instcap; # installierte Batteriekapazität + $data{$name}{current}{batinstcap} = $instcap; # installierte Batteriekapazität } else { - delete $data{$type}{$name}{current}{batinstcap}; + delete $data{$name}{current}{batinstcap}; } my $debug = $paref->{debug}; @@ -9372,15 +9273,15 @@ sub _transferBatteryValues { # Batterielade-, enladeenergie in Circular speichern ###################################################### if (!defined CircularVal ($hash, 99, 'initdaybatintot', undef)) { - $data{$type}{$name}{circular}{99}{initdaybatintot} = $btotin; # total Batterieladung zu Tagbeginn (Wh) + $data{$name}{circular}{99}{initdaybatintot} = $btotin; # total Batterieladung zu Tagbeginn (Wh) } - if (!defined CircularVal ($hash, 99, 'initdaybatouttot', undef)) { # total Batterieentladung zu Tagbeginn (Wh) - $data{$type}{$name}{circular}{99}{initdaybatouttot} = $btotout; + if (!defined CircularVal ($hash, 99, 'initdaybatouttot', undef)) { # total Batterieentladung zu Tagbeginn (Wh) + $data{$name}{circular}{99}{initdaybatouttot} = $btotout; } - $data{$type}{$name}{circular}{99}{batintot} = $btotin; # aktuell total Batterieladung - $data{$type}{$name}{circular}{99}{batouttot} = $btotout; # aktuell total Batterieentladung + $data{$name}{circular}{99}{batintot} = $btotin; # aktuell total Batterieladung + $data{$name}{circular}{99}{batouttot} = $btotout; # aktuell total Batterieentladung my $nhour = $chour+1; @@ -9401,7 +9302,7 @@ sub _transferBatteryValues { $batinthishour = 0 if($batinthishour < 0); - $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{batin} = $batinthishour; # Ringspeicher Battery In Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 + $data{$name}{circular}{sprintf("%02d",$nhour)}{batin} = $batinthishour; # Ringspeicher Battery In Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 writeToHistory ( { paref => $paref, key => 'batinthishour', val => $batinthishour, hour => $nhour } ); # Batterieentladung aktuelle Stunde in pvHistory speichern @@ -9421,7 +9322,7 @@ sub _transferBatteryValues { $batoutthishour = 0 if($batoutthishour < 0); - $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{batout} = $batoutthishour; # Ringspeicher Battery In Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 + $data{$name}{circular}{sprintf("%02d",$nhour)}{batout} = $batoutthishour; # Ringspeicher Battery In Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 writeToHistory ( { paref => $paref, key => 'batoutthishour', val => $batoutthishour, hour => $nhour } ); # täglichen max. SOC in pvHistory speichern @@ -9440,13 +9341,13 @@ 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}{batasynchron} = $h->{asynchron} if($h->{asynchron}); # asynchroner Modus = X + $data{$name}{current}{powerbatin} = int $pbi; # Hilfshash Wert aktuelle Batterieladung + $data{$name}{current}{powerbatout} = int $pbo; # Hilfshash Wert aktuelle Batterieentladung + $data{$name}{current}{batcharge} = $soc; # aktuelle Batterieladung + $data{$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); + push @{$data{$name}{current}{socslidereg}}, $soc; # Schieberegister Batterie SOC + limitArray ($data{$name}{current}{socslidereg}, $slidenummax); return; } @@ -9468,7 +9369,7 @@ sub _batSocTarget { my $batcharge = CurrentVal ($hash, 'batcharge', 0); # aktuelle Ladung in % my $batinstcap = CurrentVal ($hash, 'batinstcap', 0); # installierte Batteriekapazität Wh my $cgbt = AttrVal ($name, 'ctrlBatSocManagement', undef); - + if ($cgbt && !$batinstcap) { Log3 ($name, 1, "$name - WARNING - Attribute ctrlBatSocManagement is active, but the required key 'cap' is not setup in setupBatteryDev. Exit."); return; @@ -9483,64 +9384,87 @@ sub _batSocTarget { __batSaveSocKeyFigures ($paref) if($batcharge >= $maxsoc); delete $paref->{careCycle}; - my $nt; - my $chargereq = 0; # Ladeanforderung wenn SoC unter Minimum SoC gefallen ist + my $nt = ''; + 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 - my $batysetsoc = HistoryVal ($hash, $yday, 99, 'batsetsoc', $lowSoc); # gespeicherter SOC Sollwert des Vortages - + my $yday = strftime "%d", localtime($t - 86400); # Vortag (range 01 to 31) + my $tdconsset = CurrentVal ($hash, 'tdConFcTillSunset', 0); # Verbrauch bis Sonnenuntergang Wh + my $batymaxsoc = HistoryVal ($hash, $yday, 99, 'batmaxsoc', 0); # gespeicherter max. SOC des Vortages + my $batysetsoc = HistoryVal ($hash, $yday, 99, 'batsetsoc', $lowSoc); # gespeicherter SOC Sollwert des Vortages + my $whneed = ($maxsoc / 100 * $batinstcap) - ($batcharge / 100 * $batinstcap); # benötigte Ladeenergie in Wh bis $maxsoc + $whneed = sprintf "%.0f", $whneed; + $target = $batymaxsoc < $maxsoc ? $batysetsoc + $batSocChgDay : $batymaxsoc >= $maxsoc ? $batysetsoc - $batSocChgDay : - $batysetsoc; # neuer Min SOC für den laufenden Tag + $batysetsoc; # neuer Min SOC für den laufenden Tag - debugLog ($paref, 'batteryManagement', "SoC calc Step1 - compare with SoC history -> new Target: $target %"); + debugLog ($paref, 'batteryManagement', "SoC calc Step1 - compare with SoC history -> preliminary 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,5h vor Sonnenuntergang berechnen/anwenden + my $delayts = $sunset - 5400; # Pflege-SoC/Erhöhung SoC erst ab 1,5h vor Sonnenuntergang berechnen/anwenden my $la = ''; + my $careSoc = $target; - 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 + my $pvfctm = ReadingsNum ($name, 'Tomorrow_PVforecast', 0); # PV Prognose morgen + my $pvfctd = ReadingsNum ($name, 'RestOfDayPVforecast', 0); # PV Prognose Rest heute + my $pvexpect = $pvfctm > $pvfctd ? $pvfctm : $pvfctd - $tdconsset; # erwartete (Rest) PV-Leistung des Tages + $pvexpect = $pvexpect > 0 ? $pvexpect : 0; # erwartete PV-Leistung inkl. Verbrauchsprognose bis Sonnenuntergang + + my $ntsmsc = CircularVal ($hash, 99, 'nextTsMaxSocChge', $t); + my $days2care = floor (($ntsmsc - $t) / 86400); # verbleibende Tage bis der Batterie Pflege-SoC (default 95%) erreicht sein soll + my $docare = 0; # keine Zwangsanwendung care SoC + + if ($t > $delayts || $pvexpect < $whneed || !$days2care) { $paref->{days2care} = $days2care; __batSaveSocKeyFigures ($paref); delete $paref->{days2care}; - my $careSoc = $maxsoc - ($days2care * $batSocChgDay); # Pflege-SoC um rechtzeitig den $maxsoc zu erreichen bei 5% Steigerung pro Tag - $target = $careSoc < $target ? $target : $careSoc; # resultierender Target-SoC unter Berücksichtigung $caresoc - $la = "note remaining days until care SoC ($days2care days) -> Target: $target %"; + $careSoc = $maxsoc - ($days2care * $batSocChgDay); # Pflege-SoC um rechtzeitig den $maxsoc zu erreichen bei 5% Steigerung pro Tag + $careSoc = $careSoc < $lowSoc ? $lowSoc : $careSoc; + + if ($careSoc >= $target) { + $target = $careSoc; # resultierender Target-SoC unter Berücksichtigung $caresoc + $docare = 1; # Zwangsanwendung care SoC + } + + $la = "calc care SoC -> Remaining days until care SoC: $days2care, Target: $target %"; } else { $nt = (timestampToTimestring ($delayts, $paref->{lang}))[0]; - $la = "note remaining days until care SoC -> calculation & activation postponed to after $nt"; + $la = "calc care SoC -> use preliminary Target: $target % (care SoC calculation & activation postponed to after $nt)"; } + debugLog ($paref, 'batteryManagement', "SoC calc Step2 - basics -> docare: $docare, care SoC: $careSoc %, E expect: $pvexpect Wh, need for care SoC: $whneed Wh"); debugLog ($paref, 'batteryManagement', "SoC calc Step2 - $la"); ## Aufladewahrscheinlichkeit beachten ####################################### - my $pvfctm = ReadingsNum ($name, 'Tomorrow_PVforecast', 0); # PV Prognose morgen - my $pvfctd = ReadingsNum ($name, 'RestOfDayPVforecast', 0); # PV Prognose Rest heute - my $csopt = ReadingsNum ($name, 'Battery_OptimumTargetSoC', $lowSoc); # aktuelles SoC Optimum - - my $pvexpect = $pvfctm > $pvfctd ? $pvfctm : $pvfctd; - 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 $csopt = ReadingsNum ($name, 'Battery_OptimumTargetSoC', $lowSoc); # aktuelles SoC Optimum + my $cantarget = sprintf "%.0f", (100 - (100 / $batinstcap) * $pvexpect); # berechneter max. möglicher Minimum-SOC nach Berücksichtigung Ladewahrscheinlichkeit + my $newtarget = sprintf "%.0f", ($cantarget < $target ? $cantarget : $target); # Abgleich möglicher Minimum-SOC gg. berechneten Minimum-SOC + + debugLog ($paref, 'batteryManagement', "SoC calc Step3 - basics -> cantarget: $cantarget %, newtarget: $newtarget %"); + + if ($newtarget > $careSoc) { + $docare = 0; # keine Zwangsanwendung care SoC + } + else { + $newtarget = $careSoc; + } + my $logadd = ''; 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 + elsif ($newtarget > $csopt && $t <= $delayts && !$docare) { # bisheriges Optimum bleibt vorerst $target = $csopt; $nt = (timestampToTimestring ($delayts, $paref->{lang}))[0]; - $logadd = "(calculated new target $newtarget % is only activated after $nt)"; + $logadd = "(new target $newtarget % is activated after $nt)"; } elsif ($newtarget < $csopt) { # Targetminderung sofort umsetzen -> Freiplatz für Ladeprognose $target = $newtarget; @@ -9551,15 +9475,16 @@ sub _batSocTarget { $logadd = "(no change)"; } - debugLog ($paref, 'batteryManagement', "SoC calc Step3 - note charging probability -> Target: $target % ".$logadd); + debugLog ($paref, 'batteryManagement', "SoC calc Step3 - charging probability -> docare: $docare, Target: $target % ".$logadd); ## low/up-Grenzen beachten ############################ - $target = $target > $upSoc ? $upSoc : + $target = $docare ? $target : + $target > $upSoc ? $upSoc : $target < $lowSoc ? $lowSoc : $target; - debugLog ($paref, 'batteryManagement', "SoC calc Step4 - observe low/up limits -> Target: $target %"); + debugLog ($paref, 'batteryManagement', "SoC calc Step4 - observe low/up limits -> docare: $docare, Target: $target %"); ## auf 5er Schritte anpassen (40,45,50,...) ############################################# @@ -9568,7 +9493,7 @@ sub _batSocTarget { 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 %"); + debugLog ($paref, 'batteryManagement', "SoC calc Step5 - rounding the SoC to steps of 5 % -> Target: $target %"); ## Zwangsladeanforderung ########################## @@ -9611,16 +9536,16 @@ sub __batSaveSocKeyFigures { my $paref = shift; my $name = $paref->{name}; my $type = $paref->{type}; - my $t = $paref->{t}; # aktuelle Zeit + my $t = $paref->{t}; # aktuelle Zeit my $careCycle = $paref->{careCycle} // $carecycledef; if (defined $paref->{days2care}) { - $data{$type}{$name}{circular}{99}{days2care} = $paref->{days2care}; # verbleibende Tage bis zum Pflege-SoC erreicht werden soll + $data{$name}{circular}{99}{days2care} = $paref->{days2care}; # verbleibende Tage bis zum Pflege-SoC erreicht werden soll return; } - $data{$type}{$name}{circular}{99}{lastTsMaxSocRchd} = $t; # Timestamp des letzten Erreichens von >= maxSoC - $data{$type}{$name}{circular}{99}{nextTsMaxSocChge} = $t + (86400 * $careCycle); # Timestamp bis zu dem die Batterie mindestens einmal maxSoC erreichen soll + $data{$name}{circular}{99}{lastTsMaxSocRchd} = $t; # Timestamp des letzten Erreichens von >= maxSoC + $data{$name}{circular}{99}{nextTsMaxSocChge} = $t + (86400 * $careCycle); # Timestamp bis zu dem die Batterie mindestens einmal maxSoC erreichen soll return; } @@ -9825,8 +9750,8 @@ sub _createSummaries { my $pvre = int $todaySumRe->{PV}; - push @{$data{$type}{$name}{current}{h4fcslidereg}}, int $next4HoursSum->{PV}; # Schieberegister 4h Summe Forecast - limitArray ($data{$type}{$name}{current}{h4fcslidereg}, $slidenumdef); + push @{$data{$name}{current}{h4fcslidereg}}, int $next4HoursSum->{PV}; # Schieberegister 4h Summe Forecast + limitArray ($data{$name}{current}{h4fcslidereg}, $slidenummax); my $gcon = CurrentVal ($hash, 'gridconsumption', 0); # aktueller Netzbezug my $tconsum = CurrentVal ($hash, 'tomorrowconsumption', undef); # Verbrauchsprognose für folgenden Tag @@ -9865,12 +9790,15 @@ sub _createSummaries { $selfconsumptionrate = sprintf "%.0f", ($selfconsumption / $pvgen * 100) if($pvgen * 1 > 0); $autarkyrate = sprintf "%.0f", ($selfconsumption + $batout) / $divi * 100 if($divi); # vermeide Illegal division by zero - $data{$type}{$name}{current}{consumption} = $consumption; - $data{$type}{$name}{current}{selfconsumption} = $selfconsumption; - $data{$type}{$name}{current}{selfconsumptionrate} = $selfconsumptionrate; - $data{$type}{$name}{current}{autarkyrate} = $autarkyrate; - $data{$type}{$name}{current}{surplus} = $surplus; - $data{$type}{$name}{current}{tdConFcTillSunset} = $tdConFcTillSunset; + $data{$name}{current}{consumption} = $consumption; + $data{$name}{current}{selfconsumption} = $selfconsumption; + $data{$name}{current}{selfconsumptionrate} = $selfconsumptionrate; + $data{$name}{current}{autarkyrate} = $autarkyrate; + $data{$name}{current}{tdConFcTillSunset} = $tdConFcTillSunset; + $data{$name}{current}{surplus} = $surplus; + + push @{$data{$name}{current}{surplusslidereg}}, $surplus; # Schieberegister PV Überschuß + limitArray ($data{$name}{current}{surplusslidereg}, $splslidenummax); storeReading ('Current_GridFeedIn', (int $gfeedin). ' W'); # V 1.37.0 storeReading ('Current_GridConsumption', (int $gcon). ' W'); # V 1.37.0 @@ -9912,7 +9840,7 @@ sub _manageConsumerData { my $nhour = $chour + 1; $paref->{nhour} = sprintf "%02d", $nhour; - for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { + for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { $paref->{consumer} = $c; my $consumer = ConsumerVal ($hash, $c, "name", ""); my $alias = ConsumerVal ($hash, $c, "alias", ""); @@ -9949,8 +9877,8 @@ sub _manageConsumerData { my $delta = $etot - ConsumerVal ($hash, $c, "old_etotal", $etot); $pcurr = sprintf "%.6f", $delta / (3600 * $timespan) if($delta); # Einheitenformel beachten !!: W = Wh / (3600 * s) - $data{$type}{$name}{consumers}{$c}{old_etotal} = $etot; - $data{$type}{$name}{consumers}{$c}{old_etottime} = $t; + $data{$name}{consumers}{$c}{old_etotal} = $etot; + $data{$name}{consumers}{$c}{old_etottime} = $t; storeReading ("consumer${c}_currentPower", $pcurr.' W'); } @@ -10012,7 +9940,7 @@ sub _manageConsumerData { my $runhours = 0; my $dnum = 0; - for my $n (sort{$a<=>$b} keys %{$data{$type}{$name}{pvhist}}) { # Betriebszeit und gemessenen Verbrauch ermitteln + for my $n (sort{$a<=>$b} keys %{$data{$name}{pvhist}}) { # Betriebszeit und gemessenen Verbrauch ermitteln my $csme = HistoryVal ($hash, $n, 99, "csme${c}", 0); my $hours = HistoryVal ($hash, $n, 99, "hourscsme${c}", 0); next if(!$hours); @@ -10024,13 +9952,13 @@ sub _manageConsumerData { if ($dnum) { if ($consumerco && $runhours) { - $data{$type}{$name}{consumers}{$c}{avgenergy} = sprintf "%.2f", ($consumerco/$runhours); # Durchschnittsverbrauch pro Stunde in Wh + $data{$name}{consumers}{$c}{avgenergy} = sprintf "%.2f", ($consumerco / $runhours); # Durchschnittsverbrauch pro Stunde in Wh } else { - delete $data{$type}{$name}{consumers}{$c}{avgenergy}; + delete $data{$name}{consumers}{$c}{avgenergy}; } - $data{$type}{$name}{consumers}{$c}{runtimeAvgDay} = sprintf "%.2f", (($runhours / $dnum) * 60); # Durchschnittslaufzeit am Tag in Minuten + $data{$name}{consumers}{$c}{runtimeAvgDay} = sprintf "%.2f", (($runhours / $dnum) * 60); # Durchschnittslaufzeit am Tag in Minuten } ## Consumer Schaltstatus und Schaltzeit für Readings ermitteln @@ -10039,10 +9967,10 @@ sub _manageConsumerData { isConsumerPhysOff ($hash, $c) ? 'off' : "unknown"; - $data{$type}{$name}{consumers}{$c}{state} = $costate; + $data{$name}{consumers}{$c}{state} = $costate; my ($pstate,$starttime,$stoptime,$supplmnt) = __getPlanningStateAndTimes ($paref); my ($iilt,$rlt) = isInLocktime ($paref); # Sperrzeit Status ermitteln - my $mode = ConsumerVal ($hash, $c, 'mode', 'can'); + my $mode = getConsumerPlanningMode ($hash, $c); # Planungsmode 'can' oder 'must' my $constate = "name='$alias' state='$costate'"; $constate .= " mode='$mode' planningstate='$pstate'"; $constate .= " remainLockTime='$rlt'" if($rlt); @@ -10092,7 +10020,7 @@ sub __getAutomaticState { my $auto = 1; $auto = ReadingsVal ($dswitch, $autord, 1) if($autord); # Reading für Ready-Bit -> Einschalten möglich ? - $data{$type}{$name}{consumers}{$c}{auto} = $auto; # Automaticsteuerung: 1 - Automatic ein, 0 - Automatic aus + $data{$name}{consumers}{$c}{auto} = $auto; # Automaticsteuerung: 1 - Automatic ein, 0 - Automatic aus return; } @@ -10118,19 +10046,19 @@ sub __calcEnergyPieces { delete $paref->{etot}; } else { - delete $data{$type}{$name}{consumers}{$c}{epiecAVG}; - delete $data{$type}{$name}{consumers}{$c}{epiecAVG_hours}; - delete $data{$type}{$name}{consumers}{$c}{epiecStartEtotal}; - delete $data{$type}{$name}{consumers}{$c}{epiecHist}; - delete $data{$type}{$name}{consumers}{$c}{epiecHour}; + delete $data{$name}{consumers}{$c}{epiecAVG}; + delete $data{$name}{consumers}{$c}{epiecAVG_hours}; + delete $data{$name}{consumers}{$c}{epiecStartEtotal}; + delete $data{$name}{consumers}{$c}{epiecHist}; + delete $data{$name}{consumers}{$c}{epiecHour}; for my $h (1..$epiecMaxCycles) { - delete $data{$type}{$name}{consumers}{$c}{"epiecHist_".$h}; - delete $data{$type}{$name}{consumers}{$c}{"epiecHist_".$h."_hours"}; + delete $data{$name}{consumers}{$c}{"epiecHist_".$h}; + delete $data{$name}{consumers}{$c}{"epiecHist_".$h."_hours"}; } } - delete $data{$type}{$name}{consumers}{$c}{epieces}; + delete $data{$name}{consumers}{$c}{epieces}; my $cotype = ConsumerVal ($hash, $c, "type", $defctype ); my $mintime = ConsumerVal ($hash, $c, "mintime", $defmintime); @@ -10162,7 +10090,7 @@ sub __calcEnergyPieces { $he = $epiecem if($h > 1 && $h < $hours); # kalk. Energieverbrauch Folgestunde(n) $he = $epiecel if($h == $hours ); # kalk. Energieverbrauch letzte Stunde - $data{$type}{$name}{consumers}{$c}{epieces}{${h}} = sprintf('%.2f', $he); + $data{$name}{consumers}{$c}{epieces}{${h}} = sprintf('%.2f', $he); } return; @@ -10197,11 +10125,11 @@ sub ___csmSpecificEpieces { my $hash = $defs{$name}; if (ConsumerVal ($hash, $c, "onoff", "off") eq "on") { # Status "Aus" verzögern um Pausen im Waschprogramm zu überbrücken - $data{$type}{$name}{consumers}{$c}{lastOnTime} = $t; + $data{$name}{consumers}{$c}{lastOnTime} = $t; } - my $tsloff = defined $data{$type}{$name}{consumers}{$c}{lastOnTime} ? - $t - $data{$type}{$name}{consumers}{$c}{lastOnTime} : + my $tsloff = defined $data{$name}{consumers}{$c}{lastOnTime} ? + $t - $data{$name}{consumers}{$c}{lastOnTime} : 99; debugLog ($paref, "epiecesCalc", qq{specificEpieces -> consumer "$c" - time since last Switch Off (tsloff): $tsloff seconds}); @@ -10211,13 +10139,13 @@ sub ___csmSpecificEpieces { my $epiecHist_hours = ""; if (ConsumerVal ($hash, $c, "epiecHour", -1) < 0) { # neue Aufzeichnung - $data{$type}{$name}{consumers}{$c}{epiecStartTime} = $t; - $data{$type}{$name}{consumers}{$c}{epiecHist} += 1; - $data{$type}{$name}{consumers}{$c}{epiecHist} = 1 if(ConsumerVal ($hash, $c, "epiecHist", 0) > $epiecMaxCycles); + $data{$name}{consumers}{$c}{epiecStartTime} = $t; + $data{$name}{consumers}{$c}{epiecHist} += 1; + $data{$name}{consumers}{$c}{epiecHist} = 1 if(ConsumerVal ($hash, $c, "epiecHist", 0) > $epiecMaxCycles); $ecycle = "epiecHist_".ConsumerVal ($hash, $c, "epiecHist", 0); - delete $data{$type}{$name}{consumers}{$c}{$ecycle}; # Löschen, wird neu erfasst + delete $data{$name}{consumers}{$c}{$ecycle}; # Löschen, wird neu erfasst } $ecycle = "epiecHist_".ConsumerVal ($hash, $c, "epiecHist", 0); # Zyklusnummer für Namen @@ -10230,16 +10158,16 @@ sub ___csmSpecificEpieces { if (ConsumerVal ($hash, $c, "epiecHour", 0) != $epiecHour) { # Betriebsstundenwechsel ? Differenz von etot noch auf die vorherige Betriebsstunde anrechnen my $epiecHour_last = $epiecHour - 1; - $data{$type}{$name}{consumers}{$c}{$ecycle}{$epiecHour_last} = sprintf '%.2f', ($etot - ConsumerVal ($hash, $c, "epiecStartEtotal", 0)) if($epiecHour > 1); - $data{$type}{$name}{consumers}{$c}{epiecStartEtotal} = $etot; + $data{$name}{consumers}{$c}{$ecycle}{$epiecHour_last} = sprintf '%.2f', ($etot - ConsumerVal ($hash, $c, "epiecStartEtotal", 0)) if($epiecHour > 1); + $data{$name}{consumers}{$c}{epiecStartEtotal} = $etot; debugLog ($paref, "epiecesCalc", qq{specificEpieces -> consumer "$c" - Operating hours change - new etotal (epiecStartEtotal): $etot}); } my $ediff = $etot - ConsumerVal ($hash, $c, "epiecStartEtotal", 0); - $data{$type}{$name}{consumers}{$c}{$ecycle}{$epiecHour} = sprintf '%.2f', $ediff; - $data{$type}{$name}{consumers}{$c}{epiecHour} = $epiecHour; - $data{$type}{$name}{consumers}{$c}{$epiecHist_hours} = $ediff ? $epiecHour : $epiecHour - 1; # wenn mehr als 1 Wh verbraucht wird die Stunde gezählt + $data{$name}{consumers}{$c}{$ecycle}{$epiecHour} = sprintf '%.2f', $ediff; + $data{$name}{consumers}{$c}{epiecHour} = $epiecHour; + $data{$name}{consumers}{$c}{$epiecHist_hours} = $ediff ? $epiecHour : $epiecHour - 1; # wenn mehr als 1 Wh verbraucht wird die Stunde gezählt debugLog ($paref, "epiecesCalc", qq{specificEpieces -> consumer "$c" - energy consumption in operating hour $epiecHour (ediff): $ediff}); } @@ -10251,12 +10179,12 @@ sub ___csmSpecificEpieces { $hours += ConsumerVal ($hash, $c, "epiecHist_".$h."_hours", 0); } - my $avghours = ceil ($hours / $epiecMaxCycles); - $data{$type}{$name}{consumers}{$c}{epiecAVG_hours} = $avghours; # durchschnittliche Betriebsstunden pro Zyklus + my $avghours = ceil ($hours / $epiecMaxCycles); + $data{$name}{consumers}{$c}{epiecAVG_hours} = $avghours; # durchschnittliche Betriebsstunden pro Zyklus debugLog ($paref, "epiecesCalc", qq{specificEpieces -> consumer "$c" - Average operating hours per cycle (epiecAVG_hours): $avghours}); - delete $data{$type}{$name}{consumers}{$c}{epiecAVG}; # Durchschnitt für epics ermitteln + delete $data{$name}{consumers}{$c}{epiecAVG}; # Durchschnitt für epics ermitteln for my $hour (1..$avghours) { # jede Stunde durchlaufen my $hoursE = 1; @@ -10264,27 +10192,27 @@ sub ___csmSpecificEpieces { for my $h (1..$epiecMaxCycles) { # jedes epiec durchlaufen my $ecycle = "epiecHist_".$h; - if (defined $data{$type}{$name}{consumers}{$c}{$ecycle}{$hour}) { - if ($data{$type}{$name}{consumers}{$c}{$ecycle}{$hour} > 5) { - $data{$type}{$name}{consumers}{$c}{epiecAVG}{$hour} += $data{$type}{$name}{consumers}{$c}{$ecycle}{$hour}; + if (defined $data{$name}{consumers}{$c}{$ecycle}{$hour}) { + if ($data{$name}{consumers}{$c}{$ecycle}{$hour} > 5) { + $data{$name}{consumers}{$c}{epiecAVG}{$hour} += $data{$name}{consumers}{$c}{$ecycle}{$hour}; $hoursE += 1; } } } - my $eavg = defined $data{$type}{$name}{consumers}{$c}{epiecAVG}{$hour} ? - $data{$type}{$name}{consumers}{$c}{epiecAVG}{$hour} : + my $eavg = defined $data{$name}{consumers}{$c}{epiecAVG}{$hour} ? + $data{$name}{consumers}{$c}{epiecAVG}{$hour} : 0; my $ahval = sprintf '%.2f', $eavg / $hoursE; # Durchschnitt ermittelt und speichern - $data{$type}{$name}{consumers}{$c}{epiecAVG}{$hour} = $ahval; + $data{$name}{consumers}{$c}{epiecAVG}{$hour} = $ahval; debugLog ($paref, "epiecesCalc", qq{specificEpieces -> consumer "$c" - Average epiece of operating hour $hour: $ahval}); } } - $data{$type}{$name}{consumers}{$c}{epiecHour} = -1; # epiecHour auf initialwert setzen für nächsten durchlauf + $data{$name}{consumers}{$c}{epiecHour} = -1; # epiecHour auf initialwert setzen für nächsten durchlauf } return; @@ -10427,7 +10355,7 @@ sub ___doPlanning { my $debug = $paref->{debug}; my $type = $paref->{type}; my $lang = $paref->{lang}; - my $nh = $data{$type}{$name}{nexthours}; + my $nh = $data{$name}{nexthours}; my $cicfip = AttrVal ($name, 'affectConsForecastInPlanning', 0); # soll Consumption Vorhersage in die Überschußermittlung eingehen ? my $hash = $defs{$name}; @@ -10475,7 +10403,7 @@ sub ___doPlanning { my $epieces = ConsumerVal ($hash, $c, "epieces", ""); if (ref $epieces eq "HASH") { - $epiece1 = $data{$type}{$name}{consumers}{$c}{epieces}{1}; + $epiece1 = $data{$name}{consumers}{$c}{epieces}{1}; } else { return; @@ -10508,7 +10436,7 @@ sub ___doPlanning { $paref->{mintime} = $mintime; $paref->{stopdiff} = $stopdiff; - if ($mode eq "can") { # Verbraucher kann geplant werden + if ($mode eq 'can') { # Verbraucher kann geplant werden if ($debug =~ /consumerPlanning/x) { for my $m (sort{$a<=>$b} keys %mtimes) { Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - surplus expected: $mtimes{$m}{spexp}, }. @@ -10613,7 +10541,7 @@ sub ___saveEhodpieces { my $hash = $defs{$name}; my $p = 1; - delete $data{$type}{$name}{consumers}{$c}{ehodpieces}; + delete $data{$name}{consumers}{$c}{ehodpieces}; for (my $i = $startts; $i <= $stopts; $i+=3600) { my $chod = (strftime "%H", localtime($i)) + 1; @@ -10621,8 +10549,8 @@ sub ___saveEhodpieces { my $ep = 0; if (ref $epieces eq "HASH") { - $ep = defined $data{$type}{$name}{consumers}{$c}{epieces}{$p} ? - $data{$type}{$name}{consumers}{$c}{epieces}{$p} : + $ep = defined $data{$name}{consumers}{$c}{epieces}{$p} ? + $data{$name}{consumers}{$c}{epieces}{$p} : 0; } else { @@ -10630,7 +10558,7 @@ sub ___saveEhodpieces { } $chod = sprintf '%02d', $chod; - $data{$type}{$name}{consumers}{$c}{ehodpieces}{$chod} = sprintf '%.2f', $ep if($ep); + $data{$name}{consumers}{$c}{ehodpieces}{$chod} = sprintf '%.2f', $ep if($ep); $p++; } @@ -10654,28 +10582,28 @@ sub ___setConsumerPlanningState { 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; + $data{$name}{consumers}{$c}{planSupplement} = $supplmnt; return if(!$ps); my ($starttime,$stoptime); if (defined $lonts) { - $data{$type}{$name}{consumers}{$c}{lastAutoOnTs} = $lonts; + $data{$name}{consumers}{$c}{lastAutoOnTs} = $lonts; } if (defined $loffts) { - $data{$type}{$name}{consumers}{$c}{lastAutoOffTs} = $loffts; + $data{$name}{consumers}{$c}{lastAutoOffTs} = $loffts; } if ($startts) { $starttime = (timestampToTimestring ($startts, $lang))[3]; - $data{$type}{$name}{consumers}{$c}{planswitchon} = $startts; + $data{$name}{consumers}{$c}{planswitchon} = $startts; } if ($stopts) { $stoptime = (timestampToTimestring ($stopts, $lang))[3]; - $data{$type}{$name}{consumers}{$c}{planswitchoff} = $stopts; + $data{$name}{consumers}{$c}{planswitchoff} = $stopts; } $ps .= " " if($starttime || $stoptime); @@ -10683,7 +10611,7 @@ sub ___setConsumerPlanningState { $ps .= $stoptime if(!$starttime && $stoptime); $ps .= " - ".$stoptime if($starttime && $stoptime); - $data{$type}{$name}{consumers}{$c}{planstate} = $ps; + $data{$name}{consumers}{$c}{planstate} = $ps; return; } @@ -10848,10 +10776,10 @@ sub ___setPlanningDeleteMeth { my $offday = strftime "%d", localtime($soffkey); if ($offday ne $onday) { # Planungsdaten spezifische Löschmethode - $data{$type}{$name}{consumers}{$c}{plandelete} = "specific"; + $data{$name}{consumers}{$c}{plandelete} = "specific"; } else { # Planungsdaten Löschmethode jeden Tag in Stunde 0 (_specialActivities) - $data{$type}{$name}{consumers}{$c}{plandelete} = "regular"; + $data{$name}{consumers}{$c}{plandelete} = "regular"; } } @@ -10873,10 +10801,10 @@ sub __setTimeframeState { my $stopts = ConsumerVal ($hash, $c, "planswitchoff", undef); # geplante Unix Stopzeit if ($startts && $t >= $startts && $stopts && $t <= $stopts) { # ist Zeit innerhalb der Planzeit ein/aus ? - $data{$type}{$name}{consumers}{$c}{isIntimeframe} = 1; + $data{$name}{consumers}{$c}{isIntimeframe} = 1; } else { - $data{$type}{$name}{consumers}{$c}{isIntimeframe} = 0; + $data{$name}{consumers}{$c}{isIntimeframe} = 0; } return; @@ -10892,20 +10820,27 @@ sub __setConsRcmdState { my $c = $paref->{consumer}; my $debug = $paref->{debug}; - my $hash = $defs{$name}; - my $surplus = CurrentVal ($hash, 'surplus', 0); # aktueller Energieüberschuß - my $nompower = ConsumerVal ($hash, $c, 'power', 0); # Consumer nominale Leistungsaufnahme (W) - my $ccr = AttrVal ($name, 'ctrlConsRecommendReadings', ''); # Liste der Consumer für die ConsumptionRecommended-Readings erstellt werden sollen - my $rescons = isConsumerPhysOn($hash, $c) ? 0 : $nompower; # resultierender Verbauch nach Einschaltung Consumer + my $hash = $defs{$name}; + my $nompower = ConsumerVal ($hash, $c, 'power', 0); # Consumer nominale Leistungsaufnahme (W) + my $ccr = AttrVal ($name, 'ctrlConsRecommendReadings', ''); # Liste der Consumer für die ConsumptionRecommended-Readings erstellt werden sollen + my $rescons = isConsumerPhysOn ($hash, $c) ? 0 : $nompower; # resultierender Verbauch nach Einschaltung Consumer + + my ($method, $surplus) = determSurplus ($hash, $c); # Consumer spezifische Ermittlung des Energieüberschußes - my ($spignore, $info, $err) = isSurplusIgnoCond ($hash, $c, $debug); # Vorhandensein PV Überschuß ignorieren ? + if ($debug =~ /consumerSwitching${c}/x) { + Log3 ($name, 1, qq{$name DEBUG> ############### consumerSwitching consumer "$c" ###############}); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - ConsumptionRecommended calc method: $method, value: }. + (defined $surplus ? $surplus : 'undef')); + } + + my ($spignore, $info, $err) = isSurplusIgnoCond ($hash, $c, $debug); # PV Überschuß ignorieren? Log3 ($name, 1, "$name - $err") if($err); - + if (!$nompower || $surplus - $rescons > 0 || $spignore) { - $data{$type}{$name}{consumers}{$c}{isConsumptionRecommended} = 1; # Einschalten des Consumers günstig bzw. Freigabe für "on" von Überschußseite erteilt + $data{$name}{consumers}{$c}{isConsumptionRecommended} = 1; # Einschalten des Consumers günstig bzw. Freigabe für "on" von Überschußseite erteilt } else { - $data{$type}{$name}{consumers}{$c}{isConsumptionRecommended} = 0; + $data{$name}{consumers}{$c}{isConsumptionRecommended} = 0; } if ($ccr =~ /$c/xs) { @@ -10924,10 +10859,6 @@ sub __switchConsumer { my $c = $paref->{consumer}; my $debug = $paref->{debug}; my $state = $paref->{state}; - - if ($debug =~ /consumerSwitching${c}/x) { - Log3 ($name, 1, qq{$name DEBUG> ############### consumerSwitching consumer "$c" ###############}); - } $paref->{fscss} = 1; # erster Subaufruf Consumer Schaltzustände ermitteln & setzen $state = ___setConsumerSwitchingState ($paref); @@ -11030,7 +10961,7 @@ sub ___switchConsumerOn { debugLog ($paref, "consumerSwitching${c}", qq{Consumer switch enable by battery state: $enable}); - if ($mode eq "can" && !$enable) { # Batterieladung - keine Verbraucher "Einschalten" Freigabe + if ($mode eq 'can' && !$enable) { # Batterieladung - keine Verbraucher "Einschalten" Freigabe $paref->{ps} = "priority charging battery"; ___setConsumerPlanningState ($paref); @@ -11320,16 +11251,16 @@ sub __getCyclesAndRuntime { if (isConsumerLogOn ($hash, $c, $pcurr)) { # Verbraucher ist logisch "an" if (ConsumerVal ($hash, $c, 'onoff', 'off') eq 'off') { # Status im letzen Zyklus war "off" - $data{$type}{$name}{consumers}{$c}{onoff} = 'on'; - $data{$type}{$name}{consumers}{$c}{startTime} = $t; # startTime ist nicht von "Automatic" abhängig -> nicht identisch mit planswitchon !!! - $data{$type}{$name}{consumers}{$c}{cycleStarttime} = $t; - $data{$type}{$name}{consumers}{$c}{cycleTime} = 0; - $data{$type}{$name}{consumers}{$c}{lastMinutesOn} = ConsumerVal ($hash, $c, 'minutesOn', 0); + $data{$name}{consumers}{$c}{onoff} = 'on'; + $data{$name}{consumers}{$c}{startTime} = $t; # startTime ist nicht von "Automatic" abhängig -> nicht identisch mit planswitchon !!! + $data{$name}{consumers}{$c}{cycleStarttime} = $t; + $data{$name}{consumers}{$c}{cycleTime} = 0; + $data{$name}{consumers}{$c}{lastMinutesOn} = ConsumerVal ($hash, $c, 'minutesOn', 0); - $data{$type}{$name}{consumers}{$c}{cycleDayNum}++; # Anzahl der On-Schaltungen am Tag + $data{$name}{consumers}{$c}{cycleDayNum}++; # Anzahl der On-Schaltungen am Tag } else { - $data{$type}{$name}{consumers}{$c}{cycleTime} = (($t - ConsumerVal ($hash, $c, 'cycleStarttime', $t)) / 60); # Minuten + $data{$name}{consumers}{$c}{cycleTime} = (($t - ConsumerVal ($hash, $c, 'cycleStarttime', $t)) / 60); # Minuten } $starthour = strftime "%H", localtime(ConsumerVal ($hash, $c, 'startTime', $t)); @@ -11337,17 +11268,17 @@ sub __getCyclesAndRuntime { if ($chour eq $starthour) { my $runtime = (($t - ConsumerVal ($hash, $c, 'startTime', $t)) / 60); # in Minuten ! (gettimeofday sind ms !) - $data{$type}{$name}{consumers}{$c}{minutesOn} = ConsumerVal ($hash, $c, 'lastMinutesOn', 0) + $runtime; + $data{$name}{consumers}{$c}{minutesOn} = ConsumerVal ($hash, $c, 'lastMinutesOn', 0) + $runtime; } else { # Stundenwechsel if (ConsumerVal ($hash, $c, 'onoff', 'off') eq 'on') { # Status im letzen Zyklus war "on" my $newst = timestringToTimestamp ($date.' '.sprintf("%02d", $chour).':00:00'); - $data{$type}{$name}{consumers}{$c}{startTime} = $newst; - $data{$type}{$name}{consumers}{$c}{minutesOn} = ($t - ConsumerVal ($hash, $c, 'startTime', $newst)) / 60; # in Minuten ! (gettimeofday sind ms !) - $data{$type}{$name}{consumers}{$c}{lastMinutesOn} = 0; + $data{$name}{consumers}{$c}{startTime} = $newst; + $data{$name}{consumers}{$c}{minutesOn} = ($t - ConsumerVal ($hash, $c, 'startTime', $newst)) / 60; # in Minuten ! (gettimeofday sind ms !) + $data{$name}{consumers}{$c}{lastMinutesOn} = 0; if ($day ne $startday) { # Tageswechsel - $data{$type}{$name}{consumers}{$c}{cycleDayNum} = 1; + $data{$name}{consumers}{$c}{cycleDayNum} = 1; } } } @@ -11357,14 +11288,14 @@ sub __getCyclesAndRuntime { $startday = strftime "%d", localtime(ConsumerVal ($hash, $c, 'startTime', 1)); # aktueller Tag (range 01 to 31) if ($chour ne $starthour) { # Stundenwechsel - $data{$type}{$name}{consumers}{$c}{minutesOn} = 0; + $data{$name}{consumers}{$c}{minutesOn} = 0; } if ($day ne $startday) { # Tageswechsel - $data{$type}{$name}{consumers}{$c}{cycleDayNum} = 0; + $data{$name}{consumers}{$c}{cycleDayNum} = 0; } - $data{$type}{$name}{consumers}{$c}{onoff} = 'off'; + $data{$name}{consumers}{$c}{onoff} = 'off'; } if ($debug =~ /consumerSwitching${c}/xs) { @@ -11411,11 +11342,11 @@ sub __remainConsumerTime { my ($planstate,$startstr,$stoptstr) = __getPlanningStateAndTimes ($paref); my $stopts = ConsumerVal ($hash, $c, 'planswitchoff', undef); # geplante Unix Stopzeit - $data{$type}{$name}{consumers}{$c}{remainTime} = 0; + $data{$name}{consumers}{$c}{remainTime} = 0; if (isInTimeframe($hash, $c) && (($planstate =~ /started/xs && isConsumerPhysOn($hash, $c)) | $planstate =~ /interrupt|continu/xs)) { my $remainTime = $stopts - $t ; - $data{$type}{$name}{consumers}{$c}{remainTime} = sprintf "%.0f", ($remainTime / 60) if($remainTime > 0); + $data{$name}{consumers}{$c}{remainTime} = sprintf "%.0f", ($remainTime / 60) if($remainTime > 0); } return; @@ -11437,8 +11368,8 @@ sub __setPhysLogSwState { my $cpo = isConsumerPhysOn ($hash, $c) ? 'on' : 'off'; my $clo = isConsumerLogOn ($hash, $c, $pcurr) ? 'on' : 'off'; - $data{$type}{$name}{consumers}{$c}{physoffon} = $cpo; - $data{$type}{$name}{consumers}{$c}{logoffon} = $clo; + $data{$name}{consumers}{$c}{physoffon} = $cpo; + $data{$name}{consumers}{$c}{logoffon} = $clo; if ($debug =~ /consumerSwitching${c}/xs) { my $ao = $befsw ? 'before switching' : 'after switching'; @@ -11508,7 +11439,7 @@ sub _estConsumptionForecast { my $dayname = $paref->{dayname}; # aktueller Tagname my $hash = $defs{$name}; - my $acref = $data{$type}{$name}{consumers}; + my $acref = $data{$name}{consumers}; my $swdfcfc = AttrVal ($name, 'affectConsForecastIdentWeekdays', 0); # nutze nur gleiche Wochentage (Mo...So) für Verbrauchsvorhersage ## Beachtung der letzten X Tage falls gesetzt @@ -11534,7 +11465,7 @@ sub _estConsumptionForecast { debugLog ($paref, 'consumption|consumption_long', "################### Consumption forecast for the next day ###################"); debugLog ($paref, 'consumption|consumption_long', "Date(s) to take note: ".join ',', @dtn) if(@dtn); - for my $n (sort{$a<=>$b} keys %{$data{$type}{$name}{pvhist}}) { + for my $n (sort{$a<=>$b} keys %{$data{$name}{pvhist}}) { next if ($n eq $day); # aktuellen (unvollständigen) Tag nicht berücksichtigen if ($swdfcfc) { # nur gleiche Tage (Mo...So) einbeziehen @@ -11574,13 +11505,13 @@ sub _estConsumptionForecast { if ($dnum) { my $tomavg = int ($totcon / $dnum); - $data{$type}{$name}{current}{tomorrowconsumption} = $tomavg; # prognostizierter Durchschnittsverbrauch aller (gleicher) Wochentage + $data{$name}{current}{tomorrowconsumption} = $tomavg; # prognostizierter Durchschnittsverbrauch aller (gleicher) Wochentage debugLog ($paref, 'consumption|consumption_long', "estimated Consumption for tomorrow: $tomavg, days for avg: $dnum"); } else { my $lang = $paref->{lang}; - $data{$type}{$name}{current}{tomorrowconsumption} = $hqtxt{wfmdcf}{$lang}; + $data{$name}{current}{tomorrowconsumption} = $hqtxt{wfmdcf}{$lang}; } ## Verbrauchsvorhersage für die kommenden Stunden @@ -11588,7 +11519,7 @@ sub _estConsumptionForecast { debugLog ($paref, 'consumption|consumption_long', "################### Consumption forecast for the next hours ###################"); debugLog ($paref, 'consumption|consumption_long', "Date(s) to take note: ".join ',', @dtn) if(@dtn); - for my $k (sort keys %{$data{$type}{$name}{nexthours}}) { + for my $k (sort keys %{$data{$name}{nexthours}}) { my $nhtime = NexthoursVal ($hash, $k, "starttime", undef); # Startzeit next if(!$nhtime); @@ -11608,7 +11539,7 @@ sub _estConsumptionForecast { my $nhday = strftime "%a", localtime($utime); # Wochentagsname des NextHours Key my $nhhr = sprintf("%02d", (int (strftime "%H", localtime($utime))) + 1); # Stunde des Tages vom NextHours Key (01,02,...24) - for my $m (sort{$a<=>$b} keys %{$data{$type}{$name}{pvhist}}) { + for my $m (sort{$a<=>$b} keys %{$data{$name}{pvhist}}) { next if($m eq $day); # next wenn gleicher Tag (Datum) wie heute if ($swdfcfc) { # nur gleiche Tage (Mo...So) einbeziehen @@ -11658,13 +11589,13 @@ sub _estConsumptionForecast { } if ($dnum) { - my $conavgex = int ($conhfcex->{$nhhr} / $dnum); - $data{$type}{$name}{nexthours}{$k}{confcEx} = $conavgex; - my $conavg = int ($conhfc->{$nhhr} / $dnum); - $data{$type}{$name}{nexthours}{$k}{confc} = $conavg; # Durchschnittsverbrauch aller gleicher Wochentage pro Stunde + my $conavgex = int ($conhfcex->{$nhhr} / $dnum); + $data{$name}{nexthours}{$k}{confcEx} = $conavgex; + my $conavg = int ($conhfc->{$nhhr} / $dnum); + $data{$name}{nexthours}{$k}{confc} = $conavg; # Durchschnittsverbrauch aller gleicher Wochentage pro Stunde if (NexthoursVal ($hash, $k, "today", 0)) { # nur Werte des aktuellen Tag speichern - $data{$type}{$name}{circular}{sprintf("%02d",$nhhr)}{confc} = $conavg; + $data{$name}{circular}{sprintf("%02d",$nhhr)}{confc} = $conavg; writeToHistory ( { paref => $paref, key => 'confc', val => $conavg, hour => $nhhr } ); } @@ -11732,7 +11663,7 @@ sub __evaluateArray { my @aa = (); @aa = @{$aaref} if (ref $aaref eq 'ARRAY'); - return if(scalar @aa < $slidenumdef); + return if(scalar @aa < $slidenummax); my $gen1 = $aa[0]; my $gen2 = $aa[1]; @@ -11769,7 +11700,7 @@ sub _calcReadingsTomorrowPVFc { my $type = $paref->{type}; my $hash = $defs{$name}; - my $h = $data{$type}{$name}{nexthours}; + my $h = $data{$name}{nexthours}; my $hods = AttrVal($name, 'ctrlNextDayForecastReadings', ''); return if(!keys %{$h} || !$hods); @@ -11826,7 +11757,7 @@ sub _calcTodayPVdeviation { $dp = sprintf "%.2f", (100 - (100 * $pvre / abs $pvfcd)); # V 1.25.0 } - $data{$type}{$name}{circular}{99}{tdayDvtn} = $dp; + $data{$name}{circular}{99}{tdayDvtn} = $dp; storeReading ('Today_PVdeviation', $dp.' %'); @@ -12100,18 +12031,18 @@ sub __calcNewFactor { if ($crang ne 'simple') { my $idx = $sabin.'.'.$crang; # value für pvcorrf Sonne Altitude - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$idx} = $pvrlsum; # PV Erzeugung Summe speichern - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$idx} = $pvfcsum; # PV Prognose Summe speichern - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$idx} = $dnum; # Anzahl aller historischen Tade dieser Range - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$idx} = $factor; - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{$idx} = $qual; + $data{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$idx} = $pvrlsum; # PV Erzeugung Summe speichern + $data{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$idx} = $pvfcsum; # PV Prognose Summe speichern + $data{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$idx} = $dnum; # Anzahl aller historischen Tade dieser Range + $data{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$idx} = $factor; + $data{$name}{circular}{sprintf("%02d",$h)}{quality}{$idx} = $qual; } else { - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$crang} = $pvrlsum; - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$crang} = $pvfcsum; - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$crang} = $dnum; - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$crang} = $factor; - $data{$type}{$name}{circular}{sprintf("%02d",$h)}{quality}{$crang} = $qual; + $data{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$crang} = $pvrlsum; + $data{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$crang} = $pvfcsum; + $data{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$crang} = $dnum; + $data{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$crang} = $factor; + $data{$name}{circular}{sprintf("%02d",$h)}{quality}{$crang} = $qual; } $oldfac = sprintf "%.2f", $oldfac; @@ -12347,7 +12278,7 @@ sub _genStatisticReadings { my $type = $paref->{type}; for my $strg (@allstrings) { - for my $starttmstr (sort keys %{$data{$type}{$name}{solcastapi}{$strg}}) { + for my $starttmstr (sort keys %{$data{$name}{solcastapi}{$strg}}) { next if($starttmstr !~ /$dayaftertomorrow/xs); my $val = &{$hcsr{$kpi}{fn}} ($hash, $strg, $starttmstr, $hcsr{$kpi}{par}, $def); @@ -12394,7 +12325,7 @@ sub _genStatisticReadings { if ($kpi eq 'todayConsumptionForecast') { my $type = $paref->{type}; - for my $idx (sort keys %{$data{$type}{$name}{nexthours}}) { + for my $idx (sort keys %{$data{$name}{nexthours}}) { my $istoday = NexthoursVal ($hash, $idx, 'today', 0); last if(!$istoday); @@ -12412,7 +12343,7 @@ sub _genStatisticReadings { my $hrs = 0; my $sttm = ''; - for my $idx (sort keys %{$data{$type}{$name}{nexthours}}) { + for my $idx (sort keys %{$data{$name}{nexthours}}) { my $don = NexthoursVal ($hash, $idx, 'DoN', 2); # Wechsel von 0 -> 1 relevant last if($don == 2); @@ -12737,7 +12668,7 @@ sub _checkSetupNotComplete { my ($coset, $lat, $lon) = locCoordinates(); # Koordinaten im global device my $rip; - $rip = 1 if(exists $data{$type}{$name}{statusapi}{'?IdPair'}); # es existiert mindestens ein Paar RoofTop-ID / API-Key + $rip = 1 if(exists $data{$name}{statusapi}{'?IdPair'}); # es existiert mindestens ein Paar RoofTop-ID / API-Key my $pv0 = NexthoursVal ($hash, 'NextHour00', 'pvfc', undef); # der erste PV ForeCast Wert @@ -12828,12 +12759,12 @@ sub _checkSetupNotComplete { $ret .= ""; $ret =~ s/LINK/$link/gxs; - delete $data{$type}{$name}{current}{setupcomplete}; + delete $data{$name}{current}{setupcomplete}; return $ret; } - $data{$type}{$name}{current}{setupcomplete} = 1; + $data{$name}{current}{setupcomplete} = 1; return; } @@ -13611,7 +13542,7 @@ sub ___ghoValForm { my $unit = $paref->{unit}; my $type = $paref->{type}; - my $fn = $data{$type}{$name}{func}{ghoValForm}; + my $fn = AttrVal ($name, 'graphicHeaderOwnspecValForm', ''); return ($val, $unit) if(!$fn || !$dev || !$rdg || !defined $val); @@ -13692,7 +13623,7 @@ sub _showConsumerInGraphicBeam { # get consumer list and display it in Graphics ################################################ - my @consumers = sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}; # definierte Verbraucher ermitteln + my @consumers = sort{$a<=>$b} keys %{$data{$name}{consumers}}; # definierte Verbraucher ermitteln for (@consumers) { next if(!$_); @@ -13755,7 +13686,7 @@ sub _graphicConsumerLegend { my $clink = $paref->{clink}; my $type = $paref->{type}; - my @consumers = sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}; # definierte Verbraucher ermitteln + my @consumers = sort{$a<=>$b} keys %{$data{$name}{consumers}}; # definierte Verbraucher ermitteln $clegend = '' if($clegendstyle eq 'none' || !int @consumers); $paref->{clegend} = $clegend; @@ -13850,10 +13781,12 @@ sub _graphicConsumerLegend { my ($planstate,$starttime,$stoptime,$supplmnt) = __getPlanningStateAndTimes ($paref); $supplmnt = '-' if(!$supplmnt); my ($iilt,$rlt) = isInLocktime ($paref); # Sperrzeit Status ermitteln - + my $mode = getConsumerPlanningMode ($hash, $c); # Planungsmode 'can' oder 'must' + my $pstate = $caicon eq "times" ? $hqtxt{pstate}{$lang} : $htitles{pstate}{$lang}; my $surplusinfo = isConsRcmd($hash, $c) ? $htitles{splus}{$lang} : $htitles{nosplus}{$lang}; + $pstate =~ s//$mode/xs; $pstate =~ s//$planstate/xs; $pstate =~ s//$supplmnt/xs; $pstate =~ s//$starttime/xs; @@ -14708,7 +14641,7 @@ sub _flowGraphic { ##################################### my $cnsmr = {}; # Hashref Consumer current power - for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { # definierte Verbraucher ermitteln + for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { # definierte Verbraucher ermitteln next if(isConsumerNoshow ($hash, $c) =~ /^[13]$/xs); # auszublendende Consumer nicht berücksichtigen $cnsmr->{$c}{p} = ReadingsNum ($name, "consumer${c}_currentPower", 0); $cnsmr->{$c}{ptyp} = 'consumer'; @@ -15669,24 +15602,24 @@ sub finishTrain { my $aitrainFinishTs = $paref->{aitrainLastFinishTs}; my $aiRulesNumber = $paref->{aiRulesNumber}; - delete $data{$type}{$name}{circular}{99}{aiRulesNumber}; + delete $data{$name}{circular}{99}{aiRulesNumber}; - $data{$type}{$name}{current}{aiAddedToTrain} = 0; - $data{$type}{$name}{current}{aicanuse} = $aicanuse; - $data{$type}{$name}{current}{aitrainstate} = $aitrainstate; - $data{$type}{$name}{current}{aiinitstate} = $aiinitstate if(defined $aiinitstate); - $data{$type}{$name}{circular}{99}{runTimeTrainAI} = $runTimeTrainAI if(defined $runTimeTrainAI); # !! in Circular speichern um zu persistieren, setTimeTracking speichert zunächst in Current !! - $data{$type}{$name}{circular}{99}{aitrainLastFinishTs} = $aitrainFinishTs if(defined $aitrainFinishTs); - $data{$type}{$name}{circular}{99}{aiRulesNumber} = $aiRulesNumber if(defined $aiRulesNumber); + $data{$name}{current}{aiAddedToTrain} = 0; + $data{$name}{current}{aicanuse} = $aicanuse; + $data{$name}{current}{aitrainstate} = $aitrainstate; + $data{$name}{current}{aiinitstate} = $aiinitstate if(defined $aiinitstate); + $data{$name}{circular}{99}{runTimeTrainAI} = $runTimeTrainAI if(defined $runTimeTrainAI); # !! in Circular speichern um zu persistieren, setTimeTracking speichert zunächst in Current !! + $data{$name}{circular}{99}{aitrainLastFinishTs} = $aitrainFinishTs if(defined $aitrainFinishTs); + $data{$name}{circular}{99}{aiRulesNumber} = $aiRulesNumber if(defined $aiRulesNumber); if ($aitrainstate eq 'ok') { - _readCacheFile ({ name => $name, - type => $type, - file => $aitrained.$name, - cachename => 'aitrained', - title => 'aiTrainedData' - } - ); + readCacheFile ({ name => $name, + type => $type, + file => $aitrained.$name, + cachename => 'aitrained', + title => 'aiTrainedData' + } + ); } $paref->{debug} = getDebug ($hash); @@ -15715,15 +15648,14 @@ sub abortTrain { delete($hash->{HELPER}{AIBLOCKRUNNING}); - $data{$type}{$name}{current}{aitrainstate} = 'Traing (Child) process timed out'; - $data{$type}{$name}{current}{aiAddedToTrain} = 0; + $data{$name}{current}{aitrainstate} = 'Traing (Child) process timed out'; + $data{$name}{current}{aiAddedToTrain} = 0; return; } ################################################################ -# KI Instanz(en) aus Raw Daten Hash -# $data{$type}{$name}{aidectree}{airaw} hinzufügen +# KI Instanz(en) aus Raw Daten Hash erzeugen ################################################################ sub aiAddInstance { ## no critic "not used" my $paref = shift; @@ -15739,9 +15671,9 @@ sub aiAddInstance { ## no critic "not used" return if($err); my $dtree = AiDetreeVal ($hash, 'object', undef); - $data{$type}{$name}{current}{aiAddedToTrain} = 0; + $data{$name}{current}{aiAddedToTrain} = 0; - for my $idx (sort keys %{$data{$type}{$name}{aidectree}{airaw}}) { + for my $idx (sort keys %{$data{$name}{aidectree}{airaw}}) { next if(!$idx); my $pvrl = AiRawdataVal ($hash, $idx, 'pvrl', undef); @@ -15770,19 +15702,19 @@ sub aiAddInstance { ## no critic "not used" 1; } or do { Log3 ($name, 1, "$name - aiAddInstance ERROR: $@"); - $data{$type}{$name}{current}{aiaddistate} = $@; + $data{$name}{current}{aiaddistate} = $@; return; }; - $data{$type}{$name}{current}{aiAddedToTrain}++; + $data{$name}{current}{aiAddedToTrain}++; debugLog ($paref, 'aiProcess', qq{AI Instance added $idx - hod: $hod, sunalt: $sunalt, rad1h: $rad1h, pvrl: $pvrl, wcc: $wcc, rr1c: $rr1c, temp: $temp}, 4); } - debugLog ($paref, 'aiProcess', "AI Instance add - ".$data{$type}{$name}{current}{aiAddedToTrain}." entities added for training ".(AttrVal ($name, 'verbose', 3) != 4 ? '(set verbose 4 for output more detail)' : '')); + debugLog ($paref, 'aiProcess', "AI Instance add - ".$data{$name}{current}{aiAddedToTrain}." entities added for training ".(AttrVal ($name, 'verbose', 3) != 4 ? '(set verbose 4 for output more detail)' : '')); - $data{$type}{$name}{aidectree}{object} = $dtree; - $data{$type}{$name}{current}{aiaddistate} = 'ok'; + $data{$name}{aidectree}{object} = $dtree; + $data{$name}{current}{aiaddistate} = 'ok'; if ($taa) { manageTrain ($paref); @@ -15842,11 +15774,11 @@ sub aiTrain { ## no critic "not used" $block ? return ($serial) : return \&finishTrain ($serial); }; - $data{$type}{$name}{aidectree}{aitrained} = $dtree; - $err = writeCacheToFile ($hash, 'aitrained', $aitrained.$name); + $data{$name}{aidectree}{aitrained} = $dtree; + $err = writeCacheToFile ($hash, 'aitrained', $aitrained.$name); if (!$err) { - debugLog ($paref, 'aiProcess', qq{AI trained number of entities: }. $data{$type}{$name}{current}{aiAddedToTrain}); + debugLog ($paref, 'aiProcess', qq{AI trained number of entities: }. $data{$name}{current}{aiAddedToTrain}); debugLog ($paref, 'aiProcess', qq{AI trained and saved data into file: }.$aitrained.$name); debugLog ($paref, 'aiProcess', qq{Training instances and their associated information where purged from the AI object}); } @@ -15863,7 +15795,7 @@ sub aiTrain { ## no critic "not used" ) , ""); - delete $data{$type}{$name}{current}{runTimeTrainAI}; + delete $data{$name}{current}{runTimeTrainAI}; $block ? return ($serial) : return \&finishTrain ($serial); @@ -16041,22 +15973,22 @@ sub aiInit { ## no critic "not used" my $hash = $defs{$name}; if (!isPrepared4AI ($hash)) { - delete $data{$type}{$name}{circular}{99}{aiRulesNumber}; - delete $data{$type}{$name}{circular}{99}{runTimeTrainAI}; - delete $data{$type}{$name}{circular}{99}{aitrainLastFinishTs}; + delete $data{$name}{circular}{99}{aiRulesNumber}; + delete $data{$name}{circular}{99}{runTimeTrainAI}; + delete $data{$name}{circular}{99}{aitrainLastFinishTs}; my $err = CurrentVal ($hash, 'aicanuse', ''); debugLog ($paref, 'aiProcess', $err); - $data{$type}{$name}{current}{aiinitstate} = $err; + $data{$name}{current}{aiinitstate} = $err; return $err; } my $dtree = new AI::DecisionTree ( verbose => 0, noise_mode => 'pick_best' ); - $data{$type}{$name}{aidectree}{object} = $dtree; - $data{$type}{$name}{current}{aiinitstate} = 'ok'; + $data{$name}{aidectree}{object} = $dtree; + $data{$name}{current}{aiinitstate} = 'ok'; Log3 ($name, 3, "$name - AI::DecisionTree initialized"); @@ -16076,12 +16008,12 @@ sub aiAddRawData { my $hash = $defs{$name}; - delete $data{$type}{$name}{current}{aitrawstate}; + delete $data{$name}{current}{aitrawstate}; my $err; my $dosave = 0; - for my $pvd (sort keys %{$data{$type}{$name}{pvhist}}) { + for my $pvd (sort keys %{$data{$name}{pvhist}}) { next if(!$pvd); if ($ood) { @@ -16090,7 +16022,7 @@ sub aiAddRawData { last if(int $pvd > int $day); - for my $hod (sort keys %{$data{$type}{$name}{pvhist}{$pvd}}) { + for my $hod (sort keys %{$data{$name}{pvhist}{$pvd}}) { next if(!$hod || $hod eq '99' || ($rho && $hod ne $rho)); my $pvrlvd = HistoryVal ($hash, $pvd, $hod, 'pvrlvd', 1); @@ -16118,14 +16050,14 @@ sub aiAddRawData { my $cbin = cloud2bin ($wcc); my $sabin = sunalt2bin ($sunalt); - $data{$type}{$name}{aidectree}{airaw}{$ridx}{rad1h} = $rad1h; - $data{$type}{$name}{aidectree}{airaw}{$ridx}{temp} = $tbin; - $data{$type}{$name}{aidectree}{airaw}{$ridx}{wcc} = $cbin; - $data{$type}{$name}{aidectree}{airaw}{$ridx}{rr1c} = $rr1c; - $data{$type}{$name}{aidectree}{airaw}{$ridx}{hod} = $hod; - $data{$type}{$name}{aidectree}{airaw}{$ridx}{pvrl} = $pvrl; - $data{$type}{$name}{aidectree}{airaw}{$ridx}{sunalt} = $sabin; - $data{$type}{$name}{aidectree}{airaw}{$ridx}{sunaz} = $sunaz; + $data{$name}{aidectree}{airaw}{$ridx}{rad1h} = $rad1h; + $data{$name}{aidectree}{airaw}{$ridx}{temp} = $tbin; + $data{$name}{aidectree}{airaw}{$ridx}{wcc} = $cbin; + $data{$name}{aidectree}{airaw}{$ridx}{rr1c} = $rr1c; + $data{$name}{aidectree}{airaw}{$ridx}{hod} = $hod; + $data{$name}{aidectree}{airaw}{$ridx}{pvrl} = $pvrl; + $data{$name}{aidectree}{airaw}{$ridx}{sunalt} = $sabin; + $data{$name}{aidectree}{airaw}{$ridx}{sunaz} = $sunaz; $dosave++; @@ -16139,7 +16071,7 @@ sub aiAddRawData { $err = writeCacheToFile ($hash, 'airaw', $airaw.$name); if (!$err) { - $data{$type}{$name}{current}{aitrawstate} = 'ok'; + $data{$name}{current}{aitrawstate} = 'ok'; debugLog ($paref, 'aiProcess', "AI raw data saved into file: ".$airaw.$name); } } @@ -16158,7 +16090,7 @@ sub aiDelRawData { my $hash = $defs{$name}; - if (!keys %{$data{$type}{$name}{aidectree}{airaw}}) { + if (!keys %{$data{$name}{aidectree}{airaw}}) { return; } @@ -16169,13 +16101,13 @@ sub aiDelRawData { debugLog ($paref, 'aiProcess', qq{AI Raw delete data equal or less than index >$didx<}); - delete $data{$type}{$name}{current}{aitrawstate}; + delete $data{$name}{current}{aitrawstate}; my ($err, $dosave); - for my $idx (sort keys %{$data{$type}{$name}{aidectree}{airaw}}) { + for my $idx (sort keys %{$data{$name}{aidectree}{airaw}}) { next if(!$idx || $idx > $didx); - delete $data{$type}{$name}{aidectree}{airaw}{$idx}; + delete $data{$name}{aidectree}{airaw}{$idx}; $dosave = 1; @@ -16186,7 +16118,7 @@ sub aiDelRawData { $err = writeCacheToFile ($hash, 'airaw', $airaw.$name); if (!$err) { - $data{$type}{$name}{current}{aitrawstate} = 'ok'; + $data{$name}{current}{aitrawstate} = 'ok'; debugLog ($paref, 'aiProcess', qq{AI raw data saved into file: }.$airaw.$name); } } @@ -16256,7 +16188,7 @@ sub setPVhistory { my $hash = $defs{$name}; - $data{$type}{$name}{pvhist}{$day}{99}{dayname} = $dayname if($day); + $data{$name}{pvhist}{$day}{99}{dayname} = $dayname if($day); if ($hfspvh{$histname} && defined &{$hfspvh{$histname}{fn}}) { &{$hfspvh{$histname}{fn}} ($paref); @@ -16264,12 +16196,12 @@ sub setPVhistory { } if ($histname =~ /csm[et][0-9]+$/xs) { # Verbrauch eines Verbrauchers - $data{$type}{$name}{pvhist}{$day}{$nhour}{$histname} = $val; + $data{$name}{pvhist}{$day}{$nhour}{$histname} = $val; if ($histname =~ /csme[0-9]+$/xs) { my $sum = 0; - for my $k (keys %{$data{$type}{$name}{pvhist}{$day}}) { + for my $k (keys %{$data{$name}{pvhist}{$day}}) { next if($k eq "99"); my $csme = HistoryVal ($hash, $day, $k, "$histname", 0); next if(!$csme); @@ -16277,16 +16209,16 @@ sub setPVhistory { $sum += $csme; } - $data{$type}{$name}{pvhist}{$day}{99}{$histname} = sprintf "%.2f", $sum; + $data{$name}{pvhist}{$day}{99}{$histname} = sprintf "%.2f", $sum; } } if ($histname =~ /minutescsm[0-9]+$/xs) { # Anzahl Aktivminuten des Verbrauchers - $data{$type}{$name}{pvhist}{$day}{$nhour}{$histname} = $val; + $data{$name}{pvhist}{$day}{$nhour}{$histname} = $val; my $minutes = 0; my $num = substr ($histname,10,2); - for my $k (keys %{$data{$type}{$name}{pvhist}{$day}}) { + for my $k (keys %{$data{$name}{pvhist}{$day}}) { next if($k eq "99"); my $csmm = HistoryVal ($hash, $day, $k, "$histname", 0); next if(!$csmm); @@ -16297,13 +16229,13 @@ sub setPVhistory { my $cycles = HistoryVal ($hash, $day, 99, "cyclescsm${num}", 0); if ($cycles) { - $data{$type}{$name}{pvhist}{$day}{99}{"hourscsme${num}"} = sprintf "%.2f", ($minutes / 60 ); - $data{$type}{$name}{pvhist}{$day}{99}{"avgcycmntscsm${num}"} = sprintf "%.2f", ($minutes / $cycles); + $data{$name}{pvhist}{$day}{99}{"hourscsme${num}"} = sprintf "%.2f", ($minutes / 60 ); + $data{$name}{pvhist}{$day}{99}{"avgcycmntscsm${num}"} = sprintf "%.2f", ($minutes / $cycles); } } if ($histname =~ /cyclescsm[0-9]+$/xs) { # Anzahl Tageszyklen des Verbrauchers - $data{$type}{$name}{pvhist}{$day}{99}{$histname} = $val; + $data{$name}{pvhist}{$day}{99}{$histname} = $val; } if ($reorg) { # Reorganisation Stunde "99" @@ -16316,7 +16248,7 @@ sub setPVhistory { my $ien = {}; # Hashref Inverter energy my $pen = {}; # Hashref Producer energy - for my $k (keys %{$data{$type}{$name}{pvhist}{$reorgday}}) { + for my $k (keys %{$data{$name}{pvhist}{$reorgday}}) { next if($k eq "99"); $r1 += HistoryVal ($hash, $reorgday, $k, 'batin', 0); @@ -16345,21 +16277,21 @@ sub setPVhistory { } } - $data{$type}{$name}{pvhist}{$reorgday}{99}{batin} = $r1; - $data{$type}{$name}{pvhist}{$reorgday}{99}{batout} = $r2; - $data{$type}{$name}{pvhist}{$reorgday}{99}{pvrl} = $r3; - $data{$type}{$name}{pvhist}{$reorgday}{99}{pvfc} = $r4; - $data{$type}{$name}{pvhist}{$reorgday}{99}{confc} = $r5; - $data{$type}{$name}{pvhist}{$reorgday}{99}{gcons} = $r6; - $data{$type}{$name}{pvhist}{$reorgday}{99}{gfeedin} = $r7; - $data{$type}{$name}{pvhist}{$reorgday}{99}{con} = $r8; + $data{$name}{pvhist}{$reorgday}{99}{batin} = $r1; + $data{$name}{pvhist}{$reorgday}{99}{batout} = $r2; + $data{$name}{pvhist}{$reorgday}{99}{pvrl} = $r3; + $data{$name}{pvhist}{$reorgday}{99}{pvfc} = $r4; + $data{$name}{pvhist}{$reorgday}{99}{confc} = $r5; + $data{$name}{pvhist}{$reorgday}{99}{gcons} = $r6; + $data{$name}{pvhist}{$reorgday}{99}{gfeedin} = $r7; + $data{$name}{pvhist}{$reorgday}{99}{con} = $r8; for my $in (keys %{$ien}) { - $data{$type}{$name}{pvhist}{$reorgday}{99}{'pvrl'.$in} = $ien->{$in}; + $data{$name}{pvhist}{$reorgday}{99}{'pvrl'.$in} = $ien->{$in}; } for my $pn (keys %{$pen}) { - $data{$type}{$name}{pvhist}{$reorgday}{99}{'pprl'.$pn} = $pen->{$pn}; + $data{$name}{pvhist}{$reorgday}{99}{'pprl'.$pn} = $pen->{$pn}; } debugLog ($paref, 'saveData2Cache', "setPVhistory -> Day >$reorgday< reorganized keys: batin, batout, pvrl, pvfc, con, confc, gcons, gfeedin, pvrlXX, pprlXX"); @@ -16388,12 +16320,12 @@ sub _storeVal { ## no critic "not used" my $store = $hfspvh{$histname}{storname}; my ($validkey, $validval); - $data{$type}{$name}{pvhist}{$day}{$nhour}{$store} = $val; + $data{$name}{pvhist}{$day}{$nhour}{$store} = $val; if (defined $hfspvh{$histname}{validkey}) { # 1: bestimmter Eintrag wird intern für Prozesse (z.B. Lernprozess) berücksichtigt oder nicht (0) $validkey = $hfspvh{$histname}{validkey}; $validval = $paref->{$validkey}; - $data{$type}{$name}{pvhist}{$day}{$nhour}{$validkey} = $validval; + $data{$name}{pvhist}{$day}{$nhour}{$validkey} = $validval; } debugLog ($paref, 'saveData2Cache', "setPVhistory -> stored simple - Day: $day, Hour: $nhour, Key: $store, Value: ".(defined $val ? $val : 'undef'). @@ -16401,12 +16333,12 @@ sub _storeVal { ## no critic "not used" if (defined $hfspvh{$histname}{fpar} && $hfspvh{$histname}{fpar} eq 'comp99') { my $sum = 0; - for my $k (keys %{$data{$type}{$name}{pvhist}{$day}}) { + for my $k (keys %{$data{$name}{pvhist}{$day}}) { next if($k eq '99'); $sum += HistoryVal ($hash, $day, $k, $store, 0); } - $data{$type}{$name}{pvhist}{$day}{99}{$store} = $sum; + $data{$name}{pvhist}{$day}{99}{$store} = $sum; debugLog ($paref, 'saveData2Cache', "setPVhistory -> stored compute - Day: $day, Hour: 99, Key: $store, Value: $sum"); } @@ -16439,7 +16371,7 @@ sub listDataPool { #for my $dh (keys %{$h->{$day}}) { # if (!isNumeric ($dh)) { - # delete $data{$type}{$name}{pvhist}{$day}{$dh}; + # delete $data{$name}{pvhist}{$day}{$dh}; # Log3 ($name, 2, qq{$name - INFO - invalid key "$day -> $dh" was deleted from pvHistory storage}); # } #} @@ -16447,31 +16379,31 @@ 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 $gcons = HistoryVal ($hash, $day, $key, 'gcons', '-'); - my $con = HistoryVal ($hash, $day, $key, 'con', '-'); - my $confc = HistoryVal ($hash, $day, $key, 'confc', '-'); - my $gfeedin = HistoryVal ($hash, $day, $key, 'gfeedin', '-'); - my $wid = HistoryVal ($hash, $day, $key, 'weatherid', '-'); - my $wcc = HistoryVal ($hash, $day, $key, 'wcc', '-'); - my $rr1c = HistoryVal ($hash, $day, $key, 'rr1c', '-'); - my $temp = HistoryVal ($hash, $day, $key, 'temp', undef); - my $pvcorrf = HistoryVal ($hash, $day, $key, 'pvcorrf', '-'); - my $dayname = HistoryVal ($hash, $day, $key, 'dayname', undef); - my $btotin = HistoryVal ($hash, $day, $key, 'batintotal', '-'); - my $batin = HistoryVal ($hash, $day, $key, 'batin', '-'); - my $btotout = HistoryVal ($hash, $day, $key, 'batouttotal', '-'); - my $batout = HistoryVal ($hash, $day, $key, 'batout', '-'); - my $batmsoc = HistoryVal ($hash, $day, $key, 'batmaxsoc', '-'); - my $batssoc = HistoryVal ($hash, $day, $key, 'batsetsoc', '-'); - my $rad1h = HistoryVal ($hash, $day, $key, 'rad1h', '-'); - my $sunaz = HistoryVal ($hash, $day, $key, 'sunaz', '-'); - my $sunalt = HistoryVal ($hash, $day, $key, 'sunalt', '-'); - my $don = HistoryVal ($hash, $day, $key, 'DoN', '-'); - my $conprc = HistoryVal ($hash, $day, $key, 'conprice', '-'); - my $feedprc = HistoryVal ($hash, $day, $key, 'feedprice', '-'); + my $pvrl = HistoryVal ($name, $day, $key, 'pvrl', '-'); + my $pvrlvd = HistoryVal ($name, $day, $key, 'pvrlvd', '-'); + my $pvfc = HistoryVal ($name, $day, $key, 'pvfc', '-'); + my $gcons = HistoryVal ($name, $day, $key, 'gcons', '-'); + my $con = HistoryVal ($name, $day, $key, 'con', '-'); + my $confc = HistoryVal ($name, $day, $key, 'confc', '-'); + my $gfeedin = HistoryVal ($name, $day, $key, 'gfeedin', '-'); + my $wid = HistoryVal ($name, $day, $key, 'weatherid', '-'); + my $wcc = HistoryVal ($name, $day, $key, 'wcc', '-'); + my $rr1c = HistoryVal ($name, $day, $key, 'rr1c', '-'); + my $temp = HistoryVal ($name, $day, $key, 'temp', undef); + my $pvcorrf = HistoryVal ($name, $day, $key, 'pvcorrf', '-'); + my $dayname = HistoryVal ($name, $day, $key, 'dayname', undef); + my $btotin = HistoryVal ($name, $day, $key, 'batintotal', '-'); + my $batin = HistoryVal ($name, $day, $key, 'batin', '-'); + my $btotout = HistoryVal ($name, $day, $key, 'batouttotal', '-'); + my $batout = HistoryVal ($name, $day, $key, 'batout', '-'); + my $batmsoc = HistoryVal ($name, $day, $key, 'batmaxsoc', '-'); + my $batssoc = HistoryVal ($name, $day, $key, 'batsetsoc', '-'); + my $rad1h = HistoryVal ($name, $day, $key, 'rad1h', '-'); + my $sunaz = HistoryVal ($name, $day, $key, 'sunaz', '-'); + my $sunalt = HistoryVal ($name, $day, $key, 'sunalt', '-'); + my $don = HistoryVal ($name, $day, $key, 'DoN', '-'); + my $conprc = HistoryVal ($name, $day, $key, 'conprice', '-'); + my $feedprc = HistoryVal ($name, $day, $key, 'feedprice', '-'); if ($export eq 'csv') { $hexp->{$day}{$key}{PVreal} = $pvrl; @@ -16510,8 +16442,8 @@ sub listDataPool { my ($inve, $invl); for my $in (1..$maxinverter) { # + alle Inverter $in = sprintf "%02d", $in; - my $etoti = HistoryVal ($hash, $day, $key, 'etotali'.$in, '-'); - my $pvrli = HistoryVal ($hash, $day, $key, 'pvrl'.$in, '-'); + my $etoti = HistoryVal ($name, $day, $key, 'etotali'.$in, '-'); + my $pvrli = HistoryVal ($name, $day, $key, 'pvrl'.$in, '-'); if ($export eq 'csv') { $hexp->{$day}{$key}{"Etotal${in}"} = $etoti; @@ -16537,8 +16469,8 @@ sub listDataPool { my ($prde, $prdl); for my $pn (1..$maxproducer) { # + alle Producer $pn = sprintf "%02d", $pn; - my $etotp = HistoryVal ($hash, $day, $key, 'etotalp'.$pn, '-'); - my $pprl = HistoryVal ($hash, $day, $key, 'pprl'.$pn, '-'); + my $etotp = HistoryVal ($name, $day, $key, 'etotalp'.$pn, '-'); + my $pprl = HistoryVal ($name, $day, $key, 'pprl'.$pn, '-'); if ($export eq 'csv') { $hexp->{$day}{$key}{"Etotal${pn}"} = $etotp; @@ -16587,12 +16519,12 @@ sub listDataPool { for my $c (1..$maxconsumer) { # + alle Consumer $c = sprintf "%02d", $c; my $nl = 0; - my $csmc = HistoryVal ($hash, $day, $key, "cyclescsm${c}", undef); - my $csmt = HistoryVal ($hash, $day, $key, "csmt${c}", undef); - my $csme = HistoryVal ($hash, $day, $key, "csme${c}", undef); - my $csmm = HistoryVal ($hash, $day, $key, "minutescsm${c}", undef); - my $csmh = HistoryVal ($hash, $day, $key, "hourscsme${c}", undef); - my $csma = HistoryVal ($hash, $day, $key, "avgcycmntscsm${c}", undef); + my $csmc = HistoryVal ($name, $day, $key, "cyclescsm${c}", undef); + my $csmt = HistoryVal ($name, $day, $key, "csmt${c}", undef); + my $csme = HistoryVal ($name, $day, $key, "csme${c}", undef); + my $csmm = HistoryVal ($name, $day, $key, "minutescsm${c}", undef); + my $csmh = HistoryVal ($name, $day, $key, "hourscsme${c}", undef); + my $csma = HistoryVal ($name, $day, $key, "avgcycmntscsm${c}", undef); if ($export eq 'csv') { $hexp->{$day}{$key}{"CyclesCsm${c}"} = $csmc if(defined $csmc); @@ -16653,7 +16585,7 @@ sub listDataPool { }; if ($htol eq "pvhist") { - $h = $data{$type}{$name}{pvhist}; + $h = $data{$name}{pvhist}; if (!keys %{$h}) { return qq{PV cache is empty.}; @@ -16661,7 +16593,7 @@ sub listDataPool { for my $i (keys %{$h}) { if (!isNumeric ($i)) { - delete $data{$type}{$name}{pvhist}{$i}; + delete $data{$name}{pvhist}{$i}; Log3 ($name, 2, qq{$name - INFO - invalid key "$i" was deleted from pvHistory storage}); } } @@ -16683,7 +16615,7 @@ sub listDataPool { $htol eq 'strings' ? \&StringVal : ''; - $h = $data{$type}{$name}{$htol}; + $h = $data{$name}{$htol}; if (!keys %{$h}) { return ucfirst($htol).qq{ cache is empty.}; @@ -16691,7 +16623,7 @@ sub listDataPool { for my $i (keys %{$h}) { if ($i !~ /^[0-9]{2}$/ix && $htol ne 'strings') { # bereinigen ungültige Position, Forum: https://forum.fhem.de/index.php/topic,117864.msg1173219.html#msg1173219 - delete $data{$type}{$name}{$htol}{$i}; + delete $data{$name}{$htol}{$i}; Log3 ($name, 2, qq{$name - INFO - invalid key "$i" was deleted from }.ucfirst($htol).qq{ storage}); } } @@ -16720,7 +16652,7 @@ sub listDataPool { } if ($htol eq "circular") { - $h = $data{$type}{$name}{circular}; + $h = $data{$name}{circular}; if (!keys %{$h}) { return qq{Circular cache is empty.}; } @@ -16810,32 +16742,33 @@ sub listDataPool { } if ($htol eq "nexthours") { - $h = $data{$type}{$name}{nexthours}; + $h = $data{$name}{nexthours}; + if (!keys %{$h}) { return qq{NextHours cache is empty.}; } for my $idx (sort keys %{$h}) { - my $nhts = NexthoursVal ($hash, $idx, 'starttime', '-'); - my $hod = NexthoursVal ($hash, $idx, 'hourofday', '-'); - my $today = NexthoursVal ($hash, $idx, 'today', '-'); - my $pvfc = NexthoursVal ($hash, $idx, 'pvfc', '-'); - my $pvapifc = NexthoursVal ($hash, $idx, 'pvapifc', '-'); # PV Forecast der API - my $pvaifc = NexthoursVal ($hash, $idx, 'pvaifc', '-'); # PV Forecast der KI - my $aihit = NexthoursVal ($hash, $idx, 'aihit', '-'); # KI ForeCast Treffer Status - my $wid = NexthoursVal ($hash, $idx, 'weatherid', '-'); - my $wcc = NexthoursVal ($hash, $idx, 'wcc', '-'); - my $crang = NexthoursVal ($hash, $idx, 'cloudrange', '-'); - my $rr1c = NexthoursVal ($hash, $idx, 'rr1c', '-'); - my $rrange = NexthoursVal ($hash, $idx, 'rainrange', '-'); - my $rad1h = NexthoursVal ($hash, $idx, 'rad1h', '-'); - my $pvcorrf = NexthoursVal ($hash, $idx, 'pvcorrf', '-'); - my $temp = NexthoursVal ($hash, $idx, 'temp', '-'); - my $confc = NexthoursVal ($hash, $idx, 'confc', '-'); - my $confcex = NexthoursVal ($hash, $idx, 'confcEx', '-'); - my $don = NexthoursVal ($hash, $idx, 'DoN', '-'); - my $sunaz = NexthoursVal ($hash, $idx, 'sunaz', '-'); - my $sunalt = NexthoursVal ($hash, $idx, 'sunalt', '-'); + my $nhts = NexthoursVal ($name, $idx, 'starttime', '-'); + my $hod = NexthoursVal ($name, $idx, 'hourofday', '-'); + my $today = NexthoursVal ($name, $idx, 'today', '-'); + my $pvfc = NexthoursVal ($name, $idx, 'pvfc', '-'); + my $pvapifc = NexthoursVal ($name, $idx, 'pvapifc', '-'); # PV Forecast der API + my $pvaifc = NexthoursVal ($name, $idx, 'pvaifc', '-'); # PV Forecast der KI + my $aihit = NexthoursVal ($name, $idx, 'aihit', '-'); # KI ForeCast Treffer Status + my $wid = NexthoursVal ($name, $idx, 'weatherid', '-'); + my $wcc = NexthoursVal ($name, $idx, 'wcc', '-'); + my $crang = NexthoursVal ($name, $idx, 'cloudrange', '-'); + my $rr1c = NexthoursVal ($name, $idx, 'rr1c', '-'); + my $rrange = NexthoursVal ($name, $idx, 'rainrange', '-'); + my $rad1h = NexthoursVal ($name, $idx, 'rad1h', '-'); + my $pvcorrf = NexthoursVal ($name, $idx, 'pvcorrf', '-'); + my $temp = NexthoursVal ($name, $idx, 'temp', '-'); + my $confc = NexthoursVal ($name, $idx, 'confc', '-'); + my $confcex = NexthoursVal ($name, $idx, 'confcEx', '-'); + my $don = NexthoursVal ($name, $idx, 'DoN', '-'); + my $sunaz = NexthoursVal ($name, $idx, 'sunaz', '-'); + my $sunalt = NexthoursVal ($name, $idx, 'sunalt', '-'); $sq .= "\n" if($sq); $sq .= $idx." => "; @@ -16852,7 +16785,7 @@ sub listDataPool { } if ($htol eq "qualities") { - $h = $data{$type}{$name}{nexthours}; + $h = $data{$name}{nexthours}; if (!keys %{$h}) { return qq{NextHours cache is empty.}; } @@ -16874,7 +16807,8 @@ sub listDataPool { } if ($htol eq "current") { - $h = $data{$type}{$name}{current}; + $h = $data{$name}{current}; + if (!keys %{$h}) { return qq{Current values cache is empty.}; } @@ -16937,9 +16871,9 @@ sub listDataPool { }; if ($htol =~ /radiationApiData|weatherApiData|statusApiData/xs) { - $h = $data{$type}{$name}{solcastapi}; - $h = $data{$type}{$name}{weatherapi} if($htol eq 'weatherApiData'); - $h = $data{$type}{$name}{statusapi} if($htol eq 'statusApiData'); + $h = $data{$name}{solcastapi}; + $h = $data{$name}{weatherapi} if($htol eq 'weatherApiData'); + $h = $data{$name}{statusapi} if($htol eq 'statusApiData'); if (!keys %{$h}) { return qq{The API values cache is empty.}; @@ -16976,7 +16910,7 @@ sub listDataPool { } if ($htol eq "aiRawData") { - $h = $data{$type}{$name}{aidectree}{airaw}; + $h = $data{$name}{aidectree}{airaw}; my $maxcnt = keys %{$h}; if (!$maxcnt) { return qq{aiRawData values cache is empty.}; @@ -17153,9 +17087,9 @@ sub checkPlantConfig { my $string = shift; my $ret; - for my $key (sort keys %{$data{$type}{$name}{strings}{$string}}) { + for my $key (sort keys %{$data{$name}{strings}{$string}}) { $ret .= ", " if($ret); - $ret .= $key.": ".$data{$type}{$name}{strings}{$string}{$key}; + $ret .= $key.": ".$data{$name}{strings}{$string}{$key}; } return $ret; @@ -17163,7 +17097,7 @@ sub checkPlantConfig { ## Check Strings ################## - my $err = createStringConfig ($hash); + my $err = _createStringConfig ($hash); if ($err) { $result->{'String Configuration'}{state} = $nok; @@ -17171,12 +17105,12 @@ sub checkPlantConfig { $result->{'String Configuration'}{fault} = 1; } - for my $sn (sort keys %{$data{$type}{$name}{strings}}) { + for my $sn (sort keys %{$data{$name}{strings}}) { my $sp = $sn." => ".$sub->($sn)."
"; $result->{'String Configuration'}{note} .= $sn." => ".$sub->($sn)."
"; - if ($data{$type}{$name}{strings}{$sn}{peak} >= 500) { + if ($data{$name}{strings}{$sn}{peak} >= 500) { $result->{'String Configuration'}{result} .= qq{The peak value of string "$sn" is very high. }; $result->{'String Configuration'}{result} .= qq{It seems to be given in Wp instead of kWp.
}; $result->{'String Configuration'}{state} = $warn; @@ -17367,7 +17301,7 @@ sub checkPlantConfig { my $rtid = StatusAPIVal ($hash, '?IdPair', '?'.$pk, 'rtid', ''); my $apikey = StatusAPIVal ($hash, '?IdPair', '?'.$pk, 'apikey', ''); - if(!$rtid || !$apikey) { + if (!$rtid || !$apikey) { my $res = qq{String "$is" has no Roof Ident Pair "$pk" defined or has no Rooftop-ID and/or SolCast-API key assigned.
}; my $note = qq{Set the Roof Ident Pair "$pk" with "set $name roofIdentPair".
}; @@ -17376,11 +17310,14 @@ sub checkPlantConfig { $result->{'Roof Ident Pair Settings'}{note} .= $note; $result->{'Roof Ident Pair Settings'}{fault} = 1; } - else { - $result->{'Roof Ident Pair Settings'}{result} = $hqtxt{fulfd}{$lang} if(!$result->{'Roof Ident Pair Settings'}{fault}); + else { $result->{'Roof Ident Pair Settings'}{note} .= qq{checked "$is" Roof Ident Pair "$pk":
rtid=$rtid, apikey=$apikey
}; } } + + if (!$result->{'Roof Ident Pair Settings'}{fault}) { + $result->{'Roof Ident Pair Settings'}{result} = $hqtxt{fulfd}{$lang}; + } } ## Allgemeine Settings (auch API spezifisch) @@ -17721,23 +17658,139 @@ sub checkPlantConfig { return $out; } +##################################################################### +# Ermittelt den PV Überschuß nach verschiedenen Verfahren +# ($surpmeth). Auswertung des Schieberegisters surplusslidereg. +# +# $surpmeth = default - der aktuell gemessene Überschuß +# $surpmeth = 2 .. 20 - Durchschnitt der letzten X Messungen +# $surpmeth = median - Median der vorhandenen Überschußwerte +# $surpmeth = : - Device/Reading Kombination +# die einen berechneten +# User spezifischen PV-Überschuß +# liefert +# +# Rückgabe: PV Überschuß +# +##################################################################### +sub determSurplus { + my $hash = shift; + my $c = shift; + + my $surpmeth = ConsumerVal ($hash, $c, 'surpmeth', 'default'); + my $splref = CurrentVal ($hash, 'surplusslidereg', ''); + my $name = $hash->{NAME}; + my $method = 'default'; + + my ($surplus, $fallback); + + if ($surpmeth eq 'median') { # Median der Werte in surplusslidereg, !kann UNDEF sein! + $surplus = medianArray ($splref); + $method = 'median'; + } + elsif ($surpmeth eq 'default') { # aktueller Energieüberschuß + $surplus = CurrentVal ($hash, 'surplus', 0); + $method = 'default'; + } + elsif ($surpmeth =~ /^[2-9]$|^1[0-9]$|^20$/xs) { + $surplus = avgArray ($splref, $surpmeth); # Average Ermittlung, !kann UNDEF sein! + $method = "average:$surpmeth"; + } + elsif ($surpmeth =~ /.*:.*/xs) { + my ($dv, $rd) = split ':', $surpmeth; + $method = "$dv:$rd"; + my ($err) = isDeviceValid ( { name => $name, obj => $dv, method => 'string' } ); + + if ($err) { + $fallback = 1; + Log3 ($name, 1, qq{$name - ERROR of consumer $c key 'surpmeth': $err (fall back to default Surplus determination)}); + } + else { + $surplus = ReadingsNum ($dv, $rd, ''); + + if (!isNumeric ($surplus)) { + $fallback = 1; + Log3 ($name, 1, qq{$name - ERROR of consumer $c key 'surpmeth': Device $dv / Reading $rd is not numeric (fall back to default Surplus determination)}); + } + } + } + + if (!defined $surplus || $fallback) { # Fehlerkorrektur + $surplus = CurrentVal ($hash, 'surplus', 0); + $method = $method." but fallback to 'default'"; + } + +return ($method, $surplus); +} + ################################################################ # Array auf eine festgelegte Anzahl Elemente beschränken, # Das älteste Element wird entfernt # -# $href = Referenz zum Array +# $aref = Referenz zum Array # $limit = die Anzahl Elemente auf die gekürzt werden soll -# (default 3) +# (default $slidenummax) # ################################################################ sub limitArray { - my $href = shift; - my $limit = shift // 3; + my $aref = shift; + my $limit = shift // $slidenummax; - return if(ref $href ne "ARRAY"); + return if(ref $aref ne 'ARRAY'); - while (scalar @{$href} > $limit) { - shift @{$href}; + while (scalar @{$aref} > $limit) { + shift @{$aref}; + } + +return; +} + +################################################################ +# Durchschnitt der Werte eines Array ermitteln +# +# $aref = Referenz zum Array +# $num = Anzahl der zu verwendenden Elemente +# (es MÜSSEN die num Anzahl +# Elemente im Array vorhanden sein) +# +################################################################ +sub avgArray { + my $aref = shift; + my $num = shift // $slidenummax; + + return undef if(ref $aref ne 'ARRAY' || scalar @{$aref} < $num); + + my $sum = 0; + + for my $i (0 .. $num-1) { + $sum += ${$aref}[$i]; + } + + my $avg = $sum / $num; + +return $avg; +} + +###################################################################################### +# Median der Werte eines Array ermitteln +# (https://www.ionos.de/digitalguide/online-marketing/web-analyse/median-berechnen/) +# +# $aref = Referenz zum Array +# +###################################################################################### +sub medianArray { + my $aref = shift; + + return undef if(ref $aref ne 'ARRAY' || !scalar @{$aref}); + + my $enum = scalar @{$aref}; # Anzahl der Elemente im Array + my @sorted = sortTopicNum ('asc', @{$aref}); + + if ($enum % 2) { # Array enthält ungerade Anzahl Elemente + return $sorted[$enum/2]; # ungerade Elemente -> Median Element steht in der Mitte von @sorted + } + else { + return ($sorted[$enum/2 - 1] + $sorted[$enum/2]) / 2; # gerade Elemente -> Median ist der Durchschnitt der beiden mittleren Elemente } return; @@ -17914,34 +17967,11 @@ return; ################################################################ sub releaseCentralTask { my $hash = shift; + my $name = $hash->{NAME}; 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 + $data{$name}{current}{ctrunning} = 0; return; } @@ -18022,7 +18052,7 @@ sub createAssociatedWith { $badev = $aba->[0] // ''; push @cd, $badev; - for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { # Consumer Devices + for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { # Consumer Devices my $consumer = AttrVal ($name, "consumer${c}", ""); my ($ac,$hc) = parseParams ($consumer); my $codev = $ac->[0] // ''; @@ -18130,12 +18160,12 @@ sub deleteConsumerPlanning { my $name = $hash->{NAME}; my $calias = ConsumerVal ($hash, $c, "alias", ""); - delete $data{$type}{$name}{consumers}{$c}{planstate}; - delete $data{$type}{$name}{consumers}{$c}{planSupplement}; - delete $data{$type}{$name}{consumers}{$c}{planswitchon}; - delete $data{$type}{$name}{consumers}{$c}{planswitchoff}; - delete $data{$type}{$name}{consumers}{$c}{plandelete}; - delete $data{$type}{$name}{consumers}{$c}{ehodpieces}; + delete $data{$name}{consumers}{$c}{planstate}; + delete $data{$name}{consumers}{$c}{planSupplement}; + delete $data{$name}{consumers}{$c}{planswitchon}; + delete $data{$name}{consumers}{$c}{planswitchoff}; + delete $data{$name}{consumers}{$c}{plandelete}; + delete $data{$name}{consumers}{$c}{ehodpieces}; deleteReadingspec ($hash, "consumer${c}.*"); @@ -18144,6 +18174,20 @@ sub deleteConsumerPlanning { return; } +################################################################ +# Steuerparameter berechnen / festlegen +################################################################ +sub controller { + my $name = shift; + + 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); +} + ################################################################ # Internal MODEL und Model abhängige Setzungen / Löschungen ################################################################ @@ -18181,7 +18225,7 @@ sub setTimeTracking { my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - $data{$type}{$name}{current}{$tkn} = sprintf "%.4f", tv_interval($st); + $data{$name}{current}{$tkn} = sprintf "%.4f", tv_interval($st); return; } @@ -18211,11 +18255,11 @@ sub isPrepared4AI { } if ($err) { - $data{$type}{$name}{current}{aicanuse} = $err; + $data{$name}{current}{aicanuse} = $err; return 0; } - $data{$type}{$name}{current}{aicanuse} = 'ok'; + $data{$name}{current}{aicanuse} = 'ok'; return 1; } @@ -18312,7 +18356,7 @@ sub isConsumerLogOn { my $currpowerpercent = $pcurr; $currpowerpercent = ($pcurr / $nompower) * 100 if($nompower > 0); - $data{$type}{$name}{consumers}{$c}{currpowerpercent} = $currpowerpercent; + $data{$name}{consumers}{$c}{currpowerpercent} = $currpowerpercent; if ($pcurr > $pthreshold || $currpowerpercent > $defpopercent) { # Verbraucher ist logisch aktiv return 1; @@ -18859,6 +18903,19 @@ sub isWeatherDevValid { return ($valid, $fcname, $apiu); } +################################################################ +# Inhalt des Attr graphicHeaderOwnspecValForm validieren +################################################################ +sub isGhoValFormValid { + my $name = shift; + my $code = shift; + + my $err = q{}; + ($err, $code) = checkCode ($name, $code); + +return $err; +} + ################################################################### # püft das Alter fc_time aller Wetterdevices # $result->{agedv} : Name des DWD mit ältesten Daten @@ -19367,10 +19424,10 @@ return $dstr; # Daten Serialisieren ############################################################### sub Serialize { - my $data = shift; - my $name = $data->{name}; + my $dat = shift; + my $name = $dat->{name}; - my $serial = eval { freeze ($data) + my $serial = eval { freeze ($dat) } or do { Log3 ($name, 1, "$name - Serialization ERROR: $@"); return; @@ -19471,7 +19528,7 @@ return; ############################################################################### # Wert des pvhist-Hash zurückliefern # Usage: -# HistoryVal ($hash, $day, $hod, $key, $def) +# HistoryVal ($hash or $name, $day, $hod, $key, $def) # # $day: Tag des Monats (01,02,...,31) # $hod: Stunde des Tages (01,02,...,24,99) @@ -19504,20 +19561,21 @@ return; # ############################################################################### sub HistoryVal { - my $hash = shift; + my $name = shift; my $day = shift; my $hod = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if(defined($data{$type}{$name}{pvhist}) && - defined($data{$type}{$name}{pvhist}{$day}) && - defined($data{$type}{$name}{pvhist}{$day}{$hod}) && - defined($data{$type}{$name}{pvhist}{$day}{$hod}{$key})) { - return $data{$type}{$name}{pvhist}{$day}{$hod}{$key}; + if (defined($data{$name}{pvhist}) && + defined($data{$name}{pvhist}{$day}) && + defined($data{$name}{pvhist}{$day}{$hod}) && + defined($data{$name}{pvhist}{$day}{$hod}{$key})) { + return $data{$name}{pvhist}{$day}{$hod}{$key}; } return $def; @@ -19529,7 +19587,7 @@ return $def; # zwingend eine Beziehung zueinander !! # # Usage: -# CircularVal ($hash, $hod, $key, $def) +# CircularVal ($hash or $name, $hod, $key, $def) # # $hod: Stunde des Tages (01,02,...,24) bzw. 99 (besondere Verwendung) # $key: pvrl - realer PV Ertrag @@ -19568,29 +19626,30 @@ return $def; # ##################################################################################################### sub CircularVal { - my $hash = shift; + my $name = shift; my $hod = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if(defined($data{$type}{$name}{circular}) && - defined($data{$type}{$name}{circular}{$hod}) && - defined($data{$type}{$name}{circular}{$hod}{$key})) { - return $data{$type}{$name}{circular}{$hod}{$key}; + if(defined($data{$name}{circular}) && + defined($data{$name}{circular}{$hod}) && + defined($data{$name}{circular}{$hod}{$key})) { + return $data{$name}{circular}{$hod}{$key}; } return $def; } -################################################################### +######################################################################################################## # Wert des Autokorrekturfaktors # für eine bestimmte Sun Altitude-Range aus dem Circular-Hash # zurückliefern # Usage: -# $f = CircularSunCloudkorrVal ($hash, $hod, $sabin, $crang, $def) +# $f = CircularSunCloudkorrVal ($hash or $name, $hod, $sabin, $crang, $def) # # $f: Korrekturfaktor f. Stunde des Tages # @@ -19599,16 +19658,18 @@ return $def; # $crang: Bewölkung Bin (0..100) oder "simple" # $def: Defaultwert # -################################################################### +######################################################################################################## sub CircularSunCloudkorrVal { - my $hash = shift; + my $name = shift; my $hod = shift; my $sabin = shift; my $crang = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } + my $corrf = $def; my $qual = $def; my $idx = 'simple'; @@ -19617,18 +19678,18 @@ sub CircularSunCloudkorrVal { $idx = $sabin.'.'.$crang; } - if (defined($data{$type}{$name}{circular}) && - defined($data{$type}{$name}{circular}{$hod}) && - defined($data{$type}{$name}{circular}{$hod}{pvcorrf}) && - defined($data{$type}{$name}{circular}{$hod}{pvcorrf}{$idx})) { - $corrf = $data{$type}{$name}{circular}{$hod}{pvcorrf}{$idx}; + if (defined ($data{$name}{circular}) && + defined ($data{$name}{circular}{$hod}) && + defined ($data{$name}{circular}{$hod}{pvcorrf}) && + defined ($data{$name}{circular}{$hod}{pvcorrf}{$idx})) { + $corrf = $data{$name}{circular}{$hod}{pvcorrf}{$idx}; } - if (defined($data{$type}{$name}{circular}) && - defined($data{$type}{$name}{circular}{$hod}) && - defined($data{$type}{$name}{circular}{$hod}{quality}) && - defined($data{$type}{$name}{circular}{$hod}{quality}{$idx})) { - $qual = $data{$type}{$name}{circular}{$hod}{quality}{$idx}; + if (defined ($data{$name}{circular}) && + defined ($data{$name}{circular}{$hod}) && + defined ($data{$name}{circular}{$hod}{quality}) && + defined ($data{$name}{circular}{$hod}{quality}{$idx})) { + $qual = $data{$name}{circular}{$hod}{quality}{$idx}; } return ($corrf, $qual); @@ -19638,7 +19699,7 @@ return ($corrf, $qual); # Die durchschnittliche reale PV Erzeugung, PV Prognose und Tage # einer bestimmten Bewölkungs-Range aus dem circular-Hash zurückliefern # Usage: -# ($pvrlsum, $pvfcsum, $dnumsum) = CircularSumVal ($hash, $hod, $sabin, $crang, $def) +# ($pvrlsum, $pvfcsum, $dnumsum) = CircularSumVal ($hash or $name, $hod, $sabin, $crang, $def) # # $pvrlsum: Summe reale PV Erzeugung pro Bewölkungsbereich über die gesamte Laufzeit # $pvfcsum: Summe PV Prognose pro Bewölkungsbereich über die gesamte Laufzeit @@ -19651,14 +19712,15 @@ return ($corrf, $qual); # ####################################################################################################### sub CircularSumVal { - my $hash = shift; + my $name = shift; my $hod = shift; my $sabin = shift; my $crang = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } my $pvrlsum = $def; my $pvfcsum = $def; @@ -19669,25 +19731,25 @@ sub CircularSumVal { $idx = $sabin.'.'.$crang; } - if (defined($data{$type}{$name}{circular}) && - defined($data{$type}{$name}{circular}{$hod}) && - defined($data{$type}{$name}{circular}{$hod}{pvrlsum}) && - defined($data{$type}{$name}{circular}{$hod}{pvrlsum}{$idx})) { - $pvrlsum = $data{$type}{$name}{circular}{$hod}{pvrlsum}{$idx}; + if (defined ($data{$name}{circular}) && + defined ($data{$name}{circular}{$hod}) && + defined ($data{$name}{circular}{$hod}{pvrlsum}) && + defined ($data{$name}{circular}{$hod}{pvrlsum}{$idx})) { + $pvrlsum = $data{$name}{circular}{$hod}{pvrlsum}{$idx}; } - if (defined($data{$type}{$name}{circular}) && - defined($data{$type}{$name}{circular}{$hod}) && - defined($data{$type}{$name}{circular}{$hod}{pvfcsum}) && - defined($data{$type}{$name}{circular}{$hod}{pvfcsum}{$idx})) { - $pvfcsum = $data{$type}{$name}{circular}{$hod}{pvfcsum}{$idx}; + if (defined ($data{$name}{circular}) && + defined ($data{$name}{circular}{$hod}) && + defined ($data{$name}{circular}{$hod}{pvfcsum}) && + defined ($data{$name}{circular}{$hod}{pvfcsum}{$idx})) { + $pvfcsum = $data{$name}{circular}{$hod}{pvfcsum}{$idx}; } - if (defined($data{$type}{$name}{circular}) && - defined($data{$type}{$name}{circular}{$hod}) && - defined($data{$type}{$name}{circular}{$hod}{dnumsum}) && - defined($data{$type}{$name}{circular}{$hod}{dnumsum}{$idx})) { - $dnumsum = $data{$type}{$name}{circular}{$hod}{dnumsum}{$idx}; + if (defined ($data{$name}{circular}) && + defined ($data{$name}{circular}{$hod}) && + defined ($data{$name}{circular}{$hod}{dnumsum}) && + defined ($data{$name}{circular}{$hod}{dnumsum}{$idx})) { + $dnumsum = $data{$name}{circular}{$hod}{dnumsum}{$idx}; } return ($pvrlsum, $pvfcsum, $dnumsum); @@ -19696,7 +19758,7 @@ return ($pvrlsum, $pvfcsum, $dnumsum); ######################################################################################### # Wert des nexthours-Hash zurückliefern # Usage: -# NexthoursVal ($hash, $nhr, $key, $def) +# NexthoursVal ($hash or $name, $nhr, $key, $def) # # $nhr: nächste Stunde (NextHour00, NextHour01,...) # $key: starttime - Startzeit der abgefragten nächsten Stunde @@ -19718,18 +19780,19 @@ return ($pvrlsum, $pvfcsum, $dnumsum); # ######################################################################################### sub NexthoursVal { - my $hash = shift; + my $name = shift; my $nhr = shift; my $key = shift; my $def = shift; + + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - - if(defined($data{$type}{$name}{nexthours}) && - defined($data{$type}{$name}{nexthours}{$nhr}) && - defined($data{$type}{$name}{nexthours}{$nhr}{$key})) { - return $data{$type}{$name}{nexthours}{$nhr}{$key}; + if (defined ($data{$name}{nexthours}) && + defined ($data{$name}{nexthours}{$nhr}) && + defined ($data{$name}{nexthours}{$nhr}{$key})) { + return $data{$name}{nexthours}{$nhr}{$key}; } return $def; @@ -19738,7 +19801,7 @@ return $def; ################################################################################################### # Wert des current-Hash zurückliefern # Usage: -# CurrentVal ($hash, $key, $def) +# CurrentVal ($hash or $name, $key, $def) # # $key: aiinitstate - Initialisierungsstatus der KI # aitrainstate - Traisningsstatus der KI @@ -19750,6 +19813,7 @@ return $def; # dwdRad1hAgeTS - Alter des Rad1h Wertes als Unix Timestamp # genslidereg - Schieberegister PV Erzeugung (Array) # h4fcslidereg - Schieberegister 4h PV Forecast (Array) +# surplusslidereg - Schieberegister PV Überschuß (Array) # moonPhaseI - aktuelle Mondphase (1 .. 8) # socslidereg - Schieberegister Batterie SOC (Array) # consumption - aktueller Verbrauch (W) @@ -19773,16 +19837,17 @@ return $def; # ################################################################################################### sub CurrentVal { - my $hash = shift; + my $name = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if (defined $data{$type}{$name}{current} && - defined $data{$type}{$name}{current}{$key}) { - return $data{$type}{$name}{current}{$key}; + if (defined $data{$name}{current} && + defined $data{$name}{current}{$key}) { + return $data{$name}{current}{$key}; } return $def; @@ -19791,7 +19856,7 @@ return $def; ################################################################################################### # Wert des String Hash zurückliefern # Usage: -# StringVal ($hash, $strg, $key, $def) +# StringVal ($hash or $name, $strg, $key, $def) # # $strg: - Name des Strings aus setupInverterStrings # $key: peak - Peakleistung aus setupStringPeak @@ -19803,18 +19868,19 @@ return $def; # ################################################################################################### sub StringVal { - my $hash = shift; + my $name = shift; my $strg = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if (defined $data{$type}{$name}{strings} && - defined $data{$type}{$name}{strings}{$strg} && - defined $data{$type}{$name}{strings}{$strg}{$key}) { - return $data{$type}{$name}{strings}{$strg}{$key}; + if (defined $data{$name}{strings} && + defined $data{$name}{strings}{$strg} && + defined $data{$name}{strings}{$strg}{$key}) { + return $data{$name}{strings}{$strg}{$key}; } return $def; @@ -19823,7 +19889,7 @@ return $def; ################################################################################################### # Wert AI::DecisionTree Objects zurückliefern # Usage: -# AiDetreeVal ($hash, key, $def) +# AiDetreeVal ($hash or $name, key, $def) # # key: object - das AI Object # aitrained - AI trainierte Daten @@ -19833,16 +19899,17 @@ return $def; # ################################################################################################### sub AiDetreeVal { - my $hash = shift; + my $name = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if (defined $data{$type}{$name}{aidectree} && - defined $data{$type}{$name}{aidectree}{$key}) { - return $data{$type}{$name}{aidectree}{$key}; + if (defined $data{$name}{aidectree} && + defined $data{$name}{aidectree}{$key}) { + return $data{$name}{aidectree}{$key}; } return $def; @@ -19851,7 +19918,7 @@ return $def; ################################################################################################### # Wert AI Raw Data zurückliefern # Usage: -# AiRawdataVal ($hash, $idx, $key, $def) +# AiRawdataVal ($hash or $name, $idx, $key, $def) # AiRawdataVal ($hash, '', '', $def) -> den gesamten Hash airaw lesen # # $idx: - Index @@ -19867,24 +19934,25 @@ return $def; # ################################################################################################### sub AiRawdataVal { - my $hash = shift; + my $name = shift; my $idx = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } if (!$idx && !$key) { - if (defined $data{$type}{$name}{aidectree}{airaw}) { - return $data{$type}{$name}{aidectree}{airaw}; + if (defined $data{$name}{aidectree}{airaw}) { + return $data{$name}{aidectree}{airaw}; } } - if (defined $data{$type}{$name}{aidectree}{airaw} && - defined $data{$type}{$name}{aidectree}{airaw}{$idx} && - defined $data{$type}{$name}{aidectree}{airaw}{$idx}{$key}) { - return $data{$type}{$name}{aidectree}{airaw}{$idx}{$key}; + if (defined $data{$name}{aidectree}{airaw} && + defined $data{$name}{aidectree}{airaw}{$idx} && + defined $data{$name}{aidectree}{airaw}{$idx}{$key}) { + return $data{$name}{aidectree}{airaw}{$idx}{$key}; } return $def; @@ -19893,7 +19961,7 @@ return $def; ################################################################################################################### # Wert des consumer-Hash zurückliefern # Usage: -# ConsumerVal ($hash, $co, $key, $def) +# ConsumerVal ($hash or $name, $co, $key, $def) # # $co: Consumer Nummer (01,02,03,...) # $key: name - Name des Verbrauchers (Device) @@ -19943,18 +20011,19 @@ return $def; # #################################################################################################################### sub ConsumerVal { - my $hash = shift; + my $name = shift; my $co = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if (defined($data{$type}{$name}{consumers}) && - defined($data{$type}{$name}{consumers}{$co}) && - defined($data{$type}{$name}{consumers}{$co}{$key})) { - return $data{$type}{$name}{consumers}{$co}{$key}; + if (defined $data{$name}{consumers} && + defined $data{$name}{consumers}{$co} && + defined $data{$name}{consumers}{$co}{$key}) { + return $data{$name}{consumers}{$co}{$key}; } return $def; @@ -19963,7 +20032,7 @@ return $def; ################################################################################################### # Wert des Inverter-Hash zurückliefern # Usage: -# InverterVal ($hash, $in, $key, $def) +# InverterVal ($hash or $name, $in, $key, $def) # # $in: Inverter Nummer (01,02,03,...) # $key: ietotal - Stand etotal des WR @@ -19977,18 +20046,19 @@ return $def; # ################################################################################################### sub InverterVal { - my $hash = shift; + my $name = shift; my $in = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if (defined($data{$type}{$name}{inverters}) && - defined($data{$type}{$name}{inverters}{$in}) && - defined($data{$type}{$name}{inverters}{$in}{$key})) { - return $data{$type}{$name}{inverters}{$in}{$key}; + if (defined $data{$name}{inverters} && + defined $data{$name}{inverters}{$in} && + defined $data{$name}{inverters}{$in}{$key}) { + return $data{$name}{inverters}{$in}{$key}; } return $def; @@ -19997,7 +20067,7 @@ return $def; ################################################################################################### # Wert des non-PV Producer-Hash zurückliefern # Usage: -# ProducerVal ($hash, $pn, $key, $def) +# ProducerVal ($hash or $name, $pn, $key, $def) # # $pn: Producer Nummer (01,02,03,...) # $key: petotal - Stand etotal des Producers @@ -20010,18 +20080,19 @@ return $def; # ################################################################################################### sub ProducerVal { - my $hash = shift; + my $name = shift; my $pn = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if (defined($data{$type}{$name}{producers}) && - defined($data{$type}{$name}{producers}{$pn}) && - defined($data{$type}{$name}{producers}{$pn}{$key})) { - return $data{$type}{$name}{producers}{$pn}{$key}; + if (defined $data{$name}{producers} && + defined $data{$name}{producers}{$pn} && + defined $data{$name}{producers}{$pn}{$key}) { + return $data{$name}{producers}{$pn}{$key}; } return $def; @@ -20030,7 +20101,7 @@ return $def; ########################################################################################################################################################## # Wert des solcastapi-Hash zurückliefern # Usage: -# RadiationAPIVal ($hash, $tring, $ststr, $key, $def) +# RadiationAPIVal ($hash or $name, $tring, $ststr, $key, $def) # # $tring: Stringname aus "setupInverterStrings" (?All für allg. Werte) # $ststr: Startzeit der Form YYYY-MM-DD hh:00:00 @@ -20042,20 +20113,21 @@ return $def; # RadiationAPIVal ($hash, '?All', $ststr, 'Rad1h', $def) - Globalstrahlung mit Startzeit ohne Stringbezug ########################################################################################################################################################## sub RadiationAPIVal { - my $hash = shift; + my $name = shift; my $string = shift; my $ststr = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if (defined $data{$type}{$name}{solcastapi} && - defined $data{$type}{$name}{solcastapi}{$string} && - defined $data{$type}{$name}{solcastapi}{$string}{$ststr} && - defined $data{$type}{$name}{solcastapi}{$string}{$ststr}{$key}) { - return $data{$type}{$name}{solcastapi}{$string}{$ststr}{$key}; + if (defined $data{$name}{solcastapi} && + defined $data{$name}{solcastapi}{$string} && + defined $data{$name}{solcastapi}{$string}{$ststr} && + defined $data{$name}{solcastapi}{$string}{$ststr}{$key}) { + return $data{$name}{solcastapi}{$string}{$ststr}{$key}; } return $def; @@ -20064,7 +20136,7 @@ return $def; ########################################################################################################################################################## # Wert des weatherAPI-Hash zurückliefern # Usage: -# WeatherAPIVal ($hash, $apiname, $tstr, $key, $def) +# WeatherAPIVal ($hash or $name, $apiname, $tstr, $key, $def) # # $apiname: Hauptname der API gemäß setupWeatherDevX (z.B. OpenMeteo) # $tstr: Zeitwert der Form fcX_XX (z.B. fc1_19) @@ -20073,20 +20145,21 @@ return $def; # ########################################################################################################################################################## sub WeatherAPIVal { - my $hash = shift; + my $name = shift; my $apiname = shift; my $tstr = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if (defined $data{$type}{$name}{weatherapi} && - defined $data{$type}{$name}{weatherapi}{$apiname} && - defined $data{$type}{$name}{weatherapi}{$apiname}{$tstr} && - defined $data{$type}{$name}{weatherapi}{$apiname}{$tstr}{$key}) { - return $data{$type}{$name}{weatherapi}{$apiname}{$tstr}{$key}; + if (defined $data{$name}{weatherapi} && + defined $data{$name}{weatherapi}{$apiname} && + defined $data{$name}{weatherapi}{$apiname}{$tstr} && + defined $data{$name}{weatherapi}{$apiname}{$tstr}{$key}) { + return $data{$name}{weatherapi}{$apiname}{$tstr}{$key}; } return $def; @@ -20095,7 +20168,7 @@ return $def; ########################################################################################################################################################## # Wert des StatusAPI-Hash zurückliefern # Usage: -# StatusAPIVal ($hash, $apiname, '?All', $key, $def) +# StatusAPIVal ($hash or $name, $apiname, '?All', $key, $def) # # $apiname: Hauptname der API gemäß setupWeatherDevX (z.B. OpenMeteo) # $tstr: default '?All' @@ -20123,20 +20196,21 @@ return $def; # StatusAPIVal ($hash, '?IdPair', '?', 'apikey', $def) - SolCast API-Key, = Paarschlüssel ########################################################################################################################################################## sub StatusAPIVal { - my $hash = shift; + my $name = shift; my $apiname = shift; my $tstr = shift; my $key = shift; my $def = shift; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; + if (ref $name eq 'HASH') { + $name = $name->{NAME}; + } - if (defined $data{$type}{$name}{statusapi} && - defined $data{$type}{$name}{statusapi}{$apiname} && - defined $data{$type}{$name}{statusapi}{$apiname}{$tstr} && - defined $data{$type}{$name}{statusapi}{$apiname}{$tstr}{$key}) { - return $data{$type}{$name}{statusapi}{$apiname}{$tstr}{$key}; + if (defined $data{$name}{statusapi} && + defined $data{$name}{statusapi}{$apiname} && + defined $data{$name}{statusapi}{$apiname}{$tstr} && + defined $data{$name}{statusapi}{$apiname}{$tstr}{$key}) { + return $data{$name}{statusapi}{$apiname}{$tstr}{$key}; } return $def; @@ -21229,12 +21303,12 @@ to ensure that the system configuration is correct.
  • consumerXX <Device Name> type=<type> power=<power> [switchdev=<device>]
    - [mode=<mode>] [icon=<Icon>[@<Color>]] [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>]]
    + [mode=<mode>] [icon=<Icon>[@<Color>]] [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>] [exconfc=<Option>]

    + [surpmeth=<Option>] [interruptable=<Option>] [noshow=<Option>] [exconfc=<Option>]

    Registers a consumer <Device Name> with the SolarForecast Device. In this case, <Device Name> @@ -21356,6 +21430,12 @@ to ensure that the system configuration is correct. Reading - Reading for the delivery of the priority switch-off condition Regex - regular expression that must be satisfied for a 'true' condition to be true + surpmeth The possible options define the procedure for determining the PV surplus. (optional) + default - the PV surplus is read directly from the 'Current_Surplus' reading. (default) + median - the median of the last PV surplus measurements (max. 20) is used. + 2 .. 20 - the PV surplus used is calculated from the average of the specified number of measured values. + Device:Reading - Device/Reading combination that provides a numerical PV surplus value determined or calculated by the user. + spignorecond Condition to ignore a missing PV surplus (optional). If the condition is fulfilled, the load is switched on according to the planning even if there is no PV surplus at the time. CAUTION: Using both keys spignorecond and interruptable can lead to undesired behaviour! @@ -21487,15 +21567,15 @@ to ensure that the system configuration is correct. - - - - - - - - - + + + + + + + + +
    1. Starting from 'lowSoc', the minimum SoC is increased by 5% on the following day but not higher than
    'upSoC', if 'maxSoC' has not been reached on the current day.
    2. If 'maxSoC' is reached (again) on the current day, the minimum SoC is reduced by 5%, but not lower than 'lowSoc'.
    3. Minimum SoC is reduced so that the predicted PV energy of the current or following day
    can be absorbed by the battery. Minimum SoC is not reduced lower than 'lowSoc'.
    4. The module records the last point in time at the 'maxSoC' level in order to ensure a charge to 'maxSoC'
    at least every 'careCycle' days. For this purpose, the optimized SoC is changed depending on the remaining days
    until the next 'careCycle' point in such a way that 'maxSoC' is mathematically achieved by a daily 5% SoC increase
    at the 'careCycle' time point. If 'maxSoC' is reached in the meantime, the 'careCycle' period starts again.
    1. Starting from 'lowSoc', the minimum SoC is increased by 5% on the following day but not higher than
    'upSoC', if 'maxSoC' has not been reached on the current day.
    2. If 'maxSoC' is reached (again), the minimum SoC is reduced by 5%, but not lower than 'lowSoc'.
    3. Minimum SoC is reduced to the extent that the predicted PV energy for the current or following
    day can be absorbed by the battery. Minimum SoC is typically reduced to 'upSoc' and not lower than 'lowSoc'.
    4. The module records the last point in time at the 'maxSoC' level in order to ensure a charge to 'maxSoC'
    at least every 'careCycle' days. For this purpose, the optimized SoC is changed depending on the remaining days
    until the next 'careCycle' point in such a way that 'maxSoC' is mathematically achieved by a daily 5% SoC increase
    at the 'careCycle' time point. If 'maxSoC' is reached in the meantime, the 'careCycle' period starts again.

    @@ -23652,12 +23732,12 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
  • consumerXX <Device Name> type=<type> power=<power> [switchdev=<device>]
    - [mode=<mode>] [icon=<Icon>[@<Farbe>]] [mintime=<minutes> | SunPath[:<Offset_Sunrise>:<Offset_Sunset>]]
    + [mode=<mode>] [icon=<Icon>[@<Farbe>]] [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>] [exconfc=<Option>]

    + [surpmeth=<Option>] [interruptable=<Option>] [noshow=<Option>] [exconfc=<Option>]

    Registriert einen Verbraucher <Device Name> beim SolarForecast Device. Dabei ist <Device Name> @@ -23778,6 +23858,12 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Reading - Reading zur Lieferung der vorrangigen Ausschaltbedingung Regex - regulärer Ausdruck der für eine 'wahre' Bedingung erfüllt sein muß + surpmeth Die möglichen Optionen legen das Verfahren zur Ermittlung des PV-Überschusses fest. (optional) + default - der PV-Überschuß wird aus dem Reading 'Current_Surplus' direkt ausgelesen. (default) + median - es wird der Median der letzten PV-Überschuß Messungen (max. 20) verwendet. + 2 .. 20 - der verwendete PV-Überschuß wird als Durchschnitt der angegebenen Anzahl Meßwerte gebildet. + Device:Reading - Device/Reading-Kombination die einen vom Nutzer bestimmten bzw. berechneten numerischen PV-Überschuß Wert liefert. + spignorecond Bedingung um einen fehlenden PV Überschuß zu ignorieren (optional). Bei erfüllter Bedingung wird der Verbraucher entsprechend der Planung eingeschaltet auch wenn zu dem Zeitpunkt kein PV Überschuß vorliegt. ACHTUNG: Die Verwendung beider Schlüssel spignorecond und interruptable kann zu einem unerwünschten Verhalten führen! @@ -23912,15 +23998,15 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. - - - - - - - - - + + + + + + + + +
    1. Ausgehend von 'lowSoc' wird der Mindest-SoC am folgenden Tag um 5%, aber nicht höher als
    'upSoC' inkrementiert, sofern am laufenden Tag 'maxSoC' nicht erreicht wurde.
    2. Wird am laufenden Tag 'maxSoC' (wieder) erreicht, wird Mindest-SoC um 5%, aber nicht tiefer als 'lowSoc', verringert.
    3. Mindest-SoC wird soweit verringert, dass die prognostizierte PV Energie des aktuellen bzw. des folgenden Tages
    von der Batterie aufgenommen werden kann. Mindest-SoC wird nicht tiefer als 'lowSoc' verringert.
    4. Das Modul erfasst den letzten Zeitpunkt am 'maxSoC'-Level, um eine Ladung auf 'maxSoC' mindestens alle 'careCycle'
    Tage zu realisieren. Zu diesem Zweck wird der optimierte SoC in Abhängigkeit der Resttage bis zum nächsten
    'careCycle' Zeitpunkt derart verändert, dass durch eine tägliche 5% SoC-Steigerung 'maxSoC' am 'careCycle' Zeitpunkt
    rechnerisch erreicht wird. Wird zwischenzeitlich 'maxSoC' erreicht, beginnt der 'careCycle' Zeitraum erneut.
    1. Ausgehend von 'lowSoc' wird der Mindest-SoC kurz vor Sonnenuntergang um 5% inkrementiert sofern am laufenden
    Tag 'maxSoC' nicht erreicht wurde und die PV-Prognose keinen hinreichenden Ertrag des kommenden Tages vorhersagt.
    2. Wird 'maxSoC' (wieder) erreicht, wird Mindest-SoC um 5%, aber nicht tiefer als 'lowSoc', verringert.
    3. Mindest-SoC wird soweit verringert, dass die prognostizierte PV Energie des aktuellen bzw. des folgenden Tages
    von der Batterie aufgenommen werden kann. Mindest-SoC wird typisch auf 'upSoc' und nicht tiefer als 'lowSoc' verringert.
    4. Das Modul erfasst den letzten Zeitpunkt am 'maxSoC'-Level, um eine Ladung auf 'maxSoC' mindestens alle 'careCycle'
    Tage zu realisieren. Zu diesem Zweck wird der optimierte SoC in Abhängigkeit der Resttage bis zum nächsten
    'careCycle' Zeitpunkt derart verändert, dass durch eine tägliche 5% SoC-Steigerung 'maxSoC' am 'careCycle' Zeitpunkt
    rechnerisch erreicht wird. Wird zwischenzeitlich 'maxSoC' erreicht, beginnt der 'careCycle' Zeitraum erneut.