Hallo,
seit einigen Wochen (oder schon Monaten?) habe ich den Versuch einer einfach Logikengine in der virtuellen Schublade liegen. Damals wollte ich so etwas wie eine "Tabellen-Logik" realisieren, damit meine ich die Umsetzung der Logik mit Befehlen einer Tabellenkalkulation, z.B.
WENN(UND("1/1/1 = 1","1/1/3 = 1"),1,0);
Es sieht kompliziert aus ist es aber eigentlich nicht, da es dem Muster der Tabellenkalkulationsprogramme folgt (z.B. WENN(<Bedingung>, Dann, Sonst).
Als ich den Thread über die Logik gesehen habe, ist mir das Logik-Projekt wieder eingefallen und auch die offenen Fragen.
Da ich keinen Homeserver oder ähnliches zum Testen habe, die erste Frage: Wie kommen diese Logikengines an den Status der GAs beim ersten Start? Bei meiner jetzigen Lösung muss ich immer das Lesen Flag bei den Aktoren setzen um deren Status zu erhalten, muss ich das z.B. beim HS auch machen wenn ich die Logikengine nutze oder habe ich einen ganz falschen Ansatz (Denkfehler?!?)?
Meine Lösung verursacht bei komplizierten Logiken eine Flut an Leseanforderungen, dass kann nicht gut sein. Beim Abarbeiten des Logik_Skripts wird für jede GA eine Abfrage gestartet (sub read_value), gibt es da eine bessere Lösung?
Hier mal der erste frühe (nicht funktionsfähige) Code (mit kleiner Hilfe im Anschluss):
Hinweis zum Verständnis: die WENN-Funktion prüft ob es sich um eine GA, einen Wert , Zeit oder Bedingung handelt und gibt entsprechende Rückgabewerte, die anderen Funktionen nutzen WENN um die Eingabewerte zu prüfen.
Hier kommt der Hilfetext:
Für Hinweise wäre ich dankbar und eventuell auch eine Meinung, ob so eine Logikengine überhaupt sinn macht (natürlich nichts für große Projekte)...
seit einigen Wochen (oder schon Monaten?) habe ich den Versuch einer einfach Logikengine in der virtuellen Schublade liegen. Damals wollte ich so etwas wie eine "Tabellen-Logik" realisieren, damit meine ich die Umsetzung der Logik mit Befehlen einer Tabellenkalkulation, z.B.
WENN(UND("1/1/1 = 1","1/1/3 = 1"),1,0);
Es sieht kompliziert aus ist es aber eigentlich nicht, da es dem Muster der Tabellenkalkulationsprogramme folgt (z.B. WENN(<Bedingung>, Dann, Sonst).
Als ich den Thread über die Logik gesehen habe, ist mir das Logik-Projekt wieder eingefallen und auch die offenen Fragen.
Da ich keinen Homeserver oder ähnliches zum Testen habe, die erste Frage: Wie kommen diese Logikengines an den Status der GAs beim ersten Start? Bei meiner jetzigen Lösung muss ich immer das Lesen Flag bei den Aktoren setzen um deren Status zu erhalten, muss ich das z.B. beim HS auch machen wenn ich die Logikengine nutze oder habe ich einen ganz falschen Ansatz (Denkfehler?!?)?
Meine Lösung verursacht bei komplizierten Logiken eine Flut an Leseanforderungen, dass kann nicht gut sein. Beim Abarbeiten des Logik_Skripts wird für jede GA eine Abfrage gestartet (sub read_value), gibt es da eine bessere Lösung?
Hier mal der erste frühe (nicht funktionsfähige) Code (mit kleiner Hilfe im Anschluss):
Code:
# Plugin für eine einfache "Tabellenkalkulations-"Logik # use Time::HiRes qw( sleep ); ############################################################################## ### Definitionen ### ############################################################################## my $max_alter = "1"; # Maximales Alter von Werten im Cache in Sekunden ############################################################################## ### Logik ### ############################################################################## # Beispiele: #WENN(UND("1/1/12",ODER(&UHRZEIT.">23:00",&UHRZEIT."<06:00"),"1/1/14<2"),30,"-","1/1/14",5); # Licht mit 30% einschalten #WENN(UND("1/1/12",&UHRZEIT."<23:00",&UHRZEIT.">06:00","1/1/14<2"),100,"-","1/1/14",5); # Licht mit 100% einschalten #WENN("1/1/12","-",1,"1/1/14",5); # Licht ausschalten ############################################################################## ### Ende Logik ### ############################################################################## # Eigenen Aufruf-Zyklus ausschalten # der Aufrufzyklus ist unabhängig von der Taktzeit und muss kürzer sein! $plugin_info{$plugname.'_cycle'} = 86400; return &UHRZEIT; ############################################################################## ### Funktionen ### ############################################################################## sub UND { # UND-Funktion (AND) foreach my $GA (@_) { if ( WENN($GA) == 0) { return 0; } } return 1; } sub ODER { # ODER-Funktion (OR) foreach my $GA (@_) { if ( WENN($GA) == 1) { return 1; } } return 0; } sub NICHTUND { # Nicht-UND-Funktion (NAND) foreach my $GA (@_) { if ( WENN($GA) == 0) { return 1; } } return 0; } sub NUND { # Nicht-UND-Funktion (NAND) return NICHTUND(@_); } sub NICHTODER { # Nicht-ODER-Funktion (NOR) foreach my $GA (@_) { if ( WENN($GA) == 1) { return 0; } } return 1; } sub NODER { # Nicht-ODER-Funktion (NOR) return NICHTODER(@_); } sub NICHT { # Nicht-Funktion (NOT) if ( WENN($_[0]) eq "0" ) { return 1; } else { return 0; } } sub ExklusivODER { # Exklusiv ODER-Funktion (Antivalenz, XOR) my $result; my $value; foreach my $GA (@_) { $value = WENN($GA); if ($result == "") { $result = $value; } else { if ($value != $result) { return 1; } else { $result = $value; } } } return 0; } sub EODER { # Exklusiv ODER-Funktion (Antivalenz, XOR) return ExklusivODER(@_); } sub ExklusivNICHTODER { # Exklusiv Nicht-ODER-Funktion (Äquivalenz, XNOR) my $result; my $value; foreach my $GA (@_) { $value = WENN($GA); if ($result == "") { $result = $value; } else { if ($value != $result) { return 0; } else { $result = $value; } } } return 1; } sub ENODER { # Exklusiv Nicht-ODER-Funktion (Äquivalenz, XNOR) return ExklusivNICHTODER(@_); } sub WENN { # WENN-Funktion (IF) my ($bedingung, $ret_true, $ret_false, $GA, $DPT) = @_; my ($value1, $value2, $operation); $bedingung =~ m/([\w:\/]+)\s*([<>=!]+)\s*([\w:\/]+)/; my $value1 = $1; my $operation = $2; my $value2 = $3; $value1 = $bedingung if ($value1 eq ""); # Gruppenadressen einlesen $value1 = read_value($value1) if ($value1 ne ""); $value2 = read_value($value2) if ($value2 ne ""); # Wenn Zeiten dann in Sekunden umrechnen if ($value1 =~ m/(\d+):(\d+):*(\d*)/) { $value1 = $1 * 3600 + $2 * 60 + $3; } if ($value2 =~ m/(\d+):(\d+):*(\d*)/) { $value2 = $1 * 3600 + $2 * 60 + $3; } # Wenn keine Rückgabewerte übergeben werden, diese initialisieren if ($ret_false eq "" && $ret_true eq "") { $ret_false = 0; $ret_true = 1; } my $ret = $ret_false; # Bedingung prüfen if ($operation eq "=" || $operation eq "==") { $ret = $ret_true if ($value1 eq $value2); } elsif ($operation eq ">") { $ret = $ret_true if ($value1 > $value2); } elsif ($operation eq ">=" || $operation eq "=>") { $ret = $ret_true if ($value1 >= $value2); } elsif ($operation eq "<") { $ret = $ret_true if ($value1 < $value2); } elsif ($operation eq "<=" || $operation eq "=<") { $ret = $ret_true if ($value1 <= $value2); } elsif ($operation eq "<>" || $operation eq "!=") { $ret = $ret_true if ($value1 ne $value2); } else { $ret = $ret_true if ($value1 != 0); } # Prüfen ob GA übergeben wurde if ($GA ne "" && $ret ne "-") { AUSGABE($GA,$ret,$DPT); } $ret = 0 if ($ret eq "-"); return $ret; } sub AUSGABE { # Wert Ausgabe (Out) # 0. Wert: Gruppenadresse, 1. Wert: Ausgabewert, 2. Wert: DPT if ($_[1] ne "") { if ($_[2] eq "") { knx_write($_[0],$_[1]); } else { knx_write($_[0],$_[1],$_[2]); } return 1; } return 0; } sub EINVERZ { # Einschaltverzögerung # 0. Wert: GA Trg, 1. Wert: Zeit, 2. Wert GA Ausgang, 3. Wert DPT my ($GA_Trg, $time_value, $GA, $DPT) = @_; my $id = "EINVERZ_".$GA_Trg."_".$time_value."_".$GA."_".$DPT; $GA_Trg = WENN($GA_Trg); my $value_out = 0; if ( $GA_Trg == 1 && check_time($id, $time_value) == 1 ) { $value_out = 1; if ( $GA ne "" ) { if ($DPT eq "") { knx_write($GA,$value_out); } else { knx_write($GA,$value_out,$DPT); } } delete $plugin_info{$id} if ($GA_Trg == 0); } return $value_out; } sub AUSVERZ { # Ausschaltverzögerung # 0. Wert: GA Trg, 1. Wert: Zeit, 2. Wert GA Ausgang, 3. Wert DPT my ($GA_Trg, $time_value, $GA, $DPT) = @_; my $id = "AUSVERZ_".$time_value."_".$GA."_".$DPT; $GA_Trg = WENN($GA_Trg); my $value_out = 1; if ($GA_Trg == 1) { if (not exists($plugin_info{$id."_Ein"})) { $plugin_info{$id."_Ein"} = 1; if ( $GA ne "" ) { if ($DPT eq "") { knx_write($GA,$value_out); } else { knx_write($GA,$value_out,$DPT); } } } } else { if ($plugin_info{$id."_Ein"} == 1 && check_time($id, $time_value) == 1) { $value_out = 0; if ( $GA ne "" ) { if ($DPT eq "") { knx_write($GA,$value_out); } else { knx_write($GA,$value_out,$DPT); } } delete $plugin_info{$id}; delete $plugin_info{$id."_Ein"}; } } return $value_out; } sub EINAUSVERZ { # Ein-/Ausschaltverzögerung # 0. Wert: GA Trg, 1. Wert: Zeit Ein, 2. Wert: Zeit Aus 3. Wert: GA Ausgang, 4. Wert: DPT my ($GA_Trg, $time_value_ein, $time_value_aus, $GA, $DPT) = @_; my $id_ein = "EINVERZ_".$time_value_ein."_".$time_value_aus."_".$GA."_".$DPT; $GA_Trg = WENN($GA_Trg); if ($GA_Trg == 1) { my $value_out = 0; if ( check_time($id_ein, $time_value_ein) == 1 ) { $value_out = 1; if ( $GA ne "" ) { if ($DPT eq "") { knx_write($GA,$value_out); } else { knx_write($GA,$value_out,$DPT); } } $plugin_info{$id_ein."_Ein"} = 1; } return $value_out; } else { my $id_aus = "AUSVERZ_".$time_value_ein."_".$time_value_aus."_".$GA."_".$DPT; my $value_out = 1; if ( $plugin_info{$id_ein."_Ein"} == 1 && check_time($id_aus, $time_value_aus) == 1 ) { $value_out = 0; if ( $GA ne "" ) { if ($DPT eq "") { knx_write($GA,$value_out); } else { knx_write($GA,$value_out,$DPT); } } delete $plugin_info{$id_ein}; delete $plugin_info{$id_aus}; delete $plugin_info{$id_aus."_Aus"}; delete $plugin_info{$id_ein."_Ein"}; } return $value_out; } } sub IMPULS { # Impuls # 0. Wert: GA Trg, 1. Wert: Zeit, 2. Wert GA Ausgang, 3. Wert DPT my ($GA_Trg, $time_value, $GA, $DPT) = @_; my $id = "IMPULS_".$time_value."_".$GA."_".$DPT; $GA_Trg = WENN($GA_Trg); my $value_out = 0; $plugin_info{$id} = 1 if ($GA_Trg == 1); if ( $plugin_info{$id} == 1 && check_time($id, $time_value) == 1) { $value_out = 1; if ( $GA ne "" ) { if ($DPT eq "") { knx_write($GA,$value_out); } else { knx_write($GA,$value_out,$DPT); } } delete $plugin_info{$id} if ($GA_Trg == 0); } return $value_out; } sub ZAEHLER { # Vor- und Rückwärtszähler # 0. Wert: Cnt, 1. Wert: Dir, 2. Wert: Reset, 3. Wert: Schwelle_Ein, 4. Wert: Schwelle_Aus, 5. Wert: GA_Schaltausgang, 6. Wert: Schalt_DPT, 7. Wert: GA_Zählstand, 8. Wert: GA_Zählstand_DPT my ($GA_Cnt, $GA_Dir, $GA_Reset, $Ein, $Aus, $GA_Schalt, $DPT_Schalt, $GA_Stand, $DPT_Stand) = @_; my $id = "ZAEHLER_".$Ein."_".$Aus."_".$GA_Schalt."_".$DPT_Schalt."_".$GA_Stand."_".$DPT_Stand; $plugin_info{$id."_Cnt"} = 0 if (not exists($plugin_info{$id."_Cnt"})); $plugin_info{$id."_Stand"} = 0 if (not exists($plugin_info{$id."_Stand"})); $GA_Cnt = WENN($GA_Cnt); $GA_Dir = WENN($GA_Dir); $GA_Reset = WENN($GA_Reset); my $schalt_value = 0; if ( $plugin_info{$id."_Cnt"} == 0 && $GA_Cnt == 1) { if ($GA_Dir == 1) { $plugin_info{$id."_Stand"} = $plugin_info{$id."_Stand"} + 1; if ($plugin_info{$id."_Stand"} >= $Ein) { if ( $GA_Schalt ne "" ) { if ($DPT_Schalt eq "") { knx_write($GA_Schalt,1); } else { knx_write($GA_Schalt,1,$DPT_Schalt); } } $schalt_value = 1; } } else { $plugin_info{$id."_Stand"} = $plugin_info{$id."_Stand"} - 1; if ($plugin_info{$id."_Stand"} <= $Aus) { if ( $GA_Schalt ne "" ) { if ($DPT_Schalt eq "") { knx_write($GA_Schalt,0); } else { knx_write($GA_Schalt,0,$DPT_Schalt); } } } else { $schalt_value = 1; } } $plugin_info{$id."_Cnt"} = 1; } else { $plugin_info{$id."_Cnt"} = 0 if ($GA_Cnt == 0); $schalt_value = ""; } if ($GA_Reset == 1) { $plugin_info{$id."_Stand"} = 0; if ( $GA_Schalt ne "" ) { if ($DPT_Schalt eq "") { knx_write($GA_Schalt,0); } else { knx_write($GA_Schalt,0,$DPT_Schalt); } } $schalt_value = 0; } if ( $GA_Stand ne "" ) { if ($DPT_Stand eq "") { knx_write($GA_Stand,$plugin_info{$id."_Stand"}); } else { knx_write($GA_Stand,$plugin_info{$id."_Stand"},$DPT_Stand); } } return $schalt_value; } sub UHRZEIT { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return $hour.":".$min.":".$sec; } ############################################################################## ### Hilfsfunktionen ### ############################################################################## sub read_value { my $GA = $_[0]; my $DPT = $_[1]; my $value; if ($GA =~ m/\d+\/\d+\/\d+/) { $plugin_subscribe{$GA}{$plugname} = 1; if ($DPT ne "") { $value = knx_read($GA,$max_alter,$DPT); } else { $value = knx_read($GA,$max_alter); } return $value; } else { return $GA; } } sub check_time { my ($id, $time_value) = @_; my $ctime = time; print " Zeit: $time_value\n"; if ($time_value <= 5) { print " Zeit <= 5\n"; sleep($time_value); delete $plugin_info{$id}; return 1; } else { if ($plugin_info{$id}) { if ($plugin_info{$id} < $ctime) { return 1; } else { return 0; } } else { $plugin_info{$id} = $ctime + $time_value; return 0; } } }
Hier kommt der Hilfetext:
Code:
Kurzanleitung zum Plugin simple_logic Das Plugin simple_logic stellt grundlegende Logik-Funktionen zur Verfügung. Wichtig: Das Plugin benötigt zwingend importierte Gruppenadressen, ohne diese funktioniert das Plugin nicht korrekt! Boolsche Logik-Funktionen ------------------------- UND - UND-Funktion Aufruf: UND(?,?,?,...) ODER - ODER-Funktion Aufruf: ODER(?,?,?,...) NUND - Nicht UND-Funktion Aufruf: NUND(?,?,?,...) NODER - Nicht ODER-Funktion Aufruf: NODER(?,?,?,...) NICHT - Nicht-Funktion Aufruf: NICHT(?) EODER - Exklusiv ODER-Funktion Aufruf: EODER(?,?,?,...) ENODER - Exklusiv Nicht-ODER-Funktion Aufruf: ENODER(?,?,?,...) Hinweis: Es können beliebig viele Werte übergeben werden (außer NICHT) Die Fragezeichen stehen für: - Boolsche Werte, d.h. 1 oder 0 - Gruppenadressen, z.B. "5/3/5" - Bedingungen, z.B. UND("5 > 1","5/2/1 <> 5/2/2") (mehr dazu weiter unten) - Uhrzeiten, z.B. 06:30 oder 18:20:05 - weitere Logikfunktionen, z.B. UND(ODER("5/4/1 < 20","5/4/2"),"5/4/3","5 >= 2") Weitere Funktionen ------------------ WENN - WENN-Funktion Aufruf: WENN(Bedingung,Wert-Wahr,Wert-Falsch[,GA][,DPT]) Hinweis: Wenn als Wert-Wahr oder Wert-Falsch ein "-" angeben wird, bedeutet dies, nichts wird ausgegeben. AUSGABE - Wert auf den Bus schreiben Aufruf: AUSGABE(GA,Wert[,DPT]) EINVERZ - Einschaltverzögerung Aufruf: EINVERZ(Trigger,Zeit in s[,GA][,DPT]) AUSVERZ - Ausschaltverzögerung Aufruf: AUSVERZ(Trigger,Zeit in s[,GA][,DPT]) EINAUSVERZ - Ein-/Ausschaltverzögerung Aufruf: EINAUSVERZ(Trigger,Zeit ein in s, Zeit aus in s[,GA][,DPT]) IMPULS - Impuls Aufruf: IMPULS(Trigger, Zeit in s[,GA][,DPT] ZAEHLER - Vor-/Rückwärtszähler Aufruf: ZAEHLER(Zählimpuls, Richtung, Reset, Schwelle ein, Schwelle aus[, GA Schaltsignal][,DPT Schaltsignal][,GA Zählerstand][,DPT Zählerstand]) UHRZEIT - Aktuelle Uhrzeit Aufruf: &UHRZEIT oder UHRZEIT() Hinweis: die Angaben in den Eckigenklammern sind optional Bedingungen ----------- Allen Funktionen können Bedingungen anstelle von Werten übergeben werden. Folgende Bedingungen werden ausgewertet: = - ist gleich > - ist größer >= - ist größer oder gleich < - ist kleiner <= - ist kleiner oder gleich <> - ist ungleich Sonstiges --------- - Bei Perl muss als letztes Zeichen in einem Befehlsaufruf ein Semikolon geschrieben werden und - Kommentare beginnen mit #. - Zeichenketten durch einen Punkt (.) verbinden. Beispiele --------- Licht mit 30% Helligkeit zwischen 23:00 und 06:00 Uhr einschalten, ansonsten mit 100%. 1/1/12 - Gruppenadresse Licht ein/aus 1/1/13 - Gruppenadresse für Helligkeitswert &Uhrzeit - Aktuelle Uhrzeit WENN(UND("1/1/12",ODER(&UHRZEIT.">23:00",&UHRZEIT."<06:00"),NICHT("1/1/13")),30,"-","1/1/13",5); # Licht mit 30% einschalten WENN(UND("1/1/12",&UHRZEIT."<23:00",&UHRZEIT.">06:00",NICHT("1/1/13")),100,"-","1/1/13",5); # Licht mit 100% einschalten WENN("1/1/12","-",0,"1/1/13",5); # Licht ausschalten Erklärung: Der False-Wert bekommt ein "-",damit es in diesem Fall nicht ausgegeben wird. Dies ist notwendig, da die UND/ODER-Kombination einen False-Wert ausgibt, wenn die GA 1/1/12 eine 0 liefert und damit würde das Licht z.B. mit 100% eingeschaltet werden, wenn das Plugin ausgeführt wird. Das NICHT(1/1/13) sorgt dafür, dass sich der Helligkeitswert nicht ändert, wenn das Licht schon eingeschaltet ist. Ähnliches gilt beim Ausschalten für den True-Wert.
Kommentar