|
From: <pf...@us...> - 2012-05-12 08:21:53
|
Revision: 812
http://openautomation.svn.sourceforge.net/openautomation/?rev=812&view=rev
Author: pfry
Date: 2012-05-12 08:21:46 +0000 (Sat, 12 May 2012)
Log Message:
-----------
Timerfunktion um periodische Schaltuhrfunktion erweitert
Alle Plugins loeschen ihr Konfigurationshash vor dem return -> moeglicherweise lindert das die Memory Leaks
Modified Paths:
--------------
wiregate/plugin/generic/Heizungsregler.pl
wiregate/plugin/generic/Logikprozessor.pl
wiregate/plugin/generic/Szenencontroller.pl
wiregate/plugin/generic/conf.d/Logikprozessor.conf
Modified: wiregate/plugin/generic/Heizungsregler.pl
===================================================================
--- wiregate/plugin/generic/Heizungsregler.pl 2012-05-11 22:17:00 UTC (rev 811)
+++ wiregate/plugin/generic/Heizungsregler.pl 2012-05-12 08:21:46 UTC (rev 812)
@@ -9,7 +9,7 @@
use POSIX qw(floor);
use Math::Round qw(nearest);
-my $use_short_names=0; # 1 fuer GA-Kuerzel (erstes Wort des GA-Namens), 0 fuer die "nackte" Gruppenadresse
+my $use_short_names=1; # 1 fuer GA-Kuerzel (erstes Wort des GA-Namens), 0 fuer die "nackte" Gruppenadresse
# eibgaconf fixen falls nicht komplett indiziert
if($use_short_names && !exists $eibgaconf{ZV_Uhrzeit})
@@ -65,7 +65,7 @@
# Alle Controller-GAs abonnieren, Reglerstati initialisieren
for my $r (grep ref($house{$_}), keys %house)
{
- $plugin_subscribe{$house{$r}{control}}{$plugname}=1;
+ $plugin_subscribe{groupaddress($house{$r}{control})}{$plugname}=1;
RESET($r);
}
@@ -109,18 +109,28 @@
if(defined $Vreq)
{
$Vreq=$house{inflow_max} if defined $house{inflow_max} && $Vreq>$house{inflow_max};
- knx_write($house{inflow_control},$Vreq,9.001) if defined $house{inflow_control};
+ knx_write(groupaddress($house{inflow_control}),$Vreq,9.001) if defined $house{inflow_control};
$retval.=sprintf "Vreq=%d", $Vreq;
$anynews=1;
}
$retval=~s/\s*$//; # Space am Ende entfernen
- return unless $anynews;
+ unless($anynews)
+ {
+ for my $k (keys %house) { delete $house{$k}; } # Hilfe fuer die Garbage Collection
+ for my $k (keys %dyn) { delete $dyn{$k}; } # Hilfe fuer die Garbage Collection
+ return;
+ }
}
elsif($event=~/bus/)
{
- return if $msg{apci} eq 'A_GroupValue_Response';
+ if($msg{apci} eq 'A_GroupValue_Response')
+ {
+ for my $k (keys %house) { delete $house{$k}; } # Hilfe fuer die Garbage Collection
+ for my $k (keys %dyn) { delete $dyn{$k}; } # Hilfe fuer die Garbage Collection
+ return;
+ }
# Aufruf durch GA - neue Wunschtemperatur
my $ga=$msg{dst};
@@ -142,6 +152,8 @@
$T0=$dyn{$r}{T0};
$T0=$dyn{$r}{T0old} if $dyn{mode} eq 'OPTIMIZE';
knx_write($ga,$T0,9.001);
+ for my $k (keys %house) { delete $house{$k}; } # Hilfe fuer die Garbage Collection
+ for my $k (keys %dyn) { delete $dyn{$k}; } # Hilfe fuer die Garbage Collection
return;
}
@@ -155,8 +167,13 @@
}
elsif($T0==-1)
{
- return if $dyn{$r}{mode} eq 'OPTIMIZE'; # Entprellen
-
+ if($dyn{$r}{mode} eq 'OPTIMIZE') # Entprellen
+ {
+ for my $k (keys %house) { delete $house{$k}; } # Hilfe fuer die Garbage Collection
+ for my $k (keys %dyn) { delete $dyn{$k}; } # Hilfe fuer die Garbage Collection
+ return;
+ }
+
# Initialisierung der Optimierungsfunktion
$dyn{$r}{mode}='OPTIMIZE';
$dyn{$r}{T0old}=$dyn{$r}{T0};
@@ -167,8 +184,13 @@
}
else # neue Wunschtemperatur
{
- return if $dyn{$r}{T0} == $T0; # Entprellen
-
+ if($dyn{$r}{T0} == $T0) # Entprellen
+ {
+ for my $k (keys %house) { delete $house{$k}; } # Hilfe fuer die Garbage Collection
+ for my $k (keys %dyn) { delete $dyn{$k}; } # Hilfe fuer die Garbage Collection
+ return;
+ }
+
RESET($r) if $mode eq 'OPTIMIZE'; # Optimierung unterbrochen
$dyn{$r}{mode}='ON'; # ansonsten uebrige Werte behalten
$dyn{$r}{T0}=$T0;
@@ -186,6 +208,8 @@
# Speichere Statusvariablen aller Regler
store_to_plugin_info(\%dyn);
+for my $k (keys %house) { delete $house{$k}; } # Hilfe fuer die Garbage Collection
+for my $k (keys %dyn) { delete $dyn{$k}; } # Hilfe fuer die Garbage Collection
return $retval eq '' ? undef : $retval;
@@ -291,7 +315,9 @@
{
if(defined $ss->{$type})
{
- my $sensorlist=(ref $ss->{$type})?$ss->{$type}:[$ss->{$type}];
+ my $sensorlist=groupaddress($ss->{$type});
+ $sensorlist=[$sensorlist] unless ref $sensorlist eq 'ARRAY';
+
for my $s (@{$sensorlist})
{
unless(defined $T{$type}{$s})
@@ -342,7 +368,7 @@
{
if(!defined $R{$type} && defined $house{$type})
{
- $R{$type} = knx_read($house{$type},$house{cycle},9);
+ $R{$type} = knx_read(groupaddress($house{$type}),$house{cycle},9);
delete $R{$type} unless $R{$type};
}
}
@@ -373,17 +399,12 @@
{
if(defined $ss->{actuator})
{
- unless(ref $ss->{actuator})
+ $ss->{actuator}=[$ss->{actuator}] unless ref $ss->{actuator} eq 'ARRAY';
+
+ for my $s (@{$ss->{actuator}})
{
- knx_write($ss->{actuator},100*$U,5.001); # DPT NOCH UNKLAR ########
+ knx_write(groupaddress($s),100*$U,5.001); # DPT NOCH UNKLAR ########
}
- else
- {
- for my $s (@{$ss->{actuator}})
- {
- knx_write($s,100*$U,5.001);
- }
- }
}
}
# plugin_log($plugname, "Done trying to write $r, $U");
@@ -762,3 +783,76 @@
return sprintf "t=%dh:%02dmin Tv=%.1fmin Tn=%dmin lim=%.1f prop=%.1f spread=%.1f ", $tp/3600,($tp/60)%60,$Tv,$Tn,$lim,$prop,$refspread;
}
+
+# Umgang mit GA-Kurznamen und -Adressen
+
+sub groupaddress
+{
+ my $short=shift;
+
+ return unless defined $short;
+
+ if(ref $short)
+ {
+ my $ga=[];
+ for my $sh (@{$short})
+ {
+ if($sh!~/^[0-9\/]+$/ && defined $eibgaconf{$sh}{ga})
+ {
+ push @{$ga}, $eibgaconf{$sh}{ga};
+ }
+ else
+ {
+ push @{$ga}, $sh;
+ }
+ }
+ return $ga;
+ }
+ else
+ {
+ my $ga=$short;
+
+ if($short!~/^[0-9\/]+$/ && defined $eibgaconf{$short}{ga})
+ {
+ $ga=$eibgaconf{$short}{ga};
+ }
+
+ return $ga;
+ }
+}
+
+sub shortname
+{
+ my $gas=shift;
+
+ return unless defined $gas;
+ return $gas unless $use_short_names;
+
+ if(ref $gas)
+ {
+ my $sh=[];
+ for my $ga (@{$gas})
+ {
+ if($ga=~/^[0-9\/]+$/ && defined $eibgaconf{$ga}{short})
+ {
+ push @{$sh}, $eibgaconf{$ga}{short};
+ }
+ else
+ {
+ push @{$sh}, $ga;
+ }
+ }
+ return $sh;
+ }
+ else
+ {
+ my $sh=$gas;
+
+ if($gas=~/^[0-9\/]+$/ && defined $eibgaconf{$gas}{short})
+ {
+ $sh=$eibgaconf{$gas}{short};
+ }
+
+ return $sh;
+ }
+}
Modified: wiregate/plugin/generic/Logikprozessor.pl
===================================================================
--- wiregate/plugin/generic/Logikprozessor.pl 2012-05-11 22:17:00 UTC (rev 811)
+++ wiregate/plugin/generic/Logikprozessor.pl 2012-05-12 08:21:46 UTC (rev 812)
@@ -125,7 +125,7 @@
# transmit-Adresse abonnieren
my $transmit=groupaddress($logic{$t}{transmit});
$plugin_subscribe{$transmit}{$plugname}=1;
- plugin_log($plugname, "\$logic{$t}: Transmit-GA $transmit abonniert") if $debug;
+ plugin_log($plugname, "\$logic{$t}: Transmit-GA $transmit nicht in %eibgaconf gefunden") if $debug && !exists $eibgaconf{$transmit};
# Zaehlen und Logeintrag
$count++;
@@ -146,15 +146,15 @@
unless(ref $receive)
{
$plugin_subscribe{$receive}{$plugname}=1;
- plugin_log($plugname, "\$logic{$t}: Receive-GA $receive abonniert") if $debug;
+ plugin_log($plugname, "\$logic{$t}: Receive-GA $receive nicht in %eibgaconf gefunden") if $debug && !exists $eibgaconf{$receive};
}
else
{
for my $rec (@{$receive})
{
$plugin_subscribe{$rec}{$plugname}=1;
+ plugin_log($plugname, "\$logic{$t}: Receive-GA $rec nicht in %eibgaconf gefunden") if $debug && !exists $eibgaconf{$rec};
}
- plugin_log($plugname, "\$logic{$t}: Receive-GAs (".join(",",@{$receive}).") abonniert") if $debug;
}
}
@@ -211,8 +211,7 @@
my $result=$plugin_info{$plugname.'_'.$t.'_result'};
if(defined $result)
{
- plugin_log($plugname, "$ga:Lesetelegramm -> \$logic{$t}{transmit}(memory) -> $ga:$result gesendet ("
- .sprintf("%0.1f",time()-$stime)."s)") if $debug;
+ $retval.="$ga:Lesetelegramm -> \$logic{$t}{transmit}(memory) -> $ga:$result gesendet. " if $debug;
$stime=time();
knx_write($ga, $result);
}
@@ -240,7 +239,7 @@
# Cool-Periode definiert und noch nicht abgelaufen?
if(defined $plugin_info{$plugname.'__'.$t.'_cool'} && $plugin_info{$plugname.'__'.$t.'_cool'}>time())
{
- plugin_log($plugname, "$ga:$in -> \$logic{$t}{receive}(Cool)") if $debug;
+ $retval.="$ga:$in -> \$logic{$t}{receive}(Cool) " if $debug;
next;
}
@@ -255,15 +254,14 @@
# In bestimmten Sonderfaellen nichts schicken
unless(defined $result) # Resultat undef => nichts senden
{
- plugin_log($plugname, "$ga:$in -> \$logic{$t}{receive}(Logik) -> nichts zu senden (".sprintf("%0.1f",time()-$stime)."s)") if $debug;
+ $retval.="$ga:$in -> \$logic{$t}{receive}(Logik) -> nichts zu senden " if $debug;
$stime=time();
next;
}
if($logic{$t}{transmit_only_on_request})
{
- plugin_log($plugname, "$ga:$in -> \$logic{$t}{receive}(Logik) -> $transmit:$result gespeichert (".sprintf("%0.1f",time()-$stime)."s)")
- if $debug;
+ $retval.="$ga:$in -> \$logic{$t}{receive}(Logik) -> $transmit:$result gespeichert " if $debug;
$stime=time();
next;
}
@@ -273,15 +271,13 @@
{
$plugin_info{$plugname.'__'.$t.'_timer'}=$systemtime+$logic{$t}{delay};
$plugin_info{$plugname.'__'.$t.'_cool'}=time()+$logic{$t}{delay}+$logic{$t}{cool} if defined $logic{$t}{cool};
- plugin_log($plugname, "$msg{src} $ga:$in -> \$logic{$t}{receive}(Logik) -> $transmit:$result, zu senden in ".$logic{$t}{delay}."s ("
- .sprintf("%0.1f",time()-$stime)."s)") if $debug;
+ $retval.="$msg{src} $ga:$in -> \$logic{$t}{receive}(Logik) -> $transmit:$result, zu senden in ".$logic{$t}{delay}."s " if $debug;
$stime=time();
}
else
{
knx_write($transmit, $result);
- plugin_log($plugname, "$msg{src} $ga:$in -> \$logic{$t}{receive}(Logik) -> $transmit:$result gesendet ("
- .sprintf("%0.1f",time()-$stime)."s)") if $debug;
+ $retval.="$msg{src} $ga:$in -> \$logic{$t}{receive}(Logik) -> $transmit:$result gesendet " if $debug;
$stime=time();
# Cool-Periode starten
@@ -317,9 +313,8 @@
{
# zu sendendes Resultat = zuletzt berechnetes Ergebnis der Logik
$result=$plugin_info{$plugname.'_'.$t.'_result'};
- plugin_log($plugname, "\$logic{$t} -> $transmit:".
- (defined $result?$result.($toor?" gespeichert":" gesendet"):"nichts zu senden")." (delayed) ("
- .sprintf("%0.1f",time()-$stime)."s)") if $debug;
+ $retval.="\$logic{$t} -> $transmit:".
+ (defined $result?$result.($toor?" gespeichert":" gesendet"):"nichts zu senden")." (delayed) " if $debug;
$stime=time();
}
else
@@ -327,9 +322,8 @@
# ...es sei denn, es ist eine timer-Logik. Die muss jetzt ausgefuehrt werden
# Aufruf der Logik-Engine
$result=execute_logic($t, groupaddress($logic{$t}{receive}), undef, undef);
- plugin_log($plugname, "\$logic{$t} -> $transmit:".
- (defined $result?$result.($toor?" gespeichert":" gesendet"):"nichts zu senden")." (Timer) ("
- .sprintf("%0.1f",time()-$stime)."s)") if $debug;
+ $retval.="\$logic{$t} -> $transmit:".
+ (defined $result?$result.($toor?" gespeichert":" gesendet"):"nichts zu senden")." (Timer) " if $debug;
$stime=time();
}
@@ -357,18 +351,20 @@
}
else
{
- my $cycle=$nexttimer-time();
+ my $cycle=int($nexttimer-time());
$cycle=1 if $cycle<1;
$plugin_info{$plugname."_cycle"}=$cycle;
- plugin_log($plugname, "Cycle (Timer) gestellt auf ".$cycle."s") if $logic{debug};
+ $retval.="Cycle (Timer) gestellt auf ".$cycle."s" if $logic{debug};
}
+# experimentell - wir helfen der Garbage Collection etwas nach...
+for my $k (keys %logic) { delete $logic{$k}; }
return unless $retval;
return $retval;
# Fuer Logiken mit timer-Klausel: Zeit des naechsten Aufrufs bestimmen
-
+# Fuer einen Tag den jeweils naechsten berechnen
sub next_day
{
my $d=shift;
@@ -386,6 +382,7 @@
return $d;
}
+# Passt ein bestimmtes Datum auf das Schema in einer "Schedule"?
sub schedule_matches_day
{
my ($schedule,$day)=@_;
@@ -407,6 +404,8 @@
return $match;
}
+# Fuer eine bestimmte Timer-Logik den naechsten Aufruf berechnen (relativ komplexes Problem wegen der
+# vielen moeglichen Konfigurationen und Konstellationen)
sub set_next_call
{
my ($t,$debug)=@_; # der relevante Eintrag in %logic, und das Debugflag
@@ -431,17 +430,20 @@
for my $s (@{$schedule})
{
+ # Timereintrag pruefen und standardisieren
unless(ref $s eq 'HASH')
{
plugin_log($plugname, "Logiktimer zu Logik '$t' ist kein Hash oder Liste von Hashes");
next;
}
+
unless(defined $s->{time})
{
plugin_log($plugname, "Logiktimer zu Logik '$t' enthaelt mindestens einen Eintrag ohne Zeitangabe (time=>...)");
next;
}
+ # Eintrag pruefen und standardisieren
for my $k (keys %{$s})
{
unless($k=~/^(year|month|calendar_week|day_of_month|day_of_week|time)$/)
@@ -461,6 +463,33 @@
@{$s->{$k}}=sort @{$s->{$k}}; # alle Listen sortieren
}
+ # Expandieren periodischer Zeitangaben, das sind Zeitangaben der Form
+ # time=>'08:00+30min' - ab 08:00 alle 30min
+ # time=>'08:00+5min-09:00' - ab 08:00 alle 5min mit Ende 09:00
+ if(grep /\+/, @{$s->{time}})
+ {
+ my $newtime=[];
+ for my $ts (@{$s->{time}})
+ {
+ unless($ts=~/^(.*?)([0-9][0-9]):([0-9][0-9])\+([1-9][0-9]*)(m|h)(?:\-([0-9][0-9]):([0-9][0-9]))?(.*?)$/)
+ {
+ push @{$newtime}, $ts;
+ }
+ else
+ {
+ my ($head,$t1,$period,$t2h,$t2m,$tail)=($1,$2*60+$3,$4*($5 eq 'h'?60:1),$6,$7,$8);
+ my $t2 = (defined $t2h ? $t2h : 24)*60 + (defined $t2m ? $t2m : 0);
+
+ for(my $tm=$t1; $tm<=$t2; $tm+=$period)
+ {
+ push @{$newtime}, sprintf("$head%02d:%02d$tail",$tm/60,$tm%60);
+ }
+ }
+ }
+ @{$s->{time}} = sort @{$newtime};
+ plugin_log($plugname, "\$logic{$t} Aufrufzeiten: ".join " ", @{$newtime}) if $debug;
+ }
+
# Steht heute aus diesem Schedule noch ein Termin an?
next unless schedule_matches_day($s,$today) && $s->{time}[-1] gt $time_of_day;
@@ -511,11 +540,12 @@
}
}
-
# Es folgt die eigentliche Logik-Engine
+# Im wesentlichen Vorbesetzen von input und state, Aufrufen der Logik, Zurueckschreiben von state
sub execute_logic
{
- my ($t, $receive, $ga, $in)=@_; # Logikindex $t, Bustelegramm erhalten auf $ga mit Inhalt $in
+ my ($t, $receive, $ga, $in)=@_; # Logikindex $t, Bustelegramm erhalten auf $ga mit Inhalt $in
+ # $receive muss die direkten Gruppenadressen enthalten - Decodierung von Kuerzeln wird nicht vorgenommen
# als erstes definiere das Input-Array fuer die Logik
my $input=$in;
@@ -532,8 +562,6 @@
$input=();
for my $rec (@{$receive})
{
- my $rec=groupaddress($rec);
-
if($ga eq $rec)
{
push @{$input}, $in;
@@ -610,7 +638,6 @@
return $result;
}
-
# Umgang mit GA-Kurznamen und -Adressen
sub groupaddress
Modified: wiregate/plugin/generic/Szenencontroller.pl
===================================================================
--- wiregate/plugin/generic/Szenencontroller.pl 2012-05-11 22:17:00 UTC (rev 811)
+++ wiregate/plugin/generic/Szenencontroller.pl 2012-05-12 08:21:46 UTC (rev 812)
@@ -97,7 +97,11 @@
elsif($event=~/bus/)
{
# nur auf Write-Telegramme reagieren
- return if $msg{apci} ne 'A_GroupValue_Write';
+ if($msg{apci} ne 'A_GroupValue_Write')
+ {
+ for my $k (keys %scene) { delete $scene{$k}; } # Hilfe fuer die Garbage Collection
+ return;
+ }
# Aufruf durch GA
my $ga=$msg{dst};
@@ -107,6 +111,7 @@
unless($plugin_info{$plugname.'__SceneLookup'}=~/(St|Rc)\($ga\)=>\'(.+?)\',/)
{
delete $plugin_subscribe{$ga}{$plugname}; # unbekannte GA
+ for my $k (keys %scene) { delete $scene{$k}; } # Hilfe fuer die Garbage Collection
return;
}
@@ -116,7 +121,11 @@
if($eibgaconf{$ga}{DPTSubId} eq '1.017')
{
# Szenennummer aus physikalischer Adresse ableiten falls DPTSubId==1.017
- return unless $msg{src}=~/[0-9]+\.[0-9]+\.([0-9]+)/;
+ unless($msg{src}=~/[0-9]+\.[0-9]+\.([0-9]+)/)
+ {
+ for my $k (keys %scene) { delete $scene{$k}; } # Hilfe fuer die Garbage Collection
+ return;
+ }
$n=$1;
}
elsif($scene{$sc}{store} eq $scene{$sc}{recall})
@@ -184,6 +193,7 @@
}
}
+for my $k (keys %scene) { delete $scene{$k}; } # Hilfe fuer die Garbage Collection
return unless $retval;
return $retval;
Modified: wiregate/plugin/generic/conf.d/Logikprozessor.conf
===================================================================
--- wiregate/plugin/generic/conf.d/Logikprozessor.conf 2012-05-11 22:17:00 UTC (rev 811)
+++ wiregate/plugin/generic/conf.d/Logikprozessor.conf 2012-05-12 08:21:46 UTC (rev 812)
@@ -87,18 +87,20 @@
},
# Wenn state ein Hash ist, wird der letzte gesendete Wert in $state->{result} gespeichert.
- # 10. Eine Timer-Funktion. Hier eine einfache Zeitschaltuhr, die immer um 8 Uhr und um 10:00 am jeweils zweiten
- # Dienstag jedes Monats eine 1 auf Transmit sendet
- wecker => { transmit=>'10/1/15', timer=>{ time=>['08:00','10:00'], day_of_month=>[(8..14)], day_of_week=>'Di' }, translate => 1 },
+ # 10. Eine Timer-Funktion. Hier eine Zeitschaltuhr, die immer am jeweils zweiten Dienstag jedes Monats
+ # um 08:00, um 10:00, zwischen 09:00 und 09:30 alle 2min und zwischen 18:00 und 20:00 jede volle Stunde
+ # eine 1 auf Transmit sendet
+ wecker => { transmit=>'10/1/15', timer=>{ time=>['08:00','10:00','09:00+2m-09:30','18:00+1h-20:00'],
+ day_of_month=>[(8..14)], day_of_week=>'Di' }, translate => 1 },
# Logiken mit timer-Klausel weichen in mehreren Punkten von den bisherigen Logiken ab:
# * sie ignorieren die delay-Klausel und die cool-Klausel
- # * transmit_only_on_request funktioniert
- # * jeglicher Bustraffic auf receive-Adressen wird ignoriert. (Diese werden aber beim Timer-Aufruf abgefragt,
- # um das input-Array vorzubesetzen).
- # Als timer-Eintrag geht entweder ein einzelnes Hash timer=>{...} wie oben oder eine Liste solcher Eintraege
- # time=>[{...},{...},{...},...]. Jeder Eintrag MUSS eine Spezifikation time=>'XX:XX' enthalten (auch das darf wieder
- # eine Liste sein) und DARF zusaetzliche, die Geltungstage einschraenkende Klauseln wie year, month, day_of_month,
- # calendar_week, day_of_week (Mo...So oder Mon...Sun oder 1...7) enthalten.
+ # * Die transmit_only_on_request-Klausel funktioniert aber
+ # * jeglicher Bustraffic auf receive-Adressen wird ignoriert.
+ # * Evtl. spezifizierte receive-Adressen werden aber beim Timer-Aufruf abgefragt, um das input-Array vorzubesetzen).
+ # Als timer-Eintrag geht entweder ein einzelner Eintrag timer=>{...} wie oben oder eine Liste solcher Eintraege
+ # time=>[{...},{...},{...},...]. Jeder Eintrag MUSS eine Spezifikation time=>... (Varianten siehe Beispiel) enthalten
+ # und DARF zusaetzliche, die Geltungstage einschraenkende Klauseln wie year, month, day_of_month,
+ # calendar_week, day_of_week (Mo...So oder Mon...Sun oder 1...7) enthalten. Alle diese duerfen wieder Listen sein.
# 11. Schlussendlich wieder mal Werbung fuer die GA-Kurznamen. Setzt man im Skript Logikprozessor.pl $use_short_names=1
# und verwendet GA-Namen mit eindeutigem Kuerzel (=erstes Wort des Namens), so funktioniert auch das folgende:
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|