diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 7fd9fef0a..985268d0a 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -27,6 +27,8 @@ # Leerzeichen entfernen: sed -i 's/[[:space:]]*$//' 76_SolarForecast.pm # ######################################################################################################################### +main::LoadModule ('Astro'); # Astro Modul für Sonnenkennzahlen laden + package FHEM::SolarForecast; ## no critic 'package' use strict; @@ -53,7 +55,7 @@ use FHEM::SynoModules::SMUtils qw( moduleVersion trim ); # Hilfsroutinen Modul - + use Data::Dumper; use Blocking; use Storable qw(dclone freeze thaw nstore store retrieve); @@ -152,17 +154,27 @@ BEGIN { NexthoursVal ) ); - } # Versions History intern my %vNotesIntern = ( + "1.15.5" => "11.02.2024 change forecastQualities output, new limits for 'accurate' and 'spreaded' results from AI ". + "checkPlantConfig: change common check info output ". + "fix load Astro ", + "1.15.4" => "10.02.2024 integrate sun position from Astro module, setPVhistory: change some writes ". + "_transferAPIRadiationValues: consider 'accurate' or 'spreaded' result from AI". + "___calcPeaklossByTemp: bugfix temp, rename moduleDirection to moduleAzimuth ". + "rename moduleTiltAngle to moduleDeclination, checkPlantConfig: check global altitude attr ", + "1.15.3" => "06.02.2024 Header: add links to the API website dependend from the used API ", + "1.15.2" => "05.02.2024 __mergeDataWeather: fix merger failures, number of temperature decimal places ". + "cicrular Hash: replace 'percentile' by 'simple' ", + "1.15.1" => "04.02.2024 checkPlantConfig: fix check attribute ctrlWeatherDevX ", "1.15.0" => "03.02.2024 reduce cpu utilization, add attributes ctrlWeatherDev2, ctrlWeatherDev3 ", "1.14.3" => "02.02.2024 _transferWeatherValues: first step of multi weather device merger ", "1.14.2" => "02.02.2024 fix warning, _transferAPIRadiationValues: Consider upper and lower deviation limit AI to API forecast ", "1.14.1" => "01.02.2024 language support for ___setConsumerPlanningState -> supplement, fix setting 'swoncond not met' ", - "1.14.0" => "31.01.2024 data maintenance, new sub _addDynAttr for adding attributes at runtime ". - "replace setter currentWeatherDev by attr ctrlWeatherDev1, new data with sub CircularSumVal ". + "1.14.0" => "31.01.2024 data maintenance, new func _addDynAttr for adding attributes at runtime ". + "replace setter currentWeatherDev by attr ctrlWeatherDev1, new data with func CircularSumVal ". "rewrite correction factor calculation with _calcCaQcomplex, _calcCaQsimple, __calcNewFactor ", "1.13.0" => "27.01.2024 minor change of deleteOldBckpFiles, Setter writeHistory replaced by operatingMemory ". "save, backup and recover in-memory operating data ", @@ -173,13 +185,13 @@ my %vNotesIntern = ( "1.9.0" => "23.01.2024 modify disable, add operationMode: active/inactive ", "1.8.0" => "22.01.2024 add 'noLearning' Option to Setter pvCorrectionFactor_Auto ", "1.7.1" => "20.01.2024 optimize battery management ", - "1.7.0" => "18.01.2024 Changeover Start centralTask completely to runCentralTask, ". + "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 runCentralTask in ReadyFn to run centralTask definitely at end/begin of an hour ", + "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 ", @@ -264,7 +276,7 @@ my %vNotesIntern = ( "0.80.4" => "06.07.2023 new transferprocess for DWD data from solcastapi-Hash to estimate calculation, consolidated ". "the autocorrection model ", "0.80.3" => "03.06.2023 preparation for get DWD radiation data to solcastapi-Hash, fix sub isConsumerLogOn (use powerthreshold) ", - "0.80.2" => "02.06.2023 new ctrlDebug keys epiecesCalc, change selfconsumption with graphic Adjustment, moduleDirection ". + "0.80.2" => "02.06.2023 new ctrlDebug keys epiecesCalc, change selfconsumption with graphic Adjustment, moduleAzimuth ". "accepts azimut values -180 .. 0 .. 180 as well as azimut identifier (S, SE ..) ", "0.80.1" => "31.05.2023 adapt _calcCaQsimple to calculate corrfactor like _calcCaQcomplex ", "0.80.0" => "28.05.2023 Support for Forecast.Solar-API (https://doc.forecast.solar/api), rename Getter solCastData to solApiData ". @@ -319,8 +331,10 @@ my $airaw = $root."/FHEM/FhemUtils/AIraw_SolarForecast_"; my $aitrblto = 7200; # KI Training BlockingCall Timeout my $aibcthhld = 0.2; # Schwelle der KI Trainigszeit ab der BlockingCall benutzt wird my $aistdudef = 1095; # default Haltezeit KI Raw Daten (Tage) -my $aiDevUpLim = 150; # obere Abweichungsgrenze (%) AI von API Prognose -my $aiDevLowLim = 50; # untere Abweichungsgrenze (%) AI von API Prognose +my $aiSpreadUpLim = 150; # obere Abweichungsgrenze (%) AI 'Spread' von API Prognose +my $aiSpreadLowLim = 50; # untere Abweichungsgrenze (%) AI 'Spread' von API Prognose +my $aiAccUpLim = 170; # obere Abweichungsgrenze (%) AI 'Accurate' von API Prognose +my $aiAccLowLim = 30; # untere Abweichungsgrenze (%) AI 'Accurate' von API Prognose my $calcmaxd = 30; # Anzahl Tage die zur Berechnung Vorhersagekorrektur verwendet werden my @dweattrmust = qw(TTT Neff R101 ww SunUp SunRise SunSet); # Werte die im Attr forecastProperties des Weather-DWD_Opendata Devices mindestens gesetzt sein müssen @@ -448,8 +462,8 @@ my %hset = ( # Ha reset => { fn => \&_setreset }, roofIdentPair => { fn => \&_setroofIdentPair }, moduleRoofTops => { fn => \&_setmoduleRoofTops }, - moduleTiltAngle => { fn => \&_setmoduleTiltAngle }, - moduleDirection => { fn => \&_setmoduleDirection }, + moduleDeclination => { fn => \&_setmoduleDeclination }, + moduleAzimuth => { fn => \&_setmoduleAzimuth }, operatingMemory => { fn => \&_setoperatingMemory }, vrmCredentials => { fn => \&_setVictronCredentials }, aiDecTree => { fn => \&_setaiDecTree }, @@ -533,10 +547,10 @@ my %hqtxt = ( DE => qq{Bitte geben sie alle von Ihnen verwendeten Stringnamen mit "set LINK inverterStrings" an} }, mps => { EN => qq{Please enter the DC peak power of each string with "set LINK modulePeakString"}, DE => qq{Bitte geben sie die DC Spitzenleistung von jedem String mit "set LINK modulePeakString" an} }, - mdr => { EN => qq{Please specify the module direction with "set LINK moduleDirection"}, - DE => qq{Bitte geben sie die Modulausrichtung mit "set LINK moduleDirection" an} }, - mta => { EN => qq{Please specify the module tilt angle with "set LINK moduleTiltAngle"}, - DE => qq{Bitte geben sie den Modulneigungswinkel mit "set LINK moduleTiltAngle" an} }, + mdr => { EN => qq{Please specify the module direction with "set LINK moduleAzimuth"}, + DE => qq{Bitte geben sie die Modulausrichtung mit "set LINK moduleAzimuth" an} }, + mta => { EN => qq{Please specify the module tilt angle with "set LINK moduleDeclination"}, + DE => qq{Bitte geben sie den Modulneigungswinkel mit "set LINK moduleDeclination" an} }, rip => { EN => qq{Please specify at least one combination Rooftop-ID/SolCast-API with "set LINK roofIdentPair"}, DE => qq{Bitte geben Sie mindestens eine Kombination Rooftop-ID/SolCast-API mit "set LINK roofIdentPair" an} }, mrt => { EN => qq{Please set the assignment String / Rooftop identification with "set LINK moduleRoofTops"}, @@ -567,6 +581,8 @@ my %hqtxt = ( DE => qq{Status} }, result => { EN => qq{Result}, DE => qq{Ergebnis} }, + attrib => { EN => qq{attribute}, + DE => qq{Attribut} }, note => { EN => qq{Note}, DE => qq{Hinweis} }, wfmdcf => { EN => qq{Wait for more days with a consumption figure}, @@ -924,7 +940,7 @@ sub Initialize { $hash->{DbLog_splitFn} = \&DbLogSplit; $hash->{AttrFn} = \&Attr; $hash->{NotifyFn} = \&Notify; - $hash->{ReadyFn} = \&runCentralTask; + $hash->{ReadyFn} = \&runTask; $hash->{AttrList} = "affect70percentRule:1,dynamic,0 ". "affectBatteryPreferredCharge:slider,0,1,100 ". "affectConsForecastIdentWeekdays:1,0 ". @@ -1230,8 +1246,8 @@ sub Set { ; } elsif (isForecastSolarUsed ($hash)) { - $setlist .= "moduleDirection ". - "moduleTiltAngle " + $setlist .= "moduleAzimuth ". + "moduleDeclination " ; } elsif (isVictronKiUsed ($hash)) { @@ -1239,8 +1255,8 @@ sub Set { ; } else { - $setlist .= "moduleDirection ". - "moduleTiltAngle " + $setlist .= "moduleAzimuth ". + "moduleDeclination " ; } @@ -1393,11 +1409,11 @@ sub _setcurrentRadiationAPI { ## no critic "not used" my ($set, $lat, $lon) = locCoordinates(); return qq{set attributes 'latitude' and 'longitude' in global device first} if(!$set); - my $tilt = ReadingsVal ($name, 'moduleTiltAngle', ''); # Modul Neigungswinkel für jeden Stringbezeichner - return qq{Please complete command "set $name moduleTiltAngle".} if(!$tilt); + my $tilt = ReadingsVal ($name, 'moduleDeclination', ''); # Modul Neigungswinkel für jeden Stringbezeichner + return qq{Please complete command "set $name moduleDeclination".} if(!$tilt); - my $dir = ReadingsVal ($name, 'moduleDirection', ''); # Modul Ausrichtung für jeden Stringbezeichner - return qq{Please complete command "set $name moduleDirection".} if(!$dir); + my $dir = ReadingsVal ($name, 'moduleAzimuth', ''); # Modul Ausrichtung für jeden Stringbezeichner + return qq{Please complete command "set $name moduleAzimuth".} if(!$dir); } if ($prop eq 'VictronVRM-API') { @@ -1595,7 +1611,7 @@ sub _setinverterStrings { ## no critic "not used" return if(_checkSetupNotComplete ($hash)); # keine Stringkonfiguration wenn Setup noch nicht komplett my $ret = qq{NOTE: After setting or changing "inverterStrings" please check }. - qq{/ set all module parameter (e.g. moduleTiltAngle) again ! \n}. + qq{/ set all module parameter (e.g. moduleDeclination) again ! \n}. qq{Use "set $name plantConfiguration check" to validate your Setup.}; return $ret; @@ -1789,9 +1805,9 @@ return; } ################################################################ -# Setter moduleTiltAngle +# Setter moduleDeclination ################################################################ -sub _setmoduleTiltAngle { ## no critic "not used" +sub _setmoduleDeclination { ## no critic "not used" my $paref = shift; my $hash = $paref->{hash}; my $name = $paref->{name}; @@ -1811,8 +1827,8 @@ sub _setmoduleTiltAngle { ## no critic "not used" } } - readingsSingleUpdate ($hash, "moduleTiltAngle", $arg, 1); - writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration File schreiben + readingsSingleUpdate ($hash, 'moduleDeclination', $arg, 1); + writeCacheToFile ($hash, 'plantconfig', $plantcfg.$name); # Anlagenkonfiguration File schreiben return if(_checkSetupNotComplete ($hash)); # keine Stringkonfiguration wenn Setup noch nicht komplett @@ -1823,13 +1839,13 @@ return; } ################################################################ -# Setter moduleDirection +# Setter moduleAzimuth # # Angabe entweder als Azimut-Bezeichner oder direkte # Azimut Angabe -180 ...0...180 # ################################################################ -sub _setmoduleDirection { ## no critic "not used" +sub _setmoduleAzimuth { ## no critic "not used" my $paref = shift; my $hash = $paref->{hash}; my $name = $paref->{name}; @@ -1849,8 +1865,8 @@ sub _setmoduleDirection { ## no critic "not used" } } - readingsSingleUpdate ($hash, "moduleDirection", $arg, 1); - writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration File schreiben + readingsSingleUpdate ($hash, 'moduleAzimuth', $arg, 1); + writeCacheToFile ($hash, 'plantconfig', $plantcfg.$name); # Anlagenkonfiguration File schreiben return if(_checkSetupNotComplete ($hash)); # keine Stringkonfiguration wenn Setup noch nicht komplett @@ -2048,7 +2064,9 @@ sub _setreset { ## no critic "not used" $paref->{reorg} = 1; # den Tag Stunde "99" reorganisieren $paref->{reorgday} = $dday; + setPVhistory ($paref); + delete $paref->{reorg}; delete $paref->{reorgday}; } @@ -4208,7 +4226,7 @@ sub Attr { ### nicht mehr benötigte Daten löschen - Bereich kann später wieder raus !! ########################################################################################## - if ($cmd eq 'set' && $aName eq 'affectNumHistDays') { + if ($cmd eq 'set' && $aName eq 'affectNumHistDays') { # 30.01.2024 if (!$init_done) { return qq{Device "$name" -> The attribute '$aName' is obsolete and will be deleted soon. Please press "save config" when restart is finished.}; } @@ -4912,16 +4930,16 @@ sub _savePlantConfig { currentMeterDev currentRadiationAPI inverterStrings - moduleDirection + moduleAzimuth modulePeakString - moduleTiltAngle + moduleDeclination moduleRoofTops powerTrigger energyH4Trigger ); for my $cfg (@aconfigs) { - my $val = ReadingsVal($name, $cfg, ""); + my $val = ReadingsVal ($name, $cfg, ""); next if(!$val); push @pvconf, $cfg."<>".$val; } @@ -4933,7 +4951,7 @@ return @pvconf; # centralTask Start Management # Achtung: relevant für CPU Auslastung! ################################################################ -sub runCentralTask { +sub runTask { my $hash = shift; return if(!$init_done || CurrentVal ($hash, 'ctrunning', 0)); @@ -4984,14 +5002,15 @@ sub runCentralTask { storeReading ('nextCycletime', FmtTime($new)); centralTask ($hash, 1); } + + my $debug = getDebug ($hash); if ($min == 59 && $sec > 48) { if (!exists $hash->{HELPER}{S48DONE}) { - my $debug = getDebug ($hash); $hash->{HELPER}{S48DONE} = 1; if ($debug =~ /collectData/x) { - Log3 ($name, 1, "$name DEBUG> INFO - Start of unscheduled data collection at the end of an hour"); + Log3 ($name, 1, "$name DEBUG> INFO - runTask starts data collection at the end of an hour"); } centralTask ($hash, 1); @@ -5003,11 +5022,10 @@ sub runCentralTask { if ($min == 0 && $sec > 3) { if (!exists $hash->{HELPER}{S03DONE}) { - my $debug = getDebug ($hash); $hash->{HELPER}{S03DONE} = 1; if ($debug =~ /collectData/x) { - Log3 ($name, 1, "$name DEBUG> INFO - Start of unscheduled data collection at the beginning of an hour"); + Log3 ($name, 1, "$name DEBUG> INFO - runTask starts data collection at the beginning of an hour"); } centralTask ($hash, 1); @@ -5016,6 +5034,32 @@ sub runCentralTask { else { delete $hash->{HELPER}{S03DONE}; } + + if ($min =~ /^(2|22|42)$/xs && $sec > 20) { + if (!exists $hash->{HELPER}{SUNCALCDONE}) { + $hash->{HELPER}{SUNCALCDONE} = 1; + + if ($debug =~ /collectData/x) { + Log3 ($name, 1, "$name DEBUG> INFO - runTask starts calculation of Sun positions"); + } + + my $day = strftime "%d", localtime($t); # aktueller Tag (range 01 to 31) + my $chour = strftime "%H", localtime($t); # aktuelle Stunde in 24h format (00-23) + + _calcSunPosition ( { hash => $hash, + name => $name, + type => $hash->{TYPE}, + t => $t, + day => $day, + chour => $chour, + debug => $debug + } + ); + } + } + else { + delete $hash->{HELPER}{SUNCALCDONE}; + } return; } @@ -5050,6 +5094,62 @@ sub _addDynAttr { return; } +################################################################ +# Ermittlung der Sonnenpositionen +# Az,Alt = Azimuth und Höhe (in Dezimalgrad) des Körpers +# über dem Horizont +################################################################ +sub _calcSunPosition { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $t = $paref->{t}; # Epoche Zeit + my $chour = $paref->{chour}; + my $day = $paref->{day}; + + debugLog ($paref, 'collectData', "collect Sun position data =>"); + + for my $num (0..46) { + 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 $hod = sprintf "%02d", $h + 1; + my $nhtstr = "NextHour".sprintf "%02d", $num; + + my $az = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAz', $tstr); # statt Astro_Get geht auch FHEM::Astro::Get + my $alt = sprintf "%.0f", FHEM::Astro::Get (undef, 'global', 'text', 'SunAlt', $tstr); + + $data{$type}{$name}{nexthours}{$nhtstr}{sunaz} = $az; + $data{$type}{$name}{nexthours}{$nhtstr}{sunalt} = $alt; + + debugLog ($paref, 'collectData', "Sun position: hod: $hod, $tstr, azimuth: $az, altitude: $alt"); + + if ($fd == 0 && $hod) { # Sun Position in pvHistory speichern + $paref->{nhour} = sprintf "%02d", $hod; + + $paref->{val} = $az; + $paref->{histname} = 'sunaz'; + setPVhistory ($paref); + + $paref->{val} = $alt; + $paref->{histname} = 'sunalt'; + setPVhistory ($paref); + + delete $paref->{histname}; + delete $paref->{val}; + delete $paref->{nhour}; + } + } + + debugLog ($paref, 'collectData', "All Sun position data collected"); + +return; +} + ################################################################ # Zentraler Datenabruf ################################################################ @@ -5080,10 +5180,45 @@ sub centralTask { ### nicht mehr benötigte Daten löschen - Bereich kann später wieder raus !! ########################################################################################## - ## nicht-Bin Werte löschen - my $ra = '0|5|10|15|20|25|30|35|40|45|50|55|60|65|70|75|80|85|90|95|100|percentile'; + ## AI Raw Daten formatieren # 09.02.2024 + if (defined $data{$type}{$name}{aidectree}{airaw}) { + for my $idx (sort keys %{$data{$type}{$name}{aidectree}{airaw}}) { + $data{$type}{$name}{aidectree}{airaw}{$idx}{wrp} = 0 if(AiRawdataVal ($hash, $idx, 'wrp', 0) eq '00'); + $data{$type}{$name}{aidectree}{airaw}{$idx}{wrp} = 5 if(AiRawdataVal ($hash, $idx, 'wrp', 0) eq '05'); + $data{$type}{$name}{aidectree}{airaw}{$idx}{temp} = 0 if(AiRawdataVal ($hash, $idx, 'temp', 0) eq '00'); + $data{$type}{$name}{aidectree}{airaw}{$idx}{temp} = 5 if(AiRawdataVal ($hash, $idx, 'temp', 0) eq '05'); + $data{$type}{$name}{aidectree}{airaw}{$idx}{temp} = -5 if(AiRawdataVal ($hash, $idx, 'temp', 0) eq '-05'); + } + } - for my $hod (keys %{$data{$type}{$name}{circular}}) { + ## percentile in simple umsetzen # 05.02.2024 + for my $idx (sort keys %{$data{$type}{$name}{circular}}) { + if(defined $data{$type}{$name}{circular}{$idx}{pvcorrf}{percentile}) { + $data{$type}{$name}{circular}{$idx}{pvcorrf}{simple} = $data{$type}{$name}{circular}{$idx}{pvcorrf}{percentile}; + delete $data{$type}{$name}{circular}{$idx}{pvcorrf}{percentile}; + } + if(defined $data{$type}{$name}{circular}{$idx}{quality}{percentile}) { + $data{$type}{$name}{circular}{$idx}{quality}{simple} = $data{$type}{$name}{circular}{$idx}{quality}{percentile}; + delete $data{$type}{$name}{circular}{$idx}{quality}{percentile}; + } + if(defined $data{$type}{$name}{circular}{$idx}{pvrlsum}{percentile}) { + $data{$type}{$name}{circular}{$idx}{pvrlsum}{simple} = $data{$type}{$name}{circular}{$idx}{pvrlsum}{percentile}; + delete $data{$type}{$name}{circular}{$idx}{pvrlsum}{percentile}; + } + if(defined $data{$type}{$name}{circular}{$idx}{pvfcsum}{percentile}) { + $data{$type}{$name}{circular}{$idx}{pvfcsum}{simple} = $data{$type}{$name}{circular}{$idx}{pvfcsum}{percentile}; + delete $data{$type}{$name}{circular}{$idx}{pvfcsum}{percentile}; + } + if(defined $data{$type}{$name}{circular}{$idx}{dnumsum}{percentile}) { + $data{$type}{$name}{circular}{$idx}{dnumsum}{simple} = $data{$type}{$name}{circular}{$idx}{dnumsum}{percentile}; + delete $data{$type}{$name}{circular}{$idx}{dnumsum}{percentile}; + } + } + + ## nicht-Bin Werte löschen + my $ra = '0|00|05|5|10|15|20|25|30|35|40|45|50|55|60|65|70|75|80|85|90|95|100|percentile|simple'; + + for my $hod (keys %{$data{$type}{$name}{circular}}) { # 30.01.2024 for my $range (keys %{$data{$type}{$name}{circular}{$hod}{pvcorrf}}) { delete $data{$type}{$name}{circular}{$hod}{pvcorrf}{$range} if($range !~ /^($ra)$/xs); } @@ -5093,12 +5228,25 @@ sub centralTask { } } ## currentWeatherDev in Attr umsetzen - my $cwd = ReadingsVal ($name, 'currentWeatherDev', ''); + my $cwd = ReadingsVal ($name, 'currentWeatherDev', ''); # 30.01.2024 if ($cwd) { CommandAttr (undef, "$name ctrlWeatherDev1 $cwd"); readingsDelete ($hash, 'currentWeatherDev') if(AttrVal ($name, 'ctrlWeatherDev1', '')); # erst prüfen ob gesetzt } + ## Reading umsetzen + my $mdr = ReadingsVal ($name, 'moduleDirection', undef); # 09.02.2024 + if ($mdr) { + readingsSingleUpdate ($hash, 'moduleAzimuth', $mdr, 0); + readingsDelete ($hash, 'moduleDirection'); + } + + my $mta = ReadingsVal ($name, 'moduleTiltAngle', undef); # 09.02.2024 + if ($mta) { + readingsSingleUpdate ($hash, 'moduleDeclination', $mta, 0); + readingsDelete ($hash, 'moduleTiltAngle'); + } + ############################################################################################ return if(!$init_done); @@ -5162,7 +5310,7 @@ sub centralTask { collectAllRegConsumers ($centpars); # alle Verbraucher Infos laden _specialActivities ($centpars); # zusätzliche Events generieren + Sonderaufgaben _transferWeatherValues ($centpars); # Wetterwerte übertragen - + readingsDelete ($hash, 'AllPVforecastsToEvent'); _getRoofTopData ($centpars); # Strahlungswerte/Forecast-Werte in solcastapi-Hash erstellen @@ -5259,8 +5407,8 @@ sub createStringConfig { ## no critic "not used" } } elsif (!isVictronKiUsed ($hash)) { - my $tilt = ReadingsVal ($name, 'moduleTiltAngle', ''); # Modul Neigungswinkel für jeden Stringbezeichner - return qq{Please complete command "set $name moduleTiltAngle".} if(!$tilt); + my $tilt = ReadingsVal ($name, 'moduleDeclination', ''); # Modul Neigungswinkel für jeden Stringbezeichner + return qq{Please complete command "set $name moduleDeclination".} if(!$tilt); my ($at,$ht) = parseParams ($tilt); @@ -5269,15 +5417,15 @@ sub createStringConfig { ## no critic "not used" $data{$type}{$name}{strings}{$key}{tilt} = $value; } else { - return qq{Check "moduleTiltAngle" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"}; + return qq{Check "moduleDeclination" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"}; } } - my $dir = ReadingsVal ($name, 'moduleDirection', ''); # Modul Ausrichtung für jeden Stringbezeichner - return qq{Please complete command "set $name moduleDirection".} if(!$dir); + my $dir = ReadingsVal ($name, 'moduleAzimuth', ''); # Modul Ausrichtung für jeden Stringbezeichner + return qq{Please complete command "set $name moduleAzimuth".} if(!$dir); my ($ad,$hd) = parseParams ($dir); - my $iwrong = qq{Please check the input of set "moduleDirection". It seems to be wrong.}; + my $iwrong = qq{Please check the input of set "moduleAzimuth". It seems to be wrong.}; while (my ($key, $value) = each %$hd) { if ($key ~~ @istrings) { @@ -5285,14 +5433,14 @@ sub createStringConfig { ## no critic "not used" $data{$type}{$name}{strings}{$key}{azimut} = _ident2azimuth ($value) // return $iwrong; } else { - return qq{Check "moduleDirection" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"}; + return qq{Check "moduleAzimuth" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"}; } } } if(!keys %{$data{$type}{$name}{strings}}) { return qq{The string configuration seems to be incomplete. \n}. - qq{Please check the settings of inverterStrings, modulePeakString, moduleDirection, moduleTiltAngle }. + qq{Please check the settings of inverterStrings, modulePeakString, moduleAzimuth, moduleDeclination }. qq{and/or moduleRoofTops if SolCast-API is used.}; } @@ -5633,7 +5781,6 @@ sub _transferWeatherValues { __mergeDataWeather ($paref); # Wetterdaten zusammenfügen - my $time_str; my $cat = 'merge'; for my $num (0..46) { @@ -5649,13 +5796,13 @@ sub _transferWeatherValues { my $temp = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh1}"}{$cat}{ttt}; # Außentemperatur my $don = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh1}"}{$cat}{don}; # Tag/Nacht-Grenze - $time_str = "NextHour".sprintf "%02d", $num; - $data{$type}{$name}{nexthours}{$time_str}{weatherid} = $wid; - $data{$type}{$name}{nexthours}{$time_str}{cloudcover} = $neff; - $data{$type}{$name}{nexthours}{$time_str}{rainprob} = $r101; - $data{$type}{$name}{nexthours}{$time_str}{rainrange} = rain2bin ($r101); - $data{$type}{$name}{nexthours}{$time_str}{temp} = $temp; - $data{$type}{$name}{nexthours}{$time_str}{DoN} = $don; + my $nhtstr = "NextHour".sprintf "%02d", $num; + $data{$type}{$name}{nexthours}{$nhtstr}{weatherid} = $wid; + $data{$type}{$name}{nexthours}{$nhtstr}{cloudcover} = $neff; + $data{$type}{$name}{nexthours}{$nhtstr}{rainprob} = $r101; + $data{$type}{$name}{nexthours}{$nhtstr}{rainrange} = rain2bin ($r101); + $data{$type}{$name}{nexthours}{$nhtstr}{temp} = $temp; + $data{$type}{$name}{nexthours}{$nhtstr}{DoN} = $don; 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; @@ -5664,7 +5811,7 @@ sub _transferWeatherValues { $data{$type}{$name}{circular}{sprintf("%02d",$fh1)}{wrp} = $r101; $data{$type}{$name}{circular}{sprintf("%02d",$fh1)}{temp} = $temp; - if ($num == 0) { # aktuelle Außentemperatur + if ($num == 0) { # aktuelle Außentemperatur $data{$type}{$name}{current}{temp} = $temp; } } @@ -5766,11 +5913,17 @@ sub __mergeDataWeather { debugLog ($paref, 'collectData', "merge Weather data =>"); + my $ds = 0; + + for my $wd (1..$weatherDevMax) { + my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$wd, ''); # Weather Forecast Device + $ds++ if($fcname && $defs{$fcname}); + } + my ($q, $m) = (0,0); for my $key (sort keys %{$data{$type}{$name}{weatherdata}}) { my ($z, $neff, $r101, $temp) = (0,0,0,0); - $q++; $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}; @@ -5779,7 +5932,9 @@ sub __mergeDataWeather { $data{$type}{$name}{weatherdata}{$key}{merge}{r101} = $data{$type}{$name}{weatherdata}{$key}{1}{r101}; $data{$type}{$name}{weatherdata}{$key}{merge}{ttt} = $data{$type}{$name}{weatherdata}{$key}{1}{ttt}; - for my $step (1..$weatherDevMax) { + for my $step (1..$ds) { + $q++; + my $n = $data{$type}{$name}{weatherdata}{$key}{$step}{neff}; my $r = $data{$type}{$name}{weatherdata}{$key}{$step}{r101}; my $t = $data{$type}{$name}{weatherdata}{$key}{$step}{ttt}; @@ -5797,7 +5952,7 @@ sub __mergeDataWeather { $data{$type}{$name}{weatherdata}{$key}{merge}{neff} = sprintf "%.0f", ($neff / $z); $data{$type}{$name}{weatherdata}{$key}{merge}{r101} = sprintf "%.0f", ($r101 / $z); - $data{$type}{$name}{weatherdata}{$key}{merge}{ttt} = $temp / $z; + $data{$type}{$name}{weatherdata}{$key}{merge}{ttt} = sprintf "%.2f", ($temp / $z); debugLog ($paref, 'collectData', "Weather merged: $key, ". "don: $data{$type}{$name}{weatherdata}{$key}{merge}{don}, ". @@ -5896,15 +6051,15 @@ sub _transferAPIRadiationValues { next; } - my $fh1 = $fh + 1; - my $wantts = (timestringToTimestamp ($date.' '.$chour.':00:00')) + ($num * 3600); - my $wantdt = (timestampToTimestring ($wantts, $lang))[1]; + my $fh1 = $fh + 1; + my $wantts = (timestringToTimestamp ($date.' '.$chour.':00:00')) + ($num * 3600); + my $wantdt = (timestampToTimestring ($wantts, $lang))[1]; - my $time_str = 'NextHour'.sprintf "%02d", $num; - my ($hod) = $wantdt =~ /\s(\d{2}):/xs; - $hod = sprintf "%02d", int $hod + 1; # Stunde des Tages + my $nhtstr = 'NextHour'.sprintf "%02d", $num; + my ($hod) = $wantdt =~ /\s(\d{2}):/xs; + $hod = sprintf "%02d", int $hod + 1; # Stunde des Tages - my $rad = SolCastAPIVal ($hash, '?All', $wantdt, 'Rad1h', undef); + my $rad1h = SolCastAPIVal ($hash, '?All', $wantdt, 'Rad1h', undef); my $params = { hash => $hash, @@ -5912,7 +6067,7 @@ sub _transferAPIRadiationValues { type => $type, wantdt => $wantdt, hod => $hod, - nhidx => $time_str, + nhidx => $nhtstr, num => $num, fh1 => $fh1, fd => $fd, @@ -5922,42 +6077,57 @@ sub _transferAPIRadiationValues { my $est = __calcPVestimates ($params); - $data{$type}{$name}{nexthours}{$time_str}{pvapifc} = $est; # durch API gelieferte PV Forecast - $data{$type}{$name}{nexthours}{$time_str}{starttime} = $wantdt; - $data{$type}{$name}{nexthours}{$time_str}{hourofday} = $hod; - $data{$type}{$name}{nexthours}{$time_str}{today} = $fd == 0 ? 1 : 0; - $data{$type}{$name}{nexthours}{$time_str}{rad1h} = $rad; + $data{$type}{$name}{nexthours}{$nhtstr}{pvapifc} = $est; # durch API gelieferte PV Forecast + $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 ($err, $pvaifc) = aiGetResult ($params); # KI Entscheidungen abfragen + my ($msg, $pvaifc) = aiGetResult ($params); # KI Entscheidungen abfragen my $useai = 0; my $pvfc; - if (!$err) { + if ($msg eq 'accurate' || $msg eq 'spreaded') { my $aivar = 100 * $pvaifc / $est; - if ($aivar >= $aiDevLowLim && $aivar <= $aiDevUpLim) { # Abweichung AI von Standardvorhersage begrenzen - $data{$type}{$name}{nexthours}{$time_str}{pvaifc} = $pvaifc; # durch AI gelieferte PV Forecast - $data{$type}{$name}{nexthours}{$time_str}{pvfc} = $pvaifc; - $data{$type}{$name}{nexthours}{$time_str}{aihit} = 1; + if ($msg eq 'accurate' && $aivar >= $aiAccLowLim && $aivar <= $aiAccUpLim) { # KI liefert 'accurate' Treffer -> verwenden + $data{$type}{$name}{nexthours}{$nhtstr}{aihit} = 1; + $pvfc = $pvaifc; + $useai = 1; + debugLog ($paref, 'aiData', qq{AI Hit - accurate result found -> hod: $hod, Rad1h: $rad1h, pvfc: $pvfc Wh}); + } + + if ($msg eq 'spreaded' && $aivar >= $aiSpreadLowLim && $aivar <= $aiSpreadUpLim) { # Abweichung AI von Standardvorhersage begrenzen + $data{$type}{$name}{nexthours}{$nhtstr}{aihit} = 1; $pvfc = $pvaifc; $useai = 1; + + debugLog ($paref, 'aiData', qq{AI Hit - spreaded result found and is in tolerance -> hod: $hod, Rad1h: $rad1h, pvfc: $pvfc Wh}); } } - - if (!$useai) { - delete $data{$type}{$name}{nexthours}{$time_str}{pvaifc}; - $data{$type}{$name}{nexthours}{$time_str}{pvfc} = $est; - $data{$type}{$name}{nexthours}{$time_str}{aihit} = 0; - - $pvfc = $est; + 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, $time_str, 'pvapifc', undef); + $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, $time_str, 'pvaifc', undef); - $data{$type}{$name}{circular}{sprintf "%02d",$fh1}{aihit} = NexthoursVal ($hash, $time_str, 'aihit', 0); + $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 @@ -5971,7 +6141,7 @@ sub _transferAPIRadiationValues { $paref->{histname} = 'pvfc'; setPVhistory ($paref); - $paref->{rad1h} = $rad; + $paref->{rad1h} = $rad1h; $paref->{histname} = 'radiation'; setPVhistory ($paref); @@ -6075,8 +6245,8 @@ sub __calcPVestimates { }; if ($acu =~ /on_complex/xs) { - $lh->{"Module Temp (calculated)"} = $modtemp. " °C"; - $lh->{"Loss String Peak Power by Temp"} = $peakloss." kWP"; + $lh->{"Module Temp (calculated)"} = $modtemp. " °C"; + $lh->{"Win(+)/Loss(-) String Peak Power by Temp"} = $peakloss." kWp"; } $sq = q{}; @@ -6084,7 +6254,7 @@ sub __calcPVestimates { $sq .= $idx." => ".$lh->{$idx}."\n"; } - Log3 ($name, 1, "$name DEBUG> PV estimate for $reld Hour ".sprintf ("%02d", $hod)." string $string ->\n$sq"); + Log3 ($name, 1, "$name DEBUG> PV API estimate for $reld Hour ".sprintf ("%02d", $hod)." string $string ->\n$sq"); } $pvsum += $pv; @@ -6114,7 +6284,7 @@ sub __calcPVestimates { "Forecasted temperature" => $temp." °C", "Cloudcover" => $cloudcover, "Rainprob" => $rainprob, - "Use PV Correction" => ($acu ? $acu : 'no'), + "PV Correction mode" => ($acu ? $acu : 'no'), "PV correction factor" => $hc, "PV correction quality" => $hq, "PV generation forecast" => $pvsum." Wh ".$logao, @@ -6125,7 +6295,7 @@ sub __calcPVestimates { $sq .= $idx." => ".$lh->{$idx}."\n"; } - Log3 ($name, 1, "$name DEBUG> PV estimate for $reld Hour ".sprintf ("%02d", $hod)." summary: \n$sq"); + Log3 ($name, 1, "$name DEBUG> PV API estimate for $reld Hour ".sprintf ("%02d", $hod)." summary: \n$sq"); } return $pvsum; @@ -6137,7 +6307,7 @@ return $pvsum; # speichert die Werte im Nexthours / pvHistory Hash # # Simple: -# Liest Korrekturfaktor/Qualität aus pvCircular percentile und +# Liest Korrekturfaktor/Qualität aus pvCircular simple und # speichert die Werte im Nexthours / pvHistory Hash ###################################################################### sub ___readCandQ { @@ -6166,13 +6336,13 @@ sub ___readCandQ { $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{cloudrange} = $range; } elsif ($acu =~ /on_simple/xs) { - ($hc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), 'percentile', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) + ($hc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) $hq //= '-'; $hc //= 1; # Korrekturfaktor = 1 $hc = 1 if(1 * $hc == 0); # 0.0-Werte ignorieren (Schleifengefahr) } else { # keine Autokorrektur - ($hc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), 'percentile', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) + ($hc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), 'simple', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) $hq //= '-'; $hc = 1; } @@ -6222,7 +6392,7 @@ sub ___calcPeaklossByTemp { my $temp = $paref->{temp} // return (0,0); # vorhergesagte Temperatur Stunde X my $modtemp = $temp + ($tempmodinc * (1 - ($cloudcover/100))); # kalkulierte Modultemperatur - my $peakloss = sprintf "%.2f", $tempcoeffdef * ($temp - $tempbasedef) * $peak / 100; + my $peakloss = sprintf "%.2f", $tempcoeffdef * ($modtemp - $tempbasedef) * $peak / 100; return ($peakloss, $modtemp); } @@ -6684,11 +6854,15 @@ sub _transferBatteryValues { my $batmaxsoc = HistoryVal ($hash, $day, 99, 'batmaxsoc', 0); # gespeicherter max. SOC des Tages if ($soc >= $batmaxsoc) { - $paref->{batmaxsoc} = $soc; - $paref->{nhour} = 99; - $paref->{histname} = 'batmaxsoc'; + $paref->{val} = $soc; + $paref->{nhour} = 99; + $paref->{histname} = 'batmaxsoc'; + setPVhistory ($paref); + delete $paref->{histname}; + delete $paref->{nhour}; + delete $paref->{val}; } ###### @@ -6835,11 +7009,15 @@ sub _batSocTarget { ## pvHistory/Readings schreiben ################################# - $paref->{batsetsoc} = $target; - $paref->{nhour} = 99; - $paref->{histname} = 'batsetsoc'; + $paref->{val} = $target; + $paref->{nhour} = 99; + $paref->{histname} = 'batsetsoc'; + setPVhistory ($paref); + delete $paref->{histname}; + delete $paref->{nhour}; + delete $paref->{val}; storeReading ('Battery_OptimumTargetSoC', $target.' %'); storeReading ('Battery_ChargeRequest', $chargereq); @@ -7056,7 +7234,7 @@ sub _manageConsumerData { my $up = ConsumerVal ($hash, $c, "upcurr", ""); my $pcurr = 0; - if($paread) { + if ($paread) { my $eup = $up =~ /^kW$/xi ? 1000 : 1; $pcurr = ReadingsNum ($consumer, $paread, 0) * $eup; @@ -7069,7 +7247,7 @@ sub _manageConsumerData { my $etotread = ConsumerVal ($hash, $c, "retotal", ""); my $u = ConsumerVal ($hash, $c, "uetotal", ""); - if($etotread) { + if ($etotread) { my $eu = $u =~ /^kWh$/xi ? 1000 : 1; my $etot = ReadingsNum ($consumer, $etotread, 0) * $eu; # Summe Energieverbrauch des Verbrauchers my $ehist = HistoryVal ($hash, $day, sprintf("%02d",$nhour), "csmt${c}", undef); # gespeicherter Totalverbrauch @@ -7077,7 +7255,7 @@ sub _manageConsumerData { ## aktuelle Leistung ermitteln wenn kein Reading d. aktuellen Leistung verfügbar ################################################################################## - if(!$paread){ + if (!$paread){ my $timespan = $t - ConsumerVal ($hash, $c, "old_etottime", $t); my $delta = $etot - ConsumerVal ($hash, $c, "old_etotal", $etot); $pcurr = sprintf "%.6f", $delta / (3600 * $timespan) if($delta); # Einheitenformel beachten !!: W = Wh / (3600 * s) @@ -7088,7 +7266,7 @@ sub _manageConsumerData { storeReading ("consumer${c}_currentPower", $pcurr.' W'); } - if(defined $ehist && $etot >= $ehist && ($etot - $ehist) >= $ethreshold) { + if (defined $ehist && $etot >= $ehist && ($etot - $ehist) >= $ethreshold) { my $consumerco = $etot - $ehist; $consumerco += HistoryVal ($hash, $day, sprintf("%02d",$nhour), "csme${c}", 0); @@ -7153,12 +7331,13 @@ sub _manageConsumerData { $paref->{val} = ConsumerVal ($hash, $c, "numberDayStarts", 0); # Anzahl Tageszyklen des Verbrauchers speichern $paref->{histname} = "cyclescsm${c}"; setPVhistory ($paref); - delete $paref->{histname}; $paref->{val} = ceil ConsumerVal ($hash, $c, "minutesOn", 0); # Verbrauchsminuten akt. Stunde des Consumers $paref->{histname} = "minutescsm${c}"; setPVhistory ($paref); + delete $paref->{histname}; + delete $paref->{val}; ## Durchschnittsverbrauch / Betriebszeit ermitteln + speichern ################################################################ @@ -7177,7 +7356,7 @@ sub _manageConsumerData { } if ($dnum) { - if($consumerco && $runhours) { + if ($consumerco && $runhours) { $data{$type}{$name}{consumers}{$c}{avgenergy} = sprintf "%.2f", ($consumerco/$runhours); # Durchschnittsverbrauch pro Stunde in Wh } else { @@ -8956,7 +9135,7 @@ sub _calcCaQsimple { $paref->{pvrl} = $pvrl; $paref->{pvfc} = $pvfc; - $paref->{range} = 'percentile'; + $paref->{range} = 'simple'; $paref->{calc} = 'Simple'; my ($oldfac, $factor, $dnum) = __calcNewFactor ($paref); @@ -9758,11 +9937,20 @@ sub _checkSetupNotComplete { ### nicht mehr benötigte Daten löschen - Bereich kann später wieder raus !! ########################################################################################## ## currentWeatherDev in Attr umsetzen - ## currentWeatherDev in Attr umsetzen - my $cwd = ReadingsVal ($name, 'currentWeatherDev', ''); + my $cwd = ReadingsVal ($name, 'currentWeatherDev', ''); # 30.01.2024 if ($cwd) { CommandAttr (undef, "$name ctrlWeatherDev1 $cwd"); } + + my $mdr = ReadingsVal ($name, 'moduleDirection', undef); # 09.02.2024 + if ($mdr) { + readingsSingleUpdate ($hash, 'moduleAzimuth', $mdr, 0); + } + + my $mta = ReadingsVal ($name, 'moduleTiltAngle', undef); + if ($mta) { + readingsSingleUpdate ($hash, 'moduleDeclination', $mta, 0); + } ########################################################################################## my $is = ReadingsVal ($name, 'inverterStrings', undef); # String Konfig @@ -9772,8 +9960,8 @@ sub _checkSetupNotComplete { my $medev = ReadingsVal ($name, 'currentMeterDev', undef); # Meter Device my $peaks = ReadingsVal ($name, 'modulePeakString', undef); # String Peak - my $dir = ReadingsVal ($name, 'moduleDirection', undef); # Modulausrichtung Konfig (Azimut) - my $ta = ReadingsVal ($name, 'moduleTiltAngle', undef); # Modul Neigungswinkel Konfig + my $dir = ReadingsVal ($name, 'moduleAzimuth', undef); # Modulausrichtung Konfig (Azimut) + my $ta = ReadingsVal ($name, 'moduleDeclination', undef); # Modul Neigungswinkel Konfig my $mrt = ReadingsVal ($name, 'moduleRoofTops', undef); # RoofTop Konfiguration (SolCast API) my $vrmcr = SolCastAPIVal ($hash, '?VRM', '?API', 'credentials', ''); # Victron VRM Credentials gesetzt @@ -9809,7 +9997,7 @@ sub _checkSetupNotComplete { my $chkicon = "$img"; my $chktitle = $htitles{plchk}{$lang}; - if (!$is || !$wedev || !$radev || !$indev || !$medev || !$peaks || + if (!$is || !$wedev || !$radev || !$indev || !$medev || !$peaks || (isSolCastUsed ($hash) ? (!$rip || !$mrt) : isVictronKiUsed ($hash) ? !$vrmcr : (!$dir || !$ta )) || (isForecastSolarUsed ($hash) ? !$coset : '') || !defined $pv0) { @@ -9976,10 +10164,10 @@ sub _graphicHeader { ## Solare API Sektion ######################## - my $api = isSolCastUsed ($hash) ? 'SolCast:' : - isForecastSolarUsed ($hash) ? 'Forecast.Solar:' : - isVictronKiUsed ($hash) ? 'VictronVRM:' : - isDWDUsed ($hash) ? 'DWD:' : + my $api = isSolCastUsed ($hash) ? 'SolCast:' : + isForecastSolarUsed ($hash) ? 'Forecast.Solar:' : + isVictronKiUsed ($hash) ? 'VictronVRM:' : + isDWDUsed ($hash) ? 'DWD:' : q{}; my $nscc = ReadingsVal ($name, 'nextSolCastCall', '?'); @@ -9995,7 +10183,7 @@ sub _graphicHeader { } } - if ($api eq 'SolCast:') { + if ($api =~ /SolCast/xs) { $api .= ' '.$lrt; if ($scrm eq 'success') { @@ -10025,7 +10213,7 @@ sub _graphicHeader { $api .= ')'; $api .= ''; } - elsif ($api eq 'Forecast.Solar:') { + elsif ($api =~ /Forecast.Solar/xs) { $api .= ' '.$lrt; if ($scrm eq 'success') { @@ -10049,7 +10237,7 @@ sub _graphicHeader { $api .= ')'; $api .= ''; } - elsif ($api eq 'VictronVRM:') { + elsif ($api =~ /VictronVRM/xs) { $api .= ' '.$lrt; if ($scrm eq 'success') { @@ -10068,7 +10256,7 @@ sub _graphicHeader { $api .= ')'; $api .= ''; } - elsif ($api eq 'DWD:') { + elsif ($api =~ /DWD/xs) { $nscc = ReadingsVal ($name, 'nextCycletime', '?'); $api .= ' '.$lrt; @@ -11933,7 +12121,7 @@ sub formatVal6 { my $t = $v - int($v); # Nachkommstelle ? if (!$t) { # glatte Zahl ohne Nachkommastelle - if(!$v) { + if (!$v) { return ' '; # 0 nicht anzeigen, passt eigentlich immer bis auf einen Fall im Typ diff } elsif ($v < 10) { @@ -12201,15 +12389,17 @@ sub aiAddInstance { ## no critic "not used" my $rad1h = AiRawdataVal ($hash, $idx, 'rad1h', 0); next if($rad1h <= 0); - my $temp = AiRawdataVal ($hash, $idx, 'temp', 20); - my $wcc = AiRawdataVal ($hash, $idx, 'wcc', 0); - my $wrp = AiRawdataVal ($hash, $idx, 'wrp', 0); + my $temp = AiRawdataVal ($hash, $idx, 'temp', 20); + my $wcc = AiRawdataVal ($hash, $idx, 'wcc', 0); + my $wrp = AiRawdataVal ($hash, $idx, 'wrp', 0); + my $sunalt = AiRawdataVal ($hash, $idx, 'sunalt', 0); - eval { $dtree->add_instance (attributes => { rad1h => $rad1h, - temp => $temp, - wcc => $wcc, - wrp => $wrp, - hod => $hod + eval { $dtree->add_instance (attributes => { rad1h => $rad1h, + temp => $temp, + wcc => $wcc, + wrp => $wrp, + sunalt => $sunalt, + hod => $hod }, result => $pvrl ) @@ -12219,7 +12409,7 @@ sub aiAddInstance { ## no critic "not used" return; }; - debugLog ($paref, 'aiProcess', qq{AI Instance added - hod: $hod, rad1h: $rad1h, pvrl: $pvrl, wcc: $wcc, wrp: $wrp, temp: $temp}); + debugLog ($paref, 'aiProcess', qq{AI Instance added - hod: $hod, sunalt: $sunalt, rad1h: $rad1h, pvrl: $pvrl, wcc: $wcc, wrp: $wrp, temp: $temp}); } $data{$type}{$name}{aidectree}{object} = $dtree; @@ -12303,7 +12493,7 @@ return; ################################################################ # AI Ergebnis für ermitteln ################################################################ -sub aiGetResult { ## no critic "not used" +sub aiGetResult { my $paref = shift; my $hash = $paref->{hash}; my $name = $paref->{name}; @@ -12311,7 +12501,7 @@ sub aiGetResult { ## no critic "not used" my $hod = $paref->{hod}; my $nhidx = $paref->{nhidx}; - return 'the AI usage is not prepared' if(!isPrepared4AI ($hash, 'full')); + return 'AI usage is not prepared' if(!isPrepared4AI ($hash, 'full')); my $dtree = AiDetreeVal ($hash, 'aitrained', undef); @@ -12321,22 +12511,27 @@ sub aiGetResult { ## no critic "not used" my $rad1h = NexthoursVal ($hash, $nhidx, "rad1h", 0); return "no rad1h for hod: $hod" if($rad1h <= 0); + + debugLog ($paref, 'aiData', 'Start AI result check now'); - my $wcc = NexthoursVal ($hash, $nhidx, "cloudcover", 0); - my $wrp = NexthoursVal ($hash, $nhidx, "rainprob", 0); - my $temp = NexthoursVal ($hash, $nhidx, "temp", 20); + my $wcc = NexthoursVal ($hash, $nhidx, 'cloudcover', 0); + my $wrp = NexthoursVal ($hash, $nhidx, 'rainprob', 0); + my $temp = NexthoursVal ($hash, $nhidx, 'temp', 20); + my $sunalt = NexthoursVal ($hash, $nhidx, 'sunalt', 0); - my $tbin = temp2bin ($temp); - my $cbin = cloud2bin ($wcc); - my $rbin = rain2bin ($wrp); + my $tbin = temp2bin ($temp); + my $cbin = cloud2bin ($wcc); + my $rbin = rain2bin ($wrp); + my $sabin = sunalt2bin ($sunalt); my $pvaifc; - eval { $pvaifc = $dtree->get_result (attributes => { rad1h => $rad1h, - temp => $tbin, - wcc => $cbin, - wrp => $rbin, - hod => $hod + eval { $pvaifc = $dtree->get_result (attributes => { rad1h => $rad1h, + temp => $tbin, + wcc => $cbin, + wrp => $rbin, + sunalt => $sabin, + hod => $hod } ); }; @@ -12347,30 +12542,29 @@ sub aiGetResult { ## no critic "not used" } if (defined $pvaifc) { - debugLog ($paref, 'aiData', qq{accurate result AI: pvaifc: $pvaifc (hod: $hod, rad1h: $rad1h, wcc: $wcc, wrp: $rbin, temp: $tbin)}); - return ('', $pvaifc); + debugLog ($paref, 'aiData', qq{AI accurate result found: pvaifc: $pvaifc (hod: $hod, sunalt: $sabin, Rad1h: $rad1h, wcc: $wcc, wrp: $rbin, temp: $tbin)}); + return ('accurate', $pvaifc); } - my $msg = 'no decition delivered'; - - ($msg, $pvaifc) = _aiGetSpread ( { hash => $hash, - name => $name, - type => $type, - rad1h => $rad1h, - temp => $tbin, - wcc => $cbin, - wrp => $rbin, - hod => $hod, - dtree => $dtree, - debug => $paref->{debug} - } - ); + (my $msg, $pvaifc) = _aiGetSpread ( { hash => $hash, + name => $name, + type => $type, + rad1h => $rad1h, + temp => $tbin, + wcc => $cbin, + wrp => $rbin, + sunalt => $sabin, + hod => $hod, + dtree => $dtree, + debug => $paref->{debug} + } + ); if (defined $pvaifc) { - return ('', $pvaifc); + return ($msg, $pvaifc); } -return $msg; +return 'No AI decition delivered'; } ################################################################ @@ -12378,32 +12572,38 @@ return $msg; # rad1h-Abweichung schätzen ################################################################ sub _aiGetSpread { - my $paref = shift; - my $rad1h = $paref->{rad1h}; - my $temp = $paref->{temp}; - my $wcc = $paref->{wcc}; - my $wrp = $paref->{wrp}; - my $hod = $paref->{hod}; - my $dtree = $paref->{dtree}; + my $paref = shift; + my $rad1h = $paref->{rad1h}; + my $temp = $paref->{temp}; + my $wcc = $paref->{wcc}; + my $wrp = $paref->{wrp}; + my $sunalt = $paref->{sunalt}; + my $hod = $paref->{hod}; + my $dtree = $paref->{dtree}; my $dtn = 20; # positive und negative rad1h Abweichung testen mit Schrittweite "$step" my $step = 10; my ($pos, $neg, $p, $n); - debugLog ($paref, 'aiData', qq{no accurate result AI found with initial value "$rad1h"}); - debugLog ($paref, 'aiData', qq{test AI estimation with variance "$dtn", positive/negative step "$step"}); + debugLog ($paref, 'aiData', qq{AI no accurate result found with initial value "Rad1h: $rad1h" (hod: $hod)}); + debugLog ($paref, 'aiData', qq{AI test Rad1h variance "$dtn" and positive/negative spread with step size "$step"}); + + my $gra = { + temp => $temp, + wcc => $wcc, + wrp => $wrp, + sunalt => $sunalt, + hod => $hod + }; - for ($p = $rad1h; $p <= $rad1h + $dtn; $p += $step) { - $p = sprintf "%.2f", $p; + for ($p = $rad1h + $step; $p <= $rad1h + $dtn; $p += $step) { + $p = sprintf "%.2f", $p; + $gra->{rad1h} = $p; + + debugLog ($paref, 'aiData', qq{AI positive test value "Rad1h: $p"}); - eval { $pos = $dtree->get_result (attributes => { rad1h => $p, - temp => $temp, - wcc => $wcc, - wrp => $wrp, - hod => $hod - } - ); + eval { $pos = $dtree->get_result (attributes => $gra); }; if ($@) { @@ -12411,22 +12611,19 @@ sub _aiGetSpread { } if ($pos) { - debugLog ($paref, 'aiData', qq{AI estimation with test value "$p": $pos}); + debugLog ($paref, 'aiData', qq{AI positive tolerance hit: $pos Wh}); last; } } - for ($n = $rad1h; $n >= $rad1h - $dtn; $n -= $step) { + for ($n = $rad1h - $step; $n >= $rad1h - $dtn; $n -= $step) { last if($n <= 0); - $n = sprintf "%.2f", $n; + $n = sprintf "%.2f", $n; + $gra->{rad1h} = $n; + + debugLog ($paref, 'aiData', qq{AI negative test value "Rad1h: $n"}); - eval { $neg = $dtree->get_result (attributes => { rad1h => $n, - temp => $temp, - wcc => $wcc, - wrp => $wrp, - hod => $hod - } - ); + eval { $neg = $dtree->get_result (attributes => $gra); }; if ($@) { @@ -12434,19 +12631,22 @@ sub _aiGetSpread { } if ($neg) { - debugLog ($paref, 'aiData', qq{AI estimation with test value "$n": $neg}); + debugLog ($paref, 'aiData', qq{AI negative tolerance hit: $neg Wh}); last; } } - my $pvaifc = $pos && $neg ? sprintf "%.0f", (($pos + $neg) / 2) : undef; + my $pvaifc = $pos && $neg ? sprintf "%.0f", (($pos + $neg) / 2) : + $pos ? sprintf "%.0f", $pos : + $neg ? sprintf "%.0f", $neg : + undef; if (defined $pvaifc) { - debugLog ($paref, 'aiData', qq{appreciated result AI: pvaifc: $pvaifc (hod: $hod, wcc: $wcc, wrp: $wrp, temp: $temp)}); - return ('', $pvaifc); + debugLog ($paref, 'aiData', qq{AI determined average result: pvaifc: $pvaifc Wh (hod: $hod, sunalt: $sunalt, wcc: $wcc, wrp: $wrp, temp: $temp)}); + return ('spreaded', $pvaifc); } -return 'no decition delivered'; +return 'No AI decition delivered'; } ################################################################ @@ -12520,24 +12720,27 @@ sub aiAddRawData { my $ridx = _aiMakeIdxRaw ($pvd, $hod); - my $temp = HistoryVal ($hash, $pvd, $hod, 'temp', 20); - my $wcc = HistoryVal ($hash, $pvd, $hod, 'wcc', 0); - my $wrp = HistoryVal ($hash, $pvd, $hod, 'wrp', 0); + my $temp = HistoryVal ($hash, $pvd, $hod, 'temp', 20); + my $wcc = HistoryVal ($hash, $pvd, $hod, 'wcc', 0); + my $wrp = HistoryVal ($hash, $pvd, $hod, 'wrp', 0); + my $sunalt = HistoryVal ($hash, $pvd, $hod, 'sunalt', 0); + + my $tbin = temp2bin ($temp); + my $cbin = cloud2bin ($wcc); + my $rbin = rain2bin ($wrp); + my $sabin = sunalt2bin ($sunalt); - my $tbin = temp2bin ($temp); - my $cbin = cloud2bin ($wcc); - my $rbin = rain2bin ($wrp); - - $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}{wrp} = $rbin; - $data{$type}{$name}{aidectree}{airaw}{$ridx}{hod} = $hod; - $data{$type}{$name}{aidectree}{airaw}{$ridx}{pvrl} = $pvrl; + $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}{wrp} = $rbin; + $data{$type}{$name}{aidectree}{airaw}{$ridx}{hod} = $hod; + $data{$type}{$name}{aidectree}{airaw}{$ridx}{pvrl} = $pvrl; + $data{$type}{$name}{aidectree}{airaw}{$ridx}{sunalt} = $sabin if($sabin); $dosave = 1; - debugLog ($paref, 'aiProcess', qq{AI raw data added - idx: $ridx, day: $pvd, hod: $hod, rad1h: $rad1h, pvrl: $pvrl, wcc: $cbin, wrp: $rbin, temp: $tbin}); + debugLog ($paref, 'aiProcess', "AI raw add - idx: $ridx, day: $pvd, hod: $hod,".($sabin ? qq{ sunalt: $sabin,} : '')." rad1h: $rad1h, pvrl: $pvrl, wcc: $cbin, wrp: $rbin, temp: $tbin"); } } @@ -12631,9 +12834,7 @@ sub setPVhistory { my $batinthishour = $paref->{batinthishour}; # Batterieladung in Stunde my $btotin = $paref->{batintotal}; # totale Batterieladung my $batoutthishour = $paref->{batoutthishour}; # Batterieentladung in Stunde - my $btotout = $paref->{batouttotal}; # totale Batterieentladung - my $batmaxsoc = $paref->{batmaxsoc}; # max. erreichter SOC des Tages - my $batsetsoc = $paref->{batsetsoc}; # berechneter optimaler SOC für den laufenden Tag + my $btotout = $paref->{batouttotal}; # totale Batterieentladung my $calcpv = $paref->{calcpv} // 0; my $gcthishour = $paref->{gctotthishour} // 0; # Netzbezug my $fithishour = $paref->{gftotthishour} // 0; # Netzeinspeisung @@ -12645,7 +12846,7 @@ sub setPVhistory { my $wrp = $paref->{wrp} // 0; # Wahrscheinlichkeit von Niederschlag my $pvcorrf = $paref->{pvcorrf} // "1.00/0"; # pvCorrectionFactor my $temp = $paref->{temp}; # Außentemperatur - my $val = $paref->{val} // qq{}; # Wert zur Speicherung in pvHistory (soll mal generell verwendet werden -> Change) + my $val = $paref->{val}; # Wert zur Speicherung in pvHistory (soll mal generell verwendet werden -> Change) my $rad1h = $paref->{rad1h}; # Strahlungsdaten speichern my $reorg = $paref->{reorg} // 0; # Neuberechnung von Werten in Stunde "99" nach Löschen von Stunden eines Tages my $reorgday = $paref->{reorgday} // q{}; # Tag der reorganisiert werden soll @@ -12678,14 +12879,12 @@ sub setPVhistory { $data{$type}{$name}{pvhist}{$day}{99}{batout} = $batoutsum; } - if ($histname eq "batmaxsoc") { # max. erreichter SOC des Tages - $val = $batmaxsoc; - $data{$type}{$name}{pvhist}{$day}{99}{batmaxsoc} = $batmaxsoc; + if ($histname eq 'batmaxsoc') { # max. erreichter SOC des Tages + $data{$type}{$name}{pvhist}{$day}{$nhour}{$histname} = $val; } - if ($histname eq "batsetsoc") { # optimaler SOC für den Tages - $val = $batsetsoc; - $data{$type}{$name}{pvhist}{$day}{99}{batsetsoc} = $batsetsoc; + if ($histname eq 'batsetsoc') { # optimaler SOC für den Tages + $data{$type}{$name}{pvhist}{$day}{$nhour}{$histname} = $val; } if ($histname eq 'pvrl') { # realer Energieertrag @@ -12784,6 +12983,10 @@ sub setPVhistory { } } + if ($histname =~ /sunaz|sunalt/xs) { # Sonnenstand Azimuth / Altitude + $data{$type}{$name}{pvhist}{$day}{$nhour}{$histname} = $val; + } + if ($histname =~ /cyclescsm[0-9]+$/xs) { # Anzahl Tageszyklen des Verbrauchers $data{$type}{$name}{pvhist}{$day}{99}{$histname} = $val; } @@ -12883,11 +13086,11 @@ sub setPVhistory { $data{$type}{$name}{pvhist}{$reorgday}{99}{gfeedin} = $r7; $data{$type}{$name}{pvhist}{$reorgday}{99}{con} = $r8; - debugLog ($paref, 'saveData2Cache', "setPVhistory -> PV History day >$reorgday< reorganized keys: batin, batout, pvrl, pvfc, con, confc, gcons, gfeedin"); + debugLog ($paref, 'saveData2Cache', "setPVhistory -> Day >$reorgday< reorganized keys: batin, batout, pvrl, pvfc, con, confc, gcons, gfeedin"); } if ($histname) { - debugLog ($paref, 'saveData2Cache', "setPVhistory -> save PV History Day: $day, Hour: $nhour, Key: $histname, Value: $val"); + debugLog ($paref, 'saveData2Cache', "setPVhistory -> save Day: $day, Hour: $nhour, Key: $histname, Value: ".(defined $val ? $val : '')); } return; @@ -12932,12 +13135,17 @@ sub listDataPool { 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', '-'); $ret .= "\n " if($ret); - $ret .= $key." => etotal: $etotal, pvfc: $pvfc, pvrl: $pvrl, pvrlvd: $pvrlvd, rad1h: $rad1h"; + $ret .= $key." => "; + $ret .= "etotal: $etotal, pvfc: $pvfc, pvrl: $pvrl, pvrlvd: $pvrlvd, rad1h: $rad1h"; $ret .= "\n "; $ret .= "confc: $confc, con: $con, gcon: $gcon, gfeedin: $gfeedin"; $ret .= "\n "; + $ret .= "sunaz: $sunaz, sunalt: $sunalt"; + $ret .= "\n "; $ret .= "batintotal: $btotin, batin: $batin, batouttotal: $btotout, batout: $batout"; $ret .= "\n "; $ret .= "batmaxsoc: $batmsoc, batsetsoc: $batssoc"; @@ -13151,11 +13359,20 @@ sub listDataPool { 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', '-'); + $sq .= "\n" if($sq); - $sq .= $idx." => starttime: $nhts, hourofday: $hod, today: $today\n"; - $sq .= " pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, confc: $confc\n"; - $sq .= " confcEx: $confcex, DoN: $don, wid: $wid, wcc: $neff, wrp: $r101, temp=$temp\n"; - $sq .= " rad1h: $rad1h, rrange: $rrange, crange: $crange, correff: $pvcorrf"; + $sq .= $idx." => "; + $sq .= "starttime: $nhts, hourofday: $hod, today: $today"; + $sq .= "\n "; + $sq .= "pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, confc: $confc"; + $sq .= "\n "; + $sq .= "confcEx: $confcex, DoN: $don, wid: $wid, wcc: $neff, wrp: $r101, temp=$temp"; + $sq .= "\n "; + $sq .= "rad1h: $rad1h, sunaz: $sunaz, sunalt: $sunalt"; + $sq .= "\n "; + $sq .= "rrange: $rrange, crange: $crange, correff: $pvcorrf"; } } @@ -13167,15 +13384,16 @@ sub listDataPool { for my $idx (sort keys %{$h}) { my $nhfc = NexthoursVal ($hash, $idx, 'pvfc', undef); next if(!$nhfc); - my $nhts = NexthoursVal ($hash, $idx, 'starttime', undef); - my $neff = NexthoursVal ($hash, $idx, 'cloudcover', '-'); - my $crange = NexthoursVal ($hash, $idx, 'cloudrange', '-'); - my $r101 = NexthoursVal ($hash, $idx, 'rainprob', '-'); - my $rrange = NexthoursVal ($hash, $idx, 'rainrange', '-'); - my $pvcorrf = NexthoursVal ($hash, $idx, 'pvcorrf', '-/-'); + + my $nhts = NexthoursVal ($hash, $idx, 'starttime', '-'); + my $pvcorrf = NexthoursVal ($hash, $idx, 'pvcorrf', '-/-'); + my $aihit = NexthoursVal ($hash, $idx, 'aihit', '-'); + my $pvfc = NexthoursVal ($hash, $idx, 'pvfc', '-'); + my $neff = NexthoursVal ($hash, $idx, 'cloudcover', '-'); + my ($f,$q) = split "/", $pvcorrf; $sq .= "\n" if($sq); - $sq .= "starttime: $nhts, wrp: $r101, rrange: $rrange, wcc: $neff, crange: $crange, quality: $q, factor: $f"; + $sq .= "Start: $nhts, Quality: $q, Factor: $f, AI usage: $aihit, PV expect: $pvfc Wh, Cloud: $neff"; } } @@ -13252,14 +13470,15 @@ sub listDataPool { $sq = "Number of datasets: ".$maxcnt."\n"; for my $idx (sort keys %{$h}) { - my $hod = AiRawdataVal ($hash, $idx, 'hod', '-'); - my $rad1h = AiRawdataVal ($hash, $idx, 'rad1h', '-'); - my $wcc = AiRawdataVal ($hash, $idx, 'wcc', '-'); - my $wrp = AiRawdataVal ($hash, $idx, 'wrp', '-'); - my $pvrl = AiRawdataVal ($hash, $idx, 'pvrl', '-'); - my $temp = AiRawdataVal ($hash, $idx, 'temp', '-'); - $sq .= "\n"; - $sq .= "$idx => hod: $hod, rad1h: $rad1h, wcc: $wcc, wrp: $wrp, pvrl: $pvrl, temp: $temp"; + my $hod = AiRawdataVal ($hash, $idx, 'hod', '-'); + my $sunalt = AiRawdataVal ($hash, $idx, 'sunalt', '-'); + my $rad1h = AiRawdataVal ($hash, $idx, 'rad1h', '-'); + my $wcc = AiRawdataVal ($hash, $idx, 'wcc', '-'); + my $wrp = AiRawdataVal ($hash, $idx, 'wrp', '-'); + my $pvrl = AiRawdataVal ($hash, $idx, 'pvrl', '-'); + my $temp = AiRawdataVal ($hash, $idx, 'temp', '-'); + $sq .= "\n"; + $sq .= "$idx => hod: $hod, sunalt: $sunalt, rad1h: $rad1h, wcc: $wcc, wrp: $wrp, pvrl: $pvrl, temp: $temp"; } } @@ -13282,7 +13501,7 @@ sub _ldchash2val { no warnings 'numeric'; for my $f (sort {$a<=>$b} keys %{$pool->{$idx}{$key}}) { - next if($f eq 'percentile'); + next if($f eq 'simple'); $ret .= " " if($ret); $ret .= "$f=".$pool->{$idx}{$key}{$f}; my $ct = ($ret =~ tr/=// // 0) / 10; @@ -13291,10 +13510,10 @@ sub _ldchash2val { use warnings; - if (defined $pool->{$idx}{$key}{percentile}) { + if (defined $pool->{$idx}{$key}{simple}) { $ret .= "\n " if($ret && $ret !~ /\n\s+$/xs); - $ret .= " " if($ret); - $ret .= "percentile=".$pool->{$idx}{$key}{percentile}; + $ret .= " " if($ret); + $ret .= "simple=".$pool->{$idx}{$key}{simple}; } } else { @@ -13339,10 +13558,6 @@ sub checkPlantConfig { my $raname = ReadingsVal ($name, 'currentRadiationAPI', ''); my ($acu, $aln) = isAutoCorrUsed ($name); - my $cf = 0; # config fault: 1 -> Konfig fehlerhaft, 0 -> Konfig ok - my $wn = 0; # Warnung wenn 1 - my $io = 0; # Info wenn 1 - my $ok = FW_makeImage ('10px-kreis-gruen.png', ''); my $nok = FW_makeImage ('10px-kreis-rot.png', ''); my $warn = FW_makeImage ('message_attention@orange', ''); @@ -13415,23 +13630,31 @@ sub checkPlantConfig { ##################################### for my $step (1..$weatherDevMax) { my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$step, ''); + next if(!$fcname && $step ne 1); if (!$fcname || !$defs{$fcname}) { - $result->{'DWD Weather Attributes'}{state} = $nok; - $result->{'DWD Weather Attributes'}{result} .= qq{The DWD device "$fcname" doesn't exist.
}; - $result->{'DWD Weather Attributes'}{fault} = 1; + $result->{'DWD Weather Attributes'}{state} = $nok; + + if (!$fcname) { + $result->{'DWD Weather Attributes'}{result} .= qq{No DWD device is defined in attribute "ctrlWeatherDev$step".
}; + } + else { + $result->{'DWD Weather Attributes'}{result} .= qq{The DWD device "$fcname" doesn't exist.
}; + } + + $result->{'DWD Weather Attributes'}{fault} = 1; } else { - $result->{'DWD Weather Attributes'}{note} .= qq{checked attributes of device "$fcname":
}. join (' ', @dweattrmust). '
'; + $result->{'DWD Weather Attributes'}{note} .= qq{checked attributes of device "$fcname":
}. join (' ', @dweattrmust).'
'; $err = checkdwdattr ($name, $fcname, \@dweattrmust); if ($err) { $result->{'DWD Weather Attributes'}{state} = $nok; - $result->{'DWD Weather Attributes'}{result} .= $err. '
'; + $result->{'DWD Weather Attributes'}{result} .= $err.'
'; $result->{'DWD Weather Attributes'}{fault} = 1; } else { - $result->{'DWD Weather Attributes'}{result} = $hqtxt{fulfd}{$lang}; + $result->{'DWD Weather Attributes'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: ctrlWeatherDev$step)
"; } } } @@ -13519,11 +13742,11 @@ sub checkPlantConfig { ## Allgemeine Settings ######################## - my $eocr = AttrVal ($name, 'event-on-change-reading', ''); - my $aiprep = isPrepared4AI ($hash, 'full'); - my $aiusemsg = CurrentVal ($hash, 'aicanuse', ''); - my ($cset, $lat, $lon) = locCoordinates(); - my $einstds = ""; + my $eocr = AttrVal ($name, 'event-on-change-reading', ''); + my $aiprep = isPrepared4AI ($hash, 'full'); + my $aiusemsg = CurrentVal ($hash, 'aicanuse', ''); + my ($cset, $lat, $lon, $alt) = locCoordinates(); + my $einstds = ""; if (!$eocr || $eocr ne '.*') { $einstds = 'to .*' if($eocr ne '.*'); @@ -13553,6 +13776,13 @@ sub checkPlantConfig { $result->{'Common Settings'}{note} .= qq{Set the coordinates of your installation in the longitude attribute of the global device.
}; $result->{'Common Settings'}{warn} = 1; } + + if (!$alt) { + $result->{'Common Settings'}{state} = $nok; + $result->{'Common Settings'}{result} .= qq{Attribute altitude in global device is not set.
}; + $result->{'Common Settings'}{note} .= qq{Set the altitude in meters above sea level in the altitude attribute of the global device.
}; + $result->{'Common Settings'}{fault} = 1; + } if (!$aiprep) { $result->{'Common Settings'}{state} = $info; @@ -13583,9 +13813,7 @@ sub checkPlantConfig { $result->{'Common Settings'}{warn} = 1; } - ## allg. Settings bei Nutzung Forecast.Solar API - ################################################# - if (isForecastSolarUsed ($hash)) { + if (isForecastSolarUsed ($hash)) { # allg. Settings bei Nutzung Forecast.Solar API if (!$pcf || $pcf !~ /on/xs) { $result->{'Common Settings'}{state} = $info; $result->{'Common Settings'}{result} .= qq{pvCorrectionFactor_Auto is set to "$pcf"
}; @@ -13606,17 +13834,14 @@ sub checkPlantConfig { $result->{'Common Settings'}{fault} = 1; } - if (!$result->{'Common Settings'}{fault} && !$result->{'Common Settings'}{warn} && !$result->{'Common Settings'}{info}) { + if (!$result->{'Common Settings'}{fault}) { $result->{'Common Settings'}{result} = $hqtxt{fulfd}{$lang}; - $result->{'Common Settings'}{note} .= qq{checked parameters:
}; - $result->{'Common Settings'}{note} .= qq{global latitude, global longitude
}; + $result->{'Common Settings'}{note} .= qq{
checked parameters and attributes:
}; $result->{'Common Settings'}{note} .= qq{pvCorrectionFactor_Auto
}; } } - ## allg. Settings bei Nutzung SolCast - ####################################### - if (isSolCastUsed ($hash)) { + if (isSolCastUsed ($hash)) { # allg. Settings bei Nutzung SolCast API my $gdn = AttrVal ('global', 'dnsServer', ''); my $osi = AttrVal ($name, 'ctrlSolCastAPIoptimizeReq', 0); @@ -13655,17 +13880,14 @@ sub checkPlantConfig { $result->{'API Access'}{fault} = 1; } - if (!$result->{'Common Settings'}{fault} && !$result->{'Common Settings'}{warn} && !$result->{'Common Settings'}{info}) { + if (!$result->{'Common Settings'}{fault}) { $result->{'Common Settings'}{result} = $hqtxt{fulfd}{$lang}; - $result->{'Common Settings'}{note} .= qq{checked parameters:
}; - $result->{'Common Settings'}{note} .= qq{ctrlSolCastAPIoptimizeReq
}; - $result->{'Common Settings'}{note} .= qq{pvCorrectionFactor_Auto, event-on-change-reading, ctrlLanguage, global language, global dnsServer
}; + $result->{'Common Settings'}{note} .= qq{
checked parameters and attributes:
}; + $result->{'Common Settings'}{note} .= qq{pvCorrectionFactor_Auto, ctrlSolCastAPIoptimizeReq, global dnsServer
}; } } - ## allg. Settings bei Nutzung DWD API - ####################################### - if (isDWDUsed ($hash)) { + if (isDWDUsed ($hash)) { # allg. Settings bei Nutzung DWD API my $lam = SolCastAPIVal ($hash, '?All', '?All', 'response_message', 'success'); if ($aidtabs) { @@ -13690,20 +13912,18 @@ sub checkPlantConfig { $result->{'API Access'}{fault} = 1; } - if (!$result->{'Common Settings'}{fault} && !$result->{'Common Settings'}{warn} && !$result->{'Common Settings'}{info}) { + if (!$result->{'Common Settings'}{fault}) { $result->{'Common Settings'}{result} = $hqtxt{fulfd}{$lang}; - $result->{'Common Settings'}{note} .= qq{checked parameters:
}; - $result->{'Common Settings'}{note} .= qq{pvCorrectionFactor_Auto, event-on-change-reading, ctrlLanguage, global language
}; - $result->{'Common Settings'}{note} .= qq{checked Perl modules:
}; + $result->{'Common Settings'}{note} .= qq{
checked Perl modules:
}; $result->{'Common Settings'}{note} .= qq{AI::DecisionTree
}; + $result->{'Common Settings'}{note} .= qq{
checked parameters and attributes:
}; + $result->{'Common Settings'}{note} .= qq{pvCorrectionFactor_Auto
}; } } - ## allg. Settings bei Nutzung VictronKI-API - ############################################# - if (isVictronKiUsed ($hash)) { + if (isVictronKiUsed ($hash)) { # allg. Settings bei Nutzung VictronKI-API my $gdn = AttrVal ('global', 'dnsServer', ''); - my $vrmcr = SolCastAPIVal ($hash, '?VRM', '?API', 'credentials', ''); # Victron VRM Credentials gesetzt + my $vrmcr = SolCastAPIVal ($hash, '?VRM', '?API', 'credentials', ''); if ($pcf && $pcf !~ /off/xs) { $result->{'Common Settings'}{state} = $warn; @@ -13726,13 +13946,18 @@ sub checkPlantConfig { $result->{'API Access'}{fault} = 1; } - if (!$result->{'Common Settings'}{fault} && !$result->{'Common Settings'}{warn} && !$result->{'Common Settings'}{info}) { + if (!$result->{'Common Settings'}{fault}) { $result->{'Common Settings'}{result} = $hqtxt{fulfd}{$lang}; - $result->{'Common Settings'}{note} .= qq{checked parameters:
}; - $result->{'Common Settings'}{note} .= qq{global dnsServer, global language
}; - $result->{'Common Settings'}{note} .= qq{pvCorrectionFactor_Auto, vrmCredentials, event-on-change-reading, ctrlLanguage
}; + $result->{'Common Settings'}{note} .= qq{
checked parameters and attributes:
}; + $result->{'Common Settings'}{note} .= qq{pvCorrectionFactor_Auto, global dnsServer, vrmCredentials
}; } } + + if (!$result->{'Common Settings'}{fault}) { + $result->{'Common Settings'}{result} = $hqtxt{fulfd}{$lang}; + $result->{'Common Settings'}{note} .= qq{global latitude, global longitude, global altitude, global language
}; + $result->{'Common Settings'}{note} .= qq{event-on-change-reading, ctrlLanguage
}; + } ## FTUI Widget Support ######################## @@ -13799,28 +14024,33 @@ sub checkPlantConfig { my $hz = keys %{$result}; my $hc = 0; + my $cf = 0; # config fault: 1 -> Konfig fehlerhaft, 0 -> Konfig ok + my $wn = 0; # Warnung wenn 1 + my $io = 0; # Info wenn 1 for my $key (sort keys %{$result}) { $hc++; - $cf = $result->{$key}{fault} if($result->{$key}{fault}); - $wn = $result->{$key}{warn} if($result->{$key}{warn}); - $io = $result->{$key}{info} if($result->{$key}{info}); + $cf = $result->{$key}{fault} if($result->{$key}{fault}); + $wn = $result->{$key}{warn} if($result->{$key}{warn}); + $io = $result->{$key}{info} if($result->{$key}{info}); + + $result->{$key}{state} = $warn if($result->{$key}{warn}); + $result->{$key}{state} = $nok if($result->{$key}{fault}); + $out .= qq{}; $out .= qq{ $key }; $out .= qq{ $result->{$key}{state} }; - $out .= qq{       }; + $out .= qq{ }; $out .= qq{ $result->{$key}{result} }; - $out .= qq{       }; + $out .= qq{ }; $out .= qq{ $result->{$key}{note} }; $out .= qq{}; - #if ($hc < $hz) { # Tabelle wird auf Tablet zu groß mit Zwischenzeilen - # $out .= qq{}; - # $out .= qq{   }; - # $out .= qq{}; - #} - - $out .= qq{}; + if ($hc < $hz) { # Zwischenzeile + $out .= qq{}; + $out .= qq{   }; + $out .= qq{}; + } } $out .= qq{}; @@ -13877,26 +14107,26 @@ sub timestampToTimestring { return if($epoch !~ /[0-9]/xs); - if (length ($epoch) == 13) { # Millisekunden + if (length ($epoch) == 13) { # Millisekunden $epoch = $epoch / 1000; } my ($lyear,$lmonth,$lday,$lhour,$lmin,$lsec) = (localtime($epoch))[5,4,3,2,1,0]; my $tm; - $lyear += 1900; # year is 1900 based - $lmonth++; # month number is zero based + $lyear += 1900; # year is 1900 based + $lmonth++; # month number is zero based - my ($sec,$min,$hour,$day,$mon,$year) = (localtime(time))[0,1,2,3,4,5]; # Standard f. z.B. Readingstimstamp + my ($sec,$min,$hour,$day,$mon,$year) = (localtime(time))[0,1,2,3,4,5]; # Standard f. z.B. Readingstimstamp $year += 1900; $mon++; - my $realtm = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year,$mon,$day,$hour,$min,$sec); # engl. Variante von aktuellen timestamp - my $tmdef = sprintf("%04d-%02d-%02d %02d:%s", $lyear,$lmonth,$lday,$lhour,"00:00"); # engl. Variante von $epoch für Logging-Timestamps etc. (Minute/Sekunde == 00) - my $tmfull = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $lyear,$lmonth,$lday,$lhour,$lmin,$lsec); # engl. Variante Vollzeit von $epoch + my $realtm = sprintf ("%04d-%02d-%02d %02d:%02d:%02d", $year,$mon,$day,$hour,$min,$sec); # engl. Variante von aktuellen timestamp + my $tmdef = sprintf ("%04d-%02d-%02d %02d:%s", $lyear,$lmonth,$lday,$lhour,"00:00"); # engl. Variante von $epoch für Logging-Timestamps etc. (Minute/Sekunde == 00) + my $tmfull = sprintf ("%04d-%02d-%02d %02d:%02d:%02d", $lyear,$lmonth,$lday,$lhour,$lmin,$lsec); # engl. Variante Vollzeit von $epoch if($lang eq "DE") { - $tm = sprintf("%02d.%02d.%04d %02d:%02d:%02d", $lday,$lmonth,$lyear,$lhour,$lmin,$lsec); # deutsche Variante Vollzeit von $epoch + $tm = sprintf ("%02d.%02d.%04d %02d:%02d:%02d", $lday,$lmonth,$lyear,$lhour,$lmin,$lsec); # deutsche Variante Vollzeit von $epoch } else { $tm = $tmfull; @@ -14761,19 +14991,20 @@ return ($riseshift, $setshift); ################################################################ # Prüfung ob global Attr latitude und longitude gesetzt sind -# gibt latitude und longitude zurück +# gibt latitude, longitude und altitude zurück ################################################################ sub locCoordinates { my $set = 0; my $lat = AttrVal ('global', 'latitude', ''); my $lon = AttrVal ('global', 'longitude', ''); + my $alt = AttrVal ('global', 'altitude', ''); if($lat && $lon) { $set = 1; } -return ($set, $lat, $lon); +return ($set, $lat, $lon, $alt); } ################################################################ @@ -14954,24 +15185,24 @@ return ($cname, $dswname); # diskrete Temperaturen in "Bins" wandeln ################################################################ sub temp2bin { - my $temp = shift; + my $val = shift; - my $bin = $temp > 35 ? '35' : - $temp > 32 ? '35' : - $temp > 30 ? '30' : - $temp > 27 ? '30' : - $temp > 25 ? '25' : - $temp > 22 ? '25' : - $temp > 20 ? '20' : - $temp > 17 ? '20' : - $temp > 15 ? '15' : - $temp > 12 ? '15' : - $temp > 10 ? '10' : - $temp > 7 ? '10' : - $temp > 5 ? '05' : - $temp > 2 ? '05' : - $temp > 0 ? '00' : - '-05'; + my $bin = $val > 35 ? 35 : + $val > 32 ? 35 : + $val > 30 ? 30 : + $val > 27 ? 30 : + $val > 25 ? 25 : + $val > 22 ? 25 : + $val > 20 ? 20 : + $val > 17 ? 20 : + $val > 15 ? 15 : + $val > 12 ? 15 : + $val > 10 ? 10 : + $val > 7 ? 10 : + $val > 5 ? 5 : + $val > 2 ? 5 : + $val > 0 ? 0 : + -5; return $bin; } @@ -14980,48 +15211,48 @@ return $bin; # diskrete Bewölkung in "Bins" wandeln ################################################################ sub cloud2bin { - my $wcc = shift; + my $val = shift; - my $bin = $wcc == 100 ? '100' : - $wcc > 97 ? '100' : - $wcc > 95 ? '95' : - $wcc > 92 ? '95' : - $wcc > 90 ? '90' : - $wcc > 87 ? '90' : - $wcc > 85 ? '85' : - $wcc > 82 ? '85' : - $wcc > 80 ? '80' : - $wcc > 77 ? '80' : - $wcc > 75 ? '75' : - $wcc > 72 ? '75' : - $wcc > 70 ? '70' : - $wcc > 67 ? '70' : - $wcc > 65 ? '65' : - $wcc > 62 ? '65' : - $wcc > 60 ? '60' : - $wcc > 57 ? '60' : - $wcc > 55 ? '55' : - $wcc > 52 ? '55' : - $wcc > 50 ? '50' : - $wcc > 47 ? '50' : - $wcc > 45 ? '45' : - $wcc > 42 ? '45' : - $wcc > 40 ? '40' : - $wcc > 37 ? '40' : - $wcc > 35 ? '35' : - $wcc > 32 ? '35' : - $wcc > 30 ? '30' : - $wcc > 27 ? '30' : - $wcc > 25 ? '25' : - $wcc > 22 ? '25' : - $wcc > 20 ? '20' : - $wcc > 17 ? '20' : - $wcc > 15 ? '15' : - $wcc > 12 ? '15' : - $wcc > 10 ? '10' : - $wcc > 7 ? '10' : - $wcc > 5 ? '05' : - $wcc > 2 ? '05' : + my $bin = $val == 100 ? '100' : + $val > 97 ? '100' : + $val > 95 ? '95' : + $val > 92 ? '95' : + $val > 90 ? '90' : + $val > 87 ? '90' : + $val > 85 ? '85' : + $val > 82 ? '85' : + $val > 80 ? '80' : + $val > 77 ? '80' : + $val > 75 ? '75' : + $val > 72 ? '75' : + $val > 70 ? '70' : + $val > 67 ? '70' : + $val > 65 ? '65' : + $val > 62 ? '65' : + $val > 60 ? '60' : + $val > 57 ? '60' : + $val > 55 ? '55' : + $val > 52 ? '55' : + $val > 50 ? '50' : + $val > 47 ? '50' : + $val > 45 ? '45' : + $val > 42 ? '45' : + $val > 40 ? '40' : + $val > 37 ? '40' : + $val > 35 ? '35' : + $val > 32 ? '35' : + $val > 30 ? '30' : + $val > 27 ? '30' : + $val > 25 ? '25' : + $val > 22 ? '25' : + $val > 20 ? '20' : + $val > 17 ? '20' : + $val > 15 ? '15' : + $val > 12 ? '15' : + $val > 10 ? '10' : + $val > 7 ? '10' : + $val > 5 ? '05' : + $val > 2 ? '05' : '00'; return $bin; @@ -15031,49 +15262,96 @@ return $bin; # diskrete Rain Prob in "Bins" wandeln ################################################################ sub rain2bin { - my $wrp = shift; + my $val = shift; - my $bin = $wrp == 100 ? '100' : - $wrp > 97 ? '100' : - $wrp > 95 ? '95' : - $wrp > 92 ? '95' : - $wrp > 90 ? '90' : - $wrp > 87 ? '90' : - $wrp > 85 ? '85' : - $wrp > 82 ? '85' : - $wrp > 80 ? '80' : - $wrp > 77 ? '80' : - $wrp > 75 ? '75' : - $wrp > 72 ? '75' : - $wrp > 70 ? '70' : - $wrp > 67 ? '70' : - $wrp > 65 ? '65' : - $wrp > 62 ? '65' : - $wrp > 60 ? '60' : - $wrp > 57 ? '60' : - $wrp > 55 ? '55' : - $wrp > 52 ? '55' : - $wrp > 50 ? '50' : - $wrp > 47 ? '50' : - $wrp > 45 ? '45' : - $wrp > 42 ? '45' : - $wrp > 40 ? '40' : - $wrp > 37 ? '40' : - $wrp > 35 ? '35' : - $wrp > 32 ? '35' : - $wrp > 30 ? '30' : - $wrp > 27 ? '30' : - $wrp > 25 ? '25' : - $wrp > 22 ? '25' : - $wrp > 20 ? '20' : - $wrp > 17 ? '20' : - $wrp > 15 ? '15' : - $wrp > 12 ? '15' : - $wrp > 10 ? '10' : - $wrp > 7 ? '10' : - $wrp > 5 ? '05' : - $wrp > 2 ? '05' : - '00'; + my $bin = $val == 100 ? 100 : + $val > 97 ? 100 : + $val > 95 ? 95 : + $val > 92 ? 95 : + $val > 90 ? 90 : + $val > 87 ? 90 : + $val > 85 ? 85 : + $val > 82 ? 85 : + $val > 80 ? 80 : + $val > 77 ? 80 : + $val > 75 ? 75 : + $val > 72 ? 75 : + $val > 70 ? 70 : + $val > 67 ? 70 : + $val > 65 ? 65 : + $val > 62 ? 65 : + $val > 60 ? 60 : + $val > 57 ? 60 : + $val > 55 ? 55 : + $val > 52 ? 55 : + $val > 50 ? 50 : + $val > 47 ? 50 : + $val > 45 ? 45 : + $val > 42 ? 45 : + $val > 40 ? 40 : + $val > 37 ? 40 : + $val > 35 ? 35 : + $val > 32 ? 35 : + $val > 30 ? 30 : + $val > 27 ? 30 : + $val > 25 ? 25 : + $val > 22 ? 25 : + $val > 20 ? 20 : + $val > 17 ? 20 : + $val > 15 ? 15 : + $val > 12 ? 15 : + $val > 10 ? 10 : + $val > 7 ? 10 : + $val > 5 ? 5 : + $val > 2 ? 5 : + 0; + +return $bin; +} + +################################################################ +# diskrete Sonnen Höhe (altitude) in "Bins" wandeln +################################################################ +sub sunalt2bin { + my $val = shift; + + my $bin = $val == 90 ? 90 : + $val > 87 ? 90 : + $val > 85 ? 85 : + $val > 82 ? 85 : + $val > 80 ? 80 : + $val > 77 ? 80 : + $val > 75 ? 75 : + $val > 72 ? 75 : + $val > 70 ? 70 : + $val > 67 ? 70 : + $val > 65 ? 65 : + $val > 62 ? 65 : + $val > 60 ? 60 : + $val > 57 ? 60 : + $val > 55 ? 55 : + $val > 52 ? 55 : + $val > 50 ? 50 : + $val > 47 ? 50 : + $val > 45 ? 45 : + $val > 42 ? 45 : + $val > 40 ? 40 : + $val > 37 ? 40 : + $val > 35 ? 35 : + $val > 32 ? 35 : + $val > 30 ? 30 : + $val > 27 ? 30 : + $val > 25 ? 25 : + $val > 22 ? 25 : + $val > 20 ? 20 : + $val > 17 ? 20 : + $val > 15 ? 15 : + $val > 12 ? 15 : + $val > 10 ? 10 : + $val > 7 ? 10 : + $val > 5 ? 5 : + $val > 2 ? 5 : + 0; return $bin; } @@ -15231,6 +15509,7 @@ return; # batouttotal - totale Batterieentladung (Wh) # batout - Batterieentladung der Stunde (Wh) # batmsoc - max. SOC des Tages (%) +# batmaxsoc - maximum SOC (%) des Tages # batsetsoc - optimaler (berechneter) SOC (%) für den Tag # weatherid - Wetter ID # wcc - Grad der Bewölkung @@ -15240,6 +15519,8 @@ return; # dayname - Tagesname (Kürzel) # csmt${c} - Totalconsumption Consumer $c (1..$maxconsumer) # csme${c} - Consumption Consumer $c (1..$maxconsumer) in $hod +# sunaz - Azimuth der Sonne (in Dezimalgrad) +# sunalt - Höhe der Sonne (in Dezimalgrad) # $def: Defaultwert # ############################################################################### @@ -15334,7 +15615,7 @@ return $def; # $q: Qualität des Korrekturfaktors # # $hod: Stunde des Tages (01,02,...,24) -# $range: Range Bewölkung (1...100) oder "percentile" +# $range: Range Bewölkung (1...100) oder "simple" # $def: Defaultwert # ################################################################ @@ -15378,7 +15659,7 @@ return ($pvcorrf, $quality); # $dnumsum: Anzahl Tage pro Bewölkungsbereich über die gesamte Laufzeit # # $hod: Stunde des Tages (01,02,...,24) -# $range: Range Bewölkung (1...100) oder "percentile" +# $range: Range Bewölkung (1...100) oder "simple" # $def: Defaultwert # ####################################################################################################### @@ -15520,7 +15801,7 @@ return $def; # # $strg: - Name des Strings aus modulePeakString # $key: peak - Peakleistung aus modulePeakString -# tilt - Neigungswinkel der Module aus moduleTiltAngle +# tilt - Neigungswinkel der Module aus moduleDeclination # dir - Ausrichtung der Module als Azimut-Bezeichner (N,NE,E,SE,S,SW,W,NW) # azimut - Ausrichtung der Module als Azimut Angabe -180 .. 0 .. 180 # @@ -15585,6 +15866,7 @@ return $def; # wcc - Bewölkung als Bin # wrp - Regenwert als Bin # hod - Stunde des Tages +# sunalt - Höhe der Sonne (in Dezimalgrad) # pvrl - reale PV Erzeugung # # $def: Defaultwert @@ -15796,22 +16078,23 @@ to ensure that the system configuration is correct. After the definition of the device, depending on the forecast sources used, it is mandatory to store additional plant-specific information with the corresponding set commands.
- The following set commands are used to store information that is relevant for the function of the module:

+ The following set commands and attributes are used to store information that is relevant for the function of the + module:


@@ -16162,28 +16445,10 @@ to ensure that the system configuration is correct.
- + -
- - +
+ + +
+ + @@ -16239,23 +16539,6 @@ to ensure that the system configuration is correct.
- -
- +
+ + Note: If the latitude and longitude attributes are set in the global device, the sunrise and sunset + result from this information.
@@ -17865,23 +18155,23 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Nach der Definition des Devices sind in Abhängigkeit der verwendeten Prognosequellen zwingend weitere anlagenspezifische Angaben mit den entsprechenden set-Kommandos zu hinterlegen.
- Mit nachfolgenden set-Kommandos werden für die Funktion des Moduls maßgebliche Informationen + Mit nachfolgenden set-Kommandos und Attributen werden für die Funktion des Moduls maßgebliche Informationen hinterlegt:


@@ -18234,28 +18524,10 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
- + -
- - +
+ + +
+ + @@ -18311,23 +18618,6 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
- -
- +
+ + Hinweis: Sind die Attribute latitude und longitude im global Device gesetzt, ergibt sich der + Sonnenauf- und Sonnenuntergang aus diesen Angaben.