Ich habe hier mal die Betaversion eines Decoderplugins.
Dss Plugin kann Tastendrücke auswerten und je nach 'eingetastetem' Code unterschiedliche Aktionen ausfuehren. Wer außen am Haus einen Taster mit einem Binaereingang verbindet., oder einen sonstigen EIB-Taster zur Verfügung hat, kann damit z.B seine Haustuer oeffnen.
Es ist aber auch möglich, auf bestimmte Abfolgen von Ereignissen zu reagieren, die sich z.B. ueber einen längeren Zeitraum hinziehen, wenn die entsprechenden Timeouts im Script angepasst werden.
Die verwendeten Taster können gefahrlos weiter im Normalbetrieb verwendet werden, da das Plugin Einzeleingaben erkennt und ausfiltert.
Von einem Taster aus können beliebig viele, unterschiedliche Codes erkannt werden, und entsprechend unterschiedliche Aktionen ausgelöst werden. Für jeden Code können andererseits aber auch beliebig viele Aktionen hintereinander ausgeführt werden.
Die Eingaben können auf bestimmte physische Adressen beschränkt werden, so dass eine GA auf dem Bus nur dann zu einer Reaktion führt, wenn sie vom beabsichtigten, physischen Gerät stammt. Man kann die Eingabe aber auch von allen Geräten aus zulassen, die die fragliche GA versenden können.
Bei einer oder mehrerer Falscheingaben wird die Dekoderfunktion blockiert. Die Anzahl Fehlversuche, die anfängliche Blockadezeit und die maximale Blockadezeit sind einstellbar. Mit jeder weiteren Falscheingabe verdoppelt sich die Blockadezeit.
Aktuell sind eingestellt:
Wichtig sind dabei $pauseSec und $completeAfter.
$pauseSec gibt die Zeit an, nach der eine Ziffer als vollständig angesehen wird.
$completeAfter gibt die Zeit an, nach der der gesamte Code als vollständig angesehen wird.
Beispiel:
- Taster wird drei mal kurz hintereinander gedrueckt
- kurze Pause
- Taster wird vier mal kurz hintereinander gedrueckt
- kurze Pause
- Taster wird fuenf mal kurz hintereinander gedrueckt
wird dekodiert zu '345'. 10 Tastendrücke entsprechen einer '0', mehr als 10 Tastendrücke werden als Falscheingabe gewertet.
Tests bei mir haben ganz gut funktioniert. Die gewählten Timings sind für mich jedenfalls gut handhabbar.
Hier das Script, es ist Beta. Wenn es allgemein als OK angesehen wird, stelle ich es in SVN.
Dss Plugin kann Tastendrücke auswerten und je nach 'eingetastetem' Code unterschiedliche Aktionen ausfuehren. Wer außen am Haus einen Taster mit einem Binaereingang verbindet., oder einen sonstigen EIB-Taster zur Verfügung hat, kann damit z.B seine Haustuer oeffnen.
Es ist aber auch möglich, auf bestimmte Abfolgen von Ereignissen zu reagieren, die sich z.B. ueber einen längeren Zeitraum hinziehen, wenn die entsprechenden Timeouts im Script angepasst werden.
Die verwendeten Taster können gefahrlos weiter im Normalbetrieb verwendet werden, da das Plugin Einzeleingaben erkennt und ausfiltert.
Von einem Taster aus können beliebig viele, unterschiedliche Codes erkannt werden, und entsprechend unterschiedliche Aktionen ausgelöst werden. Für jeden Code können andererseits aber auch beliebig viele Aktionen hintereinander ausgeführt werden.
Die Eingaben können auf bestimmte physische Adressen beschränkt werden, so dass eine GA auf dem Bus nur dann zu einer Reaktion führt, wenn sie vom beabsichtigten, physischen Gerät stammt. Man kann die Eingabe aber auch von allen Geräten aus zulassen, die die fragliche GA versenden können.
Bei einer oder mehrerer Falscheingaben wird die Dekoderfunktion blockiert. Die Anzahl Fehlversuche, die anfängliche Blockadezeit und die maximale Blockadezeit sind einstellbar. Mit jeder weiteren Falscheingabe verdoppelt sich die Blockadezeit.
Aktuell sind eingestellt:
Code:
[FONT="Courier New"] my $pauseSec = 1.0; # Pausenzeit, nach der eine Ziffer komplett ist my $completeAfter = 4; # Wartezeit in Sekunden, nach der der Code ausgewertet wird my $maxFails = 3; # Anzahl Fehlversuche my $blockPeriod = 30; # anfaengliche Blockadezeit in Sekunden. my $maxBlockPeriod = 3600; # maximale Blockadezeit[/FONT]
$pauseSec gibt die Zeit an, nach der eine Ziffer als vollständig angesehen wird.
$completeAfter gibt die Zeit an, nach der der gesamte Code als vollständig angesehen wird.
Beispiel:
- Taster wird drei mal kurz hintereinander gedrueckt
- kurze Pause
- Taster wird vier mal kurz hintereinander gedrueckt
- kurze Pause
- Taster wird fuenf mal kurz hintereinander gedrueckt
wird dekodiert zu '345'. 10 Tastendrücke entsprechen einer '0', mehr als 10 Tastendrücke werden als Falscheingabe gewertet.
Tests bei mir haben ganz gut funktioniert. Die gewählten Timings sind für mich jedenfalls gut handhabbar.
Hier das Script, es ist Beta. Wenn es allgemein als OK angesehen wird, stelle ich es in SVN.
Code:
[FONT="Courier New"]# Plugin zur Erkennung eines Tastencodes # License: GPL (v2) # version von emax # # Das Plugin analysiert die Ereignisse einer gegebenen GA, und wertet die # Zeitabstaende zwischen den Telegrammen aus. Je nach erkanntem Muster wird # eine vorgegebene Funktion ausgefuehrt. # # Das Plugin ist z.B. als Notbehelf im Falle einer zugefallen Haustuer # gedacht. # # Funktionsweise: # --------------- # Mit jedem erkannten Tastendruck wird ein Zaehler inkrementiert, sofern der # letzte Tastendruck der betreffenden GA nicht laenger her ist als eine Sekunde # (einstellbar). Wird eine Pause von mehr als einer Sekunde erkannt, wird # der Wert des Zaehlers gespeichert, und der Zaehler auf Null gesetzt. # Entsprechen die so gesammelten Werte einem bestimmten Code, wird eine # vorgegebene Funktion ausgefuehrt. # # Auf diese Weise laesst sich jeder Lichtschalter z.B. als Tueroeffner # verwenden, wenn er in einem bestimmten Muster gedrueckt wird. # # Beispiel: # - Taster wird drei mal kurz hintereinander gedrueckt # - ueber eine Sekunde Pause # - Taster wird vier mal kurz hintereinander gedrueckt # - ueber eine Sekunde Pause # - Taster wird fuenf mal kurz hintereinander gedrueckt # - ueber eine Sekunde Pause # # wird dekodiert zu '345'. 10 Tastendruecke entsprechen einer '0', mehr als 10 # Tastendruecke werden als Falscheingabe gewertet. # # Wird eine Pause von mehr als vier (einstellbar) Sekunden gemacht, wird der # Code als vollstaendig angesehen und ausgewertet. Nach der Auswertung werden # alle Zaehler wieder auf Null gesetzt, und von vorne begonnen. # # Nach drei Falscheingaben (einstellbar) wird die Auswertung fuer eine Minute # (einstellbar) blockiert. Nach jeder weiteren Falscheingabe verdoppelt sich # die Blockadezeit. # # Die GA kann weiterhin fuer andere Aufgaben verwendet werden, da das Script # nur zusammenhaengenede Eingaben auswertet, und Einzeleingaben ignoriert. #----------------------------------------------------------------------------- # Einstellungen #----------------------------------------------------------------------------- my $oneDay = (24 * 3600); my $pauseSec = 1.0; # Pausenzeit, nach der eine Ziffer komplett ist my $completeAfter = 4; # Wartezeit in Sekunden, nach der der Code ausgewertet wird my $maxFails = 3; # Anzahl Fehlversuche my $blockPeriod = 30; # anfaengliche Blockadezeit in Sekunden. my $maxBlockPeriod = 3600; # maximale Blockadezeit #----------------------------------------------------------------------------- # - Es koennen beliebig viele Eintraege fuer ein und den selben Code gemacht # werden, sie werden alle ausgefuehrt. # - Wird ein 'FromPA' Wert angegeben, wird der Code nur von dieser PA akzeptiert. # - 'FromGA' und 'ToGA' duerfen nicht identisch sein. # - Der Code darf nur aus Ziffern bestehen, muss aber in Hochkommas angegeben # werden. So gehen auch fuehrende Nullen. Zehn Tastendruecke sind eine Null. #----------------------------------------------------------------------------- my @Codes = ( { Active=>1, Code=>'123',FromGA=>'1/1/121', FromPA=>undef, Value=>'1', DPT=>'1', ToGA=>'1/1/123', Log=>'1' }, { Active=>1, Code=>'123',FromGA=>'1/1/121', FromPA=>undef, Value=>'1', DPT=>'1', ToGA=>'3/2/1', Log=>'1' }, { Active=>1, Code=>'321',FromGA=>'1/1/121', FromPA=>undef, Value=>'0', DPT=>'1', ToGA=>'1/1/123', Log=>'1' }, { Active=>1, Code=>'456',FromGA=>'1/1/121', FromPA=>undef, Value=>'1', DPT=>'1', ToGA=>'1/1/121', Log=>'1' }, ); #----------------------------------------------------------------------------- # ACHTUNG: die Versionsnummer IMMER veraendern, wenn das script geaendert wurde. # Der Wert muss numerisch sein, egal ob hoeher oder niedriger als die # Vorversion. Es ist auch egal, ob die Version bereits verwendet wurde, # es kommt nur darauf an, das der Wert ANDERS ist als zuvor. #----------------------------------------------------------------------------- my $version = 1; #----------------------------------------------------------------------------- # ENDE Einstellungen #----------------------------------------------------------------------------- use POSIX; my $dbg = ':dJ:'; # ':ALL:dSS:dWS:dBS:state:code:cycle:counter:msg:cC:dJ:'; # ALL:alles, my %varInit = (initialized=>1, lastRun=>0, blockEnd=>0, curBlockPeriod=>0, curCode=>'', fails=>0, state=>'sleeping'); my ($seconds, $uSec, $tStamp, $state, $lastRun, $dTime, $piPrefix, $minCodeLength); sub debug() { my ($tag, $text) = (@_); my $caller = (caller(1))[3]; ($dbg =~ /:ALL:|$tag/) and plugin_log($plugname, "DBG$tag$caller:"."$text"); return 1; } # debug() sub doInit { &debug(':DFR:', 'entering'); # Kontrollierte Startkonditionen setzen # Die Funktion wird aufgerufen, wenn es der erste Lauf einer Plugin-Version # ist. Es werden alle Werte aus alten Versionen aus %plugin_info geloescht. # "$plugname.$version.initialized" wird gesetzt, und auch alle anderen # kuenftig verwendeten plugin_info-Variablen angelegt. plugin_log($plugname, "Starting plugin version $version."); # obsolete Versionen von $plugin_info bereinigen foreach (keys %plugin_info) { if (/^$plugname\.\d+\./) { delete $plugin_info{$_}; plugin_log($plugname, "deleted obsolete plugin_info[$_]"); } } # Variablen zuruecksetzen &reset(); # Die minimale Codelaenge wird unten ermittelt. # Kuerzere Codes werden verworfen. $minCodeLength = 999; foreach my $code (@Codes) { if (defined $code->{FromGA}) { if (defined $code->{FromGA} && defined $code->{ToGA} && $code->{FromGA} eq $code->{ToGA}) { plugin_log($plugname, "ERROR: source GA[$code->{FromGA}]and destination GA[$code->{FromGA}] are the same, entry ignored."); next; } my $GA = $code->{FromGA}; if (defined $code->{Active} && $code->{Active} == 1) { plugin_log($plugname, "subscribing to GA[$GA]"); $plugin_subscribe{$GA}{$plugname} = 1; # Ermitteln der minimalen Code-laenge. # Alle codes, die kuerzer sind, werden ignoriert, # und fuehren auch nicht zu Fehlern. (length($code->{Code}) < $minCodeLength) and $minCodeLength = length($code->{Code}); } elsif (exists $plugin_subscribe{$GA}{$plugname}) { plugin_log($plugname, "deleting obsolete subscription to GA[$GA]"); delete $plugin_subscribe{$GA}{$plugname}; } } # defined FromGA } # each $code $plugin_info{"$piPrefix.minCodeLength"} = $minCodeLength; # debug if ($dbg =~/:ALL:|:dFR:/) { foreach (keys %plugin_subscribe) { &debug(':DFR:', "pluginSubscribeKey[$_]"); } } # debug $plugin_info{$plugname.'_cycle'} = $oneDay; } # doFirstFRun sub setBlockingPeriod() { &debug(":sBT:", "entering"); my $fails = $plugin_info{"$piPrefix.fails"} + 1; plugin_log($plugname, "invalid code #[$fails]"); $plugin_info{"$piPrefix.fails"} = $fails; if ($fails >= $maxFails) { my $curBlockPeriod = $plugin_info{"$piPrefix.curBlockPeriod"}; my $newBlockPeriod = ($curBlockPeriod == 0) ? $blockPeriod : $curBlockPeriod + $curBlockPeriod; ($newBlockPeriod > $maxBlockPeriod) and $newBlockPeriod = $maxBlockPeriod; plugin_log($plugname, "setting blocking period for GA[".$plugin_info{"$piPrefix.GA"}. " from [$curBlockPeriod] sec to [$newBlockPeriod] sec"); $plugin_info{"$piPrefix.curBlockPeriod"} = $newBlockPeriod; $plugin_info{"$piPrefix.blockEnd"} = $tStamp + $newBlockPeriod; $plugin_info{"$piPrefix.state"} = 'blocked'; ($plugin_info{"$plugname".'_cycle'} < $newBlockPeriod) and $plugin_info{"$plugname".'_cycle'} = $newBlockPeriod; } else { # Wenn die maximale Anzahl Fehleingaben noch nicht erreicht wurde, # werden diese Variablen zurueckgesetzt. $plugin_info{"$piPrefix.curCode"} = ''; $plugin_info{"$piPrefix.state"} = 'sleeping'; $plugin_info{"$plugname".'_cycle'} = $oneDay; } } # setBlockingPeriod sub checkCode() { &debug(':cC:', 'entering'); # Es werden alle %Codes Eintraege auf einen passenden code ueberprueft. # Da meherere Codes je GA verarbeitet werden koennen, ist der Eintrag # abhaengig von # - sendender GA # - ermitteltem Code # Wenn diese beiden Werte uebereinstimmen, wird der %Codes-Eintrag zurueckgegeben. # Stimmt kein Eintrag ueberein, wird 'undef' zurueckgegeben. my $curCode = $plugin_info{"$piPrefix.curCode"}; &debug(':cC:', "curCode[$curCode]"); my $idx = -1; foreach my $code (@Codes) { ++$idx; if (defined $code->{FromGA} && defined $code->{ToGA} && $code->{FromGA} eq $code->{ToGA}) { plugin_log($plugname, "ERROR: source GA[$code->{FromGA}]and destination GA[$code->{FromGA}] are the same, entry ignored."); next; } (!defined $code->{Active} || !$code->{Active}) and &debug("cC:", "not Active") and next; (!defined $code->{Code} || $code->{Code} ne $plugin_info{"$piPrefix.curCode"}) and &debug("cC:", "no Code") and next; (!defined $code->{FromGA} || $code->{FromGA} ne $plugin_info{"$piPrefix.GA"}) and &debug("cC:", "not FromGA")and next; (defined $code->{FromPA} && $code->{FromPA} ne $plugin_info{"$piPrefix.PA"}) and &debug("cC:", "not FromPA")and next; &debug(':cC:', "found Code[$curCode]"); return $idx; } return undef; } sub doJob() { &debug(':dJ:', 'entering'); my $codeIdx = shift; my $codeCount = @Codes; &debug(":dJ:", "codeCount[$codeCount]"); # Hier werden alle %Codes ausgefuehrt, die den aktuellen parametern entsprechen. # Es koennen hinterainander mehrere Kommandos fuer den gleichen Code ausfuehrt # werden. Ebenso unterschiedliche Kommandos vom gleichenm Taster, je nach Code. for (; $codeIdx < $codeCount; ++$codeIdx) { # es werden alle Eintraege verarbeitet, die dem Filter entsprechen my $code = $Codes[$codeIdx]; (!defined $code->{Active} || !$code->{Active}) and next; (!defined $code->{Code} || $code->{Code} ne $plugin_info{"$piPrefix.curCode"}) and next; (!defined $code->{FromGA} || $code->{FromGA} ne $plugin_info{"$piPrefix.GA"}) and next; (defined $code->{FromPA} && $code->{FromPA} ne $plugin_info{"$piPrefix.PA"}) and next; (defined $code->{Log} && $code->{Log}) and plugin_log($plugname, 'executing from PA['.$plugin_info{"$piPrefix.PA"}. '] From GA['.$plugin_info{"$piPrefix.GA"}. '], sending ['.$code->{Value}. '] to ['.$code->{ToGA}.'].'); knx_write($code->{ToGA},$code->{Value}, $code->{DPT}); } } # doJob sub reset() { &debug(':RES:', 'entering'); # Alle Variablen und '$state' zuruecksetzen foreach (keys %varInit) { &debug(':RES:',"(re)setting [$piPrefix.$_] to [$varInit{$_}]"); $plugin_info{"$piPrefix.$_"} = $varInit{$_} } $plugin_info{"$plugname".'_cycle'} = $oneDay; } # reset() sub doSleepingState() { &debug(':dSS:', 'entering'); $plugin_info{"$piPrefix.lastRun"} = $tStamp; # Wird ausgefuehrt, wenn das Plugin das erste Mal getriggert wird nach # - der Initialisierung # - einem vollstaendigen Code oder # - dem Status 'blocked' # Wenn keine GA geliefert wurde, geschah der Aufruf aufgrund eines _cycle Timeouts. # Dann ist nichts weiter zu tun. if (!defined $msg{'dst'}) { # refresh _cycle $plugin_info{"$plugname".'_cycle'} = $oneDay; return; }; &debug(':dSS:', 'setting GA/PA filters'); # GA & PA merken. # Bis der Code vollstaendig ist, werden nur noch Telegramme dieser GA und von # dieser PA ausgwertet. Alle anderen telegramme werden so lange ingnoriert. $plugin_info{"$piPrefix.GA"} = $msg{'dst'}; $plugin_info{"$piPrefix.PA"} = $msg{'src'}; $plugin_info{"$piPrefix.counter"} = 1; $plugin_info{"$piPrefix.curCode"} = ''; $plugin_info{"$plugname".'_cycle'} = $completeAfter; $plugin_info{"$piPrefix.state"} = 'waiting'; } # doSleepingState sub doWaitingState() { &debug(':dWS:', 'entering'); # errechne abgelaufene Zeit $lastRun = $plugin_info{"$piPrefix.lastRun"}; $dTime = $tStamp - $lastRun; &debug(':dWS:', "dTime[$dTime]"); if (!defined $msg{'dst'}) { &debug(':dWS:', 'no GA'); if ($dTime < $pauseSec) { # Aus einem mir unbekannten Grunde passiert es, dass # das Script aufgerufen wird, ohne das eine GA geliefert wird, # obwohl kein _cycle Timeout stattfand. # Solche Ereignisse werden ignoriert. return; } $plugin_info{"$piPrefix.lastRun"} = $tStamp; # Keine GA, aber _cycle Timeout # Wenn es zwischenzeitlich Tastendruecke stattfanden, # diese zum Code inzufuegen. if ($plugin_info{"$piPrefix.counter"}) { ($plugin_info{"$piPrefix.counter"} == 10) and $plugin_info{"$piPrefix.counter"} = 0; $plugin_info{"$piPrefix.curCode"} .= $plugin_info{"$piPrefix.counter"}; $plugin_info{"$piPrefix.counter"} = 0; plugin_log($plugname, 'current code['.$plugin_info{"$piPrefix.curCode"}.']'); } &debug(':dWS:', "curCode[".$plugin_info{"$piPrefix.curCode"}.']'); if (length($plugin_info{"$piPrefix.curCode"} < $minCodeLength)) { plugin_log($plugname, 'code too short, discarded.'); &reset(); return; } my $codeIdx = &checkCode(); if (defined $codeIdx) { &debug(':dWS:', 'code ok'); &doJob($codeIdx); &reset(); } else { &debug(':dWS:', 'code not ok'); &setBlockingPeriod(); } return; } # ... no GA $plugin_info{"$piPrefix.lastRun"} = $tStamp; # Ein GA-Event # Ausfiltern, sofern nicht die aktuelle GA oder nicht von der gleichen PA if ($msg{'dst'} ne $plugin_info{"$piPrefix.GA"} || $msg{'src'} ne $plugin_info{"$piPrefix.PA"}) { # Das Event wird ignoriert. # verwrfen, und neue _cycle zeit ausrechnen, damit der naechste Aufruf zum # (halbwegs) richtigen Zeitpunkt stattfindet. $plugin_info{$plugname.'_cycle'} = $completeAfter - $dTime; return; } # Das Ereignis kam von 'unserer' Quelle und ist fuer 'unsere Zieladresse. if ($dTime < $pauseSec) { if ($plugin_info{"$piPrefix.counter"} == 10) { # error &debug(':dWS:', "counter too big[".$plugin_info{"$piPrefix.counter"}.']'); &setBlockingPeriod(); } else { ++$plugin_info{"$piPrefix.counter"}; } return; } else # Pause entdeckt { &debug(':dWS:', 'pause detected, counter is '.$plugin_info{"$piPrefix.counter"}); # sofern zwischenzeitlich Ereignisse gezaehlt wurde, diese dem Code # hinzufuegen und den Zaehler zuruecksetzen. if ($plugin_info{"$piPrefix.counter"}) { &debug(':dWS:', "assembling code"); ($plugin_info{"$piPrefix.counter"} == 10) and $plugin_info{"$piPrefix.counter"} = 0; $plugin_info{"$piPrefix.curCode"} .= $plugin_info{"$piPrefix.counter"}; plugin_log($plugname, 'current code['.$plugin_info{"$piPrefix.curCode"}.']'); } $plugin_info{"$piPrefix.counter"} = 1; &debug(':dWS:', "after pause curCode[".$plugin_info{"$piPrefix.curCode"}.']'); } } # doWaitingState sub doBlockedState() { &debug(':dBS:', 'entering'); $plugin_info{"$piPrefix.lastRun"} = $tStamp; if ($tStamp >= $plugin_info{"$piPrefix.blockEnd"}) { plugin_log($plugname, 'blocking time expired, going to sleep'); &debug(':dBS:', 'blocking time expired, resetting block'); $plugin_info{"$piPrefix.curCode"} = ''; $plugin_info{"$piPrefix.state"} = 'sleeping'; $plugin_info{"$plugname".'_cycle'} = $oneDay; } } # doBlockedState #============================================================================= # main() #============================================================================= ($seconds,$uSec) = gettimeofday(); $tStamp = $seconds + $uSec/1000000; $lastRun = 0; $piPrefix = "$plugname.$version"; my $oldCycleTime = $plugin_info{"$plugname".'_cycle'}; $minCodeLength = $plugin_info{"$piPrefix.minCodeLength"}; # ggf. neue Version initialiseren if (!defined $plugin_info{"$piPrefix.initialized"}) { &doInit(); return; } $state = $plugin_info{"$piPrefix.state"}; &debug(':state:', "on entry state[$state]"); &debug(':msg:', "msg debug -------------"); &debug(':msg:', "msg[$_]=$msg{$_}") foreach (keys %msg); &debug(':msg:', "/msg debug ------------"); if ($state eq 'sleeping') { &doSleepingState(); } elsif ($state eq 'waiting') { &doWaitingState(); } elsif ($state eq 'blocked') { &doBlockedState(); } else { plugin_log($plugname, "FATAL: unknown state[$state], resetting"); &doInit(); } &debug(':state:', "on exit state[".$plugin_info{"$piPrefix.state"}.']'); &debug(':counter:', "on exit counter[".$plugin_info{"$piPrefix.counter"}.']'); &debug(':code:', "on exit code[".$plugin_info{"$piPrefix.curCode"}.']'); &debug(':cycle:', "on exit cycle[".$plugin_info{"$plugname".'_cycle'}.']'); ($oldCycleTime != $plugin_info{"$plugname".'_cycle'}) and plugin_log($plugname, 'cycle time set to '.$plugin_info{"$plugname".'_cycle'}.' seconds'); [/FONT]
Kommentar