# $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