mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-01 13:29:26 +00:00
c7a3f4121a
git-svn-id: https://svn.fhem.de/fhem/trunk@14721 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2157 lines
66 KiB
Perl
2157 lines
66 KiB
Perl
# $Id: 55_GDS.pm 13494 2017-02-23 18:25:48Z betateilchen $
|
||
|
||
# copyright and license informations
|
||
=pod
|
||
###################################################################################################
|
||
#
|
||
# 55_GDS.pm
|
||
#
|
||
# An FHEM Perl module to retrieve data from "Deutscher Wetterdienst"
|
||
#
|
||
# Copyright: betateilchen ®
|
||
#
|
||
# includes: some patches provided by jensb
|
||
# forecasts provided by jensb
|
||
#
|
||
# This file is part of fhem.
|
||
#
|
||
# Fhem is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU General Public License as published by
|
||
# the Free Software Foundation, either version 2 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# Fhem is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
|
||
#
|
||
###################################################################################################
|
||
=cut
|
||
|
||
package main;
|
||
|
||
use strict;
|
||
use warnings;
|
||
use feature qw/switch/;
|
||
|
||
use Blocking;
|
||
#use Archive::Extract;
|
||
use Net::FTP;
|
||
use XML::Simple;
|
||
|
||
#use Data::Dumper;
|
||
|
||
eval "use GDSweblink";
|
||
|
||
no if $] >= 5.017011, warnings => 'experimental';
|
||
|
||
my ($bulaList, $cmapList, %rmapList, $fmapList, %bula2bulaShort,
|
||
%bulaShort2dwd, %dwd2Dir, %dwd2Name, $alertsXml, %capCityHash,
|
||
%capCellHash, $sList, $aList, $fList, $fcList, $fcmapList, $tempDir, @weekdays);
|
||
|
||
my %allForecastData;
|
||
|
||
###################################################################################################
|
||
#
|
||
# Main routines
|
||
|
||
sub GDS_Initialize($) {
|
||
my ($hash) = @_;
|
||
my $name = $hash->{NAME};
|
||
|
||
return "This module must not be used on microso... platforms!" if($^O =~ m/Win/);
|
||
|
||
$hash->{DefFn} = "GDS_Define";
|
||
$hash->{UndefFn} = "GDS_Undef";
|
||
$hash->{DeleteFn} = "GDS_Delete";
|
||
$hash->{GetFn} = "GDS_Get";
|
||
$hash->{SetFn} = "GDS_Set";
|
||
$hash->{RenameFn} = "GDS_Rename";
|
||
$hash->{ShutdownFn} = "GDS_Shutdown";
|
||
# $hash->{NotifyFn} = "GDS_Notify";
|
||
$hash->{NOTIFYDEV} = "global";
|
||
$hash->{AttrFn} = "GDS_Attr";
|
||
|
||
no warnings 'qw';
|
||
my @attrList = qw(
|
||
disable:0,1
|
||
gdsAll:0,1
|
||
gdsDebug:0,1
|
||
gdsFwName
|
||
gdsFwType:0,1,2,3,4,5,6,7
|
||
gdsHideFiles:0,1
|
||
gdsLong:0,1
|
||
gdsPassiveFtp:1,0
|
||
gdsPolygon:0,1
|
||
gdsSetForecast
|
||
gdsUseAlerts:0,1
|
||
gdsUseForecasts:0,1
|
||
);
|
||
use warnings 'qw';
|
||
$hash->{AttrList} = join(" ", @attrList);
|
||
$hash->{AttrList} .= " $readingFnAttributes";
|
||
|
||
$tempDir = "/tmp/";
|
||
_fillMappingTables($hash);
|
||
|
||
}
|
||
sub _fillMappingTables($){
|
||
my ($hash) = @_;
|
||
|
||
$tempDir = "/tmp/";
|
||
|
||
$aList = "no_data";
|
||
$sList = $aList;
|
||
$fList = $aList;
|
||
$fcList = $aList;
|
||
|
||
$bulaList = "Baden-Württemberg,Bayern,Berlin,Brandenburg,Bremen,".
|
||
"Hamburg,Hessen,Mecklenburg-Vorpommern,Niedersachsen,".
|
||
"Nordrhein-Westfalen,Rheinland-Pfalz,Saarland,Sachsen,".
|
||
"Sachsen-Anhalt,Schleswig-Holstein,Thüringen";
|
||
|
||
$cmapList = "Deutschland,Mitte,Nordost,Nordwest,Ost,Suedost,Suedwest,West";
|
||
|
||
%rmapList = (
|
||
Deutschland => "",
|
||
Mitte => "central/",
|
||
Nordost => "northeast/",
|
||
Nordwest => "northwest/",
|
||
Ost => "east/",
|
||
Suedost => "southeast/",
|
||
Suedwest => "southwest/",
|
||
West => "west/");
|
||
|
||
$fmapList = "Deutschland_heute_frueh,Deutschland_heute_mittag,Deutschland_heute_spaet,Deutschland_heute_nacht,".
|
||
"Deutschland_morgen_frueh,Deutschland_morgen_spaet,".
|
||
"Deutschland_ueberm_frueh,Deutschland_ueberm_spaet,".
|
||
"Deutschland_tag4_frueh,Deutschland_tag4_spaet,".
|
||
"Mitte_heute_frueh,Mitte_heute_mittag,Mitte_heute_spaet,Mitte_heute_nacht,".
|
||
"Mitte_morgen_frueh,Mitte_morgen_spaet,".
|
||
"Mitte_ueberm_frueh,Mitte_ueberm_spaet,".
|
||
"Mitte_tag4_frueh,Mitte_tag4_spaet,".
|
||
"Nordost_heute_frueh,Nordost_heute_mittag,Nordost_heute_spaet,Nordost_heute_nacht,".
|
||
"Nordost_morgen_frueh,Nordost_morgen_spaet,".
|
||
"Nordost_ueberm_frueh,Nordost_ueberm_spaet,".
|
||
"Nordost_tag4_frueh,Nordost_tag4_spaet,".
|
||
"Nordwest_heute_frueh,Nordwest_heute_mittag,Nordwest_heute_spaet,Nordwest_heute_nacht,".
|
||
"Nordwest_morgen_frueh,Nordwest_morgen_spaet,".
|
||
"Nordwest_ueberm_frueh,Nordwest_ueberm_spaet,".
|
||
"Nordwest_tag4_frueh,Nordwest_tag4_spaet,".
|
||
"Ost_heute_frueh,Ost_heute_mittag,Ost_heute_spaet,Ost_heute_nacht,".
|
||
"Ost_morgen_frueh,Ost_morgen_spaet,".
|
||
"Ost_ueberm_frueh,Ost_ueberm_spaet,".
|
||
"Ost_tag4_frueh,Ost_tag4_spaet,".
|
||
"Suedost_heute_frueh,Suedost_heute_mittag,Suedost_heute_spaet,Suedost_heute_nacht,".
|
||
"Suedost_morgen_frueh,Suedost_morgen_spaet,".
|
||
"Suedost_ueberm_frueh,Suedost_ueberm_spaet,".
|
||
"Suedost_tag4_frueh,Suedost_tag4_spaet,".
|
||
"Suedwest_heute_frueh,Suedwest_heute_mittag,Suedwest_heute_spaet,Suedwest_heute_nacht,".
|
||
"Suedwest_morgen_frueh,Suedwest_morgen_spaet,".
|
||
"Suedwest_ueberm_frueh,Suedwest_ueberm_spaet,".
|
||
"Suedwest_tag4_frueh,Suedwest_tag4_spaet,".
|
||
"West_heute_frueh,West_heute_mittag,West_heute_spaet,West_heute_nacht,".
|
||
"West_morgen_frueh,West_morgen_spaet,".
|
||
"West_ueberm_frueh,West_ueberm_spaet,".
|
||
"West_tag4_frueh,West_tag4_spaet";
|
||
|
||
$fcmapList = "Deutschland_frueh,Deutschland_mittag,Deutschland_spaet,Deutschland_nacht,".
|
||
"Deutschland_morgen_frueh,Deutschland_morgen_spaet,".
|
||
"Deutschland_uebermorgen_frueh,Deutschland_uebermorgen_spaet,".
|
||
"Deutschland_Tag4_frueh,Deutschland_Tag4_spaet,".
|
||
"Mitte_frueh,Mitte_mittag,Mitte_spaet,Mitte_nacht,".
|
||
"Mitte_morgen_frueh,Mitte_morgen_spaet,".
|
||
"Mitte_uebermorgen_frueh,Mitte_uebermorgen_spaet,".
|
||
"Mitte_Tag4_frueh,Mitte_Tag4_spaet,".
|
||
"Nordost_frueh,Nordost_mittag,Nordost_spaet,Nordost_nacht,".
|
||
"Nordost_morgen_frueh,Nordost_morgen_spaet,".
|
||
"Nordost_uebermorgen_frueh,Nordost_uebermorgen_spaet,".
|
||
"Nordost_Tag4_frueh,Nordost_Tag4_spaet,".
|
||
"Nordwest_frueh,Nordwest_mittag,Nordwest_spaet,Nordwest_nacht,".
|
||
"Nordwest_morgen_frueh,Nordwest_morgen_spaet,".
|
||
"Nordwest_uebermorgen_frueh,Nordwest_uebermorgen_spaet,".
|
||
"Nordwest_Tag4_frueh,Nordwest_Tag4_spaet,".
|
||
"Ost_frueh,Ost_mittag,Ost_spaet,Ost_nacht,".
|
||
"Ost_morgen_frueh,Ost_morgen_spaet,".
|
||
"Ost_uebermorgen_frueh,Ost_uebermorgen_spaet,".
|
||
"Ost_Tag4_frueh,Ost_Tag4_spaet,".
|
||
"Suedost_frueh,Suedost_mittag,Suedost_spaet,Suedost_nacht,".
|
||
"Suedost_morgen_frueh,Suedost_morgen_spaet,".
|
||
"Suedost_uebermorgen_frueh,Suedost_uebermorgen_spaet,".
|
||
"Suedost_Tag4_frueh,Suedost_Tag4_spaet,".
|
||
"Suedwest_frueh,Suedwest_mittag,Suedwest_spaet,Suedwest_nacht,".
|
||
"Suedwest_morgen_frueh,Suedwest_morgen_spaet,".
|
||
"Suedwest_uebermorgen_frueh,Suedwest_uebermorgen_spaet,".
|
||
"Suedwest_Tag4_frueh,Suedwest_Tag4_spaet,".
|
||
"West_frueh,West_mittag,West_spaet,West_nacht,".
|
||
"West_morgen_frueh,West_morgen_spaet,".
|
||
"West_uebermorgen_frueh,West_uebermorgen_spaet,".
|
||
"West_Tag4_frueh,West_Tag4_spaet";
|
||
|
||
#
|
||
# Bundesländer den entsprechenden Dienststellen zuordnen
|
||
#
|
||
%bula2bulaShort = (
|
||
"baden-württemberg" => "bw",
|
||
"bayern" => "by",
|
||
"berlin" => "be",
|
||
"brandenburg" => "bb",
|
||
"bremen" => "hb",
|
||
"hamburg" => "hh",
|
||
"hessen" => "he",
|
||
"mecklenburg-vorpommern" => "mv",
|
||
"niedersachsen" => "ni",
|
||
"nordrhein-westfalen" => "nw",
|
||
"rheinland-pfalz" => "rp",
|
||
"saarland" => "sl",
|
||
"sachsen" => "sn",
|
||
"sachsen-anhalt" => "st",
|
||
"schleswig-holstein" => "sh",
|
||
"thüringen" => "th",
|
||
"deutschland" => "xde",
|
||
"bodensee" => "xbo" );
|
||
|
||
%bulaShort2dwd = (
|
||
bw => "DWSG",
|
||
by => "DWMG",
|
||
be => "DWPG",
|
||
bb => "DWPG",
|
||
hb => "DWHG",
|
||
hh => "DWHH",
|
||
he => "DWOH",
|
||
mv => "DWPH",
|
||
ni => "DWHG",
|
||
nw => "DWEH",
|
||
rp => "DWOI",
|
||
sl => "DWOI",
|
||
sn => "DWLG",
|
||
st => "DWLH",
|
||
sh => "DWHH",
|
||
th => "DWLI",
|
||
xde => "xde",
|
||
xbo => "xbo" );
|
||
|
||
#
|
||
# Dienststellen den entsprechenden Serververzeichnissen zuordnen
|
||
#
|
||
%dwd2Dir = (
|
||
DWSG => "SU", # Stuttgart
|
||
DWMG => "MS", # München
|
||
DWPG => "PD", # Potsdam
|
||
DWHG => "HA", # Hamburg
|
||
DWHH => "HA", # Hamburg
|
||
DWOH => "OF", # Offenbach
|
||
DWPH => "PD", # Potsdam
|
||
DWHG => "HA", # Hamburg
|
||
DWEH => "EM", # Essen
|
||
DWOI => "OF", # Offenbach
|
||
DWLG => "LZ", # Leipzig
|
||
DWLH => "LZ", # Leipzig
|
||
DWLI => "LZ", # Leipzig
|
||
DWHC => "HA", # Hamburg
|
||
DWHB => "HA", # Hamburg
|
||
DWPD => "PD", # Potsdam
|
||
DWRW => "PD", # Potsdam
|
||
DWEM => "EM", # Essen
|
||
LSAX => "LZ", # Leipzig
|
||
LSNX => "LZ", # Leipzig
|
||
THLX => "LZ", # Leipzig
|
||
DWOF => "OF", # Offenbach
|
||
DWTR => "OF", # Offenbach
|
||
DWSU => "SU", # Stuttgart
|
||
DWMS => "MS", # München
|
||
xde => "D",
|
||
xbo => "Bodensee");
|
||
# ???? => "FG" # Freiburg);
|
||
|
||
%dwd2Name = (
|
||
EM => "Essen",
|
||
FG => "Freiburg",
|
||
HA => "Hamburg",
|
||
LZ => "Leipzig",
|
||
MS => "München",
|
||
OF => "Offenbach",
|
||
PD => "Potsdam",
|
||
SU => "Stuttgart");
|
||
|
||
|
||
# German weekdays
|
||
@weekdays = ("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa");
|
||
|
||
return;
|
||
}
|
||
|
||
sub GDS_Define($$$) {
|
||
my ($hash, $def) = @_;
|
||
my $name = $hash->{NAME};
|
||
my @a = split("[ \t][ \t]*", $def);
|
||
|
||
my $skuser = getKeyValue($name."_user");
|
||
my $skpass = getKeyValue($name."_pass");
|
||
my $skhost = getKeyValue($name."_host");
|
||
|
||
unless(defined($skuser) && defined($skpass)) {
|
||
return "syntax: define <name> GDS <username> <password> [<host>]" if(int(@a) < 4 );
|
||
}
|
||
|
||
$hash->{helper}{URL} = defined($a[4]) ? $a[4] : "ftp-outgoing2.dwd.de";
|
||
$hash->{helper}{INTERVAL} = 1200;
|
||
|
||
setKeyValue($name."_user",$a[2]) unless(defined($skuser));
|
||
setKeyValue($name."_pass",$a[3]) unless(defined($skpass));
|
||
setKeyValue($name."_host",$hash->{helper}{URL}) unless(defined($skhost));
|
||
|
||
$hash->{DEF} = undef;
|
||
|
||
Log3($name, 4, "GDS $name: created");
|
||
Log3($name, 4, "GDS $name: tempDir=".$tempDir);
|
||
|
||
_GDS_addExtension("GDS_CGI","gds","GDS Files");
|
||
# $hash->{firstrun} = 1;
|
||
# retrieveData($hash,'conditions') unless (time > 1457996400);
|
||
# delete $hash->{firstrun};
|
||
readingsSingleUpdate($hash, 'state', 'active',1);
|
||
|
||
return undef;
|
||
}
|
||
|
||
sub GDS_Undef($$) {
|
||
my ($hash, $arg) = @_;
|
||
my $name = $hash->{NAME};
|
||
RemoveInternalTimer($hash);
|
||
my $url = '/gds';
|
||
delete $data{FWEXT}{$url} if int(devspec2array('TYPE=GDS')) == 1;
|
||
return undef;
|
||
}
|
||
|
||
sub GDS_Delete() {
|
||
my ($hash, $arg) = @_;
|
||
my $name = $hash->{NAME};
|
||
setKeyValue($name."_user",undef);
|
||
setKeyValue($name."_pass",undef);
|
||
setKeyValue($name."_host",undef);
|
||
}
|
||
|
||
sub GDS_Rename() {
|
||
my ($new,$old) = @_;
|
||
setKeyValue($new."_user",getKeyValue($old."_user"));
|
||
setKeyValue($new."_pass",getKeyValue($old."_pass"));
|
||
setKeyValue($new."_host",getKeyValue($old."_host"));
|
||
|
||
setKeyValue($old."_user",undef);
|
||
setKeyValue($old."_pass",undef);
|
||
setKeyValue($old."_host",undef);
|
||
return undef;
|
||
}
|
||
|
||
sub GDS_Shutdown($) {
|
||
my ($hash) = @_;
|
||
my $name = $hash->{NAME};
|
||
RemoveInternalTimer($hash);
|
||
Log3 ($name,4,"GDS $name: shutdown requested");
|
||
return undef;
|
||
}
|
||
|
||
sub GDS_Notify ($$) {
|
||
my ($hash,$dev) = @_;
|
||
my $name = $hash->{NAME};
|
||
return if($dev->{NAME} ne "global");
|
||
my $type = $dev->{CHANGED}[0];
|
||
return unless (grep(m/^INITIALIZED/, $type));
|
||
|
||
$aList = "disabled_by_attribute" unless AttrVal($name,'gdsUseAlerts',0);
|
||
$fList = "disabled_by_attribute" unless AttrVal($name,'gdsUseForecasts',0);
|
||
$fcmapList = "disabled_by_attribute" unless AttrVal($name,'gdsUseForecasts',0);
|
||
|
||
GDS_GetUpdate($hash);
|
||
|
||
return undef;
|
||
}
|
||
|
||
sub GDS_Set($@) {
|
||
my ($hash, @a) = @_;
|
||
my $name = $hash->{NAME};
|
||
my $usage = "Unknown argument, choose one of ".
|
||
"clear:alerts,conditions,forecasts,all ".
|
||
"forecasts:$fList ".
|
||
"help:noArg ".
|
||
"update:noArg "; ;
|
||
|
||
my $command = lc($a[1]);
|
||
my $parameter = $a[2] if(defined($a[2]));
|
||
|
||
my ($result, $next);
|
||
|
||
return $usage if $command eq '?';
|
||
|
||
if(IsDisabled($name)) {
|
||
readingsSingleUpdate($hash, 'state', 'disabled', 0);
|
||
return "GDS $name is disabled. Aborting..." if IsDisabled($name);
|
||
}
|
||
|
||
readingsSingleUpdate($hash, 'state', 'active', 0);
|
||
|
||
given($command) {
|
||
when("clear"){
|
||
CommandDeleteReading(undef, "$name a_.*")
|
||
if(defined($parameter) && ($parameter eq "all" || $parameter eq "alerts"));
|
||
CommandDeleteReading(undef, "$name c_.*")
|
||
if(defined($parameter) && ($parameter eq "all" || $parameter eq "conditions"));
|
||
CommandDeleteReading(undef, "$name g_.*")
|
||
if(defined($parameter) && ($parameter eq "all" || $parameter eq "conditions"));
|
||
CommandDeleteReading(undef, "$name fc.?_.*")
|
||
if(defined($parameter) && ($parameter eq "all" || $parameter eq "forecasts"));
|
||
}
|
||
|
||
when("help"){
|
||
$result = setHelp();
|
||
break;
|
||
}
|
||
|
||
when("update"){
|
||
RemoveInternalTimer($hash);
|
||
GDS_GetUpdate($hash,'set update');
|
||
break;
|
||
}
|
||
|
||
when("forecasts"){
|
||
return "Error: Forecasts disabled by attribute." unless AttrVal($name,'gdsUseForecasts',0);
|
||
CommandDeleteReading(undef, "$name fc.*") if($parameter ne AttrVal($name,'gdsSetForecast',''));
|
||
$attr{$name}{gdsSetForecast} = $parameter;
|
||
GDS_GetUpdate($hash,'set forecasts');
|
||
break;
|
||
}
|
||
|
||
default { return $usage; };
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
sub GDS_Get($@) {
|
||
my ($hash, @a) = @_;
|
||
my $command = lc($a[1]);
|
||
my $parameter = $a[2] if(defined($a[2]));
|
||
my $name = $hash->{NAME};
|
||
|
||
my $usage = "Unknown argument $command, choose one of help:noArg rereadcfg:noArg ".
|
||
"list:capstations ".
|
||
"alerts:".$aList." ".
|
||
"conditionsmap:".$cmapList." ".
|
||
"forecasts:".$fcList." ".
|
||
"forecastsmap:".$fmapList." ".
|
||
"headlines ".
|
||
"radarmap:".$cmapList." ".
|
||
"warningsmap:"."Deutschland,Bodensee,".$bulaList." ".
|
||
"warnings:".$bulaList;
|
||
|
||
return $usage if $command eq '?';
|
||
|
||
if(IsDisabled($name)) {
|
||
readingsSingleUpdate($hash, 'state', 'disabled', 0);
|
||
return "GDS $name is disabled. Aborting..." if IsDisabled($name);
|
||
}
|
||
|
||
readingsSingleUpdate($hash, 'state', 'active', 0);
|
||
my $_gdsAll = AttrVal($name,"gdsAll", 0);
|
||
my $gdsDebug = AttrVal($name,"gdsDebug", 0);
|
||
|
||
my ($result, @datensatz, $found);
|
||
|
||
given($command) {
|
||
|
||
when("conditionsmap"){
|
||
# retrieve map: current conditions
|
||
$hash->{file}{dir} = "gds/specials/observations/maps/germany/";
|
||
$hash->{file}{dwd} = $parameter."*";
|
||
$hash->{file}{target} = $tempDir.$name."_conditionsmap.jpg";
|
||
retrieveData($hash,'FILE');
|
||
break;
|
||
}
|
||
|
||
when("forecastsmap"){
|
||
# retrieve map: forecasts
|
||
$hash->{file}{dir} = "gds/specials/forecasts/maps/germany/";
|
||
$hash->{file}{dwd} = $parameter."*";
|
||
$hash->{file}{target} = $tempDir.$name."_forecastsmap.jpg";
|
||
retrieveData($hash,'FILE');
|
||
break;
|
||
}
|
||
|
||
when("headlines"){
|
||
return "Error: Alerts disabled by attribute." unless AttrVal($name,'gdsUseAlerts',0);
|
||
$parameter //= "|";
|
||
return gdsAlertsHeadlines($name,$parameter);
|
||
}
|
||
|
||
when("warningsmap"){
|
||
# retrieve map: warnings
|
||
if(length($parameter) != 2){
|
||
$parameter = $bula2bulaShort{lc($parameter)};
|
||
}
|
||
$hash->{file}{dwd} = "Schilder".$dwd2Dir{$bulaShort2dwd{lc($parameter)}}.".jpg";
|
||
$hash->{file}{dir} = "gds/specials/alerts/maps/";
|
||
$hash->{file}{target} = $tempDir.$name."_warningsmap.jpg";
|
||
retrieveData($hash,'FILE');
|
||
break;
|
||
}
|
||
|
||
when("radarmap"){
|
||
# retrieve map: radar
|
||
$parameter = ucfirst($parameter);
|
||
$hash->{file}{dir} = "gds/specials/radar/".$rmapList{$parameter};
|
||
$hash->{file}{dwd} = "Webradar_".$parameter."*";
|
||
$hash->{file}{target} = $tempDir.$name."_radarmap.jpg";
|
||
retrieveData($hash,'FILE');
|
||
break;
|
||
}
|
||
|
||
when("help"){
|
||
$result = getHelp();
|
||
break;
|
||
}
|
||
|
||
when("list"){
|
||
given($parameter){
|
||
when("capstations") {
|
||
return "Error: Alerts disabled by attribute." unless AttrVal($name,'gdsUseAlerts',0);
|
||
$result = getListCapStations($hash,$parameter); }
|
||
default { $usage = "get <name> list <parameter>"; return $usage; }
|
||
}
|
||
break;
|
||
}
|
||
|
||
when("alerts"){
|
||
return "Error: Alerts disabled by attribute." unless AttrVal($name,'gdsUseAlerts',0);
|
||
|
||
if($parameter =~ y/0-9// == length($parameter)){
|
||
while ( my( $key, $val ) = each %capCellHash ) {
|
||
push @datensatz,$val if $key =~ m/^$parameter/;
|
||
}
|
||
# push @datensatz,$capCellHash{$parameter};
|
||
} else {
|
||
push @datensatz,$capCityHash{$parameter};
|
||
}
|
||
CommandDeleteReading(undef, "$name a_.*");
|
||
if($datensatz[0]){
|
||
my $anum = 0;
|
||
foreach(@datensatz) {
|
||
decodeCAPData($hash,$_,$anum);
|
||
$anum++;
|
||
};
|
||
readingsSingleUpdate($hash,'a_count',$anum,1);
|
||
} else {
|
||
$result = "Keine Warnmeldung für die gesuchte Region vorhanden.";
|
||
}
|
||
my $_gdsAll = AttrVal($name,"gdsAll", 0);
|
||
my $gdsDebug = AttrVal($name,"gdsDebug", 0);
|
||
break;
|
||
}
|
||
|
||
when("rereadcfg"){
|
||
DoTrigger($name, "REREAD", 1);
|
||
$hash->{GDS_REREAD} = int(time());
|
||
if (AttrVal($name,'gdsUseAlerts',0)) {
|
||
%capCityHash = ();
|
||
%capCellHash = ();
|
||
retrieveData($hash,'capdata');
|
||
retrieveListCapStations($hash);
|
||
}
|
||
retrieveData($hash,'forecast') if AttrVal($name,'gdsUseForecasts',0);
|
||
# GDS_GetUpdate($hash);
|
||
break;
|
||
}
|
||
|
||
when("warnings"){
|
||
my $vhdl;
|
||
$result = " VHDL30 = current | VHDL31 = weekend or holiday\n".
|
||
" VHDL32 = preliminary | VHDL33 = cancel VHDL32\n".
|
||
sepLine(31)."+".sepLine(38)."\n";
|
||
|
||
if(length($parameter) != 2){
|
||
$parameter = $bula2bulaShort{lc($parameter)};
|
||
}
|
||
my $dwd = $bulaShort2dwd{lc($parameter)};
|
||
my $dir = "gds/specials/warnings/".$dwd2Dir{$dwd}."/";
|
||
$hash->{file}{dir} = $dir;
|
||
|
||
for ($vhdl=30; $vhdl <=33; $vhdl++){
|
||
my $dwd2 = "VHDL".$vhdl."_".$dwd."*";
|
||
my $target = $tempDir.$name."_warnings_$vhdl";
|
||
unlink $target;
|
||
$hash->{file}{dwd} = $dwd2;
|
||
$hash->{file}{target} = $target;
|
||
retrieveData($hash,'FILE');
|
||
}
|
||
|
||
sleep 2;
|
||
for ($vhdl=30; $vhdl <=33; $vhdl++){
|
||
my $target = $tempDir.$name."_warnings_$vhdl";
|
||
$result .= retrieveText($hash, "warnings_$vhdl", "") if (-e $target);
|
||
$result .= "\n".sepLine(70);
|
||
}
|
||
|
||
$result .= "\n\n";
|
||
break;
|
||
}
|
||
|
||
when("forecasts"){
|
||
return "Error: Forecasts disabled by attribute." unless AttrVal($name,'gdsUseForecasts',0);
|
||
$parameter = "Daten_$parameter";
|
||
my ($k,$v,$data);
|
||
$result = sepLine(67)."\n";
|
||
|
||
# retrieve from hash
|
||
$data = undef;
|
||
while(($k, $v) = each %allForecastData){
|
||
if ($k eq $parameter) {
|
||
$data = $v;
|
||
last;
|
||
};
|
||
}
|
||
$data //= "No forecast data found.";
|
||
$data =~ s/\$/\n/g;
|
||
$result .= $data;
|
||
$result .= "\n".sepLine(67)."\n";
|
||
break;
|
||
}
|
||
|
||
default { return $usage; };
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
sub GDS_Attr(@){
|
||
my @a = @_;
|
||
my $hash = $defs{$a[1]};
|
||
my ($cmd, $name, $attrName, $attrValue) = @a;
|
||
$attrValue //= '';
|
||
my $useUpdate = 0;
|
||
|
||
given($attrName){
|
||
when("gdsUseAlerts"){
|
||
if ($attrValue == 0 || $cmd eq 'del') {
|
||
$aList = "disabled_by_attribute";
|
||
} else {
|
||
$aList = "data_retrieval_running";
|
||
retrieveData($hash,'capdata');
|
||
retrieveListCapStations($hash);
|
||
}
|
||
}
|
||
when("gdsUseForecasts"){
|
||
if ($attrValue == 0 || $cmd eq 'del') {
|
||
$fList = "disabled_by_attribute";
|
||
$fcList = "disabled_by_attribute";
|
||
} else {
|
||
$fcList = $fcmapList;
|
||
$fList = "data_retrieval_running";
|
||
$attr{$name}{$attrName} = $attrValue;
|
||
retrieveData($hash,'forecast');
|
||
$useUpdate = 1;
|
||
}
|
||
}
|
||
when("gdsHideFiles"){
|
||
my $hR = AttrVal($FW_wname,'hiddenroom','');
|
||
$hR =~ s/\,GDS.Files//g;
|
||
if($attrValue) {
|
||
$hR .= "," if(length($hR));
|
||
$hR .= "GDS Files";
|
||
}
|
||
CommandAttr(undef,"$FW_wname hiddenroom $hR");
|
||
break;
|
||
}
|
||
default {$attr{$name}{$attrName} = $attrValue;}
|
||
}
|
||
if(IsDisabled($name)) {
|
||
readingsSingleUpdate($hash, 'state', 'disabled', 0);
|
||
} else {
|
||
readingsSingleUpdate($hash, 'state', 'active', 0);
|
||
if ($useUpdate) {
|
||
RemoveInternalTimer($hash);
|
||
my $next = gettimeofday()+$hash->{helper}{INTERVAL};
|
||
InternalTimer($next, "GDS_GetUpdate", $hash, 0);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
sub GDS_GetUpdate($;$) {
|
||
my ($hash,$local) = @_;
|
||
$local //= 0;
|
||
my $name = $hash->{NAME};
|
||
|
||
RemoveInternalTimer($hash);
|
||
|
||
my $fs = AttrVal($name, "gdsSetForecast", 0);
|
||
|
||
if(IsDisabled($name)) {
|
||
readingsSingleUpdate($hash, 'state', 'disabled', 0);
|
||
Log3 ($name, 2, "GDS $name is disabled, data update cancelled.");
|
||
} else {
|
||
readingsSingleUpdate($hash, 'state', 'active', 0);
|
||
if($fs) {
|
||
retrieveData($hash,'forecast') ;
|
||
my @a;
|
||
push @a, undef;
|
||
push @a, undef;
|
||
push @a, $fs;
|
||
retrieveForecasts($hash, "fc", @a);
|
||
}
|
||
}
|
||
|
||
# schedule next update
|
||
my $next = gettimeofday()+$hash->{helper}{INTERVAL};
|
||
my $gdsAll = AttrVal($name,"gdsAll", 0);
|
||
my $gdsDebug = AttrVal($name,"gdsDebug", 0);
|
||
InternalTimer($next, "GDS_GetUpdate", $hash, 1);
|
||
readingsSingleUpdate($hash, "_nextUpdate", localtime($next), 1) if($gdsAll || $gdsDebug);
|
||
|
||
return 1;
|
||
}
|
||
|
||
###################################################################################################
|
||
#
|
||
# FWEXT implementation
|
||
|
||
sub _GDS_addExtension($$$) {
|
||
my ($func,$link,$friendlyname)= @_;
|
||
|
||
my $url = "/" . $link;
|
||
Log3(undef,4,"Register gds webservice in FWEXT");
|
||
$data{FWEXT}{$url}{FUNC} = $func;
|
||
$data{FWEXT}{$url}{LINK} = "+$link";
|
||
$data{FWEXT}{$url}{NAME} = $friendlyname;
|
||
$data{FWEXT}{$url}{FORKABLE} = 0;
|
||
}
|
||
sub GDS_CGI {
|
||
my ($request) = @_;
|
||
my ($name,$ext)= _GDS_splitRequest($request);
|
||
if(defined($name)) {
|
||
my $filename= "$tempDir/$name.$ext";
|
||
my $MIMEtype= filename2MIMEType($filename);
|
||
my @contents;
|
||
if(open(INPUTFILE, $filename)) {
|
||
binmode(INPUTFILE);
|
||
@contents= <INPUTFILE>;
|
||
close(INPUTFILE);
|
||
return("$MIMEtype; charset=utf-8", join("", @contents));
|
||
} else {
|
||
return("text/plain; charset=utf-8", "File not found: $filename");
|
||
}
|
||
} else {
|
||
return _GDS_Overview();
|
||
}
|
||
}
|
||
sub _GDS_splitRequest($) {
|
||
my ($request) = @_;
|
||
|
||
if($request =~ /^.*\/gds$/) {
|
||
# http://localhost:8083/fhem/gds2
|
||
return (undef,undef); # name, ext
|
||
} else {
|
||
my $call= $request;
|
||
$call =~ s/^.*\/gds\/([^\/]*)$/$1/;
|
||
my $name= $call;
|
||
$name =~ s/^(.*)\.(jpg)$/$1/;
|
||
my $ext= $call;
|
||
$ext =~ s/^$name\.(.*)$/$1/;
|
||
return ($name,$ext);
|
||
}
|
||
}
|
||
sub _GDS_Overview {
|
||
my ($name, $url);
|
||
my $html= __GDS_HTMLHead("GDS Overview") . "<body>\n\n";
|
||
foreach my $def (sort keys %defs) {
|
||
if($defs{$def}{TYPE} eq "GDS") {
|
||
$name = $defs{$def}{NAME};
|
||
$url = __GDS_getURL();
|
||
$html .= "$name<br>\n<ul>\n";
|
||
$html .= "<a href=\"$url/gds/$name\_conditionsmap.jpg\" target=\"_blank\">Aktuelle Wetterkarte: Wetterlage</a><br/>\n";
|
||
$html .= "<a href=\"$url/gds/$name\_forecastsmap.jpg\" target=\"_blank\">Aktuelle Wetterkarte: Vorhersage</a><br/>\n";
|
||
$html .= "<a href=\"$url/gds/$name\_warningsmap.jpg\" target=\"_blank\">Aktuelle Wetterkarte: Warnungen</a><br/>\n";
|
||
$html .= "<a href=\"$url/gds/$name\_radarmap.jpg\" target=\"_blank\">Aktuelle Wetterkarte: Radarkarte</a><br/>\n";
|
||
$html .= "</ul>\n\n";
|
||
}
|
||
}
|
||
$html.="</body>\n" . __GDS_HTMLTail();
|
||
return ("text/html; charset=utf-8", $html);
|
||
}
|
||
sub __GDS_HTMLHead($) {
|
||
my ($title) = @_;
|
||
my $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" '.
|
||
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
|
||
my $xmlns = 'xmlns="http://www.w3.org/1999/xhtml"';
|
||
my $code = "$doctype\n<html $xmlns>\n<head>\n<title>$title</title>\n</head>\n";
|
||
return $code;
|
||
}
|
||
sub __GDS_getURL {
|
||
my $proto = (AttrVal($FW_wname, 'HTTPS', 0) == 1) ? 'https' : 'http';
|
||
return $proto."://$FW_httpheader{Host}$FW_ME"; #".$FW_ME;
|
||
}
|
||
sub __GDS_HTMLTail {
|
||
return "</html>";
|
||
}
|
||
|
||
###################################################################################################
|
||
#
|
||
# Tools
|
||
|
||
sub gdsAlertsHeadlines($;$) {
|
||
my ($d,$sep) = @_;
|
||
my $text = "";
|
||
$sep = (defined($sep)) ? $sep : '|';
|
||
my $count = ReadingsVal($d,'a_count',0);
|
||
for (my $i = 0; $i < $count; $i++) {
|
||
$text .= $sep if $i;
|
||
$text .= ReadingsVal('gds','a_'.$i.'_headline','')
|
||
}
|
||
return $text;
|
||
}
|
||
|
||
sub gdsHeadlines($;$) {
|
||
my $text = "GDS error: gdsHeadlines() is deprecated. Please use gdsAlertsHeadlines()";
|
||
Log 1, $text;
|
||
return $text;
|
||
}
|
||
|
||
sub setHelp(){
|
||
return "Use one of the following commands:\n".
|
||
sepLine(35)."\n".
|
||
"set <name> clear alerts|all\n".
|
||
"set <name> forecasts <regionName>/<stationName>\n".
|
||
"set <name> help\n".
|
||
"set <name> rereadcfg\n".
|
||
"set <name> update\n";
|
||
}
|
||
|
||
sub getHelp(){
|
||
return "Use one of the following commands:\n".
|
||
sepLine(35)."\n".
|
||
"get <name> alerts <region>\n".
|
||
"get <name> forecasts <regionName>\n".
|
||
"get <name> help\n".
|
||
"get <name> list capstations|data\n".
|
||
"get <name> rereadcfg\n".
|
||
"get <name> warnings <region>\n";
|
||
}
|
||
|
||
sub sepLine($;$) {
|
||
my ($len,$c) = @_;
|
||
$c //= '-';
|
||
my ($output, $i);
|
||
for ($i=0; $i<$len; $i++) { $output .= $c; }
|
||
return $output;
|
||
}
|
||
|
||
sub _readDir($) {
|
||
my ($destinationDirectory) = @_;
|
||
eval { opendir(DIR,$destinationDirectory) or warn "$!"; };
|
||
if ($@) {
|
||
Log3(undef,1,'GDS: file system error '.$@);
|
||
return ("");
|
||
}
|
||
my @files = readdir(DIR);
|
||
close(DIR);
|
||
return @files;
|
||
}
|
||
|
||
sub retrieveText($$$) {
|
||
my ($hash, $fileName, $separator) = @_;
|
||
my $name = $hash->{NAME};
|
||
my ($err,@a);
|
||
$fileName = $tempDir.$name."_$fileName";
|
||
($err,@a) = FileRead({FileName=>$fileName,ForceType=>"file" });
|
||
return "GDS error reading $fileName" if($err);
|
||
@a = map (latin1ToUtf8($_), @a);
|
||
return join($separator, @a);
|
||
}
|
||
|
||
sub gds_calctz($@) {
|
||
my ($nt,@lt) = @_;
|
||
my $off = $lt[2]*3600+$lt[1]*60+$lt[0];
|
||
$off = 12*3600-$off;
|
||
$nt += $off; # This is noon, localtime
|
||
my @gt = gmtime($nt);
|
||
return (12-$gt[2]);
|
||
}
|
||
|
||
###################################################################################################
|
||
#
|
||
# Data retrieval (internal)
|
||
|
||
sub getListCapStations($$){
|
||
my ($hash, $command) = @_;
|
||
my $name = $hash->{NAME};
|
||
my (%capHash, $file, @columns, $key, $cList, $count);
|
||
|
||
$file = $tempDir.'capstations.csv';
|
||
return "GDS error: $file not found." unless(-e $file);
|
||
|
||
# CSV öffnen und parsen
|
||
my ($err,@a) = FileRead({FileName=>$file,ForceType=>"file" });
|
||
return "GDS error reading $file" if($err);
|
||
foreach my $l (@a) {
|
||
next if (substr($l,0,1) eq '#');
|
||
@columns = split(";",$l);
|
||
$capHash{latin1ToUtf8($columns[4])} = $columns[0];
|
||
}
|
||
|
||
# Ausgabe sortieren und zusammenstellen
|
||
foreach $key (sort keys %capHash) {
|
||
$cList .= $capHash{$key}."\t".$key."\n";
|
||
}
|
||
return $cList;
|
||
}
|
||
sub retrieveListCapStations($){
|
||
my ($hash) = @_;
|
||
$hash->{file}{dir} = "gds/help/";
|
||
$hash->{file}{dwd} = "legend_warnings_CAP_WarnCellsID.csv";
|
||
$hash->{file}{target} = $tempDir."capstations.csv";
|
||
unless(-e $hash->{file}{target}) {
|
||
retrieveData($hash,'FILE');
|
||
} else {
|
||
# read capstationslist once a day
|
||
my $alter = time() - (stat($hash->{file}{target}))[9];
|
||
retrieveData($hash,'FILE') if ($alter > 86400);
|
||
}
|
||
}
|
||
|
||
sub decodeCAPData($$$){
|
||
my ($hash, $datensatz, $anum) = @_;
|
||
my $name = $hash->{NAME};
|
||
my $info = 9999; # to be deleted
|
||
my $alert = int($datensatz/100);
|
||
my $area = $datensatz-$alert*100;
|
||
|
||
my (%readings, @dummy, $i, $k, $n, $v, $t);
|
||
|
||
my $gdsAll = AttrVal($name,"gdsAll", 0);
|
||
my $gdsDebug = AttrVal($name,"gdsDebug", 0);
|
||
my $gdsLong = AttrVal($name,"gdsLong", 0);
|
||
my $gdsPolygon = AttrVal($name,"gdsPolygon", 0);
|
||
|
||
Log3($name, 4, "GDS $name: Decoding CAP record #".$datensatz);
|
||
|
||
# topLevel informations
|
||
if($gdsAll || $gdsDebug) {
|
||
@dummy = split(/\./, $alertsXml->{alert}[$alert]{identifier});
|
||
$readings{"a_".$anum."_identifier"} = $alertsXml->{alert}[$alert]{identifier};
|
||
$readings{"a_".$anum."_idPublisher"} = $dummy[5];
|
||
$readings{"a_".$anum."_idSysten"} = $dummy[6];
|
||
$readings{"a_".$anum."_idTimeStamp"} = $dummy[7];
|
||
$readings{"a_".$anum."_idIndex"} = $dummy[8];
|
||
}
|
||
|
||
$readings{"a_".$anum."_sent"} = $alertsXml->{alert}[$alert]{sent};
|
||
$readings{"a_".$anum."_status"} = $alertsXml->{alert}[$alert]{status};
|
||
$readings{"a_".$anum."_msgType"} = $alertsXml->{alert}[$alert]{msgType};
|
||
|
||
# infoSet informations
|
||
if($gdsAll || $gdsDebug) {
|
||
$readings{"a_".$anum."_language"} = $alertsXml->{alert}[$alert]{info}{language};
|
||
$readings{"a_".$anum."_urgency"} = $alertsXml->{alert}[$alert]{info}{urgency};
|
||
$readings{"a_".$anum."_severity"} = $alertsXml->{alert}[$alert]{info}{severity};
|
||
$readings{"a_".$anum."_certainty"} = $alertsXml->{alert}[$alert]{info}{certainty};
|
||
}
|
||
|
||
$readings{"a_".$anum."_category"} = $alertsXml->{alert}[$alert]{info}{category};
|
||
$readings{"a_".$anum."_event"} = $alertsXml->{alert}[$alert]{info}{event};
|
||
$readings{"a_".$anum."_responseType"} = $alertsXml->{alert}[$alert]{info}{responseType};
|
||
|
||
# eventCode informations
|
||
# loop through array
|
||
$i = 0;
|
||
while(1){
|
||
($n, $v) = (undef, undef);
|
||
$n = $alertsXml->{alert}[$alert]{info}{eventCode}[$i]{valueName};
|
||
if(!$n) {last;}
|
||
$n = "a_".$anum."_eventCode_".$n;
|
||
$v = $alertsXml->{alert}[$alert]{info}{eventCode}[$i]{value};
|
||
$readings{$n} .= $v." " if($v);
|
||
$i++;
|
||
}
|
||
|
||
# time/validity informations
|
||
$readings{"a_".$anum."_effective"} = $alertsXml->{alert}[$alert]{info}{effective} if($gdsAll);
|
||
$readings{"a_".$anum."_onset"} = $alertsXml->{alert}[$alert]{info}{onset};
|
||
$readings{"a_".$anum."_expires"} = $alertsXml->{alert}[$alert]{info}{expires};
|
||
$readings{"a_".$anum."_valid"} = _checkCAPValid($readings{"a_".$anum."_onset"},$readings{"a_".$anum."_expires"});
|
||
$readings{"a_".$anum."_onset_local"} = _capTrans($readings{"a_".$anum."_onset"});
|
||
$readings{"a_".$anum."_expires_local"} = _capTrans($readings{"a_".$anum."_expires"})
|
||
if(defined($alertsXml->{alert}[$alert]{info}{expires}));
|
||
$readings{"a_".$anum."_sent_local"} = _capTrans($readings{"a_".$anum."_sent"});
|
||
|
||
$readings{a_valid} = ReadingsVal($name,'a_valid',0) || $readings{"a_".$anum."_valid"};
|
||
|
||
# text informations
|
||
$readings{"a_".$anum."_headline"} = $alertsXml->{alert}[$alert]{info}{headline};
|
||
$readings{"a_".$anum."_description"} = $alertsXml->{alert}[$alert]{info}{description} if($gdsAll || $gdsLong);
|
||
$readings{"a_".$anum."_instruction"} = $alertsXml->{alert}[$alert]{info}{instruction}
|
||
if($readings{"a_".$anum."_responseType"} eq "Prepare" & ($gdsAll || $gdsLong));
|
||
|
||
# area informations
|
||
$readings{"a_".$anum."_areaDesc"} = $alertsXml->{alert}[$alert]{info}{area}[$area]{areaDesc};
|
||
$readings{"a_".$anum."_areaPolygon"} = $alertsXml->{alert}[$alert]{info}{area}[$area]{polygon} if($gdsAll || $gdsPolygon);
|
||
|
||
# area geocode informations
|
||
# loop through array
|
||
$i = 0;
|
||
while(1){
|
||
($n, $v) = (undef, undef);
|
||
$n = $alertsXml->{alert}[$alert]{info}{area}[$area]{geocode}[$i]{valueName};
|
||
if(!$n) {last;}
|
||
$n = "a_".$anum."_geoCode_".$n;
|
||
$v = $alertsXml->{alert}[$alert]{info}{area}[$area]{geocode}[$i]{value};
|
||
$readings{$n} .= $v." " if($v);
|
||
$i++;
|
||
}
|
||
|
||
$readings{"a_".$anum."_altitude"} = $alertsXml->{alert}[$alert]{info}{area}[$area]{altitude} if($gdsAll);
|
||
$readings{"a_".$anum."_ceiling"} = $alertsXml->{alert}[$alert]{info}{area}[$area]{ceiling} if($gdsAll);
|
||
|
||
readingsBeginUpdate($hash);
|
||
readingsBulkUpdate($hash, "_dataSource", "Quelle: Deutscher Wetterdienst");
|
||
while(($k, $v) = each %readings){
|
||
# skip update if no valid data is available
|
||
next unless(defined($v));
|
||
readingsBulkUpdate($hash, $k, latin1ToUtf8($v));
|
||
}
|
||
|
||
# convert color value to hex
|
||
my $r = ReadingsVal($name, 'a_'.$anum.'_eventCode_AREA_COLOR', '');
|
||
if(length($r)) {
|
||
my $v = sprintf( "%02x%02x%02x", split(" ", $r));
|
||
readingsBulkUpdate($hash, 'a_'.$anum.'_eventCode_AREA_COLOR_hex', $v);
|
||
}
|
||
|
||
readingsEndUpdate($hash, 1);
|
||
|
||
return;
|
||
}
|
||
sub _checkCAPValid($$;$$){
|
||
my ($onset,$expires,$t,$tmax) = @_;
|
||
my $valid = 0;
|
||
|
||
$t = time() if (!defined($t));
|
||
my $offset = gds_calctz($t,localtime($t))*3600;
|
||
$t -= $offset;
|
||
$tmax -= $offset if (defined($tmax));
|
||
|
||
$onset =~ s/T/ /;
|
||
$onset =~ s/\+/ \+/;
|
||
$onset = time_str2num($onset);
|
||
|
||
$expires =~ s/T/ /;
|
||
$expires =~ s/\+/ \+/;
|
||
$expires = time_str2num($expires);
|
||
|
||
if (defined($tmax)) {
|
||
$valid = 1 if($tmax ge $onset && $t lt $expires);
|
||
} else {
|
||
$valid = 1 if($onset lt $t && $expires gt $t);
|
||
}
|
||
return $valid;
|
||
}
|
||
sub _capTrans($) {
|
||
my ($t) = @_;
|
||
my $valid = 0;
|
||
my $offset = gds_calctz(time,localtime(time))*3600; # used from 99_SUNRISE_EL
|
||
$t =~ s/T/ /;
|
||
$t =~ s/\+/ \+/;
|
||
$t = time_str2num($t);
|
||
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($t+$offset);
|
||
$mon += 1;
|
||
$year += 1900;
|
||
$t = sprintf "%02s.%02s.%02s %02s:%02s:%02s", $mday, $mon, $year, $hour, $min, $sec;
|
||
return $t;
|
||
}
|
||
|
||
###################################################################################################
|
||
#
|
||
# nonblocking data retrieval
|
||
|
||
sub retrieveData($$){
|
||
my ($hash,$req) = @_;
|
||
$req = uc($req);
|
||
if ($req eq "FORECAST") {
|
||
my $busyTag = "GDS_".$req."_BUSY";
|
||
if (defined($hash->{$busyTag})) {
|
||
return;
|
||
} else {
|
||
$hash->{$busyTag} = localtime(time());
|
||
}
|
||
}
|
||
my $tag = "GDS_".$req."_READ";
|
||
delete $hash->{$tag};
|
||
$tag = "GDS_".$req."_ABORTED";
|
||
delete $hash->{$tag};
|
||
BlockingCall("_retrieve$req",$hash,"_finished$req",60,"_aborted$req",$hash);
|
||
}
|
||
|
||
# any file
|
||
sub _retrieveFILE {
|
||
my ($hash) = shift;
|
||
my $name = $hash->{NAME};
|
||
my $user = getKeyValue($name."_user");
|
||
my $pass = getKeyValue($name."_pass");
|
||
my $host = getKeyValue($name."_host");
|
||
my $proxyName = AttrVal($name, "gdsProxyName", "");
|
||
my $proxyType = AttrVal($name, "gdsProxyType", "");
|
||
my $passive = AttrVal($name, "gdsPassiveFtp", 1);
|
||
|
||
my $dir = $hash->{file}{dir};
|
||
my $dwd = $hash->{file}{dwd};
|
||
my $target = $hash->{file}{target};
|
||
|
||
my $ret = "";
|
||
|
||
eval {
|
||
my $ftp = Net::FTP->new( $host,
|
||
Debug => 0,
|
||
Timeout => 10,
|
||
Passive => $passive,
|
||
FirewallType => $proxyType,
|
||
Firewall => $proxyName);
|
||
if(defined($ftp)){
|
||
Log3($name, 4, "GDS $name: ftp connection established.");
|
||
$ftp->login($user, $pass);
|
||
$ftp->binary;
|
||
$ftp->cwd($dir);
|
||
my @files = $ftp->ls($dwd);
|
||
if(@files) {
|
||
@files = sort(@files);
|
||
$dwd = $files[-1];
|
||
Log3($name, 4, "GDS $name: file found.");
|
||
Log3($name, 4, "GDS $name: retrieving $dwd");
|
||
if(defined($target)) {
|
||
$ftp->get($dwd,$target);
|
||
my $s = -s $target;
|
||
Log3($name, 4, "GDS: ftp transferred $s bytes");
|
||
} else {
|
||
my ($file_content,$file_handle);
|
||
open($file_handle, '>', \$file_content);
|
||
$ftp->get($dwd,$file_handle);
|
||
$file_content = latin1ToUtf8($file_content);
|
||
$file_content =~ s/\r\n/\$/g;
|
||
$ret = $file_content;
|
||
}
|
||
}
|
||
$ftp->quit;
|
||
}
|
||
};
|
||
return "$name;;;$dwd;;;$ret";
|
||
}
|
||
sub _finishedFILE {
|
||
my ($name,$file,$ret) = split(/;;;/,shift); #@_;
|
||
my $hash = $defs{$name};
|
||
DoTrigger($name,"REREADFILE $file",1);
|
||
}
|
||
sub _abortedFILE {
|
||
my ($hash) = shift;
|
||
}
|
||
|
||
# CapData
|
||
sub _retrieveCAPDATA {
|
||
my ($hash) = shift;
|
||
my $name = $hash->{NAME};
|
||
my $user = getKeyValue($name."_user");
|
||
my $pass = getKeyValue($name."_pass");
|
||
my $host = getKeyValue($name."_host");
|
||
my $proxyName = AttrVal($name, "gdsProxyName", "");
|
||
my $proxyType = AttrVal($name, "gdsProxyType", "");
|
||
my $passive = AttrVal($name, "gdsPassiveFtp", 1);
|
||
my $dir = "gds/specials/alerts/cap/GER/status/";
|
||
my $dwd = "Z_CAP*";
|
||
|
||
my $datafile = "";
|
||
my $targetDir = $tempDir.$name."_alerts.dir";
|
||
my $targetFile = $tempDir.$name."_alerts.zip";
|
||
mkdir $targetDir unless -d $targetDir;
|
||
|
||
# delete archive file
|
||
unlink $targetFile;
|
||
|
||
eval {
|
||
my $ftp = Net::FTP->new( $host,
|
||
Debug => 0,
|
||
Timeout => 10,
|
||
Passive => $passive,
|
||
FirewallType => $proxyType,
|
||
Firewall => $proxyName);
|
||
if(defined($ftp)){
|
||
Log3($name, 4, "GDS $name: ftp connection established.");
|
||
$ftp->login($user, $pass);
|
||
$ftp->binary;
|
||
$ftp->cwd("$dir");
|
||
my @files = $ftp->ls($dwd);
|
||
if(@files) {
|
||
Log3($name, 4, "GDS $name: filelist found.");
|
||
@files = sort(@files);
|
||
$datafile = $files[-1];
|
||
Log3($name, 5, "GDS $name: retrieving $datafile");
|
||
$ftp->get($datafile,$targetFile);
|
||
my $s = -s $targetFile;
|
||
Log3($name, 5, "GDS: ftp transferred $s bytes");
|
||
}
|
||
$ftp->quit;
|
||
}
|
||
};
|
||
|
||
|
||
# delete old files in directory
|
||
if (-d $targetDir) {
|
||
my @remove = _readDir($targetDir);
|
||
foreach my $f (@remove){
|
||
next if -d $f;
|
||
next if $targetFile =~ m/$f$/;
|
||
Log3($name, 4, "GDS $name: deleting $targetDir/$f");
|
||
unlink("$targetDir/$f");
|
||
}
|
||
}
|
||
|
||
# unzip
|
||
qx(unzip -o $targetFile -d $targetDir);
|
||
|
||
# merge
|
||
my ($countInfo,$cF) = _mergeCapFile($hash);
|
||
my ($aList,$cellData) = _buildCAPList($hash,$countInfo,$cF);
|
||
|
||
unlink $targetFile unless AttrVal($name,'gdsDebug',0);
|
||
|
||
return "$name;;;$datafile;;;$aList;;;$cF;;;$cellData";
|
||
}
|
||
sub _finishedCAPDATA {
|
||
my ($name,$datafile,$aL,$capFile,$cellData) = split(/;;;/,shift);
|
||
my $hash = $defs{$name};
|
||
$aList = $aL;
|
||
my @h = split(/;;/,$cellData);
|
||
foreach(@h) {
|
||
my ($n,$city,$cell) = split(/:/,$_);
|
||
$capCityHash{$city} = $n;
|
||
$capCellHash{"$cell$n"} = $n;
|
||
}
|
||
|
||
my $xml = new XML::Simple;
|
||
eval {
|
||
$alertsXml = $xml->XMLin($capFile, KeyAttr => {}, ForceArray => [ 'alert', 'eventCode', 'area', 'geocode' ]);
|
||
};
|
||
if ($@) {
|
||
Log3($name,1,'GDS: error analyzing alerts XML:'.$@);
|
||
return;
|
||
}
|
||
readingsSingleUpdate($hash, "_dF_alerts",$datafile,0) if(AttrVal($name, "gdsDebug", 0));
|
||
$hash->{GDS_CAPDATA_READ} = int(time());
|
||
DoTrigger($name,"REREADALERTS",1);
|
||
}
|
||
sub _abortedCAPDATA {
|
||
my ($hash) = shift;
|
||
delete $hash->{GDS_CAPDATA_READ};
|
||
$hash->{GDS_CAPDATA_ABORTED} = localtime(time());
|
||
}
|
||
sub _mergeCapFile($) {
|
||
my ($hash) = @_;
|
||
my $name = $hash->{NAME};
|
||
|
||
my $destinationDirectory = $tempDir.$name."_alerts.dir";
|
||
my @capFiles = _readDir($destinationDirectory);
|
||
|
||
my @alertsArray;
|
||
my $xmlHeader = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>';
|
||
push (@alertsArray,$xmlHeader);
|
||
push (@alertsArray,"<gds>");
|
||
my $countInfo = 0;
|
||
|
||
foreach my $cF (@capFiles){
|
||
# merge all capFiles
|
||
$cF = $destinationDirectory."/".$cF;
|
||
next if -d $cF;
|
||
next unless -s $cF;
|
||
next unless $cF =~ m/\.xml$/; # read xml files only!
|
||
Log3($name, 4, "GDS $name: analyzing $cF");
|
||
|
||
my ($err,@a) = FileRead({FileName=>$cF,ForceType=>"file" });
|
||
foreach my $l (@a) {
|
||
next unless length($l);
|
||
next if($l =~ m/^\<\?xml version.*/);
|
||
$l = "<alert>" if($l =~ m/^\<alert.*/);
|
||
# next if($l =~ m/^\<alert.*/);
|
||
# next if($l =~ m/^\<\/alert.*/);
|
||
next if($l =~ m/^\<sender\>.*/);
|
||
$countInfo++ if($l =~ m/^\<info\>/);
|
||
push (@alertsArray,$l);
|
||
}
|
||
}
|
||
push (@alertsArray,"</gds>");
|
||
|
||
# write the big XML file if needed
|
||
if(AttrVal($name,"gdsDebug", 0)) {
|
||
my $cF = $destinationDirectory."/gds_alerts";
|
||
unlink $cF if -e $cF;
|
||
FileWrite({ FileName=>$cF,ForceType=>"file" },@alertsArray);
|
||
}
|
||
|
||
my $xmlContent = join('',@alertsArray);
|
||
return ($countInfo,$xmlContent);
|
||
}
|
||
sub _buildCAPList($$$){
|
||
my ($hash,$countInfo,$cF) = @_;
|
||
my $name = $hash->{NAME};
|
||
|
||
$alertsXml = undef;
|
||
|
||
my $xml = new XML::Simple;
|
||
my $area = 0;
|
||
my $record = 0;
|
||
my $n = 0;
|
||
my ($capCity, $capCell, $capEvent, $capEvt, @a);
|
||
my $destinationDirectory = $tempDir.$name."_alerts.dir";
|
||
|
||
# make XML array and analyze data
|
||
eval {
|
||
$alertsXml = $xml->XMLin($cF, KeyAttr => {}, ForceArray => [ 'alert', 'eventCode', 'area', 'geocode' ]);
|
||
};
|
||
if ($@) {
|
||
Log3($name,1,'GDS: error analyzing alerts XML:'.$@);
|
||
return (undef,undef);
|
||
}
|
||
|
||
# analyze entries based on info and area array
|
||
# array elements are determined by $info and $area
|
||
#
|
||
|
||
my $cellData = '';
|
||
|
||
for (my $info=0; $info<=$countInfo;$info++) {
|
||
$area = 0;
|
||
while(1){
|
||
$capCity = $alertsXml->{alert}[$info]{info}{area}[$area]{areaDesc};
|
||
$capEvent = $alertsXml->{alert}[$info]{info}{event};
|
||
last unless $capCity;
|
||
$capCell = __findCAPWarnCellId($info, $area);
|
||
$n = 100*$info+$area;
|
||
$capCity = latin1ToUtf8($capCity.' '.$capEvent);
|
||
push @a, $capCity;
|
||
$capCity =~ s/\s/_/g;
|
||
$cellData .= "$n:$capCity:$capCell$n;;";
|
||
$area++;
|
||
$record++;
|
||
$capCity = undef;
|
||
}
|
||
}
|
||
|
||
@a = sort(@a);
|
||
$aList = undef;
|
||
$aList = join(",", @a);
|
||
$aList =~ s/\s/_/g;
|
||
$aList = "No_alerts_published!" if !$record;
|
||
|
||
return($aList,$cellData);
|
||
}
|
||
sub __findCAPWarnCellId($$){
|
||
my ($info, $area) = @_;
|
||
my $i = 0;
|
||
while($i < 100){
|
||
if($alertsXml->{alert}[$info]{info}{area}[$area]{geocode}[$i]{valueName} eq "WARNCELLID"){
|
||
return $alertsXml->{alert}[$info]{info}{area}[$area]{geocode}[$i]{value};
|
||
last;
|
||
}
|
||
$i++; # emergency exit :)
|
||
}
|
||
}
|
||
|
||
# ForecastData
|
||
sub _retrieveFORECAST {
|
||
my ($hash) = shift;
|
||
my $name = $hash->{NAME};
|
||
my $user = getKeyValue($name."_user");
|
||
my $pass = getKeyValue($name."_pass");
|
||
my $host = getKeyValue($name."_host");
|
||
my $proxyName = AttrVal($name, "gdsProxyName", "");
|
||
my $proxyType = AttrVal($name, "gdsProxyType", "");
|
||
my $passive = AttrVal($name, "gdsPassiveFtp", 1);
|
||
my $useFritz = AttrVal($name, "gdsUseFritzkotz", 0);
|
||
my $dir = "gds/specials/forecasts/tables/germany/";
|
||
|
||
my $ret = "";
|
||
|
||
eval {
|
||
my $ftp = Net::FTP->new( $host,
|
||
Debug => 0,
|
||
Timeout => 10,
|
||
Passive => $passive,
|
||
FirewallType => $proxyType,
|
||
Firewall => $proxyName);
|
||
if(defined($ftp)){
|
||
Log3($name, 4, "GDS $name: ftp connection established.");
|
||
$ftp->login($user, $pass);
|
||
$ftp->binary;
|
||
$ftp->cwd("$dir");
|
||
my @files = $ftp->ls();
|
||
if(@files) {
|
||
Log3($name, 4, "GDS $name: filelist found.");
|
||
@files = sort(@files);
|
||
$fcmapList = undef;
|
||
map ( $fcmapList .= (split(/Daten_/,$_,2))[1].",", @files );
|
||
my $count = 0;
|
||
foreach my $file (@files) {
|
||
my ($file_content,$file_handle);
|
||
open($file_handle, '>', \$file_content);
|
||
$ftp->get($file,$file_handle);
|
||
next unless (length($file_content));
|
||
$file_content = latin1ToUtf8($file_content);
|
||
$file_content =~ s/\r\n/\$/g;
|
||
$ret .= "$file:$file_content;";
|
||
$count++;
|
||
Log3 ($name, 5, "GDS $name retrieved forecast $file");
|
||
}
|
||
}
|
||
$ftp->quit;
|
||
}
|
||
};
|
||
|
||
return $name.";;;".$ret;
|
||
}
|
||
sub _finishedFORECAST {
|
||
my ($name,$ret) = split(/;;;/,shift); #@_;
|
||
my $hash = $defs{$name};
|
||
my @a = split(/;/,$ret);
|
||
%allForecastData = ();
|
||
foreach my $l (@a) {
|
||
my ($fn,$fc) = split(/\:/,$l);
|
||
$allForecastData{$fn} = $fc;
|
||
}
|
||
$hash->{GDS_FORECAST_READ} = int(time());
|
||
DoTrigger($name,"REREADFORECAST",1);
|
||
getListForecastStations($hash);
|
||
my $sf = AttrVal($name,'gdsSetForecast',0);
|
||
# GDS_GetUpdate($hash,1) if $sf;
|
||
my @b;
|
||
push @b, undef;
|
||
push @b, undef;
|
||
push @b, $sf;
|
||
retrieveForecasts($hash, "fc", @b);
|
||
delete $hash->{GDS_FORECAST_BUSY};
|
||
}
|
||
sub _abortedFORECAST {
|
||
my ($hash) = shift;
|
||
delete $hash->{GDS_FORECAST_READ};
|
||
$hash->{GDS_FORECAST_ABORTED} = localtime(time());
|
||
delete $hash->{GDS_FORECAST_BUSY};
|
||
}
|
||
|
||
###################################################################################################
|
||
#
|
||
# forecast retrieval - provided by jensb
|
||
|
||
sub retrieveForecasts($$@) {
|
||
#
|
||
# parameter: hash, prefix, region/station, forecast index (0 .. 10)
|
||
#
|
||
my ($hash, $prefix, @a) = @_;
|
||
my $name = $hash->{NAME};
|
||
|
||
# extract region and station name
|
||
return unless defined($a[2]);
|
||
|
||
my ($area,$station) = split(/\//,$a[2]);
|
||
return unless $station;
|
||
|
||
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
|
||
my ($dataFile, $found, $line, %fread, $k, $v, $data);
|
||
$area = utf8ToLatin1($area);
|
||
$station =~ s/_/ /g; # replace underscore in station name by space
|
||
my $searchLen = length($station);
|
||
%fread = ();
|
||
|
||
# define fetch scope (all forecasts or single forecast)
|
||
my $fc = 0;
|
||
my $fcStep = 1;
|
||
if (defined($a[3]) && $a[3] > 0) {
|
||
# single forecast
|
||
$fc = $a[3] - 1;
|
||
$fcStep = 10;
|
||
}
|
||
|
||
# fetch up to 10 forecasts for today and the next 3 days
|
||
do {
|
||
my $day;
|
||
my $early;
|
||
if ($fc < 4) {
|
||
$day = 0;
|
||
$early = 0;
|
||
} else {
|
||
$day = int(($fc - 2)/2);
|
||
$early = $fc%2 == 0;
|
||
}
|
||
my $areaAndTime = $area;
|
||
if ($day == 1) {
|
||
$areaAndTime .= "_morgen";
|
||
} elsif ($day == 2) {
|
||
$areaAndTime .= "_uebermorgen";
|
||
} elsif ($day == 3) {
|
||
$areaAndTime .= "_Tag4";
|
||
}
|
||
my $timeLabel = undef;
|
||
my $tempLabel = '_tAvgAir';
|
||
my $copyDay = undef;
|
||
my $copyTimeLabel = undef;
|
||
if ($day == 0) {
|
||
if ($fc == 0) {
|
||
$areaAndTime .= "_frueh"; # .. 6 h
|
||
$timeLabel = '06';
|
||
$tempLabel ='_tMinAir';
|
||
$copyDay = 1;
|
||
$copyTimeLabel = '12';
|
||
} elsif ($fc == 1) {
|
||
$areaAndTime .= "_mittag"; # .. 12 h
|
||
$timeLabel = '12';
|
||
$tempLabel .= $timeLabel;
|
||
} elsif ($fc == 2) {
|
||
$areaAndTime .= "_spaet"; # .. 18 h
|
||
$timeLabel = '18';
|
||
$tempLabel ='_tMaxAir';
|
||
$copyDay = 1;
|
||
$copyTimeLabel = '24';
|
||
} elsif ($fc == 3) {
|
||
$areaAndTime .= "_nacht"; # .. 24 h
|
||
$timeLabel = '24';
|
||
$tempLabel .= $timeLabel;
|
||
}
|
||
} else {
|
||
if ($early) {
|
||
$areaAndTime .= "_frueh"; # .. 12 h
|
||
$timeLabel = '12';
|
||
$tempLabel ='_tMinAir';
|
||
if ($day < 3) {
|
||
$copyDay = $day + 1;
|
||
$copyTimeLabel = '12';
|
||
}
|
||
} else {
|
||
$areaAndTime .= "_spaet"; # .. 24 h
|
||
$timeLabel .= '24';
|
||
$tempLabel ='_tMaxAir';
|
||
if ($day < 3) {
|
||
$copyDay = $day + 1;
|
||
$copyTimeLabel = '24';
|
||
}
|
||
}
|
||
} # if ($day == 0) {
|
||
|
||
# define forecast date (based on "now" + day)
|
||
my $fcEpoch = time() + $day*86400;
|
||
if ($fc == 3) {
|
||
# night continues at next day
|
||
$fcEpoch += 86400;
|
||
}
|
||
my ($fcSec,$fcMin,$fcHour,$fcMday,$fcMon,$fcYear,$fcWday,$fcYday,$fcIsdst) = localtime($fcEpoch);
|
||
my $fcWeekday = $weekdays[$fcWday];
|
||
my $fcDate = sprintf("%02d.%02d.%04d", $fcMday, 1+$fcMon, 1900+$fcYear);
|
||
my $fcDateFound = 0;
|
||
|
||
# retrieve from hash
|
||
my $noDataFound = 1;
|
||
$data = undef;
|
||
my $hashSize = keys%allForecastData; # checking size of hash seems to make all entries "visible" in loop
|
||
while(($k, $v) = each %allForecastData){
|
||
if ($k eq "Daten_$areaAndTime") {
|
||
$data = $v;
|
||
last;
|
||
};
|
||
}
|
||
|
||
if (defined($data) && $data) {
|
||
my @data = split(/\$/,$data);
|
||
|
||
foreach my $l (@data) {
|
||
if (index($l, $fcDate) > 0) {
|
||
# forecast date found
|
||
$fcDateFound = 1;
|
||
} # if
|
||
if (index(substr(lc($l),0,$searchLen), substr(lc($station),0,$searchLen)) != -1) {
|
||
# station found
|
||
$line = $l;
|
||
last;
|
||
} # if
|
||
} # foreach
|
||
|
||
# parse file
|
||
if ($fcDateFound && length($line) > 0) {
|
||
if (index(substr(lc($line),0,$searchLen), substr(lc($station),0,$searchLen)) != -1) {
|
||
# station found but there is no header line and column width varies:
|
||
$line =~ s/---/ ---/g; # column distance may drop to zero between station name
|
||
# and invalid temp "---" -> prepend 3 spaces
|
||
$line =~ s/ /;/g; # now min. column distance is 3 spaces -> convert to semicolon
|
||
$line =~ s/;+/;/g; # replace multiple consecutive semicolons by one semicolon
|
||
my @b = split(';', $line); # split columns by semicolon
|
||
$b[0] =~ s/^\s+|\s+$//g; # trim station name
|
||
$b[1] =~ s/^\s+|\s+$//g; # trim temperature
|
||
$b[2] =~ s/^\s+|\s+$//g; # trim weather
|
||
if (scalar(@b) > 3) {
|
||
$b[3] =~ s/^\s+|\s+$//g; # trim wind gust
|
||
} else {
|
||
$b[3] = ' ';
|
||
}
|
||
$fread{$prefix."_stationName"} = $area.'/'.$b[0];
|
||
$fread{$prefix.$day.$tempLabel} = $b[1];
|
||
$fread{$prefix.$day."_weather".$timeLabel} = $b[2];
|
||
$fread{$prefix.$day."_windGust".$timeLabel} = $b[3];
|
||
if ($fc != 3) {
|
||
$fread{$prefix.$day."_weekday"} = $fcWeekday;
|
||
}
|
||
$noDataFound = 0;
|
||
} else {
|
||
# station not found, abort
|
||
$fread{$prefix."_stationName"} = "unknown: $station in $area";
|
||
last;
|
||
}
|
||
}
|
||
} # unless
|
||
|
||
if ($noDataFound) {
|
||
# forecast period already passed or no data available
|
||
$fread{$prefix.$day.$tempLabel} = "---";
|
||
$fread{$prefix.$day."_weather".$timeLabel} = "---";
|
||
$fread{$prefix.$day."_windGust".$timeLabel} = "---";
|
||
if ($fc != 3) {
|
||
$fread{$prefix.$day."_weekday"} = $fcWeekday;
|
||
}
|
||
}
|
||
|
||
# day change preset by rotation
|
||
my $ltime = ReadingsTimestamp($name, $prefix.$day."_weather".$timeLabel, undef);
|
||
my ($lsec,$lmin,$lhour,$lmday,$lmon,$lyear,$lwday,$lyday,$lisdst);
|
||
if (defined($ltime)) {
|
||
($lsec,$lmin,$lhour,$lmday,$lmon,$lyear,$lwday,$lyday,$lisdst) = localtime(time_str2num($ltime));
|
||
}
|
||
if (!defined($ltime) || $mday != $lmday) {
|
||
# day has changed, rotate old forecast forward by one day because new forecast is not immediately available
|
||
my $temp = $fread{$prefix.$day.$tempLabel};
|
||
if (defined($temp) && substr($temp, 0, 2) eq '--') {
|
||
if (defined($copyTimeLabel)) {
|
||
$fread{$prefix.$day.$tempLabel} = ReadingsVal($name, $prefix.$copyDay.$tempLabel, '---');
|
||
} else {
|
||
# today noon/night and 3rd day is undefined
|
||
$fread{$prefix.$day.$tempLabel} = ' ';
|
||
}
|
||
}
|
||
my $weather = $fread{$prefix.$day."_weather".$timeLabel};
|
||
if (defined($weather) && substr($weather, 0, 2) eq '--') {
|
||
if (defined($copyTimeLabel)) {
|
||
$fread{$prefix.$day."_weather".$timeLabel} =
|
||
ReadingsVal($name, $prefix.$copyDay."_weather".$copyTimeLabel, '---');
|
||
} else {
|
||
# today noon/night and 3rd day is undefined
|
||
$fread{$prefix.$day."_weather".$timeLabel} = ' ';
|
||
}
|
||
}
|
||
my $windGust = $fread{$prefix.$day."_windGust".$timeLabel};
|
||
if (defined($windGust) && substr($windGust, 0, 2) eq '--') {
|
||
if (defined($copyTimeLabel)) {
|
||
$fread{$prefix.$day."_windGust".$timeLabel} =
|
||
ReadingsVal($name, $prefix.$copyDay."_windGust".$copyTimeLabel, '---');
|
||
} else {
|
||
# today noon/night and 3rd day is undefined
|
||
$fread{$prefix.$day."_windGust".$timeLabel} = ' ';
|
||
}
|
||
}
|
||
}
|
||
$fc += $fcStep;
|
||
} while ($fc < 10);
|
||
|
||
readingsBeginUpdate($hash);
|
||
while (($k, $v) = each %fread) {
|
||
# skip update if no valid data is available
|
||
unless(defined($v)) {delete($defs{$name}{READINGS}{$k}); next;}
|
||
if($v =~ m/^--/) {delete($defs{$name}{READINGS}{$k}); next;};
|
||
unless(length(trim($v))) {delete($defs{$name}{READINGS}{$k}); next;};
|
||
readingsBulkUpdate($hash, $k, $v);
|
||
}
|
||
readingsEndUpdate($hash, 1);
|
||
}
|
||
|
||
sub getListForecastStations($) {
|
||
my ($hash) = @_;
|
||
my $name = $hash->{NAME};
|
||
my @regions = keys(%rmapList);
|
||
my (@a,$data,$k,$v);
|
||
|
||
eval {
|
||
foreach my $region (@regions) {
|
||
$data = "";
|
||
my $areaAndTime = 'Daten_'.$region.'_morgen_spaet';
|
||
while(($k, $v) = each %allForecastData){
|
||
if ($k eq $areaAndTime) {
|
||
$data = $v;
|
||
last;
|
||
};
|
||
}
|
||
next unless $data;
|
||
my @data = split(/\$/,$data);
|
||
splice(@data, 0,2);
|
||
splice(@data,-2);
|
||
map ( push(@a,"$region/".(split(/(\s|--)/,$_,2))[0]), @data );
|
||
}
|
||
};
|
||
|
||
Log3($name, 4, "GDS $name: forecast data not found") unless (@a);
|
||
|
||
@a = sort(@a);
|
||
$fList = join(",", @a);
|
||
$fList =~ s/\s+,/,/g; # replace multiple spaces followed by comma with comma
|
||
$fList =~ s/\s/_/g; # replace spaces in stationName with underscore for list in frontend
|
||
return;
|
||
}
|
||
|
||
|
||
1;
|
||
|
||
|
||
# development documentation
|
||
=pod
|
||
###################################################################################################
|
||
#
|
||
# ToDo
|
||
#
|
||
###################################################################################################
|
||
#
|
||
# Changelog
|
||
#
|
||
###################################################################################################
|
||
#
|
||
# 2016-06-05 changed use os based unzip for decoding capdata
|
||
#
|
||
# 2016-03-29 changed remove all conditions code
|
||
#
|
||
# 2016-01-27 changed use setKeyValue/getKeyValue for username and password
|
||
#
|
||
# 2016-01-01 fixed use txt file instead html for conditions (adopt DWD changes)
|
||
#
|
||
# 2015-12-31 fixed conditions retrieval on startup
|
||
#
|
||
# 2015-11-26 fixed wrong region handling
|
||
# added gdsAlertsHeadlines()
|
||
#
|
||
# 2015-11-17 changed decodeCAPData - fix wrong cumulation (first try)
|
||
# fixed minor bugs
|
||
#
|
||
# 2015-11-06 changed character encoding in forecast readings (jensb)
|
||
# fixed problems after global rereadcfg
|
||
# fixed delete CAP-zipfile unless gdsDebug set
|
||
#
|
||
# 2015-11-01 changed getListForecastStations: fixed inverted logging "data not found"
|
||
# changed GDS_GetUpdate, retrieveData, _finishedFORECAST, _abortedFORECAST:
|
||
# prevent multiple parallel processing
|
||
# changed retrieveForecasts: make available data in hash "visible" for processing
|
||
#
|
||
# 2015-10-31 public new version released, SVN #9739
|
||
#
|
||
# 2015-10-30 public RC6 published, SVN #9727
|
||
# changed use passive ftp per default
|
||
#
|
||
# 2015-10-27 changed add own function gds_calcTz due to announced
|
||
# changes in SUNRISE_EL
|
||
#
|
||
# 2015-10-26 changed multiple instances are forbidden
|
||
#
|
||
# 2015-10-25 public RC5 published, SVN #9663
|
||
# changed a lot of code cleanup
|
||
#
|
||
# 2015-10-24 public RC3 published, SVN #9627
|
||
#
|
||
# 2015-10-13 changed getListForecastStations() completed
|
||
# changed retrieveForecasts() completed
|
||
# added DoTrigger() according to reread
|
||
#
|
||
# 2015-10-12 changed conditions completed
|
||
# changed capstationlist completed
|
||
# changed conditionsmap completed
|
||
# changed forecastsmap completed
|
||
# changed radarmap completed
|
||
# changed warningsmap completed
|
||
# changed warnings completed
|
||
# changed get alerts completed
|
||
#
|
||
# 2015-10-11 changed use Archive::Extract for unzip
|
||
# changed code cleanup
|
||
# changed forecast nonblocking retrieval:
|
||
# hash generation completed
|
||
# changed capstations nonblocking retrieval:
|
||
# alertslist dropdown completed
|
||
# datafile retrieval completed
|
||
#
|
||
#
|
||
# ---------- public RC2 published, SVN #9429
|
||
#
|
||
# 2015-10-11 renamed 99_gdsUtils.pm to GDSweblink.pm
|
||
# changed load GDSweblink.pm in eval() on module startup
|
||
#
|
||
# 2015-10-10 added attribute gdsHideFile to hide "GDS File" Menu
|
||
# added optional parameter "host" in define() to override default hostname
|
||
#
|
||
# changed weblink generator moved into 99_gdsUtils.pm
|
||
# changed perl module List::MoreUtils is no longer used
|
||
# changed perl module Text::CSV is no longer needed
|
||
# changed use binary mode for all ftp transfers to preven errors in images
|
||
#
|
||
# fixed handling for alert items msgType, sent, status
|
||
# fixed handling for alert messages without "expires" data
|
||
#
|
||
# updated commandref documentation
|
||
#
|
||
# ---------- public RC1 published, SVN #9416
|
||
#
|
||
# 2015-10-09 removed createIndexFile()
|
||
# added forecast retrieval
|
||
# added weblink generator
|
||
# added more "set clear ..." commands
|
||
# changed lots and lots of code cleanup
|
||
# feature make retrieveFile() nonblocking
|
||
#
|
||
#
|
||
# 2015-10-08 changed added mergeCapFile()
|
||
# code cleanup in buildCAPList()
|
||
# use system call "unzip" instead of Archive::Zip
|
||
# added NotifyFn for rereadcfg after INITIALIZED
|
||
# improved startup data retrieval
|
||
# improved attribute handling
|
||
#
|
||
# ---------- public first publication in ./contrib/55_GDS.2015 for testing
|
||
#
|
||
# 2015-10-07 changed remove LWP - we will only use ftp for transfers
|
||
# added first solution for filemerge
|
||
# added reliable counter for XML analyzes instead of while(1) loops
|
||
# added (implementation started) forecast retrieval by jensb
|
||
# changed make text file retrieval more generic
|
||
#
|
||
# 2015-10-06 removed Coro Support
|
||
# removed $useFTP - always use http internally
|
||
# changed use LWP::Parallel::UserAgent for nonblocking transfers
|
||
# changed use Archive::ZIP for alert files transfer and unzip
|
||
#
|
||
# 2015-10-05 started redesign for new data structures provided by DWD
|
||
#
|
||
# ----------
|
||
#
|
||
# 2015-09-24 fixed prevent fhem crash on empty conditions file
|
||
#
|
||
# 2015-04-07 fixed a_X_valid calculation: use onset, too
|
||
#
|
||
# 2015-01-30 changed use own FWEXT instead of HTTPSRV
|
||
#
|
||
# 2015-01-03 added multiple alerts handling
|
||
#
|
||
# 2014-10-15 added attr disable
|
||
#
|
||
# 2014-05-23 added set <name> clear alerts|all
|
||
# fixed some typos in docu and help
|
||
#
|
||
# 2014-05-22 added reading a_sent_local
|
||
#
|
||
# 2014-05-07 added readings a_onset_local & a_expires_local
|
||
#
|
||
# 2014-02-26 added attribute gdsPassiveFtp
|
||
#
|
||
# 2014-02-04 added ShutdownFn
|
||
# changed FTP Timeout
|
||
#
|
||
# 2013-11-03 added error handling for malformed XML files from GDS
|
||
#
|
||
# 2013-08-13 fixed some minor bugs to prevent annoying console messages
|
||
# added support for fhem installtions running on windows-based systems
|
||
#
|
||
# 2013-08-11 added retrieval for condition maps
|
||
# added retrieval for forecast maps
|
||
# added retrieval for warning maps
|
||
# added retrieval for radar maps
|
||
# modi use LWP::ua for some file transfers instead of ftp
|
||
# due to transfer errors on image files
|
||
# use parameter #5 = 1 in RetrieveFile for ftp
|
||
# added get <name> caplist
|
||
#
|
||
# 2013-08-10 added some more tolerance on text inputs
|
||
# modi switched from GetLogList to Log3
|
||
#
|
||
# 2013-08-09 added more logging
|
||
# fixed missing error message if WARNCELLID does not exist
|
||
# update commandref
|
||
#
|
||
# 2013-08-08 added logging
|
||
# added firewall/proxy support
|
||
# fixed XMLin missing parameter
|
||
# added :noArg to setlist-definitions
|
||
# added AttrFn
|
||
# modi retrieval of VHDL messages 30-33
|
||
#
|
||
# 2013-08-07 public initial release
|
||
#
|
||
###################################################################################################
|
||
#
|
||
# Further informations
|
||
#
|
||
# DWD's data format is unpleasant to read,
|
||
# since the data columns change depending on the available data
|
||
# (e.g. the SSS column for snow disappears when there is no snow).
|
||
# It's also in ISO8859-1, i.e. it contains non-ASCII characters. To
|
||
# avoid problems, we need some conversion subs in this program.
|
||
#
|
||
# Höhe : m über NN
|
||
# Luftd.: reduzierter Luftdruck auf Meereshöhe in hPa
|
||
# TT : Lufttemperatur in Grad Celsius
|
||
# Tn12 : Minimum der Lufttemperatur, 18 UTC Vortag bis 06 UTC heute, Grad Celsius
|
||
# Tx12 : Maximum der Lufttemperatur, 18 UTC Vortag bis 06 UTC heute, Grad Celsius
|
||
# Tg24 : Temperaturminimum 5cm ¸ber Erdboden, 22.05.2014 00 UTC bis 24 UTC, Grad Celsius
|
||
# Tn24 : Minimum der Lufttemperatur, 22.05.2014 00 UTC bis 24 UTC, Grad Celsius
|
||
# Tm24 : Mittel der Lufttemperatur, 22.05.2014 00 UTC bis 24 UTC, Grad Celsius
|
||
# Tx24 : Maximum der Lufttemperatur, 22.05.2014 00 UTC bis 24 UTC, Grad Celsius
|
||
# Tmin : Minimum der Lufttemperatur, 06 UTC Vortag bis 06 UTC heute, Grad Celsius
|
||
# Tmax : Maximum der Lufttemperatur, 06 UTC Vortag bis 06 UTC heute, Grad Celsius
|
||
# RR1 : Niederschlagsmenge, einstündig, mm = l/qm
|
||
# RR12 : Niederschlagsmenge, 12st¸ndig, 18 UTC Vortag bis 06 UTC heute, mm = l/qm
|
||
# RR24 : Niederschlagsmenge, 24stündig, 06 UTC Vortag bis 06 UTC heute, mm = l/qm
|
||
# SSS : Gesamtschneehöhe in cm
|
||
# SSS24 : Sonnenscheindauer 22.05.2014 in Stunden
|
||
# SGLB24: Tagessumme Globalstrahlung am 22.05.2014 in J/qcm
|
||
# DD : Windrichtung
|
||
# FF : Windgeschwindigkeit letztes 10-Minutenmittel in km/h
|
||
# FX : höchste Windspitze im Bezugszeitraum in km/h
|
||
# --- : Wert nicht vorhanden
|
||
#
|
||
###################################################################################################
|
||
=cut
|
||
|
||
# commandref documentation
|
||
=pod
|
||
=item device
|
||
=begin html
|
||
|
||
<a name="GDS"></a>
|
||
<h3>GDS</h3>
|
||
<ul>
|
||
|
||
<b>Prerequesits</b>
|
||
<ul>
|
||
|
||
<br/>
|
||
Module uses following additional Perl modules:<br/><br/>
|
||
<code>Net::FTP, XML::Simple, Archive::Extract, Archive::Zip</code><br/><br/>
|
||
If not already installed in your environment,
|
||
please install them using appropriate commands from your environment.
|
||
|
||
</ul>
|
||
<br/><br/>
|
||
|
||
<a name="GDSdefine"></a>
|
||
<b>Define</b>
|
||
<ul>
|
||
|
||
<br>
|
||
<code>define <name> GDS <username> <password> [<host>]</code><br>
|
||
<br>
|
||
This module provides connection to <a href="http://www.dwd.de/grundversorgung">GDS service</a>
|
||
generated by <a href="http://www.dwd.de">DWD</a><br>
|
||
<br/>
|
||
Optional paramater host is used to overwrite default host "ftp-outgoing2.dwd.de".<br/>
|
||
<br>
|
||
</ul>
|
||
<br/><br/>
|
||
|
||
<a name="GDSset"></a>
|
||
<b>Set-Commands</b><br/>
|
||
<ul>
|
||
|
||
<br/>
|
||
<code>set <name> clear alerts|conditions|forecasts|all</code>
|
||
<br/><br/>
|
||
<ul>
|
||
<li>alerts: Delete all a_* readings</li>
|
||
<li>all: Delete all a_*, c_*, g_* and fc_* readings</li>
|
||
</ul>
|
||
<br/>
|
||
|
||
<code>set <name> forecasts <region>/<stationName></code>
|
||
<br/><br/>
|
||
<ul>Retrieve forecasts for today and the following 3 days for selected station.<br/>
|
||
Data will be updated periodically.</ul>
|
||
<br/>
|
||
|
||
<code>set <name> help</code>
|
||
<br/><br/>
|
||
<ul>Show a help text with available commands</ul>
|
||
<br/>
|
||
|
||
<code>set <name> update</code>
|
||
<br/><br/>
|
||
<ul>Update forecasts readings at selected station and restart update-timer</ul>
|
||
<br/>
|
||
|
||
<li>forecast readings generated by SET use prefix "fcd_" and a postfix of "hh"<br/>
|
||
with d=relative day (0=today) and hh=last hour of forecast (exclusive)</li>
|
||
<li>readings generated by SET will be updated automatically every 20 minutes</li>
|
||
|
||
</ul>
|
||
<br/><br/>
|
||
|
||
<a name="GDSget"></a>
|
||
<b>Get-Commands</b><br/>
|
||
<ul>
|
||
|
||
<br/>
|
||
<code>get <name> alerts <region></code>
|
||
<br/><br/>
|
||
<ul>Retrieve alert message for selected region from previously read alert file (see rereadcfg)</ul>
|
||
<br/>
|
||
|
||
<code>get <name> conditionsmap <region></code>
|
||
<br/><br/>
|
||
<ul>Retrieve map (imagefile) showing current conditions at selected station</ul>
|
||
<br/>
|
||
|
||
<code>get <name> forecasts <region></code>
|
||
<br/><br/>
|
||
<ul>Retrieve forecasts for today and the following 3 days for selected region as text</ul>
|
||
<br/>
|
||
|
||
<code>get <name> forecastsmap <stationName></code>
|
||
<br/><br/>
|
||
<ul>Retrieve map (imagefile) showing forecasts for selected region</ul>
|
||
<br/>
|
||
|
||
<code>get <name> headlines [separator]</code>
|
||
<br/><br/>
|
||
<ul>Returns a string, containing all alert headlines. <br/>
|
||
Default separator is | but can be overriden.</ul>
|
||
<br/>
|
||
|
||
<code>get <name> help</code>
|
||
<br/><br/>
|
||
<ul>Show a help text with available commands</ul>
|
||
<br/>
|
||
|
||
<code>get <name> list capstations</code>
|
||
<br/><br/>
|
||
<ul>
|
||
<li><b>capstations:</b> Retrieve list showing all defined warning regions.
|
||
You can find your WARNCELLID with this list.</li>
|
||
</ul>
|
||
<br/>
|
||
|
||
<code>get <name> radarmap <region></code>
|
||
<br/><br/>
|
||
<ul>Retrieve map (imagefile) containig radar view from selected region</ul>
|
||
<br/>
|
||
|
||
<code>get <name> rereadcfg</code>
|
||
<br/><br/>
|
||
<ul>Reread all required data from DWD Server manually: station list and CAP data</ul>
|
||
<br/>
|
||
|
||
<code>get <name> warnings <region></code>
|
||
<br/><br/>
|
||
<ul>Retrieve current warnings report for selected region
|
||
<ul>
|
||
<br/>
|
||
<li>report type VHDL30 = regular report, issued daily</li>
|
||
<li>report type VHDL31 = regular report, issued before weekend or national holiday</li>
|
||
<li>report type VHDL32 = preliminary report, issued on special conditions</li>
|
||
<li>report type VHDL33 = cancel report, issued if necessary to cancel VHDL32</li>
|
||
</ul>
|
||
</ul>
|
||
<br/>
|
||
|
||
<code>get <name> warningssmap <region></code>
|
||
<br/><br/>
|
||
<ul>Retrieve map (imagefile) containig current warnings for selected region marked with symbols</ul>
|
||
<br/><br/>
|
||
<b>All downloaded mapfiles</b> can be found inside "GDS Files" area in left navigation bar.
|
||
|
||
</ul>
|
||
<br/><br/>
|
||
|
||
<a name="GDSattr"></a>
|
||
<b>Attributes</b><br/><br/>
|
||
<ul>
|
||
<li><a href="#do_not_notify">do_not_notify</a></li>
|
||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||
<br/>
|
||
<li><b>disable</b> - if set, gds will not try to connect to internet</li>
|
||
<li><b>gdsAll</b> - defines filter for "all data" from alert message</li>
|
||
<li><b>gdsDebug</b> - defines filter for debug informations</li>
|
||
<li><b>gdsSetForecast</b> - defines forecasts region/station to be used after system restart</li>
|
||
<li><b>gdsLong</b> - show long text fields "description" and "instruction" from alert message in readings</li>
|
||
<li><b>gdsPolygon</b> - show polygon data from alert message in a reading</li>
|
||
<li><b>gdsHideFiles</b> - if set to 1, the "GDS Files" menu in the left navigation bar will not be shown</li>
|
||
<br/>
|
||
<li><b>gdsPassiveFtp</b> - set to 1 to use passive FTP transfer</li>
|
||
<li><b>gdsFwName</b> - define firewall hostname in format <hostname>:<port></li>
|
||
<li><b>gdsFwType</b> - define firewall type in a value 0..7 please refer to
|
||
<a href="http://search.cpan.org/~gbarr/libnet-1.22/Net/Config.pm#NetConfig_VALUES">cpan documentation</a>
|
||
for further informations regarding firewall settings.</li>
|
||
</ul>
|
||
<br/><br/>
|
||
|
||
<b>Generated Readings:</b>
|
||
<br/><br/>
|
||
<ul>
|
||
<li><b>_<readingName></b> - debug informations</li>
|
||
<li><b>a_X_<readingName></b> - weather data from CAP alert messages. Readings will NOT be updated automatically<br/>
|
||
a_ readings contain a set of alert inforamtions, X represents a numeric set identifier starting with 0<br/>
|
||
that will be increased for every valid alert message in selected area<br/></li>
|
||
<li><b>a_count</b> - number of currently valid alert messages, can be used for own loop iterations on alert messages</li>
|
||
<li><b>a_valid</b> - returns 1 if at least one of decoded alert messages is valid</li>
|
||
<li><b>fc?_<readingName>??</b> - weather data from SET weather forecasts,
|
||
prefix by relative day and postfixed by last hour. Readings will be updated every 20 minutes.<br>
|
||
<i><ul>
|
||
<li>0_weather06 and ?_weather12 (with ? greater 0) is the weather in the morning</li>
|
||
<li>0_weather12 is the weather at noon</li>
|
||
<li>0_weather18 and ?_weather24 (with ? greater 0) is the weather in the afternoon</li>
|
||
<li>0_weather24 is the weather at midnight</li>
|
||
<li>0_windGust06 and ?_windGust12 (with ? greater 0) is the wind in the morning</li>
|
||
<li>0_windGust12 is the wind at noon</li>
|
||
<li>0_windGust18 and ?_windGust24 (with ? greater 0) is the wind in the afternoon</li>
|
||
<li>0_windGust24 is the wind at midnight</li>
|
||
<li>?_tMinAir is minimum temperature in the morning</li>
|
||
<li>0_tAvgAir12 is the average temperature at noon</li>
|
||
<li>?_tMaxAir is the maximum temperature in the afternoon</li>
|
||
<li>0_tAvgAir24 is the average temperature at midnight</li>
|
||
</ul></i>
|
||
</li>
|
||
</ul>
|
||
<br/><br/>
|
||
|
||
<b>Other events:</b>
|
||
<br/><br/>
|
||
<ul>
|
||
<li><b>REREAD</b> - start of rereadcfg</li>
|
||
<li><b>REREADFILE</b> - end of file read</li>
|
||
<li><b>REREADALERTS</b> - end of alerts read and analyzing</li>
|
||
<li><b>REREADFORECAST</b> - end of forecast read and analyzing</li>
|
||
</ul>
|
||
<br/><br/>
|
||
|
||
<b>Author's notes</b><br/><br/>
|
||
<ul>
|
||
|
||
<li>Module uses following additional Perl modules:<br/><br/>
|
||
<code>Net::FTP, XML::Simple, Archive::Extract, Archive::Zip</code><br/><br/>
|
||
If not already installed in your environment, please install them using appropriate commands from your environment.</li>
|
||
<br/><br/>
|
||
<li>Have fun!</li><br/>
|
||
|
||
</ul>
|
||
|
||
</ul>
|
||
|
||
=end html
|
||
=begin html_DE
|
||
|
||
<a name="GDS"></a>
|
||
<h3>GDS</h3>
|
||
<ul>
|
||
Sorry, keine deutsche Dokumentation vorhanden.<br/><br/>
|
||
Die englische Doku gibt es hier: <a href='http://fhem.de/commandref.html#GDS'>GDS</a><br/>
|
||
</ul>
|
||
=end html_DE
|
||
=cut
|