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