| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- ##############################################
- # $Id: 91_sequence.pm 16720 2018-05-10 14:22:32Z rudolfkoenig $
- package main;
- use strict;
- use warnings;
- use Time::HiRes qw(gettimeofday);
- #####################################
- sub
- sequence_Initialize($)
- {
- my ($hash) = @_;
- $hash->{DefFn} = "sequence_Define";
- $hash->{UndefFn} = "sequence_Undef";
- $hash->{NotifyFn} = "sequence_Notify";
- no warnings 'qw';
- my @attrList = qw(
- disable:0,1
- disabledForIntervals
- reportEvents:1,0
- triggerPartial:1,0
- showtime:1,0
- );
- use warnings 'qw';
- $hash->{AttrList} = join(" ", @attrList);
- }
- #####################################
- # define sq1 sequence reg1 [timeout reg2]
- sub
- sequence_Define($$)
- {
- my ($hash, $def) = @_;
- my @def = split("[ \t]+", $def);
- my $name = shift(@def);
- my $type = shift(@def);
-
- return "Usage: define <name> sequence <re1> <timeout1> <re2> ".
- "[<timeout2> <re3> ...]"
- if(int(@def) % 2 == 0 || int(@def) < 3);
- # "Syntax" checking
- for(my $i = 0; $i < int(@def); $i += 2) {
- my $re = $def[$i];
- my $to = $def[$i+1];
- eval { "Hallo" =~ m/^$re$/ };
- return "Bad regexp 1: $@" if($@);
- return "Bad timeout spec $to" # timeout or delay:timeout
- if (defined($to) && $to !~ m/^(\d+(\.\d+)?:)?\d+(\.\d+)?$/);
- }
- $hash->{RE} = $def[0];
- $hash->{IDX} = 0;
- $hash->{MAX} = int(@def);
- $hash->{STATE} = "active";
- $hash->{TS} = 0;
- return undef;
- }
- #####################################
- sub
- sequence_Notify($$)
- {
- my ($hash, $dev) = @_;
- my $ln = $hash->{NAME};
- return "" if(IsDisabled($ln));
- my $n = $dev->{NAME};
- my $re = $hash->{RE};
- my $max = int(@{$dev->{CHANGED}});
- for (my $i = 0; $i < $max; $i++) {
- my $s = $dev->{CHANGED}[$i];
- $s = "" if(!defined($s));
- next if($n !~ m/^$re$/ && "$n:$s" !~ m/^$re$/);
- RemoveInternalTimer($ln);
- if($hash->{TS} > gettimeofday()) { # the delay stuff
- sequence_Trigger($ln, "abort");
- last;
- }
- my $idx = $hash->{IDX} + 2;
- Log3 $ln, 5, "sequence $ln matched $idx";
- my @d = split("[ \t]+", $hash->{DEF});
- $hash->{EVENTS} = "" if(!$hash->{EVENTS});
- $hash->{EVENTS} .= " $n:$s";
- if($idx > $hash->{MAX}) { # Last element reached
- my $tt = "trigger";
- $tt .= $hash->{EVENTS} if(AttrVal($ln, "reportEvents", undef));
- delete($hash->{EVENTS});
- Log3 $ln, 5, "sequence $ln $tt";
- setReadingsVal($hash, "state", "active", TimeNow());
- DoTrigger($ln, $tt);
- $idx = 0;
- $hash->{TS} = 0;
- } else {
- my ($delay, $nt) = split(':', $d[$idx - 1]);
- $hash->{TS} = gettimeofday() + $delay if (defined($nt) && $delay > 0);
- $nt += gettimeofday() + $delay;
- InternalTimer($nt, "sequence_Trigger", $ln, 0);
- }
- $hash->{IDX} = $idx;
- $hash->{RE} = substr($d[$idx], 0, 1) eq ':' ? $n . $d[$idx] : $d[$idx];
- last;
- }
- return "";
- }
- sub
- sequence_Trigger($$)
- {
- my ($ln, $arg) = @_;
- my $hash = $defs{$ln};
- my @d = split("[ \t]+", $hash->{DEF});
- $hash->{RE} = $d[0];
- my $idx = $hash->{IDX}/2;
- $hash->{IDX} = 0;
- $hash->{TS} = 0;
- my $tt = "partial_$idx";
- $arg = "timeout" if(!$arg);
- Log3 $ln, 5, "sequence $ln $arg on $idx ($tt)";
- $tt .= $hash->{EVENTS} if(AttrVal($ln, "reportEvents", undef));
- delete($hash->{EVENTS});
- DoTrigger($ln, $tt) if(AttrVal($ln, "triggerPartial", undef));
- }
- sub
- sequence_Undef($$)
- {
- my ($hash, $name) = @_;
- RemoveInternalTimer($name);
- return undef;
- }
- 1;
- =pod
- =item helper
- =item summary generate an event upon reception of a defined sequence of events
- =item summary_DE generiert Event nach Empfang einer definierten Event-Sequenz
- =begin html
- <a name="sequence"></a>
- <h3>sequence</h3>
- <ul>
- <br>
- <a name="sequencedefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> sequence <re1> <timeout1>
- <re2> [<timeout2> <re3> ...]</code>
- <br><br>
- A sequence is used to allow to trigger events for a certain combination of
- button presses on a remote. E.g. to switch on a lamp when pressing the
- Btn1:on, then Btn2:off and at last Btn1:on one after the other you could
- define the following:<br>
- <br>
- <ul>
- <code>
- define lampseq sequence Btn1:on 0.5 Btn2:off 0.5 Btn1:on<br>
- define lampon notify lampseq:trigger set lamp on
- </code>
- </ul>
- <br>
- Subsequent patterns can be specified without device name as
- <code>:<re2></code>. This will reuse the device name which triggered
- the previous sequence step:
- <br>
- <ul>
- <code>
- define lampseq sequence Btn.:on 0.5 :off<br>
- </code>
- </ul>
- <br>
- You can specify timeout as <code><delay>:<timeout></code>,
- where "delay" sets time during which the next event shall not be received,
- otherwise the sequence will be aborted. This can be used to capture press
- and hold of a button. Example:<br>
- <ul>
- <code>
- define lampseq sequence Btn1:on 2:3 Btn1:off<br>
- </code>
- </ul>
- sequence will be triggerred if Btn1 is pressed for 2 to 5 seconds.
- </ul>
- <br>
- <a name="sequenceset"></a>
- <b>Set</b> <ul>N/A</ul><br>
- <a name="sequenceget"></a>
- <b>Get</b> <ul>N/A</ul><br>
- <a name="sequenceattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#disable">disable</a></li>
- <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
- <li><a href="#showtime">showtime</a></li>
- <li><a href="#triggerPartial">triggerPartial</a><br>
- if set (to 1), and not all the events of a sequence are received, then a
- partial_X event is generated by the sequence. Example:<br><code><ul>
- fhem> define seq sequence d1:on 1 d1:on 1 d1:on<br>
- fhem> attr seq triggerPartial<br>
- fhem> set d1 on;; sleep 0.5;; set d1 on<br>
- </ul></code>
- generates the event seq partial_2. This can be used to assign different
- tasks for a single button, depending on the number of times it is
- pressed.
- </li><br>
- <li><a href="#reportEvents">reportEvents</a><br>
- if set (to 1), report the events (space separated) after the
- "trigger" or "partial_X" keyword. This way one can create more general
- sequences, and create different notifies to react:<br>
- <ul><code>
- define seq sequence remote:btn.* remote:btn.*<br>
- attr seq reportEvents<br>
- define n_b1b2 notify seq:trigger.remote:btn1.remote:btn2 set lamp1 on<br>
- define n_b2b1 notify seq:trigger.remote:btn2.remote:btn1 set lamp1 off<br>
- </code></ul>
- </li>
- </ul>
- <br>
- </ul>
- =end html
- =begin html_DE
- <a name="sequence"></a>
- <h3>sequence</h3>
- <ul>
- <br>
- <a name="sequencedefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> sequence <re1> <timeout1>
- <re2> [<timeout2> <re3> ...]</code>
- <br><br>
- Ein sequence kann verwendet werden, um ein neues Event zu generieren, wenn
- eine bestimmte Folge von anderen Events in einem festgelegten Zeitraum
- eingetroffen ist. Z.Bsp. um eine Lampe dann einzuschalten, falls Btn1:on,
- dann Btn2:off und zum Schluss Btn1:on innerhalb einer Sekunde gedrückt
- wurde, definiert man folgendes:<br>
- <ul>
- <code>
- define lampseq sequence Btn1:on 0.5 Btn2:off 0.5 Btn1:on<br>
- define lampon notify lampseq:trigger set lamp on
- </code>
- </ul>
- Nachfolgende Regexps können den Namen des Gerätes weglassen, in
- diesem Fall werden nur die Events des beim ersten Event eingetroffenen
- Gerätes beachtet:
- <br>
- <ul>
- <code>
- define lampseq sequence Btn.:on 0.5 :off<br>
- </code>
- </ul>
- <br>
- Timeout kann als <code><delay>:<timeout></code> spezifiziert
- werden, dabei setzt delay die Zeit, wo kein passendes Event empfangen
- werden darf, ansonsten wird sequence abgebrochen. Das kann verwendet
- werden, um "press and hold" auszuwerten. Folgendes
- <ul>
- <code>
- define lampseq sequence Btn1:on 2:3 :off<br>
- </code>
- </ul>
- ist nur erfolgreich, falls Btn1 zwischen 2 und 5 Sekunden lang gedrückt
- wurde.
- </ul>
- <br>
- <a name="sequenceset"></a>
- <b>Set</b> <ul>N/A</ul><br>
- <a name="sequenceget"></a>
- <b>Get</b> <ul>N/A</ul><br>
- <a name="sequenceattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#disable">disable</a></li>
- <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
- <li><a href="#showtime">showtime</a></li>
- <li><a href="#triggerPartial">triggerPartial</a><br>
- Falls gesetzt (auf 1), und nicht alle erwarteten Events eingetroffen
- sind, dann wird ein partial_X Event generiert, wobei X durch Anzahl der
- eingetroffenen Events ersetzt wird. Beispiel:<br><code><ul>
- fhem> define seq sequence d1:on 1 d1:on 1 d1:on<br>
- fhem> attr seq triggerPartial<br>
- fhem> set d1 on;; sleep 0.5;; set d1 on<br>
- </ul></code>
- erzeugt das Event "seq partial_2". Dies kann verwendet werden, um z.Bsp.
- einer Taste unterschiedliche Aufgaben zuzuweisen, jenachdem wie oft sie
- gedrückt wurde.
- </li><br>
- <li><a href="#reportEvents">reportEvents</a><br>
- Falls gesetzt (auf 1), meldet trigger die empfangenen Events (Leerzeichen
- getrennt) nach dem "trigger" oder "partial_X" Schlüsselwort.
- Das kann verwendet werden, um generische sequence Instanzen zu definieren:
- <br>
- <ul><code>
- define seq sequence remote:btn.* remote:btn.*<br>
- attr seq reportEvents<br>
- define n_b1b2 notify seq:trigger.remote:btn1.remote:btn2 set lamp1 on<br>
- define n_b2b1 notify seq:trigger.remote:btn2.remote:btn1 set lamp1 off<br>
- </code></ul>
- </li>
- </ul>
- <br>
- </ul>
- =end html_DE
- =cut
|