From 665d73bb26e04ad5d2573f0cffcbea416d956c1e Mon Sep 17 00:00:00 2001
From: markusbloch <>
Date: Mon, 16 Jan 2017 21:34:34 +0000
Subject: [PATCH] PRESENCE: new mode "event" (Forum: #40287) - new mode "event"
to determine presence state based on events of other FHEM definitions. - new
attributes "absenceTimeout" and "presenceTimeout" for absence/presence
verification in mode "event".
git-svn-id: https://svn.fhem.de/fhem/trunk@13115 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/CHANGED | 5 +
fhem/FHEM/73_PRESENCE.pm | 403 +++++++++++++++++++++++++++++++--------
2 files changed, 324 insertions(+), 84 deletions(-)
diff --git a/fhem/CHANGED b/fhem/CHANGED
index ca48cdb94..18061ad57 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,5 +1,10 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it.
+ - feature: 73_PRESENCE:
+ - new mode "event" to determine presence state based on events of
+ other FHEM definitions.
+ - new attributes "absenceTimeout" and "presenceTimeout" for
+ absence/presence verification in mode "event".
- feature: 11_OWDevice: new attribute cstrings, fix for trimvalues
- new: 98_powerMap: introducing new module to calculate power and
energy for every FHEM device w/o power meter
diff --git a/fhem/FHEM/73_PRESENCE.pm b/fhem/FHEM/73_PRESENCE.pm
index c3258dc9d..902ab8e14 100755
--- a/fhem/FHEM/73_PRESENCE.pm
+++ b/fhem/FHEM/73_PRESENCE.pm
@@ -54,6 +54,8 @@ PRESENCE_Initialize($)
"bluetooth_hci_device ".
"absenceThreshold:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 ".
"presenceThreshold:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 ".
+ "absenceTimeout ".
+ "presenceTimeout ".
"powerCmd ".$readingFnAttributes;
}
@@ -168,9 +170,26 @@ PRESENCE_Define($$)
$hash->{DeviceName} = $dev;
}
+ elsif($a[2] eq "event")
+ {
+ return "missing arguments for mode event. You need to provide two event regexp" unless(defined($a[4]));
+
+ eval { qr/^$a[3]$/ };
+ return "invalid absent regexp: $@" if($@);
+
+ eval { qr/^$a[4]$/ };
+ return "invalid present regexp: $@" if($@);
+
+ $hash->{MODE} = "event";
+ $hash->{EVENT_ABSENT} = $a[3];
+ $hash->{EVENT_PRESENT} = $a[4];
+ $hash->{STATE} = "Initialized";
+
+ InternalTimer(gettimeofday(), "PRESENCE_setNotfiyDev", $hash);
+ }
else
{
- my $msg = "unknown mode \"".$a[2]."\" in define statement: Please use lan-ping, lan-bluetooth, local-bluetooth, fritzbox, shellscript or function";
+ my $msg = "unknown mode \"".$a[2]."\" in define statement: Please use lan-ping, lan-bluetooth, local-bluetooth, fritzbox, shellscript, function or event";
Log 2, "PRESENCE ($name) - ".$msg;
return $msg
}
@@ -218,7 +237,6 @@ PRESENCE_Define($$)
return undef;
}
-
#####################################
sub
PRESENCE_Undef($$)
@@ -236,7 +254,7 @@ PRESENCE_Undef($$)
return undef;
}
-
+#####################################
sub
PRESENCE_Notify($$)
{
@@ -245,9 +263,13 @@ PRESENCE_Notify($$)
return undef if(!defined($hash) or !defined($dev));
my $name = $hash->{NAME};
+ my $dev_name = $dev->{NAME};
+
+ return undef if(!defined($dev_name) or !defined($name));
+
my $events = deviceEvents($dev,0);
- if($dev->{NAME} eq "global" and grep(m/^(?:DEFINED $name|MODIFIED $name|INITIALIZED|REREADCFG)$/, @{$events}))
+ if($dev_name eq "global" and grep(m/^(?:DEFINED $name|MODIFIED $name|INITIALIZED|REREADCFG)$/, @{$events}))
{
if($hash->{MODE} =~ /(lan-ping|local-bluetooth|fritzbox|shellscript|function)/)
{
@@ -262,9 +284,35 @@ PRESENCE_Notify($$)
return DevIo_OpenDev($hash, 0, "PRESENCE_DoInit");
}
}
+ elsif($hash->{MODE} eq "event")
+ {
+ return undef if($hash->{helper}{DISABLED});
+
+ my $re_present = $hash->{EVENT_PRESENT};
+ my $re_absent = $hash->{EVENT_ABSENT};
+
+ Log3 $name, 5, "PRESENCE ($name) - processing events from $dev_name";
+ foreach my $event (@{$events})
+ {
+ if($dev_name =~ m/^$re_present$/ or "$dev_name:$event" =~ m/^$re_present$/)
+ {
+ Log3 $name, 5, "PRESENCE ($name) - $dev_name:$event matched present regexp";
+ readingsBeginUpdate($hash);
+ PRESENCE_ProcessState($hash, "present");
+ readingsEndUpdate($hash, 1);
+ }
+ elsif($dev_name =~ m/^$re_absent$/ or "$dev_name:$event" =~ m/^$re_absent$/)
+ {
+ Log3 $name, 5, "PRESENCE ($name) - $dev_name:$event matched absent regexp";
+ readingsBeginUpdate($hash);
+ PRESENCE_ProcessState($hash, "absent");
+ readingsEndUpdate($hash, 1);
+ }
+ }
+ }
}
-
+#####################################
sub
PRESENCE_Set($@)
{
@@ -413,15 +461,35 @@ PRESENCE_Attr(@)
return "powerOnFn contains no value";
}
}
- elsif($a[0] eq "set" and $a[2] eq "absenceThreshold" and not $a[3] =~ /^\d+$/)
+ elsif($a[0] eq "set" and $a[2] eq "absenceThreshold")
{
- return "absenceThreshold must be a valid integer number";
+ return $a[2]." must be a valid integer number" if($a[3] !~ /^\d+$/);
+ return $a[2]." is not applicable for mode 'event'" if($hash->{MODE} eq "event");
}
- elsif($a[0] eq "set" and $a[2] eq "presenceThreshold" and not $a[3] =~ /^\d+$/)
+ elsif($a[0] eq "set" and $a[2] eq "presenceThreshold")
{
- return "presenceThreshold must be a valid integer number";
+ return $a[2]." must be a valid integer number" if($a[3] !~ /^\d+$/);
+ return $a[2]." is not applicable for mode 'event'" if($hash->{MODE} eq "event");
}
-
+ elsif($a[0] eq "set" and $a[2] eq "absenceTimeout")
+ {
+ return $a[2]." is only applicable for mode 'event'" if($hash->{MODE} ne "event");
+
+ if($a[3] !~ /^\d?\d(?::\d\d){0,2}$/)
+ {
+ return "not a valid time frame value. See commandref for the correct syntax.";
+ }
+ }
+ elsif($a[0] eq "set" and $a[2] eq "presenceTimeout")
+ {
+ return $a[2]." is only applicable for mode 'event'" if($hash->{MODE} ne "event");
+
+ if($a[3] !~ /^\d?\d(?::\d\d){0,2}$/)
+ {
+ return "not a valid time frame value. See commandref for the correct syntax.";
+ }
+ }
+
return undef;
}
@@ -524,26 +592,7 @@ PRESENCE_Read($)
readingsEndUpdate($hash, 1);
}
-sub
-PRESENCE_DoInit($)
-{
- my ($hash) = @_;
-
- if(not exists($hash->{helper}{DISABLED}) or (exists($hash->{helper}{DISABLED}) and $hash->{helper}{DISABLED} == 0))
- {
- readingsSingleUpdate($hash, "state", "active",0);
- $hash->{helper}{CURRENT_TIMEOUT} = "normal";
- DevIo_SimpleWrite($hash, $hash->{ADDRESS}."|".$hash->{TIMEOUT_NORMAL}."\n", 2);
- }
- else
- {
- readingsSingleUpdate($hash, "state", "disabled",0);
- }
-
- return undef;
-}
-
-
+#####################################
sub
PRESENCE_Ready($)
{
@@ -552,12 +601,14 @@ PRESENCE_Ready($)
return DevIo_OpenDev($hash, 1, "PRESENCE_DoInit") if($hash->{MODE} eq "lan-bluetooth");
}
+
##########################################################################################################################
#
-#
# Functions for local testing with Blocking.pm to ensure a smooth FHEM processing
#
-#
+##########################################################################################################################
+
+#####################################
sub PRESENCE_StartLocalScan($;$)
{
my ($hash, $local) = @_;
@@ -639,8 +690,8 @@ sub PRESENCE_StartLocalScan($;$)
}
}
-sub
-PRESENCE_DoLocalPingScan($)
+#####################################
+sub PRESENCE_DoLocalPingScan($)
{
my ($string) = @_;
@@ -704,8 +755,8 @@ PRESENCE_DoLocalPingScan($)
return $return;
}
-sub
-PRESENCE_ExecuteFritzBoxCMD($$)
+#####################################
+sub PRESENCE_ExecuteFritzBoxCMD($$)
{
my ($name, $cmd) = @_;
@@ -732,8 +783,8 @@ PRESENCE_ExecuteFritzBoxCMD($$)
return $status;
}
-sub
-PRESENCE_DoLocalFritzBoxScan($)
+#####################################
+sub PRESENCE_DoLocalFritzBoxScan($)
{
my ($string) = @_;
my ($name, $device, $local, $speedcheck) = split("\\|", $string);
@@ -838,9 +889,8 @@ PRESENCE_DoLocalFritzBoxScan($)
return ($status == 0 ? "$name|$local|absent" : "$name|$local|present").($number <= $max ? "|$number" : "|").($speedcheck == 1 and defined($speed) ? "|$speed" : "");
}
-
-sub
-PRESENCE_DoLocalBluetoothScan($)
+#####################################
+sub PRESENCE_DoLocalBluetoothScan($)
{
my ($string) = @_;
my ($name, $device, $local, $btdevice) = split("\\|", $string);
@@ -913,8 +963,8 @@ PRESENCE_DoLocalBluetoothScan($)
return $return;
}
-sub
-PRESENCE_DoLocalShellScriptScan($)
+#####################################
+sub PRESENCE_DoLocalShellScriptScan($)
{
my ($string) = @_;
@@ -957,9 +1007,8 @@ PRESENCE_DoLocalShellScriptScan($)
return $return;
}
-
-sub
-PRESENCE_DoLocalFunctionScan($)
+#####################################
+sub PRESENCE_DoLocalFunctionScan($)
{
my ($string) = @_;
@@ -1000,8 +1049,8 @@ PRESENCE_DoLocalFunctionScan($)
return $return;
}
-sub
-PRESENCE_ProcessLocalScan($)
+#####################################
+sub PRESENCE_ProcessLocalScan($)
{
my ($string) = @_;
@@ -1075,12 +1124,12 @@ PRESENCE_ProcessLocalScan($)
Log3 $hash->{NAME}, 4, "PRESENCE ($name) - rescheduling next check in $seconds seconds";
RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+$seconds, "PRESENCE_StartLocalScan", $hash, 0) unless($hash->{helper}{DISABLED});
+ InternalTimer(gettimeofday()+$seconds, "PRESENCE_StartLocalScan", $hash) unless($hash->{helper}{DISABLED});
}
}
-sub
-PRESENCE_ProcessAbortedScan($)
+#####################################
+sub PRESENCE_ProcessAbortedScan($)
{
my ($hash) = @_;
@@ -1113,55 +1162,188 @@ PRESENCE_ProcessAbortedScan($)
readingsSingleUpdate($hash, "state", "timeout",1);
}
-sub
-PRESENCE_ProcessState($$)
+##########################################################################################################################
+#
+# Helper Functions
+#
+##########################################################################################################################
+
+
+#####################################
+sub PRESENCE_DoInit($)
+{
+ my ($hash) = @_;
+
+ if(not exists($hash->{helper}{DISABLED}) or (exists($hash->{helper}{DISABLED}) and $hash->{helper}{DISABLED} == 0))
+ {
+ readingsSingleUpdate($hash, "state", "active",0);
+ $hash->{helper}{CURRENT_TIMEOUT} = "normal";
+ DevIo_SimpleWrite($hash, $hash->{ADDRESS}."|".$hash->{TIMEOUT_NORMAL}."\n", 2);
+ }
+ else
+ {
+ readingsSingleUpdate($hash, "state", "disabled",0);
+ }
+
+ return undef;
+}
+
+#####################################
+sub PRESENCE_calculateThreshold($)
+{
+ my ($value) = @_;
+
+ if(defined($value) and $value ne "")
+ {
+ if($value =~ /^(\d?\d):(\d\d)$/)
+ {
+ $value = $1 * 60 + $2;
+ }
+ elsif($value =~ /^(\d?\d):(\d\d):(\d\d)$/)
+ {
+ $value = $1 * 3600 + $2 * 60 + $3;
+ }
+ elsif($value !~ /^\d?\d+$/)
+ {
+ $value = 0;
+ }
+ }
+ else
+ {
+ $value = 0;
+ }
+
+ return $value;
+}
+
+#####################################
+sub PRESENCE_ThresholdTrigger($)
+{
+ my ($hash) = @_;
+
+ if($hash->{helper}{DISABLED})
+ {
+ delete($hash->{helper}{NEW_STATE});
+ return undef;
+ }
+
+ if($hash->{helper}{NEW_STATE})
+ {
+ readingsBeginUpdate($hash);
+ readingsBulkUpdateIfChanged($hash, "state", $hash->{helper}{NEW_STATE});
+ readingsBulkUpdateIfChanged($hash, "presence", $hash->{helper}{NEW_STATE});
+ readingsEndUpdate($hash, 1);
+
+ $hash->{helper}{CURRENT_STATE} = $hash->{helper}{NEW_STATE};
+
+ delete($hash->{helper}{NEW_STATE});
+ }
+}
+
+#####################################
+sub PRESENCE_ProcessState($$)
{
my ($hash, $state) = @_;
my $name = $hash->{NAME};
+
+ my $current_state = $hash->{helper}{CURRENT_STATE} ? $hash->{helper}{CURRENT_STATE} : "";
+ my $new_state = $hash->{helper}{NEW_STATE} ? $hash->{helper}{NEW_STATE} : "";
+
my $absenceThreshold = AttrVal($name, "absenceThreshold", 1);
my $presenceThreshold = AttrVal($name, "presenceThreshold", 1);
+ my $absenceTimeout = PRESENCE_calculateThreshold(AttrVal($name, "absenceTimeout", ""));
+ my $presenceTimeout = PRESENCE_calculateThreshold(AttrVal($name, "presenceTimeout", ""));
+
if($state eq "absent")
{
+ RemoveInternalTimer($hash, "PRESENCE_ThresholdTrigger");
+
my $count = ($hash->{helper}{ABSENT_COUNT} ? $hash->{helper}{ABSENT_COUNT} : 0);
- if(++$count >= $absenceThreshold)
+ if($hash->{MODE} eq "event")
{
- readingsBulkUpdate($hash, "state", "absent");
- readingsBulkUpdate($hash, "presence", "absent");
+ if($absenceTimeout > 0 and $current_state ne "absent" and $new_state ne "absent")
+ {
+ readingsBulkUpdate($hash, "state", "maybe absent");
+ readingsBulkUpdate($hash, "presence", "maybe absent");
+ $hash->{helper}{NEW_STATE} = "absent";
+ InternalTimer(gettimeofday()+$absenceTimeout, "PRESENCE_ThresholdTrigger", $hash);
+ }
+ else
+ {
+ readingsBulkUpdate($hash, "state", "absent");
+ readingsBulkUpdate($hash, "presence", "absent");
+
+ $hash->{helper}{CURRENT_STATE} = "absent";
+ delete($hash->{helper}{NEW_STATE});
+ }
}
else
{
- $hash->{helper}{ABSENT_COUNT} = $count;
-
- readingsBulkUpdate($hash, "state", "maybe absent");
- readingsBulkUpdate($hash, "presence", "maybe absent");
+ if(++$count >= $absenceThreshold)
+ {
+ readingsBulkUpdate($hash, "state", "absent");
+ readingsBulkUpdate($hash, "presence", "absent");
- Log3 $name, 4, "PRESENCE ($name) - device is absent after $count check".($count == 1 ? "" : "s").". ".($absenceThreshold-$count)." check".(($absenceThreshold-$count) == 1 ? "" : "s")." left before going absent";
+ }
+ else
+ {
+ $hash->{helper}{ABSENT_COUNT} = $count;
+
+ readingsBulkUpdate($hash, "state", "maybe absent");
+ readingsBulkUpdate($hash, "presence", "maybe absent");
+
+ Log3 $name, 4, "PRESENCE ($name) - device is absent after $count check".($count == 1 ? "" : "s").". ".($absenceThreshold-$count)." check".(($absenceThreshold-$count) == 1 ? "" : "s")." left before going absent";
+ }
}
- delete($hash->{helper}{PRESENT_COUNT}) if(exists($hash->{helper}{PRESENT_COUNT}));
+ delete($hash->{helper}{PRESENT_COUNT});
}
elsif($state eq "present")
{
+ RemoveInternalTimer($hash, "PRESENCE_ThresholdTrigger");
my $count = ($hash->{helper}{PRESENT_COUNT} ? $hash->{helper}{PRESENT_COUNT} : 0);
- if(++$count >= $presenceThreshold)
+ if($hash->{MODE} eq "event")
{
- readingsBulkUpdate($hash, "state", "present");
- readingsBulkUpdate($hash, "presence", "present");
+ if($presenceTimeout > 0 and $current_state ne "present" and $new_state ne "present")
+ {
+ readingsBulkUpdate($hash, "state", "maybe present");
+ readingsBulkUpdate($hash, "presence", "maybe present");
+ $hash->{helper}{NEW_STATE} = "present";
+ InternalTimer(gettimeofday()+$presenceTimeout, "PRESENCE_ThresholdTrigger", $hash);
+ }
+ else
+ {
+ readingsBulkUpdate($hash, "state", "present");
+ readingsBulkUpdate($hash, "presence", "present");
+
+ $hash->{helper}{CURRENT_STATE} = "present";
+ delete($hash->{helper}{NEW_STATE});
+ }
}
else
{
- $hash->{helper}{PRESENT_COUNT} = $count;
-
- readingsBulkUpdate($hash, "state", "maybe present");
- readingsBulkUpdate($hash, "presence", "maybe present");
+ if(++$count >= $presenceThreshold)
+ {
+ readingsBulkUpdate($hash, "state", "present");
+ readingsBulkUpdate($hash, "presence", "present");
+
+ $hash->{helper}{CURRENT_STATE} = "present";
+ }
+ else
+ {
+ $hash->{helper}{PRESENT_COUNT} = $count;
+
+ readingsBulkUpdate($hash, "state", "maybe present");
+ readingsBulkUpdate($hash, "presence", "maybe present");
- Log3 $name, 4, "PRESENCE ($name) - device is present after $count check".($count == 1 ? "" : "s").". ".($presenceThreshold-$count)." check".(($presenceThreshold-$count) == 1 ? "" : "s")." left before going present";
+ Log3 $name, 4, "PRESENCE ($name) - device is present after $count check".($count == 1 ? "" : "s").". ".($presenceThreshold-$count)." check".(($presenceThreshold-$count) == 1 ? "" : "s")." left before going present";
+ }
}
-
- delete($hash->{helper}{ABSENT_COUNT}) if(exists($hash->{helper}{ABSENT_COUNT}));
+
+ delete($hash->{helper}{ABSENT_COUNT});
}
else
{
@@ -1169,6 +1351,7 @@ PRESENCE_ProcessState($$)
}
}
+#####################################
sub PRESENCE_ProcessAddonData($$)
{
my ($hash, $data) = @_;
@@ -1182,6 +1365,15 @@ sub PRESENCE_ProcessAddonData($$)
return undef;
}
+
+#####################################
+sub PRESENCE_setNotfiyDev($)
+{
+ my ($hash) = @_;
+
+ notifyRegexpChanged($hash,"(global|".$hash->{EVENT_PRESENT}."|".$hash->{EVENT_ABSENT}.")");
+}
+
1;
=pod
@@ -1197,12 +1389,13 @@ sub PRESENCE_ProcessAddonData($$)
This module provides several operational modes to serve your needs. These are:
define iPhone PRESENCE shellscript "/opt/check_device.sh iPhone"
define <name> PRESENCE evemt <absent-regexp> <present-regexp>
define Presence_John PRESENCE event Door_Switch:off Door_Switch:on
define <name> PRESENCE lan-bluetooth <bluetooth-address> <ip-address>[:port] [ <check-interval> ]
define iPhone PRESENCE shellscript "/opt/check_device.sh iPhone"
define <name> PRESENCE event <Abwesend-Regexp> <Anwesend-Regexp>
define Anwesenheit PRESENCE event Tuerschalter:off Tuerschalter:on