2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-02-01 13:29:26 +00:00
fhem-mirror/fhem/contrib/GDS/55_GDS.pm
betateilchen e2abb9f828 contrib/GDS: added $Id
git-svn-id: https://svn.fhem.de/fhem/trunk@9432 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2015-10-11 10:38:18 +00:00

2142 lines
66 KiB
Perl
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# $Id$
# 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/say switch/;
use Blocking;
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, $fcmapList, $tempDir, @weekdays);
###################################################################################################
#
# 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->{GetFn} = "GDS_Get";
$hash->{SetFn} = "GDS_Set";
$hash->{ShutdownFn} = "GDS_Shutdown";
$hash->{NotifyFn} = "GDS_Notify";
$hash->{NOTIFYDEV} = "global";
$hash->{AttrFn} = "GDS_Attr";
$hash->{AttrList} = "disable:0,1 ".
"gdsFwName gdsFwType:0,1,2,3,4,5,6,7 gdsAll:0,1 ".
"gdsDebug:0,1 gdsLong:0,1 gdsPolygon:0,1 ".
"gdsSetCond gdsSetForecast gdsPassiveFtp:0,1 ".
"gdsHideFiles:0,1 ".
$readingFnAttributes;
$tempDir = "/tmp/";
$aList = "please_use_rereadcfg_first";
$sList = $aList;
$fList = $aList;
}
sub GDS_Define($$$) {
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
my ($found, $dummy);
return "syntax: define <name> GDS <username> <password> [<host>]" if(int(@a) != 4 );
my $name = $hash->{NAME};
$hash->{helper}{USER} = $a[2];
$hash->{helper}{PASS} = $a[3];
$hash->{helper}{URL} = defined($a[4]) ? $a[4] : "ftp-outgoing2.dwd.de";
$hash->{helper}{INTERVAL} = 1200;
Log3($name, 4, "GDS $name: created");
Log3($name, 4, "GDS $name: tempDir=".$tempDir);
GDS_addExtension("GDS_CGI","gds","GDS Files");
fillMappingTables($hash);
initDropdownLists($hash);
readingsSingleUpdate($hash, '_tzOffset', _calctz(time,localtime(time))*3600, 0);
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_Shutdown($) {
my ($hash) = @_;
my $name = $hash->{NAME};
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");
return if(!grep(m/^INITIALIZED/, @{$dev->{CHANGED}}));
my $d;
GDS_Get($hash,undef,'rereadcfg');
$d = AttrVal($name,'gdsSetCond',undef);
GDS_Set($hash,undef,'conditions',$d) if(defined($d));
$d = AttrVal($name,'gdsSetForecast',undef);
GDS_Set($hash,undef,'forecasts',$d) if(defined($d));
return undef;
}
sub GDS_Set($@) {
my ($hash, @a) = @_;
my $name = $hash->{NAME};
my $usage = "Unknown argument, choose one of ".
"clear:alerts,conditions,forecasts,all ".
"conditions:$sList ".
"forecasts:$fList ".
"help:noArg ".
"update:noArg "; ;
readingsSingleUpdate($hash, '_tzOffset', _calctz(time,localtime(time))*3600, 0);
my $command = lc($a[1]);
my $parameter = $a[2] if(defined($a[2]));
my ($result, $next);
$hash->{LOCAL} = 1;
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);
break;
}
when("conditions"){
retrieveConditions($hash, "c", @a);
$attr{$name}{gdsSetCond} = ReadingsVal($name,'c_stationName',undef);
$next = gettimeofday()+$hash->{helper}{INTERVAL};
readingsSingleUpdate($hash, "_nextUpdate", localtime($next), 1);
RemoveInternalTimer($hash);
InternalTimer($next, "GDS_GetUpdate", $hash, 1);
break;
}
when("forecasts"){
CommandDeleteReading(undef, "$name fc.?_.*") if($parameter ne AttrVal($name,'gdsSetForecast',''));
retrieveForecasts($hash, "fc", @a);
my $station = ReadingsVal($name, 'fc_stationName', undef);
if (defined($station)) {
$attr{$name}{gdsSetForecast} = $station;
}
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};
$hash->{LOCAL} = 1;
my $usage = "Unknown argument $command, choose one of help:noArg rereadcfg:noArg ".
"list:stations,capstations,data ".
"alerts:".$aList." ".
"conditions:".$sList." ".
"conditionsmap:".$cmapList." ".
"forecasts:".$fcmapList." ".
"forecastsmap:".$fmapList." ".
"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);
readingsSingleUpdate($hash, '_tzOffset', _calctz(time,localtime(time))*3600, 0);
my ($result, @datensatz, $found);
given($command) {
when("conditionsmap"){
# retrieve map: current conditions
retrieveFile($hash,$command,$parameter,undef);
break;
}
when("forecastsmap"){
# retrieve map: forecasts
retrieveFile($hash,$command,$parameter,undef);
break;
}
when("warningsmap"){
# retrieve map: warnings
retrieveFile($hash,$command,$parameter,undef);
break;
}
when("radarmap"){
# retrieve map: radar
$parameter = ucfirst($parameter);
retrieveFile($hash,$command,$parameter,$rmapList{$parameter});
break;
}
when("help"){
$result = getHelp();
break;
}
when("list"){
given($parameter){
when("capstations") { $result = getListCapStations($hash,$parameter); break,}
when("data") { $result = retrieveText($hash,"conditions","\n"); break; }
when("stations") { $result = retrieveText($hash,"conditions2","\n"); break; }
default { $usage = "get <name> list <parameter>"; return $usage; }
}
break;
}
when("alerts"){
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);
readingsSingleUpdate($hash,'_lastAlertCheck','see timestamp ->',1) if($_gdsAll || $_gdsDebug);
break;
}
when("headlines"){
$result = gdsHeadlines($name);
break;
}
when("conditions"){
retrieveConditions($hash, "g", @a);
break;
}
when("rereadcfg"){
eval {
retrieveFile($hash,"alerts",undef,undef);
};
eval {
retrieveFile($hash,"conditions",undef,undef);
};
initDropdownLists($hash);
eval {
getListForecastStations($hash);
};
break;
}
when("warnings"){
my $vhdl;
$result = " VHDL30 = current | VHDL31 = weekend or holiday\n".
" VHDL32 = preliminary | VHDL33 = cancel VHDL32\n".
sepLine(31)."+".sepLine(38);
for ($vhdl=30; $vhdl <=33; $vhdl++){
(undef, $found) = retrieveFile($hash, $command, $parameter, $vhdl);
if($found){
$result .= retrieveText($hash, "warnings", "");
$result .= "\n".sepLine(70);
}
}
$result .= "\n\n";
break;
}
when("forecasts"){
$parameter = ucfirst($parameter);
$result = sepLine(67)."\n";
(undef, $found) = retrieveFile($hash,$command,$parameter,undef);
if($found){
$result .= retrieveText($hash, $command, "\n");
}
$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;
given($attrName){
when("gdsDebug"){
CommandDeleteReading(undef, "$name _dF.*") if($attrValue != 1 || $cmd eq 'delete');
break;
}
when("gdsSetCond"){
GDS_Set($hash,undef,'conditions',$attrValue) if($init_done && $cmd eq 'set');
break;
}
when("gdsSetForecast"){
GDS_Set($hash,undef,'forecasts',$attrValue) if($init_done && $cmd eq 'set');
break;
}
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);
}
return;
}
sub GDS_GetUpdate($) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $next;
my $interval = $hash->{helper}{INTERVAL};
my $forcastsStationName = ReadingsVal($name, "fc_stationName", undef);
my $condStationName = ReadingsVal($name, "c_stationName", undef);
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($condStationName) {
my @a;
push @a, undef;
push @a, undef;
push @a, ReadingsVal($name, "c_stationName", "");
retrieveConditions($hash, "c", @a);
}
if($forcastsStationName) {
my @a;
push @a, undef;
push @a, undef;
push @a, $forcastsStationName;
retrieveForecasts($hash, "fc", @a);
}
}
# schedule next update
$next = gettimeofday() + $interval;
readingsSingleUpdate($hash, "_nextUpdate", localtime($next), 1);
InternalTimer($next, "GDS_GetUpdate", $hash, 1);
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_HTMLTail {
return "</html>";
}
sub GDS_getURL {
my $proto = (AttrVal($FW_wname, 'HTTPS', 0) == 1) ? 'https' : 'http';
return $proto."://$FW_httpheader{Host}$FW_ME"; #".$FW_ME;
}
###################################################################################################
#
# Tools
#
###################################################################################################
sub setHelp(){
return "Use one of the following commands:\n".
sepLine(35)."\n".
"set <name> clear alerts|all\n".
"set <name> conditions <stationName>\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> conditions <stationName>\n".
"get <name> forecasts <regionName>\n".
"get <name> help\n".
"get <name> list capstations|stations|data\n".
"get <name> rereadcfg\n".
"get <name> warnings <region>\n";
}
sub retrieveText($$$) {
my ($hash, $fileName, $separator) = @_;
my $name = $hash->{NAME};
my ($err,@a);
given ($fileName) {
when ("conditions2") {
# get conditions stations list
$fileName = $tempDir.$name."_conditions";
($err,@a) = FileRead({FileName=>$fileName,ForceType=>"file" });
return "GDS error reading $fileName" if($err);
@a = map (substr(latin1ToUtf8($_),0,19), @a);
unshift(@a, "Use one of the following stations:", sepLine(40));
}
default {
$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 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);
if (!defined($cList)) {
# 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 getListStations($){
my ($hash) = @_;
my $name = $hash->{NAME};
my ($line, $liste);
my $fileName = $tempDir.$name."_conditions";
return unless -e $fileName;
my $filesize = -s $fileName;
return unless $filesize != 0;
my ($err,@a) = FileRead({FileName=>$fileName,ForceType=>"file" });
return "GDS error reading $fileName" if($err);
@a = map (trim(substr(latin1ToUtf8($_),0,19)), @a);
# delete header lines
splice(@a, 0, 6);
# delete legend
splice(@a, _first_index("Höhe",@a)-1);
@a = sort(@a);
$sList = join(",", @a);
$sList =~ s/\s+,/,/g; # replace multiple spaces followed by comma with comma
$sList =~ s/\s/_/g; # replace spaces in stationName with underscore for list in frontende
return;
}
sub buildCAPList($){
my ($hash) = @_;
my $name = $hash->{NAME};
%capCityHash = ();
%capCellHash = ();
$alertsXml = undef;
$aList = "please_use_rereadcfg_first";
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
my ($countInfo,$cF) = mergeCapFile($hash);
eval {
$alertsXml = $xml->XMLin($cF, KeyAttr => {}, ForceArray => [ 'info', '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
#
for (my $info=0; $info<=$countInfo;$info++) {
$area = 0;
while(1){
$capCity = $alertsXml->{info}[$info]{area}[$area]{areaDesc};
$capEvent = $alertsXml->{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;
$capCityHash{$capCity} = $n;
$capCellHash{"$capCell$n"} = $n;
$area++;
$record++;
$capCity = undef;
}
}
@a = sort(@a);
$aList = undef;
$aList = join(",", @a);
$aList =~ s/\s/_/g;
$aList = "No_alerts_published!" if !$record;
return;
}
sub decodeCAPData($$$){
my ($hash, $datensatz, $anum) = @_;
my $name = $hash->{NAME};
my $info = int($datensatz/100);
my $area = $datensatz-$info*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
@dummy = split(/\./, $alertsXml->{identifier});
$readings{"a_".$anum."_identifier"} = $alertsXml->{identifier} if($_gdsAll || $_gdsDebug);
$readings{"a_".$anum."_idPublisher"} = $dummy[5] if($_gdsAll);
$readings{"a_".$anum."_idSysten"} = $dummy[6] if($_gdsAll);
$readings{"a_".$anum."_idTimeStamp"} = $dummy[7] if($_gdsAll);
$readings{"a_".$anum."_idIndex"} = $dummy[8] if($_gdsAll);
$readings{"a_".$anum."_sent"} = $alertsXml->{sent}[0];
$readings{"a_".$anum."_status"} = $alertsXml->{status}[0];
$readings{"a_".$anum."_msgType"} = $alertsXml->{msgType}[0];
# infoSet informations
$readings{"a_".$anum."_language"} = $alertsXml->{info}[$info]{language} if($_gdsAll);
$readings{"a_".$anum."_category"} = $alertsXml->{info}[$info]{category};
$readings{"a_".$anum."_event"} = $alertsXml->{info}[$info]{event};
$readings{"a_".$anum."_responseType"} = $alertsXml->{info}[$info]{responseType};
$readings{"a_".$anum."_urgency"} = $alertsXml->{info}[$info]{urgency} if($_gdsAll);
$readings{"a_".$anum."_severity"} = $alertsXml->{info}[$info]{severity} if($_gdsAll);
$readings{"a_".$anum."_certainty"} = $alertsXml->{info}[$info]{certainty} if($_gdsAll);
# eventCode informations
# loop through array
$i = 0;
while(1){
($n, $v) = (undef, undef);
$n = $alertsXml->{info}[$info]{eventCode}[$i]{valueName};
if(!$n) {last;}
$n = "a_".$anum."_eventCode_".$n;
$v = $alertsXml->{info}[$info]{eventCode}[$i]{value};
$readings{$n} .= $v." " if($v);
$i++;
}
# time/validity informations
$readings{"a_".$anum."_effective"} = $alertsXml->{info}[$info]{effective} if($_gdsAll);
$readings{"a_".$anum."_onset"} = $alertsXml->{info}[$info]{onset};
$readings{"a_".$anum."_expires"} = $alertsXml->{info}[$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->{info}[$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->{info}[$info]{headline};
$readings{"a_".$anum."_description"} = $alertsXml->{info}[$info]{description} if($_gdsAll || $_gdsLong);
$readings{"a_".$anum."_instruction"} = $alertsXml->{info}[$info]{instruction} if($readings{"a_".$anum."_responseType"} eq "Prepare"
&& ($_gdsAll || $_gdsLong));
# area informations
$readings{"a_".$anum."_areaDesc"} = $alertsXml->{info}[$info]{area}[$area]{areaDesc};
$readings{"a_".$anum."_areaPolygon"} = $alertsXml->{info}[$info]{area}[$area]{polygon} if($_gdsAll || $_gdsPolygon);
# area geocode informations
# loop through array
$i = 0;
while(1){
($n, $v) = (undef, undef);
$n = $alertsXml->{info}[$info]{area}[$area]{geocode}[$i]{valueName};
if(!$n) {last;}
$n = "a_".$anum."_geoCode_".$n;
$v = $alertsXml->{info}[$info]{area}[$area]{geocode}[$i]{value};
$readings{$n} .= $v." " if($v);
$i++;
}
$readings{"a_".$anum."_altitude"} = $alertsXml->{info}[$info]{area}[$area]{altitude} if($_gdsAll);
$readings{"a_".$anum."_ceiling"} = $alertsXml->{info}[$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));
}
# readingsEndUpdate($hash, 1);
# convert color value to hex
eval { readingsBulkUpdate($hash, 'a_'.$anum.'_eventCode_AREA_COLOR_hex',
_rgbd2h(ReadingsVal($name, 'a_'.$anum.'_eventCode_AREA_COLOR', '')));};
readingsEndUpdate($hash, 1);
return;
}
# sub checkCAPValid($$){
# my ($onset,$expires) = @_;
# my $valid = 0;
# my $offset = _calctz(time,localtime(time))*3600; # used from 99_SUNRISE_EL
# my $t = (time - $offset);
#
# $onset =~ s/T/ /;
# $onset =~ s/\+/ \+/;
# $onset = time_str2num($onset);
#
# $expires =~ s/T/ /;
# $expires =~ s/\+/ \+/;
# $expires = time_str2num($expires);
#
# $valid = 1 if($onset lt $t && $expires gt $t);
# return $valid;
# }
sub checkCAPValid($$;$$){
my ($onset,$expires,$t,$tmax) = @_;
my $valid = 0;
$t = time() if (!defined($t));
my $offset = _calctz($t,localtime($t))*3600; # used from 99_SUNRISE_EL
$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 = _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;
}
sub findCAPWarnCellId($$){
my ($info, $area) = @_;
my $i = 0;
while($i < 100){
if($alertsXml->{info}[$info]{area}[$area]{geocode}[$i]{valueName} eq "WARNCELLID"){
return $alertsXml->{info}[$info]{area}[$area]{geocode}[$i]{value};
last;
}
$i++;
}
}
sub retrieveConditions($$@){
my ($hash, $prefix, @a) = @_;
my $name = $hash->{NAME};
(my $myStation = utf8ToLatin1($a[2])) =~ s/_/ /g; # replace underscore in stationName by space
my $searchLen = length($myStation);
my ($line, $item, %pos, %alignment, %wx, %cread, $k, $v);
Log3($name, 4, "GDS $name: Retrieving conditions data");
retrieveFile($hash,"conditions",undef,undef);
my $fileName = $tempDir.$name."_conditions";
my ($err,@file) = FileRead({FileName=>$fileName,ForceType=>"file" });
return "GDS error reading $fileName" if($err);
foreach my $l (@file) {
$line = $l; # save line for further use
if ($l =~ /Station/) { # Header line... find out data positions
@a = split(/\s+/, $l);
foreach $item (@a) {
$pos{$item} = index($line, $item);
}
}
if (index(substr(lc($line),0,$searchLen), substr(lc($myStation),0,$searchLen)) != -1) { last; }
}
%alignment = ("Station" => "l", "H\xF6he" => "r", "Luftd." => "r", "TT" => "r", "Tn12" => "r", "Tx12" => "r",
"Tmin" => "r", "Tmax" => "r", "Tg24" => "r", "Tn24" => "r", "Tm24" => "r", "Tx24" => "r", "SSS24" => "r", "SGLB24" => "r",
"RR1" => "r", "RR12" => "r", "RR24" => "r", "SSS" => "r", "DD" => "r", "FF" => "r", "FX" => "r", "Wetter/Wolken" => "l", "B\xF6en" => "l");
foreach $item (@a) {
Log3($hash, 4, "conditions item: $item");
$wx{$item} = &readItem($line, $pos{$item}, $alignment{$item}, $item);
}
%cread = ();
$cread{"_dataSource"} = "Quelle: Deutscher Wetterdienst";
if(length($wx{"Station"})){
$cread{$prefix."_stationName"} = $wx{"Station"};
$cread{$prefix."_altitude"} = $wx{"H\xF6he"};
$cread{$prefix."_pressure-nn"} = $wx{"Luftd."};
$cread{$prefix."_temperature"} = $wx{"TT"};
$cread{$prefix."_tMinAir12"} = $wx{"Tn12"};
$cread{$prefix."_tMaxAir12"} = $wx{"Tx12"};
$cread{$prefix."_tMinGrnd24"} = $wx{"Tg24"};
$cread{$prefix."_tMinAir24"} = $wx{"Tn24"};
$cread{$prefix."_tAvgAir24"} = $wx{"Tm24"};
$cread{$prefix."_tMaxAir24"} = $wx{"Tx24"};
$cread{$prefix."_tempMin"} = $wx{"Tmin"};
$cread{$prefix."_tempMax"} = $wx{"Tmax"};
$cread{$prefix."_rain1h"} = $wx{"RR1"};
$cread{$prefix."_rain12h"} = $wx{"RR12"};
$cread{$prefix."_rain24h"} = $wx{"RR24"};
$cread{$prefix."_snow"} = $wx{"SSS"};
$cread{$prefix."_sunshine"} = $wx{"SSS24"};
$cread{$prefix."_solar"} = $wx{"SGLB24"};
$cread{$prefix."_windDir"} = $wx{"DD"};
$cread{$prefix."_windSpeed"} = $wx{"FF"};
$cread{$prefix."_windPeak"} = $wx{"FX"};
$cread{$prefix."_weather"} = $wx{"Wetter\/Wolken"};
$cread{$prefix."_windGust"} = $wx{"B\xF6en"};
} else {
$cread{$prefix."_stationName"} = "unknown: $myStation";
}
readingsBeginUpdate($hash);
while (($k, $v) = each %cread) {
# 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, latin1ToUtf8($v));
}
readingsEndUpdate($hash, 1);
return ;
}
sub retrieveFile($$$$){
#
# request = type, e.g. alerts, conditions, warnings
# parameter = additional selector, e.g. Bundesland
#
my ($hash, $request, $parameter, $parameter2) = @_;
$hash->{helper}{request} = $request;
$hash->{helper}{parameter} = $parameter;
$hash->{helper}{parameter2} = $parameter2;
BlockingCall('_retrieveFile',$hash,undef,60,undef,undef);
delete $hash->{helper}{request};
delete $hash->{helper}{parameter};
delete $hash->{helper}{parameter2};
return(undef,undef);
}
sub _retrieveFile($){
my ($hash) = @_;
my $name = $hash->{NAME};
my $user = $hash->{helper}{USER};
my $pass = $hash->{helper}{PASS};
my $host = $hash->{helper}{URL};
my $request = $hash->{helper}{request};
my $parameter = $hash->{helper}{parameter};
my $parameter2 = $hash->{helper}{parameter2};
my $proxyName = AttrVal($name, "gdsProxyName", "");
my $proxyType = AttrVal($name, "gdsProxyType", "");
my $passive = AttrVal($name, "gdsPassiveFtp", 0);
my $debug = AttrVal($name, "gdsDebug",0);
my ($dwd, $dir, $ftp, @files, $dataFile, $targetFile, $found, $readingName);
my $urlString = "ftp://$user:$pass\@".$host."/";
given($request){
when("capstations"){
$dir = "gds/help/";
$dwd = "legend_warnings_CAP_WarnCellsID.csv";
$targetFile = $tempDir.$request.".csv";
break;
}
when("conditionsmap"){
$dir = "gds/specials/observations/maps/germany/";
$dwd = $parameter."*";
$targetFile = $tempDir.$name."_".$request.".jpg";
break;
}
when("forecastsmap"){
$dir = "gds/specials/forecasts/maps/germany/";
$dwd = $parameter."*";
$targetFile = $tempDir.$name."_".$request.".jpg";
break;
}
when("warningsmap"){
if(length($parameter) != 2){
$parameter = $bula2bulaShort{lc($parameter)};
}
$dwd = "Schilder".$dwd2Dir{$bulaShort2dwd{lc($parameter)}}.".jpg";
$dir = "gds/specials/alerts/maps/";
$targetFile = $tempDir.$name."_".$request.".jpg";
break;
}
when("radarmap"){
$dir = "gds/specials/radar/".$parameter2;
$dwd = "Webradar_".$parameter."*";
$targetFile = $tempDir.$name."_".$request.".jpg";
break;
}
when("alerts"){
$dir = "gds/specials/alerts/cap/GER/status/";
$dwd = "Z_CAP*";
my $targetDir = $tempDir.$name."_alerts.dir";
mkdir $targetDir unless -d $targetDir;
$targetFile = "$targetDir/$name"."_alerts.zip";
break;
}
when("conditions"){
$dir = "gds/specials/observations/tables/germany/";
$dwd = "*";
$targetFile = $tempDir.$name."_".$request;
break;
}
when("forecasts"){
$dir = "gds/specials/forecasts/tables/germany/";
$dwd = "Daten_".$parameter;
$targetFile = $tempDir.$name."_".$request;
break;
}
when("warnings"){
if(length($parameter) != 2){
$parameter = $bula2bulaShort{lc($parameter)};
}
$dwd = $bulaShort2dwd{lc($parameter)};
$dir = $dwd2Dir{$dwd};
$dwd = "VHDL".$parameter2."_".$dwd."*";
$dir = "gds/specials/warnings/".$dir."/";
$targetFile = $tempDir.$name."_".$request;
break;
}
}
# delete old file
eval{ unlink $targetFile; };
Log3($name, 4, "GDS $name: searching for $dir".$dwd." on DWD server");
$urlString .= $dir;
$found = 0;
eval {
$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");
@files = undef;
@files = $ftp->ls($dwd);
if(@files){
Log3($name, 4, "GDS $name: filelist found.");
$found = 1;
@files = sort(@files);
$dataFile = $files[-1];
$urlString .= $dataFile;
Log3($name, 5, "GDS $name: retrieving $dataFile");
$ftp->get($dataFile,$targetFile);
my $s = -s $targetFile;
Log3($name, 5, "GDS: ftp transferred $s bytes");
} else {
Log3($name, 4, "GDS $name: filelist not found.");
$found = 0;
}
$ftp->quit;
}
Log3($name, 4, "GDS $name: updating readings.");
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "_dataSource", "Quelle: Deutscher Wetterdienst");
readingsBulkUpdate($hash, "_dF_".$request, $dataFile) if(AttrVal($name, "gdsDebug", 0));
readingsEndUpdate($hash, 0);
};
return ($hash);
}
sub readItem {
my ($line, $pos, $align, $item) = @_;
my $x;
if ($align eq "l") {
$x = substr($line, $pos);
$x =~ s/ .+$//g; # after two spaces => next field
}
if ($align eq "r") {
$pos += length($item);
$x = substr($line, 0, $pos);
$x =~ s/^.+ //g; # remove all before the item
}
return $x;
}
sub sepLine($) {
my ($len) = @_;
my ($output, $i);
for ($i=0; $i<$len; $i++) { $output .= "-"; }
return $output;
}
sub _rgbd2h($) {
my ($input) = @_;
my @a = split(" ", $input);
my $output = sprintf( "%02x%02x%02x", $a[0],$a[1],$a[2]);
return $output;
}
sub _first_index ($@) {
my ($reg,@a) = @_;
my $i = 0;
foreach my $l (@a) {
return $i if ($l =~ m/$reg/);
$i++;
}
return -1;
}
sub fillMappingTables($){
my ($hash) = @_;
$tempDir = "/tmp/";
$aList = "please_use_rereadcfg_first";
$sList = $aList;
$fList = $aList;
retrieveFile($hash,"capstations",undef,undef);
$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 initDropdownLists($){
my($hash) = @_;
my $name = $hash->{NAME};
# fill $aList
if (-e $tempDir.$name."_alerts.dir/$name"."_alerts.zip"){
unzipCapFile($hash);
buildCAPList($hash);
}
# fill $sList
getListStations($hash) if(-e $tempDir.$name."_conditions");
# fill $fList
getListForecastStations($hash) if(-e $tempDir.$name."_forecasts");
return;
}
sub gdsHeadlines($;$) {
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 _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 unzipCapFile($) {
my($hash) = @_;
my $name = $hash->{NAME};
my $destinationDirectory = $tempDir.$name."_alerts.dir";
my $zipname = "$destinationDirectory/$name"."_alerts.zip";
if (-d $destinationDirectory) {
# delete old files in directory
my @remove = _readDir($destinationDirectory);
foreach my $f (@remove){
next if -d $f;
next if $zipname =~ m/$f$/;
Log3($name, 4, "GDS $name: deleting $destinationDirectory/$f");
unlink("$destinationDirectory/$f");
}
}
# unzip
system("/usr/bin/unzip $zipname -d $destinationDirectory");
# delete archive file
unlink $zipname unless AttrVal($name,"gdsDebug",0);
}
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,"<alert>");
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.*/);
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,"</alert>");
# 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);
}
# forecast retrieval
=pod
###################################################################################################
#
# forecast retrieval
#
# provided by jensb
# modified by betateilchen
# - use FileRead instead of own I/O
# - do not set empty readings
# - allow temperature readings below zero degree
# - read forecasts on startup if attr gdsSetForecasts already defined before
# - delete all fc_.* readings in case of new station selection
#
###################################################################################################
=cut
sub retrieveForecasts($$@) {
#
# parameter: hash, prefix, region/station, forecast index (0 .. 10)
#
my ($hash, $prefix, @a) = @_;
my $name = $hash->{NAME};
my $user = $hash->{helper}{USER};
my $pass = $hash->{helper}{PASS};
# extract region and station name
if (!defined($a[2])) {
return;
}
my $i = index($a[2], '/');
if ($i <= 0 ) {
return;
}
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
my ($dataFile, $found, $line, %fread, $k, $v);
my $area = utf8ToLatin1(substr($a[2], 0, $i));
my $station = utf8ToLatin1(substr($a[2], $i+1));
$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;
# FTP retrieve
my $noDataFound = 1;
Log3($name, 4, "GDS $name: Retrieving forecasts data for day $day: $areaAndTime");
retrieveFile($hash, "forecasts", $areaAndTime, undef); sleep 1;
my $fileName = $tempDir.$name."_forecasts";
my ($err,@data) = FileRead({FileName=>$fileName,ForceType=>"file" });
return "GDS error reading $fileName" if($err);
unless ($err) {
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} = utf8ToLatin1(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} =
utf8ToLatin1(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} =
utf8ToLatin1(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, latin1ToUtf8($v));
}
readingsEndUpdate($hash, 1);
}
sub getListForecastStations($) {
my ($hash) = @_;
my $name = $hash->{NAME};
my @a;
my @regions = keys(%rmapList);
foreach (@regions) {
my $areaAndTime = $_.'_morgen_spaet';
retrieveFile($hash, "forecasts", $areaAndTime, undef);
my $fileName = $tempDir.$name."_forecasts";
my ($err,@data) = FileRead({FileName=>$fileName,ForceType=>"file" });
return "GDS error reading $fileName" if($err);
my $lineCount = 0;
foreach my $line (@data) {
# skip header lines
$lineCount++;
if ($lineCount > 2) {
if (length($line) == 0 || substr($line, 0, 3) eq ' ') {
# empty line, done
last;
} else {
# line with station name found
$line = latin1ToUtf8($line);
$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
push @a, $_.'/'.$b[0]; # concat region name and station name (1st column)
}
}
} # foreach @data
} # foreach @regions
if (!@a) {
Log3($name, 4, "GDS $name: error: unable to read forecast data");
}
@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
#
# - improve nonblocking processing
#
###################################################################################################
#
# Changelog $Revision: $
#
###################################################################################################
#
# ---------- 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
=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</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 &lt;name&gt; GDS &lt;username&gt; &lt;password&gt; [&lt;host&gt;]</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 &lt;name&gt; 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 &lt;name&gt; conditions &lt;stationName&gt;</code>
<br/><br/>
<ul>Retrieve current conditions at selected station. Data will be updated periodically.</ul>
<br/>
<code>set &lt;name&gt; forecasts &lt;region&gt;/&lt;stationName&gt;</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 &lt;name&gt; help</code>
<br/><br/>
<ul>Show a help text with available commands</ul>
<br/>
<code>set &lt;name&gt; update</code>
<br/><br/>
<ul>Update conditions and forecasts readings at selected station and restart update-timer</ul>
<br/>
<li>condition readings generated by SET use prefix "c_"</li>
<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 &lt;name&gt; alerts &lt;region&gt;</code>
<br/><br/>
<ul>Retrieve alert message for selected region from previously read alert file (see rereadcfg)</ul>
<br/>
<code>get &lt;name&gt; conditions &lt;stationName&gt;</code>
<br/><br/>
<ul>Retrieve current conditions at selected station</ul>
<br/>
<code>get &lt;name&gt; forecasts &lt;region&gt;</code>
<br/><br/>
<ul>Retrieve forecasts for today and the following 3 days for selected region as text</ul>
<br/>
<code>get &lt;name&gt; conditionsmap &lt;region&gt;</code>
<br/><br/>
<ul>Retrieve map (imagefile) showing current conditions at selected station</ul>
<br/>
<code>get &lt;name&gt; forecastsmap &lt;stationName&gt;</code>
<br/><br/>
<ul>Retrieve map (imagefile) showing forecasts for selected region</ul>
<br/>
<code>get &lt;name&gt; headlines</code>
<br/><br/>
<ul>Returns a string, containing all alert headlines separated by |</ul>
<br/>
<code>get &lt;name&gt; help</code>
<br/><br/>
<ul>Show a help text with available commands</ul>
<br/>
<code>get &lt;name&gt; list capstations|data|stations</code>
<br/><br/>
<ul>
<li><b>capstations:</b> Retrieve list showing all defined warning regions.
You can find your WARNCELLID with this list.</li>
<li><b>data:</b> List current conditions for all available stations in one single table</li>
<li><b>stations:</b> List all available stations that provide conditions data</li>
</ul>
<br/>
<code>get &lt;name&gt; radarmap &lt;region&gt;</code>
<br/><br/>
<ul>Retrieve map (imagefile) containig radar view from selected region</ul>
<br/>
<code>get &lt;name&gt; rereadcfg</code>
<br/><br/>
<ul>Reread all required data from DWD Server manually: station list and CAP data</ul>
<br/>
<code>get &lt;name&gt; warnings &lt;region&gt;</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 &lt;name&gt; warningssmap &lt;region&gt;</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>gdsSetCond</b> - defines conditions area to be used after system restart</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 &lt;hostname&gt;:&lt;port&gt;</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/Events:</b>
<br/><br/>
<ul>
<li><b>_&lt;readingName&gt;</b> - debug informations</li>
<li><b>a_X_&lt;readingName&gt;</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>c_&lt;readingName&gt;</b> - weather data from SET weather conditions.
Readings will be updated every 20 minutes.</li>
<li><b>fc?_&lt;readingName&gt;??</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>
<li><b>g_&lt;readingName&gt;</b> - weather data from GET weather conditions.
Readings will NOT be updated automatically</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</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