From 462b6185e469cce85e14f47b96120b8f2309b9c3 Mon Sep 17 00:00:00 2001
From: betateilchen <>
Date: Wed, 7 Oct 2015 11:00:31 +0000
Subject: [PATCH] contrib/55_GDS.2015: added new version for testing new
GDS/DWD data structures
git-svn-id: https://svn.fhem.de/fhem/trunk@9396 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/contrib/55_GDS.2015/55_GDS.pm | 1836 ++++++++++++++++++++++++++++
1 file changed, 1836 insertions(+)
create mode 100644 fhem/contrib/55_GDS.2015/55_GDS.pm
diff --git a/fhem/contrib/55_GDS.2015/55_GDS.pm b/fhem/contrib/55_GDS.2015/55_GDS.pm
new file mode 100644
index 000000000..eb8b38e6c
--- /dev/null
+++ b/fhem/contrib/55_GDS.2015/55_GDS.pm
@@ -0,0 +1,1836 @@
+# $Id: 55_GDS.pm 9298 2015-09-24 20:57:33Z betateilchen $
+####################################################################################################
+#
+# 55_GDS.pm
+#
+# An FHEM Perl module to retrieve data from "Deutscher Wetterdienst"
+#
+# Copyright: betateilchen ®
+#
+# 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 .
+#
+####################################################################################################
+
+package main;
+
+use strict;
+use warnings;
+use feature qw/say switch/;
+use Time::HiRes qw(gettimeofday);
+use Data::Dumper;
+use Archive::Zip;
+use Text::CSV;
+use Net::FTP;
+use List::MoreUtils 'first_index';
+use XML::Simple;
+#require LWP::UserAgent;
+require LWP::Parallel::UserAgent;
+
+no if $] >= 5.017011, warnings => 'experimental';
+
+my ($bulaList, $cmapList, %rmapList, $fmapList, %bula2bulaShort, %bulaShort2dwd, %dwd2Dir, %dwd2Name,
+ $alertsXml, %capCityHash, %capCellHash, $sList, $aList, $fList, $fcmapList, @weekdays);
+
+my $tempDir = "/tmp/";
+
+####################################################################################################
+#
+# Main routines
+#
+####################################################################################################
+
+sub unzipCapFile;
+
+sub GDS_Initialize($) {
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+ my $found;
+
+ $hash->{DefFn} = "GDS_Define";
+ $hash->{UndefFn} = "GDS_Undef";
+ $hash->{GetFn} = "GDS_Get";
+ $hash->{SetFn} = "GDS_Set";
+ $hash->{ShutdownFn} = "GDS_Shutdown";
+ $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 ".
+ $readingFnAttributes;
+
+ $tempDir = "c:\\temp\\" if($^O eq "MSWin32");
+
+ fillMappingTables($hash);
+ initDropdownLists($hash);
+
+ if($name){
+ (undef, $found) = retrieveFile($hash,"conditions");
+ if($found){
+ $sList = getListStationsDropdown($hash)
+ } else {
+ Log3($name, 2, "GDS $name: No datafile (conditions) found");
+ }
+
+ (undef, $found) = retrieveFile($hash,"alerts");
+ if($found){
+ ($aList, undef) = buildCAPList($hash);
+ } else {
+ Log3($name, 2, "GDS $name: No datafile (alerts) found");
+ }
+ }
+}
+
+sub GDS_Define($$$) {
+ my ($hash, $def) = @_;
+ my @a = split("[ \t][ \t]*", $def);
+ my ($found, $dummy);
+
+ return "syntax: define GDS " if(int(@a) != 4 );
+ my $name = $hash->{NAME};
+
+ $hash->{helper}{USER} = $a[2];
+ $hash->{helper}{PASS} = $a[3];
+ $hash->{helper}{URL} = "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);
+
+ (undef, $found) = retrieveFile($hash,"conditions");
+ if($found){
+ $sList = getListStationsDropdown($hash)
+ } else {
+ Log3($name, 2, "GDS $name: No datafile (conditions) found");
+ }
+ retrieveFile($hash,"alerts");
+ if($found){
+ ($aList, undef) = buildCAPList($hash);
+ } else {
+ Log3($name, 3, "GDS $name: No datafile (alerts) found");
+ }
+ 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_Set($@) {
+ my ($hash, @a) = @_;
+ my $name = $hash->{NAME};
+ my $usage = "Unknown argument, choose one of clear:alerts,all help:noArg rereadcfg:noArg update:noArg ".
+ "conditions:".$sList." forecasts:".$fList." ";
+
+ 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_.*");
+ CommandDeleteReading(undef, "$name c_.*") if(defined($parameter) && $parameter eq "all");
+ CommandDeleteReading(undef, "$name f_.*") if(defined($parameter) && $parameter eq "all");
+ CommandDeleteReading(undef, "$name g_.*") if(defined($parameter) && $parameter eq "all");
+ }
+
+ when("help"){
+ $result = setHelp();
+ break;
+ }
+
+ when("rereadcfg"){
+ eval {
+ retrieveFile($hash,"conditions");
+ $sList = getListStationsDropdown($hash);
+ };
+ eval {
+ retrieveFile($hash,"alerts");
+ ($aList, undef) = buildCAPList($hash);
+ };
+# eval {
+# $fList = getListForecastStationsDropdown($hash);
+# }; break;
+ }
+
+ when("update"){
+ RemoveInternalTimer($hash);
+ GDS_GetUpdate($hash);
+ break;
+ }
+
+ when("conditions"){
+ readingsSingleUpdate($hash, "state", "active",1);
+ retrieveConditions($hash, "c", @a);
+ $attr{$name}{gdsSetCond} = ReadingsVal($name,'c_stationName',undef);
+ $next = gettimeofday()+$hash->{helper}{INTERVAL};
+ readingsSingleUpdate($hash, "c_nextUpdate", localtime($next), 1);
+ RemoveInternalTimer($hash);
+ InternalTimer($next, "GDS_GetUpdate", $hash, 1);
+ break;
+ }
+
+# when("forecasts"){
+# retrieveForecasts($hash, "f", @a);
+# $attr{$name}{gdsSetForecast} = ReadingsVal($name,'f_stationName',undef);
+# 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);
+ break;
+ }
+
+ when("forecastsmap"){
+ # retrieve map: forecasts
+ retrieveFile($hash,$command,$parameter);
+ break;
+ }
+
+ when("warningsmap"){
+ # retrieve map: warnings
+ retrieveFile($hash,$command,$parameter);
+ 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 = getListData($hash); break; }
+ when("stations") { $result = getListStationsText($hash); break; }
+ default { $usage = "get list "; 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.";
+ }
+ break;
+ }
+
+ when("headlines"){
+ $result = gdsHeadlines($name);
+ break;
+ }
+
+ when("conditions"){
+ retrieveConditions($hash, "g", @a);
+ break;
+ }
+
+ when("rereadcfg"){
+ eval {
+ retrieveFile($hash,"conditions");
+ };
+ eval {
+ retrieveFile($hash,"alerts");
+ };
+ initDropdownLists($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 .= retrieveTextWarn($hash);
+ $result .= "\n".sepLine(70);
+ }
+ }
+ $result .= "\n\n";
+ break;
+ }
+
+# when("forecasts"){
+# $parameter = ucfirst($parameter);
+# $result = sepLine(67)."\n";
+# (undef, $found) = retrieveFile($hash,$command,$parameter);
+# 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 (undef, $name, $attrName, $attrValue) = @a;
+ given($attrName){
+ when("gdsDebug"){
+ CommandDeleteReading(undef, "$name _dF.*") if($attrValue != 1);
+ break;
+ }
+ when("gdsSetCond"){
+ my $dummy = "gdsDummy_$name";
+ CommandDefine(undef, "$dummy at +00:00:30 set $name conditions $attrValue");
+ $defs{$dummy}{TEMPORARY} = 1;
+ $attr{$dummy}{room} = 'hidden';
+ break;
+ }
+ when("gdsSetForecast"){
+ my $dummy = "gdsDummy_$name";
+ CommandDefine(undef, "$dummy at +00:00:30 set $name forecasts $attrValue");
+ $defs{$dummy}{TEMPORARY} = 1;
+ $attr{$dummy}{room} = 'hidden';
+ 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 (@a, $next);
+
+ if(IsDisabled($name)) {
+ readingsSingleUpdate($hash, 'state', 'disabled', 0);
+ Log3 ($name, 2, "GDS $name is disabled, data update cancelled.");
+ } else {
+ readingsSingleUpdate($hash, 'state', 'active', 0);
+ push @a, undef;
+ push @a, undef;
+ push @a, ReadingsVal($name, "c_stationName", "");
+ retrieveConditions($hash, "c", @a);
+ }
+
+ $next = gettimeofday()+$hash->{helper}{INTERVAL};
+ readingsSingleUpdate($hash, "c_nextUpdate", localtime($next), 1);
+ InternalTimer($next, "GDS_GetUpdate", $hash, 1);
+
+ return 1;
+}
+
+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= ;
+ 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") . "\n\n";
+ foreach my $def (sort keys %defs) {
+ if($defs{$def}{TYPE} eq "GDS") {
+ $name= $defs{$def}{NAME};
+ $url = GDS_getURL();
+ $html .= "$name
\n\n\n";
+ }
+ }
+ $html.="\n" . GDS_HTMLTail();
+
+ return ("text/html; charset=utf-8", $html);
+}
+
+sub GDS_HTMLHead($) {
+ my ($title) = @_;
+ my $doctype= '';
+ my $xmlns= 'xmlns="http://www.w3.org/1999/xhtml"';
+ my $code= "$doctype\n\n\n$title\n\n";
+ return $code;
+}
+
+sub GDS_HTMLTail {
+ return "";
+}
+
+sub GDS_getURL {
+ my $proto = (AttrVal($FW_wname, 'HTTPS', 0) == 1) ? 'https' : 'http';
+ return $proto."://$FW_httpheader{Host}$FW_ME"; #".$FW_ME;
+}
+
+####################################################################################################
+#
+# Tools
+#
+####################################################################################################
+
+
+sub getHelp(){
+ return "Use one of the following commands:\n".
+ sepLine(35)."\n".
+ "get alerts \n".
+ "get conditions \n".
+ "get help\n".
+ "get list capstations|stations|data\n".
+ "get rereadcfg\n".
+ "get warnings \n";
+}
+
+sub getListData($){
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my ($line, @a);
+ open WXDATA, $tempDir.$name."_conditions";
+ while (chomp($line = )) {
+ push @a, latin1ToUtf8($line);
+ }
+ close WXDATA;
+
+ return join("\n", @a);
+}
+
+sub getListStationsText($){
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my ($line, @a);
+ open WXDATA, $tempDir.$name."_conditions";
+ while (chomp($line = )) {
+ push @a, substr(latin1ToUtf8($line),0,19);
+ }
+ close WXDATA;
+
+ splice(@a,0,6);
+ splice(@a,first_index { /Höhe/ } @a);
+ splice(@a,-1);
+ @a = sort(@a);
+ unshift(@a, "Use one of the following stations:", sepLine(40));
+
+ return join("\n", @a);
+}
+
+sub getListCapStations($$){
+ my ($hash, $command) = @_;
+ my $name = $hash->{NAME};
+ my (%capHash, $file, $csv, @columns, $err, $key, $cList);
+
+ $file = $tempDir.'capstations.csv';
+ $csv = Text::CSV->new( { binary => 1 } );
+ $csv->sep_char (";");
+
+ # prüfen, ob CSV schon vorhanden,
+ # falls nicht: vom Server holen
+ if (!-e $tempDir."caplist.csv"){
+ retrieveFile($hash, $command);
+ }
+
+ # CSV öffnen und parsen
+ open (CSV, "<", $file) or die $!;
+ while () {
+ next if ($. == 1);
+ if ($csv->parse($_)) {
+ @columns = $csv->fields();
+ $capHash{latin1ToUtf8($columns[4])} = $columns[0];
+ } else {
+ $err = $csv->error_input;
+ print "Failed to parse line: $err";
+ }
+ }
+ close CSV;
+
+ # Ausgabe sortieren und zusammenstellen
+ foreach $key (sort keys %capHash) {
+ $cList .= $capHash{$key}."\t".$key."\n";
+ }
+ return $cList;
+}
+
+sub setHelp(){
+ return "Use one of the following commands:\n".
+ sepLine(35)."\n".
+ "set clear alerts|all\n".
+ "set conditions \n".
+ "set rereadcfg\n".
+ "set update\n".
+ "set help\n";
+}
+
+sub buildCAPList(@){
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my $destinationDirectory = $tempDir.$name."_alerts.dir";
+ my @capFiles = _readDir($destinationDirectory);
+
+ my $xml = new XML::Simple;
+ $alertsXml = undef;
+
+ my $area = 0;
+ my $record = 0;
+ my $n = 0;
+ my ($capCity, $capCell, $capEvent, $capEvt, $list, @a);
+
+ %capCityHash = ();
+ %capCellHash = ();
+ $aList = undef;
+
+ my @alertsArray;
+ my $xmlHeader = '';
+ push (@alertsArray,$xmlHeader);
+ push (@alertsArray,"");
+ my $countInfo = 0;
+
+ foreach my $cF (@capFiles){
+ # merge all capFiles
+ $cF = $destinationDirectory."/".$cF;
+ next if -d $cF;
+ 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/^\.*/);
+ $countInfo++ if($l =~ m/^\/);
+ push (@alertsArray,$l);
+ }
+ }
+ push (@alertsArray,"");
+
+ # write the big XML file
+ my $cF = $destinationDirectory."/gds_alerts";
+ my $err = FileWrite({ FileName=>$cF,ForceType=>"file" },@alertsArray);
+
+ # make XML array
+ 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);
+ $list = join(",", @a);
+ $list =~ s/\s/_/g;
+ $list = "No_alerts_published!" if !$record;
+ return ($list, $record);
+}
+
+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};
+ $readings{"a_".$anum."_status"} = $alertsXml->{status};
+ $readings{"a_".$anum."_msgType"} = $alertsXml->{msgType};
+
+# 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"});
+ $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){
+ readingsBulkUpdate($hash, $k, latin1ToUtf8($v)) if(defined($v)); }
+ readingsEndUpdate($hash, 1);
+ eval {readingsSingleUpdate($hash, 'a_'.$anum.'_eventCode_AREA_COLOR_hex', _rgbd2h(ReadingsVal($name, 'a_'.$anum.'_eventCode_AREA_COLOR', '')),0);};
+
+ return;
+}
+
+# sub
+# _calctz($@)
+# {
+ # my ($nt,@lt) = @_;
+
+ # my $off = $lt[2]*3600+$lt[1]*60+$lt[0];
+ # $off = 12*3600-$off;
+ # $nt += $off; # This is noon, localtime
+
+ # my @gt = gmtime($nt);
+
+ # return (12-$gt[2]);
+# }
+
+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 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 retrieveTextWarn($){
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my ($line, @a);
+ open WXDATA, $tempDir.$name."_warnings";
+ while (chomp($line = )) {
+ push @a, latin1ToUtf8($line); }
+ close WXDATA;
+ return join("", @a);
+}
+
+sub retrieveConditions($$@){
+ my ($hash, $prefix, @a) = @_;
+ my $name = $hash->{NAME};
+ my $user = $hash->{helper}{USER};
+ my $pass = $hash->{helper}{PASS};
+ (my $myStation = utf8ToLatin1($a[2])) =~ s/_/ /g; # replace underscore in stationName by space
+ my $searchLen = length($myStation);
+
+ my ($debug, $dataFile, $found, $line, $item, %pos, %alignment, %wx, %cread, $k, $v);
+
+ $debug = AttrVal($name, "gdsDebug", 0);
+
+ Log3($name, 4, "GDS $name: Retrieving conditions data");
+
+ ($dataFile, $found) = retrieveFile($hash,"conditions",undef,undef);
+ open WXDATA, $tempDir.$name."_conditions";
+ while (chomp($line = )) {
+ map {s/\r//g;} ($line);
+ if ($line =~ /Station/) { # Header line... find out data positions
+ @a = split(/\s+/, $line);
+ foreach $item (@a) {
+ $pos{$item} = index($line, $item);
+ }
+ }
+ if (index(substr(lc($line),0,$searchLen), substr(lc($myStation),0,$searchLen)) != -1) { last; }
+ }
+ close WXDATA;
+
+ %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";
+ }
+
+# CommandDeleteReading(undef, "$name $prefix"."_.*");
+ readingsBeginUpdate($hash);
+ while(($k, $v) = each %cread) {
+ readingsBulkUpdate($hash, $k, latin1ToUtf8($v)) if(defined($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, $useFtp) = @_;
+# my $name = $hash->{NAME};
+# my $user = $hash->{helper}{USER};
+# my $pass = $hash->{helper}{PASS};
+# 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\@ftp-outgoing2.dwd.de/";
+# my $ua;
+# eval { $ua = LWP::UserAgent->new; };
+#
+# if(!defined($ua)) {
+# Log3($name, 1, "GDS $name: LWP not available!");
+# readingsSingleUpdate($hash, 'LWP error', 'LWP not available!',1);
+# return;
+# }
+#
+# $ua->timeout(10);
+# $ua->env_proxy;
+#
+# 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/warnings/maps/";
+# $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/warnings/xml/PVW/";
+# $dir = "gds/specials/alerts/cap/GER/status/";
+# $dwd = "Z_CAP*";
+# $targetFile = $tempDir.$name."_".$request.".zip";
+# break;
+# }
+#
+# when("conditions"){
+# $useFtp = 1;
+# $dir = "gds/specials/observations/tables/germany/";
+# $dwd = "*";
+# $targetFile = $tempDir.$name."_".$request;
+# break;
+# }
+#
+# when("warnings"){
+# $useFtp = 1;
+# if(length($parameter) != 2){
+# $parameter = $bula2bulaShort{lc($parameter)};
+# }
+# $dwd = $bulaShort2dwd{lc($parameter)};
+# $dir = $dwd2Dir{$dwd};
+# $dwd = "VHDL".$parameter2."_".$dwd."*";
+# $dir = "gds/specials/warnings/".$dir."/";
+# # $dir = "gds/specials/alerts/".$dir."/";
+# $targetFile = $tempDir.$name."_".$request;
+# break;
+# }
+# }
+#
+# Log3($name, 4, "GDS $name: searching for $dir".$dwd." on DWD server");
+# $urlString .= $dir;
+#
+# $found = 0;
+# eval {
+# $ftp = Net::FTP->new( "ftp-outgoing2.dwd.de",
+# Debug => 0,
+# Timeout => 10,
+# Passive => $passive,
+# FirewallType => $proxyType,
+# Firewall => $proxyName);
+# Log3($name, 4, "GDS $name: ftp connection established.");
+# if(defined($ftp)){
+# $ftp->login($user, $pass);
+# $ftp->cwd("$dir");
+# @files = undef;
+# @files = $ftp->ls($dwd);
+# if(@files){
+# Log3($name, 4, "GDS $name: filelist found.");
+# @files = sort(@files);
+# $dataFile = $files[-1];
+# $urlString .= $dataFile;
+# Log3($name, 4, "GDS $name: retrieving $dataFile");
+# if($useFtp){
+# Log3($name, 4, "GDS $name: using FTP for retrieval");
+# $ftp->get($files[-1], $targetFile);
+# } else {
+# Log3($name, 4, "GDS $name: using HTTP for retrieval");
+# $ua->get($urlString,':content_file' => $targetFile);
+# }
+# $found = 1;
+# } 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, 1);
+# };
+# return ($dataFile, $found);
+# }
+
+sub retrieveFile($$;$$){
+#
+# request = type, e.g. alerts, conditions, warnings
+# parameter = additional selector, e.g. Bundesland
+#
+
+ my ($hash, $request, $parameter, $parameter2) = @_;
+ my $name = $hash->{NAME};
+ my $user = $hash->{helper}{USER};
+ my $pass = $hash->{helper}{PASS};
+ 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\@ftp-outgoing2.dwd.de/";
+ my $ua;
+ eval { $ua = LWP::Parallel::UserAgent->new; };
+
+ if(!defined($ua)) {
+ Log3($name, 1, "GDS $name: LWP not available!");
+ readingsSingleUpdate($hash, 'LWP error', 'LWP not available!',1);
+ return;
+ }
+
+ $ua->timeout(10);
+ $ua->env_proxy;
+ $ua->nonblock("true");
+
+ 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/warnings/xml/PVW/";
+ $dir = "gds/specials/alerts/cap/GER/status/";
+ $dwd = "Z_CAP*";
+ $targetFile = $tempDir.$name."_".$request.".zip";
+ break;
+ }
+
+ when("conditions"){
+ $dir = "gds/specials/observations/tables/germany/";
+ $dwd = "*";
+ $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;
+ }
+ }
+
+ Log3($name, 4, "GDS $name: searching for $dir".$dwd." on DWD server");
+ $urlString .= $dir;
+
+ $found = 0;
+ eval {
+ $ftp = Net::FTP->new( "ftp-outgoing2.dwd.de",
+ Debug => 0,
+ Timeout => 10,
+ Passive => $passive,
+ FirewallType => $proxyType,
+ Firewall => $proxyName);
+ Log3($name, 4, "GDS $name: ftp connection established.");
+ if(defined($ftp)){
+ $ftp->login($user, $pass);
+ $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, 4, "GDS $name: retrieving $dataFile");
+ $ua->get($urlString,':content_file' => $targetFile);
+ } 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, 1);
+ };
+ return ($dataFile, $found);
+}
+
+sub getListStationsDropdown($){
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+ my ($line, $liste, @a);
+
+ my $filename = $tempDir.$name."_conditions";
+ return unless -e $filename;
+ my $filesize = -s $filename;
+ return unless $filesize != 0;
+
+ open WXDATA, $filename;
+ while (chomp($line = )) {
+ push @a, trim(substr(latin1ToUtf8($line),0,19));
+ }
+ close WXDATA;
+
+ splice(@a,0,6);
+ splice(@a,first_index { /Höhe/ } @a);
+ splice(@a,-1);
+ @a = sort(@a);
+
+ $liste = join(",", @a);
+ $liste =~ s/\s+,/,/g; # replace multiple spaces followed by comma with comma
+ $liste =~ s/\s/_/g; # replace spaces in stationName with underscore for list in frontende
+ return $liste;
+}
+
+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 createIndexFile($){
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ if($name){
+ my $text = "".
+ "Aktuelle Wetterkarte: Wetterlage
".
+ "Aktuelle Wetterkarte: Vorhersage
".
+ "Aktuelle Wetterkarte: Warnungen
".
+ "Aktuelle Radarkarte
".
+ "";
+ open (DATEI, ">".$tempDir.$name.".html") or die $!;
+ print DATEI $text;
+ close (DATEI);
+ }
+ return;
+}
+
+sub fillMappingTables($){
+
+ $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";
+
+#
+# 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");
+
+return;
+}
+
+sub initDropdownLists($){
+ my($hash) = @_;
+ my $name = $hash->{NAME};
+
+ if ($name){
+ if(-e $tempDir.$name."_conditions"){
+ $sList = getListStationsDropdown($hash);
+ } else {
+ Log3($name, 3, "GDS $name: no datafile (conditions) found");
+ $sList = "please_use_rereadcfg_first";
+ }
+
+ if (-e $tempDir.$name."_alerts.zip"){
+ unzipCapFile($hash);
+ ($aList, undef) = buildCAPList($hash);
+ } else {
+ Log3($name, 3, "GDS $name: no datafile (alerts) found");
+ $aList = "please_use_rereadcfg_first";
+ }
+ } else {
+ $aList = "please_use_rereadcfg_first";
+ $sList = $aList;
+ }
+ 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) = @_;
+ opendir(DIR,$destinationDirectory) or warn "$!";
+ my @files = readdir(DIR);
+ close(DIR);
+ return @files;
+}
+
+sub unzipCapFile($) {
+ my($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my $zipname = $tempDir.$name."_alerts.zip";
+ my $destinationDirectory = $tempDir.$name."_alerts.dir";
+
+ if (-d $destinationDirectory) {
+# delete old files in directory
+ my @remove = _readDir($destinationDirectory);
+ foreach my $f (@remove){
+ next if -d $f;
+ Log3($name, 4, "GDS $name: deleting $destinationDirectory/$f");
+ unlink("$destinationDirectory/$f");
+ }
+ }
+
+# unzip files from archive
+ my $zip = Archive::Zip->new($zipname);
+ foreach my $member ($zip->members)
+ {
+ next if $member->isDirectory;
+ (my $extractName = $member->fileName) =~ s{.*/}{};
+ Log3($name, 4, "GDS $name: extracting $destinationDirectory/$extractName");
+ $member->extractToFileNamed(
+ "$destinationDirectory/$extractName");
+ }
+ Log3($name, 4, "GDS $name: deleting $zipname");
+
+# delete archive file
+ unlink $zipname;
+ return;
+}
+
+1;
+
+
+####################################################################################################
+#
+# Documentation
+#
+####################################################################################################
+#
+# Changelog:
+#
+# 2013-08-07 initial release
+#
+# 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-09 added more logging
+# fixed missing error message if WARNCELLID does not exist
+# update commandref
+#
+# 2013-08-10 added some more tolerance on text inputs
+# modi switched from GetLogList to Log3
+#
+# 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 caplist
+#
+# 2013-08-13 fixed some minor bugs to prevent annoying console messages
+# added support for fhem installtions running on windows-based systems
+#
+# 2013-11-03 added error handling for malformed XML files from GDS
+#
+# 2014-02-04 added ShutdownFn
+# changed FTP Timeout
+#
+# 2014-02-26 added attribute gdsPassiveFtp
+#
+# 2014-05-07 added readings a_onset_local & a_expires_local
+#
+# 2014-05-22 added reading a_sent_local
+#
+# 2014-05-23 added set clear alerts|all
+# fixed some typos in docu and help
+#
+# 2014-10-15 added attr disable
+#
+# 2015-01-03 added multiple alerts handling
+#
+# 2015-01-30 changed use own FWEXT instead of HTTPSRV
+#
+# 2015-04-07 fixed a_X_valid calculation: use onset, too
+#
+# 2015-09-24 fixed prevent fhem crash on empty conditions file
+#
+# 2015-10-05 ------- Start rewrite for new data structures provided by DWD
+#
+# 2015-10-06 changed remove Coro Support
+# remove $useFTP - always use http internally
+# use LWP::Parallel::UserAgent for nonblocking transfers
+# add Archive::ZIP for alert files transfer and unzip
+# apt-get install libarchive-zip-perl
+#
+####################################################################################################
+#
+# 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
+#
+####################################################################################################
+
+=pod
+=begin html
+
+
+GDS
+
+
+ Prerequesits
+
+
+
+ Module uses following additional Perl modules:
+ Net::FTP, List::MoreUtils, XML::Simple, Text::CSV
+ Archive::Zip, LWP::Parallel::UserAgent
+ If not already installed in your environment, please install them using appropriate commands from your environment.
+
+
+
+
+
+ Define
+
+
+
+ define <name> GDS <username> <password>
+
+ This module provides connection to GDS service generated by DWD
+
+
+
+
+
+ Set-Commands
+
+
+
+ set <name> clear alerts|all
+
+
+ - alerts: Delete all a_* readings
+ - all: Delete all a_*, c_* and g_* readings
+
+
+
+ set <name> conditions <stationName>
+
+ Retrieve current conditions at selected station. Data will be updated periodically.
+
+
+ set <name> help
+
+ Show a help text with available commands
+
+
+ set <name> rereadcfg
+
+ Reread all required data from DWD Server manually: station list and CAP data
+
+
+ set <name> update
+
+ Update conditions readings at selected station and restart update-timer
+
+
+ - condition readings generated by SET use prefix "c_"
+ - readings generated by SET will be updated automatically every 60 minutes
+
+
+
+
+
+ Get-Commands
+
+
+
+ get <name> alerts <region>
+
+ Retrieve alert message for selected region from previously read alert file (see rereadcfg)
+
+
+ get <name> conditions <stationName>
+
+ Retrieve current conditions at selected station
+
+
+ get <name> conditionsmap <region>
+
+ Retrieve map (imagefile) showing current conditions at selected station
+
+
+ get <name> forecastsmap <stationName>
+
+ Retrieve map (imagefile) showing forecasts for selected region
+
+
+ get <name> headlines
+
+ Returns a string, containing all alert headlines separated by |
+
+
+ get <name> help
+
+ Show a help text with available commands
+
+
+ get <name> list capstations|data|stations
+
+
+ - capstations: Retrieve list showing all defined warning regions. You can find your WARNCELLID with this list.
+ - data: List current conditions for all available stations in one single table
+ - stations: List all available stations that provide conditions data
+
+
+
+ get <name> radarmap <region>
+
+ Retrieve map (imagefile) containig radar view from selected region
+
+
+ get <name> rereadcfg
+
+ Reread all required data from DWD Server manually: station list and CAP data
+
+
+ get <name> warnings <region>
+
+ Retrieve current warnings report for selected region
+
+
+ - report type VHDL30 = regular report, issued daily
+ - report type VHDL31 = regular report, issued before weekend or national holiday
+ - report type VHDL32 = preliminary report, issued on special conditions
+ - report type VHDL33 = cancel report, issued if necessary to cancel VHDL32
+
+
+
+
+ get <name> warningssmap <region>
+
+ Retrieve map (imagefile) containig current warnings for selected region marked with symbols
+
+ All downloaded mapfiles can be found inside "GDS Files" area in left navigation bar.
+
+
+
+
+
+ Attributes
+
+ - do_not_notify
+ - readingFnAttributes
+
+ - disable - if set, gds will not try to connect to internet
+ - gdsAll - defines filter for "all data" from alert message
+ - gdsDebug - defines filter for debug informations
+ - gdsSetCond - defines conditions area to be used after system restart
+ - gdsLong - show long text fields "description" and "instruction" from alert message in readings
+ - gdsPolygon - show polygon data from alert message in a reading
+
+ - gdsPassiveFtp - set to 1 to use passive FTP transfer
+ - gdsFwName - define firewall hostname in format <hostname>:<port>
+ - gdsFwType - define firewall type in a value 0..7 please refer to cpan documentation for further informations regarding firewall settings.
+
+
+
+ Generated Readings/Events:
+
+
+ - _<readingName> - debug informations
+ - a_X_<readingName> - weather data from CAP alert messages. Readings will NOT be updated automatically
+ a_ readings contain a set of alert inforamtions, X represents a numeric set identifier starting with 0
+ that will be increased for every valid alert message in selected area
+ - a_count - number of currently valid alert messages, can be used for own loop iterations on alert messages
+ - a_valid - returns 1 if at least one of decoded alert messages is valid
+ - c_<readingName> - weather data from SET weather conditions. Readings will be updated every 20 minutes
+ - g_<readingName> - weather data from GET weather conditions. Readings will NOT be updated automatically
+
+
+
+ Author's notes
+
+
+ - Module uses following additional Perl modules:
+ Net::FTP, List::MoreUtils, XML::Simple, Text::CSV
+ If not already installed in your environment, please install them using appropriate commands from your environment.
+
+ - Have fun!
+
+
+
+
+
+=end html
+=begin html_DE
+
+
+GDS
+
+Sorry, keine deutsche Dokumentation vorhanden.
+Die englische Doku gibt es hier: GDS
+
+=end html_DE
+=cut