91_watchdog.pm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. ##############################################
  2. # $Id: 91_watchdog.pm 16963 2018-07-09 07:40:22Z rudolfkoenig $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use Time::HiRes qw(gettimeofday);
  7. #####################################
  8. sub
  9. watchdog_Initialize($)
  10. {
  11. my ($hash) = @_;
  12. $hash->{DefFn} = "watchdog_Define";
  13. $hash->{UndefFn} = "watchdog_Undef";
  14. $hash->{AttrFn} = "watchdog_Attr";
  15. $hash->{SetFn} = "watchdog_Set";
  16. $hash->{NotifyFn} = "watchdog_Notify";
  17. no warnings 'qw';
  18. my @attrList = qw(
  19. activateOnStart:0,1
  20. addStateEvent:0,1
  21. autoRestart:0,1
  22. disable:0,1
  23. disabledForIntervals
  24. execOnReactivate
  25. regexp1WontReactivate:0,1
  26. );
  27. use warnings 'qw';
  28. $hash->{AttrList} = join(" ", @attrList);
  29. # acivateOnStart handling
  30. InternalTimer(1, sub() {
  31. my @arr = devspec2array("TYPE=watchdog");
  32. my $now = time();
  33. foreach my $wd (@arr) {
  34. next if(!AttrVal($wd, "activateOnStart", 0));
  35. my $wh = $defs{$wd};
  36. my $aTime = ReadingsTimestamp($wd, "Activated", undef);
  37. my $tTime = ReadingsTimestamp($wd, "Triggered", undef);
  38. my $rTime = ReadingsTimestamp($wd, "Reset", undef);
  39. next if(!$aTime ||
  40. ($rTime && $rTime gt $aTime) ||
  41. ($tTime && $tTime gt $aTime) ||
  42. time_str2num($aTime)+$wh->{TO} <= $now);
  43. my $remaining = time_str2num($aTime)+$wh->{TO};
  44. watchdog_Activate($wh, $remaining);
  45. }
  46. }, 1) if(!$init_done);
  47. }
  48. sub
  49. watchdog_reset($)
  50. {
  51. my ($watchdog) = @_;
  52. $watchdog->{STATE} = "defined";
  53. setReadingsVal($watchdog, "Reset", "reset", TimeNow());
  54. }
  55. #####################################
  56. # defined watchme watchdog reg1 timeout reg2 command
  57. sub
  58. watchdog_Define($$)
  59. {
  60. my ($watchdog, $def) = @_;
  61. my ($name, $type, $re1, $to, $re2, $cmd) = split("[ \t]+", $def, 6);
  62. if(defined($watchdog->{TO})) { # modify
  63. $re1 = $watchdog->{RE1} if(!defined($re1));
  64. $to = $watchdog->{TO} if(!defined($to));
  65. $re2 = $watchdog->{RE2} if(!defined($re2));
  66. $cmd = $watchdog->{CMD} if(!defined($cmd));
  67. $watchdog->{DEF} = "$re1 $to $re2 $cmd";
  68. } else {
  69. return "Usage: define <name> watchdog <re1> <timeout> <re2> <command>"
  70. if(!$cmd);
  71. }
  72. # Checking for misleading regexps
  73. eval { "Hallo" =~ m/^$re1$/ };
  74. return "Bad regexp 1: $@" if($@);
  75. $re2 = $re1 if($re2 eq "SAME");
  76. eval { "Hallo" =~ m/^$re2$/ };
  77. return "Bad regexp 2: $@" if($@);
  78. return "Wrong timespec, must be HH:MM[:SS]"
  79. if($to !~ m/^(\d\d):(\d\d)(:\d\d)?$/);
  80. $to = $1*3600+$2*60+($3 ? substr($3,1) : 0);
  81. $watchdog->{RE1} = $re1;
  82. $watchdog->{RE2} = $re2;
  83. $watchdog->{TO} = $to;
  84. $watchdog->{CMD} = $cmd;
  85. if($re1 eq ".") {
  86. watchdog_Activate($watchdog)
  87. } else {
  88. $watchdog->{STATE} = "defined"; # do not set the reading
  89. }
  90. InternalTimer(1, sub($){
  91. my $re = ($re1 eq "." ? "" : $re1).
  92. (($re2 eq "SAME" || $re2 eq $re1) ? "" : "|$re2");
  93. $re .= "|$name"; # for trigger w .
  94. notifyRegexpChanged($watchdog, $re);
  95. }, $watchdog, 0);
  96. return undef;
  97. }
  98. #####################################
  99. sub
  100. watchdog_Notify($$)
  101. {
  102. my ($watchdog, $dev) = @_;
  103. my $ln = $watchdog->{NAME};
  104. return "" if(IsDisabled($ln) || $watchdog->{STATE} eq "inactive");
  105. my $dontReAct = AttrVal($ln, "regexp1WontReactivate", 0);
  106. my $n = $dev->{NAME};
  107. my $re1 = $watchdog->{RE1};
  108. my $re2 = $watchdog->{RE2};
  109. my $events = deviceEvents($dev, AttrVal($ln, "addStateEvent", 0));
  110. my $max = int(@{$events});
  111. for (my $i = 0; $i < $max; $i++) {
  112. my $s = $events->[$i];
  113. $s = "" if(!defined($s));
  114. my $dotTrigger = ($ln eq $n && $s eq "."); # trigger w .
  115. if($watchdog->{STATE} =~ m/Next:/) {
  116. if($dotTrigger) {
  117. RemoveInternalTimer($watchdog);
  118. watchdog_reset($watchdog);
  119. return;
  120. }
  121. if($n =~ m/^$re2$/ || "$n:$s" =~ m/^$re2$/) {
  122. my $isRe1 = ($re1 eq $re2 || $re1 eq ".");
  123. return if($isRe1 && $dontReAct); # 60414
  124. RemoveInternalTimer($watchdog);
  125. if($re1 eq $re2 || $re1 eq ".") {
  126. watchdog_Activate($watchdog);
  127. return "";
  128. } else {
  129. watchdog_reset($watchdog);
  130. }
  131. } elsif($n =~ m/^$re1$/ || "$n:$s" =~ m/^$re1$/) {
  132. watchdog_Activate($watchdog) if(!$dontReAct);
  133. }
  134. } elsif($watchdog->{STATE} eq "defined") {
  135. if($dotTrigger || ($n =~ m/^$re1$/ || "$n:$s" =~ m/^$re1$/)) {
  136. watchdog_Activate($watchdog)
  137. }
  138. } elsif($dotTrigger) {
  139. watchdog_reset($watchdog); # trigger w .
  140. }
  141. }
  142. return "";
  143. }
  144. sub
  145. watchdog_Trigger($)
  146. {
  147. my ($watchdog) = @_;
  148. my $name = $watchdog->{NAME};
  149. if(IsDisabled($name) || $watchdog->{STATE} eq "inactive") {
  150. watchdog_reset($watchdog);
  151. return "";
  152. }
  153. Log3 $name, 3, "Watchdog $name triggered";
  154. my $exec = SemicolonEscape($watchdog->{CMD});
  155. $watchdog->{STATE} = "triggered";
  156. setReadingsVal($watchdog, "Triggered", "triggered", TimeNow());
  157. my $ret = AnalyzeCommandChain(undef, $exec);
  158. Log3 $name, 3, $ret if($ret);
  159. if(AttrVal($name, "autoRestart", 0)) {
  160. watchdog_reset($watchdog); # auto trigger w .
  161. }
  162. }
  163. sub
  164. watchdog_Activate($;$)
  165. {
  166. my ($watchdog, $remaining) = @_;
  167. my $nt = ($remaining ? $remaining : gettimeofday() + $watchdog->{TO});
  168. $watchdog->{STATE} = "Next: " . FmtTime($nt);
  169. RemoveInternalTimer($watchdog);
  170. InternalTimer($nt, "watchdog_Trigger", $watchdog, 0);
  171. my $eor = AttrVal($watchdog->{NAME}, "execOnReactivate", undef);
  172. if($eor) {
  173. my $wName = $watchdog->{NAME};
  174. my $aTime = ReadingsTimestamp($wName, "Activated", "");
  175. my $tTime = ReadingsTimestamp($wName, "Triggered", "");
  176. $eor = undef if(!$aTime || !$tTime || $aTime ge $tTime)
  177. }
  178. setReadingsVal($watchdog, "Activated","activated", TimeNow()) if(!$remaining);
  179. AnalyzeCommandChain(undef, SemicolonEscape($eor)) if($eor);
  180. }
  181. sub
  182. watchdog_Undef($$)
  183. {
  184. my ($hash, $name) = @_;
  185. RemoveInternalTimer($hash);
  186. return undef;
  187. }
  188. sub
  189. watchdog_Attr(@)
  190. {
  191. my ($cmd, $name, $attrName, $attrVal) = @_;
  192. my $do = 0;
  193. my $hash = $defs{$name};
  194. if($cmd eq "set" && $attrName eq "disable") {
  195. $do = (!defined($attrVal) || $attrVal) ? 1 : 2;
  196. }
  197. $do = 2 if($cmd eq "del" && (!$attrName || $attrName eq "disable"));
  198. return if(!$do);
  199. $hash->{STATE} = ($do == 1 ? "disabled" : "defined");
  200. return undef;
  201. }
  202. sub
  203. watchdog_Set($@)
  204. {
  205. my ($hash, @a) = @_;
  206. my $me = $hash->{NAME};
  207. return "no set argument specified" if(int(@a) < 2);
  208. my %sets = (inactive=>0, active=>0);
  209. my $cmd = $a[1];
  210. return "Unknown argument $cmd, choose one of ".join(" ", sort keys %sets)
  211. if(!defined($sets{$cmd}));
  212. return "$cmd needs $sets{$cmd} parameter(s)" if(@a-$sets{$cmd} != 2);
  213. if($cmd eq "inactive") {
  214. readingsSingleUpdate($hash, "state", "inactive", 1);
  215. }
  216. elsif($cmd eq "active") {
  217. readingsSingleUpdate($hash, "state", "defined", 1)
  218. if(!AttrVal($me, "disable", undef));
  219. }
  220. return undef;
  221. }
  222. 1;
  223. =pod
  224. =item helper
  225. =item summary execute a command, if no event is received within timeout
  226. =item summary_DE f&uuml;hrt Befehl aus, falls innerhalb des Timeouts kein Event empfangen wurde
  227. =begin html
  228. <a name="watchdog"></a>
  229. <h3>watchdog</h3>
  230. <ul>
  231. <br>
  232. <a name="watchdogdefine"></a>
  233. <b>Define</b>
  234. <ul>
  235. <code>define &lt;name&gt; watchdog &lt;regexp1&gt; &lt;timespec&gt; &lt;regexp2&gt; &lt;command&gt;</code><br>
  236. <br>
  237. Start an arbitrary FHEM command if after &lt;timespec&gt; receiving an
  238. event matching &lt;regexp1&gt; no event matching &lt;regexp2&gt; is
  239. received.<br>
  240. The syntax for &lt;regexp1&gt; and &lt;regexp2&gt; is the same as the
  241. regexp for <a href="#notify">notify</a>.<br>
  242. &lt;timespec&gt; is HH:MM[:SS]<br>
  243. &lt;command&gt; is a usual fhem command like used in the <a
  244. href="#at">at</a> or <a href="#notify">notify</a>
  245. <br><br>
  246. Examples:
  247. <code><ul>
  248. # Request data from the FHT80 _once_ if we do not receive any message for<br>
  249. # 15 Minutes.<br>
  250. define w watchdog FHT80 00:15:00 SAME set FHT80 date<br>
  251. # Request data from the FHT80 _each_ time we do not receive any message for
  252. <br>
  253. # 15 Minutes, i.e. reactivate the watchdog after it triggered. Might be<br>
  254. # dangerous, as it can trigger in a loop.<br>
  255. define w watchdog FHT80 00:15:00 SAME set FHT80 date;; trigger w .<br>
  256. # Shout once if the HMS100-FIT is not alive<br>
  257. define w watchdog HMS100-FIT 01:00:00 SAME "alarm-fit.sh"<br>
  258. # Send mail if the window is left open<br>
  259. define w watchdog contact1:open 00:15 contact1:closed
  260. "mail_me close window1"<br>
  261. attr w regexp1WontReactivate<br>
  262. </ul></code>
  263. Notes:<br>
  264. <ul>
  265. <li>if &lt;regexp1&gt; is . (dot), then activate the watchdog at
  266. definition time. Else it will be activated when the first matching
  267. event is received.</li>
  268. <li>&lt;regexp1&gt; resets the timer of a running watchdog, to avoid it
  269. use the regexp1WontReactivate attribute.</li>
  270. <li>if &lt;regexp2&gt; is SAME, then it will be the same as the first
  271. regexp, and it will be reactivated, when it is received.
  272. </li>
  273. <li>trigger &lt;watchdogname&gt; . will activate the trigger if its state
  274. is defined, and set it into state defined if its state is active
  275. (Next:...) or triggered. You always have to reactivate the watchdog
  276. with this command once it has triggered (unless you restart
  277. fhem)</li>
  278. <li>a generic watchdog (one watchdog responsible for more devices) is
  279. currently not possible.</li>
  280. <li>with modify all parameters are optional, and will not be changed if
  281. not specified.</li>
  282. </ul>
  283. <br>
  284. </ul>
  285. <a name="watchdogset"></a>
  286. <b>Set </b>
  287. <ul>
  288. <li>inactive<br>
  289. Inactivates the current device. Note the slight difference to the
  290. disable attribute: using set inactive the state is automatically saved
  291. to the statefile on shutdown, there is no explicit save necesary.<br>
  292. This command is intended to be used by scripts to temporarily
  293. deactivate the notify.<br>
  294. The concurrent setting of the disable attribute is not recommended.</li>
  295. <li>active<br>
  296. Activates the current device (see inactive).</li>
  297. </ul>
  298. <br>
  299. <a name="watchdogget"></a>
  300. <b>Get</b> <ul>N/A</ul><br>
  301. <a name="watchdogattr"></a>
  302. <b>Attributes</b>
  303. <ul>
  304. <li><a name="#activateOnStart">activateOnStart</a><br>
  305. if set, the watchdog will be activated after a FHEM start if appropriate,
  306. determined by the Timestamp of the Activated and the Triggered readings.
  307. Note: since between shutdown and startup events may be missed, this
  308. attribute is 0 (disabled) by default.
  309. </li>
  310. <li><a href="#addStateEvent">addStateEvent</a></li>
  311. <li><a href="#disable">disable</a></li>
  312. <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
  313. <li><a name="regexp1WontReactivate">regexp1WontReactivate</a><br>
  314. When a watchdog is active, a second event matching regexp1 will
  315. normally reset the timeout. Set this attribute to prevents this.</li>
  316. <li><a href="#execOnReactivate">execOnActivate</a>
  317. If set, its value will be executed as a FHEM command when the watchdog is
  318. reactivated (after triggering) by receiving an event matching regexp1.
  319. </li>
  320. <li><a href="#autoRestart">autoRestart</a>
  321. When the watchdog has triggered it will be automatically re-set to state
  322. defined again (waiting for regexp1) if this attribute is set to 1.
  323. </li>
  324. </ul>
  325. <br>
  326. </ul>
  327. =end html
  328. =begin html_DE
  329. <a name="watchdog"></a>
  330. <h3>watchdog</h3>
  331. <ul>
  332. <br>
  333. <a name="watchdogdefine"></a>
  334. <b>Define</b>
  335. <ul>
  336. <code>define &lt;name&gt; watchdog &lt;regexp1&gt; &lt;timespec&gt;
  337. &lt;regexp2&gt; &lt;command&gt;</code><br>
  338. <br>
  339. Startet einen beliebigen FHEM Befehl wenn nach dem Empfang des
  340. Ereignisses &lt;regexp1&gt; nicht innerhalb von &lt;timespec&gt; ein
  341. &lt;regexp2&gt; Ereignis empfangen wird.<br>
  342. Der Syntax f&uuml;r &lt;regexp1&gt; und &lt;regexp2&gt; ist der gleiche wie
  343. regexp f&uuml;r <a href="#notify">notify</a>.<br>
  344. &lt;timespec&gt; ist HH:MM[:SS]<br>
  345. &lt;command&gt; ist ein gew&ouml;hnlicher fhem Befehl wie z.B. in <a
  346. href="#at">at</a> oderr <a href="#notify">notify</a>
  347. <br><br>
  348. Beispiele:
  349. <code><ul>
  350. # Frage Daten vom FHT80 _einmalig_ ab, wenn wir keine Nachricht f&uuml;r<br>
  351. # 15 Minuten erhalten haben.<br>
  352. define w watchdog FHT80 00:15:00 SAME set FHT80 date<br><br>
  353. # Frage Daten vom FHT80 jedes Mal ab, wenn keine Nachricht f&uuml;r<br>
  354. # 15 Minuten emfpangen wurde, d.h. reaktiviere den Watchdog nachdem er
  355. getriggert wurde.<br>
  356. # Kann gef&auml;hrlich sein, da er so in einer Schleife getriggert werden
  357. kann.<br>
  358. define w watchdog FHT80 00:15:00 SAME set FHT80 date;; trigger w .<br><br>
  359. # Alarmiere einmalig wenn vom HMS100-FIT f&uuml;r eine Stunde keine
  360. Nachricht empfangen wurde.<br>
  361. define w watchdog HMS100-FIT 01:00 SAME "alarm-fit.sh"<br><br>
  362. # Sende eine Mail wenn das Fenster offen gelassen wurde<br>
  363. define w watchdog contact1:open 00:15 contact1:closed "mail_me close
  364. window1"<br>
  365. attr w regexp1WontReactivate<br><br>
  366. </ul></code>
  367. Hinweise:<br>
  368. <ul>
  369. <li>Wenn &lt;regexp1&gt; . (Punkt) ist, dann aktiviere den Watchdog zur
  370. definierten Zeit. Sonst wird er durch den Empfang des ersten passenden
  371. Events aktiviert.</li>
  372. <li>&lt;regexp1&gt; Resetet den Timer eines laufenden Watchdogs. Um das
  373. zu verhindern wird das regexp1WontReactivate Attribut gesetzt.</li>
  374. <li>Wenn &lt;regexp2&gt; SAME ist , dann ist es das gleiche wie das erste
  375. regexp, und wird reaktiviert wenn es empfangen wird. </li>
  376. <li>trigger &lt;watchdogname&gt; . aktiviert den Trigger wenn dessen
  377. Status defined ist und setzt ihn in den Status defined wenn sein status
  378. triggered oder aktiviert (Next:...) ist.<br>
  379. Der Watchdog musst immer mit diesem Befehl reaktiviert werden wenn er
  380. getriggert wurde.</li>
  381. <li>Ein generischer Watchdog (ein Watchdog, verantwortlich f&uuml;r
  382. mehrere Devices) ist derzeit nicht m&ouml;glich.</li>
  383. <li>Bei modify sind alle Parameter optional, und werden nicht geaendert,
  384. falls nicht spezifiziert.</li>
  385. </ul>
  386. <br>
  387. </ul>
  388. <a name="watchdogset"></a>
  389. <b>Set </b>
  390. <ul>
  391. <li>inactive<br>
  392. Deaktiviert das entsprechende Ger&auml;t. Beachte den leichten
  393. semantischen Unterschied zum disable Attribut: "set inactive"
  394. wird bei einem shutdown automatisch in fhem.state gespeichert, es ist
  395. kein save notwendig.<br>
  396. Der Einsatzzweck sind Skripte, um das notify tempor&auml;r zu
  397. deaktivieren.<br>
  398. Das gleichzeitige Verwenden des disable Attributes wird nicht empfohlen.
  399. </li>
  400. <li>active<br>
  401. Aktiviert das entsprechende Ger&auml;t, siehe inactive.
  402. </li>
  403. </ul>
  404. <br>
  405. <a name="watchdogget"></a>
  406. <b>Get</b> <ul>N/A</ul><br>
  407. <a name="watchdogattr"></a>
  408. <b>Attribute</b>
  409. <ul>
  410. <li><a name="#activateOnStart">activateOnStart</a><br>
  411. Falls gesetzt, wird der Watchdog nach FHEM-Neustart aktiviert, je nach
  412. Zeitstempel der Activated und Triggered Readings. Da zwischen Shutdown
  413. und Neustart Events verlorengehen k&ouml;nnen, ist die Voreinstellung 0
  414. (deaktiviert).
  415. </li>
  416. <li><a href="#addStateEvent">addStateEvent</a></li>
  417. <li><a href="#disable">disable</a></li>
  418. <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
  419. <li><a name="regexp1WontReactivate">regexp1WontReactivate</a><br>
  420. Wenn ein Watchdog aktiv ist, wird ein zweites Ereignis das auf regexp1
  421. passt normalerweise den Timer zur&uuml;cksetzen. Dieses Attribut wird
  422. das verhindern.</li>
  423. <li><a href="#execOnReactivate">execOnActivate</a>
  424. Falls gesetzt, wird der Wert des Attributes als FHEM Befehl
  425. ausgef&uuml;hrt, wenn ein regexp1 Ereignis den Watchdog
  426. aktiviert nachdem er ausgel&ouml;st wurde.</li>
  427. <li><a href="#autoRestart">autoRestart</a>
  428. Wenn dieses Attribut gesetzt ist, wird der Watchdog nach dem er
  429. getriggert wurde, automatisch wieder in den Zustand defined
  430. gesetzt (Wartet also wieder auf Aktivierung durch regexp1)</li>
  431. </ul>
  432. <br>
  433. </ul>
  434. =end html_DE
  435. =cut