# Id ########################################################################## # $Id$ # copyright ################################################################### # # 98_monitoring.pm # # Copyright by igami # # This file is part of FHEM. # # FHEM is free software: you can redistribute it and/or modify it under the # terms of the GNU General Public License as published by the Free Software # Foundation, either version 2 of the License, or (at your option) any later # version. # # FHEM is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with # FHEM. If not, see . # packages #################################################################### package main; use strict; use warnings; # forward declarations ######################################################## sub monitoring_Initialize($); sub monitoring_Define($$); sub monitoring_Undefine($$); sub monitoring_Set($@); sub monitoring_Get($@); sub archetype_Attr(@); sub monitoring_Notify($$); sub monitoring_modify($); sub monitoring_RemoveInternalTimer($); sub monitoring_return($$); sub monitoring_setActive($); sub monitoring_setInactive($); # initialize ################################################################## sub monitoring_Initialize($) { my ($hash) = @_; my $TYPE = "monitoring"; $hash->{DefFn} = $TYPE."_Define"; $hash->{UndefFn} = $TYPE."_Undefine"; $hash->{SetFn} = $TYPE."_Set"; $hash->{GetFn} = $TYPE."_Get"; $hash->{AttrFn} = $TYPE."_Attr"; $hash->{NotifyFn} = $TYPE."_Notify"; $hash->{AttrList} = "addStateEvent:1,0 ". "blacklist:textField-long ". "disable:1,0 ". "disabledForIntervals ". "errorFuncAdd:textField-long ". "errorFuncAdded:textField-long ". "errorFuncRemove:textField-long ". "errorWait ". "errorReturn:textField-long ". "getDefault:all,error,warning ". "setActiveFunc:textField-long ". "setInactiveFunc:textField-long ". "warningFuncAdd:textField-long ". "warningFuncAdded:textField-long ". "warningFuncRemove:textField-long ". "warningWait ". "warningReturn:textField-long ". "whitelist:textField-long ". $readingFnAttributes ; } # regular Fn ################################################################## sub monitoring_Define($$) { my ($hash, $def) = @_; my ($SELF, $TYPE, @re) = split(/[\s]+/, $def, 5); return("Usage: define $TYPE []") if(int(@re) < 1 || int(@re) > 2); monitoring_NOTIFYDEV($hash); monitoring_setActive($hash) if($init_done); return; } sub monitoring_Undefine($$) { my ($hash, $arg) = @_; monitoring_setInactive($hash); monitoring_RemoveInternalTimer($hash); return; } sub monitoring_Set($@) { my ($hash, @a) = @_; my $TYPE = $hash->{TYPE}; return("\"set $TYPE\" needs at least one argument") if(@a < 2); my $SELF = shift @a; my $argument = shift @a; my $value = join(" ", @a) if (@a); my %monitoring_sets = ( "active" => "active:noArg", "clear" => "clear:all,error,warning", "errorAdd" => "errorAdd:textField", "errorRemove" => "errorRemove:". join(",", ReadingsVal($SELF, "error", "")), "inactive" => "inactive:noArg", "warningAdd" => "warningAdd:textField", "warningRemove" => "warningRemove:". join(",", ReadingsVal($SELF, "warning", "")) ); return( "Unknown argument $argument, choose one of ". join(" ", sort(values %monitoring_sets)) ) unless(exists($monitoring_sets{$argument})); if($argument eq "active"){ monitoring_setActive($hash); } elsif($argument eq "inactive"){ monitoring_setInactive($hash); readingsSingleUpdate($hash, "state", $argument, 0); Log3($SELF, 3, "$SELF ($TYPE) set $SELF inactive"); monitoring_RemoveInternalTimer($hash); } elsif($argument eq "clear"){ readingsBeginUpdate($hash); if($value =~ m/^(warning|all)$/){ readingsBulkUpdate($hash, "warning", "", 0); readingsBulkUpdate($hash, "warningCount", 0, 0); foreach my $r (keys %{$hash->{READINGS}}){ if($r =~ m/(warning)Add_(.+)/){ RemoveInternalTimer("$SELF|$1|add|$2"); delete $hash->{READINGS}{$r}; } } } if($value =~ m/^(error|all)$/){ readingsBulkUpdate($hash, "error", "", 0); readingsBulkUpdate($hash, "errorCount", 0, 0); foreach my $r (keys %{$hash->{READINGS}}){ if($r =~ m/(error)Add_(.+)/){ RemoveInternalTimer("$SELF|$1|add|$2"); delete $hash->{READINGS}{$r}; } } } readingsBulkUpdate($hash, "state", "$argument $value", 0) unless(IsDisabled($SELF)); readingsEndUpdate($hash, 0); Log3($SELF, 2, "$TYPE ($SELF) set $SELF $argument $value"); } elsif($argument =~ /^(error|warning)(Add|Remove)$/){ monitoring_modify("$SELF|$1|".lc($2)."|$value"); } return; } sub monitoring_Get($@) { my ($hash, @a) = @_; my $TYPE = $hash->{TYPE}; my $SELF = shift @a; return if(IsDisabled($SELF)); return("\"get $TYPE\" needs at least one argument") if(@a < 1); my $argument = shift @a; my $value = join(" ", @a) if (@a); my $default = AttrVal($SELF, "getDefault", "all"); my %monitoring_gets = ( "all" => "all:noArg", "default" => "default:noArg", "error" => "error:noArg", "warning" => "warning:noArg" ); my @ret; return( "Unknown argument $argument, choose one of ". join(" ", sort(values %monitoring_gets)) ) unless(exists($monitoring_gets{$argument})); if($argument eq "all" || ($argument eq "default" && $default eq "all")){ push(@ret, monitoring_return($hash, "error")); push(@ret, monitoring_return($hash, "warning")); } elsif($argument eq "default"){ push(@ret, monitoring_return($hash, $default)); } elsif($argument eq "error"){ push(@ret, monitoring_return($hash, "error")); } elsif($argument eq "warning"){ push(@ret, monitoring_return($hash, "warning")); } return(join("\n\n", @ret)."\n") if(@ret); return; } sub monitoring_Attr(@) { my ($cmd, $SELF, $attribute, $value) = @_; my ($hash) = $defs{$SELF}; if($attribute =~ "blacklist" && $value){ my @blacklist; push(@blacklist, devspec2array($_)) foreach (split(/[\s]+/, $value)); my %blacklist = map{$_, 1} @blacklist; foreach my $name (sort(keys %blacklist)){ monitoring_modify("$SELF|warning|remove|$name"); monitoring_modify("$SELF|error|remove|$name"); } } elsif($attribute eq "whitelist"){ monitoring_NOTIFYDEV($hash); if($value){ my @whitelist; push(@whitelist, devspec2array($_)) foreach (split(/[\s]+/, $value)); foreach my $list ("warning", "error"){ foreach my $name (split(",", ReadingsVal($SELF, $list, ""))){ monitoring_modify("$SELF|$list|remove|$name") unless(grep(/$name/, @whitelist)); } } } } elsif($attribute eq "disable"){ if($cmd eq "set" and $value == 1){ monitoring_setActive($hash); } else{ monitoring_setInactive($hash); readingsSingleUpdate($hash, "state", "disabled", 0); Log3($SELF, 3, "$hash->{TYPE} ($SELF) attr $SELF disabled"); } } return; } sub monitoring_Notify($$) { my ($hash, $dev_hash) = @_; my $SELF = $hash->{NAME}; my $name = $dev_hash->{NAME}; my $TYPE = $hash->{TYPE}; return if( !$init_done || IsDisabled($SELF) || IsDisabled($name) || $SELF eq $name # do not process own events ); my $events = deviceEvents($dev_hash, AttrVal($SELF, "addStateEvent", 0)); return unless($events); if($name eq "global" && "INITIALIZED" =~ m/\Q@{$events}\E/){ monitoring_setActive($hash); return; } my ($addRegex, $removeRegex) = split(/[\s]+/, InternalVal($SELF, "DEF", "")); return unless( $addRegex =~ m/^$name:/ || $removeRegex && $removeRegex =~ m/^$name:/ || $events ); my @blacklist; push(@blacklist, devspec2array($_)) foreach (split(/[\s]+/, AttrVal($SELF, "blacklist", ""))); return if(@blacklist && grep(/$name/, @blacklist)); my @whitelist; push(@whitelist, devspec2array($_)) foreach (split(/[\s]+/, AttrVal($SELF, "whitelist", ""))); return if(@whitelist && !(grep(/$name/, @whitelist))); foreach my $event (@{$events}){ next unless($event); my $addMatch = "$name:$event" =~ m/^$addRegex$/; my $removeMatch = $removeRegex ? "$name:$event" =~ m/^$removeRegex$/ : 0; next unless(defined($event) && ($addMatch || $removeMatch)); Log3($SELF, 4 , "$TYPE ($SELF) triggered by \"$name $event\""); foreach my $list ("error", "warning"){ my $listFuncAdd = AttrVal($SELF, $list."FuncAdd", "preset"); my $listFuncRemove = AttrVal($SELF, $list."FuncRemove", "preset"); my $listWait = eval(AttrVal($SELF, $list."Wait", 0)); $listWait = 0 unless(looks_like_number($listWait)); if($listFuncAdd eq "preset" && $listFuncRemove eq "preset"){ Log3( $SELF, 5, "$TYPE ($SELF) ". $list."FuncAdd and $list"."FuncRemove are preset" ); if(!$removeRegex){ if($listWait == 0){ Log3( $SELF, 2, "$TYPE ($SELF) ". "set \"$list"."Wait\" while \"$list". "FuncAdd\" and \"$list"."FuncRemove\" are same" ) if($list eq "error"); next; } Log3($SELF, 5, "$TYPE ($SELF) only addRegex is defined"); monitoring_modify("$SELF|$list|remove|$name"); monitoring_modify("$SELF|$list|add|$name|$listWait"); next; } else{ next unless($list eq "error" || AttrVal($SELF, "errorWait", undef)); Log3( $SELF, 5, "$TYPE ($SELF) ". "addRegex ($addRegex) and removeRegex ($removeRegex) are defined" ); monitoring_modify("$SELF|$list|remove|$name") if($removeMatch); monitoring_modify("$SELF|$list|add|$name|$listWait") if($addMatch); next; } } $listFuncAdd = 1 if($listFuncAdd eq "preset" && $addMatch); if(!$removeRegex){ Log3($SELF, 5, "$TYPE ($SELF) only addRegex is defined"); if($listFuncRemove eq "preset"){ if($listWait == 0){ Log3( $SELF, 2, "$TYPE ($SELF) ". "set \"$list"."Wait\" while \"$list". "FuncAdd\" and \"$list"."FuncRemove\" are same" ) if($list eq "error"); next; } $listFuncRemove = $listFuncAdd; } } else{ Log3( $SELF, 5, "$TYPE ($SELF) ". "addRegex ($addRegex) and removeRegex ($removeRegex) are defined" ); $listFuncRemove = 1 if($listFuncRemove eq "preset" && $removeMatch); } $listFuncAdd = eval($listFuncAdd) if($listFuncAdd =~ /^\{.*\}$/s); $listFuncRemove = eval($listFuncRemove) if($listFuncRemove =~ /^\{.*\}$/s); monitoring_modify("$SELF|$list|remove|$name") if($listFuncRemove && $listFuncRemove eq "1"); monitoring_modify("$SELF|$list|add|$name|$listWait") if($listFuncAdd && $listFuncAdd eq "1"); next; } } return; } # module Fn ################################################################### sub monitoring_modify($) { my ($SELF, $list, $operation, $value, $wait) = split("\\|", shift); my ($hash) = $defs{$SELF}; return unless(defined($hash)); return if(IsDisabled($SELF)); my $at = eval($wait + gettimeofday()) if($wait && $wait ne "quiet"); my $TYPE = $hash->{TYPE}; my (@change, %readings); %readings = map{$_, 1} split(",", ReadingsVal($SELF, $list, "")); my $arg = "$SELF|$list|$operation|$value"; my $reading = $list."Add_".$value; Log3( $SELF, 5 , "$TYPE ($SELF)". "\n entering monitoring_modify". "\n reading: $list". "\n operation: $operation". "\n value: $value". "\n at: ".($at ? FmtDateTime($at) : "now") ); if($operation eq "add"){ return if( $readings{$value} || ReadingsVal($SELF, "error", "") =~ m/(?:^|,)$value(?:,|$)/ ); if($at){ return if($hash->{READINGS}{$reading}); readingsSingleUpdate($hash, $reading, FmtDateTime($at), 0); InternalTimer($at, "monitoring_modify", $arg); return; } else{ monitoring_modify("$SELF|warning|remove|$value|quiet") if($list eq "error"); $readings{$value} = 1; delete $hash->{READINGS}{$reading}; } } elsif($operation eq "remove"){ push(@change, 1) if(delete $readings{$value}); delete $hash->{READINGS}{"$reading"}; } RemoveInternalTimer("$SELF|$list|add|$value"); return unless(@change || $operation eq "add"); my $allCount = int(keys %readings) + ReadingsNum($SELF, ($list eq "warning" ? "error" : "warning")."Count", 0) ; if ($operation eq "add") { my $name = $value; my $listFuncAdded = AttrVal($SELF, $list."FuncAdded", ""); $listFuncAdded = $listFuncAdded =~ /^\{.*\}$/s ? eval($listFuncAdded) : fhem($listFuncAdded); } readingsBeginUpdate($hash); readingsBulkUpdate($hash, "state", "$list $operation: $value"); readingsBulkUpdate($hash, $list, join(",", sort(keys %readings))); readingsBulkUpdate($hash, $list."Count", int(keys %readings)); readingsBulkUpdate($hash, "allCount", $allCount) unless($wait &&$wait eq "quiet"); readingsEndUpdate($hash, 1); return; } sub monitoring_NOTIFYDEV($) { my ($hash) = @_; my $SELF = $hash->{NAME}; my $NOTIFYDEV = AttrVal($SELF, "whitelist", undef) || join(",", (InternalVal($SELF, "DEF", undef) =~ m/(?:^|\s)([^:\s]+):/g)) ; $NOTIFYDEV =~ s/\s/,/g; notifyRegexpChanged($hash, $NOTIFYDEV); } sub monitoring_RemoveInternalTimer($) { my ($hash) = @_; my $SELF = $hash->{NAME}; foreach my $reading (sort(keys %{$hash->{READINGS}})){ RemoveInternalTimer("$SELF|$1|add|$2") if($reading =~ m/(error|warning)Add_(.+)/); } return; } sub monitoring_return($$) { my ($hash, $list) = @_; my $SELF = $hash->{NAME}; my @errors = split(",", ReadingsVal($SELF, "error", "")); my @warnings = split(",", ReadingsVal($SELF, "warning", "")); my $value = ReadingsVal($SELF, $list, undef); my $ret = AttrVal($SELF, $list."Return", undef); $ret = '"$list: $value"' if(!$ret && $value); return unless($ret); return eval($ret); } sub monitoring_setActive($) { my ($hash) = @_; my $SELF = $hash->{NAME}; my $TYPE = $hash->{TYPE}; readingsSingleUpdate($hash, "state", "active", 0); Log3($SELF, 3, "$TYPE ($SELF) set $SELF active"); foreach my $reading (reverse sort(keys %{$hash->{READINGS}})){ if($reading =~ m/(error|warning)Add_(.+)/){ my $wait = time_str2num(ReadingsVal($SELF, $reading, "")); next unless(looks_like_number($wait)); $wait -= gettimeofday(); if($wait > 0){ Log3($SELF, 4 , "$TYPE ($SELF) restore Timer \"$SELF|$1|add|$2\""); monitoring_modify("$SELF|$1|add|$2|$wait"); } else{ monitoring_modify("$SELF|$1|add|$2"); } } } AnalyzeCommandChain(undef, AttrVal($SELF, "setActiveFunc", "preset")); return; } sub monitoring_setInactive($) { my ($hash) = @_; my $SELF = $hash->{NAME}; my $TYPE = $hash->{TYPE}; AnalyzeCommandChain(undef, AttrVal($SELF, "setInactiveFunc", "preset")); return; } 1; # commandref ################################################################## =pod =item helper =item summary monitors devices towards events and stores them in two lists =item summary_DE überwacht Geräte auf Events und speichert diese in zwei Listen =begin html

monitoring

( en | de )
    Each monitoring has a warning and an error list, which are stored as readings.
    When a defined add-event occurs, the device is set to the warning list after a predefined time.
    After a further predefined time, the device is deleted from the warning list and set to the error list.
    If a defined remove-event occurs, the device is deleted from both lists and still running timers are canceled.
    This makes it easy to create group messages and send them formatted by two attributes.

    The following applications are possible and are described below:
    • opened windows
    • battery warnings
    • activity monitor
    • regular maintenance (for example changing the table water filter or cleaning rooms)
    • operating hours dependent maintenance (for example clean the Beamer filter)

    The monitor does not send a message by itself, a notify or DOIF is necessary, which responds to the event "<monitoring-name> error add: <name>" and then sends the return value of "get <monitoring-name> default".

    Define
      define <name> monitoring <add-event> [<remove-event>]
      The syntax for <add-event> and <remove-event> is the same as the pattern for notify (device-name or device-name:event).
      If only an <add-event> is defined, the device is deleted from both lists as it occurs and the timers for warning and error are started.

    Set
    • active
      Two things will happen:
      1. Restores pending timers, or sets the devices immediately to the corresponding list if the time is in the past.
      2. Executes the commands specified under the "setActiveFunc" attribute.
    • clear (warning|error|all)
      Removes all devices from the specified list and aborts timers for this list. With "all", all devices are removed from both lists and all running timers are aborted.
    • errorAdd <name>
      Add <name> to the error list.
    • errorRemove <name>
      Removes <name> from the error list.
    • inactive
      Two things will happen:
      1. Executes the commands specified under the "setInactiveFunc" attribute.
      2. 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.
    • warningAdd <name>
      Add <name> to the warning list.
    • warningRemove <name>
      Removes <name> from the warning list.

    Get
    • all
      Returns the error and warning list, separated by a blank line.
      The formatting can be set with the attributes "errorReturn" and "warningReturn".
    • default
      The "default" value can be set in the attribute "getDefault" and is intended to leave the configuration for the return value in the monitoring device. If nothing is specified "all" is used.
    • error
      Returns the error list.
      The formatting can be set with the attribute "errorReturn".
    • warning
      Returns the warning list.
      The formatting can be set with the attribute "warningReturn".

    Readings
    • allCount
      Displays the amount of devices on the warning and error list..
    • error
      Comma-separated list of devices.
    • errorAdd_<name>
      Displays the time when the device will be set to the error list.
    • errorCount
      Displays the amount of devices on the error list.
    • state
      Displays the status (active, inactive, or disabled). In "active" it displays which device added to which list or was removed from which list.
    • warning
      Comma-separated list of devices.
    • warningAdd_<name>
      Displays the time when the device will be set to the warning list.
    • warningCount
      Displays the amount of devices on the warning list.

    Attribute
    • addStateEvent
    • blacklist
      Space-separated list of devspecs which will be ignored.
      If the attribute is set all devices which are specified by the devspecs are removed from both lists.
    • disable (1|0)
      1: Executes the commands specified under the "setInactiveFunc" attribute and disables the monitoring.
              0: see "set active"
    • disabledForIntervals HH:MM-HH:MM HH:MM-HH-MM ...
    • errorFuncAdd {<perl code>}
      The following variables are available in this function:
      • $name
        Name of the event triggering device
      • $event
        Includes the complete event, e.g. measured-temp: 21.7 (Celsius)
      • $addMatch
        Has the value 1 if the add-event is true
      • $removeMatch
        Has the value 1 if the remove-event is true
      • $SELF
        Name of the monitoring
      If the function returns a 1, the device is set to the error list after the wait time.
      If the attribute is not set, it will be checked for $addMatch.
    • errorFuncAdded {<perl code>}
      The following variables are available in this function:
      • $name
        Name of the event triggering device
      • $SELF
        Name of the monitoring
      This function will be executed when a device is added to the error list.
    • errorFuncRemove {<perl code>}
      This function provides the same variables as for "errorFuncAdd".
      If the function returns a 1, the device is removed from the error list and still running timers are canceled.
      If the attribute is not set, it will be checked for $removeMatch if there is a <remove-event> in the DEF, otherwise it will be checked for errorFuncAdd.
    • errorWait <perl code>
      Wait until the device is set to the error list.
    • errorReturn {<perl code>}
      The following variables are available in this attribute:
      • @errors
        Array with all devices on the error list.
      • @warnings
        Array with all devices on the warning list.
      • $SELF
        Name of the monitoring
      With this attribute the output created with "get <name> error" can be formatted.
    • getDefault (all|error|warning)
      This attribute can be used to specify which list(s) are / are returned by "get <name> default". If the attribute is not set, "all" will be used.
    • setActiveFunc <Anweisung>
      The statement is one of the FHEM command types and is executed when you define the monitoring or "set active".
      For a battery message "trigger battery=low battery: low" can be useful.
    • warningFuncAdd {<perl code>}
      Like errorFuncAdd, just for the warning list.
    • warningFuncAdd {<perl code>}
      Like errorFuncAdded, just for the warning list.
    • warningFuncRemove {<perl code>}
      Like errorFuncRemove, just for the warning list.
    • warningWait <perl code>
      Like errorWait, just for the warning list.
    • warningReturn {<perl code>}
      Like errorReturn, just for the warning list.
    • whitelist {<perl code>}
      Space-separated list of devspecs which are allowed.
      If the attribute is set all devices which are not specified by the devspecs are removed from both lists.
    • readingFnAttributes

    Examples
      The following sample codes can be imported via "Raw definition".

    • Global, flexible opened windows/doors message (similar to those described in the forum)
      defmod Fenster_monitoring monitoring .*:(open|tilted) .*:closed
      attr Fenster_monitoring errorReturn {return unless(@errors);;\
       $_ = AttrVal($_, "alias", $_) foreach(@errors);;\
       return("Das Fenster \"$errors[0]\" ist schon länger geöffnet.") if(int(@errors) == 1);;\
       @errors = sort {lc($a) cmp lc($b)} @errors;;\
       return(join("\n - ", "Die folgenden ".@errors." Fenster sind schon länger geöffnet:", @errors))\
      }
      attr Fenster_monitoring errorWait {AttrVal($name, "winOpenTimer", 60*10)}
      attr Fenster_monitoring warningReturn {return unless(@warnings);;\
       $_ = AttrVal($_, "alias", $_) foreach(@warnings);;\
       return("Das Fenster \"$warnings[0]\" ist seit kurzem geöffnet.") if(int(@warnings) == 1);;\
       @warnings = sort {lc($a) cmp lc($b)} @warnings;;\
       return(join("\n - ", "Die folgenden ".@warnings." Fenster sind seit kurzem geöffnet:", @warnings))\
      }
      As soon as a device triggers an "open" or "tilded" event, the device is set to the warning list and a timer is started after which the device is moved from the warning to the error list. The waiting time can be set for each device via userattr "winOpenTimer". The default value is 10 minutes.
      As soon as a device triggers a "closed" event, the device is deleted from both lists and still running timers are stopped.

    • Battery monitoring
      defmod Batterie_monitoring monitoring .*:battery:.low .*:battery:.ok
      attr Batterie_monitoring errorReturn {return unless(@errors);;\
       $_ = AttrVal($_, "alias", $_) foreach(@errors);;\
       return("Bei dem Gerät \"$errors[0]\" muss die Batterie gewechselt werden.") if(int(@errors) == 1);;\
       @errors = sort {lc($a) cmp lc($b)} @errors;;\
       return(join("\n - ", "Die folgenden ".@errors." Geräten muss die Batterie gewechselt werden:", @errors))\
      }
      attr Batterie_monitoring errorWait 60*60*24*14
      attr Batterie_monitoring warningReturn {return unless(@warnings);;\
       $_ = AttrVal($_, "alias", $_) foreach(@warnings);;\
       return("Bei dem Gerät \"$warnings[0]\" muss die Batterie demnächst gewechselt werden.") if(int(@warnings) == 1);;\
       @warnings = sort {lc($a) cmp lc($b)} @warnings;;\
       return(join("\n - ", "Die folgenden ".@warnings." Geräten muss die Batterie demnächst gewechselt werden:", @warnings))\
      }
      As soon as a device triggers a "battery: low" event, the device is set to the warning list and a timer is started after which the device is moved from the warning to the error list. The waiting time is set to 14 days.
      As soon as a device triggers a "battery: ok" event, the device is deleted from both lists and still running timers are stopped.

    • Activity Monitor
      defmod Activity_monitoring monitoring .*:.*
      attr Activity_monitoring errorReturn {return unless(@errors);;\
       $_ = AttrVal($_, "alias", $_) foreach(@errors);;\
       return("Das Gerät \"$errors[0]\" hat sich seit mehr als 24 Stunden nicht mehr gemeldet.") if(int(@errors) == 1);;\
       @errors = sort {lc($a) cmp lc($b)} @errors;;\
       return(join("\n - ", "Die folgenden ".@errors." Geräten haben sich seit mehr als 24 Stunden nicht mehr gemeldet:", @errors))\
      }
      attr Activity_monitoring errorWait 60*60*24
      attr Activity_monitoring warningReturn {return unless(@warnings);;\
       $_ = AttrVal($_, "alias", $_) foreach(@warnings);;\
       return("Das Gerät \"$warnings[0]\" hat sich seit mehr als 12 Stunden nicht mehr gemeldet.") if(int(@warnings) == 1);;\
       @warnings = sort {lc($a) cmp lc($b)} @warnings;;\
       return(join("\n - ", "Die folgenden ".@warnings." Geräten haben sich seit mehr als 12 Stunden nicht mehr gemeldet:", @warnings))\
      }
      attr Activity_monitoring warningWait 60*60*12
      Devices are not monitored until they have triggered at least one event. If the device does not trigger another event in 12 hours, it will be set to the warning list. If the device does not trigger another event within 24 hours, it will be moved from the warning list to the error list.

      Note: It is recommended to use the whitelist attribute.

    • Regular maintenance (for example changing the table water filter)
      defmod Wasserfilter_monitoring monitoring Wasserfilter_DashButton:.*:.short
      attr Wasserfilter_monitoring errorReturn {return unless(@errors);;\
       return "Der Wasserfilter muss gewechselt werden.";;\
      }
      attr Wasserfilter_monitoring errorWait 60*60*24*30
      attr Wasserfilter_monitoring warningReturn {return unless(@warnings);;\
       return "Der Wasserfilter muss demnächst gewechselt werden.";;\
      }
      attr Wasserfilter_monitoring warningWait 60*60*24*25
      A DashButton is used to tell FHEM that the water filter has been changed.
      After 30 days, the DashButton is set to the error list.

    • Regular maintenance (for example cleaning rooms)
      defmod putzen_DashButton dash_dhcp
      attr putzen_DashButton allowed AC:63:BE:2E:19:AF,AC:63:BE:49:23:48,AC:63:BE:49:5E:FD,50:F5:DA:93:2B:EE,AC:63:BE:B2:07:78
      attr putzen_DashButton devAlias ac-63-be-2e-19-af:Badezimmer\
      ac-63-be-49-23-48:Küche\
      ac-63-be-49-5e-fd:Schlafzimmer\
      50-f5-da-93-2b-ee:Arbeitszimmer\
      ac-63-be-b2-07-78:Wohnzimmer
      attr putzen_DashButton event-min-interval .*:5
      attr putzen_DashButton port 6767
      attr putzen_DashButton userReadings state {return (split(":", @{$hash->{CHANGED}}[0]))[0];;}
      attr putzen_DashButton widgetOverride allowed:textField-long devAlias:textField-long
      
      defmod putzen_monitoring monitoring putzen_DashButton:.*:.short
      attr putzen_monitoring errorFuncAdd {$event =~ m/^(.+):/;;\
       $name = $1;;\
       return 1;;\
      }
      attr putzen_monitoring errorReturn {return unless(@errors);;\
       return("Der Raum \"$errors[0]\" muss wieder geputzt werden.") if(int(@errors) == 1);;\
       return(join("\n - ", "Die folgenden Räume müssen wieder geputzt werden:", @errors))\
      }
      attr putzen_monitoring errorWait 60*60*24*7
      Several DashButton are used to inform FHEM that the rooms have been cleaned.
      After 7 days, the room is set to the error list.
      However, the room name is not the device name but the readings name and is changed in the errorFuncAdd attribute.

    • Operating hours dependent maintenance (for example, clean the Beamer filter)
      defmod BeamerFilter_monitoring monitoring Beamer_HourCounter:pulseTimeOverall BeamerFilter_DashButton:.*:.short
      attr BeamerFilter_monitoring userattr errorInterval
      attr BeamerFilter_monitoring errorFuncAdd {return 1\
         if(ReadingsVal($name, "pulseTimeOverall", 0) >= \
              ReadingsVal($name, "pulseTimeService", 0)\
            + (AttrVal($SELF, "errorInterval", 0))\
            && $addMatch\
         );;\
       return;;\
      }
      attr BeamerFilter_monitoring errorFuncRemove {return unless($removeMatch);;\
       $name = "Beamer_HourCounter";;\
       fhem(\
          "setreading $name pulseTimeService "\
         .ReadingsVal($name, "pulseTimeOverall", 0)\
       );;\
       return 1;;\
      }
      attr BeamerFilter_monitoring errorInterval 60*60*200
      attr BeamerFilter_monitoring errorReturn {return unless(@errors);;\
       return "Der Filter vom Beamer muss gereinigt werden.";;\
      }
      attr BeamerFilter_monitoring warningFuncAdd {return}
      attr BeamerFilter_monitoring warningFuncRemove {return}
      An HourCounter is used to record the operating hours of a beamer and a DashButton to tell FHEM that the filter has been cleaned.
      If the filter has not been cleaned for more than 200 hours, the device is set to the error list.
      If cleaning is acknowledged with the DashButton, the device is removed from the error list and the current operating hours are stored in the HourCounter device.
=end html =begin html_DE

monitoring

( en | de )
    Jedes monitoring verfügt über eine warning- und eine error-Liste, welche als Readings gespeichert werden.
    Beim auftreten eines definierten add-events wird das Gerät nach einer vorgegeben Zeit auf die warning-Liste gesetzt.
    Nach einer weiteren vorgegeben Zeit wird das Gerät von der warning-Liste gelöscht und auf die error-Liste gesetzt.
    Beim auftreten eines definierten remove-events wird das Gerät von beiden Listen gelöscht und noch laufende Timer abgebrochen.
    Hiermit lassen sich auf einfache Weise Sammelmeldungen erstellen und durch zwei Attribute formatiert ausgeben.

    Folgende Anwendungen sind möglich und werden unten beschrieben:
    • geöffnete Fenster
    • Batterie Warnungen
    • Activity Monitor
    • regelmäßige Wartungsarbeiten (z.B. Tischwasserfilter wechseln oder Räume putzen)
    • Betriebsstunden abhängige Wartungsarbeiten (z.B. Beamer Filter reinigen)

    Das monitor sendet selbst keine Benachrichtung, hierfür ist ein notify oder DOIF notwendig, welches auf das Event "<monitoring-name> error add: <name>" reagiert und dann den Rückgabewert von "get <monitoring-name> default" versendet.

    Define
      define <name> mointoring <add-event> [<remove-event>]
      Die Syntax für <add-event> und <remove-event> ist die gleiche wie für das Suchmuster von notify (Gerätename oder Gerätename:Event).
      Ist nur ein <add-event> definiert wird beim auftreten das Gerät von beiden Listen gelöscht und die Timer für warning und error werden gestartet.

    Set
    • active
      Es passieren zwei Dinge:
      1. Stellt noch ausstehende Timer wieder her, bzw. setzt die Geräte sofort auf die entsprechende Liste, falls der Zeitpunkt in der Vergangenheit liegt.
      2. Führt die unter dem Attribut "setActiveFunc" angegeben Befehle aus.
    • clear (warning|error|all)
      Entfernt alle Geräte von der angegeben Liste und bricht für diese Liste laufende Timer ab. Bei "all" werden alle Geräte von beiden Listen entfernt und alle laufenden Timer abgebrochen.
    • errorAdd <name>
      Fügt <name> zu der error-Liste hinzu.
    • errorRemove <name>
      Entfernt <name> von der error-Liste.
    • inactive
      Deaktiviert das monitoring. Beachte den leichten semantischen Unterschied zum disable Attribut: "set inactive" wird bei einem shutdown automatisch in fhem.state gespeichert, es ist kein save notwendig.
    • warningAdd <name>
      Fügt <name> zu der warning-Liste hinzu.
    • warningRemove <name>
      Entfernt <name> von der warning-Liste.

    Get
    • all
      Gibt, durch eine Leerzeile getrennt, die error- und warning-Liste zurück.
      Die Formatierung kann dabei mit den Attributen "errorReturn" und "warningReturn" eingestellt werden.
    • default
      Der "default" Wert kann in dem Attribut "getDefault" festgelegt werden und ist dazu gedacht um die Konfiguration für den Rückgabewert im monitoring Gerät zu belassen. Wird nichts angegeben wird "all" verwendent.
    • error
      Gibt die error-Liste zurück.
      Die Formatierung kann dabei mit dem Attribut "errorReturn" eingestellt werden.
    • warning
      Gibt die warning-Liste zurück.
      Die Formatierung kann dabei mit dem Attribut "warningReturn" eingestellt werden.

    Readings
    • allCount
      Zeigt die Anzahl der Geräte in der warning- und error-Liste an.
    • error
      Durch Komma getrennte Liste von Geräten.
    • errorAdd_<name>
      Zeigt den Zeitpunkt an wann das Gerät auf die error-Liste gesetzt wird.
    • errorCount
      Zeigt die Anzahl der Geräte in der error-Liste an.
    • state
      Zeigt den Status (active, inactive oder disabled) an. Bei "active" wird angezeigt welches Gerät zu welcher Liste hinzugefügt bzw. von welcher Liste entfernt wurde.
    • warning
      Durch Komma getrennte Liste von Geräten.
    • warningAdd_<name>
      Zeigt den Zeitpunkt an wann das Gerät auf die warning-Liste gesetzt wird.
    • warningCount
      Zeigt die Anzahl der Geräte in der warning-Liste an.

    Attribute
    • addStateEvent
    • blacklist
      Durch Leerzeichen getrennte Liste von devspecs die ignoriert werden.
      Wenn das Attribut gesetzt wird werden alle Geräte die durch die devspecs definiert sind von beiden Listen gelöscht.
    • disable (1|0)
      1: Deaktiviert das monitoring.
      0: siehe "set active"
    • disabledForIntervals HH:MM-HH:MM HH:MM-HH-MM ...
    • errorFuncAdd {<perl code>}
      In dieser Funktion stehen die folgende Variablen zur Verfügung:
      • $name
        Name des Event auslösenden Gerätes
      • $event
        Beinhaltet das komplette Event, z.B. measured-temp: 21.7 (Celsius)
      • $addMatch
        Hat den Wert 1, falls das add-event zutrifft
      • $removeMatch
        Hat den Wert 1, falls das remove-event zutrifft
      • $SELF
        Eigenname des monitoring
      Gibt die Funktion eine 1 zurück, wird das Gerät, nach der Wartezeit, auf die error-Liste gesetzt.
      Wenn das Attribut nicht gesetzt ist wird auf $addMatch geprüft.
    • errorFuncAdded {<perl code>}
      In dieser Funktion stehen die folgende Variablen zur Verfügung:
      • $name
        Name des Event auslösenden Gerätes
      • $SELF
        Eigenname des monitoring
      Diese Funktion wird ausgeführt, wenn ein Gerät in die Fehlerliste aufgenommen wird.
    • errorFuncRemove {<perl code>}
      In dieser Funktion stehen die selben Variablen wie bei "errorFuncAdd" zur Verfügung.
      Gibt die Funktion eine 1 zurück, wird das Gerät von der error-Liste entfernt und noch laufende Timer werden abgebrochen.
      Wenn das Attribut nicht gesetzt ist wird bei einer DEF mit <remove-event> auf $removeMatch geprüft und bei einer DEF ohne <remove-event> auf errorFuncAdd.
    • errorWait <perl code>
      Wartezeit bis das Gerät auf die error-Liste gesetzt wird.
    • errorReturn {<perl code>}
      In diesem Attribut stehen folgende Variablen zur Verfügung:
      • @errors
        Array mit allen Geräten auf der error-Liste.
      • @warnings
        Array mit allen Geräten auf der warning-Liste.
      • $SELF
        Eigenname des monitoring
      Mit diesem Attribut kann die Ausgabe die mit "get <name> error" erzeugt wird angepasst werden.
    • getDefault (all|error|warning)
      Mit diesem Attribut kann festgelegt werden welche Liste/n mit "get <name> default" zurück gegeben wird/werden. Wenn das Attribut nicht gesetzt ist wird "all" verwendet.
    • setActiveFunc <Anweisung>
      Die Anweisung ist einer der FHEM Befehlstypen und wird beim definieren des monitoring oder bei "set active" ausgeführt.
      Für eine Batterie Meldung kann "trigger battery=low battery:low" sinnvoll sein.
    • warningFuncAdd {<perl code>}
      Wie errorFuncAdd, nur für die warning-Liste.
    • warningFuncAdded {<perl code>}
      Wie errorFuncAdded, nur für die warning-Liste.
    • warningFuncRemove {<perl code>}
      Wie errorFuncRemove, nur für die warning-Liste.
    • warningWait <perl code>
      Wie errorWait, nur für die warning-Liste.
    • warningReturn {<perl code>}
      Wie errorReturn, nur für die warning-Liste.
    • whitelist {<perl code>}
      Durch Leerzeichen getrennte Liste von devspecs die erlaubt sind werden.
      Wenn das Attribut gesetzt wird werden alle Geräte die nicht durch die devspecs definiert sind von beiden Listen gelöscht.
    • readingFnAttributes

    Beispiele
      Die folgenden beispiel Codes können per "Raw defnition" importiert werden.

    • Globale, flexible Fenster-/Tür-Offen-Meldungen (ähnlich wie im Forum beschrieben)
      defmod Fenster_monitoring monitoring .*:(open|tilted) .*:closed
      attr Fenster_monitoring errorReturn {return unless(@errors);;\
       $_ = AttrVal($_, "alias", $_) foreach(@errors);;\
       return("Das Fenster \"$errors[0]\" ist schon länger geöffnet.") if(int(@errors) == 1);;\
       @errors = sort {lc($a) cmp lc($b)} @errors;;\
       return(join("\n - ", "Die folgenden ".@errors." Fenster sind schon länger geöffnet:", @errors))\
      }
      attr Fenster_monitoring errorWait {AttrVal($name, "winOpenTimer", 60*10)}
      attr Fenster_monitoring warningReturn {return unless(@warnings);;\
       $_ = AttrVal($_, "alias", $_) foreach(@warnings);;\
       return("Das Fenster \"$warnings[0]\" ist seit kurzem geöffnet.") if(int(@warnings) == 1);;\
       @warnings = sort {lc($a) cmp lc($b)} @warnings;;\
       return(join("\n - ", "Die folgenden ".@warnings." Fenster sind seit kurzem geöffnet:", @warnings))\
      }
      Sobald ein Gerät ein "open" oder "tilded" Event auslöst wird das Gerät auf die warning-Liste gesetzt und es wird ein Timer gestartet nach dessen Ablauf das Gerät von der warning- auf die error-Liste verschoben wird. Die Wartezeit kann für jedes Gerät per userattr "winOpenTimer" festgelegt werden. Der Vorgabewert sind 10 Minuten.
      Sobald ein Gerät ein "closed" Event auslöst wird das Gerät von beiden Listen gelöscht und noch laufende Timer werden gestoppt.

    • Batterieüberwachung
      defmod Batterie_monitoring monitoring .*:battery:.low .*:battery:.ok
      attr Batterie_monitoring errorReturn {return unless(@errors);;\
       $_ = AttrVal($_, "alias", $_) foreach(@errors);;\
       return("Bei dem Gerät \"$errors[0]\" muss die Batterie gewechselt werden.") if(int(@errors) == 1);;\
       @errors = sort {lc($a) cmp lc($b)} @errors;;\
       return(join("\n - ", "Die folgenden ".@errors." Geräten muss die Batterie gewechselt werden:", @errors))\
      }
      attr Batterie_monitoring errorWait 60*60*24*14
      attr Batterie_monitoring warningReturn {return unless(@warnings);;\
       $_ = AttrVal($_, "alias", $_) foreach(@warnings);;\
       return("Bei dem Gerät \"$warnings[0]\" muss die Batterie demnächst gewechselt werden.") if(int(@warnings) == 1);;\
       @warnings = sort {lc($a) cmp lc($b)} @warnings;;\
       return(join("\n - ", "Die folgenden ".@warnings." Geräten muss die Batterie demnächst gewechselt werden:", @warnings))\
      }
      Sobald ein Gerät ein "battery: low" Event auslöst wird das Gerät auf die warning-Liste gesetzt und es wird ein Timer gestartet nach dessen Ablauf das Gerät von der warning- auf die error-Liste verschoben wird. Die Wartezeit ist auf 14 Tage eingestellt.
      Sobald ein Gerät ein "battery: ok" Event auslöst wird das Gerät von beiden Listen gelöscht und noch laufende Timer werden gestoppt.

    • Activity Monitor
      defmod Activity_monitoring monitoring .*:.*
      attr Activity_monitoring errorReturn {return unless(@errors);;\
       $_ = AttrVal($_, "alias", $_) foreach(@errors);;\
       return("Das Gerät \"$errors[0]\" hat sich seit mehr als 24 Stunden nicht mehr gemeldet.") if(int(@errors) == 1);;\
       @errors = sort {lc($a) cmp lc($b)} @errors;;\
       return(join("\n - ", "Die folgenden ".@errors." Geräten haben sich seit mehr als 24 Stunden nicht mehr gemeldet:", @errors))\
      }
      attr Activity_monitoring errorWait 60*60*24
      attr Activity_monitoring warningReturn {return unless(@warnings);;\
       $_ = AttrVal($_, "alias", $_) foreach(@warnings);;\
       return("Das Gerät \"$warnings[0]\" hat sich seit mehr als 12 Stunden nicht mehr gemeldet.") if(int(@warnings) == 1);;\
       @warnings = sort {lc($a) cmp lc($b)} @warnings;;\
       return(join("\n - ", "Die folgenden ".@warnings." Geräten haben sich seit mehr als 12 Stunden nicht mehr gemeldet:", @warnings))\
      }
      attr Activity_monitoring warningWait 60*60*12
      Geräte werden erst überwacht, wenn sie mindestens ein Event ausgelöst haben. Sollte das Gerät in 12 Stunden kein weiterer Event auslösen, wird es auf die warning-Liste gesetzt. Sollte das Gerät in 24 Stunden kein weiteres Event auslösen, wird es von der warning- auf die error-Liste verschoben.

      Hinweis: Es ist empfehlenswert das whitelist Attribut zu verwenden.

    • regelmäßige Wartungsarbeiten (z.B. Tischwasserfilter wechseln)
      defmod Wasserfilter_monitoring monitoring Wasserfilter_DashButton:.*:.short
      attr Wasserfilter_monitoring errorReturn {return unless(@errors);;\
       return "Der Wasserfilter muss gewechselt werden.";;\
      }
      attr Wasserfilter_monitoring errorWait 60*60*24*30
      attr Wasserfilter_monitoring warningReturn {return unless(@warnings);;\
       return "Der Wasserfilter muss demnächst gewechselt werden.";;\
      }
      attr Wasserfilter_monitoring warningWait 60*60*24*25
      Hierbei wird ein DashButton genutzt um FHEM mitzuteilen, dass der Wasserfilter gewechselt wurde.
      Nach 30 Tagen wird der DashButton auf die error-Liste gesetzt.

    • regelmäßige Wartungsarbeiten (z.B. Räume putzen)
      defmod putzen_DashButton dash_dhcp
      attr putzen_DashButton allowed AC:63:BE:2E:19:AF,AC:63:BE:49:23:48,AC:63:BE:49:5E:FD,50:F5:DA:93:2B:EE,AC:63:BE:B2:07:78
      attr putzen_DashButton devAlias ac-63-be-2e-19-af:Badezimmer\
      ac-63-be-49-23-48:Küche\
      ac-63-be-49-5e-fd:Schlafzimmer\
      50-f5-da-93-2b-ee:Arbeitszimmer\
      ac-63-be-b2-07-78:Wohnzimmer
      attr putzen_DashButton event-min-interval .*:5
      attr putzen_DashButton port 6767
      attr putzen_DashButton userReadings state {return (split(":", @{$hash->{CHANGED}}[0]))[0];;}
      attr putzen_DashButton widgetOverride allowed:textField-long devAlias:textField-long
      
      defmod putzen_monitoring monitoring putzen_DashButton:.*:.short
      attr putzen_monitoring errorFuncAdd {$event =~ m/^(.+):/;;\
       $name = $1;;\
       return 1;;\
      }
      attr putzen_monitoring errorReturn {return unless(@errors);;\
       return("Der Raum \"$errors[0]\" muss wieder geputzt werden.") if(int(@errors) == 1);;\
       return(join("\n - ", "Die folgenden Räume müssen wieder geputzt werden:", @errors))\
      }
      attr putzen_monitoring errorWait 60*60*24*7
      Hierbei werden mehrere DashButton genutzt um FHEM mitzuteilen, dass die Räume geputzt wurden.
      Nach 7 Tagen wird der Raum auf die error-Liste gesetzt.
      Der Raum Name ist hierbei jedoch nicht der Geräte-Name, sondern der Readings-Name und wird in dem errorFuncAdd-Attribut geändert.

    • Betriebsstunden abhängige Wartungsarbeiten (z.B. Beamer Filter reinigen)
      defmod BeamerFilter_monitoring monitoring Beamer_HourCounter:pulseTimeOverall BeamerFilter_DashButton:.*:.short
      attr BeamerFilter_monitoring userattr errorInterval
      attr BeamerFilter_monitoring errorFuncAdd {return 1\
         if(ReadingsVal($name, "pulseTimeOverall", 0) >= \
              ReadingsVal($name, "pulseTimeService", 0)\
            + (AttrVal($SELF, "errorInterval", 0))\
            && $addMatch\
         );;\
       return;;\
      }
      attr BeamerFilter_monitoring errorFuncRemove {return unless($removeMatch);;\
       $name = "Beamer_HourCounter";;\
       fhem(\
          "setreading $name pulseTimeService "\
         .ReadingsVal($name, "pulseTimeOverall", 0)\
       );;\
       return 1;;\
      }
      attr BeamerFilter_monitoring errorInterval 60*60*200
      attr BeamerFilter_monitoring errorReturn {return unless(@errors);;\
       return "Der Filter vom Beamer muss gereinigt werden.";;\
      }
      attr BeamerFilter_monitoring warningFuncAdd {return}
      attr BeamerFilter_monitoring warningFuncRemove {return}
      Hierbei wird ein HourCounter genutzt um die Betriebsstunden eine Beamer zu erfassen und ein DashButton um FHEM mitzuteilen, dass der Filter gereinigt wurde.
      Wurde der Filter länger als 200 Betriebsstunden nicht gereinigt wird das Gerät auf die error-Liste gesetzt.
      Wurde die Reinigung mit dem DashButton quittiert wird das Gerät von der error-Liste entfernt und der aktuelle Betriebsstunden-Stand in dem HourCounter Gerät gespeichert.
=end html_DE =cut