############################################## # $Id$ package main; use strict; use warnings; use Time::HiRes qw(gettimeofday); ##################################### sub watchdog_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "watchdog_Define"; $hash->{UndefFn} = "watchdog_Undef"; $hash->{AttrFn} = "watchdog_Attr"; $hash->{SetFn} = "watchdog_Set"; $hash->{NotifyFn} = "watchdog_Notify"; no warnings 'qw'; my @attrList = qw( activateOnStart:0,1 addStateEvent:0,1 autoRestart:0,1 disable:0,1 disabledForIntervals execOnReactivate regexp1WontReactivate:0,1 ); use warnings 'qw'; $hash->{AttrList} = join(" ", @attrList); # acivateOnStart handling InternalTimer(1, sub() { my @arr = devspec2array("TYPE=watchdog"); my $now = time(); foreach my $wd (@arr) { next if(!AttrVal($wd, "activateOnStart", 0)); my $wh = $defs{$wd}; my $aTime = ReadingsTimestamp($wd, "Activated", undef); my $tTime = ReadingsTimestamp($wd, "Triggered", undef); my $rTime = ReadingsTimestamp($wd, "Reset", undef); next if(!$aTime || ($rTime && $rTime gt $aTime) || ($tTime && $tTime gt $aTime) || time_str2num($aTime)+$wh->{TO} <= $now); my $remaining = time_str2num($aTime)+$wh->{TO}; watchdog_Activate($wh, $remaining); } }, 1) if(!$init_done); } sub watchdog_reset($) { my ($watchdog) = @_; $watchdog->{STATE} = "defined"; setReadingsVal($watchdog, "Reset", "reset", TimeNow()); } ##################################### # defined watchme watchdog reg1 timeout reg2 command sub watchdog_Define($$) { my ($watchdog, $def) = @_; my ($name, $type, $re1, $to, $re2, $cmd) = split("[ \t]+", $def, 6); if(defined($watchdog->{TO})) { # modify $re1 = $watchdog->{RE1} if(!defined($re1)); $to = $watchdog->{TO} if(!defined($to)); $re2 = $watchdog->{RE2} if(!defined($re2)); $cmd = $watchdog->{CMD} if(!defined($cmd)); $watchdog->{DEF} = "$re1 $to $re2 $cmd"; } else { return "Usage: define watchdog " if(!$cmd); } # Checking for misleading regexps eval { "Hallo" =~ m/^$re1$/ }; return "Bad regexp 1: $@" if($@); $re2 = $re1 if($re2 eq "SAME"); eval { "Hallo" =~ m/^$re2$/ }; return "Bad regexp 2: $@" if($@); return "Wrong timespec, must be HH:MM[:SS]" if($to !~ m/^(\d\d):(\d\d)(:\d\d)?$/); $to = $1*3600+$2*60+($3 ? substr($3,1) : 0); $watchdog->{RE1} = $re1; $watchdog->{RE2} = $re2; $watchdog->{TO} = $to; $watchdog->{CMD} = $cmd; if($re1 eq ".") { watchdog_Activate($watchdog) } else { $watchdog->{STATE} = "defined"; # do not set the reading } InternalTimer(1, sub($){ my $re = ($re1 eq "." ? "" : $re1). (($re2 eq "SAME" || $re2 eq $re1) ? "" : "|$re2"); $re .= "|$name"; # for trigger w . notifyRegexpChanged($watchdog, $re); }, $watchdog, 0); return undef; } ##################################### sub watchdog_Notify($$) { my ($watchdog, $dev) = @_; my $ln = $watchdog->{NAME}; return "" if(IsDisabled($ln) || $watchdog->{STATE} eq "inactive"); my $dontReAct = AttrVal($ln, "regexp1WontReactivate", 0); my $n = $dev->{NAME}; my $re1 = $watchdog->{RE1}; my $re2 = $watchdog->{RE2}; my $events = deviceEvents($dev, AttrVal($ln, "addStateEvent", 0)); my $max = int(@{$events}); for (my $i = 0; $i < $max; $i++) { my $s = $events->[$i]; $s = "" if(!defined($s)); my $dotTrigger = ($ln eq $n && $s eq "."); # trigger w . if($watchdog->{STATE} =~ m/Next:/) { if($dotTrigger) { RemoveInternalTimer($watchdog); watchdog_reset($watchdog); return; } if($n =~ m/^$re2$/ || "$n:$s" =~ m/^$re2$/) { my $isRe1 = ($re1 eq $re2 || $re1 eq "."); return if($isRe1 && $dontReAct); # 60414 RemoveInternalTimer($watchdog); if($re1 eq $re2 || $re1 eq ".") { watchdog_Activate($watchdog); return ""; } else { watchdog_reset($watchdog); } } elsif($n =~ m/^$re1$/ || "$n:$s" =~ m/^$re1$/) { watchdog_Activate($watchdog) if(!$dontReAct); } } elsif($watchdog->{STATE} eq "defined") { if($dotTrigger || ($n =~ m/^$re1$/ || "$n:$s" =~ m/^$re1$/)) { watchdog_Activate($watchdog) } } elsif($dotTrigger) { watchdog_reset($watchdog); # trigger w . } } return ""; } sub watchdog_Trigger($) { my ($watchdog) = @_; my $name = $watchdog->{NAME}; if(IsDisabled($name) || $watchdog->{STATE} eq "inactive") { watchdog_reset($watchdog); return ""; } Log3 $name, 3, "Watchdog $name triggered"; my $exec = SemicolonEscape($watchdog->{CMD}); $watchdog->{STATE} = "triggered"; setReadingsVal($watchdog, "Triggered", "triggered", TimeNow()); my $ret = AnalyzeCommandChain(undef, $exec); Log3 $name, 3, $ret if($ret); if(AttrVal($name, "autoRestart", 0)) { watchdog_reset($watchdog); # auto trigger w . } } sub watchdog_Activate($;$) { my ($watchdog, $remaining) = @_; my $nt = ($remaining ? $remaining : gettimeofday() + $watchdog->{TO}); $watchdog->{STATE} = "Next: " . FmtTime($nt); RemoveInternalTimer($watchdog); InternalTimer($nt, "watchdog_Trigger", $watchdog, 0); my $eor = AttrVal($watchdog->{NAME}, "execOnReactivate", undef); if($eor) { my $wName = $watchdog->{NAME}; my $aTime = ReadingsTimestamp($wName, "Activated", ""); my $tTime = ReadingsTimestamp($wName, "Triggered", ""); $eor = undef if(!$aTime || !$tTime || $aTime ge $tTime) } setReadingsVal($watchdog, "Activated","activated", TimeNow()) if(!$remaining); AnalyzeCommandChain(undef, SemicolonEscape($eor)) if($eor); } sub watchdog_Undef($$) { my ($hash, $name) = @_; RemoveInternalTimer($hash); return undef; } sub watchdog_Attr(@) { my ($cmd, $name, $attrName, $attrVal) = @_; my $do = 0; my $hash = $defs{$name}; if($cmd eq "set" && $attrName eq "disable") { $do = (!defined($attrVal) || $attrVal) ? 1 : 2; } $do = 2 if($cmd eq "del" && (!$attrName || $attrName eq "disable")); return if(!$do); $hash->{STATE} = ($do == 1 ? "disabled" : "defined"); return undef; } sub watchdog_Set($@) { my ($hash, @a) = @_; my $me = $hash->{NAME}; return "no set argument specified" if(int(@a) < 2); my %sets = (inactive=>0, active=>0); my $cmd = $a[1]; return "Unknown argument $cmd, choose one of ".join(" ", sort keys %sets) if(!defined($sets{$cmd})); return "$cmd needs $sets{$cmd} parameter(s)" if(@a-$sets{$cmd} != 2); if($cmd eq "inactive") { readingsSingleUpdate($hash, "state", "inactive", 1); } elsif($cmd eq "active") { readingsSingleUpdate($hash, "state", "defined", 1) if(!AttrVal($me, "disable", undef)); } return undef; } 1; =pod =item helper =item summary execute a command, if no event is received within timeout =item summary_DE führt Befehl aus, falls innerhalb des Timeouts kein Event empfangen wurde =begin html

watchdog


    Define
      define <name> watchdog <regexp1> <timespec> <regexp2> <command>

      Start an arbitrary FHEM command if after <timespec> receiving an event matching <regexp1> no event matching <regexp2> is received.
      The syntax for <regexp1> and <regexp2> is the same as the regexp for notify.
      <timespec> is HH:MM[:SS]
      <command> is a usual fhem command like used in the at or notify

      Examples:
        # Request data from the FHT80 _once_ if we do not receive any message for
        # 15 Minutes.
        define w watchdog FHT80 00:15:00 SAME set FHT80 date
        # Request data from the FHT80 _each_ time we do not receive any message for
        # 15 Minutes, i.e. reactivate the watchdog after it triggered. Might be
        # dangerous, as it can trigger in a loop.
        define w watchdog FHT80 00:15:00 SAME set FHT80 date;; trigger w .
        # Shout once if the HMS100-FIT is not alive
        define w watchdog HMS100-FIT 01:00:00 SAME "alarm-fit.sh"
        # Send mail if the window is left open
        define w watchdog contact1:open 00:15 contact1:closed "mail_me close window1"
        attr w regexp1WontReactivate
      Notes:
      • if <regexp1> is . (dot), then activate the watchdog at definition time. Else it will be activated when the first matching event is received.
      • <regexp1> resets the timer of a running watchdog, to avoid it use the regexp1WontReactivate attribute.
      • if <regexp2> is SAME, then it will be the same as the first regexp, and it will be reactivated, when it is received.
      • trigger <watchdogname> . will activate the trigger if its state is defined, and set it into state defined if its state is active (Next:...) or triggered. You always have to reactivate the watchdog with this command once it has triggered (unless you restart fhem)
      • a generic watchdog (one watchdog responsible for more devices) is currently not possible.
      • with modify all parameters are optional, and will not be changed if not specified.

    Set
    • inactive
      Inactivates the current device. Note the slight difference to the disable attribute: using set inactive the state is automatically saved to the statefile on shutdown, there is no explicit save necesary.
      This command is intended to be used by scripts to temporarily deactivate the notify.
      The concurrent setting of the disable attribute is not recommended.
    • active
      Activates the current device (see inactive).

    Get
      N/A

    Attributes
    • activateOnStart
      if set, the watchdog will be activated after a FHEM start if appropriate, determined by the Timestamp of the Activated and the Triggered readings. Note: since between shutdown and startup events may be missed, this attribute is 0 (disabled) by default.
    • addStateEvent
    • disable
    • disabledForIntervals
    • regexp1WontReactivate
      When a watchdog is active, a second event matching regexp1 will normally reset the timeout. Set this attribute to prevents this.
    • execOnActivate If set, its value will be executed as a FHEM command when the watchdog is reactivated (after triggering) by receiving an event matching regexp1.
    • autoRestart When the watchdog has triggered it will be automatically re-set to state defined again (waiting for regexp1) if this attribute is set to 1.

=end html =begin html_DE

watchdog


    Define
      define <name> watchdog <regexp1> <timespec> <regexp2> <command>

      Startet einen beliebigen FHEM Befehl wenn nach dem Empfang des Ereignisses <regexp1> nicht innerhalb von <timespec> ein <regexp2> Ereignis empfangen wird.
      Der Syntax für <regexp1> und <regexp2> ist der gleiche wie regexp für notify.
      <timespec> ist HH:MM[:SS]
      <command> ist ein gewöhnlicher fhem Befehl wie z.B. in at oderr notify

      Beispiele:
        # Frage Daten vom FHT80 _einmalig_ ab, wenn wir keine Nachricht für
        # 15 Minuten erhalten haben.
        define w watchdog FHT80 00:15:00 SAME set FHT80 date

        # Frage Daten vom FHT80 jedes Mal ab, wenn keine Nachricht für
        # 15 Minuten emfpangen wurde, d.h. reaktiviere den Watchdog nachdem er getriggert wurde.
        # Kann gefährlich sein, da er so in einer Schleife getriggert werden kann.
        define w watchdog FHT80 00:15:00 SAME set FHT80 date;; trigger w .

        # Alarmiere einmalig wenn vom HMS100-FIT für eine Stunde keine Nachricht empfangen wurde.
        define w watchdog HMS100-FIT 01:00 SAME "alarm-fit.sh"

        # Sende eine Mail wenn das Fenster offen gelassen wurde
        define w watchdog contact1:open 00:15 contact1:closed "mail_me close window1"
        attr w regexp1WontReactivate

      Hinweise:
      • Wenn <regexp1> . (Punkt) ist, dann aktiviere den Watchdog zur definierten Zeit. Sonst wird er durch den Empfang des ersten passenden Events aktiviert.
      • <regexp1> Resetet den Timer eines laufenden Watchdogs. Um das zu verhindern wird das regexp1WontReactivate Attribut gesetzt.
      • Wenn <regexp2> SAME ist , dann ist es das gleiche wie das erste regexp, und wird reaktiviert wenn es empfangen wird.
      • trigger <watchdogname> . aktiviert den Trigger wenn dessen Status defined ist und setzt ihn in den Status defined wenn sein status triggered oder aktiviert (Next:...) ist.
        Der Watchdog musst immer mit diesem Befehl reaktiviert werden wenn er getriggert wurde.
      • Ein generischer Watchdog (ein Watchdog, verantwortlich für mehrere Devices) ist derzeit nicht möglich.
      • Bei modify sind alle Parameter optional, und werden nicht geaendert, falls nicht spezifiziert.

    Set
    • inactive
      Deaktiviert das entsprechende Gerät. Beachte den leichten semantischen Unterschied zum disable Attribut: "set inactive" wird bei einem shutdown automatisch in fhem.state gespeichert, es ist kein save notwendig.
      Der Einsatzzweck sind Skripte, um das notify temporär zu deaktivieren.
      Das gleichzeitige Verwenden des disable Attributes wird nicht empfohlen.
    • active
      Aktiviert das entsprechende Gerät, siehe inactive.

    Get
      N/A

    Attribute
    • activateOnStart
      Falls gesetzt, wird der Watchdog nach FHEM-Neustart aktiviert, je nach Zeitstempel der Activated und Triggered Readings. Da zwischen Shutdown und Neustart Events verlorengehen können, ist die Voreinstellung 0 (deaktiviert).
    • addStateEvent
    • disable
    • disabledForIntervals
    • regexp1WontReactivate
      Wenn ein Watchdog aktiv ist, wird ein zweites Ereignis das auf regexp1 passt normalerweise den Timer zurücksetzen. Dieses Attribut wird das verhindern.
    • execOnActivate Falls gesetzt, wird der Wert des Attributes als FHEM Befehl ausgeführt, wenn ein regexp1 Ereignis den Watchdog aktiviert nachdem er ausgelöst wurde.
    • autoRestart Wenn dieses Attribut gesetzt ist, wird der Watchdog nach dem er getriggert wurde, automatisch wieder in den Zustand defined gesetzt (Wartet also wieder auf Aktivierung durch regexp1)

=end html_DE =cut