2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-02-25 09:55:38 +00:00

76_SolarForecast: attr setupRadiationAPI and setupWeatherDev1 can be set largely independently of each other

git-svn-id: https://svn.fhem.de/fhem/trunk@29377 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
nasseeder1 2024-11-30 17:31:34 +00:00
parent a170dc94e9
commit ae9aae6352
3 changed files with 472 additions and 490 deletions

View File

@ -1,5 +1,7 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it # Do not insert empty lines here, update check depends on it
- featur: 76_SolarForecast: attr setupRadiationAPI and setupWeatherDev1 can
be set largely independently of each other
- change: 76_SolarForecast: Separation of radiation API-data, API-state data, - change: 76_SolarForecast: Separation of radiation API-data, API-state data,
weather-API data weather-API data
- change: 76_SolarForecast: extended Debug collectData for EnergyConsumption - change: 76_SolarForecast: extended Debug collectData for EnergyConsumption

View File

@ -156,7 +156,10 @@ BEGIN {
# Versions History intern # Versions History intern
my %vNotesIntern = ( my %vNotesIntern = (
"1.37.9" => "29.11.2024 activate StatusAPI-Hash, Separation of radiation API-data , API-state data , weather-API data ", "1.38.0" => "30.11.2024 optimize data handling, rename getter solApiData to radiationApiData, ".
"set setupStringAzimuth, setupStringDeclination is checked due to dependencies to OpenMeteo ".
"attr setupRadiationAPI and setupWeatherDev1 can be set largely independently of each other ",
"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 ". "_saveEnergyConsumption: extended for Debug collectData, preparation of weatherApiData ".
"new func WeatherAPIVal, StatusAPIVal ", "new func WeatherAPIVal, StatusAPIVal ",
@ -601,7 +604,7 @@ my %hget = ( # Ha
forecastQualities => { fn => \&_getForecastQualities, needcred => 0 }, forecastQualities => { fn => \&_getForecastQualities, needcred => 0 },
nextHours => { fn => \&_getlistNextHours, needcred => 0 }, nextHours => { fn => \&_getlistNextHours, needcred => 0 },
rooftopData => { fn => \&_getRoofTopData, needcred => 0 }, rooftopData => { fn => \&_getRoofTopData, needcred => 0 },
solApiData => { fn => \&_getlistSolCastData, needcred => 0 }, radiationApiData => { fn => \&_getlistRadiationApiData, needcred => 0 },
weatherApiData => { fn => \&_getlistWeatherApiData, needcred => 0 }, weatherApiData => { fn => \&_getlistWeatherApiData, needcred => 0 },
statusApiData => { fn => \&_getlistStatusApiData, needcred => 0 }, statusApiData => { fn => \&_getlistStatusApiData, needcred => 0 },
valDecTree => { fn => \&_getaiDecTree, needcred => 0 }, valDecTree => { fn => \&_getaiDecTree, needcred => 0 },
@ -1293,9 +1296,6 @@ sub Initialize {
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
########################################################################################################################## ##########################################################################################################################
# my $av = 'obsolete#-#use#attr#flowGraphicControl#instead';
# $hash->{AttrList} .= " flowGraphicCss:$av flowGraphicSize:$av flowGraphicAnimate:$av flowGraphicConsumerDistance:$av flowGraphicShowConsumer:$av flowGraphicShowConsumerDummy:$av flowGraphicShowConsumerPower:$av flowGraphicShowConsumerRemainTime:$av flowGraphicShift:$av ";
# my $av1 = "obsolete#-#the#attribute#will#be#deleted#soon"; # my $av1 = "obsolete#-#the#attribute#will#be#deleted#soon";
# $hash->{AttrList} .= " affect70percentRule:$av1 ctrlAutoRefresh:$av1 ctrlAutoRefreshFW:$av1 "; # $hash->{AttrList} .= " affect70percentRule:$av1 ctrlAutoRefresh:$av1 ctrlAutoRefreshFW:$av1 ";
########################################################################################################################## ##########################################################################################################################
@ -1364,7 +1364,7 @@ sub Define {
$params->{file} = $scpicache.$name; # Cache File SolCast API Werte einlesen wenn vorhanden $params->{file} = $scpicache.$name; # Cache File SolCast API Werte einlesen wenn vorhanden
$params->{cachename} = 'solcastapi'; $params->{cachename} = 'solcastapi';
$params->{title} = 'solApiData'; $params->{title} = 'radiationApiData';
_readCacheFile ($params); _readCacheFile ($params);
$params->{file} = $statcache.$name; # Cache File API-Status einlesen wenn vorhanden $params->{file} = $statcache.$name; # Cache File API-Status einlesen wenn vorhanden
@ -1590,6 +1590,8 @@ sub Set {
"powerTrigger:textField-long ". "powerTrigger:textField-long ".
"pvCorrectionFactor_Auto:noLearning,on_simple".($ipai ? ',on_simple_ai,' : ',')."on_complex".($ipai ? ',on_complex_ai,' : ',')."off ". "pvCorrectionFactor_Auto:noLearning,on_simple".($ipai ? ',on_simple_ai,' : ',')."on_complex".($ipai ? ',on_complex_ai,' : ',')."off ".
"reset:$resets ". "reset:$resets ".
"setupStringAzimuth ".
"setupStringDeclination ".
$cf." " $cf." "
; ;
@ -1599,20 +1601,10 @@ sub Set {
$setlist .= "roofIdentPair " $setlist .= "roofIdentPair "
; ;
} }
elsif (isForecastSolarUsed ($hash)) {
$setlist .= "setupStringAzimuth ".
"setupStringDeclination "
;
}
elsif (isVictronKiUsed ($hash)) { elsif (isVictronKiUsed ($hash)) {
$setlist .= "vrmCredentials " $setlist .= "vrmCredentials "
; ;
} }
else {
$setlist .= "setupStringAzimuth ".
"setupStringDeclination "
;
}
## KI spezifische Setter ## KI spezifische Setter
########################## ##########################
@ -2513,7 +2505,7 @@ sub Get {
"pvCircular:noArg ". "pvCircular:noArg ".
"pvHistory:#,exportToCsv,$pvl ". "pvHistory:#,exportToCsv,$pvl ".
"rooftopData:noArg ". "rooftopData:noArg ".
"solApiData:noArg ". "radiationApiData:noArg ".
"statusApiData:noArg ". "statusApiData:noArg ".
"valCurrent:noArg ". "valCurrent:noArg ".
"weatherApiData:noArg " "weatherApiData:noArg "
@ -2569,24 +2561,36 @@ sub _getRoofTopData {
delete $data{$type}{$name}{current}{dwdRad1hAge}; delete $data{$type}{$name}{current}{dwdRad1hAge};
delete $data{$type}{$name}{current}{dwdRad1hAgeTS}; delete $data{$type}{$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";
my $ret = "$name is not a valid SolarForeCast Model: ".$hash->{MODEL}; if ($rapi eq 'SolCast') {
if ($hash->{MODEL} eq 'SolCastAPI') {
$ret = __getSolCastData ($paref); $ret = __getSolCastData ($paref);
} }
elsif ($hash->{MODEL} eq 'ForecastSolarAPI') { elsif ($rapi eq 'ForecastSolar') {
$ret = __getForecastSolarData ($paref); $ret = __getForecastSolarData ($paref);
} }
elsif ($hash->{MODEL} eq 'DWD') { elsif ($rapi eq 'DWD') {
$ret = __getDWDSolarData ($paref); $ret = __getDWDSolarData ($paref);
} }
elsif ($hash->{MODEL} eq 'VictronKiAPI') { elsif ($rapi eq 'VictronKi') {
$ret = __getVictronSolarData ($paref); $ret = __getVictronSolarData ($paref);
} }
elsif ($hash->{MODEL} =~ /^OpenMeteo/xs) {
if ($rapi eq 'OpenMeteo' || $wapi eq 'OpenMeteo') {
if ($rapi eq 'OpenMeteo') {
$paref->{reqm} = 'MODEL';
}
else {
$paref->{reqm} = 'WEATHERMODEL';
}
$ret = __getopenMeteoData ($paref); $ret = __getopenMeteoData ($paref);
} }
delete $paref->{reqm};
return $ret; return $ret;
} }
@ -2876,7 +2880,7 @@ 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 $period =~ s/.*(\d\d).*/$1/; # -> die neuen Daten (in dem Fall nur die einer halben Stunde) im nächsten Schritt addiert werden
my $est50 = SolCastAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0) / (60/$period); my $est50 = SolCastAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0) / (60/$period);
$data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = $est50 if($est50); $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = sprintf "%.0f", $est50 if($est50);
$k++; $k++;
next; next;
@ -2902,15 +2906,17 @@ sub __solCast_ApiResponse {
$period = $jdata->{'forecasts'}[$k]{'period'}; $period = $jdata->{'forecasts'}[$k]{'period'};
$period =~ s/.*(\d\d).*/$1/; $period =~ s/.*(\d\d).*/$1/;
$pvest50 = sprintf "%.0f", ($pvest50 * ($period/60) * 1000);
if ($debug =~ /apiProcess/x) { # nur für Debugging if ($debug =~ /apiProcess/x) { # nur für Debugging
if (exists $data{$type}{$name}{solcastapi}{$string}{$starttmstr}) { if (exists $data{$type}{$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 - Start Date/Time: }. $starttmstr);
Log3 ($name, 1, qq{$name DEBUG> SolCast API Hash - pv_estimate50 add: }.(sprintf "%.0f", ($pvest50 * ($period/60) * 1000)).qq{, contains already: }.SolCastAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0)); Log3 ($name, 1, qq{$name DEBUG> SolCast API Hash - pv_estimate50 add: }.$pvest50.qq{, contains already: }.SolCastAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0));
} }
} }
$data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} += sprintf "%.0f", ($pvest50 * ($period/60) * 1000); $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} += $pvest50;
$k++; $k++;
} }
@ -4085,6 +4091,7 @@ sub __getopenMeteoData {
my $t = $paref->{t}; my $t = $paref->{t};
my $lang = $paref->{lang}; my $lang = $paref->{lang};
my $debug = $paref->{debug}; my $debug = $paref->{debug};
my $reqm = $paref->{reqm};
my $hash = $defs{$name}; my $hash = $defs{$name};
my $donearq = StatusAPIVal ($hash, 'OpenMeteo', '?All', 'todayDoneAPIrequests', 0); my $donearq = StatusAPIVal ($hash, 'OpenMeteo', '?All', 'todayDoneAPIrequests', 0);
@ -4106,7 +4113,8 @@ sub __getopenMeteoData {
debugLog ($paref, 'apiCall', qq{Open-Meteo API Call - the daily API requests -> limited to: $ometmaxreq, done: $donearq}); debugLog ($paref, 'apiCall', qq{Open-Meteo API Call - the daily API requests -> limited to: $ometmaxreq, done: $donearq});
my $submodel = InternalVal ($hash->{NAME}, 'MODEL', ''); my $submodel = InternalVal ($hash->{NAME}, $reqm, '');
$paref->{allstrings} = AttrVal ($name, 'setupInverterStrings', ''); $paref->{allstrings} = AttrVal ($name, 'setupInverterStrings', '');
$paref->{submodel} = $submodel eq 'OpenMeteoDWDAPI' ? 'DWD ICON Seamless' : $paref->{submodel} = $submodel eq 'OpenMeteoDWDAPI' ? 'DWD ICON Seamless' :
$submodel eq 'OpenMeteoDWDEnsembleAPI' ? 'DWD ICON Seamless Ensemble' : $submodel eq 'OpenMeteoDWDEnsembleAPI' ? 'DWD ICON Seamless Ensemble' :
@ -4117,6 +4125,7 @@ sub __getopenMeteoData {
$paref->{callequivalent} = $submodel eq 'OpenMeteoDWDEnsembleAPI' ? 20 : 1; $paref->{callequivalent} = $submodel eq 'OpenMeteoDWDEnsembleAPI' ? 20 : 1;
$paref->{begin} = 1; $paref->{begin} = 1;
$paref->{requestmode} = $reqm;
__openMeteoDWD_ApiRequest ($paref); __openMeteoDWD_ApiRequest ($paref);
@ -4154,7 +4163,8 @@ sub __openMeteoDWD_ApiRequest {
my $debug = $paref->{debug}; my $debug = $paref->{debug};
my $lang = $paref->{lang}; my $lang = $paref->{lang};
my $t = $paref->{t} // int time; my $t = $paref->{t} // int time;
my $submodel = $paref->{submodel}; # abzufragendes Wettermodell my $submodel = $paref->{submodel}; # abzufragendes Datenmodell
my $requestmode = $paref->{requestmode};
my $hash = $defs{$name}; my $hash = $defs{$name};
@ -4167,18 +4177,26 @@ sub __openMeteoDWD_ApiRequest {
return; return;
} }
my $string; my ($string, $err);
($string, $allstrings) = split ",", $allstrings, 2; ($string, $allstrings) = split ",", $allstrings, 2;
my ($set, $lat, $lon, $elev) = locCoordinates(); my ($set, $lat, $lon, $elev) = locCoordinates();
if (!$set) { if (!$set) {
my $err = qq{the attribute 'latitude' and/or 'longitude' in global device is not set}; $err = qq{ERROR - the attribute 'latitude' and/or 'longitude' in global device is not set};
singleUpdateState ( {hash => $hash, state => $err, evt => 1} ); Log3 ($name, 1, "$name - $err");
return $err; return;
} }
my $tilt = StringVal ($hash, $string, 'tilt', '<unknown>'); my $tilt = StringVal ($hash, $string, 'tilt', '<unknown>');
my $az = StringVal ($hash, $string, 'azimut', '<unknown>'); my $az = StringVal ($hash, $string, 'azimut', '<unknown>');
if ($requestmode eq 'WEATHERMODEL' && $string eq 'KI-based') {$tilt = 0; $az = 0;} # Dummy Settings
if ($tilt eq '<unknown>' || $az eq '<unknown>') {
$err = qq{ERROR OpenMeteo API Call - the reading 'setupStringAzimuth' and/or 'setupStringDeclination' is not set};
Log3 ($name, 1, "$name - $err");
return;
}
my $url = "https://api.open-meteo.com/v1/forecast?"; my $url = "https://api.open-meteo.com/v1/forecast?";
$url = "https://ensemble-api.open-meteo.com/v1/ensemble?" if($submodel =~ /Ensemble/xs); # Ensemble Modell gewählt $url = "https://ensemble-api.open-meteo.com/v1/ensemble?" if($submodel =~ /Ensemble/xs); # Ensemble Modell gewählt
@ -4195,8 +4213,9 @@ sub __openMeteoDWD_ApiRequest {
$url .= "&tilt=".$tilt; $url .= "&tilt=".$tilt;
$url .= "&azimuth=".$az; $url .= "&azimuth=".$az;
debugLog ($paref, 'apiCall', qq{Open-Meteo API Call - Request for PV-String "$string" with Weather Model >$submodel<:\n$url}); debugLog ($paref, 'apiCall', qq{Open-Meteo API Call - Request for PV-String "$string" with Data Model >$submodel<:\n$url});
debugLog ($paref, 'apiCall|apiProcess', qq{Open-Meteo API Call - Request Mode: $requestmode});
my $caller = (caller(0))[3]; # Rücksprungmarke my $caller = (caller(0))[3]; # Rücksprungmarke
my $param = { my $param = {
@ -4209,6 +4228,7 @@ sub __openMeteoDWD_ApiRequest {
submodel => $submodel, submodel => $submodel,
begin => $paref->{begin}, begin => $paref->{begin},
callequivalent => $paref->{callequivalent}, callequivalent => $paref->{callequivalent},
requestmode => $requestmode,
caller => \&$caller, caller => \&$caller,
stc => [gettimeofday], stc => [gettimeofday],
allstrings => $allstrings, allstrings => $allstrings,
@ -4257,6 +4277,7 @@ sub __openMeteoDWD_ApiResponse {
my $caller = $paref->{caller}; my $caller = $paref->{caller};
my $string = $paref->{string}; my $string = $paref->{string};
my $allstrings = $paref->{allstrings}; my $allstrings = $paref->{allstrings};
my $requestmode = $paref->{requestmode}; # MODEL oder WEATHERMODEL
my $stc = $paref->{stc}; # Startzeit API Abruf my $stc = $paref->{stc}; # Startzeit API Abruf
my $lang = $paref->{lang}; my $lang = $paref->{lang};
my $debug = $paref->{debug}; my $debug = $paref->{debug};
@ -4379,6 +4400,7 @@ sub __openMeteoDWD_ApiResponse {
my $rad1wh = $jdata->{hourly}{global_tilted_irradiance}[$k]; # Wh/m2 my $rad1wh = $jdata->{hourly}{global_tilted_irradiance}[$k]; # Wh/m2
my $rad = 10 * (sprintf "%.0f", ($rad1wh * $WhtokJ) / 10); # Umrechnung Wh/m2 in kJ/m2 -> my $rad = 10 * (sprintf "%.0f", ($rad1wh * $WhtokJ) / 10); # Umrechnung Wh/m2 in kJ/m2 ->
my $pv = sprintf "%.2f", int ($rad1wh / 1000 * $peak * $prdef); # Rad wird in kWh/m2 erwartet my $pv = sprintf "%.2f", int ($rad1wh / 1000 * $peak * $prdef); # Rad wird in kWh/m2 erwartet
my $don = $jdata->{hourly}{is_day}[$k]; my $don = $jdata->{hourly}{is_day}[$k];
my $temp = $jdata->{hourly}{temperature_2m}[$k]; my $temp = $jdata->{hourly}{temperature_2m}[$k];
my $rain = $jdata->{hourly}{rain}[$k]; # Regen in Millimeter = kg/m2 my $rain = $jdata->{hourly}{rain}[$k]; # Regen in Millimeter = kg/m2
@ -4388,7 +4410,10 @@ sub __openMeteoDWD_ApiResponse {
if ($k == 0 && $curwid) { $curwid = ($don ? 0 : 100) + $curwid } if ($k == 0 && $curwid) { $curwid = ($don ? 0 : 100) + $curwid }
if ($debug =~ /apiProcess/xs) { if ($debug =~ /apiProcess/xs) {
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $pvtmstr - Rad1Wh: $rad1wh, Rad1kJ: $rad, PV est: $pv Wh"); if ($requestmode eq 'MODEL') {
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $pvtmstr - Rad1Wh: $rad1wh, Rad1kJ: $rad, PV est: $pv Wh");
}
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $pvtmstr - RR1c: $rain"); Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $pvtmstr - RR1c: $rain");
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $otmstr - DoN: $don"); Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $otmstr - DoN: $don");
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $otmstr - Temp: $temp"); Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $otmstr - Temp: $temp");
@ -4402,29 +4427,24 @@ sub __openMeteoDWD_ApiResponse {
} }
} }
$data{$type}{$name}{solcastapi}{$string}{$pvtmstr}{pv_estimate50} = $pv; # Startstunde verschieben if ($requestmode eq 'MODEL') {
$data{$type}{$name}{solcastapi}{$string}{$pvtmstr}{pv_estimate50} = $pv; # Startstunde verschieben
my $fwtg = formatWeatherTimestrg ($pvtmstr); if ($paref->{begin}) { # im ersten Call den DS löschen -> dann Aufsummierung
delete $data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h};
}
if ($paref->{begin}) { # im ersten Call den DS löschen -> dann Aufsummierung $data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h} += $rad; # Startstunde verschieben, Rad Werte aller Strings addieren
delete $data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h};
} }
$data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h} += $rad; # Startstunde verschieben, Rad Werte aller Strings addieren
## Wetterdaten ## Wetterdaten
################ ################
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{rr1c} = $rain; # Startstunde verschieben my $fwtg = formatWeatherTimestrg ($pvtmstr); # Zeit gemäß DWD_OpenData-Format
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{rr1c} = $rain;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{rr1c} = $rain;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $pvtmstr; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $pvtmstr;
$fwtg = formatWeatherTimestrg ($otmstr); $fwtg = formatWeatherTimestrg ($otmstr); # Zeit gemäß DWD_OpenData-Format
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{don} = $don;
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{neff} = $wcc;
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ww} = $wid;
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ttt} = $temp;
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{UpdateTime} = $rt;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{don} = $don; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{don} = $don;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $wcc; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $wcc;
@ -4433,11 +4453,7 @@ sub __openMeteoDWD_ApiResponse {
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{UpdateTime} = $rt; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{UpdateTime} = $rt;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $otmstr; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $otmstr;
if ($k == 0) { if ($k == 0) {
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{neff} = $curwcc if(defined $curwcc);
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ww} = $curwid if(defined $curwid);
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ttt} = $curtmp if(defined $curtmp);
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $curwcc if(defined $curwcc); $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}{ww} = $curwid if(defined $curwid);
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{ttt} = $curtmp if(defined $curtmp); $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{ttt} = $curtmp if(defined $curtmp);
@ -4464,9 +4480,6 @@ sub __openMeteoDWD_ApiResponse {
} }
if ($k == 0) { if ($k == 0) {
$data{$type}{$name}{solcastapi}{'?All'}{sunrise}{today} = $sunrise;
$data{$type}{$name}{solcastapi}{'?All'}{sunset}{today} = $sunset;
$data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{today} = $sunrise; $data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{today} = $sunrise;
$data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{today} = $sunset; $data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{today} = $sunset;
@ -4477,9 +4490,6 @@ sub __openMeteoDWD_ApiResponse {
} }
if ($k == 1) { if ($k == 1) {
$data{$type}{$name}{solcastapi}{'?All'}{sunrise}{tomorrow} = $sunrise;
$data{$type}{$name}{solcastapi}{'?All'}{sunset}{tomorrow} = $sunset;
$data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{tomorrow} = $sunrise; $data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{tomorrow} = $sunrise;
$data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{tomorrow} = $sunset; $data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{tomorrow} = $sunset;
@ -4504,6 +4514,7 @@ sub __openMeteoDWD_ApiResponse {
allstrings => $allstrings, allstrings => $allstrings,
submodel => $submodel, submodel => $submodel,
callequivalent => $paref->{callequivalent}, callequivalent => $paref->{callequivalent},
requestmode => $requestmode,
lang => $lang lang => $lang
}; };
@ -4730,14 +4741,14 @@ return $ret;
} }
############################################################### ###############################################################
# Getter solApiData # Getter radiationApiData
############################################################### ###############################################################
sub _getlistSolCastData { sub _getlistRadiationApiData {
my $paref = shift; my $paref = shift;
my $name = $paref->{name}; my $name = $paref->{name};
my $hash = $defs{$name}; my $hash = $defs{$name};
my $ret = listDataPool ($hash, 'solApiData'); my $ret = listDataPool ($hash, 'radiationApiData');
$ret .= lineFromSpaces ($ret, 10); $ret .= lineFromSpaces ($ret, 10);
return $ret; return $ret;
@ -5425,16 +5436,6 @@ sub Attr {
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
###################################################################################################################### ######################################################################################################################
# 20.10.2024
if ($cmd eq 'set' && $aName =~ /^flowGraphicCss|flowGraphicSize|flowGraphicAnimate|flowGraphicConsumerDistance|flowGraphicShowConsumer|flowGraphicShowConsumerDummy|flowGraphicShowConsumerPower|flowGraphicShowConsumerRemainTime|flowGraphicShift$/) {
if (!$init_done) {
# return qq{Device "$name" -> The attribute '$aName' is replaced by 'flowGraphicControl'. Please press "save config" when restart is finished.};
Log3 ($name, 1, qq{$name - The attribute '$aName' is replaced by 'flowGraphicControl'. Please press "save config" when restart is finished.});
}
else {
return qq{The attribute '$aName' is obsolete and replaced by 'flowGraphicControl'.};
}
}
# 31.10.2024 # 31.10.2024
if ($cmd eq 'set' && $aName =~ /^graphicStartHtml|affect70percentRule|graphicEndHtml|ctrlAutoRefresh|ctrlAutoRefreshFW$/) { if ($cmd eq 'set' && $aName =~ /^graphicStartHtml|affect70percentRule|graphicEndHtml|ctrlAutoRefresh|ctrlAutoRefreshFW$/) {
if (!$init_done) { if (!$init_done) {
@ -6197,19 +6198,26 @@ sub _attrWeatherDev { ## no critic "not used"
if ($aVal =~ /^OpenMeteo/xs) { if ($aVal =~ /^OpenMeteo/xs) {
if ($aName ne 'setupWeatherDev1') { if ($aName ne 'setupWeatherDev1') {
return qq{Only the leading attribute 'setupWeatherDev1' can set to '$aVal'}; return qq{Only the leading weather attribute 'setupWeatherDev1' can set to '$aVal'};
}
my @istrings = split ",", AttrVal ($name, 'setupInverterStrings', ''); # Stringbezeichner
if ((!ReadingsVal ($name, 'setupStringAzimuth', '') || !ReadingsVal ($name, 'setupStringDeclination', '')) &&
!grep /KI-based/, @istrings) {
return qq{Execute 'set $name setupStringAzimuth' and/or 'set $name setupStringDeclination' first.};
} }
InternalTimer (gettimeofday()+1, 'FHEM::SolarForecast::__setRadAPIdelayed', $hash, 0); # automatisch setupRadiationAPI setzen wenn setupWeatherDev1
return;
} }
my $err = checkdwdattr ($name, $aVal, \@dweattrmust); if ($aVal !~ /-API$/xs) { # Attribute des DWD-Devices prüfen
return $err if($err); my $err = checkdwdattr ($name, $aVal, \@dweattrmust);
return $err if($err);
}
} }
InternalTimer (gettimeofday() + 1, 'FHEM::SolarForecast::setModel', $hash, 0); InternalTimer (gettimeofday() + 1, 'FHEM::SolarForecast::__harmonizeAPIdelayed', $hash, 0);
InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::setModel', $hash, 0);
InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0);
return; return;
} }
@ -6233,14 +6241,14 @@ sub _attrRadiationAPI { ## no critic "not used"
return qq{The device "$aVal" doesn't exist or has no TYPE "DWD_OpenData"}; return qq{The device "$aVal" doesn't exist or has no TYPE "DWD_OpenData"};
} }
my $awdev1 = AttrVal ($name, 'setupWeatherDev1', ''); #my $awdev1 = AttrVal ($name, 'setupWeatherDev1', '');
if (($awdev1 eq 'OpenMeteoDWD-API' && $aVal ne 'OpenMeteoDWD-API') || #if (($awdev1 eq 'OpenMeteoDWD-API' && $aVal ne 'OpenMeteoDWD-API') ||
($awdev1 eq 'OpenMeteoDWDEnsemble-API' && $aVal ne 'OpenMeteoDWDEnsemble-API') || # ($awdev1 eq 'OpenMeteoDWDEnsemble-API' && $aVal ne 'OpenMeteoDWDEnsemble-API') ||
($awdev1 eq 'OpenMeteoWorld-API' && $aVal ne 'OpenMeteoWorld-API')) { # ($awdev1 eq 'OpenMeteoWorld-API' && $aVal ne 'OpenMeteoWorld-API')) {
return "The attribute 'setupWeatherDev1' is set to '$awdev1'. \n". # return "The attribute 'setupWeatherDev1' is set to '$awdev1'. \n".
"Change that attribute to another weather device first if you want use an other API."; # "Change that attribute to another weather device first if you want use an other API.";
} #}
if ($aVal =~ /(SolCast|OpenMeteoDWD|OpenMeteoDWDEnsemble|OpenMeteoWorld)-API/xs) { if ($aVal =~ /(SolCast|OpenMeteoDWD|OpenMeteoDWDEnsemble|OpenMeteoWorld)-API/xs) {
return "The library FHEM::Utility::CTZ is missing. Please update FHEM completely." if($ctzAbsent); return "The library FHEM::Utility::CTZ is missing. Please update FHEM completely." if($ctzAbsent);
@ -6267,9 +6275,10 @@ sub _attrRadiationAPI { ## no critic "not used"
readingsDelete ($hash, 'nextRadiationAPICall'); readingsDelete ($hash, 'nextRadiationAPICall');
InternalTimer (gettimeofday() + 1, 'FHEM::SolarForecast::setModel', $hash, 0); # Model setzen InternalTimer (gettimeofday() + 1, 'FHEM::SolarForecast::__harmonizeAPIdelayed', $hash, 0);
InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::setModel', $hash, 0); # Model setzen
InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0);
InternalTimer (gettimeofday() + 4, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben
return; return;
} }
@ -6302,15 +6311,22 @@ return;
} }
################################################################ ################################################################
# setupRadiationAPI verzögert aus Attr setzen # Attr setupRadiationAPI und setupWeatherDev1
# harmonisieren wenn erforderlich
# setupRadiationAPI ist führend
################################################################ ################################################################
sub __setRadAPIdelayed { sub __harmonizeAPIdelayed {
my $hash = shift; my $hash = shift;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $awdev1 = AttrVal ($name, 'setupWeatherDev1', ''); my $wedev1 = AttrVal ($name, 'setupWeatherDev1', '');
my $radapi = AttrVal ($name, 'setupRadiationAPI', '');
CommandAttr (undef, "$name setupRadiationAPI $awdev1"); # automatisch setupRadiationAPI setzen
return if($wedev1 eq $radapi);
if ($radapi =~ /OpenMeteo/xs && $wedev1 =~ /OpenMeteo/xs) { # auf OpenMeteo Datenmodell harmonisieren
CommandAttr (undef, "$name setupWeatherDev1 $radapi");
}
return; return;
} }
@ -7082,36 +7098,7 @@ sub centralTask {
setModel ($hash); # Model setzen setModel ($hash); # Model setzen
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
########################################################################################################################## ##########################################################################################################################
for my $n (1..24) { # 08.10.2024
$n = sprintf "%02d", $n;
readingsDelete ($hash, "pvCorrectionFactor_${n}_autocalc");
}
my $fg1 = AttrVal ($name, 'flowGraphicSize', undef); # 20.10.2024
my $fg2 = AttrVal ($name, 'flowGraphicAnimate', undef);
my $fg3 = AttrVal ($name, 'flowGraphicConsumerDistance', undef);
my $fg4 = AttrVal ($name, 'flowGraphicShowConsumer', undef);
my $fg5 = AttrVal ($name, 'flowGraphicShowConsumerDummy', undef);
my $fg6 = AttrVal ($name, 'flowGraphicShowConsumerPower', undef);
my $fg7 = AttrVal ($name, 'flowGraphicShowConsumerRemainTime', undef);
my $fg8 = AttrVal ($name, 'flowGraphicShift', undef);
my $newval;
$newval .= "size=$fg1 " if(defined $fg1);
$newval .= "animate=$fg2 " if(defined $fg2);
$newval .= "consumerdist=$fg3 " if(defined $fg3);
$newval .= "showconsumer=$fg4 " if(defined $fg4);
$newval .= "showconsumerdummy=$fg5 " if(defined $fg5);
$newval .= "showconsumerpower=$fg6 " if(defined $fg6);
$newval .= "showconsumerremaintime=$fg7 " if(defined $fg7);
$newval .= "shift=$fg8 " if(defined $fg8);
if ($newval) {
CommandAttr (undef, "$name flowGraphicControl $newval");
::CommandDeleteAttr (undef, "$name flowGraphicSize|flowGraphicAnimate|flowGraphicConsumerDistance|flowGraphicShowConsumer|flowGraphicShowConsumerDummy|flowGraphicShowConsumerPower|flowGraphicShowConsumerRemainTime|flowGraphicShift");
}
if (exists $data{$type}{$name}{solcastapi}{'?IdPair'}) { # 29.11.2024 if (exists $data{$type}{$name}{solcastapi}{'?IdPair'}) { # 29.11.2024
for my $pk (keys %{$data{$type}{$name}{solcastapi}{'?IdPair'}}) { for my $pk (keys %{$data{$type}{$name}{solcastapi}{'?IdPair'}}) {
my $apikey = SolCastAPIVal ($hash, '?IdPair', $pk, 'apikey', ''); my $apikey = SolCastAPIVal ($hash, '?IdPair', $pk, 'apikey', '');
@ -7132,7 +7119,7 @@ sub centralTask {
for my $key (keys %{$data{$type}{$name}{solcastapi}{'?All'}{'?All'}}) { for my $key (keys %{$data{$type}{$name}{solcastapi}{'?All'}{'?All'}}) {
my $val = SolCastAPIVal ($hash, '?All', '?All', $key, ''); my $val = SolCastAPIVal ($hash, '?All', '?All', $key, '');
if ($val) { if ($rapi && $val) {
$data{$type}{$name}{statusapi}{$rapi}{'?All'}{$key} = $val; $data{$type}{$name}{statusapi}{$rapi}{'?All'}{$key} = $val;
delete $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{$key}; delete $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{$key};
} }
@ -7156,7 +7143,6 @@ sub centralTask {
$data{$type}{$name}{statusapi}{'?VRM'}{'?API'}{credentials} = $vrmcr; $data{$type}{$name}{statusapi}{'?VRM'}{'?API'}{credentials} = $vrmcr;
delete $data{$type}{$name}{solcastapi}{'?VRM'}; delete $data{$type}{$name}{solcastapi}{'?VRM'};
} }
########################################################################################################################## ##########################################################################################################################
my (undef, $disabled, $inactive) = controller ($name); my (undef, $disabled, $inactive) = controller ($name);
@ -7225,7 +7211,7 @@ sub centralTask {
readingsDelete ($hash, 'AllPVforecastsToEvent'); readingsDelete ($hash, 'AllPVforecastsToEvent');
_getRoofTopData ($centpars); # Strahlungswerte/Forecast-Werte in solcastapi-Hash erstellen _getRoofTopData ($centpars); # Strahlungs/Wetter-Daten der gewählten API's abrufen und in internen Strukturen speichern
_transferInverterValues ($centpars); # WR Werte übertragen _transferInverterValues ($centpars); # WR Werte übertragen
_transferAPIRadiationValues ($centpars); # Raw Erzeugungswerte aus solcastapi-Hash übertragen und Forecast mit/ohne Korrektur erstellen _transferAPIRadiationValues ($centpars); # Raw Erzeugungswerte aus solcastapi-Hash übertragen und Forecast mit/ohne Korrektur erstellen
_calcMaxEstimateToday ($centpars); # heutigen Max PV Estimate & dessen Tageszeit ermitteln _calcMaxEstimateToday ($centpars); # heutigen Max PV Estimate & dessen Tageszeit ermitteln
@ -7320,9 +7306,10 @@ sub createStringConfig { ## no critic "not used"
return qq{You use a KI based model. Please set only "KI-based" as String with command "attr $name setupInverterStrings".}; return qq{You use a KI based model. Please set only "KI-based" as String with command "attr $name setupInverterStrings".};
} }
} }
elsif (!isVictronKiUsed ($hash)) {
if (!grep /^KI-based$/, @istrings) {
my $tilt = ReadingsVal ($name, 'setupStringDeclination', ''); # Modul Neigungswinkel für jeden Stringbezeichner my $tilt = ReadingsVal ($name, 'setupStringDeclination', ''); # Modul Neigungswinkel für jeden Stringbezeichner
return qq{Please complete command "set $name setupStringDeclination".} if(!$tilt); return qq{Please complete command "set $name setupStringDeclination"} if(!$tilt);
my ($at,$ht) = parseParams ($tilt); my ($at,$ht) = parseParams ($tilt);
@ -7336,7 +7323,7 @@ sub createStringConfig { ## no critic "not used"
} }
my $dir = ReadingsVal ($name, 'setupStringAzimuth', ''); # Modul Ausrichtung für jeden Stringbezeichner my $dir = ReadingsVal ($name, 'setupStringAzimuth', ''); # Modul Ausrichtung für jeden Stringbezeichner
return qq{Please complete command "set $name setupStringAzimuth".} if(!$dir); return qq{Please complete command "set $name setupStringAzimuth"} if(!$dir);
my ($ad,$hd) = parseParams ($dir); my ($ad,$hd) = parseParams ($dir);
my $iwrong = qq{Please check the input of set "setupStringAzimuth". It seems to be wrong.}; my $iwrong = qq{Please check the input of set "setupStringAzimuth". It seems to be wrong.};
@ -7934,29 +7921,24 @@ sub __delObsoleteAPIData {
my $date = $paref->{date}; # aktuelles Datum my $date = $paref->{date}; # aktuelles Datum
my $hash = $defs{$name}; my $hash = $defs{$name};
my ($rapi, $wapi) = getStatusApiName ($hash);
## Wetter-API Daten löschen ## Wetter-API Daten löschen
############################# #############################
if (keys %{$data{$type}{$name}{weatherapi}}) { if (keys %{$data{$type}{$name}{weatherapi}}) {
if (isWeatherOpenMeteoUsed ($hash)) { delete $data{$type}{$name}{weatherapi}{OpenMeteo} if($wapi ne 'OpenMeteo');
for my $idx (keys %{$data{$type}{$name}{weatherapi}{OpenMeteo}}) {
delete $data{$type}{$name}{weatherapi}{OpenMeteo}{$idx} if($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs);
}
}
else {
delete $data{$type}{$name}{weatherapi}{OpenMeteo};
}
} }
## Status-API Daten löschen ## Status-API Daten löschen
############################# #############################
if (keys %{$data{$type}{$name}{statusapi}}) { if (keys %{$data{$type}{$name}{statusapi}}) {
delete $data{$type}{$name}{statusapi}{OpenMeteo} if(!isOpenMeteoUsed ($hash) && !isWeatherOpenMeteoUsed ($hash)); delete $data{$type}{$name}{statusapi}{OpenMeteo} if($rapi ne 'OpenMeteo' && $wapi ne 'OpenMeteo');
delete $data{$type}{$name}{statusapi}{ForecastSolar} if(!isForecastSolarUsed ($hash)); delete $data{$type}{$name}{statusapi}{ForecastSolar} if($rapi ne 'ForecastSolar');
delete $data{$type}{$name}{statusapi}{SolCast} if(!isSolCastUsed ($hash)); delete $data{$type}{$name}{statusapi}{SolCast} if($rapi ne 'SolCast');
delete $data{$type}{$name}{statusapi}{'?IdPair'} if(!isSolCastUsed ($hash)); delete $data{$type}{$name}{statusapi}{'?IdPair'} if($rapi ne 'SolCast');
delete $data{$type}{$name}{statusapi}{DWD} if(!isDWDUsed ($hash)); delete $data{$type}{$name}{statusapi}{DWD} if($rapi ne 'DWD');
delete $data{$type}{$name}{statusapi}{VictronKi} if(!isVictronKiUsed ($hash)); delete $data{$type}{$name}{statusapi}{VictronKi} if($rapi ne 'VictronKi');
delete $data{$type}{$name}{statusapi}{'?VRM'} if(!isVictronKiUsed ($hash)); delete $data{$type}{$name}{statusapi}{'?VRM'} if($rapi ne 'VictronKi');
} }
## Solar-API Daten löschen ## Solar-API Daten löschen
@ -12020,6 +12002,7 @@ return ($fd, $fh);
} }
################################################################ ################################################################
# Zeit gemäß DWD_OpenData-Format
# Berechnen Tag / Stunden Verschieber ab aktuellen Tag # Berechnen Tag / Stunden Verschieber ab aktuellen Tag
# Input: YYYY-MM-DD HH:MM:SS # Input: YYYY-MM-DD HH:MM:SS
# Output: $fd - 0 (Heute), 1 (Morgen), 2 (Übermorgen), .... # Output: $fd - 0 (Heute), 1 (Morgen), 2 (Übermorgen), ....
@ -12660,10 +12643,7 @@ sub _checkSetupNotComplete {
$ret .= "<td>"; $ret .= "<td>";
$ret .= $hqtxt{entry}{$lang}; # Entry Text $ret .= $hqtxt{entry}{$lang}; # Entry Text
if (!$wedev) { ## no critic 'Cascading' if (!$strings) { ## no critic 'Cascading'
$ret .= $hqtxt{cfd}{$lang};
}
elsif (!$strings) {
$ret .= $hqtxt{ist}{$lang}; $ret .= $hqtxt{ist}{$lang};
} }
elsif (!$peaks) { elsif (!$peaks) {
@ -12671,7 +12651,7 @@ sub _checkSetupNotComplete {
} }
elsif (!$radev) { elsif (!$radev) {
$ret .= $hqtxt{crd}{$lang}; $ret .= $hqtxt{crd}{$lang};
} }
elsif (!$indev) { elsif (!$indev) {
$ret .= $hqtxt{cid}{$lang}; $ret .= $hqtxt{cid}{$lang};
} }
@ -12690,6 +12670,9 @@ sub _checkSetupNotComplete {
elsif (!$mdec && !isSolCastUsed ($hash) && !isVictronKiUsed ($hash)) { elsif (!$mdec && !isSolCastUsed ($hash) && !isVictronKiUsed ($hash)) {
$ret .= $hqtxt{mta}{$lang}; $ret .= $hqtxt{mta}{$lang};
} }
elsif (!$wedev) {
$ret .= $hqtxt{cfd}{$lang};
}
elsif (!$vrmcr && isVictronKiUsed ($hash)) { elsif (!$vrmcr && isVictronKiUsed ($hash)) {
$ret .= $hqtxt{vrmcr}{$lang}; $ret .= $hqtxt{vrmcr}{$lang};
} }
@ -16820,7 +16803,7 @@ sub listDataPool {
return $ret; return $ret;
}; };
if ($htol =~ /solApiData|weatherApiData|statusApiData/xs) { if ($htol =~ /radiationApiData|weatherApiData|statusApiData/xs) {
$h = $data{$type}{$name}{solcastapi}; $h = $data{$type}{$name}{solcastapi};
$h = $data{$type}{$name}{weatherapi} if($htol eq 'weatherApiData'); $h = $data{$type}{$name}{weatherapi} if($htol eq 'weatherApiData');
$h = $data{$type}{$name}{statusapi} if($htol eq 'statusApiData'); $h = $data{$type}{$name}{statusapi} if($htol eq 'statusApiData');
@ -17027,10 +17010,10 @@ sub checkPlantConfig {
my $info = FW_makeImage ('message_info', ''); my $info = FW_makeImage ('message_info', '');
my $result = { # Ergebnishash my $result = { # Ergebnishash
'String Configuration' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 }, 'String Configuration' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 },
'DWD Weather Properties' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 }, 'Weather Properties' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 },
'Common Settings' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 }, 'Common Settings' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 },
'FTUI Widget Files' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 }, 'FTUI Widget Files' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 },
}; };
my $sub = sub { my $sub = sub {
@ -17099,44 +17082,44 @@ sub checkPlantConfig {
next if(!$fcname && $step ne 1); next if(!$fcname && $step ne 1);
if (!$valid) { if (!$valid) {
$result->{'DWD Weather Properties'}{state} = $nok; $result->{'Weather Properties'}{state} = $nok;
if (!$fcname) { if (!$fcname) {
$result->{'DWD Weather Properties'}{result} .= qq{No DWD device is defined in attribute "setupWeatherDev$step". <br>}; $result->{'Weather Properties'}{result} .= qq{No DWD device is defined in attribute "setupWeatherDev$step". <br>};
} }
else { else {
$result->{'DWD Weather Properties'}{result} .= qq{The DWD device "$fcname" doesn't exist. <br>}; $result->{'Weather Properties'}{result} .= qq{The DWD device "$fcname" doesn't exist. <br>};
} }
$result->{'DWD Weather Properties'}{fault} = 1; $result->{'Weather Properties'}{fault} = 1;
} }
else { else {
if (!$apiu) { if (!$apiu) {
$err = checkdwdattr ($name, $fcname, \@dweattrmust); $err = checkdwdattr ($name, $fcname, \@dweattrmust);
if ($err) { if ($err) {
$result->{'DWD Weather Properties'}{state} = $nok; $result->{'Weather Properties'}{state} = $nok;
$result->{'DWD Weather Properties'}{result} .= $err.'<br>'; $result->{'Weather Properties'}{result} .= $err.'<br>';
$result->{'DWD Weather Properties'}{fault} = 1; $result->{'Weather Properties'}{fault} = 1;
} }
else { else {
$mosm = AttrVal ($fcname, 'forecastRefresh', 6) == 6 ? 'MOSMIX_L' : 'MOSMIX_S'; $mosm = AttrVal ($fcname, 'forecastRefresh', 6) == 6 ? 'MOSMIX_L' : 'MOSMIX_S';
if ($mosm eq 'MOSMIX_L') { if ($mosm eq 'MOSMIX_L') {
$result->{'DWD Weather Properties'}{state} = $info; $result->{'Weather Properties'}{state} = $info;
$result->{'DWD Weather Properties'}{result} .= qq(The device "$fcname" uses "$mosm" which is only updated by DWD every 6 hours. <br>); $result->{'Weather Properties'}{result} .= qq(The device "$fcname" uses "$mosm" which is only updated by DWD every 6 hours. <br>);
$result->{'DWD Weather Properties'}{info} = 1; $result->{'Weather Properties'}{info} = 1;
} }
$result->{'DWD Weather Properties'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: setupWeatherDev$step)<br>"; $result->{'Weather Properties'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: setupWeatherDev$step)<br>";
} }
$result->{'DWD Weather Properties'}{note} .= qq{checked parameters and attributes of device "$fcname": <br>}; $result->{'Weather Properties'}{note} .= qq{checked parameters and attributes of device "$fcname": <br>};
$result->{'DWD Weather Properties'}{note} .= 'forecastProperties -> '.join (',', @dweattrmust).'<br>'; $result->{'Weather Properties'}{note} .= 'forecastProperties -> '.join (',', @dweattrmust).'<br>';
$result->{'DWD Weather Properties'}{note} .= 'forecastRefresh '.($mosm eq 'MOSMIX_L' ? '-> set attribute to below "6" if possible' : '').'<br>'; $result->{'Weather Properties'}{note} .= 'forecastRefresh '.($mosm eq 'MOSMIX_L' ? '-> set attribute to below "6" if possible' : '').'<br>';
} }
else { else {
$result->{'DWD Weather Properties'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: setupWeatherDev$step)<br>"; $result->{'Weather Properties'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: setupWeatherDev$step)<br>";
} }
} }
} }
@ -17146,16 +17129,16 @@ sub checkPlantConfig {
($err, $resh) = isWeatherAgeExceeded ( {name => $name, lang => $lang} ); ($err, $resh) = isWeatherAgeExceeded ( {name => $name, lang => $lang} );
if (!$err && $resh->{exceed}) { if (!$err && $resh->{exceed}) {
$result->{'DWD Weather Properties'}{state} = $warn; $result->{'Weather Properties'}{state} = $warn;
$result->{'DWD Weather Properties'}{note} .= qq{The Prediction time of Weather data is older than expected when using $resh->{mosmix}. <br>}; $result->{'Weather Properties'}{note} .= qq{The Prediction time of Weather data is older than expected when using $resh->{mosmix}. <br>};
$result->{'DWD Weather Properties'}{note} .= qq{Data time forecast: $resh->{fctime} <br>}; $result->{'Weather Properties'}{note} .= qq{Data time forecast: $resh->{fctime} <br>};
$result->{'DWD Weather Properties'}{note} .= qq{Check the DWD device(s) for proper functioning of the data retrieval. <br>}; $result->{'Weather Properties'}{note} .= qq{Check the DWD device(s) for proper functioning of the data retrieval. <br>};
$result->{'DWD Weather Properties'}{warn} = 1; $result->{'Weather Properties'}{warn} = 1;
} }
$result->{'DWD Weather Properties'}{note} .= '<br>'; $result->{'Weather Properties'}{note} .= '<br>';
$result->{'DWD Weather Properties'}{note} .= qq{checked global Weather parameters: <br>}; $result->{'Weather Properties'}{note} .= qq{checked global Weather parameters: <br>};
$result->{'DWD Weather Properties'}{note} .= 'MOSMIX variant or ICON Forecast Model, Age of Weather data. <br>'; $result->{'Weather Properties'}{note} .= 'MOSMIX variant or ICON Forecast Model, Age of Weather data. <br>';
## Check DWD Radiation Device ## Check DWD Radiation Device
############################### ###############################
@ -17969,8 +17952,10 @@ sub setModel {
elsif ($radapi =~ /OpenMeteoWorld-/xs) { $hash->{MODEL} = 'OpenMeteoWorldAPI'; } elsif ($radapi =~ /OpenMeteoWorld-/xs) { $hash->{MODEL} = 'OpenMeteoWorldAPI'; }
else { $hash->{MODEL} = 'DWD'; } else { $hash->{MODEL} = 'DWD'; }
if ($wthapi =~ /OpenMeteo.*-API/xs) { $hash->{WEATHERMODEL} = 'OpenMeteo'; } if ($wthapi =~ /OpenMeteoDWDEnsemble-/xs) { $hash->{WEATHERMODEL} = 'OpenMeteoDWDEnsembleAPI'; }
else { $hash->{WEATHERMODEL} = 'DWD'; } elsif ($wthapi =~ /OpenMeteoDWD-/xs) { $hash->{WEATHERMODEL} = 'OpenMeteoDWDAPI'; }
elsif ($wthapi =~ /OpenMeteoWorld-/xs) { $hash->{WEATHERMODEL} = 'OpenMeteoWorldAPI'; }
else { $hash->{WEATHERMODEL} = 'DWD'; }
return; return;
} }
@ -18653,10 +18638,12 @@ sub isWeatherDevValid {
if ($fcname) { $valid = 1 } if ($fcname) { $valid = 1 }
if (!$defs{$fcname} || $defs{$fcname}{TYPE} ne "DWD_OpenData") { $valid = '' } if (!$defs{$fcname} || $defs{$fcname}{TYPE} ne "DWD_OpenData") { $valid = '' }
my ($rapi, $wapi) = getStatusApiName ($hash); # $rapi - Radiation-API, $wapi - Weather-API
if (isOpenMeteoUsed($hash) && $fcname =~ /^OpenMeteo/xs) { if ($wapi =~ /^OpenMeteo/xs) {
$valid = 1; $valid = 1;
$apiu = $fcname; $apiu = $wapi;
} }
return ($valid, $fcname, $apiu); return ($valid, $fcname, $apiu);
@ -18992,6 +18979,9 @@ return ($err, $cname, $dswname);
################################################################ ################################################################
# Namen der Strahlungs-API und Wetter-API ermitteln. # Namen der Strahlungs-API und Wetter-API ermitteln.
# Wird als Schlüssel in statusapi bzw. weatherapi verwendet. # Wird als Schlüssel in statusapi bzw. weatherapi verwendet.
# Return:
# $rapi - Name der Strahlungsdaten-API
# $wapi - Name der Wetter-API
################################################################ ################################################################
sub getStatusApiName { sub getStatusApiName {
my $hash = shift; my $hash = shift;
@ -20166,8 +20156,7 @@ to ensure that the system configuration is correct.
<ul> <ul>
<a id="SolarForecast-set-setupStringAzimuth"></a> <a id="SolarForecast-set-setupStringAzimuth"></a>
<li><b>setupStringAzimuth &lt;Stringname1&gt;=&lt;dir&gt; [&lt;Stringname2&gt;=&lt;dir&gt; &lt;Stringname3&gt;=&lt;dir&gt; ...] </b> <br> <li><b>setupStringAzimuth &lt;Stringname1&gt;=&lt;dir&gt; [&lt;Stringname2&gt;=&lt;dir&gt; &lt;Stringname3&gt;=&lt;dir&gt; ...] </b> <br><br>
(only model DWD, OpenMeteo*, ForecastSolarAPI) <br><br>
Alignment &lt;dir&gt; of the solar modules in the string "StringnameX". The string name is a key value of the Alignment &lt;dir&gt; of the solar modules in the string "StringnameX". The string name is a key value of the
<b>setupInverterStrings</b> attribute. <br> <b>setupInverterStrings</b> attribute. <br>
@ -20204,8 +20193,7 @@ to ensure that the system configuration is correct.
<ul> <ul>
<a id="SolarForecast-set-setupStringDeclination"></a> <a id="SolarForecast-set-setupStringDeclination"></a>
<li><b>setupStringDeclination &lt;Stringname1&gt;=&lt;Angle&gt; [&lt;Stringname2&gt;=&lt;Angle&gt; &lt;Stringname3&gt;=&lt;Angle&gt; ...] </b> <br> <li><b>setupStringDeclination &lt;Stringname1&gt;=&lt;Angle&gt; [&lt;Stringname2&gt;=&lt;Angle&gt; &lt;Stringname3&gt;=&lt;Angle&gt; ...] </b> <br><br>
(only model DWD, ForecastSolarAPI) <br><br>
Tilt angle of the solar modules. The string name is a key value of the attribute <b>setupInverterStrings</b>. <br> Tilt angle of the solar modules. The string name is a key value of the attribute <b>setupInverterStrings</b>. <br>
Possible angles of inclination are: 0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90 Possible angles of inclination are: 0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90
@ -20784,8 +20772,8 @@ to ensure that the system configuration is correct.
<br> <br>
<ul> <ul>
<a id="SolarForecast-get-solApiData"></a> <a id="SolarForecast-get-radiationApiData"></a>
<li><b>solApiData </b> <br><br> <li><b>radiationApiData </b> <br><br>
Lists the radiation data saved in the context of the API call. Lists the radiation data saved in the context of the API call.
The forecast data supplied by the API regarding the global radiation Rad1h and the predicted PV yield (Wh) The forecast data supplied by the API regarding the global radiation Rad1h and the predicted PV yield (Wh)
@ -22122,9 +22110,10 @@ to ensure that the system configuration is correct.
<li><b>setupRadiationAPI </b> <br><br> <li><b>setupRadiationAPI </b> <br><br>
Defines the source for the delivery of the solar radiation data. You can select a device of the type DWD_OpenData or Defines the source for the delivery of the solar radiation data. You can select a device of the type DWD_OpenData or
an implemented API can be selected. <br> an implemented API can be selected. <br><br>
<b>Note:</b> If OpenMeteoDWD-API is set in the 'setupWeatherDev1' attribute, no radiation data service other than
OpenMeteoDWD-API can be selected. <br><br> <b>Note:</b> If an OpenMeteo API is also set in the 'setupWeatherDev1' attribute, the settings of both attributes
are harmonized, whereby the setting of 'setupRadiationAPI' is leading. <br><br>
<b>OpenMeteoDWD-API</b> <br> <b>OpenMeteoDWD-API</b> <br>
@ -22254,29 +22243,30 @@ to ensure that the system configuration is correct.
</li> </li>
<br> <br>
<a id="SolarForecast-attr-setupWeatherDev" data-pattern="setupWeatherDev.*"></a> <a id="SolarForecast-attr-setupWeatherDev" data-pattern="setupWeatherDev.*"></a>
<li><b>setupWeatherDevX </b> <br><br> <li><b>setupWeatherDevX </b> <br><br>
Specifies the device or API for providing the required weather data (cloud cover, precipitation, etc.).<br> Specifies the device or API for providing the required weather data (cloud cover, precipitation, etc.).<br>
The attribute 'setupWeatherDev1' specifies the leading weather service and is mandatory.<br> The attribute 'setupWeatherDev1' specifies the leading weather service and is mandatory.<br><br>
If an Open-Meteo API is selected in the 'setupWeatherDev1' attribute, this Open-Meteo service is automatically set as the
source of the radiation data (Attribute setupRadiationAPI). <br><br> <b>Note:</b> If an OpenMeteo API is also set in the 'setupRadiationAPI' attribute, the settings of both attributes
are harmonized, whereby the setting of 'setupRadiationAPI' is leading. <br><br>
<b>OpenMeteoDWD-API</b> <br> <b>OpenMeteoDWD-API</b> <br>
Open-Meteo is an open source weather API and offers free access for non-commercial purposes. Open-Meteo is an open source weather API and offers free access for non-commercial purposes.
No API key is required. No API key is required.
Open-Meteo leverages a powerful combination of global (11 km) and mesoscale (1 km) weather models from esteemed Open-Meteo leverages a powerful combination of global (11 km) and mesoscale (1 km) weather models from esteemed
national weather services. national weather services.
This API provides access to the renowned ICON weather models of the German Weather Service (DWD), which provide This API provides access to the renowned ICON weather models of the German Weather Service (DWD), which provide
15-minute data for short-term forecasts in Central Europe and global forecasts with a resolution of 11 km. 15-minute data for short-term forecasts in Central Europe and global forecasts with a resolution of 11 km.
The ICON model is a preferred choice for general weather forecast APIs when no other high-resolution weather The ICON model is a preferred choice for general weather forecast APIs when no other high-resolution weather
models are available. The models DWD Icon D2, DWD Icon EU and DWD Icon Global models are merged into a models are available. The models DWD Icon D2, DWD Icon EU and DWD Icon Global models are merged into a
seamless forecast. seamless forecast.
The comprehensive and clearly laid out The comprehensive and clearly laid out
<a href='https://open-meteo.com/en/docs/dwd-api' target='_blank'>API Documentation</a> is available on <a href='https://open-meteo.com/en/docs/dwd-api' target='_blank'>API Documentation</a> is available on
the service's website. the service's website.
<br><br> <br><br>
<b>OpenMeteoDWDEnsemble-API</b> <br> <b>OpenMeteoDWDEnsemble-API</b> <br>
@ -22552,8 +22542,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<ul> <ul>
<a id="SolarForecast-set-setupStringAzimuth"></a> <a id="SolarForecast-set-setupStringAzimuth"></a>
<li><b>setupStringAzimuth &lt;Stringname1&gt;=&lt;dir&gt; [&lt;Stringname2&gt;=&lt;dir&gt; &lt;Stringname3&gt;=&lt;dir&gt; ...] </b> <br> <li><b>setupStringAzimuth &lt;Stringname1&gt;=&lt;dir&gt; [&lt;Stringname2&gt;=&lt;dir&gt; &lt;Stringname3&gt;=&lt;dir&gt; ...] </b> <br><br>
(nur Model DWD, OpenMeteo*, ForecastSolarAPI) <br><br>
Ausrichtung &lt;dir&gt; der Solarmodule im String "StringnameX". Der Stringname ist ein Schlüsselwert des Ausrichtung &lt;dir&gt; der Solarmodule im String "StringnameX". Der Stringname ist ein Schlüsselwert des
Attributs <b>setupInverterStrings</b>. <br> Attributs <b>setupInverterStrings</b>. <br>
@ -22590,8 +22579,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<ul> <ul>
<a id="SolarForecast-set-setupStringDeclination"></a> <a id="SolarForecast-set-setupStringDeclination"></a>
<li><b>setupStringDeclination &lt;Stringname1&gt;=&lt;Winkel&gt; [&lt;Stringname2&gt;=&lt;Winkel&gt; &lt;Stringname3&gt;=&lt;Winkel&gt; ...] </b> <br> <li><b>setupStringDeclination &lt;Stringname1&gt;=&lt;Winkel&gt; [&lt;Stringname2&gt;=&lt;Winkel&gt; &lt;Stringname3&gt;=&lt;Winkel&gt; ...] </b> <br><br>
(nur Model DWD, ForecastSolarAPI) <br><br>
Neigungswinkel der Solarmodule. Der Stringname ist ein Schlüsselwert des Attributs <b>setupInverterStrings</b>. <br> Neigungswinkel der Solarmodule. Der Stringname ist ein Schlüsselwert des Attributs <b>setupInverterStrings</b>. <br>
Mögliche Neigungswinkel sind: 0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90 Mögliche Neigungswinkel sind: 0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90
@ -23179,8 +23167,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<br> <br>
<ul> <ul>
<a id="SolarForecast-get-solApiData"></a> <a id="SolarForecast-get-radiationApiData"></a>
<li><b>solApiData </b> <br><br> <li><b>radiationApiData </b> <br><br>
Listet die im Kontext des API-Abrufs gespeicherten Strahlungsdaten auf. Listet die im Kontext des API-Abrufs gespeicherten Strahlungsdaten auf.
Die von der API gelieferten Vorhersagedaten bzgl. der Globalstrahlung Rad1h und des auf einen String bezogenen Die von der API gelieferten Vorhersagedaten bzgl. der Globalstrahlung Rad1h und des auf einen String bezogenen
@ -24517,9 +24505,10 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<li><b>setupRadiationAPI </b> <br><br> <li><b>setupRadiationAPI </b> <br><br>
Legt die Quelle zur Lieferung der solaren Strahlungsdaten fest. Es kann ein Device vom Typ DWD_OpenData oder Legt die Quelle zur Lieferung der solaren Strahlungsdaten fest. Es kann ein Device vom Typ DWD_OpenData oder
eine implementierte API eines Dienstes ausgewählt werden. <br> eine implementierte API eines Dienstes ausgewählt werden. <br><br>
<b>Hinweis:</b> Ist eine OpenMeteo API im Attribut 'setupWeatherDev1' gesetzt, kann kein anderer Strahlungsdatendienst als
diese OpenMeteo API ausgewählt werden. <br><br> <b>Hinweis:</b> Ist im Attribut 'setupWeatherDev1' ebenfalls eine OpenMeteo API gesetzt, werden die Einstellungen
beider Attribute harmonisiert wobei die Einstellung von 'setupRadiationAPI' führend ist. <br><br>
<b>OpenMeteoDWD-API</b> <br> <b>OpenMeteoDWD-API</b> <br>
@ -24651,28 +24640,29 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
</li> </li>
<br> <br>
<a id="SolarForecast-attr-setupWeatherDev" data-pattern="setupWeatherDev.*"></a> <a id="SolarForecast-attr-setupWeatherDev" data-pattern="setupWeatherDev.*"></a>
<li><b>setupWeatherDevX </b> <br><br> <li><b>setupWeatherDevX </b> <br><br>
Gibt das Gerät oder die API zur Lieferung der erforderlichen Wetterdaten (Wolkendecke, Niederschlag usw.) an.<br> Gibt das Gerät oder die API zur Lieferung der erforderlichen Wetterdaten (Wolkendecke, Niederschlag usw.) an.<br>
Das Attribut 'setupWeatherDev1' definiert den führenden Wetterdienst und ist zwingend erforderlich.<br> Das Attribut 'setupWeatherDev1' definiert den führenden Wetterdienst und ist zwingend erforderlich. <br><br>
Ist eine Open-Meteo API im Attribut 'setupWeatherDev1' ausgewählt, wird dieser Open-Meteo Dienst automatisch auch als Quelle
der Strahlungsdaten (Attribut setupRadiationAPI) eingestellt. <br><br> <b>Hinweis:</b> Ist im Attribut 'setupRadiationAPI' ebenfalls eine OpenMeteo API gesetzt, werden die Einstellungen
beider Attribute harmonisiert wobei die Einstellung von 'setupRadiationAPI' führend ist. <br><br>
<b>OpenMeteoDWD-API</b> <br> <b>OpenMeteoDWD-API</b> <br>
Open-Meteo ist eine Open-Source-Wetter-API und bietet kostenlosen Zugang für nicht-kommerzielle Zwecke. Open-Meteo ist eine Open-Source-Wetter-API und bietet kostenlosen Zugang für nicht-kommerzielle Zwecke.
Es ist kein API-Schlüssel erforderlich. Es ist kein API-Schlüssel erforderlich.
Open-Meteo nutzt eine leistungsstarke Kombination aus globalen (11 km) und mesoskaligen (1 km) Wettermodellen Open-Meteo nutzt eine leistungsstarke Kombination aus globalen (11 km) und mesoskaligen (1 km) Wettermodellen
von angesehenen nationalen Wetterdiensten. von angesehenen nationalen Wetterdiensten.
Diese API bietet Zugang zu den renommierten ICON-Wettermodellen des Deutschen Wetterdienstes (DWD), die Diese API bietet Zugang zu den renommierten ICON-Wettermodellen des Deutschen Wetterdienstes (DWD), die
15-minütige Daten für kurzfristige Vorhersagen in Mitteleuropa und globale Vorhersagen mit einer Auflösung 15-minütige Daten für kurzfristige Vorhersagen in Mitteleuropa und globale Vorhersagen mit einer Auflösung
von 11 km liefern. Das ICON-Modell ist eine bevorzugte Wahl für allgemeine Wettervorhersage-APIs, wenn keine von 11 km liefern. Das ICON-Modell ist eine bevorzugte Wahl für allgemeine Wettervorhersage-APIs, wenn keine
anderen hochauflösenden Wettermodelle verfügbar sind. Es werden die Modelle DWD Icon D2, DWD Icon EU anderen hochauflösenden Wettermodelle verfügbar sind. Es werden die Modelle DWD Icon D2, DWD Icon EU
und DWD Icon Global zu einer nahtlosen Vorhersage zusammengeführt. und DWD Icon Global zu einer nahtlosen Vorhersage zusammengeführt.
Auf der Webseite des Dienstes ist die umfangreiche und übersichtliche Auf der Webseite des Dienstes ist die umfangreiche und übersichtliche
<a href='https://open-meteo.com/en/docs/dwd-api' target='_blank'>API Dokumentation</a> verfügbar. <a href='https://open-meteo.com/en/docs/dwd-api' target='_blank'>API Dokumentation</a> verfügbar.
<br><br> <br><br>
<b>OpenMeteoDWDEnsemble-API</b> <br> <b>OpenMeteoDWDEnsemble-API</b> <br>

View File

@ -156,7 +156,10 @@ BEGIN {
# Versions History intern # Versions History intern
my %vNotesIntern = ( my %vNotesIntern = (
"1.37.9" => "29.11.2024 activate StatusAPI-Hash, Separation of radiation API-data , API-state data , weather-API data ", "1.38.0" => "30.11.2024 optimize data handling, rename getter solApiData to radiationApiData, ".
"set setupStringAzimuth, setupStringDeclination is checked due to dependencies to OpenMeteo ".
"attr setupRadiationAPI and setupWeatherDev1 can be set largely independently of each other ",
"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 ". "_saveEnergyConsumption: extended for Debug collectData, preparation of weatherApiData ".
"new func WeatherAPIVal, StatusAPIVal ", "new func WeatherAPIVal, StatusAPIVal ",
@ -601,7 +604,7 @@ my %hget = ( # Ha
forecastQualities => { fn => \&_getForecastQualities, needcred => 0 }, forecastQualities => { fn => \&_getForecastQualities, needcred => 0 },
nextHours => { fn => \&_getlistNextHours, needcred => 0 }, nextHours => { fn => \&_getlistNextHours, needcred => 0 },
rooftopData => { fn => \&_getRoofTopData, needcred => 0 }, rooftopData => { fn => \&_getRoofTopData, needcred => 0 },
solApiData => { fn => \&_getlistSolCastData, needcred => 0 }, radiationApiData => { fn => \&_getlistRadiationApiData, needcred => 0 },
weatherApiData => { fn => \&_getlistWeatherApiData, needcred => 0 }, weatherApiData => { fn => \&_getlistWeatherApiData, needcred => 0 },
statusApiData => { fn => \&_getlistStatusApiData, needcred => 0 }, statusApiData => { fn => \&_getlistStatusApiData, needcred => 0 },
valDecTree => { fn => \&_getaiDecTree, needcred => 0 }, valDecTree => { fn => \&_getaiDecTree, needcred => 0 },
@ -1293,9 +1296,6 @@ sub Initialize {
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
########################################################################################################################## ##########################################################################################################################
# my $av = 'obsolete#-#use#attr#flowGraphicControl#instead';
# $hash->{AttrList} .= " flowGraphicCss:$av flowGraphicSize:$av flowGraphicAnimate:$av flowGraphicConsumerDistance:$av flowGraphicShowConsumer:$av flowGraphicShowConsumerDummy:$av flowGraphicShowConsumerPower:$av flowGraphicShowConsumerRemainTime:$av flowGraphicShift:$av ";
# my $av1 = "obsolete#-#the#attribute#will#be#deleted#soon"; # my $av1 = "obsolete#-#the#attribute#will#be#deleted#soon";
# $hash->{AttrList} .= " affect70percentRule:$av1 ctrlAutoRefresh:$av1 ctrlAutoRefreshFW:$av1 "; # $hash->{AttrList} .= " affect70percentRule:$av1 ctrlAutoRefresh:$av1 ctrlAutoRefreshFW:$av1 ";
########################################################################################################################## ##########################################################################################################################
@ -1364,7 +1364,7 @@ sub Define {
$params->{file} = $scpicache.$name; # Cache File SolCast API Werte einlesen wenn vorhanden $params->{file} = $scpicache.$name; # Cache File SolCast API Werte einlesen wenn vorhanden
$params->{cachename} = 'solcastapi'; $params->{cachename} = 'solcastapi';
$params->{title} = 'solApiData'; $params->{title} = 'radiationApiData';
_readCacheFile ($params); _readCacheFile ($params);
$params->{file} = $statcache.$name; # Cache File API-Status einlesen wenn vorhanden $params->{file} = $statcache.$name; # Cache File API-Status einlesen wenn vorhanden
@ -1590,6 +1590,8 @@ sub Set {
"powerTrigger:textField-long ". "powerTrigger:textField-long ".
"pvCorrectionFactor_Auto:noLearning,on_simple".($ipai ? ',on_simple_ai,' : ',')."on_complex".($ipai ? ',on_complex_ai,' : ',')."off ". "pvCorrectionFactor_Auto:noLearning,on_simple".($ipai ? ',on_simple_ai,' : ',')."on_complex".($ipai ? ',on_complex_ai,' : ',')."off ".
"reset:$resets ". "reset:$resets ".
"setupStringAzimuth ".
"setupStringDeclination ".
$cf." " $cf." "
; ;
@ -1599,20 +1601,10 @@ sub Set {
$setlist .= "roofIdentPair " $setlist .= "roofIdentPair "
; ;
} }
elsif (isForecastSolarUsed ($hash)) {
$setlist .= "setupStringAzimuth ".
"setupStringDeclination "
;
}
elsif (isVictronKiUsed ($hash)) { elsif (isVictronKiUsed ($hash)) {
$setlist .= "vrmCredentials " $setlist .= "vrmCredentials "
; ;
} }
else {
$setlist .= "setupStringAzimuth ".
"setupStringDeclination "
;
}
## KI spezifische Setter ## KI spezifische Setter
########################## ##########################
@ -2513,7 +2505,7 @@ sub Get {
"pvCircular:noArg ". "pvCircular:noArg ".
"pvHistory:#,exportToCsv,$pvl ". "pvHistory:#,exportToCsv,$pvl ".
"rooftopData:noArg ". "rooftopData:noArg ".
"solApiData:noArg ". "radiationApiData:noArg ".
"statusApiData:noArg ". "statusApiData:noArg ".
"valCurrent:noArg ". "valCurrent:noArg ".
"weatherApiData:noArg " "weatherApiData:noArg "
@ -2569,24 +2561,36 @@ sub _getRoofTopData {
delete $data{$type}{$name}{current}{dwdRad1hAge}; delete $data{$type}{$name}{current}{dwdRad1hAge};
delete $data{$type}{$name}{current}{dwdRad1hAgeTS}; delete $data{$type}{$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";
my $ret = "$name is not a valid SolarForeCast Model: ".$hash->{MODEL}; if ($rapi eq 'SolCast') {
if ($hash->{MODEL} eq 'SolCastAPI') {
$ret = __getSolCastData ($paref); $ret = __getSolCastData ($paref);
} }
elsif ($hash->{MODEL} eq 'ForecastSolarAPI') { elsif ($rapi eq 'ForecastSolar') {
$ret = __getForecastSolarData ($paref); $ret = __getForecastSolarData ($paref);
} }
elsif ($hash->{MODEL} eq 'DWD') { elsif ($rapi eq 'DWD') {
$ret = __getDWDSolarData ($paref); $ret = __getDWDSolarData ($paref);
} }
elsif ($hash->{MODEL} eq 'VictronKiAPI') { elsif ($rapi eq 'VictronKi') {
$ret = __getVictronSolarData ($paref); $ret = __getVictronSolarData ($paref);
} }
elsif ($hash->{MODEL} =~ /^OpenMeteo/xs) {
if ($rapi eq 'OpenMeteo' || $wapi eq 'OpenMeteo') {
if ($rapi eq 'OpenMeteo') {
$paref->{reqm} = 'MODEL';
}
else {
$paref->{reqm} = 'WEATHERMODEL';
}
$ret = __getopenMeteoData ($paref); $ret = __getopenMeteoData ($paref);
} }
delete $paref->{reqm};
return $ret; return $ret;
} }
@ -2876,7 +2880,7 @@ 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 $period =~ s/.*(\d\d).*/$1/; # -> die neuen Daten (in dem Fall nur die einer halben Stunde) im nächsten Schritt addiert werden
my $est50 = SolCastAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0) / (60/$period); my $est50 = SolCastAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0) / (60/$period);
$data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = $est50 if($est50); $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = sprintf "%.0f", $est50 if($est50);
$k++; $k++;
next; next;
@ -2902,15 +2906,17 @@ sub __solCast_ApiResponse {
$period = $jdata->{'forecasts'}[$k]{'period'}; $period = $jdata->{'forecasts'}[$k]{'period'};
$period =~ s/.*(\d\d).*/$1/; $period =~ s/.*(\d\d).*/$1/;
$pvest50 = sprintf "%.0f", ($pvest50 * ($period/60) * 1000);
if ($debug =~ /apiProcess/x) { # nur für Debugging if ($debug =~ /apiProcess/x) { # nur für Debugging
if (exists $data{$type}{$name}{solcastapi}{$string}{$starttmstr}) { if (exists $data{$type}{$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 - Start Date/Time: }. $starttmstr);
Log3 ($name, 1, qq{$name DEBUG> SolCast API Hash - pv_estimate50 add: }.(sprintf "%.0f", ($pvest50 * ($period/60) * 1000)).qq{, contains already: }.SolCastAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0)); Log3 ($name, 1, qq{$name DEBUG> SolCast API Hash - pv_estimate50 add: }.$pvest50.qq{, contains already: }.SolCastAPIVal ($hash, $string, $starttmstr, 'pv_estimate50', 0));
} }
} }
$data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} += sprintf "%.0f", ($pvest50 * ($period/60) * 1000); $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} += $pvest50;
$k++; $k++;
} }
@ -4085,6 +4091,7 @@ sub __getopenMeteoData {
my $t = $paref->{t}; my $t = $paref->{t};
my $lang = $paref->{lang}; my $lang = $paref->{lang};
my $debug = $paref->{debug}; my $debug = $paref->{debug};
my $reqm = $paref->{reqm};
my $hash = $defs{$name}; my $hash = $defs{$name};
my $donearq = StatusAPIVal ($hash, 'OpenMeteo', '?All', 'todayDoneAPIrequests', 0); my $donearq = StatusAPIVal ($hash, 'OpenMeteo', '?All', 'todayDoneAPIrequests', 0);
@ -4106,7 +4113,8 @@ sub __getopenMeteoData {
debugLog ($paref, 'apiCall', qq{Open-Meteo API Call - the daily API requests -> limited to: $ometmaxreq, done: $donearq}); debugLog ($paref, 'apiCall', qq{Open-Meteo API Call - the daily API requests -> limited to: $ometmaxreq, done: $donearq});
my $submodel = InternalVal ($hash->{NAME}, 'MODEL', ''); my $submodel = InternalVal ($hash->{NAME}, $reqm, '');
$paref->{allstrings} = AttrVal ($name, 'setupInverterStrings', ''); $paref->{allstrings} = AttrVal ($name, 'setupInverterStrings', '');
$paref->{submodel} = $submodel eq 'OpenMeteoDWDAPI' ? 'DWD ICON Seamless' : $paref->{submodel} = $submodel eq 'OpenMeteoDWDAPI' ? 'DWD ICON Seamless' :
$submodel eq 'OpenMeteoDWDEnsembleAPI' ? 'DWD ICON Seamless Ensemble' : $submodel eq 'OpenMeteoDWDEnsembleAPI' ? 'DWD ICON Seamless Ensemble' :
@ -4117,6 +4125,7 @@ sub __getopenMeteoData {
$paref->{callequivalent} = $submodel eq 'OpenMeteoDWDEnsembleAPI' ? 20 : 1; $paref->{callequivalent} = $submodel eq 'OpenMeteoDWDEnsembleAPI' ? 20 : 1;
$paref->{begin} = 1; $paref->{begin} = 1;
$paref->{requestmode} = $reqm;
__openMeteoDWD_ApiRequest ($paref); __openMeteoDWD_ApiRequest ($paref);
@ -4154,7 +4163,8 @@ sub __openMeteoDWD_ApiRequest {
my $debug = $paref->{debug}; my $debug = $paref->{debug};
my $lang = $paref->{lang}; my $lang = $paref->{lang};
my $t = $paref->{t} // int time; my $t = $paref->{t} // int time;
my $submodel = $paref->{submodel}; # abzufragendes Wettermodell my $submodel = $paref->{submodel}; # abzufragendes Datenmodell
my $requestmode = $paref->{requestmode};
my $hash = $defs{$name}; my $hash = $defs{$name};
@ -4167,18 +4177,26 @@ sub __openMeteoDWD_ApiRequest {
return; return;
} }
my $string; my ($string, $err);
($string, $allstrings) = split ",", $allstrings, 2; ($string, $allstrings) = split ",", $allstrings, 2;
my ($set, $lat, $lon, $elev) = locCoordinates(); my ($set, $lat, $lon, $elev) = locCoordinates();
if (!$set) { if (!$set) {
my $err = qq{the attribute 'latitude' and/or 'longitude' in global device is not set}; $err = qq{ERROR - the attribute 'latitude' and/or 'longitude' in global device is not set};
singleUpdateState ( {hash => $hash, state => $err, evt => 1} ); Log3 ($name, 1, "$name - $err");
return $err; return;
} }
my $tilt = StringVal ($hash, $string, 'tilt', '<unknown>'); my $tilt = StringVal ($hash, $string, 'tilt', '<unknown>');
my $az = StringVal ($hash, $string, 'azimut', '<unknown>'); my $az = StringVal ($hash, $string, 'azimut', '<unknown>');
if ($requestmode eq 'WEATHERMODEL' && $string eq 'KI-based') {$tilt = 0; $az = 0;} # Dummy Settings
if ($tilt eq '<unknown>' || $az eq '<unknown>') {
$err = qq{ERROR OpenMeteo API Call - the reading 'setupStringAzimuth' and/or 'setupStringDeclination' is not set};
Log3 ($name, 1, "$name - $err");
return;
}
my $url = "https://api.open-meteo.com/v1/forecast?"; my $url = "https://api.open-meteo.com/v1/forecast?";
$url = "https://ensemble-api.open-meteo.com/v1/ensemble?" if($submodel =~ /Ensemble/xs); # Ensemble Modell gewählt $url = "https://ensemble-api.open-meteo.com/v1/ensemble?" if($submodel =~ /Ensemble/xs); # Ensemble Modell gewählt
@ -4195,8 +4213,9 @@ sub __openMeteoDWD_ApiRequest {
$url .= "&tilt=".$tilt; $url .= "&tilt=".$tilt;
$url .= "&azimuth=".$az; $url .= "&azimuth=".$az;
debugLog ($paref, 'apiCall', qq{Open-Meteo API Call - Request for PV-String "$string" with Weather Model >$submodel<:\n$url}); debugLog ($paref, 'apiCall', qq{Open-Meteo API Call - Request for PV-String "$string" with Data Model >$submodel<:\n$url});
debugLog ($paref, 'apiCall|apiProcess', qq{Open-Meteo API Call - Request Mode: $requestmode});
my $caller = (caller(0))[3]; # Rücksprungmarke my $caller = (caller(0))[3]; # Rücksprungmarke
my $param = { my $param = {
@ -4209,6 +4228,7 @@ sub __openMeteoDWD_ApiRequest {
submodel => $submodel, submodel => $submodel,
begin => $paref->{begin}, begin => $paref->{begin},
callequivalent => $paref->{callequivalent}, callequivalent => $paref->{callequivalent},
requestmode => $requestmode,
caller => \&$caller, caller => \&$caller,
stc => [gettimeofday], stc => [gettimeofday],
allstrings => $allstrings, allstrings => $allstrings,
@ -4257,6 +4277,7 @@ sub __openMeteoDWD_ApiResponse {
my $caller = $paref->{caller}; my $caller = $paref->{caller};
my $string = $paref->{string}; my $string = $paref->{string};
my $allstrings = $paref->{allstrings}; my $allstrings = $paref->{allstrings};
my $requestmode = $paref->{requestmode}; # MODEL oder WEATHERMODEL
my $stc = $paref->{stc}; # Startzeit API Abruf my $stc = $paref->{stc}; # Startzeit API Abruf
my $lang = $paref->{lang}; my $lang = $paref->{lang};
my $debug = $paref->{debug}; my $debug = $paref->{debug};
@ -4379,6 +4400,7 @@ sub __openMeteoDWD_ApiResponse {
my $rad1wh = $jdata->{hourly}{global_tilted_irradiance}[$k]; # Wh/m2 my $rad1wh = $jdata->{hourly}{global_tilted_irradiance}[$k]; # Wh/m2
my $rad = 10 * (sprintf "%.0f", ($rad1wh * $WhtokJ) / 10); # Umrechnung Wh/m2 in kJ/m2 -> my $rad = 10 * (sprintf "%.0f", ($rad1wh * $WhtokJ) / 10); # Umrechnung Wh/m2 in kJ/m2 ->
my $pv = sprintf "%.2f", int ($rad1wh / 1000 * $peak * $prdef); # Rad wird in kWh/m2 erwartet my $pv = sprintf "%.2f", int ($rad1wh / 1000 * $peak * $prdef); # Rad wird in kWh/m2 erwartet
my $don = $jdata->{hourly}{is_day}[$k]; my $don = $jdata->{hourly}{is_day}[$k];
my $temp = $jdata->{hourly}{temperature_2m}[$k]; my $temp = $jdata->{hourly}{temperature_2m}[$k];
my $rain = $jdata->{hourly}{rain}[$k]; # Regen in Millimeter = kg/m2 my $rain = $jdata->{hourly}{rain}[$k]; # Regen in Millimeter = kg/m2
@ -4388,7 +4410,10 @@ sub __openMeteoDWD_ApiResponse {
if ($k == 0 && $curwid) { $curwid = ($don ? 0 : 100) + $curwid } if ($k == 0 && $curwid) { $curwid = ($don ? 0 : 100) + $curwid }
if ($debug =~ /apiProcess/xs) { if ($debug =~ /apiProcess/xs) {
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $pvtmstr - Rad1Wh: $rad1wh, Rad1kJ: $rad, PV est: $pv Wh"); if ($requestmode eq 'MODEL') {
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $pvtmstr - Rad1Wh: $rad1wh, Rad1kJ: $rad, PV est: $pv Wh");
}
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $pvtmstr - RR1c: $rain"); Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $pvtmstr - RR1c: $rain");
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $otmstr - DoN: $don"); Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $otmstr - DoN: $don");
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $otmstr - Temp: $temp"); Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API $otmstr - Temp: $temp");
@ -4402,29 +4427,24 @@ sub __openMeteoDWD_ApiResponse {
} }
} }
$data{$type}{$name}{solcastapi}{$string}{$pvtmstr}{pv_estimate50} = $pv; # Startstunde verschieben if ($requestmode eq 'MODEL') {
$data{$type}{$name}{solcastapi}{$string}{$pvtmstr}{pv_estimate50} = $pv; # Startstunde verschieben
my $fwtg = formatWeatherTimestrg ($pvtmstr); if ($paref->{begin}) { # im ersten Call den DS löschen -> dann Aufsummierung
delete $data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h};
}
if ($paref->{begin}) { # im ersten Call den DS löschen -> dann Aufsummierung $data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h} += $rad; # Startstunde verschieben, Rad Werte aller Strings addieren
delete $data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h};
} }
$data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h} += $rad; # Startstunde verschieben, Rad Werte aller Strings addieren
## Wetterdaten ## Wetterdaten
################ ################
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{rr1c} = $rain; # Startstunde verschieben my $fwtg = formatWeatherTimestrg ($pvtmstr); # Zeit gemäß DWD_OpenData-Format
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{rr1c} = $rain;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{rr1c} = $rain;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $pvtmstr; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $pvtmstr;
$fwtg = formatWeatherTimestrg ($otmstr); $fwtg = formatWeatherTimestrg ($otmstr); # Zeit gemäß DWD_OpenData-Format
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{don} = $don;
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{neff} = $wcc;
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ww} = $wid;
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ttt} = $temp;
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{UpdateTime} = $rt;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{don} = $don; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{don} = $don;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $wcc; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $wcc;
@ -4433,11 +4453,7 @@ sub __openMeteoDWD_ApiResponse {
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{UpdateTime} = $rt; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{UpdateTime} = $rt;
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $otmstr; $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{StartTime} = $otmstr;
if ($k == 0) { if ($k == 0) {
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{neff} = $curwcc if(defined $curwcc);
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ww} = $curwid if(defined $curwid);
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ttt} = $curtmp if(defined $curtmp);
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{neff} = $curwcc if(defined $curwcc); $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}{ww} = $curwid if(defined $curwid);
$data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{ttt} = $curtmp if(defined $curtmp); $data{$type}{$name}{weatherapi}{OpenMeteo}{$fwtg}{ttt} = $curtmp if(defined $curtmp);
@ -4464,9 +4480,6 @@ sub __openMeteoDWD_ApiResponse {
} }
if ($k == 0) { if ($k == 0) {
$data{$type}{$name}{solcastapi}{'?All'}{sunrise}{today} = $sunrise;
$data{$type}{$name}{solcastapi}{'?All'}{sunset}{today} = $sunset;
$data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{today} = $sunrise; $data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{today} = $sunrise;
$data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{today} = $sunset; $data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{today} = $sunset;
@ -4477,9 +4490,6 @@ sub __openMeteoDWD_ApiResponse {
} }
if ($k == 1) { if ($k == 1) {
$data{$type}{$name}{solcastapi}{'?All'}{sunrise}{tomorrow} = $sunrise;
$data{$type}{$name}{solcastapi}{'?All'}{sunset}{tomorrow} = $sunset;
$data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{tomorrow} = $sunrise; $data{$type}{$name}{weatherapi}{OpenMeteo}{sunrise}{tomorrow} = $sunrise;
$data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{tomorrow} = $sunset; $data{$type}{$name}{weatherapi}{OpenMeteo}{sunset}{tomorrow} = $sunset;
@ -4504,6 +4514,7 @@ sub __openMeteoDWD_ApiResponse {
allstrings => $allstrings, allstrings => $allstrings,
submodel => $submodel, submodel => $submodel,
callequivalent => $paref->{callequivalent}, callequivalent => $paref->{callequivalent},
requestmode => $requestmode,
lang => $lang lang => $lang
}; };
@ -4730,14 +4741,14 @@ return $ret;
} }
############################################################### ###############################################################
# Getter solApiData # Getter radiationApiData
############################################################### ###############################################################
sub _getlistSolCastData { sub _getlistRadiationApiData {
my $paref = shift; my $paref = shift;
my $name = $paref->{name}; my $name = $paref->{name};
my $hash = $defs{$name}; my $hash = $defs{$name};
my $ret = listDataPool ($hash, 'solApiData'); my $ret = listDataPool ($hash, 'radiationApiData');
$ret .= lineFromSpaces ($ret, 10); $ret .= lineFromSpaces ($ret, 10);
return $ret; return $ret;
@ -5425,16 +5436,6 @@ sub Attr {
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
###################################################################################################################### ######################################################################################################################
# 20.10.2024
if ($cmd eq 'set' && $aName =~ /^flowGraphicCss|flowGraphicSize|flowGraphicAnimate|flowGraphicConsumerDistance|flowGraphicShowConsumer|flowGraphicShowConsumerDummy|flowGraphicShowConsumerPower|flowGraphicShowConsumerRemainTime|flowGraphicShift$/) {
if (!$init_done) {
# return qq{Device "$name" -> The attribute '$aName' is replaced by 'flowGraphicControl'. Please press "save config" when restart is finished.};
Log3 ($name, 1, qq{$name - The attribute '$aName' is replaced by 'flowGraphicControl'. Please press "save config" when restart is finished.});
}
else {
return qq{The attribute '$aName' is obsolete and replaced by 'flowGraphicControl'.};
}
}
# 31.10.2024 # 31.10.2024
if ($cmd eq 'set' && $aName =~ /^graphicStartHtml|affect70percentRule|graphicEndHtml|ctrlAutoRefresh|ctrlAutoRefreshFW$/) { if ($cmd eq 'set' && $aName =~ /^graphicStartHtml|affect70percentRule|graphicEndHtml|ctrlAutoRefresh|ctrlAutoRefreshFW$/) {
if (!$init_done) { if (!$init_done) {
@ -6197,19 +6198,26 @@ sub _attrWeatherDev { ## no critic "not used"
if ($aVal =~ /^OpenMeteo/xs) { if ($aVal =~ /^OpenMeteo/xs) {
if ($aName ne 'setupWeatherDev1') { if ($aName ne 'setupWeatherDev1') {
return qq{Only the leading attribute 'setupWeatherDev1' can set to '$aVal'}; return qq{Only the leading weather attribute 'setupWeatherDev1' can set to '$aVal'};
}
my @istrings = split ",", AttrVal ($name, 'setupInverterStrings', ''); # Stringbezeichner
if ((!ReadingsVal ($name, 'setupStringAzimuth', '') || !ReadingsVal ($name, 'setupStringDeclination', '')) &&
!grep /KI-based/, @istrings) {
return qq{Execute 'set $name setupStringAzimuth' and/or 'set $name setupStringDeclination' first.};
} }
InternalTimer (gettimeofday()+1, 'FHEM::SolarForecast::__setRadAPIdelayed', $hash, 0); # automatisch setupRadiationAPI setzen wenn setupWeatherDev1
return;
} }
my $err = checkdwdattr ($name, $aVal, \@dweattrmust); if ($aVal !~ /-API$/xs) { # Attribute des DWD-Devices prüfen
return $err if($err); my $err = checkdwdattr ($name, $aVal, \@dweattrmust);
return $err if($err);
}
} }
InternalTimer (gettimeofday() + 1, 'FHEM::SolarForecast::setModel', $hash, 0); InternalTimer (gettimeofday() + 1, 'FHEM::SolarForecast::__harmonizeAPIdelayed', $hash, 0);
InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::setModel', $hash, 0);
InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0);
return; return;
} }
@ -6233,14 +6241,14 @@ sub _attrRadiationAPI { ## no critic "not used"
return qq{The device "$aVal" doesn't exist or has no TYPE "DWD_OpenData"}; return qq{The device "$aVal" doesn't exist or has no TYPE "DWD_OpenData"};
} }
my $awdev1 = AttrVal ($name, 'setupWeatherDev1', ''); #my $awdev1 = AttrVal ($name, 'setupWeatherDev1', '');
if (($awdev1 eq 'OpenMeteoDWD-API' && $aVal ne 'OpenMeteoDWD-API') || #if (($awdev1 eq 'OpenMeteoDWD-API' && $aVal ne 'OpenMeteoDWD-API') ||
($awdev1 eq 'OpenMeteoDWDEnsemble-API' && $aVal ne 'OpenMeteoDWDEnsemble-API') || # ($awdev1 eq 'OpenMeteoDWDEnsemble-API' && $aVal ne 'OpenMeteoDWDEnsemble-API') ||
($awdev1 eq 'OpenMeteoWorld-API' && $aVal ne 'OpenMeteoWorld-API')) { # ($awdev1 eq 'OpenMeteoWorld-API' && $aVal ne 'OpenMeteoWorld-API')) {
return "The attribute 'setupWeatherDev1' is set to '$awdev1'. \n". # return "The attribute 'setupWeatherDev1' is set to '$awdev1'. \n".
"Change that attribute to another weather device first if you want use an other API."; # "Change that attribute to another weather device first if you want use an other API.";
} #}
if ($aVal =~ /(SolCast|OpenMeteoDWD|OpenMeteoDWDEnsemble|OpenMeteoWorld)-API/xs) { if ($aVal =~ /(SolCast|OpenMeteoDWD|OpenMeteoDWDEnsemble|OpenMeteoWorld)-API/xs) {
return "The library FHEM::Utility::CTZ is missing. Please update FHEM completely." if($ctzAbsent); return "The library FHEM::Utility::CTZ is missing. Please update FHEM completely." if($ctzAbsent);
@ -6267,9 +6275,10 @@ sub _attrRadiationAPI { ## no critic "not used"
readingsDelete ($hash, 'nextRadiationAPICall'); readingsDelete ($hash, 'nextRadiationAPICall');
InternalTimer (gettimeofday() + 1, 'FHEM::SolarForecast::setModel', $hash, 0); # Model setzen InternalTimer (gettimeofday() + 1, 'FHEM::SolarForecast::__harmonizeAPIdelayed', $hash, 0);
InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); InternalTimer (gettimeofday() + 2, 'FHEM::SolarForecast::setModel', $hash, 0); # Model setzen
InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0);
InternalTimer (gettimeofday() + 4, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben
return; return;
} }
@ -6302,15 +6311,22 @@ return;
} }
################################################################ ################################################################
# setupRadiationAPI verzögert aus Attr setzen # Attr setupRadiationAPI und setupWeatherDev1
# harmonisieren wenn erforderlich
# setupRadiationAPI ist führend
################################################################ ################################################################
sub __setRadAPIdelayed { sub __harmonizeAPIdelayed {
my $hash = shift; my $hash = shift;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $awdev1 = AttrVal ($name, 'setupWeatherDev1', ''); my $wedev1 = AttrVal ($name, 'setupWeatherDev1', '');
my $radapi = AttrVal ($name, 'setupRadiationAPI', '');
CommandAttr (undef, "$name setupRadiationAPI $awdev1"); # automatisch setupRadiationAPI setzen
return if($wedev1 eq $radapi);
if ($radapi =~ /OpenMeteo/xs && $wedev1 =~ /OpenMeteo/xs) { # auf OpenMeteo Datenmodell harmonisieren
CommandAttr (undef, "$name setupWeatherDev1 $radapi");
}
return; return;
} }
@ -7082,36 +7098,7 @@ sub centralTask {
setModel ($hash); # Model setzen setModel ($hash); # Model setzen
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
########################################################################################################################## ##########################################################################################################################
for my $n (1..24) { # 08.10.2024
$n = sprintf "%02d", $n;
readingsDelete ($hash, "pvCorrectionFactor_${n}_autocalc");
}
my $fg1 = AttrVal ($name, 'flowGraphicSize', undef); # 20.10.2024
my $fg2 = AttrVal ($name, 'flowGraphicAnimate', undef);
my $fg3 = AttrVal ($name, 'flowGraphicConsumerDistance', undef);
my $fg4 = AttrVal ($name, 'flowGraphicShowConsumer', undef);
my $fg5 = AttrVal ($name, 'flowGraphicShowConsumerDummy', undef);
my $fg6 = AttrVal ($name, 'flowGraphicShowConsumerPower', undef);
my $fg7 = AttrVal ($name, 'flowGraphicShowConsumerRemainTime', undef);
my $fg8 = AttrVal ($name, 'flowGraphicShift', undef);
my $newval;
$newval .= "size=$fg1 " if(defined $fg1);
$newval .= "animate=$fg2 " if(defined $fg2);
$newval .= "consumerdist=$fg3 " if(defined $fg3);
$newval .= "showconsumer=$fg4 " if(defined $fg4);
$newval .= "showconsumerdummy=$fg5 " if(defined $fg5);
$newval .= "showconsumerpower=$fg6 " if(defined $fg6);
$newval .= "showconsumerremaintime=$fg7 " if(defined $fg7);
$newval .= "shift=$fg8 " if(defined $fg8);
if ($newval) {
CommandAttr (undef, "$name flowGraphicControl $newval");
::CommandDeleteAttr (undef, "$name flowGraphicSize|flowGraphicAnimate|flowGraphicConsumerDistance|flowGraphicShowConsumer|flowGraphicShowConsumerDummy|flowGraphicShowConsumerPower|flowGraphicShowConsumerRemainTime|flowGraphicShift");
}
if (exists $data{$type}{$name}{solcastapi}{'?IdPair'}) { # 29.11.2024 if (exists $data{$type}{$name}{solcastapi}{'?IdPair'}) { # 29.11.2024
for my $pk (keys %{$data{$type}{$name}{solcastapi}{'?IdPair'}}) { for my $pk (keys %{$data{$type}{$name}{solcastapi}{'?IdPair'}}) {
my $apikey = SolCastAPIVal ($hash, '?IdPair', $pk, 'apikey', ''); my $apikey = SolCastAPIVal ($hash, '?IdPair', $pk, 'apikey', '');
@ -7132,7 +7119,7 @@ sub centralTask {
for my $key (keys %{$data{$type}{$name}{solcastapi}{'?All'}{'?All'}}) { for my $key (keys %{$data{$type}{$name}{solcastapi}{'?All'}{'?All'}}) {
my $val = SolCastAPIVal ($hash, '?All', '?All', $key, ''); my $val = SolCastAPIVal ($hash, '?All', '?All', $key, '');
if ($val) { if ($rapi && $val) {
$data{$type}{$name}{statusapi}{$rapi}{'?All'}{$key} = $val; $data{$type}{$name}{statusapi}{$rapi}{'?All'}{$key} = $val;
delete $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{$key}; delete $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{$key};
} }
@ -7156,7 +7143,6 @@ sub centralTask {
$data{$type}{$name}{statusapi}{'?VRM'}{'?API'}{credentials} = $vrmcr; $data{$type}{$name}{statusapi}{'?VRM'}{'?API'}{credentials} = $vrmcr;
delete $data{$type}{$name}{solcastapi}{'?VRM'}; delete $data{$type}{$name}{solcastapi}{'?VRM'};
} }
########################################################################################################################## ##########################################################################################################################
my (undef, $disabled, $inactive) = controller ($name); my (undef, $disabled, $inactive) = controller ($name);
@ -7225,7 +7211,7 @@ sub centralTask {
readingsDelete ($hash, 'AllPVforecastsToEvent'); readingsDelete ($hash, 'AllPVforecastsToEvent');
_getRoofTopData ($centpars); # Strahlungswerte/Forecast-Werte in solcastapi-Hash erstellen _getRoofTopData ($centpars); # Strahlungs/Wetter-Daten der gewählten API's abrufen und in internen Strukturen speichern
_transferInverterValues ($centpars); # WR Werte übertragen _transferInverterValues ($centpars); # WR Werte übertragen
_transferAPIRadiationValues ($centpars); # Raw Erzeugungswerte aus solcastapi-Hash übertragen und Forecast mit/ohne Korrektur erstellen _transferAPIRadiationValues ($centpars); # Raw Erzeugungswerte aus solcastapi-Hash übertragen und Forecast mit/ohne Korrektur erstellen
_calcMaxEstimateToday ($centpars); # heutigen Max PV Estimate & dessen Tageszeit ermitteln _calcMaxEstimateToday ($centpars); # heutigen Max PV Estimate & dessen Tageszeit ermitteln
@ -7320,9 +7306,10 @@ sub createStringConfig { ## no critic "not used"
return qq{You use a KI based model. Please set only "KI-based" as String with command "attr $name setupInverterStrings".}; return qq{You use a KI based model. Please set only "KI-based" as String with command "attr $name setupInverterStrings".};
} }
} }
elsif (!isVictronKiUsed ($hash)) {
if (!grep /^KI-based$/, @istrings) {
my $tilt = ReadingsVal ($name, 'setupStringDeclination', ''); # Modul Neigungswinkel für jeden Stringbezeichner my $tilt = ReadingsVal ($name, 'setupStringDeclination', ''); # Modul Neigungswinkel für jeden Stringbezeichner
return qq{Please complete command "set $name setupStringDeclination".} if(!$tilt); return qq{Please complete command "set $name setupStringDeclination"} if(!$tilt);
my ($at,$ht) = parseParams ($tilt); my ($at,$ht) = parseParams ($tilt);
@ -7336,7 +7323,7 @@ sub createStringConfig { ## no critic "not used"
} }
my $dir = ReadingsVal ($name, 'setupStringAzimuth', ''); # Modul Ausrichtung für jeden Stringbezeichner my $dir = ReadingsVal ($name, 'setupStringAzimuth', ''); # Modul Ausrichtung für jeden Stringbezeichner
return qq{Please complete command "set $name setupStringAzimuth".} if(!$dir); return qq{Please complete command "set $name setupStringAzimuth"} if(!$dir);
my ($ad,$hd) = parseParams ($dir); my ($ad,$hd) = parseParams ($dir);
my $iwrong = qq{Please check the input of set "setupStringAzimuth". It seems to be wrong.}; my $iwrong = qq{Please check the input of set "setupStringAzimuth". It seems to be wrong.};
@ -7934,29 +7921,24 @@ sub __delObsoleteAPIData {
my $date = $paref->{date}; # aktuelles Datum my $date = $paref->{date}; # aktuelles Datum
my $hash = $defs{$name}; my $hash = $defs{$name};
my ($rapi, $wapi) = getStatusApiName ($hash);
## Wetter-API Daten löschen ## Wetter-API Daten löschen
############################# #############################
if (keys %{$data{$type}{$name}{weatherapi}}) { if (keys %{$data{$type}{$name}{weatherapi}}) {
if (isWeatherOpenMeteoUsed ($hash)) { delete $data{$type}{$name}{weatherapi}{OpenMeteo} if($wapi ne 'OpenMeteo');
for my $idx (keys %{$data{$type}{$name}{weatherapi}{OpenMeteo}}) {
delete $data{$type}{$name}{weatherapi}{OpenMeteo}{$idx} if($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs);
}
}
else {
delete $data{$type}{$name}{weatherapi}{OpenMeteo};
}
} }
## Status-API Daten löschen ## Status-API Daten löschen
############################# #############################
if (keys %{$data{$type}{$name}{statusapi}}) { if (keys %{$data{$type}{$name}{statusapi}}) {
delete $data{$type}{$name}{statusapi}{OpenMeteo} if(!isOpenMeteoUsed ($hash) && !isWeatherOpenMeteoUsed ($hash)); delete $data{$type}{$name}{statusapi}{OpenMeteo} if($rapi ne 'OpenMeteo' && $wapi ne 'OpenMeteo');
delete $data{$type}{$name}{statusapi}{ForecastSolar} if(!isForecastSolarUsed ($hash)); delete $data{$type}{$name}{statusapi}{ForecastSolar} if($rapi ne 'ForecastSolar');
delete $data{$type}{$name}{statusapi}{SolCast} if(!isSolCastUsed ($hash)); delete $data{$type}{$name}{statusapi}{SolCast} if($rapi ne 'SolCast');
delete $data{$type}{$name}{statusapi}{'?IdPair'} if(!isSolCastUsed ($hash)); delete $data{$type}{$name}{statusapi}{'?IdPair'} if($rapi ne 'SolCast');
delete $data{$type}{$name}{statusapi}{DWD} if(!isDWDUsed ($hash)); delete $data{$type}{$name}{statusapi}{DWD} if($rapi ne 'DWD');
delete $data{$type}{$name}{statusapi}{VictronKi} if(!isVictronKiUsed ($hash)); delete $data{$type}{$name}{statusapi}{VictronKi} if($rapi ne 'VictronKi');
delete $data{$type}{$name}{statusapi}{'?VRM'} if(!isVictronKiUsed ($hash)); delete $data{$type}{$name}{statusapi}{'?VRM'} if($rapi ne 'VictronKi');
} }
## Solar-API Daten löschen ## Solar-API Daten löschen
@ -12020,6 +12002,7 @@ return ($fd, $fh);
} }
################################################################ ################################################################
# Zeit gemäß DWD_OpenData-Format
# Berechnen Tag / Stunden Verschieber ab aktuellen Tag # Berechnen Tag / Stunden Verschieber ab aktuellen Tag
# Input: YYYY-MM-DD HH:MM:SS # Input: YYYY-MM-DD HH:MM:SS
# Output: $fd - 0 (Heute), 1 (Morgen), 2 (Übermorgen), .... # Output: $fd - 0 (Heute), 1 (Morgen), 2 (Übermorgen), ....
@ -12660,10 +12643,7 @@ sub _checkSetupNotComplete {
$ret .= "<td>"; $ret .= "<td>";
$ret .= $hqtxt{entry}{$lang}; # Entry Text $ret .= $hqtxt{entry}{$lang}; # Entry Text
if (!$wedev) { ## no critic 'Cascading' if (!$strings) { ## no critic 'Cascading'
$ret .= $hqtxt{cfd}{$lang};
}
elsif (!$strings) {
$ret .= $hqtxt{ist}{$lang}; $ret .= $hqtxt{ist}{$lang};
} }
elsif (!$peaks) { elsif (!$peaks) {
@ -12671,7 +12651,7 @@ sub _checkSetupNotComplete {
} }
elsif (!$radev) { elsif (!$radev) {
$ret .= $hqtxt{crd}{$lang}; $ret .= $hqtxt{crd}{$lang};
} }
elsif (!$indev) { elsif (!$indev) {
$ret .= $hqtxt{cid}{$lang}; $ret .= $hqtxt{cid}{$lang};
} }
@ -12690,6 +12670,9 @@ sub _checkSetupNotComplete {
elsif (!$mdec && !isSolCastUsed ($hash) && !isVictronKiUsed ($hash)) { elsif (!$mdec && !isSolCastUsed ($hash) && !isVictronKiUsed ($hash)) {
$ret .= $hqtxt{mta}{$lang}; $ret .= $hqtxt{mta}{$lang};
} }
elsif (!$wedev) {
$ret .= $hqtxt{cfd}{$lang};
}
elsif (!$vrmcr && isVictronKiUsed ($hash)) { elsif (!$vrmcr && isVictronKiUsed ($hash)) {
$ret .= $hqtxt{vrmcr}{$lang}; $ret .= $hqtxt{vrmcr}{$lang};
} }
@ -16820,7 +16803,7 @@ sub listDataPool {
return $ret; return $ret;
}; };
if ($htol =~ /solApiData|weatherApiData|statusApiData/xs) { if ($htol =~ /radiationApiData|weatherApiData|statusApiData/xs) {
$h = $data{$type}{$name}{solcastapi}; $h = $data{$type}{$name}{solcastapi};
$h = $data{$type}{$name}{weatherapi} if($htol eq 'weatherApiData'); $h = $data{$type}{$name}{weatherapi} if($htol eq 'weatherApiData');
$h = $data{$type}{$name}{statusapi} if($htol eq 'statusApiData'); $h = $data{$type}{$name}{statusapi} if($htol eq 'statusApiData');
@ -17027,10 +17010,10 @@ sub checkPlantConfig {
my $info = FW_makeImage ('message_info', ''); my $info = FW_makeImage ('message_info', '');
my $result = { # Ergebnishash my $result = { # Ergebnishash
'String Configuration' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 }, 'String Configuration' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 },
'DWD Weather Properties' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 }, 'Weather Properties' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 },
'Common Settings' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 }, 'Common Settings' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 },
'FTUI Widget Files' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 }, 'FTUI Widget Files' => { 'state' => $ok, 'result' => '', 'note' => '', 'info' => 0, 'warn' => 0, 'fault' => 0 },
}; };
my $sub = sub { my $sub = sub {
@ -17099,44 +17082,44 @@ sub checkPlantConfig {
next if(!$fcname && $step ne 1); next if(!$fcname && $step ne 1);
if (!$valid) { if (!$valid) {
$result->{'DWD Weather Properties'}{state} = $nok; $result->{'Weather Properties'}{state} = $nok;
if (!$fcname) { if (!$fcname) {
$result->{'DWD Weather Properties'}{result} .= qq{No DWD device is defined in attribute "setupWeatherDev$step". <br>}; $result->{'Weather Properties'}{result} .= qq{No DWD device is defined in attribute "setupWeatherDev$step". <br>};
} }
else { else {
$result->{'DWD Weather Properties'}{result} .= qq{The DWD device "$fcname" doesn't exist. <br>}; $result->{'Weather Properties'}{result} .= qq{The DWD device "$fcname" doesn't exist. <br>};
} }
$result->{'DWD Weather Properties'}{fault} = 1; $result->{'Weather Properties'}{fault} = 1;
} }
else { else {
if (!$apiu) { if (!$apiu) {
$err = checkdwdattr ($name, $fcname, \@dweattrmust); $err = checkdwdattr ($name, $fcname, \@dweattrmust);
if ($err) { if ($err) {
$result->{'DWD Weather Properties'}{state} = $nok; $result->{'Weather Properties'}{state} = $nok;
$result->{'DWD Weather Properties'}{result} .= $err.'<br>'; $result->{'Weather Properties'}{result} .= $err.'<br>';
$result->{'DWD Weather Properties'}{fault} = 1; $result->{'Weather Properties'}{fault} = 1;
} }
else { else {
$mosm = AttrVal ($fcname, 'forecastRefresh', 6) == 6 ? 'MOSMIX_L' : 'MOSMIX_S'; $mosm = AttrVal ($fcname, 'forecastRefresh', 6) == 6 ? 'MOSMIX_L' : 'MOSMIX_S';
if ($mosm eq 'MOSMIX_L') { if ($mosm eq 'MOSMIX_L') {
$result->{'DWD Weather Properties'}{state} = $info; $result->{'Weather Properties'}{state} = $info;
$result->{'DWD Weather Properties'}{result} .= qq(The device "$fcname" uses "$mosm" which is only updated by DWD every 6 hours. <br>); $result->{'Weather Properties'}{result} .= qq(The device "$fcname" uses "$mosm" which is only updated by DWD every 6 hours. <br>);
$result->{'DWD Weather Properties'}{info} = 1; $result->{'Weather Properties'}{info} = 1;
} }
$result->{'DWD Weather Properties'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: setupWeatherDev$step)<br>"; $result->{'Weather Properties'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: setupWeatherDev$step)<br>";
} }
$result->{'DWD Weather Properties'}{note} .= qq{checked parameters and attributes of device "$fcname": <br>}; $result->{'Weather Properties'}{note} .= qq{checked parameters and attributes of device "$fcname": <br>};
$result->{'DWD Weather Properties'}{note} .= 'forecastProperties -> '.join (',', @dweattrmust).'<br>'; $result->{'Weather Properties'}{note} .= 'forecastProperties -> '.join (',', @dweattrmust).'<br>';
$result->{'DWD Weather Properties'}{note} .= 'forecastRefresh '.($mosm eq 'MOSMIX_L' ? '-> set attribute to below "6" if possible' : '').'<br>'; $result->{'Weather Properties'}{note} .= 'forecastRefresh '.($mosm eq 'MOSMIX_L' ? '-> set attribute to below "6" if possible' : '').'<br>';
} }
else { else {
$result->{'DWD Weather Properties'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: setupWeatherDev$step)<br>"; $result->{'Weather Properties'}{result} .= $hqtxt{fulfd}{$lang}." ($hqtxt{attrib}{$lang}: setupWeatherDev$step)<br>";
} }
} }
} }
@ -17146,16 +17129,16 @@ sub checkPlantConfig {
($err, $resh) = isWeatherAgeExceeded ( {name => $name, lang => $lang} ); ($err, $resh) = isWeatherAgeExceeded ( {name => $name, lang => $lang} );
if (!$err && $resh->{exceed}) { if (!$err && $resh->{exceed}) {
$result->{'DWD Weather Properties'}{state} = $warn; $result->{'Weather Properties'}{state} = $warn;
$result->{'DWD Weather Properties'}{note} .= qq{The Prediction time of Weather data is older than expected when using $resh->{mosmix}. <br>}; $result->{'Weather Properties'}{note} .= qq{The Prediction time of Weather data is older than expected when using $resh->{mosmix}. <br>};
$result->{'DWD Weather Properties'}{note} .= qq{Data time forecast: $resh->{fctime} <br>}; $result->{'Weather Properties'}{note} .= qq{Data time forecast: $resh->{fctime} <br>};
$result->{'DWD Weather Properties'}{note} .= qq{Check the DWD device(s) for proper functioning of the data retrieval. <br>}; $result->{'Weather Properties'}{note} .= qq{Check the DWD device(s) for proper functioning of the data retrieval. <br>};
$result->{'DWD Weather Properties'}{warn} = 1; $result->{'Weather Properties'}{warn} = 1;
} }
$result->{'DWD Weather Properties'}{note} .= '<br>'; $result->{'Weather Properties'}{note} .= '<br>';
$result->{'DWD Weather Properties'}{note} .= qq{checked global Weather parameters: <br>}; $result->{'Weather Properties'}{note} .= qq{checked global Weather parameters: <br>};
$result->{'DWD Weather Properties'}{note} .= 'MOSMIX variant or ICON Forecast Model, Age of Weather data. <br>'; $result->{'Weather Properties'}{note} .= 'MOSMIX variant or ICON Forecast Model, Age of Weather data. <br>';
## Check DWD Radiation Device ## Check DWD Radiation Device
############################### ###############################
@ -17969,8 +17952,10 @@ sub setModel {
elsif ($radapi =~ /OpenMeteoWorld-/xs) { $hash->{MODEL} = 'OpenMeteoWorldAPI'; } elsif ($radapi =~ /OpenMeteoWorld-/xs) { $hash->{MODEL} = 'OpenMeteoWorldAPI'; }
else { $hash->{MODEL} = 'DWD'; } else { $hash->{MODEL} = 'DWD'; }
if ($wthapi =~ /OpenMeteo.*-API/xs) { $hash->{WEATHERMODEL} = 'OpenMeteo'; } if ($wthapi =~ /OpenMeteoDWDEnsemble-/xs) { $hash->{WEATHERMODEL} = 'OpenMeteoDWDEnsembleAPI'; }
else { $hash->{WEATHERMODEL} = 'DWD'; } elsif ($wthapi =~ /OpenMeteoDWD-/xs) { $hash->{WEATHERMODEL} = 'OpenMeteoDWDAPI'; }
elsif ($wthapi =~ /OpenMeteoWorld-/xs) { $hash->{WEATHERMODEL} = 'OpenMeteoWorldAPI'; }
else { $hash->{WEATHERMODEL} = 'DWD'; }
return; return;
} }
@ -18653,10 +18638,12 @@ sub isWeatherDevValid {
if ($fcname) { $valid = 1 } if ($fcname) { $valid = 1 }
if (!$defs{$fcname} || $defs{$fcname}{TYPE} ne "DWD_OpenData") { $valid = '' } if (!$defs{$fcname} || $defs{$fcname}{TYPE} ne "DWD_OpenData") { $valid = '' }
my ($rapi, $wapi) = getStatusApiName ($hash); # $rapi - Radiation-API, $wapi - Weather-API
if (isOpenMeteoUsed($hash) && $fcname =~ /^OpenMeteo/xs) { if ($wapi =~ /^OpenMeteo/xs) {
$valid = 1; $valid = 1;
$apiu = $fcname; $apiu = $wapi;
} }
return ($valid, $fcname, $apiu); return ($valid, $fcname, $apiu);
@ -18992,6 +18979,9 @@ return ($err, $cname, $dswname);
################################################################ ################################################################
# Namen der Strahlungs-API und Wetter-API ermitteln. # Namen der Strahlungs-API und Wetter-API ermitteln.
# Wird als Schlüssel in statusapi bzw. weatherapi verwendet. # Wird als Schlüssel in statusapi bzw. weatherapi verwendet.
# Return:
# $rapi - Name der Strahlungsdaten-API
# $wapi - Name der Wetter-API
################################################################ ################################################################
sub getStatusApiName { sub getStatusApiName {
my $hash = shift; my $hash = shift;
@ -20166,8 +20156,7 @@ to ensure that the system configuration is correct.
<ul> <ul>
<a id="SolarForecast-set-setupStringAzimuth"></a> <a id="SolarForecast-set-setupStringAzimuth"></a>
<li><b>setupStringAzimuth &lt;Stringname1&gt;=&lt;dir&gt; [&lt;Stringname2&gt;=&lt;dir&gt; &lt;Stringname3&gt;=&lt;dir&gt; ...] </b> <br> <li><b>setupStringAzimuth &lt;Stringname1&gt;=&lt;dir&gt; [&lt;Stringname2&gt;=&lt;dir&gt; &lt;Stringname3&gt;=&lt;dir&gt; ...] </b> <br><br>
(only model DWD, OpenMeteo*, ForecastSolarAPI) <br><br>
Alignment &lt;dir&gt; of the solar modules in the string "StringnameX". The string name is a key value of the Alignment &lt;dir&gt; of the solar modules in the string "StringnameX". The string name is a key value of the
<b>setupInverterStrings</b> attribute. <br> <b>setupInverterStrings</b> attribute. <br>
@ -20204,8 +20193,7 @@ to ensure that the system configuration is correct.
<ul> <ul>
<a id="SolarForecast-set-setupStringDeclination"></a> <a id="SolarForecast-set-setupStringDeclination"></a>
<li><b>setupStringDeclination &lt;Stringname1&gt;=&lt;Angle&gt; [&lt;Stringname2&gt;=&lt;Angle&gt; &lt;Stringname3&gt;=&lt;Angle&gt; ...] </b> <br> <li><b>setupStringDeclination &lt;Stringname1&gt;=&lt;Angle&gt; [&lt;Stringname2&gt;=&lt;Angle&gt; &lt;Stringname3&gt;=&lt;Angle&gt; ...] </b> <br><br>
(only model DWD, ForecastSolarAPI) <br><br>
Tilt angle of the solar modules. The string name is a key value of the attribute <b>setupInverterStrings</b>. <br> Tilt angle of the solar modules. The string name is a key value of the attribute <b>setupInverterStrings</b>. <br>
Possible angles of inclination are: 0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90 Possible angles of inclination are: 0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90
@ -20784,8 +20772,8 @@ to ensure that the system configuration is correct.
<br> <br>
<ul> <ul>
<a id="SolarForecast-get-solApiData"></a> <a id="SolarForecast-get-radiationApiData"></a>
<li><b>solApiData </b> <br><br> <li><b>radiationApiData </b> <br><br>
Lists the radiation data saved in the context of the API call. Lists the radiation data saved in the context of the API call.
The forecast data supplied by the API regarding the global radiation Rad1h and the predicted PV yield (Wh) The forecast data supplied by the API regarding the global radiation Rad1h and the predicted PV yield (Wh)
@ -22122,9 +22110,10 @@ to ensure that the system configuration is correct.
<li><b>setupRadiationAPI </b> <br><br> <li><b>setupRadiationAPI </b> <br><br>
Defines the source for the delivery of the solar radiation data. You can select a device of the type DWD_OpenData or Defines the source for the delivery of the solar radiation data. You can select a device of the type DWD_OpenData or
an implemented API can be selected. <br> an implemented API can be selected. <br><br>
<b>Note:</b> If OpenMeteoDWD-API is set in the 'setupWeatherDev1' attribute, no radiation data service other than
OpenMeteoDWD-API can be selected. <br><br> <b>Note:</b> If an OpenMeteo API is also set in the 'setupWeatherDev1' attribute, the settings of both attributes
are harmonized, whereby the setting of 'setupRadiationAPI' is leading. <br><br>
<b>OpenMeteoDWD-API</b> <br> <b>OpenMeteoDWD-API</b> <br>
@ -22254,29 +22243,30 @@ to ensure that the system configuration is correct.
</li> </li>
<br> <br>
<a id="SolarForecast-attr-setupWeatherDev" data-pattern="setupWeatherDev.*"></a> <a id="SolarForecast-attr-setupWeatherDev" data-pattern="setupWeatherDev.*"></a>
<li><b>setupWeatherDevX </b> <br><br> <li><b>setupWeatherDevX </b> <br><br>
Specifies the device or API for providing the required weather data (cloud cover, precipitation, etc.).<br> Specifies the device or API for providing the required weather data (cloud cover, precipitation, etc.).<br>
The attribute 'setupWeatherDev1' specifies the leading weather service and is mandatory.<br> The attribute 'setupWeatherDev1' specifies the leading weather service and is mandatory.<br><br>
If an Open-Meteo API is selected in the 'setupWeatherDev1' attribute, this Open-Meteo service is automatically set as the
source of the radiation data (Attribute setupRadiationAPI). <br><br> <b>Note:</b> If an OpenMeteo API is also set in the 'setupRadiationAPI' attribute, the settings of both attributes
are harmonized, whereby the setting of 'setupRadiationAPI' is leading. <br><br>
<b>OpenMeteoDWD-API</b> <br> <b>OpenMeteoDWD-API</b> <br>
Open-Meteo is an open source weather API and offers free access for non-commercial purposes. Open-Meteo is an open source weather API and offers free access for non-commercial purposes.
No API key is required. No API key is required.
Open-Meteo leverages a powerful combination of global (11 km) and mesoscale (1 km) weather models from esteemed Open-Meteo leverages a powerful combination of global (11 km) and mesoscale (1 km) weather models from esteemed
national weather services. national weather services.
This API provides access to the renowned ICON weather models of the German Weather Service (DWD), which provide This API provides access to the renowned ICON weather models of the German Weather Service (DWD), which provide
15-minute data for short-term forecasts in Central Europe and global forecasts with a resolution of 11 km. 15-minute data for short-term forecasts in Central Europe and global forecasts with a resolution of 11 km.
The ICON model is a preferred choice for general weather forecast APIs when no other high-resolution weather The ICON model is a preferred choice for general weather forecast APIs when no other high-resolution weather
models are available. The models DWD Icon D2, DWD Icon EU and DWD Icon Global models are merged into a models are available. The models DWD Icon D2, DWD Icon EU and DWD Icon Global models are merged into a
seamless forecast. seamless forecast.
The comprehensive and clearly laid out The comprehensive and clearly laid out
<a href='https://open-meteo.com/en/docs/dwd-api' target='_blank'>API Documentation</a> is available on <a href='https://open-meteo.com/en/docs/dwd-api' target='_blank'>API Documentation</a> is available on
the service's website. the service's website.
<br><br> <br><br>
<b>OpenMeteoDWDEnsemble-API</b> <br> <b>OpenMeteoDWDEnsemble-API</b> <br>
@ -22552,8 +22542,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<ul> <ul>
<a id="SolarForecast-set-setupStringAzimuth"></a> <a id="SolarForecast-set-setupStringAzimuth"></a>
<li><b>setupStringAzimuth &lt;Stringname1&gt;=&lt;dir&gt; [&lt;Stringname2&gt;=&lt;dir&gt; &lt;Stringname3&gt;=&lt;dir&gt; ...] </b> <br> <li><b>setupStringAzimuth &lt;Stringname1&gt;=&lt;dir&gt; [&lt;Stringname2&gt;=&lt;dir&gt; &lt;Stringname3&gt;=&lt;dir&gt; ...] </b> <br><br>
(nur Model DWD, OpenMeteo*, ForecastSolarAPI) <br><br>
Ausrichtung &lt;dir&gt; der Solarmodule im String "StringnameX". Der Stringname ist ein Schlüsselwert des Ausrichtung &lt;dir&gt; der Solarmodule im String "StringnameX". Der Stringname ist ein Schlüsselwert des
Attributs <b>setupInverterStrings</b>. <br> Attributs <b>setupInverterStrings</b>. <br>
@ -22590,8 +22579,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<ul> <ul>
<a id="SolarForecast-set-setupStringDeclination"></a> <a id="SolarForecast-set-setupStringDeclination"></a>
<li><b>setupStringDeclination &lt;Stringname1&gt;=&lt;Winkel&gt; [&lt;Stringname2&gt;=&lt;Winkel&gt; &lt;Stringname3&gt;=&lt;Winkel&gt; ...] </b> <br> <li><b>setupStringDeclination &lt;Stringname1&gt;=&lt;Winkel&gt; [&lt;Stringname2&gt;=&lt;Winkel&gt; &lt;Stringname3&gt;=&lt;Winkel&gt; ...] </b> <br><br>
(nur Model DWD, ForecastSolarAPI) <br><br>
Neigungswinkel der Solarmodule. Der Stringname ist ein Schlüsselwert des Attributs <b>setupInverterStrings</b>. <br> Neigungswinkel der Solarmodule. Der Stringname ist ein Schlüsselwert des Attributs <b>setupInverterStrings</b>. <br>
Mögliche Neigungswinkel sind: 0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90 Mögliche Neigungswinkel sind: 0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90
@ -23179,8 +23167,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<br> <br>
<ul> <ul>
<a id="SolarForecast-get-solApiData"></a> <a id="SolarForecast-get-radiationApiData"></a>
<li><b>solApiData </b> <br><br> <li><b>radiationApiData </b> <br><br>
Listet die im Kontext des API-Abrufs gespeicherten Strahlungsdaten auf. Listet die im Kontext des API-Abrufs gespeicherten Strahlungsdaten auf.
Die von der API gelieferten Vorhersagedaten bzgl. der Globalstrahlung Rad1h und des auf einen String bezogenen Die von der API gelieferten Vorhersagedaten bzgl. der Globalstrahlung Rad1h und des auf einen String bezogenen
@ -24517,9 +24505,10 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<li><b>setupRadiationAPI </b> <br><br> <li><b>setupRadiationAPI </b> <br><br>
Legt die Quelle zur Lieferung der solaren Strahlungsdaten fest. Es kann ein Device vom Typ DWD_OpenData oder Legt die Quelle zur Lieferung der solaren Strahlungsdaten fest. Es kann ein Device vom Typ DWD_OpenData oder
eine implementierte API eines Dienstes ausgewählt werden. <br> eine implementierte API eines Dienstes ausgewählt werden. <br><br>
<b>Hinweis:</b> Ist eine OpenMeteo API im Attribut 'setupWeatherDev1' gesetzt, kann kein anderer Strahlungsdatendienst als
diese OpenMeteo API ausgewählt werden. <br><br> <b>Hinweis:</b> Ist im Attribut 'setupWeatherDev1' ebenfalls eine OpenMeteo API gesetzt, werden die Einstellungen
beider Attribute harmonisiert wobei die Einstellung von 'setupRadiationAPI' führend ist. <br><br>
<b>OpenMeteoDWD-API</b> <br> <b>OpenMeteoDWD-API</b> <br>
@ -24651,28 +24640,29 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
</li> </li>
<br> <br>
<a id="SolarForecast-attr-setupWeatherDev" data-pattern="setupWeatherDev.*"></a> <a id="SolarForecast-attr-setupWeatherDev" data-pattern="setupWeatherDev.*"></a>
<li><b>setupWeatherDevX </b> <br><br> <li><b>setupWeatherDevX </b> <br><br>
Gibt das Gerät oder die API zur Lieferung der erforderlichen Wetterdaten (Wolkendecke, Niederschlag usw.) an.<br> Gibt das Gerät oder die API zur Lieferung der erforderlichen Wetterdaten (Wolkendecke, Niederschlag usw.) an.<br>
Das Attribut 'setupWeatherDev1' definiert den führenden Wetterdienst und ist zwingend erforderlich.<br> Das Attribut 'setupWeatherDev1' definiert den führenden Wetterdienst und ist zwingend erforderlich. <br><br>
Ist eine Open-Meteo API im Attribut 'setupWeatherDev1' ausgewählt, wird dieser Open-Meteo Dienst automatisch auch als Quelle
der Strahlungsdaten (Attribut setupRadiationAPI) eingestellt. <br><br> <b>Hinweis:</b> Ist im Attribut 'setupRadiationAPI' ebenfalls eine OpenMeteo API gesetzt, werden die Einstellungen
beider Attribute harmonisiert wobei die Einstellung von 'setupRadiationAPI' führend ist. <br><br>
<b>OpenMeteoDWD-API</b> <br> <b>OpenMeteoDWD-API</b> <br>
Open-Meteo ist eine Open-Source-Wetter-API und bietet kostenlosen Zugang für nicht-kommerzielle Zwecke. Open-Meteo ist eine Open-Source-Wetter-API und bietet kostenlosen Zugang für nicht-kommerzielle Zwecke.
Es ist kein API-Schlüssel erforderlich. Es ist kein API-Schlüssel erforderlich.
Open-Meteo nutzt eine leistungsstarke Kombination aus globalen (11 km) und mesoskaligen (1 km) Wettermodellen Open-Meteo nutzt eine leistungsstarke Kombination aus globalen (11 km) und mesoskaligen (1 km) Wettermodellen
von angesehenen nationalen Wetterdiensten. von angesehenen nationalen Wetterdiensten.
Diese API bietet Zugang zu den renommierten ICON-Wettermodellen des Deutschen Wetterdienstes (DWD), die Diese API bietet Zugang zu den renommierten ICON-Wettermodellen des Deutschen Wetterdienstes (DWD), die
15-minütige Daten für kurzfristige Vorhersagen in Mitteleuropa und globale Vorhersagen mit einer Auflösung 15-minütige Daten für kurzfristige Vorhersagen in Mitteleuropa und globale Vorhersagen mit einer Auflösung
von 11 km liefern. Das ICON-Modell ist eine bevorzugte Wahl für allgemeine Wettervorhersage-APIs, wenn keine von 11 km liefern. Das ICON-Modell ist eine bevorzugte Wahl für allgemeine Wettervorhersage-APIs, wenn keine
anderen hochauflösenden Wettermodelle verfügbar sind. Es werden die Modelle DWD Icon D2, DWD Icon EU anderen hochauflösenden Wettermodelle verfügbar sind. Es werden die Modelle DWD Icon D2, DWD Icon EU
und DWD Icon Global zu einer nahtlosen Vorhersage zusammengeführt. und DWD Icon Global zu einer nahtlosen Vorhersage zusammengeführt.
Auf der Webseite des Dienstes ist die umfangreiche und übersichtliche Auf der Webseite des Dienstes ist die umfangreiche und übersichtliche
<a href='https://open-meteo.com/en/docs/dwd-api' target='_blank'>API Dokumentation</a> verfügbar. <a href='https://open-meteo.com/en/docs/dwd-api' target='_blank'>API Dokumentation</a> verfügbar.
<br><br> <br><br>
<b>OpenMeteoDWDEnsemble-API</b> <br> <b>OpenMeteoDWDEnsemble-API</b> <br>