91_sequence.pm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. ##############################################
  2. # $Id: 91_sequence.pm 14996 2017-09-03 15:20:42Z rudolfkoenig $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use Time::HiRes qw(gettimeofday);
  7. #####################################
  8. sub
  9. sequence_Initialize($)
  10. {
  11. my ($hash) = @_;
  12. $hash->{DefFn} = "sequence_Define";
  13. $hash->{UndefFn} = "sequence_Undef";
  14. $hash->{NotifyFn} = "sequence_Notify";
  15. $hash->{AttrList} = "disable:0,1 triggerPartial:1,0 showtime:1,0 reportEvents:1,0";
  16. }
  17. #####################################
  18. # define sq1 sequence reg1 [timeout reg2]
  19. sub
  20. sequence_Define($$)
  21. {
  22. my ($hash, $def) = @_;
  23. my @def = split("[ \t]+", $def);
  24. my $name = shift(@def);
  25. my $type = shift(@def);
  26. return "Usage: define <name> sequence <re1> <timeout1> <re2> ".
  27. "[<timeout2> <re3> ...]"
  28. if(int(@def) % 2 == 0 || int(@def) < 3);
  29. # "Syntax" checking
  30. for(my $i = 0; $i < int(@def); $i += 2) {
  31. my $re = $def[$i];
  32. my $to = $def[$i+1];
  33. eval { "Hallo" =~ m/^$re$/ };
  34. return "Bad regexp 1: $@" if($@);
  35. return "Bad timeout spec $to" # timeout or delay:timeout
  36. if (defined($to) && $to !~ m/^(\d+(\.\d+)?:)?\d+(\.\d+)?$/);
  37. }
  38. $hash->{RE} = $def[0];
  39. $hash->{IDX} = 0;
  40. $hash->{MAX} = int(@def);
  41. $hash->{STATE} = "active";
  42. $hash->{TS} = 0;
  43. return undef;
  44. }
  45. #####################################
  46. sub
  47. sequence_Notify($$)
  48. {
  49. my ($hash, $dev) = @_;
  50. my $ln = $hash->{NAME};
  51. return "" if($attr{$ln} && $attr{$ln}{disable});
  52. my $n = $dev->{NAME};
  53. my $re = $hash->{RE};
  54. my $max = int(@{$dev->{CHANGED}});
  55. for (my $i = 0; $i < $max; $i++) {
  56. my $s = $dev->{CHANGED}[$i];
  57. $s = "" if(!defined($s));
  58. next if($n !~ m/^$re$/ && "$n:$s" !~ m/^$re$/);
  59. RemoveInternalTimer($ln);
  60. if($hash->{TS} > gettimeofday()) { # the delay stuff
  61. sequence_Trigger($ln, "abort");
  62. last;
  63. }
  64. my $idx = $hash->{IDX} + 2;
  65. Log3 $ln, 5, "sequence $ln matched $idx";
  66. my @d = split("[ \t]+", $hash->{DEF});
  67. $hash->{EVENTS} = "" if(!$hash->{EVENTS});
  68. $hash->{EVENTS} .= " $n:$s";
  69. if($idx > $hash->{MAX}) { # Last element reached
  70. my $tt = "trigger";
  71. $tt .= $hash->{EVENTS} if(AttrVal($ln, "reportEvents", undef));
  72. delete($hash->{EVENTS});
  73. Log3 $ln, 5, "sequence $ln $tt";
  74. setReadingsVal($hash, "state", "active", TimeNow());
  75. DoTrigger($ln, $tt);
  76. $idx = 0;
  77. $hash->{TS} = 0;
  78. } else {
  79. my ($delay, $nt) = split(':', $d[$idx - 1]);
  80. $hash->{TS} = gettimeofday() + $delay if (defined($nt) && $delay > 0);
  81. $nt += gettimeofday() + $delay;
  82. InternalTimer($nt, "sequence_Trigger", $ln, 0);
  83. }
  84. $hash->{IDX} = $idx;
  85. $hash->{RE} = substr($d[$idx], 0, 1) eq ':' ? $n . $d[$idx] : $d[$idx];
  86. last;
  87. }
  88. return "";
  89. }
  90. sub
  91. sequence_Trigger($$)
  92. {
  93. my ($ln, $arg) = @_;
  94. my $hash = $defs{$ln};
  95. my @d = split("[ \t]+", $hash->{DEF});
  96. $hash->{RE} = $d[0];
  97. my $idx = $hash->{IDX}/2;
  98. $hash->{IDX} = 0;
  99. $hash->{TS} = 0;
  100. my $tt = "partial_$idx";
  101. $arg = "timeout" if(!$arg);
  102. Log3 $ln, 5, "sequence $ln $arg on $idx ($tt)";
  103. $tt .= $hash->{EVENTS} if(AttrVal($ln, "reportEvents", undef));
  104. delete($hash->{EVENTS});
  105. DoTrigger($ln, $tt) if(AttrVal($ln, "triggerPartial", undef));
  106. }
  107. sub
  108. sequence_Undef($$)
  109. {
  110. my ($hash, $name) = @_;
  111. RemoveInternalTimer($name);
  112. return undef;
  113. }
  114. 1;
  115. =pod
  116. =item helper
  117. =item summary generate an event upon reception of a defined sequence of events
  118. =item summary_DE generiert Event nach Empfang einer definierten Event-Sequenz
  119. =begin html
  120. <a name="sequence"></a>
  121. <h3>sequence</h3>
  122. <ul>
  123. <br>
  124. <a name="sequencedefine"></a>
  125. <b>Define</b>
  126. <ul>
  127. <code>define &lt;name&gt; sequence &lt;re1&gt; &lt;timeout1&gt;
  128. &lt;re2&gt; [&lt;timeout2&gt; &lt;re3&gt; ...]</code>
  129. <br><br>
  130. A sequence is used to allow to trigger events for a certain combination of
  131. button presses on a remote. E.g. to switch on a lamp when pressing the
  132. Btn1:on, then Btn2:off and at last Btn1:on one after the other you could
  133. define the following:<br>
  134. <br>
  135. <ul>
  136. <code>
  137. define lampseq sequence Btn1:on 0.5 Btn2:off 0.5 Btn1:on<br>
  138. define lampon notify lampseq:trigger set lamp on
  139. </code>
  140. </ul>
  141. <br>
  142. Subsequent patterns can be specified without device name as
  143. <code>:&lt;re2&gt;</code>. This will reuse the device name which triggered
  144. the previous sequence step:
  145. <br>
  146. <ul>
  147. <code>
  148. define lampseq sequence Btn.:on 0.5 :off<br>
  149. </code>
  150. </ul>
  151. <br>
  152. You can specify timeout as <code>&lt;delay&gt;:&lt;timeout&gt;</code>,
  153. where "delay" sets time during which the next event shall not be received,
  154. otherwise the sequence will be aborted. This can be used to capture press
  155. and hold of a button. Example:<br>
  156. <ul>
  157. <code>
  158. define lampseq sequence Btn1:on 2:3 Btn1:off<br>
  159. </code>
  160. </ul>
  161. sequence will be triggerred if Btn1 is pressed for 2 to 5 seconds.
  162. </ul>
  163. <br>
  164. <a name="sequenceset"></a>
  165. <b>Set</b> <ul>N/A</ul><br>
  166. <a name="sequenceget"></a>
  167. <b>Get</b> <ul>N/A</ul><br>
  168. <a name="sequenceattr"></a>
  169. <b>Attributes</b>
  170. <ul>
  171. <li><a href="#disable">disable</a></li>
  172. <li><a href="#showtime">showtime</a></li>
  173. <li><a href="#triggerPartial">triggerPartial</a><br>
  174. if set (to 1), and not all the events of a sequence are received, then a
  175. partial_X event is generated by the sequence. Example:<br><code><ul>
  176. fhem> define seq sequence d1:on 1 d1:on 1 d1:on<br>
  177. fhem> attr seq triggerPartial<br>
  178. fhem> set d1 on;; sleep 0.5;; set d1 on<br>
  179. </ul></code>
  180. generates the event seq partial_2. This can be used to assign different
  181. tasks for a single button, depending on the number of times it is
  182. pressed.
  183. </li><br>
  184. <li><a href="#reportEvents">reportEvents</a><br>
  185. if set (to 1), report the events (space separated) after the
  186. "trigger" or "partial_X" keyword. This way one can create more general
  187. sequences, and create different notifies to react:<br>
  188. <ul><code>
  189. define seq sequence remote:btn.* remote:btn.*<br>
  190. attr seq reportEvents<br>
  191. define n_b1b2 notify seq:trigger.remote:btn1.remote:btn2 set lamp1 on<br>
  192. define n_b2b1 notify seq:trigger.remote:btn2.remote:btn1 set lamp1 off<br>
  193. </code></ul>
  194. </li>
  195. </ul>
  196. <br>
  197. </ul>
  198. =end html
  199. =begin html_DE
  200. <a name="sequence"></a>
  201. <h3>sequence</h3>
  202. <ul>
  203. <br>
  204. <a name="sequencedefine"></a>
  205. <b>Define</b>
  206. <ul>
  207. <code>define &lt;name&gt; sequence &lt;re1&gt; &lt;timeout1&gt;
  208. &lt;re2&gt; [&lt;timeout2&gt; &lt;re3&gt; ...]</code>
  209. <br><br>
  210. Ein sequence kann verwendet werden, um ein neues Event zu generieren, wenn
  211. eine bestimmte Folge von anderen Events in einem festgelegten Zeitraum
  212. eingetroffen ist. Z.Bsp. um eine Lampe dann einzuschalten, falls Btn1:on,
  213. dann Btn2:off und zum Schluss Btn1:on innerhalb einer Sekunde gedr&uuml;ckt
  214. wurde, definiert man folgendes:<br>
  215. <ul>
  216. <code>
  217. define lampseq sequence Btn1:on 0.5 Btn2:off 0.5 Btn1:on<br>
  218. define lampon notify lampseq:trigger set lamp on
  219. </code>
  220. </ul>
  221. Nachfolgende Regexps k&ouml;nnen den Namen des Ger&auml;tes weglassen, in
  222. diesem Fall werden nur die Events des beim ersten Event eingetroffenen
  223. Ger&auml;tes beachtet:
  224. <br>
  225. <ul>
  226. <code>
  227. define lampseq sequence Btn.:on 0.5 :off<br>
  228. </code>
  229. </ul>
  230. <br>
  231. Timeout kann als <code>&lt;delay&gt;:&lt;timeout&gt;</code> spezifiziert
  232. werden, dabei setzt delay die Zeit, wo kein passendes Event empfangen
  233. werden darf, ansonsten wird sequence abgebrochen. Das kann verwendet
  234. werden, um "press and hold" auszuwerten. Folgendes
  235. <ul>
  236. <code>
  237. define lampseq sequence Btn1:on 2:3 :off<br>
  238. </code>
  239. </ul>
  240. ist nur erfolgreich, falls Btn1 zwischen 2 und 5 Sekunden lang gedr&uuml;ckt
  241. wurde.
  242. </ul>
  243. <br>
  244. <a name="sequenceset"></a>
  245. <b>Set</b> <ul>N/A</ul><br>
  246. <a name="sequenceget"></a>
  247. <b>Get</b> <ul>N/A</ul><br>
  248. <a name="sequenceattr"></a>
  249. <b>Attributes</b>
  250. <ul>
  251. <li><a href="#disable">disable</a></li>
  252. <li><a href="#triggerPartial">triggerPartial</a><br>
  253. Falls gesetzt (auf 1), und nicht alle erwarteten Events eingetroffen
  254. sind, dann wird ein partial_X Event generiert, wobei X durch Anzahl der
  255. eingetroffenen Events ersetzt wird. Beispiel:<br><code><ul>
  256. fhem> define seq sequence d1:on 1 d1:on 1 d1:on<br>
  257. fhem> attr seq triggerPartial<br>
  258. fhem> set d1 on;; sleep 0.5;; set d1 on<br>
  259. </ul></code>
  260. erzeugt das Event "seq partial_2". Dies kann verwendet werden, um z.Bsp.
  261. einer Taste unterschiedliche Aufgaben zuzuweisen, jenachdem wie oft sie
  262. gedr&uuml;ckt wurde.
  263. </li>
  264. </ul>
  265. <br>
  266. <a name="sequenceattr"></a>
  267. <b>Attributes</b>
  268. <ul>
  269. <li><a href="#disable">disable</a></li>
  270. <li><a href="#showtime">showtime</a></li>
  271. <li><a href="#triggerPartial">triggerPartial</a><br>
  272. Falls gesetzt (auf 1), und nicht alle erwarteten Events eingetroffen
  273. sind, dann wird ein partial_X Event generiert, wobei X durch Anzahl der
  274. eingetroffenen Events ersetzt wird. Beispiel:<br><code><ul>
  275. fhem> define seq sequence d1:on 1 d1:on 1 d1:on<br>
  276. fhem> attr seq triggerPartial<br>
  277. fhem> set d1 on;; sleep 0.5;; set d1 on<br>
  278. </ul></code>
  279. erzeugt das Event "seq partial_2". Dies kann verwendet werden, um z.Bsp.
  280. einer Taste unterschiedliche Aufgaben zuzuweisen, jenachdem wie oft sie
  281. gedr&uuml;ckt wurde.
  282. </li><br>
  283. <li><a href="#reportEvents">reportEvents</a><br>
  284. Falls gesetzt (auf 1), meldet trigger die empfangenen Events (Leerzeichen
  285. getrennt) nach dem "trigger" oder "partial_X" Schl&uuml;sselwort.
  286. Das kann verwendet werden, um generische sequence Instanzen zu definieren:
  287. <br>
  288. <ul><code>
  289. define seq sequence remote:btn.* remote:btn.*<br>
  290. attr seq reportEvents<br>
  291. define n_b1b2 notify seq:trigger.remote:btn1.remote:btn2 set lamp1 on<br>
  292. define n_b2b1 notify seq:trigger.remote:btn2.remote:btn1 set lamp1 off<br>
  293. </code></ul>
  294. </li>
  295. </ul>
  296. <br>
  297. </ul>
  298. =end html_DE
  299. =cut