mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-16 10:46:03 +00:00
76_SolarForecast: contrib 1.17.0
git-svn-id: https://svn.fhem.de/fhem/trunk@28702 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
9887cfcdab
commit
50971a84c8
@ -158,7 +158,7 @@ BEGIN {
|
||||
|
||||
# Versions History intern
|
||||
my %vNotesIntern = (
|
||||
"1.17.0" => "20.03.2024 new DWD ICON API, change defmaxvar from 0.5 to 0.8 ",
|
||||
"1.17.0" => "24.03.2024 new DWD ICON API, change defmaxvar from 0.5 to 0.8, attr ctrlWeatherDev1 can select OpenMeteoDWD-API ",
|
||||
"1.16.8" => "16.03.2024 plantConfigCheck: adjust pvCorrectionFactor_Auto check, settings of forecastRefresh ".
|
||||
"rename reading nextSolCastCall to nextRadiationAPICall ".
|
||||
"currentMeterDev: new optional keys conprice, feedprice ".
|
||||
@ -1332,12 +1332,13 @@ sub Set {
|
||||
);
|
||||
my $resets = join ",",@re;
|
||||
|
||||
my @fcdevs = devspec2array ("TYPE=DWD_OpenData");
|
||||
|
||||
push @fcdevs, 'SolCast-API';
|
||||
push @fcdevs, 'ForecastSolar-API';
|
||||
push @fcdevs, 'VictronKI-API';
|
||||
push @fcdevs, 'OpenMeteoDWD-API';
|
||||
my @fcdevs = qw( OpenMeteoDWD-API
|
||||
SolCast-API
|
||||
ForecastSolar-API
|
||||
VictronKI-API
|
||||
);
|
||||
|
||||
push @fcdevs, devspec2array ("TYPE=DWD_OpenData");
|
||||
|
||||
my $rdd = join ",", @fcdevs;
|
||||
|
||||
@ -1545,6 +1546,11 @@ sub _setcurrentRadiationAPI { ## no critic "not used"
|
||||
if ($prop !~ /-API$/x && (!$defs{$prop} || $defs{$prop}{TYPE} ne "DWD_OpenData")) {
|
||||
return qq{The device "$prop" doesn't exist or has no TYPE "DWD_OpenData"};
|
||||
}
|
||||
|
||||
if ($prop ne 'OpenMeteoDWD-API' && AttrVal ($name, 'ctrlWeatherDev1', '') eq 'OpenMeteoDWD-API') {
|
||||
return "The attribute 'ctrlWeatherDev1' is set to 'OpenMeteoDWD-API'. \n".
|
||||
"Change that attribute to another weather device first if you want use an other API.";
|
||||
}
|
||||
|
||||
if ($prop =~ /(SolCast|OpenMeteoDWD)-API/xs) {
|
||||
return "The library FHEM::Utility::CTZ is missing. Please update FHEM completely." if($ctzAbsent);
|
||||
@ -2497,7 +2503,7 @@ sub _setclientAction { ## no critic "not used"
|
||||
my $arg = $paref->{arg};
|
||||
my $argsref = $paref->{argsref};
|
||||
|
||||
if(!$arg) {
|
||||
if (!$arg) {
|
||||
return qq{The command "$opt" needs an argument !};
|
||||
}
|
||||
|
||||
@ -2511,25 +2517,25 @@ sub _setclientAction { ## no critic "not used"
|
||||
|
||||
Log3 ($name, 4, qq{$name - Client Action received / execute: "$action $cname $tail"});
|
||||
|
||||
if($action eq 'set') {
|
||||
if ($action eq 'set') {
|
||||
CommandSet (undef, "$cname $tail");
|
||||
my $async = ConsumerVal ($hash, $c, 'asynchron', 0);
|
||||
centralTask ($hash, $evt) if(!$async); # nur wenn Consumer synchron arbeitet direkte Statusabfrage, sonst via Notify
|
||||
return;
|
||||
}
|
||||
|
||||
if($action eq 'get') {
|
||||
if ($action eq 'get') {
|
||||
if($tail eq 'data') {
|
||||
centralTask ($hash, $evt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($action eq 'setreading') {
|
||||
if ($action eq 'setreading') {
|
||||
CommandSetReading (undef, "$cname $tail");
|
||||
}
|
||||
|
||||
if($action eq 'consumerImmediatePlanning') {
|
||||
if ($action eq 'consumerImmediatePlanning') {
|
||||
CommandSet (undef, "$name $action $cname $evt");
|
||||
return;
|
||||
}
|
||||
@ -3545,7 +3551,7 @@ sub __getDWDSolarData {
|
||||
for my $num (0..47) {
|
||||
my $dateTime = strftime "%Y-%m-%d %H:%M:00", localtime($sts + (3600 * $num)); # laufendes Datum ' ' Zeit
|
||||
my $runh = int strftime "%H", localtime($sts + (3600 * $num) + 3600); # Stunde in 24h format (00-23), Rad1h = Absolute Globalstrahlung letzte 1 Stunde
|
||||
my ($fd,$fh) = _calcDayHourMove (0, $num);
|
||||
my ($fd,$fh) = calcDayHourMove (0, $num);
|
||||
|
||||
next if($fh == 24);
|
||||
|
||||
@ -4008,12 +4014,12 @@ return;
|
||||
# Abruf Open-Meteo DWD ICON API data
|
||||
################################################################################################
|
||||
sub __getopenMeteoDWDdata {
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $force = $paref->{force} // 0;
|
||||
my $t = $paref->{t};
|
||||
my $lang = $paref->{lang};
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $force = $paref->{force} // 0;
|
||||
my $t = $paref->{t};
|
||||
my $lang = $paref->{lang};
|
||||
|
||||
if (!$force) { # regulärer API Abruf
|
||||
my $lrt = SolCastAPIVal ($hash, '?All', '?All', 'lastretrieval_timestamp', 0);
|
||||
@ -4237,9 +4243,11 @@ sub __openMeteoDWD_ApiResponse {
|
||||
Log3 ($name, 1, "$name DEBUG> Open-Meteo DWD ICON API Call - status: success");
|
||||
}
|
||||
|
||||
my $peak = StringVal ($hash, $string, 'peak', 0); # String Peak (kWp)
|
||||
$peak *= 1000; # kWp in Wp
|
||||
my $k = 0;
|
||||
my $date = strftime "%Y-%m-%d", localtime(time);
|
||||
my $refts = timestringToTimestamp ($date.' 00:00:00'); # Referenztimestring
|
||||
my $peak = StringVal ($hash, $string, 'peak', 0); # String Peak (kWp)
|
||||
$peak *= 1000; # kWp in Wp
|
||||
my $k = 0;
|
||||
|
||||
while ($jdata->{hourly}{time}[$k]) {
|
||||
($err, my $otmstr) = timestringUTCtoLocal ($name, $jdata->{hourly}{time}[$k], '%Y-%m-%dT%H:%M');
|
||||
@ -4253,6 +4261,9 @@ sub __openMeteoDWD_ApiResponse {
|
||||
|
||||
my $ots = timestringToTimestamp ($otmstr);
|
||||
my $pvtmstr = (timestampToTimestring ($ots-3600))[0]; # Strahlung wird als Durchschnitt der !vorangegangenen! Stunde geliefert!
|
||||
|
||||
next if(timestringToTimestamp($pvtmstr) < $refts); # Daten älter als akt. Tag 00:00:00 verwerfen
|
||||
|
||||
my $rad1wh = $jdata->{hourly}{global_tilted_irradiance_instant}[$k]; # Wh/m2
|
||||
my $pv = sprintf "%.1f", ($rad1wh / 1000 * $peak * $prdef); # Rad wird in kWh/m2 erwartet
|
||||
my $don = $jdata->{hourly}{is_day}[$k];
|
||||
@ -4270,19 +4281,25 @@ sub __openMeteoDWD_ApiResponse {
|
||||
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 - Temp: $temp");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$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};
|
||||
delete $data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{Rad1h};
|
||||
}
|
||||
|
||||
$data{$type}{$name}{solcastapi}{$string}{$pvtmstr}{pv_estimate50} = $pv; # Startstunde verschieben
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{Rad1h} += $rad1wh; # Startstunde verschieben, Rad Werte aller Strings addieren
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$otmstr}{don} = $don;
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$otmstr}{ttt} = $temp;
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{rr1c} = $rain; # Startstunde verschieben
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{ww} = $wid; # Startstunde verschieben
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$pvtmstr}{neff} = $wcc; # Startstunde verschieben
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{Rad1h} += $rad1wh; # Startstunde verschieben, Rad Werte aller Strings addieren
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{rr1c} = $rain; # Startstunde verschieben
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ww} = $wid; # Startstunde verschieben
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{neff} = $wcc; # Startstunde verschieben
|
||||
|
||||
$fwtg = formatWeatherTimestrg ($otmstr);
|
||||
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{don} = $don;
|
||||
$data{$type}{$name}{solcastapi}{'?All'}{$fwtg}{ttt} = $temp;
|
||||
}
|
||||
|
||||
$k = 0;
|
||||
@ -5414,10 +5431,15 @@ sub _attrWeatherDev { ## no critic "not used"
|
||||
return if(!$init_done);
|
||||
|
||||
if ($paref->{cmd} eq 'set' ) {
|
||||
if (!$defs{$aVal} || $defs{$aVal}{TYPE} ne "DWD_OpenData") {
|
||||
if ($aVal ne 'OpenMeteoDWD-API' && (!$defs{$aVal} || $defs{$aVal}{TYPE} ne "DWD_OpenData")) {
|
||||
return qq{The device "$aVal" doesn't exist or has no TYPE "DWD_OpenData"};
|
||||
}
|
||||
|
||||
|
||||
if ($aVal eq 'OpenMeteoDWD-API') {
|
||||
CommandSet (undef,"$name currentRadiationAPI $aVal"); # automatisch currentRadiationAPI setzen
|
||||
return;
|
||||
}
|
||||
|
||||
my $err = checkdwdattr ($name, $aVal, \@dweattrmust);
|
||||
return $err if($err);
|
||||
}
|
||||
@ -5495,7 +5517,7 @@ sub Notify {
|
||||
for my $event (@{$events}) {
|
||||
$event = "" if(!defined($event));
|
||||
|
||||
my @parts = split(/: /,$event, 2);
|
||||
my @parts = split (/: /,$event, 2);
|
||||
$reading = shift @parts;
|
||||
|
||||
if (@parts == 2) {
|
||||
@ -5976,7 +5998,6 @@ return;
|
||||
################################################################
|
||||
sub _addDynAttr {
|
||||
my $hash = shift;
|
||||
|
||||
my $type = $hash->{TYPE};
|
||||
|
||||
## Attr ctrlWeatherDevX zur Laufzeit hinzufügen
|
||||
@ -5989,10 +6010,10 @@ sub _addDynAttr {
|
||||
my $atd = 'ctrlWeatherDev';
|
||||
@deva = grep {!/$atd/} @deva;
|
||||
|
||||
push @deva, ($adwds ? "ctrlWeatherDev1:$adwds " : "ctrlWeatherDev1:noArg");
|
||||
#push @deva, ($adwds ? "ctrlWeatherDev1:$adwds " : "ctrlWeatherDev1:noArg");
|
||||
|
||||
for my $step (1..$weatherDevMax) {
|
||||
push @deva, ($adwds ? "ctrlWeatherDev".$step.":$adwds " : "ctrlWeatherDev1:noArg");
|
||||
push @deva, ($adwds ? "ctrlWeatherDev".$step.":OpenMeteoDWD-API,$adwds " : "ctrlWeatherDev1:OpenMeteoDWD-API");
|
||||
}
|
||||
|
||||
$hash->{".AttrList"} = join " ", @deva;
|
||||
@ -6035,7 +6056,12 @@ sub centralTask {
|
||||
readingsSingleUpdate ($hash, 'nextRadiationAPICall', $nscc, 0);
|
||||
deleteReadingspec ($hash, 'nextSolCastCall');
|
||||
}
|
||||
|
||||
|
||||
#for my $idx (sort keys %{$data{$type}{$name}{solcastapi}{'?All'}}) { # 23.03.2024
|
||||
# my $ds = timestringToTimestamp ($idx);
|
||||
# delete $data{$type}{$name}{solcastapi}{'?All'}{$idx} if($ds); # valider Zeitstring
|
||||
#}
|
||||
|
||||
#######################################################################################################################
|
||||
|
||||
return if(!$init_done);
|
||||
@ -6331,15 +6357,16 @@ return ($interval, $disabled, $inactive);
|
||||
# Sonderaufgaben !
|
||||
################################################################
|
||||
sub _specialActivities {
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $type = $paref->{type};
|
||||
my $date = $paref->{date}; # aktuelles Datum
|
||||
my $chour = $paref->{chour};
|
||||
my $t = $paref->{t}; # aktuelle Zeit
|
||||
my $day = $paref->{day};
|
||||
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $type = $paref->{type};
|
||||
my $date = $paref->{date}; # aktuelles Datum
|
||||
my $chour = $paref->{chour};
|
||||
my $minute = $paref->{minute};
|
||||
my $t = $paref->{t}; # aktuelle Zeit
|
||||
my $day = $paref->{day};
|
||||
|
||||
my ($ts,$ts1,$pvfc,$pvrl,$gcon);
|
||||
|
||||
$ts1 = $date." ".sprintf("%02d",$chour).":00:00";
|
||||
@ -6373,9 +6400,13 @@ sub _specialActivities {
|
||||
|
||||
## bestimmte einmalige Aktionen
|
||||
##################################
|
||||
my $tlim = "00";
|
||||
if ($chour =~ /^($tlim)$/x) {
|
||||
$chour = int $chour;
|
||||
$minute = int $minute;
|
||||
|
||||
if ($chour == 0 && $minute >= 1) {
|
||||
if (!exists $hash->{HELPER}{H00DONE}) {
|
||||
$hash->{HELPER}{H00DONE} = 1;
|
||||
|
||||
$date = strftime "%Y-%m-%d", localtime($t-7200); # Vortag (2 h Differenz reichen aus)
|
||||
$ts = $date." 23:59:59";
|
||||
|
||||
@ -6398,21 +6429,11 @@ sub _specialActivities {
|
||||
deleteReadingspec ($hash, "Today_PVdeviation");
|
||||
deleteReadingspec ($hash, "Today_PVreal");
|
||||
|
||||
for my $wdr (@widgetreadings) {
|
||||
for my $wdr (@widgetreadings) { # Array der Hilfsreadings (Attributspeicher) löschen
|
||||
deleteReadingspec ($hash, $wdr);
|
||||
}
|
||||
|
||||
for my $n (1..24) {
|
||||
$n = sprintf "%02d", $n;
|
||||
|
||||
deleteReadingspec ($hash, ".pvCorrectionFactor_${n}_cloudcover"); # verstecktes Reading löschen
|
||||
deleteReadingspec ($hash, ".pvCorrectionFactor_${n}_apipercentil"); # verstecktes Reading löschen
|
||||
deleteReadingspec ($hash, ".signaldone_${n}"); # verstecktes Reading löschen
|
||||
|
||||
if (ReadingsVal ($name, "pvCorrectionFactor_Auto", "off") =~ /on/xs) {
|
||||
deleteReadingspec ($hash, "pvCorrectionFactor_${n}.*");
|
||||
}
|
||||
}
|
||||
__deleteHiddenReadings ($paref); # verstecktes Steuerungsreading löschen
|
||||
|
||||
delete $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{todayDoneAPIrequests};
|
||||
delete $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{todayDoneAPIcalls};
|
||||
@ -6461,8 +6482,6 @@ sub _specialActivities {
|
||||
delete $paref->{taa};
|
||||
|
||||
periodicWriteCachefiles ($hash, 'bckp'); # Backup Files erstellen und alte Versionen löschen
|
||||
|
||||
$hash->{HELPER}{H00DONE} = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -6472,6 +6491,29 @@ sub _specialActivities {
|
||||
return;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# versteckte Steuerungsreadings löschen
|
||||
#############################################################################
|
||||
sub __deleteHiddenReadings {
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
|
||||
for my $n (1..24) {
|
||||
$n = sprintf "%02d", $n;
|
||||
|
||||
deleteReadingspec ($hash, ".pvCorrectionFactor_${n}_cloudcover");
|
||||
deleteReadingspec ($hash, ".pvCorrectionFactor_${n}_apipercentil");
|
||||
deleteReadingspec ($hash, ".signaldone_${n}");
|
||||
|
||||
if (ReadingsVal ($name, "pvCorrectionFactor_Auto", "off") =~ /on/xs) {
|
||||
deleteReadingspec ($hash, "pvCorrectionFactor_${n}.*");
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# zusätzliche Events erzeugen - PV Vorhersage bis Ende des kommenden Tages
|
||||
#############################################################################
|
||||
@ -6515,6 +6557,10 @@ sub __delObsoleteAPIData {
|
||||
delete $data{$type}{$name}{solcastapi}{$idx}{$scd} if ($ds && $ds < $refts);
|
||||
}
|
||||
}
|
||||
|
||||
for my $idx (keys %{$data{$type}{$name}{solcastapi}{'?All'}}) { # Wetterindexe löschen
|
||||
delete $data{$type}{$name}{solcastapi}{'?All'}{$idx} if($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs);
|
||||
}
|
||||
|
||||
writeCacheToFile ($hash, "solcastapi", $scpicache.$name); # Cache File SolCast API Werte schreiben
|
||||
|
||||
@ -6534,7 +6580,7 @@ return;
|
||||
|
||||
################################################################
|
||||
# Wetter Werte aus dem angebenen Wetterdevice extrahieren
|
||||
################################################################
|
||||
################################################################
|
||||
sub _transferWeatherValues {
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
@ -6542,20 +6588,32 @@ sub _transferWeatherValues {
|
||||
my $t = $paref->{t}; # Epoche Zeit
|
||||
my $chour = $paref->{chour};
|
||||
|
||||
my $fcname = AttrVal ($name, 'ctrlWeatherDev1', ''); # Standard Weather Forecast Device
|
||||
return if(!$fcname || !$defs{$fcname});
|
||||
my ($valid, $fcname, $apiu) = isWeatherDevValid ($hash, 'ctrlWeatherDev1'); # Standard Weather Forecast Device
|
||||
return if(!$valid);
|
||||
|
||||
my $type = $paref->{type};
|
||||
|
||||
delete $data{$type}{$name}{weatherdata}; # Wetterdaten Hash löschen
|
||||
|
||||
$paref->{apiu} = $apiu; # API wird verwendet
|
||||
$paref->{fcname} = $fcname;
|
||||
__sunRS ($paref); # Sonnenauf- und untergang
|
||||
delete $paref->{fcname};
|
||||
delete $paref->{apiu};
|
||||
|
||||
my ($fctime, $fctimets); # Alter der DWD Daten
|
||||
|
||||
if (!$apiu) {
|
||||
$fctime = ReadingsVal ($fcname, 'fc_time', '-');
|
||||
$fctimets = timestringToTimestamp ($fctime);
|
||||
}
|
||||
else {
|
||||
$fctime = SolCastAPIVal ($hash, '?All', '?All', 'lastretrieval_time', '-');
|
||||
$fctimets = SolCastAPIVal ($hash, '?All', '?All', 'lastretrieval_timestamp', '-');
|
||||
}
|
||||
|
||||
my $fctime = ReadingsVal ($fcname, 'fc_time', '-'); # Alter der DWD Daten
|
||||
$data{$type}{$name}{current}{dwdWfchAge} = $fctime;
|
||||
$data{$type}{$name}{current}{dwdWfchAgeTS} = timestringToTimestamp ($fctime);
|
||||
$data{$type}{$name}{current}{dwdWfchAgeTS} = $fctimets;
|
||||
|
||||
for my $step (1..$weatherDevMax) {
|
||||
$paref->{step} = $step;
|
||||
@ -6566,7 +6624,7 @@ sub _transferWeatherValues {
|
||||
__mergeDataWeather ($paref); # Wetterdaten zusammenfügen
|
||||
|
||||
for my $num (0..46) {
|
||||
my ($fd, $fh) = _calcDayHourMove ($chour, $num);
|
||||
my ($fd, $fh) = calcDayHourMove ($chour, $num);
|
||||
last if($fd > 1);
|
||||
|
||||
my $wid = $data{$type}{$name}{weatherdata}{"fc${fd}_${fh}"}{merge}{ww}; # signifikantes Wetter = Wetter ID
|
||||
@ -6641,8 +6699,15 @@ sub __readDataWeather {
|
||||
my $type = $paref->{type};
|
||||
my $step = $paref->{step};
|
||||
|
||||
my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$step, ''); # Weather Forecast Device
|
||||
return if(!$fcname || !$defs{$fcname});
|
||||
my ($valid, $fcname, $apiu) = isWeatherDevValid ($hash, 'ctrlWeatherDev'.$step); # Weather Forecast Device
|
||||
return if(!$valid);
|
||||
|
||||
if ($apiu) { # eine API wird verwendet
|
||||
$paref->{fcname} = $fcname;
|
||||
___readDataWeatherAPI ($paref);
|
||||
delete $paref->{fcname};
|
||||
return;
|
||||
}
|
||||
|
||||
my $err = checkdwdattr ($name, $fcname, \@dweattrmust);
|
||||
$paref->{state} = $err if($err);
|
||||
@ -6650,7 +6715,7 @@ sub __readDataWeather {
|
||||
debugLog ($paref, 'collectData', "collect Weather data step $step - device: $fcname =>");
|
||||
|
||||
for my $n (0..46) {
|
||||
my ($fd, $fh) = _calcDayHourMove ($chour, $n);
|
||||
my ($fd, $fh) = calcDayHourMove ($chour, $n);
|
||||
last if($fd > 1);
|
||||
|
||||
my $wid = ReadingsNum ($fcname, "fc${fd}_${fh}_ww", undef); # Signifikantes Wetter zum Vorhersagezeitpunkt
|
||||
@ -6686,13 +6751,54 @@ sub __readDataWeather {
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
################################################################
|
||||
# lese Wetterdaten aus API Speicher (solcastapi)
|
||||
################################################################
|
||||
sub ___readDataWeatherAPI {
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $type = $paref->{type};
|
||||
my $step = $paref->{step};
|
||||
my $fcname = $paref->{fcname};
|
||||
|
||||
debugLog ($paref, 'collectData', "collect Weather data step $step - API: $fcname =>");
|
||||
|
||||
for my $idx (sort keys %{$data{$type}{$name}{solcastapi}{'?All'}}) {
|
||||
if ($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs) { # valider Weather API Index
|
||||
my $rr1c = $data{$type}{$name}{solcastapi}{'?All'}{$idx}{rr1c};
|
||||
my $wid = $data{$type}{$name}{solcastapi}{'?All'}{$idx}{ww};
|
||||
my $neff = $data{$type}{$name}{solcastapi}{'?All'}{$idx}{neff};
|
||||
my $don = $data{$type}{$name}{solcastapi}{'?All'}{$idx}{don};
|
||||
my $ttt = $data{$type}{$name}{solcastapi}{'?All'}{$idx}{ttt};
|
||||
|
||||
$data{$type}{$name}{weatherdata}{$idx}{$step}{ww} = $wid if(defined $wid);
|
||||
$data{$type}{$name}{weatherdata}{$idx}{$step}{neff} = $neff if(defined $neff);
|
||||
$data{$type}{$name}{weatherdata}{$idx}{$step}{rr1c} = $rr1c if(defined $rr1c);
|
||||
$data{$type}{$name}{weatherdata}{$idx}{$step}{ttt} = $ttt if(defined $ttt);
|
||||
$data{$type}{$name}{weatherdata}{$idx}{$step}{don} = $don if(defined $don);
|
||||
|
||||
debugLog ($paref, 'collectData', "Weather $step: $idx".
|
||||
", don: ". (defined $don ? $don : '<undef>').
|
||||
", ww: ". (defined $wid ? $wid : '<undef>').
|
||||
", RR1c: ".(defined $rr1c ? $rr1c : '<undef>').
|
||||
", TTT: ". (defined $ttt ? $ttt : '<undef>').
|
||||
", Neff: ".(defined $neff ? $neff : '<undef>')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Wetterdaten mergen
|
||||
################################################################
|
||||
sub __mergeDataWeather {
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $type = $paref->{type};
|
||||
|
||||
@ -6701,8 +6807,8 @@ sub __mergeDataWeather {
|
||||
my $ds = 0;
|
||||
|
||||
for my $wd (1..$weatherDevMax) {
|
||||
my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$wd, ''); # Weather Forecast Device
|
||||
$ds++ if($fcname && $defs{$fcname});
|
||||
my ($valid, $fcname, $apiu) = isWeatherDevValid ($hash, 'ctrlWeatherDev'.$wd); # Weather Forecast Device
|
||||
$ds++ if($valid);
|
||||
}
|
||||
|
||||
my ($q, $m) = (0,0);
|
||||
@ -6828,7 +6934,7 @@ sub _transferAPIRadiationValues {
|
||||
my $lang = $paref->{lang};
|
||||
|
||||
for my $num (0..47) {
|
||||
my ($fd,$fh) = _calcDayHourMove ($chour, $num);
|
||||
my ($fd,$fh) = calcDayHourMove ($chour, $num);
|
||||
|
||||
if ($fd > 1) { # überhängende Werte löschen
|
||||
delete $data{$type}{$name}{nexthours}{"NextHour".sprintf "%02d", $num};
|
||||
@ -6984,7 +7090,7 @@ sub __calcSunPosition {
|
||||
my $num = $paref->{num};
|
||||
my $nhtstr = $paref->{nhtstr};
|
||||
|
||||
my ($fd, $fh) = _calcDayHourMove ($chour, $num);
|
||||
my ($fd, $fh) = calcDayHourMove ($chour, $num);
|
||||
last if($fd > 1);
|
||||
|
||||
my $tstr = (timestampToTimestring ($t + ($num * 3600)))[3];
|
||||
@ -10143,10 +10249,10 @@ return ($oldfac, $factor, $dnum);
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Berechnen Forecast Tag / Stunden Verschieber
|
||||
# Berechnen Tag / Stunden Verschieber
|
||||
# aus aktueller Stunde + lfd. Nummer
|
||||
################################################################
|
||||
sub _calcDayHourMove {
|
||||
sub calcDayHourMove {
|
||||
my $chour = shift;
|
||||
my $num = shift;
|
||||
|
||||
@ -10154,7 +10260,26 @@ sub _calcDayHourMove {
|
||||
my $fd = int ($fh / 24) ;
|
||||
$fh = $fh - ($fd * 24);
|
||||
|
||||
return ($fd,$fh);
|
||||
return ($fd, $fh);
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Berechnen Tag / Stunden Verschieber ab aktuellen Tag
|
||||
# Input: YYYY-MM-DD HH:MM:SS
|
||||
# Output: $fd - 0 (Heute), 1 (Morgen), 2 (Übermorgen), ....
|
||||
# $fh - Stunde von $fd ohne führende Null
|
||||
# Return: fc${fd}_${fh}
|
||||
################################################################
|
||||
sub formatWeatherTimestrg {
|
||||
my $date = shift // return;
|
||||
|
||||
my $cdate = strftime "%Y-%m-%d", localtime(time);
|
||||
my $refts = timestringToTimestamp ($cdate.' 00:00:00'); # Referenztimestring
|
||||
my $datts = timestringToTimestamp ($date);
|
||||
my $fd = int (($datts - $refts) / 86400);
|
||||
my $fh = int ((split /[ :]/, $date)[1]);
|
||||
|
||||
return "fc${fd}_${fh}";
|
||||
}
|
||||
|
||||
################################################################
|
||||
@ -10857,11 +10982,6 @@ sub _checkSetupNotComplete {
|
||||
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
|
||||
##########################################################################################
|
||||
## currentWeatherDev in Attr umsetzen
|
||||
my $cwd = ReadingsVal ($name, 'currentWeatherDev', ''); # 30.01.2024
|
||||
if ($cwd) {
|
||||
CommandAttr (undef, "$name ctrlWeatherDev1 $cwd");
|
||||
}
|
||||
|
||||
my $mdr = ReadingsVal ($name, 'moduleDirection', undef); # 09.02.2024
|
||||
if ($mdr) {
|
||||
readingsSingleUpdate ($hash, 'moduleAzimuth', $mdr, 0);
|
||||
@ -15283,15 +15403,15 @@ sub createAssociatedWith {
|
||||
($ara,$h) = parseParams ($radev);
|
||||
$radev = $ara->[0] // "";
|
||||
|
||||
my $indev = ReadingsVal($name, 'currentInverterDev', ''); # Inverter Device
|
||||
my $indev = ReadingsVal($name, 'currentInverterDev', ''); # Inverter Device
|
||||
($ain,$h) = parseParams ($indev);
|
||||
$indev = $ain->[0] // "";
|
||||
|
||||
my $medev = ReadingsVal($name, 'currentMeterDev', ''); # Meter Device
|
||||
my $medev = ReadingsVal($name, 'currentMeterDev', ''); # Meter Device
|
||||
($ame,$h) = parseParams ($medev);
|
||||
$medev = $ame->[0] // "";
|
||||
|
||||
my $badev = ReadingsVal($name, 'currentBatteryDev', ''); # Battery Device
|
||||
my $badev = ReadingsVal($name, 'currentBatteryDev', ''); # Battery Device
|
||||
($aba,$h) = parseParams ($badev);
|
||||
$badev = $aba->[0] // "";
|
||||
|
||||
@ -15306,10 +15426,10 @@ sub createAssociatedWith {
|
||||
|
||||
@nd = @cd;
|
||||
|
||||
push @nd, $fcdev1 if($fcdev1);
|
||||
push @nd, $fcdev2 if($fcdev2);
|
||||
push @nd, $fcdev3 if($fcdev3);
|
||||
push @nd, $radev if($radev !~ /^($fcdev1|$fcdev2|$fcdev3)/xs && $radev !~ /SolCast-API/xs);
|
||||
push @nd, $fcdev1 if($fcdev1 && $fcdev1 !~ /-API/xs);
|
||||
push @nd, $fcdev2 if($fcdev2 && $fcdev2 !~ /-API/xs);
|
||||
push @nd, $fcdev3 if($fcdev3 && $fcdev3 !~ /-API/xs);
|
||||
push @nd, $radev if($radev && $radev !~ /-API/xs);
|
||||
push @nd, $indev;
|
||||
push @nd, $medev;
|
||||
push @nd, $badev;
|
||||
@ -15925,6 +16045,31 @@ sub isOpenMeteoUsed {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Prüft ob das in ctrlWeatherDevX
|
||||
# übergebene Weather Device valide ist
|
||||
# return - $valid -> ist die Angabe valide (1)
|
||||
# $apiu -> wird ein Device (0) oder API (1) verwendet
|
||||
#####################################################################
|
||||
sub isWeatherDevValid {
|
||||
my $hash = shift;
|
||||
my $wdev = shift;
|
||||
|
||||
my $valid = '';
|
||||
my $apiu = '';
|
||||
my $fcname = AttrVal ($hash->{NAME}, $wdev, ''); # Weather Forecast Device
|
||||
|
||||
if ($fcname) { $valid = 1 }
|
||||
if (!$defs{$fcname} || $defs{$fcname}{TYPE} ne "DWD_OpenData") { $valid = '' }
|
||||
|
||||
if (isOpenMeteoUsed($hash) && $fcname =~ /OpenMeteoDWD-API/xs) {
|
||||
$valid = 1;
|
||||
$apiu = 1;
|
||||
}
|
||||
|
||||
return ($valid, $fcname, $apiu);
|
||||
}
|
||||
|
||||
################################################################
|
||||
# welche PV Autokorrektur wird verwendet ?
|
||||
# Standard bei nur "on" -> on_simple
|
||||
@ -15989,8 +16134,9 @@ sub isWeatherAgeExceeded {
|
||||
my $name = $paref->{name};
|
||||
my $lang = $paref->{lang};
|
||||
|
||||
my $dt = strftime "%Y-%m-%d %H:%M:%S", localtime(time);
|
||||
my $currts = timestringToTimestamp ($dt);
|
||||
#my $dt = strftime "%Y-%m-%d %H:%M:%S", localtime(time);
|
||||
#my $currts = timestringToTimestamp ($dt);
|
||||
my $currts = int time;
|
||||
my $agets = $currts;
|
||||
|
||||
my $resh->{agedv} = '-';
|
||||
@ -15998,36 +16144,56 @@ sub isWeatherAgeExceeded {
|
||||
$resh->{exceed} = '';
|
||||
$resh->{fctime} = '-';
|
||||
|
||||
my ($newts, $th);
|
||||
|
||||
for my $step (1..$weatherDevMax) {
|
||||
my $fcname = AttrVal ($name, 'ctrlWeatherDev'.$step, '');
|
||||
my ($valid, $fcname, $apiu) = isWeatherDevValid ($hash, 'ctrlWeatherDev'.$step);
|
||||
next if(!$fcname && $step ne 1);
|
||||
|
||||
if (!$fcname || !$defs{$fcname}) {
|
||||
if (!$fcname) {
|
||||
return (qq{No DWD device is defined in attribute "ctrlWeatherDev$step"}, $resh);
|
||||
if (!$apiu) {
|
||||
if (!$fcname || !$valid) {
|
||||
if (!$fcname) {
|
||||
return (qq{No DWD device is defined in attribute "ctrlWeatherDev$step"}, $resh);
|
||||
}
|
||||
else {
|
||||
return (qq{The DWD device "$fcname" doesn't exist}, $resh);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return (qq{The DWD device "$fcname" doesn't exist}, $resh);
|
||||
|
||||
my $fct = ReadingsVal ($fcname, 'fc_time', '');
|
||||
return (qq{The reading 'fc_time' ($fcname) doesn't exist or is empty}, $resh) if(!$fct);
|
||||
|
||||
$newts = timestringToTimestamp ($fct);
|
||||
|
||||
if ($newts <= $agets) {
|
||||
$agets = $newts;
|
||||
$resh->{agedv} = $fcname;
|
||||
$resh->{apiu} = $apiu;
|
||||
}
|
||||
}
|
||||
|
||||
my $fct = ReadingsVal ($fcname, 'fc_time', '');
|
||||
return (qq{The reading 'fc_time' ($fcname) doesn't exist or is empty}, $resh) if(!$fct);
|
||||
|
||||
my $newts = timestringToTimestamp ($fct);
|
||||
|
||||
if ($newts <= $agets) {
|
||||
$agets = $newts;
|
||||
$resh->{agedv} = $fcname;
|
||||
else {
|
||||
$newts = SolCastAPIVal ($hash, '?All', '?All', 'lastretrieval_timestamp', $agets);
|
||||
|
||||
if ($newts <= $agets) {
|
||||
$agets = $newts;
|
||||
$resh->{agedv} = $fcname;
|
||||
$resh->{apiu} = $apiu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$resh->{mosmix} = AttrVal ($resh->{agedv}, 'forecastRefresh', 6) == 6 ? 'MOSMIX_L' : 'MOSMIX_S';
|
||||
my $th = $resh->{mosmix} eq 'MOSMIX_S' ? 7200 : 25200;
|
||||
if (!$resh->{apiu}) { # DWD Device ist Wetterdatenlieferant
|
||||
$resh->{mosmix} = AttrVal ($resh->{agedv}, 'forecastRefresh', 6) == 6 ? 'MOSMIX_L' : 'MOSMIX_S';
|
||||
$th = $resh->{mosmix} eq 'MOSMIX_S' ? 7200 : 25200;
|
||||
}
|
||||
else { # API ist Wetterdatenlieferant
|
||||
$resh->{mosmix} = 'ICON';
|
||||
$th = 5400;
|
||||
}
|
||||
|
||||
$resh->{exceed} = $currts - $agets > $th ? 1 : 0;
|
||||
$resh->{fctime} = (timestampToTimestring ($agets, $lang))[0];
|
||||
|
||||
|
||||
return ('', $resh);
|
||||
}
|
||||
|
||||
@ -17417,7 +17583,9 @@ to ensure that the system configuration is correct.
|
||||
<li><b>currentRadiationAPI </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
|
||||
an implemented API can be selected. <br><br>
|
||||
an implemented API can be selected. <br>
|
||||
<b>Note:</b> If OpenMeteoDWD-API is set in the 'ctrlWeatherDev1' attribute, no radiation data service other than
|
||||
OpenMeteoDWD-API can be selected. <br><br>
|
||||
|
||||
<b>OpenMeteoDWD-API</b> <br>
|
||||
|
||||
@ -18787,12 +18955,16 @@ to ensure that the system configuration is correct.
|
||||
<a id="SolarForecast-attr-ctrlWeatherDev" data-pattern="ctrlWeatherDev.*"></a>
|
||||
<li><b>ctrlWeatherDevX </b> <br><br>
|
||||
|
||||
Defines the device (type DWD_OpenData), which provides the required weather data (cloudiness, precipitation, etc.). <br>
|
||||
If no device of this type exists, the selection list is empty and a device must first be defined
|
||||
Specifies the device or API that provides the required weather data (cloud cover, precipitation, etc.).<br>
|
||||
The attribute 'ctrlWeatherDev1' specifies the leading weather service and is mandatory.<br>
|
||||
If 'OpenMeteoDWD-API' is selected in the 'ctrlWeatherDev1' attribute, the Open-Meteo service is automatically set as the
|
||||
source of the radiation data (setter currentRadiationAPI).<br>
|
||||
If an FHEM device is to be used to supply the weather data, it must be of type 'DWD_OpenData'.<br>
|
||||
If no device of this type exists, at least one DWD_OpenData device must first be defined.
|
||||
(see <a href="http://fhem.de/commandref.html#DWD_OpenData">DWD_OpenData Commandref</a>). <br>
|
||||
If more than one ctrlWeatherDevX is specified, the average of all weather stations is determined and used
|
||||
If more than one ctrlWeatherDevX is specified, the average of all weather stations is determined
|
||||
if the respective value was supplied and is numerical. <br>
|
||||
Otherwise, the data from 'ctrlWeatherDev1' is always used as the leading weather device. <br>
|
||||
Otherwise, the data from 'ctrlWeatherDev1' is always used as the leading weather device.<br>
|
||||
At least these attributes must be set in the selected DWD_OpenData Device: <br><br>
|
||||
|
||||
<ul>
|
||||
@ -19566,7 +19738,9 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
|
||||
<li><b>currentRadiationAPI </b> <br><br>
|
||||
|
||||
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><br>
|
||||
eine implementierte API eines Dienstes ausgewählt werden. <br>
|
||||
<b>Hinweis:</b> Ist OpenMeteoDWD-API im Attribut 'ctrlWeatherDev1' gesetzt, kann kein anderer Strahlungsdatendienst als
|
||||
OpenMeteoDWD-API ausgewählt werden. <br><br>
|
||||
|
||||
<b>OpenMeteoDWD-API</b> <br>
|
||||
|
||||
@ -20947,14 +21121,17 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
|
||||
<a id="SolarForecast-attr-ctrlWeatherDev" data-pattern="ctrlWeatherDev.*"></a>
|
||||
<li><b>ctrlWeatherDevX </b> <br><br>
|
||||
|
||||
Legt das Device (Typ DWD_OpenData) fest, welches die benötigten Wetterdaten (Bewölkung, Niederschlag, usw.)
|
||||
liefert. <br>
|
||||
Ist noch kein Device dieses Typs vorhanden, ist die Auswahlliste leer und es muß zunächst mindestens ein Device
|
||||
Gibt das Gerät oder die API an, das/die die erforderlichen Wetterdaten (Wolkendecke, Niederschlag usw.) liefert.<br>
|
||||
Das Attribut 'ctrlWeatherDev1' gibt den führenden Wetterdienst an und ist zwingend erforderlich.<br>
|
||||
Wird 'OpenMeteoDWD-API' im Attribut 'ctrlWeatherDev1' ausgewählt, wird der Dienst Open-Meteo automatisch auch als Quelle
|
||||
der Strahlungsdaten (Setter currentRadiationAPI) eingestellt. <br>
|
||||
Soll ein FHEM Gerät zur Lieferung der Wetterdaten dienen, muß es vom Typ 'DWD_OpenData' sein.<br>
|
||||
Ist noch kein Gerät dieses Typs vorhanden, muß zunächst mindestens ein DWD_OpenData-Gerät
|
||||
definiert werden (siehe <a href="http://fhem.de/commandref.html#DWD_OpenData">DWD_OpenData Commandref</a>). <br>
|
||||
Sind mehr als ein ctrlWeatherDevX angegeben, wird der Durchschnitt aller Wetterstationen ermittelt und verwendet
|
||||
Sind mehr als ein ctrlWeatherDevX angegeben, wird der Durchschnitt aller Wetterstationen ermittelt
|
||||
sofern der jeweilige Wert geliefert wurde und numerisch ist. <br>
|
||||
Anderenfalls werden immer die Daten von 'ctrlWeatherDev1' als führendes Wetterdevice genutzt. <br>
|
||||
Im ausgewählten DWD_OpenData Device müssen mindestens diese Attribute gesetzt sein: <br><br>
|
||||
Im ausgewählten DWD_OpenData Gerät müssen mindestens diese Attribute gesetzt sein: <br><br>
|
||||
|
||||
<ul>
|
||||
<table>
|
||||
|
Loading…
x
Reference in New Issue
Block a user