2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-21 01:46:08 +00:00

55_DWD_OpenData: 1.17.2 alpha 4 - skip download of alert data if DWD document is unchanged, attribute forecastDataPresision replaced with attribute forecastRefresh

git-svn-id: https://svn.fhem.de/fhem/trunk@28578 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
jensb 2024-03-01 23:25:12 +00:00
parent 87014e0b48
commit 7e606b4b9c

View File

@ -1,5 +1,5 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# $Id: 55_DWD_OpenData.pm 28556 2024-02-28 17:59:00Z jensb $ # $Id: 55_DWD_OpenData.pm 28556 2024-03-01 20:12:00Z jensb $
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
=encoding UTF-8 =encoding UTF-8
@ -627,7 +627,7 @@ use constant DOWNLOAD_TIMEOUT_DEFAULT => DOWNLOAD_TIMEOUT_MIN; # [s]
use constant PROCESSING_TIMEOUT => DOWNLOAD_TIMEOUT_MAX + 30; # [s] use constant PROCESSING_TIMEOUT => DOWNLOAD_TIMEOUT_MAX + 30; # [s]
require Exporter; require Exporter;
our $VERSION = '1.017001'; our $VERSION = '1.017002';
our @ISA = qw(Exporter); our @ISA = qw(Exporter);
our @EXPORT = qw(GetForecast GetAlerts UpdateAlerts UPDATE_DISTRICTS UPDATE_COMMUNEUNIONS UPDATE_ALL); our @EXPORT = qw(GetForecast GetAlerts UpdateAlerts UPDATE_DISTRICTS UPDATE_COMMUNEUNIONS UPDATE_ALL);
our @EXPORT_OK = qw(IsCommuneUnionWarncellId); our @EXPORT_OK = qw(IsCommuneUnionWarncellId);
@ -923,6 +923,15 @@ sub Attr {
} }
} }
} }
when ("forecastRefresh") {
if (!(defined($value) && looks_like_number($value) && $value >= 1 && $value <= 6)) {
my $oldRefresh = ::AttrVal($name, 'forecastRefresh', 6);
if ($::init_done && (($oldRefresh < 6 && $value >= 6) || ($oldRefresh >= 6 && $value < 6))) {
# delete readings when switching between MOSMIX S and L
::CommandDeleteReading(undef, "$name ^fc.*");
}
}
}
when ("forecastResolution") { when ("forecastResolution") {
if (defined($value) && looks_like_number($value) && $value > 0) { if (defined($value) && looks_like_number($value) && $value > 0) {
my $oldForecastResolution = ::AttrVal($name, 'forecastResolution', 6); my $oldForecastResolution = ::AttrVal($name, 'forecastResolution', 6);
@ -935,7 +944,7 @@ sub Attr {
} }
when ("downloadTimeout") { when ("downloadTimeout") {
if (!(defined($value) && looks_like_number($value) && $value >= DOWNLOAD_TIMEOUT_MIN && $value <= DOWNLOAD_TIMEOUT_MAX)) { if (!(defined($value) && looks_like_number($value) && $value >= DOWNLOAD_TIMEOUT_MIN && $value <= DOWNLOAD_TIMEOUT_MAX)) {
return "invalid value for forecastResolution (" . DOWNLOAD_TIMEOUT_MIN . " .. " . DOWNLOAD_TIMEOUT_MAX . ")"; return "invalid value for downloadTimeout (" . DOWNLOAD_TIMEOUT_MIN . " .. " . DOWNLOAD_TIMEOUT_MAX . ")";
} }
} }
when ("forecastStation") { when ("forecastStation") {
@ -944,13 +953,6 @@ sub Attr {
::CommandDeleteReading(undef, "$name ^fc.*"); ::CommandDeleteReading(undef, "$name ^fc.*");
} }
} }
# @TODO check attribute name
when ("forecastDataPrecision") {
my $oldForecastProcess = ::AttrVal($name, 'forecastDataPrecision', 'low');
if ($::init_done && $oldForecastProcess ne $value) {
::CommandDeleteReading(undef, "$name ^fc.*");
}
}
when ("forecastWW2Text") { when ("forecastWW2Text") {
if ($::init_done && !$value) { if ($::init_done && !$value) {
::CommandDeleteReading(undef, "$name ^fc.*wwd\$"); ::CommandDeleteReading(undef, "$name ^fc.*wwd\$");
@ -981,8 +983,7 @@ sub Attr {
when ("forecastStation") { when ("forecastStation") {
::CommandDeleteReading(undef, "$name ^fc.*"); ::CommandDeleteReading(undef, "$name ^fc.*");
} }
# @TODO check attribute name when ("forecastResolution") {
when ("forecastDataPrecision") {
::CommandDeleteReading(undef, "$name ^fc.*"); ::CommandDeleteReading(undef, "$name ^fc.*");
} }
when ("forecastWW2Text") { when ("forecastWW2Text") {
@ -1121,10 +1122,10 @@ sub Timer {
# perform updates every quarter of an hour: alerts=every, forecast=specific # perform updates every quarter of an hour: alerts=every, forecast=specific
my $firstRun = delete $hash->{'.firstRun'} // 0; my $firstRun = delete $hash->{'.firstRun'} // 0;
my $forecastQuarter = 2; # DWD provides forecast data typically 25 minutes past the full hour my $forecastQuarter = ::AttrVal($name, 'forecastRefresh', 6) >= 6 ? 0 : 2;
my $fetchAlerts = defined($hash->{".fetchAlerts"}) && $hash->{".fetchAlerts"}; # fetch either alerts or forecast my $fetchAlerts = defined($hash->{".fetchAlerts"}) && $hash->{".fetchAlerts"}; # fetch either alerts or forecast
::Log3 $name, 3, "$name: Timer first:$firstRun forecastQuarter:$forecastQuarter fetchAlerts:$fetchAlerts"; ::Log3 $name, 5, "$name: Timer first:$firstRun forecastQuarter:$forecastQuarter fetchAlerts:$fetchAlerts";
# update forecast and alerts immediately at startup # update forecast and alerts immediately at startup
$forecastQuarter = $actQuarter if ($firstRun); $forecastQuarter = $actQuarter if ($firstRun);
@ -1265,21 +1266,21 @@ sub LocaltimeOffset {
=item * param t: epoch seconds =item * param t: epoch seconds
=item * return date time string with with format "YYYY-MM-DD HH:MM:SS" with UTC timezone =item * return date time string with with format "YYYY-MM-DD HH:MM:SSZ" with UTC timezone
=back =back
=cut =cut
sub FormatDateTimeUTC { sub FormatDateTimeUTC {
return ::strftime('%Y-%m-%d %H:%M:%S', gmtime(@_)); return ::strftime('%Y-%m-%d %H:%M:%SZ', gmtime(@_));
} }
=head2 ParseDateTimeUTC($$) =head2 ParseDateTimeUTC($$)
=over =over
=item * param s: date string with format "YYYY-MM-DD HH:MM:SS" with UTC timezone =item * param s: date string with format "YYYY-MM-DD HH:MM:SSZ" with UTC timezone
=item * return epoch seconds or C<undef> on error =item * return epoch seconds or C<undef> on error
@ -1289,7 +1290,7 @@ sub FormatDateTimeUTC {
sub ParseDateTimeUTC { sub ParseDateTimeUTC {
my $t; my $t;
eval { $t = ::strptime(@_, '%Y-%m-%d %H:%M:%S') }; eval { $t = ::timegm(::strptime(@_, '%Y-%m-%d %H:%M:%S%z')) };
return $t; return $t;
} }
@ -1691,8 +1692,6 @@ sub GetForecast {
=cut =cut
use Data::Dumper;
sub GetHeaders { sub GetHeaders {
my $url=shift; my $url=shift;
my $ua = new LWP::UserAgent(env_proxy => 1, timeout => 5, agent => 'fhem'); my $ua = new LWP::UserAgent(env_proxy => 1, timeout => 5, agent => 'fhem');
@ -1705,6 +1704,58 @@ sub GetHeaders {
return undef; return undef;
} }
=head2 IsDocumentUpdated($$$)
Check if a web document was updated by comparing the webserver header info with reading values.
=over
=item * param hash: hash of DWD_OpenData device
=item * param url: URL for wich the HTTP headers should be retrieved.
=item * param prefix: reading name prefix ('fc' or 'a') for document size and timestamp
=item * param docSize: output, size [bytes] of the web document
=item * param docTime: output, timestamp [UTC] of the web document
=back
=cut
sub IsDocumentUpdated {
my ($hash, $url, $prefix) = @_;
my $name = $hash->{NAME};
# check if file on webserver was modified
::Log3 $name, 5, "$name: IsDocumentUpdated BEFORE";
my $headers = GetHeaders($url);
my $update = 1;
if (defined($headers)) {
$_[3] = $headers->content_length(); # docSize
$_[4] = FormatDateTimeUTC($headers->last_modified()); # docTime
my $lastURL = ::ReadingsVal($name, $prefix.'_url', '');
my $lastSize = ::ReadingsVal($name, $prefix.'_dwdDocSize', 0);
my $lastTime = ::ReadingsVal($name , $prefix.'_dwdDocTime', '');
my $emptyAlertsZipSize = 22; # bytes of empty zip file
::Log3 $name, 5, "$name: IsDocumentUpdated docSize:$_[3]/$lastSize docTime:$_[4]/$lastTime URL:$url/$lastURL";
if ($url eq $lastURL && ($_[3] == $lastSize && $_[4] eq $lastTime) || ($prefix eq 'a' && $_[3] == $emptyAlertsZipSize && $lastSize == $emptyAlertsZipSize)) {
# not modified
$update = 0;
}
}
else
{
# headers not available
$_[3] = 0; # docSize
$_[4] = ''; # docTime
}
::Log3 $name, 5, "$name: IsDocumentUpdated AFTER";
return $update;
}
=head2 GetForecastStart($) =head2 GetForecastStart($)
BlockingCall I<BlockingFn> callback BlockingCall I<BlockingFn> callback
@ -1734,36 +1785,23 @@ sub GetForecastStart {
# get forecast for station from DWD server # get forecast for station from DWD server
my $url; my $url;
my $dataPrecision = ::AttrVal($name, 'forecastDataPrecision', 'low') eq 'high' ? 'S' : 'L'; my $mosmixType = ::AttrVal($name, 'forecastRefresh', 6) < 6 ? 'S' : 'L';
if ($dataPrecision eq 'S') { if ($mosmixType eq 'S') {
$url = "https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_S/all_stations/kml/MOSMIX_S_LATEST_240.kmz"; $url = "https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_S/all_stations/kml/MOSMIX_S_LATEST_240.kmz";
} else { } else {
$url = 'https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_L/single_stations/' . $station . '/kml/MOSMIX_L_LATEST_' . $station . '.kmz'; $url = 'https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_L/single_stations/' . $station . '/kml/MOSMIX_L_LATEST_' . $station . '.kmz';
} }
::Log3 $name, 3, "$name: GetForecastStart BEFORE"; # determine if a new forecast report should be downloaded
my ($dwdDocSize, $dwdDocTime);
my $update = IsDocumentUpdated($hash, $url, 'fc', $dwdDocSize, $dwdDocTime);
my $lastDocSize = ::ReadingsVal($name , 'fc_dwdDocSize', 0);
my $lastDocTimestamp = ParseDateTimeUTC(::ReadingsVal($name , 'fc_dwdDocTime', '1970-01-01 00:00:00Z'));
my $dwdDocTimestamp = length($dwdDocTime) ? ParseDateTimeUTC($dwdDocTime) : time();
my $maxDocAge = (::AttrVal($name, 'forecastRefresh', 6) - 0.5) * 60 * 60; # [s]
$update = $update && ($lastDocSize == 0 || ($dwdDocTimestamp - $lastDocTimestamp) >= $maxDocAge);
# check if file on server was modified ::Log3 $name, 5, "$name: GetForecastStart $dwdDocTime $dwdDocTimestamp $lastDocTimestamp $maxDocAge $update";
my $headers = GetHeaders($url);
my $update = 1;
my $kmzSize = 0;
my $kmzTime = '';
if (defined($headers)) {
$kmzSize = $headers->content_length();
$kmzTime = FormatDateTimeUTC($headers->last_modified());
my $lastURL = ::ReadingsVal($name, 'fc_url', '');
my $lastSize = ::ReadingsVal($name, 'fc_dwdDocSize', 0);
my $lastTime = ::ReadingsVal($name , 'fc_dwdDocTime', '');
::Log3 $name, 3, "$name: GetForecastStart kmzSize:$kmzSize/$lastSize kmzTime:$kmzTime/$lastTime URL:$url/$lastURL";
if ($url eq $lastURL && $kmzSize == $lastSize && $kmzTime eq $lastTime) {
::Log3 $name, 3, "$name: unchanged";
$update = 0;
} else {
::Log3 $name, 3, "$name: modified";
}
}
::Log3 $name, 3, "$name: GetForecastStart AFTER";
my $result; my $result;
if ($update) { if ($update) {
@ -1773,9 +1811,9 @@ sub GetForecastStart {
timeout => ::AttrVal($name, 'downloadTimeout', DOWNLOAD_TIMEOUT_DEFAULT), timeout => ::AttrVal($name, 'downloadTimeout', DOWNLOAD_TIMEOUT_DEFAULT),
hash => $hash, hash => $hash,
station => $station, station => $station,
dataPrecision => $dataPrecision, mosmixType => $mosmixType,
dwdDocSize => $kmzSize, dwdDocSize => $dwdDocSize,
dwdDocTime => $kmzTime dwdDocTime => $dwdDocTime
}; };
::Log3 $name, 5, "$name: GetForecastStart START (PID $$): $url"; ::Log3 $name, 5, "$name: GetForecastStart START (PID $$): $url";
my ($httpError, $fileContent) = ::HttpUtils_BlockingGet($param); my ($httpError, $fileContent) = ::HttpUtils_BlockingGet($param);
@ -1864,7 +1902,7 @@ sub ProcessForecast {
my $url = $param->{url}; my $url = $param->{url};
my $code = $param->{code}; my $code = $param->{code};
my $station = $param->{station}; my $station = $param->{station};
my $dataPrecision = $param->{dataPrecision}; my $mosmixType = $param->{mosmixType};
my $dwdDocSize = $param->{dwdDocSize}; my $dwdDocSize = $param->{dwdDocSize};
my $dwdDocTime = $param->{dwdDocTime}; my $dwdDocTime = $param->{dwdDocTime};
@ -1892,7 +1930,7 @@ sub ProcessForecast {
my %selectedProperties; my %selectedProperties;
if (!@properties) { if (!@properties) {
# no selection: use defaults # no selection: use defaults
if ($dataPrecision eq 'S') { if ($mosmixType eq 'S') {
%selectedProperties = %forecastDefaultPropertiesS; %selectedProperties = %forecastDefaultPropertiesS;
} else { } else {
%selectedProperties = %forecastDefaultPropertiesL; %selectedProperties = %forecastDefaultPropertiesL;
@ -1975,7 +2013,7 @@ sub ProcessForecast {
my $placemarkNodeList = $dom->getElementsByLocalName('Placemark'); my $placemarkNodeList = $dom->getElementsByLocalName('Placemark');
if ($placemarkNodeList->size()) { if ($placemarkNodeList->size()) {
my $placemarkNodePos; my $placemarkNodePos;
if ($dataPrecision eq 'S') { if ($mosmixType eq 'S') {
$placemarkNodePos = getStationPos ($name, $station, $placemarkNodeList); $placemarkNodePos = getStationPos ($name, $station, $placemarkNodeList);
if ($placemarkNodePos < 1) { if ($placemarkNodePos < 1) {
die "station '" . $station . "' not found in XML data"; die "station '" . $station . "' not found in XML data";
@ -2444,20 +2482,35 @@ sub GetAlertsStart {
my $communeUnion = IsCommuneUnionWarncellId($warncellId); my $communeUnion = IsCommuneUnionWarncellId($warncellId);
my $alertLanguage = ::AttrVal($name, 'alertLanguage', 'DE'); my $alertLanguage = ::AttrVal($name, 'alertLanguage', 'DE');
my $url = 'https://opendata.dwd.de/weather/alerts/cap/'.($communeUnion? 'COMMUNEUNION' : 'DISTRICT').'_CELLS_STAT/Z_CAP_C_EDZW_LATEST_PVW_STATUS_PREMIUMCELLS_'.($communeUnion? 'COMMUNEUNION' : 'DISTRICT').'_'.$alertLanguage.'.zip'; my $url = 'https://opendata.dwd.de/weather/alerts/cap/'.($communeUnion? 'COMMUNEUNION' : 'DISTRICT').'_CELLS_STAT/Z_CAP_C_EDZW_LATEST_PVW_STATUS_PREMIUMCELLS_'.($communeUnion? 'COMMUNEUNION' : 'DISTRICT').'_'.$alertLanguage.'.zip';
my $param = {
url => $url,
method => "GET",
timeout => ::AttrVal($name, 'downloadTimeout', DOWNLOAD_TIMEOUT_DEFAULT),
hash => $hash,
warncellId => $warncellId
};
::Log3 $name, 5, "$name: GetAlertsStart START (PID $$): $url";
my ($httpError, $fileContent) = ::HttpUtils_BlockingGet($param);
# process retrieved data my ($dwdDocSize, $dwdDocTime);
my $result = ProcessAlerts($param, $httpError, $fileContent); my $update = IsDocumentUpdated($hash, $url, 'a', $dwdDocSize, $dwdDocTime);
::Log3 $name, 5, "$name: GetAlertsStart END"; my $result;
if ($update) {
my $param = {
url => $url,
method => "GET",
timeout => ::AttrVal($name, 'downloadTimeout', DOWNLOAD_TIMEOUT_DEFAULT),
hash => $hash,
warncellId => $warncellId,
dwdDocSize => $dwdDocSize,
dwdDocTime => $dwdDocTime,
};
::Log3 $name, 5, "$name: GetAlertsStart START (PID $$): $url $dwdDocSize $dwdDocTime";
my ($httpError, $fileContent) = ::HttpUtils_BlockingGet($param);
# process retrieved data
$result = ProcessAlerts($param, $httpError, $fileContent);
::Log3 $name, 5, "$name: GetAlertsStart END";
} else {
# already up to date
$result = [$name, 'up-to-date', $warncellId];
::Log3 $name, 5, "$name: GetAlertsStart UP-TO-DATE";
}
return $result; return $result;
} }
@ -2466,7 +2519,11 @@ sub GetAlertsStart {
=over =over
=item * param hash: hash of DWD_OpenData device =item * param param: parameter hash from call to HttpUtils_NonblockingGet
=item * param httpError: nothing or HTTP error string
=item * param fileContent: data retrieved from URL
=item * return result required by function L</GetAlertsFinish(@)> =item * return result required by function L</GetAlertsFinish(@)>
@ -2481,14 +2538,15 @@ ATTENTION: This method is executed in a different process than FHEM.
sub ProcessAlerts { sub ProcessAlerts {
my ($param, $httpError, $fileContent) = @_; my ($param, $httpError, $fileContent) = @_;
my $time = time();
my $hash = $param->{hash}; my $hash = $param->{hash};
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $url = $param->{url}; my $url = $param->{url};
my $code = $param->{code}; my $code = $param->{code};
my $warncellId = $param->{warncellId}; my $warncellId = $param->{warncellId};
::Log3 $name, 5, "$name: ProcessAlerts START (PID $$)"; $param->{receivedTime} = time();
::Log3 $name, 5, "$name: ProcessAlerts START (PID $$) $warncellId";
my %alerts; my %alerts;
eval { eval {
@ -2648,7 +2706,7 @@ sub ProcessAlerts {
::Log3 $name, 5, "$name: ProcessAlerts END"; ::Log3 $name, 5, "$name: ProcessAlerts END";
return [$name, $errorMessage, $warncellId, $time]; return [$name, $errorMessage, $param->{warncellId}, $param->{receivedTime}, $param->{url}, $param->{dwdDocSize}, $param->{dwdDocTime}];
} }
=head2 GetAlertsFinish(@) =head2 GetAlertsFinish(@)
@ -2670,10 +2728,20 @@ BlockingCall I<FinishFn> callback, expects array returned by function L</GetAler
=cut =cut
sub GetAlertsFinish { sub GetAlertsFinish {
my ($name, $errorMessage, $warncellId, $time) = @_; my ($name, $errorMessage, $warncellId, $receivedTime, $url, $dwdDocSize, $dwdDocTime) = @_;
my $paramCount = @_;
my %docHeader;
if ($paramCount > 3) {
$docHeader{warncellId} = $warncellId;
$docHeader{receivedTime} = $receivedTime;
$docHeader{url} = $url;
$docHeader{dwdDocSize} = $dwdDocSize;
$docHeader{dwdDocTime} = $dwdDocTime;
}
if (defined($name)) { if (defined($name)) {
::Log3 $name, 5, "$name: GetAlertsFinish START (PID $$)"; ::Log3 $name, 5, "$name: GetAlertsFinish START (PID $$) $warncellId";
my $hash = $::defs{$name}; my $hash = $::defs{$name};
my $communeUnion = IsCommuneUnionWarncellId($warncellId); my $communeUnion = IsCommuneUnionWarncellId($warncellId);
@ -2733,18 +2801,24 @@ sub GetAlertsFinish {
::readingsSingleUpdate($hash, 'state', 'alerts cache updated', 1); ::readingsSingleUpdate($hash, 'state', 'alerts cache updated', 1);
} }
} }
$alertsReceived[$communeUnion] = $time; $alertsReceived[$communeUnion] = $receivedTime;
if (defined($errorMessage) && length($errorMessage) > 0) { if (defined($errorMessage) && length($errorMessage) > 0) {
$alertsErrorMessage[$communeUnion] = $errorMessage; if ($errorMessage eq 'up-to-date') {
::readingsSingleUpdate($hash, 'state', "alerts error: $errorMessage", 1); ::readingsBeginUpdate($hash);
::readingsBulkUpdate($hash, 'state', "alerts unchanged");
::readingsBulkUpdate($hash, 'a_state', 'updated');
::readingsEndUpdate($hash, 1);
} else {
::readingsSingleUpdate($hash, 'state', "alerts error: $errorMessage", 1);
}
} else { } else {
$alertsErrorMessage[$communeUnion] = undef; $alertsErrorMessage[$communeUnion] = undef;
} }
if ($warncellId >= 0) { if ($paramCount > 3 && $errorMessage ne 'up-to-date') {
# update alert readings for warncell id # update alert readings for warncell id
UpdateAlerts($hash, $warncellId); UpdateAlerts($hash, $warncellId, \%docHeader);
} }
$alertsUpdating[$communeUnion] = undef; $alertsUpdating[$communeUnion] = undef;
@ -2806,11 +2880,11 @@ update alert readings for given warncell id from global alerts list
=cut =cut
sub UpdateAlerts { sub UpdateAlerts {
my ($hash, $warncellId) = @_; my ($hash, $warncellId, $docHeader) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
# delete existing alert readings # delete existing alert readings
::CommandDeleteReading(undef, "$name ^(?!a_count|a_state|a_time)a_.*"); ::CommandDeleteReading(undef, "$name ^(?!a_count|a_state|a_time|a_url|a_dwdDocSize|a_dwdDocTime)a_.*");
::readingsBeginUpdate($hash); ::readingsBeginUpdate($hash);
@ -2894,9 +2968,15 @@ sub UpdateAlerts {
} }
} }
# alert count and receive time # alert count, receive time and DWD document properties
::readingsBulkUpdate($hash, 'a_count', $index); ::readingsBulkUpdate($hash, 'a_count', $index);
::readingsBulkUpdate($hash, "a_time", FormatDateTimeLocal($hash, $alertsReceived[$communeUnion])); if (defined($docHeader)) {
::readingsBulkUpdate($hash, "a_time", FormatDateTimeLocal($hash, $docHeader->{receivedTime}));
::readingsBulkUpdate($hash, "a_url", $docHeader->{url});
::readingsBulkUpdate($hash, "a_dwdDocSize", $docHeader->{dwdDocSize});
::readingsBulkUpdate($hash, "a_dwdDocTime", $docHeader->{dwdDocTime});
}
::readingsBulkUpdate($hash, 'state', 'alerts updated'); ::readingsBulkUpdate($hash, 'state', 'alerts updated');
::readingsEndUpdate($hash, 1); ::readingsEndUpdate($hash, 1);
@ -2934,7 +3014,7 @@ sub DWD_OpenData_Initialize {
$hash->{GetFn} = 'DWD_OpenData::Get'; $hash->{GetFn} = 'DWD_OpenData::Get';
$hash->{AttrList} = 'disable:0,1 ' $hash->{AttrList} = 'disable:0,1 '
.'forecastStation forecastDays forecastProperties forecastResolution:1,3,6 forecastWW2Text:0,1 forecastPruning:0,1 forecastDataPrecision:low,high ' .'forecastStation forecastDays forecastProperties forecastResolution:1,3,6 forecastWW2Text:0,1 forecastPruning:0,1 forecastRefresh:slider,1,1,6 '
.'alertArea alertLanguage:DE,EN alertExcludeEvents ' .'alertArea alertLanguage:DE,EN alertExcludeEvents '
.'timezone ' .'timezone '
.'downloadTimeout ' .'downloadTimeout '
@ -2949,6 +3029,10 @@ sub DWD_OpenData_Initialize {
# #
# CHANGES # CHANGES
# #
# 01.03.2024 (version 1.17.2) jensb
# feature: skip download of alert data if DWD document is unchanged
# change: attribute forecastDataPresision replaced with attribute forecastRefresh
#
# 28.02.2024 (version 1.17.1) jensb # 28.02.2024 (version 1.17.1) jensb
# feature: skip download of forecast data if DWD document is unchanged # feature: skip download of forecast data if DWD document is unchanged
# feature: show context description for get commands and attributes in FHEMWEB # feature: show context description for get commands and attributes in FHEMWEB
@ -3198,11 +3282,12 @@ sub DWD_OpenData_Initialize {
Time resolution (number of hours between 2 samples).<br> Time resolution (number of hours between 2 samples).<br>
Note: When value is changed all existing forecast readings will be deleted. Note: When value is changed all existing forecast readings will be deleted.
</li><br> </li><br>
<a id="DWD_OpenData-attr-forecastDataPrecision"></a> <a id="DWD_OpenData-attr-forecastRefresh"></a>
<li>forecastDataPrecision {low|high}, default: low<br> <li>forecastRefresh &lt;n&gt;, 1 .. 6 h, default: 6 h<br>
The DWD distinguishes between MOSMIX S and L reports, which differ in terms of update frequency and available data elements:<br> The DWD distinguishes between MOSMIX S and L reports, which differ in terms of update frequency and available data elements:<br>
- low: MOSMIX L, ~115 data elements, updated every 6 h, download volume ~3 kB/h<br> - 1 .. 5 h: MOSMIX S, 40 data elements, updated every 1 h at ~25 min past every hour, download volume ~400 MB/h<br>
- high: MOSMIX S, 40 data elements, updated every 1 h, download volume ~400 MB/h<br> - 6 h: MOSMIX L, ~115 data elements, updated every 6 h at ~55 min past 21/3/9/15 UTC, download volume ~3 kB/h<br>
See the See the
<a href="https://www.dwd.de/DE/leistungen/met_verfahren_mosmix/mosmix_verfahrenbeschreibung_gesamt.pdf">MOSMIX processes description</a> <a href="https://www.dwd.de/DE/leistungen/met_verfahren_mosmix/mosmix_verfahrenbeschreibung_gesamt.pdf">MOSMIX processes description</a>
and the and the
@ -3214,7 +3299,8 @@ sub DWD_OpenData_Initialize {
- MOSMIX S requires more than 100 times the recources of MOSMIX L.<br> - MOSMIX S requires more than 100 times the recources of MOSMIX L.<br>
- minimum hardware recommendations: CPU with 2 cores, 4 GB RAM, 1 GB tempfs for /tmp<br> - minimum hardware recommendations: CPU with 2 cores, 4 GB RAM, 1 GB tempfs for /tmp<br>
- Using an SD card instead of tmpfs for /tmp will reduce the lifetime of the SD card significantly due to the write rate of ~1.5 GB/h.<br> - Using an SD card instead of tmpfs for /tmp will reduce the lifetime of the SD card significantly due to the write rate of ~1.5 GB/h.<br>
- Processing time dependes on download rate and hardware performance and may take several minutes. - Processing time dependes on download rate and hardware performance and may take several minutes.<br>
- When switching between MOSMIX S and L all existing forecast readings will be deleted.
</li><br> </li><br>
<a id="DWD_OpenData-attr-forecastProperties"></a> <a id="DWD_OpenData-attr-forecastProperties"></a>
<li>forecastProperties [&lt;p1&gt;[,&lt;p2&gt;]...], default: Tx, Tn, Tg, TTT, DD, FX1, Neff, RR6c, RRhc, Rh00, ww<br> <li>forecastProperties [&lt;p1&gt;[,&lt;p2&gt;]...], default: Tx, Tn, Tg, TTT, DD, FX1, Neff, RR6c, RRhc, Rh00, ww<br>
@ -3335,6 +3421,9 @@ sub DWD_OpenData_Initialize {
<li>fc_description - station description</li> <li>fc_description - station description</li>
<li>fc_coordinates - world coordinate and height of station</li> <li>fc_coordinates - world coordinate and height of station</li>
<li>fc_time - time the forecast was issued based on the timezone attribute</li> <li>fc_time - time the forecast was issued based on the timezone attribute</li>
<li>fc_url - URL of the forecast report document on the DWD webserver</li>
<li>fc_dwdDocTime - time of the forecast report document on the DWD webserver (UTC)</li>
<li>fc_dwdDocSize - size of the forecast report document on the DWD webserver (bytes)</li>
<li>fc_copyright - legal information, must be displayed with forecast data, see DWD usage conditions</li> <li>fc_copyright - legal information, must be displayed with forecast data, see DWD usage conditions</li>
</ul> </ul>
</ul> <br> </ul> <br>
@ -3373,10 +3462,13 @@ sub DWD_OpenData_Initialize {
<ul> <ul>
<ul> <ul>
<li>a_state - state of the last alerts update, possible values are 'updated' and 'error: ...'</li> <li>a_state - state of the last alerts update, possible values are 'updated' and 'error: ...'</li>
<li>a_time - time the last alerts update was downloaded, based on the timezone attribute</li> <li>a_time - time the last alerts update was downloaded, based on the timezone attribute</li>
<li>a_count - number of alerts available for selected warncell id</li> <li>a_count - number of alerts available for selected warncell id</li>
<li>a_copyright - legal information, must be displayed with forecast data, see DWD usage conditions, not available if count is zero</li> <li>a_url - URL of the alerts report document on the DWD webserver</li>
<li>a_dwdDocTime - time of the alerts report document on the DWD webserver (UTC)</li>
<li>a_dwdDocSize - size of the alerts report document on the DWD webserver (bytes)</li>
<li>a_copyright - legal information, must be displayed with forecast data, see DWD usage conditions, not available if count is zero</li>
</ul> </ul>
</ul> <br> </ul> <br>