2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-09 20:57:11 +00:00

55_DWD_OpenData.pm: new readings alertExcludeEvents, SunAz, SunEl and SunUp, support forecast resolution value 1 hour (see Forum #83097)

git-svn-id: https://svn.fhem.de/fhem/trunk@18767 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
jensb 2019-03-01 18:52:26 +00:00
parent 8fb01308c2
commit b84d5626c0
2 changed files with 262 additions and 44 deletions

View File

@ -1,5 +1,8 @@
# 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.
- feature: 55_DWD_OpenData:
new readings alertExcludeEvents, SunAz, SunEl and SunUp
support forecast resolution value 1 hour
- bugfix: 12_HProtocolGateway: fix checksum
- bugfix: 73_AutoShuttersControl: fix typo in Event RegEx for Twilight Event
- bugfix: 73_AutoShuttersControl: fix little winrec bug then state opened

View File

@ -11,11 +11,24 @@ DWD Open Data Server.
=head1 LICENSE AND COPYRIGHT
Copyright (C) 2018 Jens B.
Copyright (C) 2018 Jens B.
Copyright (C) 2018 JoWiemann (use of HttpUtils instead of LWP::Simple)
All rights reserved
All rights reserved
Use of HttpUtils instead of LWP::Simple:
Copyright (C) 2018 JoWiemann (FHEM forum post)
see https://forum.fhem.de/index.php/topic,83097.msg761015.html#msg761015
Sun position:
Copyright (C) 2013 Dietmar Ortmann
Copyright (C) 2012 Sebastian Stuecker
see FHEM/59_Twilight.pm
Copyright (C) 2011 aglutz (Symcon forum post)
see http://www.ip-symcon.de/forum/threads/14925-Sonnenstand-berechnen-(Azimut-amp-Elevation)
Copyright (C) 2011 DocZoid (Twilight.tcl)
see http://www.wikimatic.de/wiki/TCLScript:twilight
This script is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -46,7 +59,9 @@ use warnings;
use Encode;
use File::Temp qw(tempfile);
use IO::Uncompress::Unzip qw(unzip $UnzipError);
use Math::Trig ':pi';
use POSIX;
use Scalar::Util qw(looks_like_number);
use Storable qw(freeze thaw);
use Time::HiRes qw(gettimeofday usleep);
use Time::Local;
@ -63,7 +78,7 @@ use constant UPDATE_COMMUNEUNIONS => -2;
use constant UPDATE_ALL => -3;
require Exporter;
our $VERSION = 1.011.001;
our $VERSION = 1.013.000;
our @ISA = qw(Exporter);
our @EXPORT = qw(GetForecast GetAlerts UpdateAlerts UPDATE_DISTRICTS UPDATE_COMMUNEUNIONS UPDATE_ALL);
our @EXPORT_OK = qw(IsCommuneUnionWarncellId);
@ -71,15 +86,15 @@ our @EXPORT_OK = qw(IsCommuneUnionWarncellId);
my %forecastPropertyAliases = ( 'TX' => 'Tx', 'TN' => 'Tn', 'TG' => 'Tg', 'TM' => 'Tm' );
my %forecastPropertyPeriods = (
'DD' => 1, 'DRR1' => 1, 'E_DD' => 1, 'E_FF' => 1, 'E_PPP' => 1, 'E_Td' => 1, 'E_TTT' => 1, 'FF' => 1, 'FX1' => 1, 'FX3' => 1, 'FX625' => 1, 'FX640' => 1, 'FX655' => 1, 'FXh' => 1, 'FXh25' => 1, 'FXh40' => 1, 'FXh55' => 1, 'N' => 1, 'N05' => 1, 'Neff' => 1, 'Nh' => 1, 'Nl' => 1, 'Nlm' => 1, 'Nm' => 1, 'PPPP' => 1, 'R101' => 1, 'R102' => 1, 'R103' => 1, 'R105' => 1, 'R107' => 1, 'R110' => 1, 'R120' => 1, 'R130' => 1, 'R150' => 1, 'R600' => 1, 'R602' => 1, 'R610' => 1, 'R650' => 1, 'RR1c' => 1, 'RR1o1' => 1, 'RR1u1' => 1, 'RR1w1' => 1, 'RR3c' => 1, 'RR6c' => 1, 'RRL1c' => 1, 'RRS1c' => 1, 'RRS3c' => 1, 'RRad1' => 1, 'Rad1h' => 1, 'RRhc' => 1, 'Rh00' => 1, 'Rh02' => 1, 'Rh10' => 1, 'Rh50' => 1, 'SunD1' => 1, 'SunD3' => 1, 'T5cm' => 1, 'Td' => 1, 'TTT' => 1, 'VV' => 1, 'VV10' => 1, 'W1W2' => 1, 'WPc11' => 1, 'WPc31' => 1, 'WPc61' => 1, 'WPcd1' => 1, 'WPch1' => 1, 'ww' => 1, 'ww3' => 1, 'wwC' => 1, 'wwC6' => 1, 'wwCh' => 1, 'wwD' => 1, 'wwD6' => 1, 'wwDh' => 1, 'wwF' => 1, 'wwF6' => 1, 'wwFh' => 1, 'wwL' => 1, 'wwL6' => 1, 'wwLh' => 1, 'wwM' => 1, 'wwM6' => 1, 'wwMd' => 1, 'wwMh' => 1, 'wwP' => 1, 'wwP6' => 1, 'wwPd' => 1, 'wwPh' => 1, 'wwS' => 1, 'wwS6' => 1, 'wwSh' => 1, 'wwT' => 1, 'wwT6' => 1, 'wwTd' => 1, 'wwTh' => 1, 'wwZ' => 1, 'wwZ6' => 1, 'wwZh' => 1,
'DD' => 1, 'DRR1' => 1, 'E_DD' => 1, 'E_FF' => 1, 'E_PPP' => 1, 'E_Td' => 1, 'E_TTT' => 1, 'FF' => 1, 'FX1' => 1, 'FX3' => 1, 'FX625' => 1, 'FX640' => 1, 'FX655' => 1, 'FXh' => 1, 'FXh25' => 1, 'FXh40' => 1, 'FXh55' => 1, 'N' => 1, 'N05' => 1, 'Neff' => 1, 'Nh' => 1, 'Nl' => 1, 'Nlm' => 1, 'Nm' => 1, 'PPPP' => 1, 'R101' => 1, 'R102' => 1, 'R103' => 1, 'R105' => 1, 'R107' => 1, 'R110' => 1, 'R120' => 1, 'R130' => 1, 'R150' => 1, 'R600' => 1, 'R602' => 1, 'R610' => 1, 'R650' => 1, 'RR1c' => 1, 'RR1o1' => 1, 'RR1u1' => 1, 'RR1w1' => 1, 'RR3c' => 1, 'RR6c' => 1, 'RRL1c' => 1, 'RRS1c' => 1, 'RRS3c' => 1, 'RRad1' => 1, 'Rad1h' => 1, 'RRhc' => 1, 'Rh00' => 1, 'Rh02' => 1, 'Rh10' => 1, 'Rh50' => 1, 'SunAz' => 1, 'SunD1' => 1, 'SunD3' => 1, 'SunEl' => 1, 'SunUp' => 1, 'T5cm' => 1, 'Td' => 1, 'TTT' => 1, 'VV' => 1, 'VV10' => 1, 'W1W2' => 1, 'WPc11' => 1, 'WPc31' => 1, 'WPc61' => 1, 'WPcd1' => 1, 'WPch1' => 1, 'ww' => 1, 'ww3' => 1, 'wwC' => 1, 'wwC6' => 1, 'wwCh' => 1, 'wwD' => 1, 'wwD6' => 1, 'wwDh' => 1, 'wwF' => 1, 'wwF6' => 1, 'wwFh' => 1, 'wwL' => 1, 'wwL6' => 1, 'wwLh' => 1, 'wwM' => 1, 'wwM6' => 1, 'wwMd' => 1, 'wwMh' => 1, 'wwP' => 1, 'wwP6' => 1, 'wwPd' => 1, 'wwPh' => 1, 'wwS' => 1, 'wwS6' => 1, 'wwSh' => 1, 'wwT' => 1, 'wwT6' => 1, 'wwTd' => 1, 'wwTh' => 1, 'wwZ' => 1, 'wwZ6' => 1, 'wwZh' => 1,
'PEvap' => 24, 'PSd00' => 24, 'PSd30' => 24, 'PSd60' => 24, 'RRdc' => 24, 'RSunD' => 24, 'Rd00' => 24, 'Rd02' => 24, 'Rd10' => 24, 'Rd50' => 24, 'SunD' => 24, 'Tg' => 24, 'Tm' => 24, 'Tn' => 24, 'Tx' => 24
);
my %forecastDefaultProperties = (
'Tg' => 1, 'Tn' => 1, 'Tx' => 1, 'DD' => 1, 'FX1' => 1, 'Neff' => 1, 'RR6c' => 1, 'RRhc' => 1, 'Rh00' => 1, 'TTT' => 1, 'ww' => 1
'Tg' => 1, 'Tn' => 1, 'Tx' => 1, 'DD' => 1, 'FX1' => 1, 'Neff' => 1, 'RR6c' => 1, 'R600' => 1, 'RRhc' => 1, 'Rh00' => 1, 'TTT' => 1, 'ww' => 1, 'SunUp' => 1
);
# 1 = temperature in K, 2 = integer value, 3 = wind speed in m/s, 4 = pressure in Pa
# conversion of DWD value to: 1 = temperature in K, 2 = integer value, 3 = wind speed in m/s, 4 = pressure in Pa
my %forecastPropertyTypes = (
'Tx' => 1, 'Tn' => 1, 'Tg' => 1, 'Tm'=> 1, 'Td' => 1, 'T5cm' => 1, 'TTT' => 1,
'DD' => 2, 'Neff' => 2, 'Nh' => 2, 'Nl' => 2, 'Nlm' => 2, 'Nm' => 2, 'Rh00' => 2, 'ww' => 2, 'ww3' => 2, 'WPc11' => 2, 'WPc31' => 2, 'WPc61' => 2, 'WPch1' => 2, 'WPcd1' => 2,
@ -334,7 +349,7 @@ sub Attr(@) {
given($attribute) {
when("disable") {
# enable/disable polling
if ($main::init_done) {
if ($::init_done) {
if ($value) {
::RemoveInternalTimer($hash);
::readingsSingleUpdate($hash, 'state', 'disabled', 1);
@ -344,9 +359,25 @@ sub Attr(@) {
}
}
}
when("forecastResolution") {
if (defined($value) && looks_like_number($value) && $value > 0) {
my $oldForecastResolution = ::AttrVal($name, 'forecastResolution', 6);
if ($::init_done && defined($oldForecastResolution) && $oldForecastResolution != $value) {
::CommandDeleteReading(undef, "$name ^fc.*");
}
} else {
return "invalid value for forecastResolution (possible values are 1, 3 and 6)";
}
}
when("forecastStation") {
my $oldForecastStation = ::AttrVal($name, 'forecastStation', undef);
if ($::init_done && defined($oldForecastStation) && $oldForecastStation ne $value) {
::CommandDeleteReading(undef, "$name ^fc.*");
}
}
when("forecastWW2Text") {
if (!$value) {
::CommandDeleteReading(undef, "$name fc.*wwd");
if ($::init_done && !$value) {
::CommandDeleteReading(undef, "$name ^fc.*wwd\$");
}
}
when("timezone") {
@ -365,8 +396,17 @@ sub Attr(@) {
::readingsSingleUpdate($hash, 'state', 'defined', 1);
::InternalTimer(gettimeofday() + 3, 'DWD_OpenData::Timer', $hash, 0);
}
when("forecastResolution") {
my $oldForecastResolution = ::AttrVal($name, 'forecastResolution', 6);
if ($oldForecastResolution != 6) {
::CommandDeleteReading(undef, "$name ^fc.*");
}
}
when("forecastStation") {
::CommandDeleteReading(undef, "$name ^fc.*");
}
when("forecastWW2Text") {
::CommandDeleteReading(undef, "$name fc.*wwd");
::CommandDeleteReading(undef, "$name ^fc.*wwd\$");
}
when("timezone") {
$hash->{'.TZ'} = $hash->{FHEM_TZ};
@ -765,6 +805,110 @@ sub IsCommuneUnionWarncellId($) {
|| $warncellId == UPDATE_COMMUNEUNIONS || $warncellId == UPDATE_ALL? 1 : 0;
}
=head2 SunPosition(;$$$$)
Calculate the azimuth and elevation of the sun for the given time and location.
Background: see https://en.wikipedia.org/wiki/Position_of_the_Sun
=over
=item * param time: epoch time [s], optional, default: now
=item * param longitude: geographic longitude [deg], optional, default: global longitude or Frankfurt, Germany
=item * param latitude: geographic latitude [deg], optional, default: global latitude or Frankfurt, Germany
=item * return array of azimuth and elevation [deg]
=back
=cut
sub SunPosition(;$$$$) {
my ($time, $longitude, $latitude) = @_;
if (!defined($time)) {
$time = time();
}
if (!defined($longitude) || !defined($latitude)) {
# undefined: use Frankfurt, Germany
$longitude = ::AttrVal("global", "longitude", "8.686");
$latitude = ::AttrVal("global", "latitude", "50.112");
}
# Convert epoch time into its date/time parts
my ($seconds, $minutes, $hours, $day, $month, $year, $wday, $yday, $isdst) = gmtime($time);
$month++;
$year += 100;
# Convert day time into decimal hours
my $timeAsHours = $hours + $minutes/60.0 + $seconds/3600.0;
# Calculate difference in days between the current day and
# noon 1 January 2000 Universal Time
my $yearsAfter2000 = $year;
my $aux1 = (14 - ($month))/12;
my $aux2 = $month + 12*$aux1 - 3;
my $aux3 = (153 * $aux2 + 2)/5;
my $aux4 = 365 * ($yearsAfter2000 - $aux1);
my $aux5 = ($yearsAfter2000 - $aux1)/4;
my $elapsedDays = ($day + $aux3 + $aux4 + $aux5 + 59) - 0.5 + $timeAsHours/24.0;
# Calculate ecliptic coordinates (ecliptic longitude and obliquity of the
# ecliptic in radians but without limiting the angle to 2*pi
# (i.e., the result may be greater than 2*pi)
my $omega = 2.1429 - 0.0010394594 * $elapsedDays;
my $meanLongitude = 4.8950630 + 0.017202791698 * $elapsedDays; # [rad]
my $meanAnomaly = 6.2400600 + 0.0172019699 * $elapsedDays;
my $eclipticLongitude = $meanLongitude + 0.03341607 * sin($meanAnomaly) + 0.00034894 * sin(2*$meanAnomaly ) - 0.0001134 - 0.0000203 * sin($omega);
my $eclipticObliquity = 0.4090928 - 6.2140e-9 * $elapsedDays +0.0000396 * cos($omega);
# Calculate celestial coordinates (right ascension and declination) in radians
# but without limiting the angle to 2*pi (i.e., the result may be
# greater than 2*pi)
my $sinEclipticLongitude = sin($eclipticLongitude);
my $y1 = cos($eclipticObliquity)*$sinEclipticLongitude;
my $x1 = cos($eclipticLongitude);
my $rightAscension = atan2($y1, $x1);
if ($rightAscension < 0.0) {
$rightAscension = $rightAscension + pi2;
}
my $declination = asin(sin($eclipticObliquity)*$sinEclipticLongitude);
# Calculate local coordinates (azimuth [deg] and zenith angle [rad])
my $rad = (pi/180);
my $greenwichMeanSiderealTime = 6.6974243242 + 0.0657098283*$elapsedDays + $timeAsHours;
my $localMeanSiderealTime = ($greenwichMeanSiderealTime*15 + $longitude)*$rad;
my $hourAngle = $localMeanSiderealTime - $rightAscension;
my $cosHourAngle = cos($hourAngle);
my $latitudeRadians = $latitude*$rad;
my $cosLatitude = cos($latitudeRadians);
my $sinLatitude = sin($latitudeRadians);
my $zenithAngle = (acos($cosLatitude*$cosHourAngle*cos($declination) + sin($declination)*$sinLatitude));
my $y = -sin($hourAngle);
my $x = tan($declination )*$cosLatitude - $sinLatitude*$cosHourAngle;
my $azimuth = atan2($y, $x);
if ($azimuth < 0.0) {
$azimuth = $azimuth + pi2;
}
$azimuth = $azimuth/$rad;
$azimuth = sprintf("%0.1f", $azimuth); # round(1)
# Parallax correction of zenith angle [deg]
my $meanEarthRadius = 6371.01; # [km]
my $astronomicalUnit = 149597890; # [km]
my $parallax = ($meanEarthRadius/$astronomicalUnit)*sin($zenithAngle);
$zenithAngle = ($zenithAngle + $parallax)/$rad;
# Elevation [deg]
my $elevation = 90 - $zenithAngle;
$elevation = sprintf("%0.1f", $elevation); # round(1)
return ($azimuth, $elevation);
}
=head2 RotateForecast($$;$)
=over
@ -798,7 +942,7 @@ sub RotateForecast($$;$)
my $stationChanged = ::ReadingsVal($name, 'fc_station', '') ne $station;
if ($stationChanged) {
# different station, delete all existing readings
::CommandDeleteReading(undef, "$name fc.*");
::CommandDeleteReading(undef, "$name ^fc.*");
$daysAvailable = 0;
} elsif (defined($oldToday)) {
# same station, shift existing readings
@ -843,12 +987,12 @@ sub RotateForecast($$;$)
}
# delete existing readings of all days that have not been written
for (my $d=($daysAvailable - $daysForward); $d<$daysAvailable; $d++) {
::CommandDeleteReading(undef, "$name fc".$d."_.*");
::CommandDeleteReading(undef, "$name ^fc".$d."_.*");
}
$daysAvailable -= $daysForward;
} else {
# nothing to shift, delete existing readings
::CommandDeleteReading(undef, "$name fc.*");
# nothing remains after shifting, delete existing day readings
::CommandDeleteReading(undef, "$name ^fc\\d+.*");
$daysAvailable = 0;
}
}
@ -994,6 +1138,7 @@ sub ProcessForecast($$$)
my %forecast;
my $relativeDay = 0;
my @coordinates;
eval {
if (defined($httpError) && length($httpError) > 0) {
die "error retrieving URL '$url': $httpError";
@ -1085,6 +1230,7 @@ sub ProcessForecast($$$)
# extract time data
my %timeProperties;
my ($longitude, $latitude);
my $placemarkNodeList = $dom->getElementsByLocalName('Placemark');
if ($placemarkNodeList->size()) {
my $placemarkNode = $placemarkNodeList->get_node(1);
@ -1104,7 +1250,7 @@ sub ProcessForecast($$$)
my $textContent = $extendedDataChildNode->nonBlankChildNodes()->get_node(1)->textContent();
$textContent =~ s/^\s+|\s+$//g; # trim outside
$textContent =~ s/\s+/ /g; # trim inside
my @values = split(' ',$textContent);
my @values = split(' ', $textContent);
$timeProperties{$elementName} = \@values;
}
}
@ -1112,9 +1258,33 @@ sub ProcessForecast($$$)
} elsif ($placemarkChildNode->nodeName() eq 'kml:Point') {
my $coordinates = $placemarkChildNode->nonBlankChildNodes()->get_node(1)->textContent();
$header{coordinates} = $coordinates;
($longitude, $latitude) = split(',', $coordinates);
}
}
}
# calculate sun position properties for each timestamp
if (defined($longitude) && defined($latitude)) {
my @azimuths;
my @elevations;
my @sunups;
foreach my $timestamp (@timestamps) {
my ($azimuth, $elevation) = SunPosition($timestamp, $longitude, $latitude);
push(@azimuths, $azimuth); # [deg]
push(@elevations, $elevation); # [deg]
push(@sunups, $elevation >= -12? 1 : 0); # nautical twilight
}
if (defined($selectedProperties{SunAz})) {
$timeProperties{SunAz} = \@azimuths;
}
if (defined($selectedProperties{SunEl})) {
$timeProperties{SunEl} = \@elevations;
}
if (defined($selectedProperties{SunUp})) {
$timeProperties{SunUp} = \@sunups;
}
}
$forecast{timeProperties} = \%timeProperties;
}
$forecast{header} = \%header;
@ -1316,7 +1486,7 @@ sub UpdateForecast($$)
my $forecastTime = $timestamps->[$i];
my ($fcSec, $fcMin, $fcHour, $fcMday, $fcMon, $fcYear, $fcWday, $fcYday, $fcIsdst) = Localtime($hash, $forecastTime);
my $forecastDate = Timelocal($hash, 0, 0, 0, $fcMday, $fcMon, $fcYear);
$relativeDay = sprintf("%.0f", ($forecastDate - $today)/(24*60*60)); # Perl equivalent for round()
$relativeDay = sprintf("%.0f", ($forecastDate - $today)/(24*60*60)); # round()
if ($relativeDay > $forecastDays) {
# max. number of days processed, done
last;
@ -1352,7 +1522,7 @@ sub UpdateForecast($$)
if ($forecastPropertyType == 1) {
$value -= 273.15; # K -> °C
if (length($value) > 6) {
$value = sprintf('%0.2f', $value); # round to compensate floating point granularity
$value = sprintf('%0.2f', $value); # round(2) to compensate floating point granularity
}
}
elsif ($forecastPropertyType == 2) {
@ -1388,7 +1558,7 @@ sub UpdateForecast($$)
if ($relativeDay >= 0 && $daysAvailable > $relativeDay + 1) {
::Log3 $name, 5, "$name: deleting days with index " . ($relativeDay + 1) . " to " . ($daysAvailable - 1);
for (my $d=($relativeDay + 1); $d<$daysAvailable; $d++) {
::CommandDeleteReading(undef, "$name fc".$d."_.*");
::CommandDeleteReading(undef, "$name ^fc".$d."_.*");
}
}
@ -1860,7 +2030,7 @@ sub UpdateAlerts($$)
my $name = $hash->{NAME};
# delete existing alert readings
::CommandDeleteReading(undef, "$name a_.*");
::CommandDeleteReading(undef, "$name ^(?!a_count|a_state|a_time)a_.*");
::readingsBeginUpdate($hash);
@ -1894,6 +2064,14 @@ sub UpdateAlerts($$)
::readingsBulkUpdate($hash, 'a_state', 'updated');
}
# prepare processing
my $alertExcludeEvents = ::AttrVal($name, 'alertExcludeEvents', undef);
my @excludeEventsList = split(',', $alertExcludeEvents) if (defined($alertExcludeEvents));
foreach my $excludeEvent (@excludeEventsList) {
$excludeEvent =~ s/^\s+|\s+$//g; # trim
}
my %excludeEvents = map { $_ => 1 } @excludeEventsList;
# order alerts by onset
my $alerts = $alertsData[$communeUnion];
my @identifiers = sort { $alerts->{$a}->{onset} <=> $alerts->{$b}->{onset} } keys(%{$alerts});
@ -1902,8 +2080,8 @@ sub UpdateAlerts($$)
# find alert for selected warncell
my $areaIndex = 0;
foreach my $wcId (@{$alert->{warncellid}}) {
if ($wcId == $warncellId) {
# alert found, create readings
if ($wcId == $warncellId && !(lc($alert->{severity}) eq 'minor' && defined($excludeEvents{$alert->{eventCode}}))) {
# alert found that is not on the exclude list, create readings
my $prefix = 'a_'.$index.'_';
::readingsBulkUpdate($hash, $prefix.'category', $alert->{category});
::readingsBulkUpdate($hash, $prefix.'event', $alert->{eventCode});
@ -1974,8 +2152,8 @@ sub DWD_OpenData_Initialize($) {
$hash->{GetFn} = 'DWD_OpenData::Get';
$hash->{AttrList} = 'disable:0,1 '
.'forecastStation forecastDays forecastProperties forecastResolution:3,6 forecastWW2Text:0,1 '
.'alertArea alertLanguage:DE,EN '
.'forecastStation forecastDays forecastProperties forecastResolution:1,3,6 forecastWW2Text:0,1 '
.'alertArea alertLanguage:DE,EN alertExcludeEvents '
.'timezone '
.$readingFnAttributes;
}
@ -1988,6 +2166,22 @@ sub DWD_OpenData_Initialize($) {
#
# CHANGES
#
# 23.02.2019 (version 1.13.0) jensb
# feature: new sun position readings SunAz, SunEl and SunUp
#
# 10.02.2019 (version 1.12.3) jensb
# feature: do not delete readings a_count, a_state, a_time when updating alerts
#
# 28.12.2018 (version 1.12.2) jensb
# bugfix: modified regexp to delete forecast readings on attribute change
#
# 23.12.2018 (version 1.12.1) jensb
# feature: new attribute alertExcludeEvents
# feature: delete forecast readings if attribute forecastResolution or forecastStation are changed
#
# 20.12.2018 (version 1.12.0) jensb
# feature: enable 1h forecast resolution
#
# 02.12.2018 (version 1.11.0) jensb
# feature: async processing of forecast enhanced (HttpUtils_NonblockingGet replaced by BlockingCall) to further unload FHEM process
# feature: staggered update of forecast and alert to spread load
@ -2073,7 +2267,7 @@ sub DWD_OpenData_Initialize($) {
<ul>
The Deutsche Wetterdienst (DWD) provides public weather related data via its <a href="https://www.dwd.de/DE/leistungen/opendata/opendata.html">Open Data Server</a>. Any usage of the service and the data provided by the DWD is subject to the usage conditions on the Open Data Server webpage. An overview of the available content can be found at <a href="https://www.dwd.de/DE/leistungen/opendata/help/inhalt_allgemein/opendata_content_de_en_xls.xls">OpenData_weather_content.xls</a>. <br><br>
This modules provides two elements of the available data:
This module provides two elements of the available data:
<ul> <br>
<li>weather forecasts:
<a href="https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_L/single_stations/">Total lists of local forecasts of WMO, national and interpolated stations, all variables, 3, 9, 15, 21 UTC</a>. More than 70 properties are available for worldwide POIs and the German DWD network. This data typically spans 10 days and is updated by the DWD every 6 hours.<br><br>
@ -2155,18 +2349,21 @@ sub DWD_OpenData_Initialize($) {
<ul> <br>
<li>forecastStation &lt;station code&gt;, default: none<br>
Setting forecastStation enables automatic updates every hour.
The station code is either a 5 digit WMO station code or an alphanumeric DWD station code from the <a href="https://www.dwd.de/DE/leistungen/met_verfahren_mosmix/mosmix_stationskatalog.pdf">MOSMIX station catalogue</a>.
The station code is either a 5 digit WMO station code or an alphanumeric DWD station code from the <a href="https://www.dwd.de/DE/leistungen/met_verfahren_mosmix/mosmix_stationskatalog.pdf">MOSMIX station catalogue</a>.<br>
Note: When value is changed all existing forecast readings will be deleted.
</li><br>
<li>forecastDays &lt;n&gt;, default: 6<br>
Limits number of forecast days. Setting 0 will still provide forecast data for today. The maximum value is 9 (for today and 9 future days).
</li><br>
<li>forecastResolution {3|6}, default: 6 h<br>
Time resolution (number of hours between 2 samples).
<li>forecastResolution {1|3|6}, default: 6 h<br>
Time resolution (number of hours between 2 samples).<br>
Note: When value is changed all existing forecast readings will be deleted.
</li><br>
<li>forecastProperties [&lt;p1&gt;[,&lt;p2&gt;]...] , default: Tx, Tn, Tg, TTT, DD, FX1, Neff, RR6c, RRhc, Rh00, ww<br>
A list of the properties available can be found <a href="https://opendata.dwd.de/weather/lib/MetElementDefinition.xml">here</a>.
If you remove a property from the list existing readings must be deleted manually in continuous mode.<br>
Note: Not all properties are available for all stations and for all hours.
A list of the properties available can be found <a href="https://opendata.dwd.de/weather/lib/MetElementDefinition.xml">here</a>.<br>
Notes:<br>
- Not all properties are available for all stations and for all hours.<br>
- If you remove a property from the list then already existing readings must be deleted manually in continuous mode.<br>
</li><br>
<li>forecastWW2Text {0|1}, default: 0<br>
Create additional wwd readings containing the weather code as a descriptive text in German language.
@ -2176,11 +2373,15 @@ sub DWD_OpenData_Initialize($) {
<b>alert</b> related:
<ul> <br>
<li>alertArea &lt;warncell id&gt;, default: none<br>
Setting alertArea enables automatic updates of the alerts cache every 15 minutes.
Setting alertArea enables automatic updates of the alerts cache every 15 minutes.<br>
A warncell id is a 9 digit numeric value from the <a href="https://www.dwd.de/DE/leistungen/opendata/help/warnungen/cap_warncellids_csv.csv">Warncell-IDs for CAP alerts catalogue</a>. Supported ids start with 8 (communeunion), 1 and 9 (district) or 5 (coast). To verify that alerts are provided for the warncell id you selected you should consult another source, wait for an alert situation and compare.
</li>
<li>alertLanguage [DE|EN], default: DE<br>
Language of descriptive alert properties.</a>.
Language of descriptive alert properties.
</li>
<li>alertExcludeEvents &lt;event code&gt;, default: none<br>
Comma separated list of numeric events codes for which no alerts should be created.<br>
Only minor alerts may be suppressed. Use at your own risk!
</li>
</ul> <br><br>
@ -2197,18 +2398,18 @@ sub DWD_OpenData_Initialize($) {
<ul>
<li>day - relative day (0 .. 9) based on the timezone attribute where 0 is today</li><br>
<li>sample - relative time (0 .. 3 or 7) equivalent to multiples of 6 or 3 hours UTC depending on the forecastHours attribute</li><br>
<li>sample - relative time (0 .. 3, 7 or 23) equivalent to multiples of 6, 3 or 1 hours UTC depending on the <code>forecastResolution</code> attribute</li><br>
<li>day properties (typically for 06:00 station time, see raw data of station for time relation)
<li>day properties (typically for 06:00 station time, see raw data of station for actual time relation)
<ul>
<li>date - date based on the timezone attribute</li>
<li>weekday - abbreviated weekday based on the timezone attribute in the language of your FHEM system</li>
<li>Tn [°C] - minimum temperature of previous 24 hours</li>
<li>Tx [°C] - maximum temperature of previous 24 hours (typically for 18:00 station time)</li>
<li>Tn [°C] - minimum temperature of previous 12 hours</li>
<li>Tx [°C] - maximum temperature of previous 12 hours (typically at 18:00 station time)</li>
<li>Tm [°C] - average temperature of previous 24 hours</li>
<li>Tg [°C] - minimum temperature 5 cm above ground of previous 24 hours</li>
<li>Tg [°C] - minimum temperature 5 cm above ground of previous 12 hours</li>
<li>PEvap [kg/m2] - evapotranspiration of previous 24 hours</li>
<li>SunD [s] - total sunshine duration of previous 24 hours</li>
<li>SunD [s] - total sunshine duration of previous day</li>
</ul>
</li><br>
@ -2220,6 +2421,10 @@ sub DWD_OpenData_Initialize($) {
<li>DD [°] - average wind direction 10 m above ground</li>
<li>FF [km/h] - average wind speed 10 m above ground</li>
<li>FX1 [km/h] - maximum wind speed in the last hour</li>
<li>SunD1 [s] - sunshine duration in the last hour</li>
<li>SunD3 [s] - sunshine duration in the last 3 hours</li>
<li>RR1c [kg/m2] - precipitation amount in the last hour</li>
<li>RR3c [kg/m2] - precipitation amount in the last 3 hours</li>
<li>RR6c [kg/m2] - precipitation amount in the last 6 hours</li>
<li>R600 [%] - probability of rain in the last 6 hours</li>
<li>RRhc [kg/m2] - precipitation amount in the last 12 hours</li>
@ -2232,10 +2437,18 @@ sub DWD_OpenData_Initialize($) {
<li>Neff [%] - effective cloud cover</li>
<li>Nl [%] - lower level cloud cover below 2000 m</li>
<li>Nm [%] - medium level cloud cover below 7000 m</li>
<li>Nh [%] - high level cloud cover obove 7000 m</li>
<li>Nh [%] - high level cloud cover above 7000 m</li>
<li>PPPP [hPa] - pressure equivalent at sea level</li>
</ul>
</li>
<li>extra hour properties (not provided by the DWD but calculated by the FHEM module)
<ul>
<li>SunAz [°] - sun azimuth</li>
<li>SunEl [°] - sun elevation</li>
<li>SunUp - sun up (0/1) based on nautical twilight (-12 °)</li>
</ul>
</li>
</ul> <br>
Additionally there are global forecast readings:
@ -2244,14 +2457,12 @@ sub DWD_OpenData_Initialize($) {
<li>fc_state - state of the last forecast update, possible values are 'updated' and 'error: ...'</li>
<li>fc_station - forecast station code (WMO or DWD)</li>
<li>fc_description - station description</li>
<li>fc_coordinates - world coordinat 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_copyright - legal information, must be displayed with forecast data, see DWD usage conditions</li>
</ul>
</ul> <br>
Note that depending on your device configuration each forecast consists of quite a lot of readings and each reading update will cause a FHEM event that needs to be processed. Depending on your hardware and your FHEM configuration this will take several hundred milliseconds. If you need to improve overall performance you can limit the number of readings created by setting the attribute <code>forecastProperties</code> and reduce the event processing overhead by setting the attribute <code>event-on-update-reading</code> to a small list of important reading (e.g. <code>to state,fc_time,a_time</code>). <br><br>
The <b>alert</b> readings are ordered by onset and are build like this: <br><br>
<code>a_&lt;index&gt;_&lt;property&gt;</code> <br><br>
@ -2298,7 +2509,11 @@ sub DWD_OpenData_Initialize($) {
Note that all alert readings are completely replaced and reindexed with each update! <br><br>
Further information regarding the alert properties can be found in the documentation of the <a href="https://www.dwd.de/DE/leistungen/opendata/help/warnungen/cap_dwd_profile_de_pdf.pdf">CAP DWS Profile</a>. <br>
Further information regarding the alert properties can be found in the documentation of the <a href="https://www.dwd.de/DE/leistungen/opendata/help/warnungen/cap_dwd_profile_de_pdf.pdf">CAP DWS Profile</a>. <br><br>
<b>Performance</b> <br><br>
Note that depending on your device configuration each forecast consists of quite a lot of readings and each reading update will cause a FHEM event that needs to be processed. Depending on your hardware and your FHEM configuration this will take several hundred milliseconds. If you need to improve overall performance you can limit the number of readings created by setting a) the attribute <code>forecastProperties</code> to the ones you actually use, b) the attribute <code>forecastResolution</code> to the highest value suitable for your purposes and c) the attribute <code>forecastDays</code> to the lowest number suitable for your purposes. To further reduce the event processing overhead you can set the attribute <code>event-on-update-reading</code> to a small list of important reading that really need events (e.g. <code>state,fc_state,a_state</code>). For almost the same reason be selective when creating a log device. If you use wildcards for all readings without filtering either at the source device with <a href="#readingFnAttributes">readingFnAttributes</a> or at the destination device with a regexp you will get significant extra file IO when the readings are updated and quite a lot of data. <br>
</ul> <br>